UNPKG

@wepublish/api-db-mongodb

Version:

We.publish Database adapter for mongoDB

322 lines (277 loc) 7.86 kB
import { DBCommentAdapter, AddPublicCommentArgs, UpdatePublicCommentArgs, TakeActionOnCommentArgs, OptionalComment, Comment, GetCommentsArgs, GetPublicCommentsArgs, ConnectionResult, CommentState, LimitType, InputCursorType, CommentSort, SortOrder, PublicComment, OptionalPublicComment } from '@wepublish/api' import {Collection, Db, FilterQuery, MongoCountPreferences} from 'mongodb' import {Cursor} from './cursor' import {MaxResultsPerPage} from './defaults' import {CollectionName, DBComment} from './schema' function commentSortFieldForSort(sort: CommentSort) { switch (sort) { case CommentSort.CreatedAt: return 'createdAt' case CommentSort.ModifiedAt: return 'modifiedAt' } } function commentDateForSort(comment: DBComment, sort: CommentSort): Date { switch (sort) { case CommentSort.CreatedAt: return comment.createdAt case CommentSort.ModifiedAt: return comment.modifiedAt } } export class MongoDBCommentAdapter implements DBCommentAdapter { private comments: Collection<DBComment> private locale: string constructor(db: Db, locale: string) { this.comments = db.collection(CollectionName.Comments) this.locale = locale } async addPublicComment({input}: AddPublicCommentArgs): Promise<PublicComment> { const {text, ...data} = input const {ops} = await this.comments.insertOne({ ...data, revisions: [ { text, createdAt: new Date() } ], createdAt: new Date(), modifiedAt: new Date() }) const {_id: id, ...comment} = ops[0] return { ...comment, id, text } } async updatePublicComment({ id, text, state }: UpdatePublicCommentArgs): Promise<OptionalPublicComment> { const {value} = await this.comments.findOneAndUpdate( {_id: id}, { $set: { state, modifiedAt: new Date() }, $addToSet: { revisions: { text, createdAt: new Date() } } }, { returnOriginal: false } ) if (!value) return null const {_id: outID, ...comment} = value return { ...comment, id: outID, text } } async getComments({ filter, sort, order, cursor, limit }: GetCommentsArgs): Promise<ConnectionResult<Comment>> { const metaFilters: FilterQuery<any> = [] if (filter?.state) { metaFilters.push({state: filter.state}) } const limitCount = Math.min(limit.count, MaxResultsPerPage) const sortDirection = limit.type === LimitType.First ? order : -order const cursorData = cursor.type !== InputCursorType.None ? Cursor.from(cursor.data) : undefined const expr = order === SortOrder.Ascending ? cursor.type === InputCursorType.After ? '$gt' : '$lt' : cursor.type === InputCursorType.After ? '$lt' : '$gt' const sortField = commentSortFieldForSort(sort) const cursorFilter = cursorData ? { $or: [ {[sortField]: {[expr]: cursorData.date}}, {_id: {[expr]: cursorData.id}, [sortField]: cursorData.date} ] } : {} const [totalCount, comments] = await Promise.all([ this.comments.countDocuments({}, { collation: {locale: this.locale, strength: 2} } as MongoCountPreferences), // MongoCountPreferences doesn't include collation this.comments .aggregate( [ // sort depending on state value { $addFields: { stateSort: { $indexOfArray: [ [ CommentState.PendingApproval, CommentState.PendingUserChanges, CommentState.Approved, CommentState.Rejected ], '$state' ] } } }, { $sort: { stateSort: 1, [sortField]: sortDirection } } ], {collation: {locale: this.locale, strength: 2}} ) .match(metaFilters.length ? {$and: metaFilters} : {}) .match(cursorFilter) .skip(limit.skip ?? 0) .limit(limitCount + 1) .toArray() ]) const nodes = comments.slice(0, limitCount) if (limit.type === LimitType.Last) { nodes.reverse() } const hasNextPage = limit.type === LimitType.First ? comments.length > limitCount : cursor.type === InputCursorType.Before const hasPreviousPage = limit.type === LimitType.Last ? comments.length > limitCount : cursor.type === InputCursorType.After const firstComment = nodes[0] const lastComment = nodes[nodes.length - 1] const startCursor = firstComment ? new Cursor(firstComment._id, commentDateForSort(firstComment, sort)).toString() : null const endCursor = lastComment ? new Cursor(lastComment._id, commentDateForSort(lastComment, sort)).toString() : null return { nodes: comments.map<Comment>(({_id: id, ...comment}) => ({id, ...comment})), pageInfo: { startCursor, endCursor, hasNextPage, hasPreviousPage }, totalCount } } async getPublicCommentsForItemByID(args: GetPublicCommentsArgs): Promise<PublicComment[]> { const {id, userID} = args let userUnapprovedComments: DBComment[] = [] const comments = await this.comments .aggregate([{$sort: {modifiedAt: -1}}], { collation: {locale: this.locale, strength: 2} }) .match({ $and: [{itemID: id}, {state: CommentState.Approved, parentID: null}] }) .toArray() if (userID) { userUnapprovedComments = await this.comments .aggregate([{$sort: {'modifiedAt.date': -1}}], { collation: {locale: this.locale, strength: 2} }) .match({ $and: [{itemID: id}, {userID}, {state: {$ne: CommentState.Approved}}, {parentID: null}] }) .toArray() } return [...userUnapprovedComments, ...comments].map<PublicComment>( ({_id: id, revisions, ...comment}) => ({ id, text: revisions[revisions.length - 1].text, ...comment }) ) } async getCommentById(id: string): Promise<OptionalComment> { const value = await this.comments.findOne({_id: id}) if (!value) return null const {_id: outID, ...comment} = value return { id: outID, ...comment } } async getPublicChildrenCommentsByParentId(id: string, userID: string): Promise<PublicComment[]> { const [childrenComments] = await Promise.all([ this.comments .aggregate([{$sort: {modifiedAt: -1}}], { collation: {locale: this.locale, strength: 2} }) .match({ $and: [{parentID: id}, {$or: [{state: CommentState.Approved}, {userID: userID}]}] }) .toArray() ]) return childrenComments.map<PublicComment>(({_id: id, revisions, ...comment}) => ({ id, text: revisions[revisions.length - 1].text, ...comment })) } async takeActionOnComment({ id, state, rejectionReason }: TakeActionOnCommentArgs): Promise<OptionalComment> { const {value} = await this.comments.findOneAndUpdate( {_id: id}, { $set: { state, rejectionReason, modifiedAt: new Date() } }, { returnOriginal: false } ) if (!value) return null const {_id: outID, ...comment} = value return { ...comment, id: outID } } }