Skip to content
This repository was archived by the owner on Sep 16, 2025. It is now read-only.

Commit b5ef5bc

Browse files
feat: add graphql support (#8)
* feat(getPluginService): add support for any plugin * refactor(settings): add uid model veersion to models for unified resource access * feat: add graphql support * chore(README): add graphql examples * chore(strapi meta): add displayName * chore(FindSlugResponse): update description * fix(graphql types): ensure model exists before processing * chore(README): incorrect title in graphql response
1 parent 883d74f commit b5ef5bc

File tree

10 files changed

+199
-16
lines changed

10 files changed

+199
-16
lines changed

README.md

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,33 +86,62 @@ Hitting the `/api/slugify/slugs/:modelName/:slug` endpoint for any configured co
8686

8787
Like all other created API endpoints the `findSlug` route must be allowed under `User & Permissions -> Roles -> Public/Authenticated` for the user to be able to access the route.
8888

89-
#### Example Request
89+
## Examples
90+
91+
### Example Request
9092

9193
Making the following request with the sample configuration will look as follows
9294

95+
#### REST
96+
9397
```js
9498
await fetch(`${API_URL}/api/slugify/slugs/article/lorem-ipsum-dolor`);
9599
// GET /api/slugify/slugs/article/lorem-ipsum-dolor
96100
```
97101

98-
#### Example Response
102+
#### GraphQL
103+
104+
```graphql
105+
{
106+
findSlug(modelName:"article",slug:"lorem-ipsum-dolor"){
107+
... on ArticleEntityResponse{
108+
data{
109+
id
110+
attributes{
111+
title
112+
}
113+
}
114+
}
115+
}
116+
}
117+
```
118+
119+
### Example Response
120+
121+
If an article with the slug of `lorem-ipsum-dolor` exists the response will look the same as a single entity response
122+
123+
#### REST
99124

100-
If an article with the slug of `lorem-ipsum-dolor` exists the reponse will look the same as a single entity response
125+
##### Successful Response
101126

102127
```json
103128
{
104129
"data": {
105130
"id": 1,
106-
"title": "lorem ipsum dolor",
107-
"slug": "lorem-ipsum-dolor",
108-
"createdAt": "2022-02-17T01:49:31.961Z",
109-
"updatedAt": "2022-02-17T03:47:09.950Z",
110-
"publishedAt": null
131+
"attributes":{
132+
"title": "lorem ipsum dolor",
133+
"slug": "lorem-ipsum-dolor",
134+
"createdAt": "2022-02-17T01:49:31.961Z",
135+
"updatedAt": "2022-02-17T03:47:09.950Z",
136+
"publishedAt": null
137+
}
111138
}
112139
}
113140
```
114141

115-
**IMPORTANT NOTE** To be inline with Strapi's default behavior for single types if an article with the slug of `lorem-ipsum-dolor` does not exist a 404 error will be returned.
142+
##### Error Response
143+
144+
To be inline with Strapi's default behavior for single types if an article with the slug of `lorem-ipsum-dolor` does not exist a 404 error will be returned.
116145

117146
```json
118147
{
@@ -121,6 +150,38 @@ If an article with the slug of `lorem-ipsum-dolor` exists the reponse will look
121150
}
122151
```
123152

153+
#### GraphQL
154+
155+
##### Successful Response
156+
157+
```json
158+
{
159+
"data": {
160+
"findSlug": {
161+
"data": {
162+
"id": "1",
163+
"attributes": {
164+
"title": "lorem ipsum dolor"
165+
}
166+
}
167+
}
168+
}
169+
}
170+
```
171+
172+
##### Error Response
173+
To be inline with Strapi's default behavior for single types if an article with the slug of `lorem-ipsum-dolor` does not exist the data will be null.
174+
175+
```json
176+
{
177+
"data": {
178+
"findSlug": {
179+
"data": null
180+
}
181+
}
182+
}
183+
```
184+
124185
## Bugs
125186

126187
If any bugs are found please report them as a [Github Issue](https://github.com/ComfortablyCoding/strapi-plugin-slugify/issues)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"yup": "^0.32.9"
4141
},
4242
"strapi": {
43+
"displayName": "Slugify",
4344
"name": "slugify",
4445
"description": "A plugin for Strapi Headless CMS that provides the ability to auto slugify a field for any content type.",
4546
"kind": "plugin"

server/bootstrap.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,22 @@ module.exports = ({ strapi }) => {
1010
const { contentTypes, slugifyOptions } = settings;
1111

1212
// build settings structure
13-
const uids = {};
1413
const models = {};
1514
_.forEach(strapi.contentTypes, (value, key) => {
1615
if (contentTypes[value.modelName]) {
1716
const data = {
1817
uid: value.uid,
1918
...contentTypes[value.modelName],
19+
contentType: value,
2020
};
21-
uids[key] = data;
21+
models[key] = data;
2222
models[value.modelName] = data;
2323
}
2424
});
2525

2626
// reset plugin settings
2727
getPluginService(strapi, 'settingsService').set({
2828
models,
29-
uids,
3029
slugifyOptions,
3130
});
3231

server/graphql/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const { getCustomTypes } = require('./types');
4+
const { getResolversConfig } = require('./resolversConfig');
5+
const { getPluginService } = require('../utils/getPluginService');
6+
7+
const registerGraphlQLQuery = (strapi) => {
8+
// build plugins schema extension
9+
const extension = ({ nexus }) => ({
10+
types: getCustomTypes(strapi, nexus),
11+
resolversConfig: getResolversConfig(),
12+
});
13+
14+
getPluginService(strapi, 'extension', 'graphql').use(extension);
15+
};
16+
17+
module.exports = {
18+
registerGraphlQLQuery,
19+
};

server/graphql/resolversConfig.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const getResolversConfig = () => ({
2+
Query: {
3+
findSlug: {
4+
auth: {
5+
scope: 'plugin::slugify.slugController.findSlug',
6+
},
7+
},
8+
},
9+
});
10+
11+
module.exports = {
12+
getResolversConfig,
13+
};

server/graphql/types.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const _ = require('lodash');
2+
const { getPluginService } = require('../utils/getPluginService');
3+
4+
const getCustomTypes = (strapi, nexus) => {
5+
const { naming } = getPluginService(strapi, 'utils', 'graphql');
6+
const { toEntityResponse } = getPluginService(strapi, 'format', 'graphql').returnTypes;
7+
const { models } = getPluginService(strapi, 'settingsService').get();
8+
const { getEntityResponseName } = naming;
9+
10+
// get all types required for findSlug query
11+
let findSlugTypes = {
12+
response: [],
13+
};
14+
_.forEach(strapi.contentTypes, (value, key) => {
15+
if (models[key]) {
16+
findSlugTypes.response.push(getEntityResponseName(value));
17+
}
18+
});
19+
20+
// ensure we have at least one type before attempting to register
21+
if (!findSlugTypes.response.length) {
22+
return [];
23+
}
24+
25+
// build custom union type based on defined models
26+
const FindSlugResponse = nexus.unionType({
27+
name: 'FindSlugResponse',
28+
description: 'Union Type of all registered slug content types',
29+
definition(t) {
30+
t.members(...findSlugTypes.response);
31+
},
32+
resolveType: (ctx) => {
33+
return getEntityResponseName(models[ctx.info.resourceUID].contentType);
34+
},
35+
});
36+
37+
return [
38+
FindSlugResponse,
39+
nexus.extendType({
40+
type: 'Query',
41+
definition: (t) => {
42+
t.field('findSlug', {
43+
type: FindSlugResponse,
44+
args: {
45+
modelName: nexus.stringArg('The model name of the content type'),
46+
slug: nexus.stringArg('The slug to query for'),
47+
},
48+
resolve: async (_, args) => {
49+
const { models } = getPluginService(strapi, 'settingsService').get();
50+
const { modelName, slug } = args;
51+
52+
const model = models[modelName];
53+
54+
// ensure valid model is passed
55+
if (!model) {
56+
return toEntityResponse(null, { resourceUID: uid });
57+
}
58+
59+
const { uid, field } = model;
60+
let query = {
61+
filters: {
62+
[field]: slug,
63+
},
64+
};
65+
66+
const data = await getPluginService(strapi, 'slugService').findOne(uid, query);
67+
return toEntityResponse(data, { resourceUID: uid });
68+
},
69+
});
70+
},
71+
}),
72+
];
73+
};
74+
75+
module.exports = {
76+
getCustomTypes,
77+
};

server/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
'use strict';
22

33
const bootstrap = require('./bootstrap');
4+
const register = require('./register');
45
const config = require('./config');
56
const controllers = require('./controllers');
67
const routes = require('./routes');
78
const services = require('./services');
89

910
module.exports = {
1011
bootstrap,
12+
register,
1113
config,
1214
controllers,
1315
routes,

server/register.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
const { registerGraphlQLQuery } = require('./graphql');
4+
5+
module.exports = ({ strapi }) => {
6+
// add graphql query if present
7+
if (strapi.plugin('graphql')) {
8+
strapi.log.info('[slugify] graphql detected, registering queries');
9+
registerGraphlQLQuery(strapi);
10+
}
11+
};

server/services/slug-service.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ const { stringToSlug } = require('../utils/stringToSlug');
55

66
module.exports = ({ strapi }) => ({
77
slugify(ctx) {
8-
const { uids, slugifyOptions } = getPluginService(strapi, 'settingsService').get();
8+
const { models, slugifyOptions } = getPluginService(strapi, 'settingsService').get();
99

1010
const { params, model: entityModel } = ctx;
11-
const model = uids[entityModel.uid];
11+
const model = models[entityModel.uid];
1212
const { data } = params;
1313

1414
if (!data) {
@@ -19,7 +19,7 @@ module.exports = ({ strapi }) => ({
1919
const references = data[model.references];
2020

2121
// for empty values they are null, undefined means they are not on the model.
22-
if (!field || typeof references === 'undefined') {
22+
if (!field || typeof data[model.field] === 'undefined' || typeof references === 'undefined') {
2323
return;
2424
}
2525

server/utils/getPluginService.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { pluginId } = require('./pluginId');
77
*
88
* @return service
99
*/
10-
const getPluginService = (strapi, name) => strapi.plugin(pluginId).service(name);
10+
const getPluginService = (strapi, name, plugin = pluginId) => strapi.plugin(plugin).service(name);
1111

1212
module.exports = {
1313
getPluginService,

0 commit comments

Comments
 (0)