Skip to content

Commit 324ec80

Browse files
authored
Merge pull request #311 from pluginpal/feature/new-addons
feat: add search controller + search and reverseSearch methods
2 parents 08c0017 + d0fa92f commit 324ec80

File tree

6 files changed

+153
-7
lines changed

6 files changed

+153
-7
lines changed

packages/cli/src/utils/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const badge = (text: string, bgColor: ChalkFunction, textColor: ChalkFunction =
1414
const textIndent = (
1515
text: string | string[],
1616
indentFirst = true,
17-
indent: number = MAX_PREFIX_LENGTH + 2
17+
indent: number = MAX_PREFIX_LENGTH + 2,
1818
) => {
1919
const parts = Array.isArray(text) ? text : [text];
2020

packages/core/server/controllers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import urlAliasController from './url-alias';
22
import urlPatternController from './url-pattern';
33
import infoController from './info';
44
import coreController from './core';
5+
import searchController from './search';
56

67
export default {
78
'url-alias': urlAliasController,
89
'url-pattern': urlPatternController,
910
info: infoController,
1011
core: coreController,
12+
search: searchController,
1113
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Context } from 'koa';
2+
import { UID, Schema } from '@strapi/strapi';
3+
import { errors } from '@strapi/utils';
4+
import { isContentTypeEnabled } from '../util/enabledContentTypes';
5+
import { getPluginService } from '../util/getPluginService';
6+
7+
interface DocumentEntry {
8+
id: number;
9+
documentId: string;
10+
[key: string]: unknown; // Dynamic fields like title, etc.
11+
}
12+
13+
interface SearchResult extends DocumentEntry {
14+
contentType: string;
15+
}
16+
17+
/**
18+
* Search controller
19+
*/
20+
export default {
21+
search: async (ctx: Context & { params: { id: number } }) => {
22+
const { q } = ctx.query;
23+
const results: SearchResult[] = [];
24+
25+
const qStr = typeof q === 'string' ? q.trim() : '';
26+
if (!qStr) {
27+
throw new errors.ValidationError('Missing or invalid query parameter "?q=" (must be a non-empty string)');
28+
}
29+
30+
await Promise.all(
31+
Object.entries(strapi.contentTypes).map(
32+
async ([uid, config]: [UID.ContentType, Schema.ContentType]) => {
33+
const hasWT = isContentTypeEnabled(config);
34+
if (!hasWT) return;
35+
36+
const mainField = await getPluginService('get-main-field').getMainField(uid);
37+
if (!mainField) return;
38+
39+
const fieldsArr: string[] = ['documentId', ...(mainField ? [mainField] : [])];
40+
const entries = (await strapi.documents(uid).findMany({
41+
filters: {
42+
[mainField]: { $containsi: qStr },
43+
},
44+
// @ts-expect-error
45+
fields: fieldsArr,
46+
}));
47+
48+
if (!entries || entries.length === 0) return;
49+
50+
const entriesWithContentType: SearchResult[] = entries.map((entry: DocumentEntry) => ({
51+
...entry,
52+
contentType: uid,
53+
}));
54+
55+
results.push(...entriesWithContentType);
56+
},
57+
),
58+
);
59+
60+
ctx.body = results;
61+
},
62+
reverseSearch: async (ctx: Context & { params: { contentType: string; documentId: string } }) => {
63+
const { contentType, documentId } = ctx.params;
64+
65+
if (typeof contentType !== 'string' || !(contentType in strapi.contentTypes)) {
66+
throw new errors.ValidationError(`Unknown or invalid content type: ${contentType}`);
67+
}
68+
69+
const mainField = await getPluginService('get-main-field').getMainField(
70+
contentType as UID.ContentType,
71+
);
72+
// eslint-disable-next-line max-len
73+
const fieldsArr: string[] = ['id', 'documentId', ...(mainField ? [mainField] : [])];
74+
75+
const entry = (await strapi
76+
.documents(contentType as UID.ContentType)
77+
.findOne({
78+
documentId,
79+
// @ts-expect-error
80+
fields: fieldsArr,
81+
}));
82+
83+
if (!entry) {
84+
throw new errors.NotFoundError('Entry not found');
85+
}
86+
87+
ctx.body = {
88+
id: entry.id,
89+
documentId: entry.documentId,
90+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
91+
...(mainField ? { [mainField]: entry[mainField] } : {}),
92+
};
93+
},
94+
};

packages/core/server/routes/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,28 @@ export default {
186186
policies: [],
187187
},
188188
},
189+
/**
190+
* Search routes
191+
*/
192+
{
193+
method: 'GET',
194+
path: '/search',
195+
handler: 'search.search',
196+
config: {
197+
policies: [],
198+
},
199+
},
200+
/**
201+
* Reverse Search routes for a title or slug
202+
*/
203+
{
204+
method: 'GET',
205+
path: '/search/reverse/:contentType/:documentId',
206+
handler: 'search.reverseSearch',
207+
config: {
208+
policies: [],
209+
},
210+
},
189211
],
190212
},
191213
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { UID } from '@strapi/strapi';
2+
3+
interface ContentManagerConfig {
4+
settings?: {
5+
mainField?: string;
6+
};
7+
}
8+
9+
export const getMainField = async (uid: UID.ContentType): Promise<string | null> => {
10+
const coreStoreSettings = (await strapi
11+
// TODO use documents service instead of any
12+
.query('strapi::core-store' as UID.Schema)
13+
.findMany({
14+
where: { key: `plugin_content_manager_configuration_content_types::${uid}` },
15+
})) as Array<{ value: string }>;
16+
17+
if (!coreStoreSettings?.[0]) return null;
18+
19+
const value = JSON.parse(coreStoreSettings[0].value) as ContentManagerConfig;
20+
21+
return value?.settings?.mainField ?? null;
22+
};
23+
24+
export default () => ({
25+
getMainField,
26+
});
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import urlAliasController from './url-alias';
2-
import urlPatternController from './url-pattern';
3-
import bulkGenerateController from './bulk-generate';
1+
import urlAliasService from './url-alias';
2+
import urlPatternService from './url-pattern';
3+
import bulkGenerateService from './bulk-generate';
4+
import getMainFieldService from './get-main-field';
45

56
export default {
6-
'url-alias': urlAliasController,
7-
'url-pattern': urlPatternController,
8-
'bulk-generate': bulkGenerateController,
7+
'url-alias': urlAliasService,
8+
'url-pattern': urlPatternService,
9+
'bulk-generate': bulkGenerateService,
10+
'get-main-field': getMainFieldService,
911
};

0 commit comments

Comments
 (0)