diff --git a/packages/core/server/controllers/index.ts b/packages/core/server/controllers/index.ts index 7e77af6..70702ca 100644 --- a/packages/core/server/controllers/index.ts +++ b/packages/core/server/controllers/index.ts @@ -2,10 +2,12 @@ import urlAliasController from './url-alias'; import urlPatternController from './url-pattern'; import infoController from './info'; import coreController from './core'; +import searchController from './search'; export default { 'url-alias': urlAliasController, 'url-pattern': urlPatternController, info: infoController, core: coreController, + search: searchController, }; diff --git a/packages/core/server/controllers/search.ts b/packages/core/server/controllers/search.ts new file mode 100644 index 0000000..0779c6a --- /dev/null +++ b/packages/core/server/controllers/search.ts @@ -0,0 +1,79 @@ +import { Context } from 'koa'; +import { UID } from '@strapi/strapi'; +import { isContentTypeEnabled } from '../util/enabledContentTypes'; +import { getMainField } from '../services/get-main-field'; + +/** + * Search controller + */ +export default { + search: async (ctx: Context & { params: { id: number } }) => { + const { q } = ctx.query; + const { id } = ctx.params; + let results = []; + + const qStr = typeof q === 'string' ? q.trim() : ''; + if (!qStr) { + ctx.throw(400, 'Missing or invalid query parameter "?q=" (must be a non-empty strin4g)'); + return; + } + + await Promise.all( + Object.entries(strapi.contentTypes).map(async ([uid, config]: [UID.CollectionType, any]) => { + const hasWT = isContentTypeEnabled(config); + if (!hasWT) return; + + const mainField = await getMainField(uid); + if (!mainField) return; + + const entries = await (strapi as any).documents(uid).findMany({ + filters: { + [mainField]: { $containsi: qStr }, + }, + fields: [mainField, 'documentId'], + populate: { + url_alias: { fields: ['id'] }, + }, + }); + + if (!entries || entries.length === 0) return; + + const entriesWithContentType = entries.map((entry: any) => ({ + ...entry, + contentType: uid, + })); + + results.push(...entriesWithContentType); + }), + ); + + // @ts-ignore + ctx.body = results; + }, + reverseSearch: async (ctx: Context & { params: { contentType: string; documentId: string } }) => { + const { contentType, documentId } = ctx.params; + + if (typeof contentType !== 'string' || !(contentType in strapi.contentTypes)) { + ctx.throw(400, `Unknown or invalid content type: ${contentType}`); + return; + } + + const mainField = await getMainField(contentType as UID.CollectionType); + + const entry = await (strapi as any).documents(contentType as UID.CollectionType).findOne({ + documentId, + fields: ['id', 'documentId', ...(mainField ? [mainField] : [])], + }); + + if (!entry) { + ctx.throw(404, 'Entry not found'); + return; + } + + ctx.body = { + id: entry.id, + documentId: entry.documentId, + ...(mainField ? { [mainField]: entry[mainField] } : {}), + }; + }, +}; diff --git a/packages/core/server/routes/index.ts b/packages/core/server/routes/index.ts index 8811d69..780db7b 100644 --- a/packages/core/server/routes/index.ts +++ b/packages/core/server/routes/index.ts @@ -186,6 +186,28 @@ export default { policies: [], }, }, + /** + * Search routes + */ + { + method: 'GET', + path: '/search', + handler: 'search.search', + config: { + policies: [], + }, + }, + /** + * Reverse Search routes for a title or slug + */ + { + method: 'GET', + path: '/search/reverse/:contentType/:documentId', + handler: 'search.reverseSearch', + config: { + policies: [], + }, + }, ], }, }; diff --git a/packages/core/server/services/get-main-field.ts b/packages/core/server/services/get-main-field.ts new file mode 100644 index 0000000..43a8a37 --- /dev/null +++ b/packages/core/server/services/get-main-field.ts @@ -0,0 +1,11 @@ +import { UID } from '@strapi/strapi'; + +export async function getMainField(uid: UID.CollectionType): Promise { + const coreStoreSettings = await strapi.query('strapi::core-store').findMany({ + where: { key: `plugin_content_manager_configuration_content_types::${uid}` }, + }); + if (!coreStoreSettings?.[0]) return null; + + const value = JSON.parse(coreStoreSettings[0].value); + return value?.settings?.mainField ?? null; +}