UNPKG

@wepublish/api-db-mongodb

Version:

We.publish Database adapter for mongoDB

270 lines (226 loc) 7.84 kB
import { ConnectionResult, GetMemberPlansArgs, CreateMemberPlanArgs, DBMemberPlanAdapter, DeleteMemberPlanArgs, InputCursorType, LimitType, MemberPlan, MemberPlanSort, OptionalMemberPlan, SortOrder, UpdateMemberPlanArgs } from '@wepublish/api' import {Collection, Db, FilterQuery, MongoCountPreferences} from 'mongodb' import {CollectionName, DBMemberPlan} from './schema' import {MaxResultsPerPage} from './defaults' import {Cursor} from './cursor' import {escapeRegExp} from '../utility' export class MongoDBMemberPlanAdapter implements DBMemberPlanAdapter { private memberPlans: Collection<DBMemberPlan> private locale: string constructor(db: Db, locale: string) { this.memberPlans = db.collection(CollectionName.MemberPlans) this.locale = locale } async createMemberPlan({input}: CreateMemberPlanArgs): Promise<MemberPlan> { const {ops} = await this.memberPlans.insertOne({ createdAt: new Date(), modifiedAt: new Date(), name: input.name, slug: input.slug, tags: input.tags ? input.tags : [], imageID: input.imageID, description: input.description, active: input.active, amountPerMonthMin: input.amountPerMonthMin, availablePaymentMethods: input.availablePaymentMethods }) const {_id: id, ...memberPlan} = ops[0] return {id, ...memberPlan} } async updateMemberPlan({id, input}: UpdateMemberPlanArgs): Promise<OptionalMemberPlan> { const {value} = await this.memberPlans.findOneAndUpdate( {_id: id}, { $set: { modifiedAt: new Date(), name: input.name, slug: input.slug, tags: input.tags, imageID: input.imageID, description: input.description, active: input.active, amountPerMonthMin: input.amountPerMonthMin, availablePaymentMethods: input.availablePaymentMethods } }, {returnOriginal: false} ) if (!value) return null const {_id: outID, ...memberPlan} = value return {id: outID, ...memberPlan} } async deleteMemberPlan({id}: DeleteMemberPlanArgs): Promise<string | null> { const {deletedCount} = await this.memberPlans.deleteOne({_id: id}) return deletedCount !== 0 ? id : null } async getMemberPlanById(id: string): Promise<OptionalMemberPlan> { const memberPlan = await this.memberPlans.findOne({_id: id}) if (memberPlan) { const {_id: id} = memberPlan return {id, ...memberPlan} } return null } async getMemberPlansByID(ids: readonly string[]): Promise<OptionalMemberPlan[]> { const memberPlans = await this.memberPlans.find({_id: {$in: ids}}).toArray() const memberPlanMap = Object.fromEntries( memberPlans.map(({_id: id, ...memberPlan}) => [id, {id, ...memberPlan}]) ) return ids.map(id => memberPlanMap[id] ?? null) } async getMemberPlansBySlug(slugs: string[]): Promise<OptionalMemberPlan[]> { const memberPlans = await this.memberPlans.find({slug: {$in: slugs}}).toArray() const memberPlansMap = Object.fromEntries( memberPlans.map(({_id: id, slug, ...memberPlan}) => [slug, {id, slug, ...memberPlan}]) ) return slugs.map(slug => memberPlansMap[slug] ?? null) } async getMemberPlans({ filter, sort, order, cursor, limit }: GetMemberPlansArgs): Promise<ConnectionResult<MemberPlan>> { 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 = memberPlanSortFieldForSort(sort) const cursorFilter = cursorData ? { $or: [ {[sortField]: {[expr]: cursorData.date}}, {_id: {[expr]: cursorData.id}, [sortField]: cursorData.date} ] } : {} const textFilter: FilterQuery<any> = {} if (filter && JSON.stringify(filter) !== '{}') { textFilter.$and = [] } // TODO: Rename to search if (filter?.name !== undefined) { textFilter.$and?.push({name: {$regex: escapeRegExp(filter.name), $options: 'i'}}) } if (filter?.active !== undefined) { textFilter.$and?.push({active: filter.active}) } if (filter?.tags) { textFilter.$and?.push({tags: {$in: filter.tags}}) } const [totalCount, memberPlans] = await Promise.all([ this.memberPlans.countDocuments(textFilter, { collation: {locale: this.locale, strength: 2} } as MongoCountPreferences), // MongoCountPreferences doesn't include collation this.memberPlans .aggregate([], {collation: {locale: this.locale, strength: 2}}) .match(textFilter) .match(cursorFilter) .sort({[sortField]: sortDirection, _id: sortDirection}) .limit(limitCount + 1) .toArray() ]) const nodes = memberPlans.slice(0, limitCount) if (limit.type === LimitType.Last) { nodes.reverse() } const hasNextPage = limit.type === LimitType.First ? memberPlans.length > limitCount : cursor.type === InputCursorType.Before const hasPreviousPage = limit.type === LimitType.Last ? memberPlans.length > limitCount : cursor.type === InputCursorType.After const firstMemberPlan = nodes[0] const lastMemberPlan = nodes[nodes.length - 1] const startCursor = firstMemberPlan ? new Cursor(firstMemberPlan._id, memberPlanDateForSort(firstMemberPlan, sort)).toString() : null const endCursor = lastMemberPlan ? new Cursor(lastMemberPlan._id, memberPlanDateForSort(lastMemberPlan, sort)).toString() : null return { nodes: nodes.map<MemberPlan>(({_id: id, ...memberPlan}) => ({id, ...memberPlan})), pageInfo: { startCursor, endCursor, hasNextPage, hasPreviousPage }, totalCount } } async getActiveMemberPlansByID(ids: readonly string[]): Promise<OptionalMemberPlan[]> { const memberPlans = await this.memberPlans.find({_id: {$in: ids}, active: true}).toArray() const memberPlansMap = Object.fromEntries( memberPlans.map(({_id: id, ...memberPlan}) => [id, {id, ...memberPlan}]) ) return ids.map(id => memberPlansMap[id] ?? null) } async getActiveMemberPlansBySlug(slugs: string[]): Promise<OptionalMemberPlan[]> { const memberPlans = await this.memberPlans.find({slug: {$in: slugs}, active: true}).toArray() const memberPlansMap = Object.fromEntries( memberPlans.map(({_id: id, slug, ...memberPlan}) => [slug, {id, slug, ...memberPlan}]) ) return slugs.map(slug => memberPlansMap[slug] ?? null) } async getActiveMemberPlans({ filter, sort, order, cursor, limit }: GetMemberPlansArgs): Promise<ConnectionResult<MemberPlan>> { const {nodes, pageInfo, totalCount} = await this.getMemberPlans({ filter: {...filter, active: true}, sort, order, cursor, limit }) return { nodes, pageInfo, totalCount } } } function memberPlanSortFieldForSort(sort: MemberPlanSort) { switch (sort) { case MemberPlanSort.CreatedAt: return 'createdAt' case MemberPlanSort.ModifiedAt: return 'modifiedAt' } } function memberPlanDateForSort(memberPlan: DBMemberPlan, sort: MemberPlanSort): Date { switch (sort) { case MemberPlanSort.CreatedAt: return memberPlan.createdAt case MemberPlanSort.ModifiedAt: return memberPlan.modifiedAt } }