diff --git a/packages/api/src/functions/refreshMetadataCollectionLongProcess/handler.ts b/packages/api/src/functions/refreshMetadataCollectionLongProcess/handler.ts index 85b30dfd..913c3481 100644 --- a/packages/api/src/functions/refreshMetadataCollectionLongProcess/handler.ts +++ b/packages/api/src/functions/refreshMetadataCollectionLongProcess/handler.ts @@ -46,14 +46,15 @@ const lambda: ValidatedEventAPIGatewayProxyEvent = async ( ), switchMap(([params, db]) => from( - findOne(db, "obokucollection", { - selector: { _id: collectionId } - }) + findOne( + "obokucollection", + { + selector: { _id: collectionId } + }, + { throwOnNotFound: true, db } + ) ).pipe( mergeMap((collection) => { - if (!collection) - throw new Error(`Unable to find book ${collectionId}`) - return from( refreshMetadata(collection, { db, diff --git a/packages/api/src/functions/refreshMetadataCollectionLongProcess/src/refreshMetadata.ts b/packages/api/src/functions/refreshMetadataCollectionLongProcess/src/refreshMetadata.ts index 413d63f2..6d364bf4 100644 --- a/packages/api/src/functions/refreshMetadataCollectionLongProcess/src/refreshMetadata.ts +++ b/packages/api/src/functions/refreshMetadataCollectionLongProcess/src/refreshMetadata.ts @@ -119,9 +119,9 @@ export const refreshMetadata = async ( } // try to get latest collection to stay as fresh as possible - const currentCollection = await findOne(db, "obokucollection", { + const currentCollection = await findOne("obokucollection", { selector: { _id: collection._id } - }) + }, { db }) if (!currentCollection) throw new Error("Unable to find collection") diff --git a/packages/api/src/functions/refreshMetadataLongProcess/handler.ts b/packages/api/src/functions/refreshMetadataLongProcess/handler.ts index 59ae1618..a9f153f6 100644 --- a/packages/api/src/functions/refreshMetadataLongProcess/handler.ts +++ b/packages/api/src/functions/refreshMetadataLongProcess/handler.ts @@ -72,7 +72,7 @@ const lambda: ValidatedEventAPIGatewayProxyEvent = async ( const db = await getNanoDbForUser(userName, jwtPrivateKey) - const book = await findOne(db, "book", { selector: { _id: bookId } }) + const book = await findOne("book", { selector: { _id: bookId } }, { db }) if (!book) throw new Error(`Unable to find book ${bookId}`) @@ -85,7 +85,11 @@ const lambda: ValidatedEventAPIGatewayProxyEvent = async ( const firstLinkId = (book.links || [])[0] || "-1" - const link = await findOne(db, "link", { selector: { _id: firstLinkId } }) + const link = await findOne( + "link", + { selector: { _id: firstLinkId } }, + { db } + ) if (!link) throw new Error(`Unable to find link ${firstLinkId}`) diff --git a/packages/api/src/libs/couch/dbHelpers.ts b/packages/api/src/libs/couch/dbHelpers.ts index 6a94109a..0bde1356 100644 --- a/packages/api/src/libs/couch/dbHelpers.ts +++ b/packages/api/src/libs/couch/dbHelpers.ts @@ -13,6 +13,9 @@ import { User } from "../couchDbEntities" import { waitForRandomTime } from "../utils" import { COUCH_DB_URL } from "../../constants" import { generatePassword } from "../authentication/generatePassword" +import { findOne } from "./findOne" + +export { findOne } export const createUser = async ( db: createNano.ServerScope, @@ -104,39 +107,6 @@ export const insert = async < return doc } -export const findOne = async < - M extends DocType["rx_model"], - D extends ModelOf ->( - db: createNano.DocumentScope, - rxModel: M, - query: SafeMangoQuery -) => { - const { fields, ...restQuery } = query - const fieldsWithRequiredFields = fields - if (Array.isArray(fieldsWithRequiredFields)) { - fieldsWithRequiredFields.push(`rx_model`) - } - - const response = await retryFn(() => - db.find({ - ...restQuery, - fields: fields as string[], - selector: { rx_model: rxModel, ...(query?.selector as any) }, - limit: 1 - }) - ) - - if (response.docs.length === 0) return null - - const doc = response - .docs[0] as createNano.MangoResponse["docs"][number] & D - - if (rxModel !== doc.rx_model) throw new Error(`Invalid document type`) - - return doc -} - export const findAllDataSources = async ( db: createNano.DocumentScope ) => { @@ -246,7 +216,7 @@ export const getOrCreateTagFromName = ( ) => { return retryFn(async () => { // Get all tag ids and create one if it does not exist - const existingTag = await findOne(db, "tag", { selector: { name } }) + const existingTag = await findOne("tag", { selector: { name } }, { db }) if (existingTag) { return existingTag._id } @@ -276,7 +246,7 @@ export const createTagFromName = ( silent: boolean ) => { return retryFn(async () => { - const existingTag = await findOne(db, "tag", { selector: { name } }) + const existingTag = await findOne("tag", { selector: { name } }, { db }) if (existingTag) { if (silent) { diff --git a/packages/api/src/libs/couch/findOne.ts b/packages/api/src/libs/couch/findOne.ts new file mode 100644 index 00000000..e5058511 --- /dev/null +++ b/packages/api/src/libs/couch/findOne.ts @@ -0,0 +1,74 @@ +import createNano from "nano" +import { SafeMangoQuery, DocType, ModelOf } from "@oboku/shared" +import { retryFn } from "./dbHelpers" + +type FindOneOptionsBase = { + db: createNano.DocumentScope +} + +type FindOneOptionsWithThrow = FindOneOptionsBase & { + throwOnNotFound: true +} + +type FindOneOptionsWithoutThrow = FindOneOptionsBase & { + throwOnNotFound?: false +} + +type FindOneOptions = FindOneOptionsWithThrow | FindOneOptionsWithoutThrow + +export function findOne>( + rxModel: M, + query: SafeMangoQuery, + options: FindOneOptionsWithThrow +): Promise< + D & { + _id: string + _rev: string + } +> + +export function findOne>( + rxModel: M, + query: SafeMangoQuery, + options: FindOneOptionsWithoutThrow +): Promise< + | (D & { + _id: string + _rev: string + }) + | null +> + +export async function findOne< + M extends DocType["rx_model"], + D extends ModelOf +>(rxModel: M, query: SafeMangoQuery, options: FindOneOptions) { + const { fields, ...restQuery } = query + const fieldsWithRequiredFields = fields + if (Array.isArray(fieldsWithRequiredFields)) { + fieldsWithRequiredFields.push(`rx_model`) + } + + const response = await retryFn(() => + options.db.find({ + ...restQuery, + fields: fields as string[], + selector: { rx_model: rxModel, ...(query?.selector as any) }, + limit: 1 + }) + ) + + if (response.docs.length === 0) { + if (options.throwOnNotFound) { + throw new Error("Document not found") + } + return null + } + + const doc = response + .docs[0] as createNano.MangoResponse["docs"][number] & D + + if (rxModel !== doc.rx_model) throw new Error(`Invalid document type`) + + return doc +} diff --git a/packages/api/src/libs/plugins/helpers.ts b/packages/api/src/libs/plugins/helpers.ts index 97b877d0..580b660a 100644 --- a/packages/api/src/libs/plugins/helpers.ts +++ b/packages/api/src/libs/plugins/helpers.ts @@ -23,9 +23,13 @@ export const createHelpers = ( refreshBookMetadata: (opts: { bookId: string }) => refreshBookMetadata(opts).catch(console.error), getDataSourceData: async (): Promise> => { - const dataSource = await findOne(db, "datasource", { - selector: { _id: dataSourceId } - }) + const dataSource = await findOne( + "datasource", + { + selector: { _id: dataSourceId } + }, + { db } + ) let data = {} try { if (dataSource?.data) { @@ -40,7 +44,7 @@ export const createHelpers = ( findOne: >( model: M, query: SafeMangoQuery - ) => findOne(db, model, query), + ) => findOne(model, query, { db }), find: >( model: M, query: SafeMangoQuery diff --git a/packages/api/src/libs/sync/collections/repairCollectionBooks.ts b/packages/api/src/libs/sync/collections/repairCollectionBooks.ts index 220d5344..70c86c1a 100644 --- a/packages/api/src/libs/sync/collections/repairCollectionBooks.ts +++ b/packages/api/src/libs/sync/collections/repairCollectionBooks.ts @@ -29,9 +29,15 @@ export const repairCollectionBooks = async ({ ctx: Context collectionId: string }) => { - const collection = await findOne(ctx.db, "obokucollection", { - selector: { _id: collectionId } - }) + const collection = await findOne( + "obokucollection", + { + selector: { _id: collectionId } + }, + { + db: ctx.db + } + ) if (collection) { const [booksHavingCollectionAttached, booksFromCollectionList] =