Skip to content

Commit b00c041

Browse files
Merge pull request Human-Connection#19 from Human-Connection/develop
merge usersettings into master
2 parents 8668423 + a7318ac commit b00c041

29 files changed

+538
-105
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@
3333
"yarn": ">= 1.3.0"
3434
},
3535
"scripts": {
36-
"clear": "shx rm -Rf tmp",
36+
"clear": "rm -Rf tmp",
3737
"test": "npm run eslint && npm run mocha",
3838
"eslint": "eslint server/. test/. --config .eslintrc.json",
3939
"start": "concurrently 'mongod' 'wait-on tcp:27017 && NODE_ENV=production node server/'",
4040
"start:win": "concurrently \"mongod\" \"wait-on tcp:27017 &&SET NODE_ENV=production&& node server/\"",
4141
"dev:debug": "npm run clear && concurrently '$npm_package_config_mongoDev' 'wait-on tcp:27017 && NODE_ENV=development nodemon --inspect server/'",
4242
"dev": "npm run clear && concurrently '$npm_package_config_mongoDev' 'wait-on tcp:27017 && NODE_ENV=development DEBUG=feathers nodemon server/'",
4343
"dev:noseed": "concurrently 'mongod --dbpath data' 'wait-on tcp:27017 && NODE_ENV=development DEBUG=feathers nodemon server/'",
44-
"dev:win": "npm run clear && concurrently \"mongod --dbpath data\" \"wait-on tcp:27017&&SET NODE_ENV=development&&SET DEBUG=feathers&& nodemon --inspect server/\"",
44+
"dev:win": "npm run clear && concurrently \"mongod --dbpath /data/db\" \"wait-on tcp:27017&& cross-env NODE_ENV=development&& cross-env DEBUG=feathers&& nodemon --inspect server/\"",
4545
"mocha": "npm run clear && $npm_package_config_concurrently '$npm_package_config_mongoDev &>/dev/null' 'wait-on tcp:27017 && NODE_ENV=test $npm_package_config_mocha'",
4646
"coverage": "npm run clear && $npm_package_config_concurrently '$npm_package_config_mongoDev &>/dev/null' 'wait-on tcp:27017 && NODE_ENV=test istanbul cover $npm_package_config_mochaCoverage'"
4747
},
@@ -56,6 +56,7 @@
5656
"cheerio": "^1.0.0-rc.2",
5757
"compression": "~1.7.1",
5858
"cors": "~2.8.4",
59+
"cross-env": "^5.1.4",
5960
"crypto": "~1.0.1",
6061
"crypto-js": "^3.1.9-1",
6162
"dauria": "~2.0.0",

server/authentication.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,17 @@ module.exports = function () {
2424
]
2525
}
2626
});
27+
28+
app.on('login', (result, meta) => {
29+
try {
30+
if (meta.connection && meta.connection.user) {
31+
// update last active timestamp on loggedin user
32+
app.service('users').patch(meta.connection.user, {
33+
lastActiveAt: new Date()
34+
});
35+
}
36+
} catch (err) {
37+
app.error(err);
38+
}
39+
});
2740
};

