From 8319b79e1f1e723302abc32797dfe8a01f8c2afe Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Sat, 12 Aug 2017 16:28:18 +0200 Subject: [PATCH] Add campaign model and endpoint --- api/config/api-url.js | 9 +++--- api/models/campaign.js | 18 +++++++++++ api/models/war.js | 4 +++ api/routes/campaigns.js | 43 +++++++++++++++++++++++++++ api/routes/wars.js | 36 ++++++++++++++++------ api/server.js | 3 ++ static/src/assets/award.png | Bin 0 -> 1366 bytes static/src/assets/loading.png | Bin 0 -> 7974 bytes static/src/assets/opt-logo-klein.png | Bin 0 -> 7847 bytes 9 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 api/models/campaign.js create mode 100644 api/routes/campaigns.js create mode 100644 static/src/assets/award.png create mode 100644 static/src/assets/loading.png create mode 100644 static/src/assets/opt-logo-klein.png diff --git a/api/config/api-url.js b/api/config/api-url.js index ee2bb53..4e68b70 100644 --- a/api/config/api-url.js +++ b/api/config/api-url.js @@ -1,18 +1,19 @@ const rootRoute = '/api'; module.exports = { + account: rootRoute + '/account', auth: rootRoute + '/authenticate', - signUp: rootRoute + '/authenticate/signup', awards: rootRoute + '/awardings', - command: rootRoute + '/cmd', + campaigns: rootRoute + '/campaigns', cmdCreateSig: rootRoute + '/cmd/createSignature', + command: rootRoute + '/cmd', decorations: rootRoute + '/decorations', overview: rootRoute + '/overview', ranks: rootRoute + '/ranks', + request: rootRoute + '/request', signatures: '/signatures', + signUp: rootRoute + '/authenticate/signup', squads: rootRoute + '/squads', users: rootRoute + '/users', - account: rootRoute + '/account', - request: rootRoute + '/request', wars: rootRoute + '/wars' }; diff --git a/api/models/campaign.js b/api/models/campaign.js new file mode 100644 index 0000000..405a0c1 --- /dev/null +++ b/api/models/campaign.js @@ -0,0 +1,18 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const CampaignSchema = new Schema({ + title: { + type: String, + required: true + } +}, { + collection: 'campaign', + timestamps: {createdAt: 'timestamp'} +}); +// optional more indices +CampaignSchema.index({timestamp: 1}); + +module.exports = mongoose.model('Campaign', CampaignSchema); diff --git a/api/models/war.js b/api/models/war.js index ad0cd43..b89bcae 100644 --- a/api/models/war.js +++ b/api/models/war.js @@ -35,6 +35,10 @@ const WarSchema = new Schema({ get: v => Math.round(v), set: v => Math.round(v), default: 0 + }, + campaign: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Campaign' } }, { collection: 'war', diff --git a/api/routes/campaigns.js b/api/routes/campaigns.js new file mode 100644 index 0000000..6864cc5 --- /dev/null +++ b/api/routes/campaigns.js @@ -0,0 +1,43 @@ +"use strict"; + +// modules +const express = require('express'); +const logger = require('debug')('cc:campaigns'); + +// HTTP status codes by name +const codes = require('./http-codes'); + +const routerHandling = require('../middleware/router-handling'); + +// Mongoose Model using mongoDB +const CampaignModel = require('../models/campaign'); + +const campaigns = express.Router(); + +// routes ********************** +campaigns.route('/') + + .post((req, res, next) => { + const campaign = new CampaignModel(req.body); + // timestamp and default are set automatically by Mongoose Schema Validation + campaign.save((err) => { + if (err) { + err.status = codes.wrongrequest; + err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); + return next(err); + } + res.status(codes.created); + res.locals.items = campaign; + next(); + }); + }) + + .all( + routerHandling.httpMethodNotAllowed + ); + +// this middleware function can be used, if you like or remove it +// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json +campaigns.use(routerHandling.emptyResponse); + +module.exports = campaigns; diff --git a/api/routes/wars.js b/api/routes/wars.js index 60a9206..e98ca43 100644 --- a/api/routes/wars.js +++ b/api/routes/wars.js @@ -18,6 +18,7 @@ const checkMT = require('../middleware/permission-check').checkMT; const routerHandling = require('../middleware/router-handling'); // Mongoose Model using mongoDB +const CampaignModel = require('../models/campaign'); const WarModel = require('../models/war'); const PlayerModel = require('../models/player'); @@ -26,20 +27,37 @@ const wars = express.Router(); // routes ********************** wars.route('/') .get((req, res, next) => { - const filter = {}; - WarModel.find(filter, {}, {sort: {date: 'desc'}}, (err, items) => { + let result = []; + CampaignModel.find({}, {}, {sort: {timestamp: 'desc'}}, (err, campaigns) => { if (err) { err.status = codes.servererror; return next(err); } - if (items) { - res.locals.items = items; - } else { - res.locals.items = []; + if (campaigns) { + WarModel.find({}, {}, {sort: {date: 'desc'}}, (err, wars) => { + if (err) { + err.status = codes.servererror; + return next(err); + } + if (wars) { + campaigns.forEach(campaign => { + let entry = {title: campaign.title, wars: []}; + wars.forEach((war) => { + if (String(campaign._id) === String(war.campaign)) { + entry.wars.push(war); + } + }); + result.push(entry); + }); + res.locals.items = result; + } + res.locals.processed = true; + next(); + } + ) + ; } - res.locals.processed = true; - next(); - }); + }) }) .post(apiAuthenticationMiddleware, checkMT, upload.single('log'), (req, res, next) => { diff --git a/api/server.js b/api/server.js index c51684f..d1d2ef2 100644 --- a/api/server.js +++ b/api/server.js @@ -15,6 +15,7 @@ const urls = require('./config/api-url'); const restAPIchecks = require('./middleware/request-checks.js'); const errorResponseWare = require('./middleware/error-response'); const apiAuthenticationMiddleware = require('./middleware/auth-middleware'); +const checkMT = require('./middleware/permission-check').checkMT; const checkAdmin = require('./middleware/permission-check').checkAdmin; const signatureCronJob = require('./cron-job/cron').cronJobSignature; const backupCronJob = require('./cron-job/cron').cronJobBackup; @@ -32,6 +33,7 @@ const awardingRouter = require('./routes/awardings'); const requestRouter = require('./routes/request'); const signatureRouter = require('./routes/signatures'); const commandRouter = require('./routes/command'); +const campaignRouter = require('./routes/campaigns'); const warRouter = require('./routes/wars'); // Configuration *********************************** @@ -79,6 +81,7 @@ app.use(urls.decorations, decorationRouter); app.use(urls.request, requestRouter); app.use(urls.awards, awardingRouter); app.use(urls.wars, warRouter); +app.use(urls.campaigns, apiAuthenticationMiddleware, checkMT, campaignRouter); app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter); app.use(urls.account, apiAuthenticationMiddleware, checkAdmin, accountRouter); diff --git a/static/src/assets/award.png b/static/src/assets/award.png new file mode 100644 index 0000000000000000000000000000000000000000..fbabac300093e8087d7051dab21d0b768c0995bd GIT binary patch literal 1366 zcmV-c1*!UpP)M800006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru;RO&03O2K`{PX|-1mj6W zK~z}7rB_{O990zlc9KkzoiW*6C%U`I*pyUO!?xP^5MlkB06G9H*le}|ybItD08g6W z*8u!oD_9NI)T3W1tjc!0==05Pmi4!ErL?XdxG|I)rMFOCzs%&UzprR-Qz?qpD9yoA--QC?J z`Djumk2!%nWyY8Qc&!nwl642HDrwN0JPMLRS5URwrKWD-gG{eJ4YzNeK%B9TYqS>#^{)Kvg1l}eOlnRRt_djj)#JPd_GynFYq zko|70`mX}GF*P*>Ns?wxaN0}$f9G%jv*8Zp`oDxw{G2X1&Tx>P!t8t&CM7eA4g;3t{Y~@ zska~i>bg#nJbwH*bzSF~GiNG>Ze(PHUayyld z+_`fM27~PG?&j&!rx}aI7>Pu9_Uu^(0s+p>&N83RQ`0m7GH>=VyKZGGtpRuk!1De3 z_u=#TaOch)tgNgc8jT_zk3&%uEH5vkP$;0cw->Qk4D0Lbc<|r>{C+=jxg5+?9|Dl8 zX0M;Khboy&GLy+Lo6Yj}?c2;|v&>{NOe7Lcp$*k~T&|lfLJ7b>W-)ad_&-x$GVOn< z6-X*0|Jep~vqpbM`W`@_j4lpf$$lZ)XxZslm7|>5sBBppp$(^|)m)un3F)@^9j^M8 zsBo^X1CRlHZthh%ySJ+WdaH^WSP$SE7n`4|0n54!@`GvDSH+~7+@3_+E(5%5u{T_7 zdn%=9vrF1;1NhPM|Eo)s`YM5qxIClmZ=@S8U>{UcuDFC{)@J`g75nCNT<}B{j?X&y zm0Wz@bPD6sE+P3>G%hy!&c*KyC$KRWr$5WcuekUPx>bz{>odyaPs$ATjLq(f*{!Ww zWV+qq8i1&6pieChtrcd!VcM+P0*;Id(VpW YKM<@C_X>WG0RR9107*qoM6N<$f*Q(tng9R* literal 0 HcmV?d00001 diff --git a/static/src/assets/loading.png b/static/src/assets/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..0e00d49a95b34e04bf30cfeee1779562886c756e GIT binary patch literal 7974 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%0A)EJBlwaMso{Egs(O&S&|`h z$$g#mu~r46=vfolCz)64z-LEL9$gRar5dFiHTS7?brwaIsI8)fuD{J{1FKtdO8dvw zj33{ZkS4pQmj%Z9Nlu?WmY=2BK=i;frrhICQ0zh@t;2cA?mO}B+DK33xIo9Dcj*y- z_6>l~4-lItxwb8rXBQ_T&+ZQ0+X_)7O?k1TqT~PBk}4$%|DO+~W$ozj{bFqG0vSV{ z`2@ zuJoW_snM-)nvimY6j_MVWLC6+p! zc5&ywFm7({!rq^vM>jqot^bumca~p}9AR=kU-X~4_?F0om3o7-FrtJwgS<)} z3_OW>Gd?ftgP()72kx}Y&)WxP>`yFDJj|)j9s*TX<=WTf#J!kyv55#v7O@oHa{?gK z|9dTief{`99m{ZopK}#iWO=>$r$4DOVm~V4K==ymf--~oY`C5e3yY;{a4lnr@F^hU z+R>`Hammi}k0}qjm>Efof;9U_4jPrMQrH~s{TTt29}&ijn04Q zE(@1T@^AhG|NVNsp08p#H*(Swk9S~IjO9x?qUZ<+I1+=Ehl2+Xt=s>hjYBT0=0&hp zW{H0Jp(dI}%l_w`x|Z`<4UBECt}akPlbil?U)E7nsp${pJg8qc_@kBz;g098#uA_q zc8dLRpi_y7-`+@!yMb$Xht&7ke`j#W#e|^xWFoZH0WCh{UrN+cKh;XnYKoG2F^87+ zbIQ10x#YKR6}{*4Q12J?Sl=p=^+)5>!X>ZZRXR>CiKc}}>eQ`-w}}mSpROyE+slrV zmun<&^6o-|&7mI|`urCz8ofLqajKL)P zV#vuZ#pk1bWE@>}bZTtNdDa=!sTaUca?aJWH)OX_r^mKSeau#4ar{2Bw7(l8 z5iff;+%F!3=5<}ITz$y=`glj-YC5^)#gdx5TAYL95(&>%lXW^?^v8c?;3nt%Z1rpx zS&s)}FUEJ~?O)S%%iW2(vY%fgh|DN{ELch_)y&K+Oep8+?y&qAt&jvCp~CXcW%qYD z&(X9De~O0IYzS9PixdXVJrSj&{Jq=0*R+Rm>%D4jZf5`FeYsaQ3=VP8V4i2_tyr8e z7WiCST6*5&B8jQ$3@_$~2twprWqz&vex|?*R+*CiM94$S9U-Sjot+sS!1YQ4R#XBX zzC?n8cG$mYQXEPiG!e{WeNS3ZT(LQqRT%a!<{hEzwfy9Cj+ErWg=yqu&ff1iTmfo} zkjmPWIaR_MWLmD-5T9Oa0`@F6eT{eqRZ{FD9b5NkA`xg01N6 zHA{VrdHm)}H}|ZUYp%zftyJ>eoP@6o5SCPaF9Xcf*x~lyzchCK6EXh?^6(Pze#G@w zolj>|rO+{di?<5kAjkO|_mSOpx3yZhwGhTeQD5BB;#%uHZ%|TFL6HSMcmF}{qTz~L zMjErxeM<+06LC>{Hfbz=-)Qf<0KRp0zS7awxh)#+B>25Kw>TGFP_W`EDSs@AxCDMn z6`o69ozxXxz?Qc~`J%z^k&?E$ZSp^anQ^bp*ae1*HYj|5iYM>HKIw8pySAdYGuvkc zLY&~JOm7P)W~;dZXaoN7GV-1DhskwRNYkXr2`XkgD( zcuDSksKDI-BW^49$-}Uoh5IBVSIq19`@s13@1_qbOxtAh4aZ4**%0T^X_|RQrFlo1 zPBmdPoFC>|yt|Cy=E)b&QP2~r5keK87KNNt#x}GO`4fQvaW6FADkvKaX>K-1tlqex Z;P{bJz7z;8A>3#Kx|)U>wQ3G;{tp6jUW@<$ literal 0 HcmV?d00001 diff --git a/static/src/assets/opt-logo-klein.png b/static/src/assets/opt-logo-klein.png new file mode 100644 index 0000000000000000000000000000000000000000..41f597d2641149b0b43a1504fe73572b1f6e6568 GIT binary patch literal 7847 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

