diff --git a/packages/core/core/src/Strapi.ts b/packages/core/core/src/Strapi.ts index da350e28aa..b93ab0bf60 100644 --- a/packages/core/core/src/Strapi.ts +++ b/packages/core/core/src/Strapi.ts @@ -65,7 +65,7 @@ import getNumberOfDynamicZones from './services/utils/dynamic-zones'; import convertCustomFieldType from './utils/convert-custom-field-type'; import { transformContentTypesToModels } from './utils/transform-content-types-to-models'; import { FeaturesService, createFeaturesService } from './services/features'; -import { createDocumentEngine } from './services/document-service/document-engine'; +import { createDocumentService } from './services/document-service/document-service'; /** * Resolve the working directories based on the instance options. @@ -152,7 +152,7 @@ class Strapi extends Container implements StrapiI { entityService?: EntityService.EntityService; - documents?: Documents.Repository; + documents?: Documents.Service; telemetry: TelemetryService; @@ -512,7 +512,7 @@ class Strapi extends Container implements StrapiI { entityValidator: this.entityValidator, }); - this.documents = createDocumentEngine(this); + this.documents = createDocumentService(this); if (this.config.get('server.cron.enabled', true)) { const cronTasks = this.config.get('server.cron.tasks', {}); diff --git a/packages/core/core/src/services/document-service/document-engine.ts b/packages/core/core/src/services/document-service/document-engine.ts index e0531fbfe0..551dc8611f 100644 --- a/packages/core/core/src/services/document-service/document-engine.ts +++ b/packages/core/core/src/services/document-service/document-engine.ts @@ -1,159 +1,300 @@ -import { Strapi, Common, Documents } from '@strapi/types'; -import createDocumentRepository from './document-repository'; -import createMiddlewareManager from './middlewares'; -import { loadDefaultMiddlewares } from './middlewares/defaults'; +import type { Database } from '@strapi/database'; +import type { Common, Documents, Schema, Shared, Strapi } from '@strapi/types'; +import { contentTypes as contentTypesUtils, convertQueryParams, mapAsync } from '@strapi/utils'; + +import { isArray, omit } from 'lodash/fp'; +import uploadFiles from '../utils/upload-files'; + +import { + cloneComponents, + createComponents, + deleteComponents, + getComponents, + omitComponentData, + updateComponents, +} from '../entity-service/components'; + +import { createDocumentId } from '../../utils/transform-content-types-to-models'; +import { applyTransforms } from '../entity-service/attributes'; +import entityValidator from '../entity-validator'; +import { pickSelectionParams } from './params'; + +const { transformParamsToQuery } = convertQueryParams; /** - * Repository to : - * - Access documents via actions (findMany, findOne, create, update, delete, ...) - * - Execute middlewares on document actions - * - Apply default parameters to document actions - * - * @param strapi - * @param options.defaults - Default parameters to apply to all actions - * @param options.parent - Parent repository, used when creating a new repository with .with() - * @returns DocumentRepository - * - * @example Access documents - * const article = strapi.documents('api::article.article').create(params) - * const allArticles = strapi.documents('api::article.article').findMany(params) + * TODO: Sanitization / validation built-in + * TODO: i18n - Move logic to i18n package + * TODO: Webhooks + * TODO: Audit logs + * TODO: Entity Validation - Uniqueness across same locale and publication status + * TODO: File upload + * TODO: replace 'any' + * TODO: availableLocales * */ -export const createDocumentEngine = ( - strapi: Strapi, - { defaults = {} }: { defaults?: any } = {} -): Documents.Engine => { - const documents = createDocumentRepository({ strapi, db: strapi.db! }); - - const middlewareManager = createMiddlewareManager(); - loadDefaultMiddlewares(middlewareManager); - - function create( - uid: TContentTypeUID - ): Documents.EngineInstance { - return { - async findMany(params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'findMany', uid, params, options: {} }, ({ params }) => - documents.findMany(uid, params) - ) - ); - }, - - async findFirst(params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'findFirst', uid, params, options: {} }, ({ params }) => - documents.findFirst(uid, params) - ) - ); - }, - - async findOne(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'findOne', uid, params, options: { id } }, ({ params }) => - documents.findOne(uid, id, params) - ) - ); - }, - - async delete(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'delete', uid, params, options: { id } }, ({ params }) => - documents.delete(uid, id, params) - ) - ); - }, - - async deleteMany(params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'deleteMany', uid, params, options: {} }, ({ params }) => - documents.deleteMany(uid, params) - ) - ); - }, - - async create(params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'create', uid, params, options: {} }, ({ params }) => - documents.create(uid, params) - ) - ); - }, - - async clone(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'clone', uid, params, options: { id } }, ({ params }) => - documents.clone(uid, id, params) - ) - ); - }, - - async update(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'update', uid, params, options: { id } }, ({ params }) => - documents.update(uid, id, params) - ) - ); - }, - - async count(params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'count', uid, params, options: {} }, ({ params }) => - documents.count(uid, params) - ) - ); - }, - - async publish(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run({ action: 'publish', uid, params, options: { id } }, ({ params }) => - documents.publish(uid, id, params) - ) - ); - }, - - async unpublish(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run( - { action: 'unpublish', uid, params, options: { id } }, - ({ params }) => documents.unpublish(uid, id, params) - ) - ); - }, - - async discardDraft(id: string, params = {} as any) { - return strapi.db?.transaction?.(async () => - middlewareManager.run( - { action: 'discardDraft', uid, params, options: { id } }, - ({ params }) => documents.discardDraft(uid, id, params) - ) - ); - }, - - // @ts-expect-error - TODO: Fix this - with(params: object) { - return createDocumentEngine(strapi, { - defaults: { ...defaults, ...params }, - })(uid); - }, - - use(action, cb, opts) { - middlewareManager.add(uid, action, cb, opts); - return this; - }, - }; - } - - Object.assign(create, { - use(action: any, cb: any, opts?: any) { - middlewareManager.add('_all', action, cb, opts); - return create; - }, - middlewares: middlewareManager, - // NOTE : We should do this in a different way, where lifecycles are executed for the different methods - ...documents, - }); - - // @ts-expect-error - TODO: Fix this - return create; +type Context = { + contentType: Schema.ContentType; +}; + +const createPipeline = (data: Record, context: Context) => { + return applyTransforms(data, context); +}; + +const updatePipeline = (data: Record, context: Context) => { + return applyTransforms(data, context); +}; + +const createDocumentEngine = ({ + strapi, + db, +}: { + strapi: Strapi; + db: Database; +}): Documents.Engine => ({ + uploadFiles, + + async findMany(uid, params) { + const { kind } = strapi.getModel(uid); + + const query = transformParamsToQuery(uid, params || ({} as any)); + query.where = { ...params?.lookup, ...query.where }; + + if (kind === 'singleType') { + return db.query(uid).findOne(query); + } + + return db.query(uid).findMany(query); + }, + + async findFirst(uid, params) { + const query = transformParamsToQuery(uid, params || ({} as any)); + + return db.query(uid).findOne({ ...query, where: { ...params?.lookup, ...query.where } }); + }, + + async findOne(uid, documentId, params) { + const query = transformParamsToQuery(uid, params || ({} as any)); + return db + .query(uid) + .findOne({ ...query, where: { ...params?.lookup, ...query.where, documentId } }); + }, + + async delete(uid, documentId, params = {} as any) { + const query = transformParamsToQuery(uid, params as any); + + if (params.status === 'draft') { + throw new Error('Cannot delete a draft document'); + } + + const entriesToDelete = await db.query(uid).findMany({ + ...query, + where: { + ...params.lookup, + ...query?.where, + documentId, + }, + }); + + // Delete all matched entries and its components + await mapAsync(entriesToDelete, async (entryToDelete: any) => { + const componentsToDelete = await getComponents(uid, entryToDelete); + await db.query(uid).delete({ where: { id: entryToDelete.id } }); + await deleteComponents(uid, componentsToDelete as any, { loadComponents: false }); + }); + + // TODO: Change return value to actual count + return { versions: entriesToDelete }; + }, + + // TODO: should we provide two separate methods? + async deleteMany(uid, paramsOrIds) { + let queryParams; + if (isArray(paramsOrIds)) { + queryParams = { filter: { documentID: { $in: paramsOrIds } } }; + } else { + queryParams = paramsOrIds; + } + + const query = transformParamsToQuery(uid, queryParams || ({} as any)); + + return db.query(uid).deleteMany(query); + }, + + async create(uid, params) { + // TODO: Entity validator. + // TODO: File upload - Probably in the lifecycles? + const { data } = params; + + if (!data) { + throw new Error('Create requires data attribute'); + } + + const model = strapi.getModel(uid) as Shared.ContentTypes[Common.UID.ContentType]; + + const validData = await entityValidator.validateEntityCreation(model, data, { isDraft: true }); + + const componentData = await createComponents(uid, validData); + const entryData = createPipeline( + Object.assign(omitComponentData(model, validData), componentData), + { + contentType: model, + } + ); + + // select / populate + const query = transformParamsToQuery(uid, pickSelectionParams(params) as any); + + return db.query(uid).create({ ...query, data: entryData }); + }, + + // NOTE: What happens if user doesn't provide specific publications state and locale to update? + async update(uid, documentId, params) { + // TODO: Prevent updating a published document + // TODO: Entity validator. + // TODO: File upload + const { data } = params || {}; + const model = strapi.getModel(uid) as Shared.ContentTypes[Common.UID.ContentType]; + + const query = transformParamsToQuery(uid, pickSelectionParams(params || {}) as any); + + // Find all locales of the document + const entryToUpdate = await db + .query(uid) + .findOne({ ...query, where: { ...params?.lookup, ...query?.where, documentId } }); + + // Document does not exist + if (!entryToUpdate) { + return null; + } + + const validData = await entityValidator.validateEntityUpdate( + model, + data, + { isDraft: true }, // Always update the draft version + entryToUpdate + ); + + const componentData = await updateComponents(uid, entryToUpdate, validData); + const entryData = updatePipeline( + Object.assign(omitComponentData(model, validData), componentData), + { contentType: model } + ); + + return db.query(uid).update({ ...query, where: { id: entryToUpdate.id }, data: entryData }); + }, + + async count(uid, params = undefined) { + const query = transformParamsToQuery(uid, params || ({} as any)); + query.where = { ...params?.lookup, ...query.where }; + + return db.query(uid).count(query); + }, + + async clone(uid, documentId, params) { + // TODO: File upload + // TODO: Entity validator. + const { data = {} as any } = params!; + + const model = strapi.getModel(uid); + const query = transformParamsToQuery(uid, pickSelectionParams(params) as any); + + // Find all locales of the document + const entries = await db.query(uid).findMany({ + ...query, + where: { ...params?.lookup, ...query.where, documentId }, + }); + + // Document does not exist + if (!entries.length) { + return null; + } + + const newDocumentId = createDocumentId(); + + const versions = await mapAsync(entries, async (entryToClone: any) => { + const isDraft = contentTypesUtils.isDraft(data); + // Todo: Merge data with entry to clone + const validData = await entityValidator.validateEntityUpdate( + model, + // Omit id fields, the cloned entity id will be generated by the database + omit(['id'], data), + { isDraft }, + entryToClone + ); + + const componentData = await cloneComponents(uid, entryToClone, validData); + const entityData = createPipeline( + Object.assign(omitComponentData(model, validData), componentData), + { contentType: model } + ); + + // TODO: Transform params to query + return db.query(uid).clone(entryToClone.id, { + ...query, + // Allows entityData to override the documentId (e.g. when publishing) + data: { documentId: newDocumentId, ...entityData, locale: entryToClone.locale }, + }); + }); + + return { id: newDocumentId, versions }; + }, + + // TODO: Handle relations so they target the published version + async publish(uid, documentId, params) { + // Delete already published versions that match the locales to be published + await this.delete(uid, documentId, { + ...params, + lookup: { ...params?.lookup, publishedAt: { $ne: null } }, + }); + + // Clone every draft version to be published + const clonedDocuments = (await this.clone(uid, documentId, { + ...(params || {}), + // @ts-expect-error - Generic type does not have publishedAt attribute by default + data: { documentId, publishedAt: new Date() }, + })) as any; + + // TODO: Return actual count + return { versions: clonedDocuments?.versions || [] }; + }, + + async unpublish(uid, documentId, params) { + // Delete all published versions + return this.delete(uid, documentId, { + ...params, + lookup: { ...params?.lookup, publishedAt: { $ne: null } }, + }) as any; + }, + + /** + * Steps: + * - Delete the matching draft versions (publishedAt = null) + * - Clone the matching published versions into draft versions + */ + async discardDraft(uid, documentId, params) { + // Delete draft versions, clone published versions into draft versions + await this.delete(uid, documentId, { + ...params, + // Delete all drafts that match query + lookup: { ...params?.lookup, publishedAt: null }, + }); + + // Clone published versions into draft versions + const clonedDocuments = (await this.clone(uid, documentId, { + ...(params || {}), + // Clone only published versions + lookup: { ...params?.lookup, publishedAt: { $ne: null } }, + // @ts-expect-error - Generic type does not have publishedAt attribute by default + data: { documentId, publishedAt: null }, + })) as any; + + return { versions: clonedDocuments?.versions || [] }; + }, +}); + +export default (ctx: { strapi: Strapi; db: Database }): Documents.Engine => { + const implementation = createDocumentEngine(ctx); + + // TODO: Wrap with database error handling + return implementation; }; diff --git a/packages/core/core/src/services/document-service/document-repository.ts b/packages/core/core/src/services/document-service/document-repository.ts deleted file mode 100644 index 74982483b3..0000000000 --- a/packages/core/core/src/services/document-service/document-repository.ts +++ /dev/null @@ -1,300 +0,0 @@ -import type { Database } from '@strapi/database'; -import type { Common, Documents, Schema, Shared, Strapi } from '@strapi/types'; -import { contentTypes as contentTypesUtils, convertQueryParams, mapAsync } from '@strapi/utils'; - -import { isArray, omit } from 'lodash/fp'; -import uploadFiles from '../utils/upload-files'; - -import { - cloneComponents, - createComponents, - deleteComponents, - getComponents, - omitComponentData, - updateComponents, -} from '../entity-service/components'; - -import { createDocumentId } from '../../utils/transform-content-types-to-models'; -import { applyTransforms } from '../entity-service/attributes'; -import entityValidator from '../entity-validator'; -import { pickSelectionParams } from './params'; - -const { transformParamsToQuery } = convertQueryParams; - -/** - * TODO: Sanitization / validation built-in - * TODO: i18n - Move logic to i18n package - * TODO: Webhooks - * TODO: Audit logs - * TODO: Entity Validation - Uniqueness across same locale and publication status - * TODO: File upload - * TODO: replace 'any' - * TODO: availableLocales - * - */ -type Context = { - contentType: Schema.ContentType; -}; - -const createPipeline = (data: Record, context: Context) => { - return applyTransforms(data, context); -}; - -const updatePipeline = (data: Record, context: Context) => { - return applyTransforms(data, context); -}; - -const createDocumentRepository = ({ - strapi, - db, -}: { - strapi: Strapi; - db: Database; -}): Documents.Repository => ({ - uploadFiles, - - async findMany(uid, params) { - const { kind } = strapi.getModel(uid); - - const query = transformParamsToQuery(uid, params || ({} as any)); - query.where = { ...params?.lookup, ...query.where }; - - if (kind === 'singleType') { - return db.query(uid).findOne(query); - } - - return db.query(uid).findMany(query); - }, - - async findFirst(uid, params) { - const query = transformParamsToQuery(uid, params || ({} as any)); - - return db.query(uid).findOne({ ...query, where: { ...params?.lookup, ...query.where } }); - }, - - async findOne(uid, documentId, params) { - const query = transformParamsToQuery(uid, params || ({} as any)); - return db - .query(uid) - .findOne({ ...query, where: { ...params?.lookup, ...query.where, documentId } }); - }, - - async delete(uid, documentId, params = {} as any) { - const query = transformParamsToQuery(uid, params as any); - - if (params.status === 'draft') { - throw new Error('Cannot delete a draft document'); - } - - const entriesToDelete = await db.query(uid).findMany({ - ...query, - where: { - ...params.lookup, - ...query?.where, - documentId, - }, - }); - - // Delete all matched entries and its components - await mapAsync(entriesToDelete, async (entryToDelete: any) => { - const componentsToDelete = await getComponents(uid, entryToDelete); - await db.query(uid).delete({ where: { id: entryToDelete.id } }); - await deleteComponents(uid, componentsToDelete as any, { loadComponents: false }); - }); - - // TODO: Change return value to actual count - return { versions: entriesToDelete }; - }, - - // TODO: should we provide two separate methods? - async deleteMany(uid, paramsOrIds) { - let queryParams; - if (isArray(paramsOrIds)) { - queryParams = { filter: { documentID: { $in: paramsOrIds } } }; - } else { - queryParams = paramsOrIds; - } - - const query = transformParamsToQuery(uid, queryParams || ({} as any)); - - return db.query(uid).deleteMany(query); - }, - - async create(uid, params) { - // TODO: Entity validator. - // TODO: File upload - Probably in the lifecycles? - const { data } = params; - - if (!data) { - throw new Error('Create requires data attribute'); - } - - const model = strapi.getModel(uid) as Shared.ContentTypes[Common.UID.ContentType]; - - const validData = await entityValidator.validateEntityCreation(model, data, { isDraft: true }); - - const componentData = await createComponents(uid, validData); - const entryData = createPipeline( - Object.assign(omitComponentData(model, validData), componentData), - { - contentType: model, - } - ); - - // select / populate - const query = transformParamsToQuery(uid, pickSelectionParams(params)); - - return db.query(uid).create({ ...query, data: entryData }); - }, - - // NOTE: What happens if user doesn't provide specific publications state and locale to update? - async update(uid, documentId, params) { - // TODO: Prevent updating a published document - // TODO: Entity validator. - // TODO: File upload - const { data } = params || {}; - const model = strapi.getModel(uid) as Shared.ContentTypes[Common.UID.ContentType]; - - const query = transformParamsToQuery(uid, pickSelectionParams(params || {})); - - // Find all locales of the document - const entryToUpdate = await db - .query(uid) - .findOne({ ...query, where: { ...params?.lookup, ...query?.where, documentId } }); - - // Document does not exist - if (!entryToUpdate) { - return null; - } - - const validData = await entityValidator.validateEntityUpdate( - model, - data, - { isDraft: true }, // Always update the draft version - entryToUpdate - ); - - const componentData = await updateComponents(uid, entryToUpdate, validData); - const entryData = updatePipeline( - Object.assign(omitComponentData(model, validData), componentData), - { contentType: model } - ); - - return db.query(uid).update({ ...query, where: { id: entryToUpdate.id }, data: entryData }); - }, - - async count(uid, params = undefined) { - const query = transformParamsToQuery(uid, params || ({} as any)); - query.where = { ...params?.lookup, ...query.where }; - - return db.query(uid).count(query); - }, - - async clone(uid, documentId, params) { - // TODO: File upload - // TODO: Entity validator. - const { data = {} as any } = params!; - - const model = strapi.getModel(uid); - const query = transformParamsToQuery(uid, pickSelectionParams(params)); - - // Find all locales of the document - const entries = await db.query(uid).findMany({ - ...query, - where: { ...params?.lookup, ...query.where, documentId }, - }); - - // Document does not exist - if (!entries.length) { - return null; - } - - const newDocumentId = createDocumentId(); - - const versions = await mapAsync(entries, async (entryToClone: any) => { - const isDraft = contentTypesUtils.isDraft(data); - // Todo: Merge data with entry to clone - const validData = await entityValidator.validateEntityUpdate( - model, - // Omit id fields, the cloned entity id will be generated by the database - omit(['id'], data), - { isDraft }, - entryToClone - ); - - const componentData = await cloneComponents(uid, entryToClone, validData); - const entityData = createPipeline( - Object.assign(omitComponentData(model, validData), componentData), - { contentType: model } - ); - - // TODO: Transform params to query - return db.query(uid).clone(entryToClone.id, { - ...query, - // Allows entityData to override the documentId (e.g. when publishing) - data: { documentId: newDocumentId, ...entityData, locale: entryToClone.locale }, - }); - }); - - return { id: newDocumentId, versions }; - }, - - // TODO: Handle relations so they target the published version - async publish(uid, documentId, params) { - // Delete already published versions that match the locales to be published - await this.delete(uid, documentId, { - ...params, - lookup: { ...params?.lookup, publishedAt: { $ne: null } }, - }); - - // Clone every draft version to be published - const clonedDocuments = (await this.clone(uid, documentId, { - ...(params || {}), - // @ts-expect-error - Generic type does not have publishedAt attribute by default - data: { documentId, publishedAt: new Date() }, - })) as any; - - // TODO: Return actual count - return { versions: clonedDocuments?.versions || [] }; - }, - - async unpublish(uid, documentId, params) { - // Delete all published versions - return this.delete(uid, documentId, { - ...params, - lookup: { ...params?.lookup, publishedAt: { $ne: null } }, - }) as any; - }, - - /** - * Steps: - * - Delete the matching draft versions (publishedAt = null) - * - Clone the matching published versions into draft versions - */ - async discardDraft(uid, documentId, params) { - // Delete draft versions, clone published versions into draft versions - await this.delete(uid, documentId, { - ...params, - // Delete all drafts that match query - lookup: { ...params?.lookup, publishedAt: null }, - }); - - // Clone published versions into draft versions - const clonedDocuments = (await this.clone(uid, documentId, { - ...(params || {}), - // Clone only published versions - lookup: { ...params?.lookup, publishedAt: { $ne: null } }, - // @ts-expect-error - Generic type does not have publishedAt attribute by default - data: { documentId, publishedAt: null }, - })) as any; - - return { versions: clonedDocuments?.versions || [] }; - }, -}); - -export default (ctx: { strapi: Strapi; db: Database }): Documents.Repository => { - const implementation = createDocumentRepository(ctx); - - // TODO: Wrap with database error handling - return implementation; -}; diff --git a/packages/core/core/src/services/document-service/document-service.ts b/packages/core/core/src/services/document-service/document-service.ts new file mode 100644 index 0000000000..072848217a --- /dev/null +++ b/packages/core/core/src/services/document-service/document-service.ts @@ -0,0 +1,159 @@ +import { Strapi, Common, Documents } from '@strapi/types'; +import createDocumentRepository from './document-engine'; +import createMiddlewareManager from './middlewares'; +import { loadDefaultMiddlewares } from './middlewares/defaults'; + +/** + * Repository to : + * - Access documents via actions (findMany, findOne, create, update, delete, ...) + * - Execute middlewares on document actions + * - Apply default parameters to document actions + * + * @param strapi + * @param options.defaults - Default parameters to apply to all actions + * @param options.parent - Parent repository, used when creating a new repository with .with() + * @returns DocumentService + * + * @example Access documents + * const article = strapi.documents('api::article.article').create(params) + * const allArticles = strapi.documents('api::article.article').findMany(params) + * + */ +export const createDocumentService = ( + strapi: Strapi, + { defaults = {} }: { defaults?: any } = {} +): Documents.Service => { + const documents = createDocumentRepository({ strapi, db: strapi.db! }); + + const middlewareManager = createMiddlewareManager(); + loadDefaultMiddlewares(middlewareManager); + + function create( + uid: TContentTypeUID + ): Documents.ServiceInstance { + return { + async findMany(params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'findMany', uid, params, options: {} }, ({ params }) => + documents.findMany(uid, params) + ) + ); + }, + + async findFirst(params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'findFirst', uid, params, options: {} }, ({ params }) => + documents.findFirst(uid, params) + ) + ); + }, + + async findOne(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'findOne', uid, params, options: { id } }, ({ params }) => + documents.findOne(uid, id, params) + ) + ); + }, + + async delete(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'delete', uid, params, options: { id } }, ({ params }) => + documents.delete(uid, id, params) + ) + ); + }, + + async deleteMany(params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'deleteMany', uid, params, options: {} }, ({ params }) => + documents.deleteMany(uid, params) + ) + ); + }, + + async create(params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'create', uid, params, options: {} }, ({ params }) => + documents.create(uid, params) + ) + ); + }, + + async clone(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'clone', uid, params, options: { id } }, ({ params }) => + documents.clone(uid, id, params) + ) + ); + }, + + async update(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'update', uid, params, options: { id } }, ({ params }) => + documents.update(uid, id, params) + ) + ); + }, + + async count(params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'count', uid, params, options: {} }, ({ params }) => + documents.count(uid, params) + ) + ); + }, + + async publish(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run({ action: 'publish', uid, params, options: { id } }, ({ params }) => + documents.publish(uid, id, params) + ) + ); + }, + + async unpublish(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run( + { action: 'unpublish', uid, params, options: { id } }, + ({ params }) => documents.unpublish(uid, id, params) + ) + ); + }, + + async discardDraft(id: string, params = {} as any) { + return strapi.db?.transaction?.(async () => + middlewareManager.run( + { action: 'discardDraft', uid, params, options: { id } }, + ({ params }) => documents.discardDraft(uid, id, params) + ) + ); + }, + + // @ts-expect-error - TODO: Fix this + with(params: object) { + return createDocumentService(strapi, { + defaults: { ...defaults, ...params }, + })(uid); + }, + + use(action, cb, opts) { + middlewareManager.add(uid, action, cb, opts); + return this; + }, + }; + } + + Object.assign(create, { + use(action: any, cb: any, opts?: any) { + middlewareManager.add('_all', action, cb, opts); + return create; + }, + middlewares: middlewareManager, + // NOTE : We should do this in a different way, where lifecycles are executed for the different methods + ...documents, + }); + + // @ts-expect-error - TODO: Fix this + return create; +}; diff --git a/packages/core/types/src/index.ts b/packages/core/types/src/index.ts index 781b4bdb0b..e2bc227c5d 100644 --- a/packages/core/types/src/index.ts +++ b/packages/core/types/src/index.ts @@ -108,7 +108,7 @@ export interface Strapi extends Container { store?: CoreStore; entityValidator?: EntityValidator; entityService?: EntityService.EntityService; - documents?: Documents.Engine; + documents?: Documents.Service; telemetry: TelemetryService; requestContext: RequestContext; customFields: CustomFields.CustomFields; diff --git a/packages/core/types/src/modules/documents/document-repository.ts b/packages/core/types/src/modules/documents/document-engine.ts similarity index 94% rename from packages/core/types/src/modules/documents/document-repository.ts rename to packages/core/types/src/modules/documents/document-engine.ts index 631d094f92..d8c9089d33 100644 --- a/packages/core/types/src/modules/documents/document-repository.ts +++ b/packages/core/types/src/modules/documents/document-engine.ts @@ -1,6 +1,6 @@ import type { Common } from '../../types'; -import type * as Params from './params/document-repository'; -import type * as Result from './result/document-repository'; +import type * as Params from './params/document-engine'; +import type * as Result from './result/document-enigne'; export type ID = string; @@ -10,7 +10,7 @@ export type UploadFile = ( files: Record ) => Promise; -export interface DocumentRepository { +export interface DocumentEngine { uploadFiles: UploadFile; findMany< diff --git a/packages/core/types/src/modules/documents/index.ts b/packages/core/types/src/modules/documents/index.ts index f21da7b4fd..b381e1bc0b 100644 --- a/packages/core/types/src/modules/documents/index.ts +++ b/packages/core/types/src/modules/documents/index.ts @@ -1,16 +1,16 @@ import { Common } from '../..'; -import { ID, type DocumentRepository } from './document-repository'; +import { ID, type DocumentEngine } from './document-engine'; import type * as Middleware from './middleware'; -import type * as Params from './params/document-repository'; -import type * as Result from './result/document-repository'; +import type * as Params from './params/document-engine'; +import type * as Result from './result/document-enigne'; -export { ID, DocumentRepository as Repository } from './document-repository'; +export { ID, DocumentEngine as Engine } from './document-engine'; export type * as Middleware from './middleware'; export * as Params from './params'; export * from './plugin'; export * from './result'; -export type EngineInstance< +export type ServiceInstance< TContentTypeUID extends Common.UID.ContentType = Common.UID.ContentType > = { findMany: >( @@ -74,7 +74,7 @@ export type EngineInstance< * return result; * }) */ - use: ( + use: ( action: TAction, // QUESTION: How do we type the result type of next? // Should we send params + document id attribute? @@ -82,7 +82,7 @@ export type EngineInstance< | Middleware.Middleware | Middleware.Middleware[], opts?: Middleware.Options - ) => ThisType>; + ) => ThisType>; /** * `.with()` instantiates a new document repository with default parameters @@ -99,13 +99,13 @@ export type EngineInstance< */ with: >( params?: TParams - ) => EngineInstance; + ) => ServiceInstance; }; -export type Engine = { +export type Service = { ( uid: TContentTypeUID - ): EngineInstance; + ): ServiceInstance; /** Add a middleware for all uid's and a specific action * @example - Add a default locale @@ -114,13 +114,13 @@ export type Engine = { * return next(ctx) * }) */ - use: ( + use: ( action: TAction, cb: | Middleware.Middleware | Middleware.Middleware[], opts?: Middleware.Options - ) => Engine; + ) => Service; middlewares: Middleware.Manager; -} & DocumentRepository; +} & DocumentEngine; diff --git a/packages/core/types/src/modules/documents/middleware.ts b/packages/core/types/src/modules/documents/middleware.ts index 151ed703b2..76d65f5743 100644 --- a/packages/core/types/src/modules/documents/middleware.ts +++ b/packages/core/types/src/modules/documents/middleware.ts @@ -1,7 +1,7 @@ // Utility type to reuse Param definition in MiddlewareContext import { Common } from '../..'; -import { DocumentRepository } from './document-repository'; -import type * as Params from './params/document-repository'; +import { DocumentEngine } from './document-engine'; +import type * as Params from './params/document-engine'; export type ParamsMap = { findOne: Params.FindOne; @@ -20,7 +20,7 @@ export type ParamsMap { uid: TContentTypeUID; action: TAction; @@ -40,11 +40,11 @@ export interface Options { export type Middleware< TContentTypeUID extends Common.UID.ContentType, - TAction extends keyof DocumentRepository + TAction extends keyof DocumentEngine > = ( ctx: Context, - next: (ctx: Context) => ReturnType -) => ReturnType; + next: (ctx: Context) => ReturnType +) => ReturnType; /** * Handles middlewares for document service diff --git a/packages/core/types/src/modules/documents/params/document-repository.ts b/packages/core/types/src/modules/documents/params/document-engine.ts similarity index 100% rename from packages/core/types/src/modules/documents/params/document-repository.ts rename to packages/core/types/src/modules/documents/params/document-engine.ts diff --git a/packages/core/types/src/modules/documents/result/document-repository.ts b/packages/core/types/src/modules/documents/result/document-enigne.ts similarity index 97% rename from packages/core/types/src/modules/documents/result/document-repository.ts rename to packages/core/types/src/modules/documents/result/document-enigne.ts index 058968c43c..1e3a497a80 100644 --- a/packages/core/types/src/modules/documents/result/document-repository.ts +++ b/packages/core/types/src/modules/documents/result/document-enigne.ts @@ -1,6 +1,6 @@ import { Common, Utils } from '../../..'; import { Result } from '.'; -import * as Params from '../params/document-repository'; +import * as Params from '../params/document-engine'; export type CountResult = { count: number };