UNPKG

@wepublish/api-db-mongodb

Version:

We.publish Database adapter for mongoDB

250 lines (205 loc) 7.06 kB
import { ConnectionResult, CreateInvoiceArgs, DBInvoiceAdapter, DeleteInvoiceArgs, GetInvoicesArgs, InputCursorType, Invoice, InvoiceSort, LimitType, OptionalInvoice, SortOrder, UpdateInvoiceArgs } from '@wepublish/api' import {Collection, Db, FilterQuery, MongoCountPreferences} from 'mongodb' import {CollectionName, DBInvoice} from './schema' import {Cursor} from './cursor' import {MaxResultsPerPage} from './defaults' import {mapDateFilterComparisonToMongoQueryOperatior} from './utility' import {escapeRegExp} from '../utility' export class MongoDBInvoiceAdapter implements DBInvoiceAdapter { private invoices: Collection<DBInvoice> private locale: string constructor(db: Db, locale: string) { this.invoices = db.collection(CollectionName.Invoices) this.locale = locale } async createInvoice({input}: CreateInvoiceArgs): Promise<Invoice> { const {ops} = await this.invoices.insertOne({ createdAt: new Date(), modifiedAt: new Date(), mail: input.mail, dueAt: input.dueAt, subscriptionID: input.subscriptionID, description: input.description, paidAt: input.paidAt, canceledAt: input.canceledAt, sentReminderAt: input.sentReminderAt, items: input.items }) const {_id: id, ...invoice} = ops[0] return {id, ...invoice} } async updateInvoice({id, input}: UpdateInvoiceArgs): Promise<OptionalInvoice> { const {value} = await this.invoices.findOneAndUpdate( {_id: id}, { $set: { modifiedAt: new Date(), mail: input.mail, dueAt: input.dueAt, subscriptionID: input.subscriptionID, description: input.description, paidAt: input.paidAt, canceledAt: input.canceledAt, sentReminderAt: input.sentReminderAt, items: input.items, manuallySetAsPaidByUserId: input.manuallySetAsPaidByUserId } }, {returnOriginal: false} ) if (!value) return null const {_id: outID, ...invoice} = value return {id: outID, ...invoice} } async deleteInvoice({id}: DeleteInvoiceArgs): Promise<string | null> { const {deletedCount} = await this.invoices.deleteOne({_id: id}) return deletedCount !== 0 ? id : null } async getInvoiceByID(id: string): Promise<OptionalInvoice> { const invoice = await this.invoices.findOne({_id: id}) return invoice ? {id: invoice._id, ...invoice} : null } async getInvoicesByID(ids: readonly string[]): Promise<OptionalInvoice[]> { const invoices = await this.invoices.find({_id: {$in: ids}}).toArray() const invoiceMap = Object.fromEntries( invoices.map(({_id: id, ...invoice}) => [id, {id, ...invoice}]) ) return ids.map(id => invoiceMap[id] ?? null) } async getInvoicesBySubscriptionID(subscriptionID: string): Promise<OptionalInvoice[]> { const invoices = await this.invoices.find({subscriptionID}).toArray() return invoices.map(({_id: id, ...invoice}) => ({id, ...invoice})) } async getInvoices({ filter, sort, order, cursor, limit }: GetInvoicesArgs): Promise<ConnectionResult<Invoice>> { 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 = invoiceSortFieldForSort(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?.mail !== undefined) { textFilter.$and?.push({mail: {$regex: escapeRegExp(filter.mail), $options: 'i'}}) } if (filter?.paidAt !== undefined) { const {comparison, date} = filter.paidAt textFilter.$and?.push({ paidAt: {[mapDateFilterComparisonToMongoQueryOperatior(comparison)]: date} }) } if (filter?.canceledAt !== undefined) { const {comparison, date} = filter.canceledAt textFilter.$and?.push({ canceledAt: {[mapDateFilterComparisonToMongoQueryOperatior(comparison)]: date} }) } if (filter?.userID !== undefined) { textFilter.$and?.push({userID: {$eq: filter.userID}}) } if (filter?.subscriptionID !== undefined) { textFilter.$and?.push({subscriptionID: {$eq: filter.subscriptionID}}) } const [totalCount, invoices] = await Promise.all([ this.invoices.countDocuments(textFilter, { collation: {locale: this.locale, strength: 2} } as MongoCountPreferences), // MongoCountPreferences doesn't include collation this.invoices .aggregate([], {collation: {locale: this.locale, strength: 2}}) .match(textFilter) .match(cursorFilter) .sort({[sortField]: sortDirection, _id: sortDirection}) .skip(limit.skip ?? 0) .limit(limitCount + 1) .toArray() ]) const nodes = invoices.slice(0, limitCount) if (limit.type === LimitType.Last) { nodes.reverse() } const hasNextPage = limit.type === LimitType.First ? invoices.length > limitCount : cursor.type === InputCursorType.Before const hasPreviousPage = limit.type === LimitType.Last ? invoices.length > limitCount : cursor.type === InputCursorType.After const firstInvoice = nodes[0] const lastInvoice = nodes[nodes.length - 1] const startCursor = firstInvoice ? new Cursor(firstInvoice._id, invoiceDateForSort(firstInvoice, sort)).toString() : null const endCursor = lastInvoice ? new Cursor(lastInvoice._id, invoiceDateForSort(lastInvoice, sort)).toString() : null return { nodes: nodes.map<Invoice>(({_id: id, ...invoice}) => ({id, ...invoice})), pageInfo: { startCursor, endCursor, hasNextPage, hasPreviousPage }, totalCount } } } function invoiceSortFieldForSort(sort: InvoiceSort) { switch (sort) { case InvoiceSort.CreatedAt: return 'createdAt' case InvoiceSort.ModifiedAt: return 'modifiedAt' case InvoiceSort.PaidAt: return 'paidAt' } } function invoiceDateForSort(invoice: DBInvoice, sort: InvoiceSort): Date { switch (sort) { case InvoiceSort.CreatedAt: return invoice.createdAt case InvoiceSort.ModifiedAt: return invoice.modifiedAt case InvoiceSort.PaidAt: return invoice.paidAt ? invoice.paidAt : invoice.createdAt default: return invoice.createdAt } }