jAjLta*X2$0Ky0X>KY^xpDrazK0YH@1 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�=Zx;h;8rVkmC?-WJR4dBT0&#&_yKD7VFv12ms@Oae^3dc-4 zRa<)^Q;PW0qocI36$h&l=iPeO69ol@Uje8mglGVSBuVPqwQE=SoR7IUb`vgQFviHv zoja*cr_*fxbjz%|x+CifN9V)|Jk`pIf{KIH%Gfc(ot>S&c0ps>xb3s;ZH(r}F_{b*`Zf_9=t z6!3>PKB<54#r5;=x#!MxcDtEoWDNag|NdPIR;^n5@SAVGaxEdD>7h^rf*|zWfBzHf z;^PzOx!1D7=PtX3DWz?c(!;VWw~QY@K729A`%jwu^D6M^r=JQdR;-w%s{VHX#>uiQ zYPBLb%3;bCS8b(~b8Xu6VaeRNx7|8*>U2g3A=~wmr@#NYiskKH<1@Jesb@jmzS3dE|)8P|NcEU z0VFyuEZPuB*6LAxLb z(EtPhp+_Ehc*ueU3!76@Qxh0t-w{F@&dD~)%gcE|5C%T~{1ae zRaHU=@k)}^Wipw1&*ui0$p4X-f&KgU3tq36XfzsCk|e6pXkv^;qe2Mj898!f|8C7SQ}V@E^~TH@h(Lm!bdw002ovPDHLk FV1o8&D((OP literal 0 HcmV?d00001