commit
17a4154dab
|
@ -22,7 +22,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const logger = require('debug')('me2:filterware');
|
const logger = require('debug')('middleware:filterware');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* private helper function to filter Objects by given keys
|
* private helper function to filter Objects by given keys
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/** This module defines a express.Router() instance
|
||||||
|
* - supporting offset=<number> and limit=<number>*
|
||||||
|
* - calls next with error if a impossible offset and/or limit value is given
|
||||||
|
*
|
||||||
|
* Note: it expects to be called BEFORE any data fetched from DB
|
||||||
|
* Note: it sets an object { limit: 0, skip: 0 } with the proper number values in req.locals.limitskip
|
||||||
|
* Note: it sets an Error-Object to next with error.status set to HTTP status code 400
|
||||||
|
*
|
||||||
|
* @author Johannes Konert
|
||||||
|
* @licence CC BY-SA 4.0
|
||||||
|
*
|
||||||
|
* @module restapi/limitoffset-middleware-mongo
|
||||||
|
* @type {Router}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// remember: in modules you have 3 variables given by CommonJS
|
||||||
|
// 1.) require() function
|
||||||
|
// 2.) module.exports
|
||||||
|
// 3.) exports (which is module.exports)
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const router = require('express').Router();
|
||||||
|
const logger = require('debug')('middleware:offsetlimit');
|
||||||
|
|
||||||
|
|
||||||
|
// the exported router with handler
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
let offset = undefined;
|
||||||
|
let limit = undefined;
|
||||||
|
const offsetString = req.query.offset;
|
||||||
|
const limitString = req.query.limit;
|
||||||
|
let err = null;
|
||||||
|
|
||||||
|
|
||||||
|
if (offsetString) {
|
||||||
|
if (!isNaN(offsetString)) {
|
||||||
|
offset = parseInt(offsetString);
|
||||||
|
if (offset < 0) {
|
||||||
|
err = new Error('offset is negative')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err = new Error('given offset is not a valid number ' + offsetString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (limitString) {
|
||||||
|
if (!isNaN(limitString)) {
|
||||||
|
limit = parseInt(limitString);
|
||||||
|
if (limit < 1) {
|
||||||
|
err = new Error('limit is zero or negative')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err = new Error('given limit is not a valid number ' + limitString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
logger('problem occurred with limit/offset values');
|
||||||
|
err.status = 400;
|
||||||
|
next(err)
|
||||||
|
} else {
|
||||||
|
res.locals.limitskip = {}; // mongoDB uses parameter object for skip/limit
|
||||||
|
if (limit) res.locals.limitskip.limit = limit;
|
||||||
|
if (offset) res.locals.limitskip.skip = offset;
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -10,89 +10,57 @@ const codes = require('./http-codes');
|
||||||
|
|
||||||
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
|
const apiAuthenticationMiddleware = require('../middleware/auth-middleware');
|
||||||
const checkHl = require('../middleware/permission-check').checkHl;
|
const checkHl = require('../middleware/permission-check').checkHl;
|
||||||
const sortCollectionBy = require('../middleware/util').sortCollection;
|
const offsetlimitMiddleware = require('../middleware/limitoffset-middleware-mongo');
|
||||||
|
const filterHandlerCreator = require('../middleware/filter-handler-mongo');
|
||||||
const routerHandling = require('../middleware/router-handling');
|
const routerHandling = require('../middleware/router-handling');
|
||||||
|
|
||||||
// Mongoose Model using mongoDB
|
// Mongoose Model using mongoDB
|
||||||
const UserModel = require('../models/user');
|
const UserModel = require('../models/user');
|
||||||
const RankModel = require('../models/rank');
|
const SquadModel = require('../models/squad');
|
||||||
const AwardingModel = require('../models/awarding');
|
const AwardingModel = require('../models/awarding');
|
||||||
|
|
||||||
const resultSet = {'__v': 0, 'updatedAt': 0, 'timestamp': 0};
|
|
||||||
|
|
||||||
|
|
||||||
const users = express.Router();
|
const users = express.Router();
|
||||||
|
|
||||||
|
users.get('/', filterHandlerCreator(UserModel.schema.paths));
|
||||||
|
users.get('/', offsetlimitMiddleware);
|
||||||
|
|
||||||
// routes **********************
|
// routes **********************
|
||||||
users.route('/')
|
users.route('/')
|
||||||
.get((req, res, next) => {
|
.get((req, res, next) => {
|
||||||
if (req.query.simple) {
|
const userQuery = () => {
|
||||||
UserModel.find({}, res.locals.filter, res.locals.limitskip, (err, items) => {
|
UserModel.find(dbFilter, res.locals.filter, res.locals.limitskip)
|
||||||
if (err) {
|
.populate('squadId')
|
||||||
err.status = codes.servererror;
|
.collation({locale: "en", strength: 2}) // case insensitive order
|
||||||
return next(err);
|
.sort('username').exec((err, users) => {
|
||||||
}
|
if (err) return next(err);
|
||||||
// if the collection is empty we do not send empty arrays back.
|
if (users.length === 0) {
|
||||||
|
res.locals.items = users;
|
||||||
res.locals.items = items;
|
res.locals.processed = true;
|
||||||
res.locals.processed = true;
|
return next();
|
||||||
next();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const nameQuery = req.query.q;
|
|
||||||
const fractionFilter = req.query.fractFilter;
|
|
||||||
const squadFilter = req.query.squadId;
|
|
||||||
|
|
||||||
UserModel.find({}, (err, users) => {
|
|
||||||
if (err) return next(err);
|
|
||||||
if (users.length === 0) {
|
|
||||||
res.locals.items = users;
|
|
||||||
res.locals.processed = true;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
let resUsers = [];
|
|
||||||
let rowsLength = users.length;
|
|
||||||
users.forEach((user) => {
|
|
||||||
// filter by name
|
|
||||||
if (!nameQuery || (nameQuery && user.username.toLowerCase().includes(nameQuery.toLowerCase()))) {
|
|
||||||
getExtendedUser(user, next, (extUser) => {
|
|
||||||
|
|
||||||
// filter by squad
|
|
||||||
if (squadFilter) {
|
|
||||||
if (extUser.squad && extUser.squad._id.toString() === squadFilter) {
|
|
||||||
resUsers.push(extUser);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rowsLength -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// filter by fraction
|
|
||||||
else if (!fractionFilter ||
|
|
||||||
(fractionFilter && extUser.squad && extUser.squad.fraction.toLowerCase() === fractionFilter) ||
|
|
||||||
(fractionFilter && fractionFilter === 'unassigned' && !extUser.squad)) {
|
|
||||||
resUsers.push(extUser);
|
|
||||||
} else {
|
|
||||||
rowsLength -= 1;
|
|
||||||
}
|
|
||||||
if (resUsers.length === rowsLength) {
|
|
||||||
resUsers = sortCollectionBy(resUsers, 'username');
|
|
||||||
res.locals.items = resUsers;
|
|
||||||
res.locals.processed = true;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
rowsLength -= 1;
|
|
||||||
// no user matching query - return empty []
|
|
||||||
if (rowsLength === 0) {
|
|
||||||
res.locals.items = resUsers;
|
|
||||||
res.locals.processed = true;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
UserModel.count(dbFilter, (err, totalCount) => {
|
||||||
|
res.set('x-total-count', totalCount);
|
||||||
|
res.locals.items = users;
|
||||||
|
res.locals.processed = true;
|
||||||
|
return next();
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!req.query.q) req.query.q = '';
|
||||||
|
const dbFilter = {username: {"$regex": req.query.q, "$options": "i"}};
|
||||||
|
if (req.query.squadId) dbFilter["squadId"] = {"$eq": req.query.squadId};
|
||||||
|
// squad / fraction filter setup
|
||||||
|
if (req.query.fractFilter && req.query.fractFilter !== 'UNASSIGNED' && !req.query.squadId) {
|
||||||
|
SquadModel.find({'fraction': req.query.fractFilter}, {_id: 1}, (err, squads) => {
|
||||||
|
dbFilter['squadId'] = {$in: squads.map(squad => squad.id)};
|
||||||
|
userQuery();
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
if (req.query.fractFilter === 'UNASSIGNED') {
|
||||||
|
dbFilter['squadId'] = {$eq: null};
|
||||||
|
}
|
||||||
|
userQuery();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -105,7 +73,8 @@ users.route('/')
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
res.status(codes.created);
|
res.status(codes.created);
|
||||||
getExtendedUser(user, next, (extUser) => {
|
|
||||||
|
UserModel.populate(user, {path: 'squadId'}, (err, extUser) => {
|
||||||
res.locals.items = extUser;
|
res.locals.items = extUser;
|
||||||
res.locals.processed = true;
|
res.locals.processed = true;
|
||||||
return next();
|
return next();
|
||||||
|
@ -118,26 +87,19 @@ users.route('/')
|
||||||
|
|
||||||
users.route('/:id')
|
users.route('/:id')
|
||||||
.get((req, res, next) => {
|
.get((req, res, next) => {
|
||||||
|
UserModel.findById(req.params.id).populate('squadId').exec((err, user) => {
|
||||||
UserModel.findById(req.params.id, (err, item) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
err.status = codes.servererror;
|
err.status = codes.servererror;
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
else if (!item) {
|
else if (!user) {
|
||||||
err = new Error("item not found");
|
err = new Error("item not found");
|
||||||
err.status = codes.notfound;
|
err.status = codes.notfound;
|
||||||
return next(err);
|
return next(err);
|
||||||
} else if (req.query.simple) {
|
|
||||||
res.locals.items = item;
|
|
||||||
next();
|
|
||||||
}
|
}
|
||||||
getExtendedUser(item, next, (extUser) => {
|
res.locals.items = user;
|
||||||
res.locals.items = extUser;
|
res.locals.processed = true;
|
||||||
res.locals.processed = true;
|
return next();
|
||||||
return next();
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -162,29 +124,21 @@ users.route('/:id')
|
||||||
else if (!item) {
|
else if (!item) {
|
||||||
err = new Error("item not found");
|
err = new Error("item not found");
|
||||||
err.status = codes.notfound;
|
err.status = codes.notfound;
|
||||||
} else if (req.query.simple) {
|
}
|
||||||
res.locals.items = item;
|
UserModel.populate(item, {path: 'squadId'}, (err, extUser) => {
|
||||||
|
if (err) {
|
||||||
|
err.status = codes.servererror;
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
if (!user) {
|
||||||
|
res.locals.items = {};
|
||||||
|
res.locals.processed = true;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
res.locals.items = extUser;
|
||||||
res.locals.processed = true;
|
res.locals.processed = true;
|
||||||
return next();
|
return next();
|
||||||
}
|
})
|
||||||
else {
|
|
||||||
UserModel.findById(item._id, (err, user) => {
|
|
||||||
if (err) {
|
|
||||||
err.status = codes.servererror;
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
if (!user) {
|
|
||||||
res.locals.items = {};
|
|
||||||
res.locals.processed = true;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
getExtendedUser(user, next, (extUser) => {
|
|
||||||
res.locals.items = extUser;
|
|
||||||
res.locals.processed = true;
|
|
||||||
return next();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -198,7 +152,7 @@ users.route('/:id')
|
||||||
return; // prevent node to process this function further after next() has finished.
|
return; // prevent node to process this function further after next() has finished.
|
||||||
}
|
}
|
||||||
// main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema
|
// main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema
|
||||||
var user = new UserModel(req.body);
|
const user = new UserModel(req.body);
|
||||||
UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) {
|
UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) {
|
||||||
// with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one.
|
// with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one.
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -210,7 +164,7 @@ users.route('/:id')
|
||||||
err.status = codes.notfound;
|
err.status = codes.notfound;
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
// optional task 3b: check that version is still accurate
|
// check that version is still accurate
|
||||||
else if (user.__v !== item.__v) {
|
else if (user.__v !== item.__v) {
|
||||||
err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again")
|
err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again")
|
||||||
err.status = codes.conflict;
|
err.status = codes.conflict;
|
||||||
|
@ -224,7 +178,7 @@ users.route('/:id')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional task 3: update updatedAt and increase version
|
// update updatedAt and increase version
|
||||||
item.updatedAt = new Date();
|
item.updatedAt = new Date();
|
||||||
item.increment(); // this sets __v++
|
item.increment(); // this sets __v++
|
||||||
item.save(function (err) {
|
item.save(function (err) {
|
||||||
|
@ -234,7 +188,8 @@ users.route('/:id')
|
||||||
err.status = codes.wrongrequest;
|
err.status = codes.wrongrequest;
|
||||||
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
|
err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors);
|
||||||
}
|
}
|
||||||
getExtendedUser(item, next, (extUser) => {
|
|
||||||
|
UserModel.populate(item, {path: 'squadId'}, (err, extUser) => {
|
||||||
res.locals.items = extUser;
|
res.locals.items = extUser;
|
||||||
res.locals.processed = true;
|
res.locals.processed = true;
|
||||||
return next();
|
return next();
|
||||||
|
@ -283,56 +238,4 @@ users.route('/:id')
|
||||||
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
|
// it looks for object(s) in res.locals.items and if they exist, they are send to the client as json
|
||||||
users.use(routerHandling.emptyResponse);
|
users.use(routerHandling.emptyResponse);
|
||||||
|
|
||||||
/**
|
|
||||||
* Create model for single extended user and
|
|
||||||
* return via callback
|
|
||||||
*/
|
|
||||||
let getExtendedUser = (user, next, callback) => {
|
|
||||||
let extUser = {};
|
|
||||||
UserModel.findById(user._id, resultSet)
|
|
||||||
.populate('squadId', resultSet).exec((err, member) => {
|
|
||||||
if (err) {
|
|
||||||
err.status = codes.servererror;
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
extUser._id = user._id;
|
|
||||||
extUser.username = user.username;
|
|
||||||
extUser.squad = member.squadId;
|
|
||||||
|
|
||||||
if (extUser.squad) {
|
|
||||||
RankModel.findOne({
|
|
||||||
level: member.rankLvl,
|
|
||||||
fraction: member.squadId.fraction
|
|
||||||
}, resultSet, (err, rank) => {
|
|
||||||
if (err) {
|
|
||||||
err.status = codes.servererror;
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
extUser.rank = rank;
|
|
||||||
}).then(() => {
|
|
||||||
addAwards(extUser).then(() => {
|
|
||||||
callback(extUser);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
extUser.rank = {level: user.rankLvl};
|
|
||||||
addAwards(extUser).then(() => {
|
|
||||||
callback(extUser);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let addAwards = (extUser) => {
|
|
||||||
return AwardingModel.find({userId: extUser._id}, resultSet, {sort: {date: 'desc'}})
|
|
||||||
.populate('decorationId', resultSet)
|
|
||||||
.exec((err, awards) => {
|
|
||||||
if (err) {
|
|
||||||
err.status = codes.servererror;
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
extUser.awards = awards;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = users;
|
module.exports = users;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "opt-cc",
|
"name": "opt-cc",
|
||||||
"version": "1.5.3",
|
"version": "1.5.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -15,8 +15,7 @@
|
||||||
"start-e2e": "npm run deploy-static && npm run e2e --prefix ./api",
|
"start-e2e": "npm run deploy-static && npm run e2e --prefix ./api",
|
||||||
"test-e2e": "npm run e2e --prefix ./static"
|
"test-e2e": "npm run e2e --prefix ./static"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^3.4.0",
|
"concurrently": "^3.4.0",
|
||||||
"wait-on": "^2.0.2"
|
"wait-on": "^2.0.2"
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"ngx-bootstrap": "^2.0.0-beta.6",
|
"ngx-bootstrap": "^2.0.0-beta.6",
|
||||||
"ngx-clipboard": "^8.1.0",
|
"ngx-clipboard": "^8.1.0",
|
||||||
"ngx-cookie-service": "^1.0.9",
|
"ngx-cookie-service": "^1.0.9",
|
||||||
|
"ngx-infinite-scroll": "^0.5.2",
|
||||||
"rxjs": "^5.2.0",
|
"rxjs": "^5.2.0",
|
||||||
"ts-helpers": "^1.1.1",
|
"ts-helpers": "^1.1.1",
|
||||||
"typescript": "^2.3.4",
|
"typescript": "^2.3.4",
|
||||||
|
|
|
@ -29,6 +29,15 @@ li {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version-label {
|
||||||
|
display:block;
|
||||||
|
position: fixed;
|
||||||
|
top: 32px;
|
||||||
|
left: 106px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7e7d64;
|
||||||
|
}
|
||||||
|
|
||||||
.unprocessed {
|
.unprocessed {
|
||||||
-webkit-animation-name: color-blink; /* Safari 4.0 - 8.0 */
|
-webkit-animation-name: color-blink; /* Safari 4.0 - 8.0 */
|
||||||
-webkit-animation-duration: 4s; /* Safari 4.0 - 8.0 */
|
-webkit-animation-duration: 4s; /* Safari 4.0 - 8.0 */
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<a class="navbar-brand" href="#" style="padding-top: 2px">
|
<a class="navbar-brand" href="#" style="padding-top: 2px">
|
||||||
<img height="40px" src="assets/opt-logo-klein.png">
|
<img height="40px" src="assets/opt-logo-klein.png">
|
||||||
</a>
|
</a>
|
||||||
|
<span class="version-label">{{version}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse navbar-collapse" id="mynavbar">
|
<div class="collapse navbar-collapse" id="mynavbar">
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {PromotionService} from "./services/promotion-service/promotion.service";
|
||||||
import {AwardingService} from "./services/awarding-service/awarding.service";
|
import {AwardingService} from "./services/awarding-service/awarding.service";
|
||||||
import {RouteConfig} from "./app.config";
|
import {RouteConfig} from "./app.config";
|
||||||
|
|
||||||
|
declare function require(url: string);
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: 'app.component.html',
|
templateUrl: 'app.component.html',
|
||||||
|
@ -16,7 +18,9 @@ export class AppComponent {
|
||||||
|
|
||||||
loading: boolean = false;
|
loading: boolean = false;
|
||||||
|
|
||||||
constructor(public loginService: LoginService,
|
version = 'v' + require('./../../../package.json').version;;
|
||||||
|
|
||||||
|
constructor(public loginService: LoginService,
|
||||||
private promotionService: PromotionService,
|
private promotionService: PromotionService,
|
||||||
private awardingService: AwardingService,
|
private awardingService: AwardingService,
|
||||||
private router: Router) {
|
private router: Router) {
|
||||||
|
@ -26,7 +30,9 @@ export class AppComponent {
|
||||||
}
|
}
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
window.scrollTo(0, 0);
|
// if (!router.url.includes('right')) {
|
||||||
|
// window.scrollTo(0, 0);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
<div class="army-member-view-container">
|
<div class="army-member-view-container">
|
||||||
<div class="return-button">
|
<div class="return-button">
|
||||||
<span class="btn btn-default" style="position:absolute;" (click)="backToOverview()">< zurück zur Übersicht</span>
|
<span class="btn btn-default" style="position:absolute;" (click)="backToOverview()">< zurück zur Übersicht</span>
|
||||||
<h3 class="text-center" [ngClass]="user.squad?.fraction === 'BLUFOR' ? 'blufor' : 'opfor'">
|
<h3 class="text-center" [ngClass]="user.squadId?.fraction === 'BLUFOR' ? 'blufor' : 'opfor'">
|
||||||
Auszeichnungen von {{user.rank?.name}} {{user.username}}
|
Auszeichnungen von {{user.username}}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img src="resource/signature/{{user._id}}.png">
|
<img src="{{signatureUrl}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group" style="width: 662px; margin: auto;">
|
<div class="input-group" style="width: 662px; margin: auto;">
|
||||||
<input type="text" style="background: white;" class="form-control" [(ngModel)]="signatureUrl" readonly>
|
<input type="text" style="background: white;" class="form-control" [(ngModel)]="signatureUrl" readonly>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<th class="col-sm-1 text-right" style="border-radius: 0 10px 0 0;">Verliehen am</th>
|
<th class="col-sm-1 text-right" style="border-radius: 0 10px 0 0;">Verliehen am</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngFor="let award of user.awards">
|
<tbody *ngFor="let award of awards">
|
||||||
<tr *ngIf="award.confirmed === 1" class="cell-outline">
|
<tr *ngIf="award.confirmed === 1" class="cell-outline">
|
||||||
<td class="text-center" *ngIf="award.decorationId.isMedal">
|
<td class="text-center" *ngIf="award.decorationId.isMedal">
|
||||||
<img height="90px" src="resource/decoration/{{award.decorationId._id}}.png">
|
<img height="90px" src="resource/decoration/{{award.decorationId._id}}.png">
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {Component} from "@angular/core";
|
import {Component} from "@angular/core";
|
||||||
import {User} from "../models/model-interfaces";
|
import {Award, User} from "../models/model-interfaces";
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
import {UserService} from "../services/user-service/user.service";
|
import {UserService} from "../services/user-service/user.service";
|
||||||
import {Subscription} from "rxjs/Subscription";
|
import {Subscription} from "rxjs/Subscription";
|
||||||
import {RouteConfig} from "../app.config";
|
import {RouteConfig} from "../app.config";
|
||||||
|
import {AwardingService} from "../services/awarding-service/awarding.service";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -17,13 +18,16 @@ export class ArmyMemberComponent {
|
||||||
|
|
||||||
user: User = {};
|
user: User = {};
|
||||||
|
|
||||||
|
awards: Award[] = [];
|
||||||
|
|
||||||
signatureUrl;
|
signatureUrl;
|
||||||
|
|
||||||
isCopied = false;
|
isCopied = false;
|
||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private userService: UserService) {
|
private userService: UserService,
|
||||||
|
private awardingService: AwardingService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -34,6 +38,9 @@ export class ArmyMemberComponent {
|
||||||
.subscribe(user => {
|
.subscribe(user => {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.signatureUrl = window.location.origin + '/resource/signature/' + user._id + '.png';
|
this.signatureUrl = window.location.origin + '/resource/signature/' + user._id + '.png';
|
||||||
|
this.awardingService.getUserAwardings(user._id).subscribe((awards => {
|
||||||
|
this.awards = awards;
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,8 @@ export interface User {
|
||||||
_id?: string;
|
_id?: string;
|
||||||
boardUserId?: number;
|
boardUserId?: number;
|
||||||
username?: string;
|
username?: string;
|
||||||
squad?: any; //Squad or id-string
|
squadId?: any; //Squad or id-string
|
||||||
|
rankLvl?: number;
|
||||||
rank?: Rank;
|
rank?: Rank;
|
||||||
awards?: Award[];
|
awards?: Award[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="user">Aktueller Rang</label>
|
<label for="user">Aktueller Rang</label>
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
[(ngModel)]="user.rank.name"
|
[(ngModel)]="selectedUserRank"
|
||||||
[ngModelOptions]="{standalone: true}"
|
[ngModelOptions]="{standalone: true}"
|
||||||
readonly>
|
readonly>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
*ngIf="showForm"
|
*ngIf="showForm"
|
||||||
(click)="addPromotion()"
|
(click)="addPromotion()"
|
||||||
class="btn btn-default"
|
class="btn btn-default"
|
||||||
[disabled]="newLevel === user.rank.level">
|
[disabled]="newLevel === user.rankLvl">
|
||||||
Bestätigen
|
Bestätigen
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ export class RequestPromotionComponent {
|
||||||
|
|
||||||
uncheckedPromotions = [];
|
uncheckedPromotions = [];
|
||||||
|
|
||||||
|
selectedUserRank;
|
||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
|
@ -56,7 +58,8 @@ export class RequestPromotionComponent {
|
||||||
toggleUser() {
|
toggleUser() {
|
||||||
if (this.user._id != '0') {
|
if (this.user._id != '0') {
|
||||||
this.showForm = true;
|
this.showForm = true;
|
||||||
this.newLevel = this.user.rank.level;
|
this.newLevel = this.user.rankLvl;
|
||||||
|
this.selectedUserRank = this.ranks.filter(rank => rank.level === this.user.rankLvl).map(rank => rank.name);
|
||||||
} else {
|
} else {
|
||||||
this.showForm = false;
|
this.showForm = false;
|
||||||
}
|
}
|
||||||
|
@ -66,20 +69,20 @@ export class RequestPromotionComponent {
|
||||||
addPromotion() {
|
addPromotion() {
|
||||||
const promotion = {
|
const promotion = {
|
||||||
"userId": this.user._id,
|
"userId": this.user._id,
|
||||||
"oldRankLvl": this.user.rank.level,
|
"oldRankLvl": this.user.rankLvl,
|
||||||
"newRankLvl": this.newLevel
|
"newRankLvl": this.newLevel
|
||||||
};
|
};
|
||||||
|
|
||||||
this.promotionService.requestPromotion(promotion).subscribe(() => {
|
this.promotionService.requestPromotion(promotion).subscribe(() => {
|
||||||
this.showSuccessLabel = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showSuccessLabel = false;
|
|
||||||
}, 2000);
|
|
||||||
this.showForm = false;
|
|
||||||
this.user = {};
|
|
||||||
let currentUser = this.loginService.getCurrentUser();
|
let currentUser = this.loginService.getCurrentUser();
|
||||||
this.promotionService.getSquadPromotions(currentUser.squad._id).subscribe(promotions => {
|
this.promotionService.getSquadPromotions(currentUser.squad._id).subscribe(promotions => {
|
||||||
this.uncheckedPromotions = promotions;
|
this.uncheckedPromotions = promotions;
|
||||||
|
this.showForm = false;
|
||||||
|
this.user = {_id: '0'};
|
||||||
|
this.showSuccessLabel = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showSuccessLabel = false;
|
||||||
|
}, 2000);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ export class UserStore {
|
||||||
case LOAD:
|
case LOAD:
|
||||||
return [...action.data];
|
return [...action.data];
|
||||||
case ADD:
|
case ADD:
|
||||||
|
if (action.data instanceof Array) {
|
||||||
|
return users.concat(action.data);
|
||||||
|
}
|
||||||
return [...users, action.data];
|
return [...users, action.data];
|
||||||
case EDIT:
|
case EDIT:
|
||||||
return users.map(user => {
|
return users.map(user => {
|
||||||
|
|
|
@ -11,13 +11,15 @@ export class UserService {
|
||||||
|
|
||||||
users$: Observable<User[]>;
|
users$: Observable<User[]>;
|
||||||
|
|
||||||
|
totalCount = 0;
|
||||||
|
|
||||||
constructor(private http: HttpClient,
|
constructor(private http: HttpClient,
|
||||||
private userStore: UserStore,
|
private userStore: UserStore,
|
||||||
private config: AppConfig) {
|
private config: AppConfig) {
|
||||||
this.users$ = userStore.items$;
|
this.users$ = userStore.items$;
|
||||||
}
|
}
|
||||||
|
|
||||||
findUsers(query = '', fractionFilter?, squadFilter?) {
|
findUsers(query = '', fractionFilter?, squadFilter?, limit?, offset?, action = LOAD) {
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
searchParams.append('q', query);
|
searchParams.append('q', query);
|
||||||
if (fractionFilter) {
|
if (fractionFilter) {
|
||||||
|
@ -26,12 +28,17 @@ export class UserService {
|
||||||
if (squadFilter) {
|
if (squadFilter) {
|
||||||
searchParams.append('squadId', squadFilter);
|
searchParams.append('squadId', squadFilter);
|
||||||
}
|
}
|
||||||
|
searchParams.append('limit', limit);
|
||||||
|
searchParams.append('offset', offset);
|
||||||
this.http.get(this.config.apiUserPath, searchParams)
|
this.http.get(this.config.apiUserPath, searchParams)
|
||||||
.map(res => res.json())
|
.do((res) => {
|
||||||
.do((users) => {
|
let headerCount = parseInt(res.headers.get('x-total-count'));
|
||||||
this.userStore.dispatch({type: LOAD, data: users});
|
if (headerCount) {
|
||||||
}).subscribe(_ => {
|
this.totalCount = headerCount;
|
||||||
});
|
}
|
||||||
|
}).map(res => res.json()).do((users) => {
|
||||||
|
this.userStore.dispatch({type: action, data: users});
|
||||||
|
}).subscribe(_ => {});
|
||||||
|
|
||||||
return this.users$;
|
return this.users$;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,13 @@
|
||||||
.table-container {
|
.table-container {
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
width: 50%;
|
width: 90%;
|
||||||
|
min-width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
width: 25%;
|
width: 30%;
|
||||||
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<select class="form-control"
|
<select class="form-control"
|
||||||
name="squad"
|
name="squad"
|
||||||
id="squad"
|
id="squad"
|
||||||
[(ngModel)]="user.squad"
|
[(ngModel)]="user.squadId"
|
||||||
[compareWith]="equals"
|
[compareWith]="equals"
|
||||||
(change)="toggleRanks()">
|
(change)="toggleRanks()">
|
||||||
<option [value]="0">Ohne Fraktion/ Squad</option>
|
<option [value]="0">Ohne Fraktion/ Squad</option>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<label for="rank">Rang</label>
|
<label for="rank">Rang</label>
|
||||||
<select class="form-control"
|
<select class="form-control"
|
||||||
name="rank"
|
name="rank"
|
||||||
id="rank" [ngModel]="user.rank?.level"
|
id="rank" [ngModel]="user.rankLvl"
|
||||||
#rankLevel
|
#rankLevel
|
||||||
style="min-width: 200px;">
|
style="min-width: 200px;">
|
||||||
<option *ngFor="let rank of ranks" [value]="rank.level">{{rank.name}}</option>
|
<option *ngFor="let rank of ranks" [value]="rank.level">{{rank.name}}</option>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export class EditUserComponent {
|
||||||
|
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
|
||||||
user: User = {username: '', squad: '0', rank: {level: 0}};
|
user: User = {username: '', squadId: '0', rankLvl: 0};
|
||||||
|
|
||||||
squads: Squad[] = [];
|
squads: Squad[] = [];
|
||||||
|
|
||||||
|
@ -46,11 +46,11 @@ export class EditUserComponent {
|
||||||
.filter(id => id != undefined)
|
.filter(id => id != undefined)
|
||||||
.flatMap(id => this.userService.getUser(id))
|
.flatMap(id => this.userService.getUser(id))
|
||||||
.subscribe(user => {
|
.subscribe(user => {
|
||||||
if (!user.squad) {
|
if (!user.squadId) {
|
||||||
user.squad = "0";
|
user.squadId = "0";
|
||||||
this.ranksDisplay = 'none';
|
this.ranksDisplay = 'none';
|
||||||
} else {
|
} else {
|
||||||
this.rankService.findRanks('', user.squad.fraction).subscribe(ranks => {
|
this.rankService.findRanks('', user.squadId.fraction).subscribe(ranks => {
|
||||||
this.ranks = ranks;
|
this.ranks = ranks;
|
||||||
this.ranksDisplay = 'block';
|
this.ranksDisplay = 'block';
|
||||||
});
|
});
|
||||||
|
@ -64,8 +64,8 @@ export class EditUserComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleRanks() {
|
toggleRanks() {
|
||||||
if (this.user.squad != '0') {
|
if (this.user.squadId != '0') {
|
||||||
this.rankService.findRanks('', this.user.squad.fraction).subscribe(
|
this.rankService.findRanks('', this.user.squadId.fraction).subscribe(
|
||||||
ranks => {
|
ranks => {
|
||||||
this.ranks = ranks;
|
this.ranks = ranks;
|
||||||
this.ranksDisplay = 'block';
|
this.ranksDisplay = 'block';
|
||||||
|
@ -83,8 +83,8 @@ export class EditUserComponent {
|
||||||
rankLvl: rankLevel,
|
rankLvl: rankLevel,
|
||||||
squadId: null
|
squadId: null
|
||||||
};
|
};
|
||||||
if (this.user.squad._id !== '0') {
|
if (this.user.squadId._id !== '0') {
|
||||||
updateObject.squadId = this.user.squad._id
|
updateObject.squadId = this.user.squadId._id
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.user._id) {
|
if (this.user._id) {
|
||||||
|
@ -107,7 +107,7 @@ export class EditUserComponent {
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
// duplicated user error message
|
// duplicated user error message
|
||||||
if (error._body.indexOf('duplicate') >= 0) {
|
if (error._body.includes('duplicate')) {
|
||||||
this.error = "Benutzername existiert bereits";
|
this.error = "Benutzername existiert bereits";
|
||||||
this.showErrorLabel = true;
|
this.showErrorLabel = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
<a>{{user.username}}</a>
|
<a>{{user.username}}</a>
|
||||||
</span>
|
</span>
|
||||||
<br>
|
<br>
|
||||||
<small *ngIf="user.squad && user.squad.fraction == 'OPFOR'">CSAT - {{user.squad.name}}</small>
|
<small *ngIf="user.squadId && user.squadId.fraction == 'OPFOR'">CSAT - {{user.squadId.name}}</small>
|
||||||
<small *ngIf="user.squad && user.squad.fraction == 'BLUFOR'">NATO - {{user.squad.name}}</small>
|
<small *ngIf="user.squadId && user.squadId.fraction == 'BLUFOR'">NATO - {{user.squadId.name}}</small>
|
||||||
<small *ngIf="!user.squad">ohne Squad/Fraktion</small>
|
<small *ngIf="!user.squadId">ohne Squad/Fraktion</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<div class="select-list">
|
<div class="select-list">
|
||||||
<div class="input-group list-header pull-left">
|
<div class="input-group list-header pull-left">
|
||||||
<div class="btn-group" (click)="filterUsers()">
|
<div class="btn-group" (click)="filterUsers()">
|
||||||
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="blufor" uncheckable>NATO</label>
|
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="BLUFOR" uncheckable>NATO</label>
|
||||||
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="opfor" uncheckable>CSAT</label>
|
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="OPFOR" uncheckable>CSAT</label>
|
||||||
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="unassigned" uncheckable>Ohne Squad</label>
|
<label class="btn btn-success" [(ngModel)]="radioModel" btnRadio="UNASSIGNED" uncheckable>Ohne Squad</label>
|
||||||
</div>
|
</div>
|
||||||
<a class="pull-right btn btn-success" (click)="openNewUserForm()">+</a>
|
<a class="pull-right btn btn-success" (click)="openNewUserForm()">+</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,12 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="search-results"
|
||||||
|
data-infinite-scroll
|
||||||
|
debounce
|
||||||
|
[infiniteScrollDistance]="scrollDistance"
|
||||||
|
[infiniteScrollThrottle]="throttle"
|
||||||
|
(scrolled)="onScrollDown()">
|
||||||
<pjm-user-item *ngFor="let user of users$ | async"
|
<pjm-user-item *ngFor="let user of users$ | async"
|
||||||
[user]="user"
|
[user]="user"
|
||||||
(userDelete)="deleteUser(user)"
|
(userDelete)="deleteUser(user)"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {ActivatedRoute, Router} from "@angular/router";
|
||||||
import {Observable} from "rxjs/Observable";
|
import {Observable} from "rxjs/Observable";
|
||||||
import {UserService} from "../../services/user-service/user.service";
|
import {UserService} from "../../services/user-service/user.service";
|
||||||
import {User} from "../../models/model-interfaces";
|
import {User} from "../../models/model-interfaces";
|
||||||
|
import {ADD, LOAD} from "../../services/stores/user.store";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'squad-list',
|
selector: 'squad-list',
|
||||||
|
@ -22,6 +23,14 @@ export class UserListComponent implements OnInit {
|
||||||
|
|
||||||
public radioModel: string;
|
public radioModel: string;
|
||||||
|
|
||||||
|
throttle = 300;
|
||||||
|
|
||||||
|
scrollDistance = 1;
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
limit = 20;
|
||||||
|
|
||||||
constructor(private userService: UserService,
|
constructor(private userService: UserService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -42,9 +51,8 @@ export class UserListComponent implements OnInit {
|
||||||
|
|
||||||
Observable.merge(paramsStream, searchTermStream)
|
Observable.merge(paramsStream, searchTermStream)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.switchMap(query => this.userService.findUsers(query, this.radioModel))
|
.switchMap(query => this.filterUsers())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openNewUserForm() {
|
openNewUserForm() {
|
||||||
|
@ -70,8 +78,23 @@ export class UserListComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filterUsers() {
|
filterUsers(action?) {
|
||||||
this.users$ = this.userService.findUsers(this.searchTerm.value, this.radioModel);
|
if (!action || action === LOAD) {
|
||||||
|
this.offset = 0;
|
||||||
|
this.limit = 20;
|
||||||
|
}
|
||||||
|
return this.users$ = this.userService.findUsers(this.searchTerm.value, this.radioModel,
|
||||||
|
null, this.limit, this.offset, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScrollDown() {
|
||||||
|
if (this.offset + this.limit > this.userService.totalCount) {
|
||||||
|
this.limit = this.userService.totalCount - this.offset;
|
||||||
|
}
|
||||||
|
if (this.limit != 0) {
|
||||||
|
this.offset += this.limit;
|
||||||
|
this.filterUsers(ADD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustBrowserUrl(queryString = '') {
|
adjustBrowserUrl(queryString = '') {
|
||||||
|
|
|
@ -3,10 +3,11 @@ import {usersRouterModule, usersRoutingComponents} from './users.routing';
|
||||||
import {CommonModule} from "@angular/common";
|
import {CommonModule} from "@angular/common";
|
||||||
import {SharedModule} from "../shared.module";
|
import {SharedModule} from "../shared.module";
|
||||||
import {ButtonsModule} from "ngx-bootstrap";
|
import {ButtonsModule} from "ngx-bootstrap";
|
||||||
|
import {InfiniteScrollModule} from "ngx-infinite-scroll";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: usersRoutingComponents,
|
declarations: usersRoutingComponents,
|
||||||
imports: [CommonModule, SharedModule, ButtonsModule.forRoot(), usersRouterModule],
|
imports: [CommonModule, SharedModule, ButtonsModule.forRoot(), InfiniteScrollModule, usersRouterModule],
|
||||||
})
|
})
|
||||||
export class UsersModule {
|
export class UsersModule {
|
||||||
static routes = usersRouterModule;
|
static routes = usersRouterModule;
|
||||||
|
|
Loading…
Reference in New Issue