Skip to content

Commit

Permalink
feat: update permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Shurtu-gal committed Sep 26, 2023
1 parent e08c33b commit 67b4998
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 29 deletions.
23 changes: 18 additions & 5 deletions server/schema/comment/comment.datasources.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ const CommentModel = require('./comment.model');

const findByID = () =>
new DataLoader(
async (ids) => {
async (data) => {
try {
const _comments = await CommentModel.find({ _id: ids });
const _comments = await CommentModel.find({ _id: { $in: data.map(({ id }) => id) } });

const _returnComments = ids.map((id) => _comments.find((_comment) => _comment.id.toString() === id.toString()));
const _returnComments = data.map(({ id, permission, mid }) => {
const _comment = _comments.find((comment) => comment._id.toString() === id.toString());
return _comment && _comment.approved ? _comment : permission || mid === _comment.createdBy ? _comment : null;
});
return _returnComments;
} catch (error) {
throw APIError(null, error);
Expand All @@ -21,15 +24,21 @@ const findByID = () =>
}
);

const findAll = (offset, limit) => CommentModel.find().sort({ createdAt: 'desc' }).skip(offset).limit(limit);
const findAll = (offset, limit, permission, mid) => {
// Get approved comments if the user does not have permission to read unapproved comments and the user is not the author
// Get all comments if the user has permission to read unapproved comments or the user is the author
const query = permission ? {} : { $or: [{ approved: true }, { approved: false, createdBy: mid }] };
return CommentModel.find(query).sort({ createdAt: 'desc' }).skip(offset).limit(limit);
};

const countNumberOfComments = (parentID, parentModel) =>
CommentModel.countDocuments({
'parent.reference': parentID,
'parent.model': parentModel,
approved: true,
});

const create = async (authorID, content, parentID, parentType, session, authToken, mid) => {
const create = async (authorID, content, parentID, parentType, session, authToken, mid, approved) => {
try {
const _author = await userModel.findById(authorID);
if (!_author) {
Expand All @@ -47,6 +56,7 @@ const create = async (authorID, content, parentID, parentType, session, authToke
reference: parentID,
model: parentType,
},
approved: approved || false,
createdBy: UserSession.valid(session, authToken) ? mid : null,
},
]);
Expand Down Expand Up @@ -74,13 +84,16 @@ const updateContent = async (id, content, session, authToken, mid) => {
}
};

const approve = (id) => CommentModel.findByIdAndUpdate(id, { approved: true }, { new: true });

const remove = (id) => CommentModel.findByIdAndDelete(id);

const CommentDataSources = () => ({
findAll,
findByID: findByID(),
countNumberOfComments,
create,
approve,
updateContent,
remove,
});
Expand Down
5 changes: 5 additions & 0 deletions server/schema/comment/comment.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const CommentSchema = new Schema(
trim: true,
},
},
approved: {
type: Boolean,
required: false,
default: false,
},
parent: {
model: {
type: String,
Expand Down
10 changes: 9 additions & 1 deletion server/schema/comment/comment.mutation.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const {
// GraphQLJSONObject,
} = require('../scalars');
const { CommentParentModelEmum } = require('./comment.enum.types');
const { createComment, deleteComment, updateCommentContent } = require('./comment.resolver');
const { createComment, deleteComment, updateCommentContent, approveComment } = require('./comment.resolver');

const CommentType = require('./comment.type');

Expand All @@ -39,6 +39,14 @@ module.exports = new GraphQLObjectType({
},
resolve: createComment,
},
approveComment: {
description: 'Approve a comment',
type: CommentType,
args: {
id: { type: new GraphQLNonNull(GraphQLID) },
},
resolve: approveComment,
},
updateCommentContent: {
description: 'Update Comment by Id',
type: CommentType,
Expand Down
9 changes: 5 additions & 4 deletions server/schema/comment/comment.query.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {
GraphQLNonNull,
// GraphQLError,
GraphQLList,
GraphQLString,
// GraphQLString,
GraphQLID,
// GraphQLBoolean,
GraphQLInt,
Expand All @@ -20,6 +20,7 @@ const {
// GraphQLJSON,
// GraphQLJSONObject,
} = require('../scalars');
const { CommentParentModelEmum } = require('./comment.enum.types');
const { getListOfComments, getCommentById, countOfComments } = require('./comment.resolver');

const CommentType = require('./comment.type');
Expand All @@ -36,11 +37,11 @@ module.exports = new GraphQLObjectType({
type: new GraphQLList(new GraphQLNonNull(GraphQLID)),
},
limit: {
description: 'No of Comments to be retrieved',
description: 'No. of Comments to be retrieved',
type: GraphQLInt,
},
offset: {
description: 'No of Comments to be skipped | pagination',
description: 'No. of Comments to be skipped | pagination',
type: GraphQLInt,
},
},
Expand All @@ -67,7 +68,7 @@ module.exports = new GraphQLObjectType({
},
parentType: {
description: 'Type of parent',
type: new GraphQLNonNull(GraphQLString),
type: new GraphQLNonNull(CommentParentModelEmum),
},
},
resolve: countOfComments,
Expand Down
74 changes: 58 additions & 16 deletions server/schema/comment/comment.resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,66 @@ const UserPermission = require('../../utils/userAuth/permission');
const DEF_LIMIT = 10,
DEF_OFFSET = 0;

const canMutateComment = async (session, authToken, decodedToken, id, mid, Comment) => {
const canMutateComment = async (session, authToken, decodedToken, id, mid, Comment, needsAdmin = false) => {
const _comment = await Comment.findByID.load(id);

if (!_comment) {
throw APIError('NOT FOUND', null, { reason: 'Requested comments were not found' });
}

if (
(_comment.author.reference !== mid ||
!UserPermission.exists(session, authToken, decodedToken, 'comment.write.new')) &&
!UserPermission.exists(session, authToken, decodedToken, 'comment.write.all')
// If the user is not the author of the comment or does not have permission to delete his/her own comment
_comment.createdBy !== mid ||
(!UserPermission.exists(session, authToken, decodedToken, 'comment.write.self') &&
// Furthermore, if the user is not an admin or does not have permission to delete other's comment
needsAdmin &&
!UserPermission.exists(session, authToken, decodedToken, 'comment.write.delete'))
) {
throw APIError('FORBIDDEN', null, 'User does not have required permission to update the comment');
}
};

const canReadUnApprovedComments = (session, authToken, decodedToken) => {
try {
if (!UserPermission.exists(session, authToken, decodedToken, 'comment.read.unapproved')) {
return false;
}

return true;
} catch (error) {
return false;
}
};

module.exports = {
getListOfComments: async (_parent, { ids = null, limit = DEF_LIMIT, offset = DEF_OFFSET }, { API: { Comment } }) => {
getListOfComments: async (
_parent,
{ ids = null, limit = DEF_LIMIT, offset = DEF_OFFSET },
{ session, authToken, decodedToken, mid, API: { Comment } }
) => {
try {
const permission = canReadUnApprovedComments(session, authToken, decodedToken);

const _comments = ids
? await Promise.all(ids.slice(offset, offset + limit).map((id) => Comment.findByID.load(id)))
: await Comment.findAll(offset, limit);
? // Gets approved and unapproved comments of the user if the user does not have permission to read unapproved comments
// Gets all comments if the user has permission to read unapproved comments
// Self comments are always returned regardless of permission to be handled by the client
await Promise.all(
ids.slice(offset, offset + limit).map((id) => Comment.findByID.load({ id, permission, mid }))
)
: await Comment.findAll(offset, limit, permission, mid);

return _comments.filter((comment) => comment);
} catch (error) {
throw APIError(null, error);
}
},
getCommentById: async (_parent, { id }, { API: { Comment } }) => {
getCommentById: async (_parent, { id }, { session, authToken, decodedToken, mid, API: { Comment } }) => {
try {
const _comment = await Comment.findByID.load(id);
// Gets only approved comments if the user does not have permission to read unapproved comments
const permission = canReadUnApprovedComments(session, authToken, decodedToken);

const _comment = await Comment.findByID.load({ id, permission, mid });
if (!_comment) {
throw APIError('NOT FOUND', null, { reason: 'Invalid id for comment' });
}
Expand All @@ -58,14 +87,14 @@ module.exports = {
{ session, authToken, decodedToken, mid, API: { Comment } }
) => {
try {
if (
!UserPermission.exists(session, authToken, decodedToken, 'comment.write.new') &&
!UserPermission.exists(session, authToken, decodedToken, 'comment.write.all')
) {
if (!UserPermission.exists(session, authToken, decodedToken, 'comment.write.new')) {
throw APIError('FORBIDDEN', null, 'User does not have required permission to create comment');
}

const _comment = await Comment.create(authorID, content, parentID, parentType, session, authToken, mid);
// User can write pre-approved comments if they have permission to write approved comments
const approved = UserPermission.exists(session, authToken, decodedToken, 'comment.write.approve');

const _comment = await Comment.create(authorID, content, parentID, parentType, session, authToken, mid, approved);

return _comment;
} catch (error) {
Expand All @@ -78,17 +107,30 @@ module.exports = {
{ session, authToken, decodedToken, mid, API: { Comment } }
) => {
try {
await canMutateComment(session, authToken, decodedToken, id, mid, Comment);
await canMutateComment(session, authToken, decodedToken, id, mid, Comment, false);
const _comment = await Comment.updateContent(id, content, session, authToken, mid);

return _comment;
} catch (error) {
throw APIError(null, error);
}
},
approveComment: async (_parent, { id }, { session, authToken, decodedToken, API: { Comment } }) => {
try {
if (!UserPermission.exists(session, authToken, decodedToken, 'comment.approve.all')) {
throw APIError('FORBIDDEN', null, 'User does not have required permission to approve comment');
}

const _comment = await Comment.approve(id);

return _comment;
} catch (error) {
throw APIError(null, error);
}
},
deleteComment: async (_parent, { id }, { session, authToken, decodedToken, mid, API: { Comment } }) => {
try {
await canMutateComment(session, authToken, decodedToken, id, mid, Comment);
await canMutateComment(session, authToken, decodedToken, id, mid, Comment, true);

const _comment = await Comment.remove(id);

Expand Down
6 changes: 4 additions & 2 deletions server/schema/comment/comment.type.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const {
// GraphQLList,
GraphQLString,
GraphQLID,
// GraphQLBoolean,
GraphQLBoolean,
GraphQLInt,
// GraphQLFloat,
// GraphQLDate,
Expand Down Expand Up @@ -71,6 +71,8 @@ const CommentType = new GraphQLObjectType({
type: AuthorType,
},

approved: { type: GraphQLBoolean },

parent: { type: ParentType },

createdAt: { type: GraphQLDateTime },
Expand All @@ -85,7 +87,7 @@ const ParentUniontype = new GraphQLUnionType({
name: 'ParentUnion',
description: 'Union of article and comment for parent of comment',
types: [ArticleType, CommentType],
resolveType: (value) => (value.categories ? ArticleType : CommentType),
resolveType: (value) => (value.categories ? 'Article' : 'Comment'),
});

module.exports = CommentType;
3 changes: 2 additions & 1 deletion server/utils/userAuth/role.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const UserRole = {
cache: async (_RoleModel = RoleModel) => {
try {
const _roles = await _RoleModel.find({}, 'name permissions section', { lean: true });
fs.writeFileSync('./roles.json', JSON.stringify(_roles));
// Write the roles to a file for caching with proper formatting
fs.writeFileSync('./roles.json', JSON.stringify(_roles, null, 2));
rolesCacheFile = fs.realpathSync('./roles.json');
return rolesCacheFile;
} catch (error) {
Expand Down

0 comments on commit 67b4998

Please sign in to comment.