server/helper/get-unique-slug.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
const getUniqueSlug = (service, slug, count) => {
1+
const getUniqueSlug = (service, slug, count, id) => {
22
return new Promise(resolve => {
3-
const testslug = count?slug+count:slug;
3+
const testSlug = count ? slug + count : slug;
44

55
// Test if we already have data with this slug
6+
const query = {
7+
slug: testSlug
8+
};
9+
// ignore entry with given id (if set)
10+
if (id) {
11+
query._id = {
12+
$ne: id
13+
};
14+
}
615
service.find({
7-
query: {
8-
slug: testslug
9-
}
16+
query
1017
}).then((result) => {
11-
if(result.data.length > 0) {
12-
count = count?count+1:1;
18+
if (result.data.length > 0) {
19+
count = count ? count + 1 : 1;
1320
resolve(getUniqueSlug(service, slug, count));
1421
} else {
15-
resolve(testslug);
22+
resolve(testSlug);
1623
}
1724
});
1825
});
1926
};
2027

21-
module.exports = getUniqueSlug;
28+
module.exports = getUniqueSlug;

server/helper/seed-helpers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ module.exports = {
7878
}
7979
return 'https://source.unsplash.com/daily?' + unsplashTopicsTmp.pop() + ',' + unsplashTopicsTmp.pop();
8080
},
81-
randomCategories: (seederstore, allowEmpty = true) => {
81+
randomCategories: (seederstore, allowEmpty = false) => {
8282
let count = Math.round(Math.random() * 3);
8383
if (allowEmpty === false && count === 0) {
8484
count = 1;

server/hooks/create-excerpt.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,14 @@ module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
2121
try {
2222
/* eslint no-use-before-define: 0 */ // --> OFF
2323
const content = sanitizeHtml(hook.data[options.field], sanitizeOptions)
24-
.replace(/\<br\>|\<\/br\>|\<\/ br\>|\<br\>|\<br\\\>/ig, "\n")
25-
.replace(/\<p\>\<br\>\<\/p\>/ig, ' ')
24+
.replace(/\<br\s*\>|\<br\s*\/\>/ig, "\n")
2625
.replace(/(\ ){2,}/ig, ' ')
2726
.trim();
2827
hook.data[`${options.field}Excerpt`] = trunc(content, options.length, {
2928
ignoreTags: ['img', 'script', 'iframe']
3029
}).html;
3130
} catch (err) {
32-
if (hook.data.teaserImg) {
33-
hook.data[`${options.field}Excerpt`] = '-----';
34-
} else {
35-
throw new Error('Text content needed!');
36-
}
31+
throw new Error('Text content needed!');
3732
}
3833
hook.data[options.field] = hook.data[options.field]
3934
.replace(/(\ ){2,}/ig, ' ')

server/hooks/create-slug.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
// https://www.npmjs.com/package/slug
22
const slug = require('slug');
33
const getUniqueSlug = require('../helper/get-unique-slug');
4-
const _ = require('lodash');
4+
const { isEmpty } = require('lodash');
55

6-
module.exports = function (options = { field: null }) {
6+
module.exports = function (options = { field: null, overwrite: false }) {
77
return function (hook) {
8-
if(!options.field || !hook.data[options.field]) return hook;
8+
if (!options.field || !hook.data[options.field]) return hook;
99

1010
// do not overwrite existing slug
11-
// TODO: we should make that possible and relying on ids for routing insead only on slugs
11+
// TODO: we should make that possible and relying on ids for routing instead only on slugs
1212
// the slug should be there for seo reasons but the id should be what counts
13-
if (!_.isEmpty(hook.data.slug)) return hook;
13+
if (!isEmpty(hook.data.slug) && options.overwrite !== true) return hook;
1414

1515
return new Promise(resolve => {
16-
17-
const titleslug = slug(hook.data[options.field], {
16+
const titleSlug = slug(hook.data[options.field], {
1817
lower: true
1918
});
20-
21-
getUniqueSlug(hook.service, titleslug).then((uniqueslug) => {
22-
hook.data.slug = uniqueslug;
23-
resolve(hook);
24-
});
19+
getUniqueSlug(hook.service, titleSlug, null, hook.id)
20+
.then((uniqueSlug) => {
21+
hook.data.slug = uniqueSlug;
22+
resolve(hook);
23+
});
2524
});
2625
};
2726
};

server/hooks/is-moderator-boolean.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Check if user is moderator
22
module.exports = () => hook => {
3-
if(!hook.params || !hook.params.user || !['admin','moderator'].includes(hook.params.user.role)) {
3+
if(!hook.params || !hook.params.user) {
44
return false;
55
}
6-
return true;
6+
return ['admin', 'moderator'].includes(hook.params.user.role);
77
};

server/hooks/map-create-to-upsert.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// upsert if needed
2+
// https://blog.feathersjs.com/how-to-use-upsert-for-better-performance-with-feathers-mongoose-ec40e45d0d4a
3+
4+
module.exports = function (upsertQuery) {
5+
if (typeof upsertQuery !== 'function') {
6+
throw new Error('No `upsertQuery` function was passed to the mapCreateToUpsert hook. Please set params.upsertQuery in the hook context to dynamically declare the function.');
7+
}
8+
9+
return function mapCreateToUpsert (context) {
10+
const { service, data, params } = context; // const data = { address: '123', identifier: 'my-identifier' }
11+
12+
upsertQuery = params.upsertQuery || upsertQuery;
13+
if (typeof upsertQuery !== 'function') {
14+
throw new Error('you must pass a `upsertQuery` function to the mapCreateToUpsert hook in the options or as `params.upsertQuery` in the hook context');
15+
}
16+
17+
params.mongoose = Object.assign({}, params.mongoose, { upsert: true });
18+
params.query = upsertQuery(context); // { address: '123' }
19+
20+
return service.patch(null, data, params)
21+
.then(result => {
22+
context.result = result;
23+
return context;
24+
});
25+
};
26+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const { getByDot, deleteByDot } = require('feathers-hooks-common');
2+
3+
module.exports = function restrictReviewAndEnableChange () { // eslint-disable-line no-unused-vars
4+
return (hook) => {
5+
6+
if (!getByDot(hook, 'params.before')) {
7+
throw new Error('The "restrictReviewAndEnableChange" hook should be used after the "stashBefore()" hook');
8+
}
9+
10+
const role = getByDot(hook, 'params.user.role');
11+
const isModOrAdmin = role && ['admin', 'moderator'].includes(role);
12+
const isReviewed = getByDot(hook, 'params.before.reviewedBy');
13+
const userId = getByDot(hook, 'params.user._id');
14+
const ownerId = getByDot(hook, 'params.before.userId');
15+
const isOwner = userId && ownerId && ownerId.toString() === userId.toString();
16+
17+
// only allow mods and admins to change the review status
18+
if (!isModOrAdmin) {
19+
deleteByDot(hook.data, 'isReviewed');
20+
}
21+
22+
// set reviewedBy to current user if the user has mod rights
23+
// and wants to confirm the review status
24+
deleteByDot(hook.data, 'reviewedBy');
25+
if (hook.data.isReviewed) {
26+
hook.data.reviewedBy = userId;
27+
}
28+
29+
// only allow changes to mods, admin and owners (if its already reviewed)
30+
if (!isModOrAdmin && (!isOwner || (isOwner && !isReviewed))) {
31+
deleteByDot(hook.data, 'isEnabled');
32+
}
33+
34+
return hook;
35+
};
36+
};
37+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const { getByDot } = require('feathers-hooks-common');
2+
const errors = require('feathers-errors');
3+
4+
module.exports = function restrictToOwnerOrModerator (query = {}) { // eslint-disable-line no-unused-vars
5+
return function (hook) {
6+
if (hook.type !== 'before') {
7+
throw new Error('The "restrictToOwnerOrModerator" hook should only be used as a "before" hook.');
8+
}
9+
const isFindOrGet = ['find', 'get'].includes(hook.method);
10+
if (!isFindOrGet && !getByDot(hook, 'params.before')) {
11+
throw new Error('The "restrictToOwnerOrModerator" hook should be used after the "stashBefore()" hook');
12+
}
13+
14+
if (!hook.params || !hook.params.user) {
15+
return false;
16+
}
17+
18+
const role = getByDot(hook, 'params.user.role');
19+
const isModOrAdmin = role && ['admin', 'moderator'].includes(role);
20+
21+
const userId = getByDot(hook, 'params.user._id');
22+
const ownerId = getByDot(hook, 'params.before.userId');
23+
const isOwner = userId && ownerId && ownerId.toString() === userId.toString();
24+
25+
// allow for mods or admins
26+
if (isModOrAdmin) {
27+
return hook;
28+
}
29+
30+
// change the query if the method is find or get
31+
if (isFindOrGet) {
32+
// restrict to owner or given query
33+
const restrictedQuery = {
34+
$or: [
35+
{ userId },
36+
{ ...query }
37+
]
38+
};
39+
hook.params.query = Object.assign(hook.params.query, restrictedQuery);
40+
return hook;
41+
}
42+
43+
let hasError = false;
44+
const keys = Object.keys(query);
45+
if (!isOwner && keys.length) {
46+
keys.forEach((key) => {
47+
if (query[key] !== getByDot(hook, `params.before.${key}`)) {
48+
hasError = true;
49+
}
50+
});
51+
if (hasError) {
52+
// if any of the given query params is not identical with the current values this action is forbidden
53+
throw new errors.Forbidden('You can\'t alter this record!');
54+
}
55+
} else if (!isOwner) {
56+
// his action is forbidden if its not the owner
57+
throw new errors.Forbidden('You can\'t alter this record!');
58+
}
59+
60+
return hook;
61+
};
62+
};
63+

0 commit comments

Comments
 (0)