Skip to content

Commit 4785da1

Browse files
committed
fix: manage route prefix
1 parent 26fba58 commit 4785da1

File tree

42 files changed

+426
-233
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+426
-233
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
- run: yarn test-next-app
2929
- run: yarn test-react-app
3030
- run: yarn test-vue-app
31+
- run: yarn test-nuxt-app
3132
- run: yarn test-gen-env
3233
- run: yarn test-gen-openapi3
3334
- run: yarn check

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"test-gen-env": "rm -rf ./tmp && yarn build && API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=https://demo.api-platform.com API_PLATFORM_CLIENT_GENERATOR_OUTPUT=./tmp ./lib/index.js",
6767
"test-react-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create react-app ./tmp/app/reactapp && yarn --cwd ./tmp/app/reactapp add react-router-dom@5 redux redux-thunk react-redux redux-form connected-react-router && cp -R ./tmp/react/* ./tmp/app/reactapp/src && cp ./templates/react/index.js ./tmp/app/reactapp/src && start-server-and-test 'BROWSER=none yarn --cwd ./tmp/app/reactapp start' http://127.0.0.1:3000/books/ 'yarn playwright test'",
6868
"test-next-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create next-app --typescript ./tmp/app/next && yarn --cwd ./tmp/app/next add isomorphic-unfetch formik react-query && cp -R ./tmp/next/* ./tmp/app/next && rm ./tmp/app/next/pages/index.tsx && rm -rf ./tmp/app/next/pages/api && yarn --cwd ./tmp/app/next build && start-server-and-test 'yarn --cwd ./tmp/app/next start' http://127.0.0.1:3000/books/ 'yarn playwright test'",
69-
"test-vue-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && cd ./tmp/app && npm init -y vue@2 -- --router vue && cd ../.. && yarn --cwd ./tmp/app/vue add vuex@3 vuex-map-fields lodash && cp -R ./tmp/vue/* ./tmp/app/vue/src && cp ./templates/vue/main.js ./tmp/app/vue/src && yarn --cwd ./tmp/app/vue build && start-server-and-test 'yarn --cwd ./tmp/app/vue vite preview --host 127.0.0.1 --port 3000' http://127.0.0.1:3000/books/ 'yarn playwright test'"
69+
"test-vue-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && cd ./tmp/app && npm init -y vue@2 -- --router vue && cd ../.. && yarn --cwd ./tmp/app/vue add vuex@3 vuex-map-fields lodash && cp -R ./tmp/vue/* ./tmp/app/vue/src && cp ./templates/vue/main.js ./tmp/app/vue/src && yarn --cwd ./tmp/app/vue build && start-server-and-test 'yarn --cwd ./tmp/app/vue vite preview --host 127.0.0.1 --port 3000' http://127.0.0.1:3000/books/ 'yarn playwright test'",
70+
"test-nuxt-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create nuxt-app --answers \"'{\\\"name\\\":\\\"nuxt\\\",\\\"language\\\":\\\"js\\\",\\\"pm\\\":\\\"yarn\\\",\\\"ui\\\":\\\"vuetify\\\",\\\"features\\\":[],\\\"linter\\\":[],\\\"test\\\":\\\"none\\\",\\\"mode\\\":\\\"spa\\\",\\\"target\\\":\\\"static\\\",\\\"devTools\\\":[],\\\"vcs\\\":\\\"none\\\"}'\" ./tmp/app/nuxt && yarn --cwd ./tmp/app/nuxt add moment lodash vuelidate vuex-map-fields && cp -R ./tmp/nuxt/* ./tmp/app/nuxt && NUXT_TELEMETRY_DISABLED=1 NODE_OPTIONS=--openssl-legacy-provider yarn --cwd ./tmp/app/nuxt generate && start-server-and-test 'yarn --cwd ./tmp/app/nuxt start' http://127.0.0.1:3000/books/ 'yarn playwright test'"
7071
},
7172
"lint-staged": {
7273
"src/**/*.js": "yarn lint --fix"

src/generators/NextGenerator.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import chalk from "chalk";
22
import handlebars from "handlebars";
33
import hbhComparison from "handlebars-helpers/lib/comparison.js";
4+
import hbhString from "handlebars-helpers/lib/string.js";
45
import BaseGenerator from "./BaseGenerator.js";
56

67
export default class NextGenerator extends BaseGenerator {
@@ -35,6 +36,7 @@ export default class NextGenerator extends BaseGenerator {
3536
]);
3637

3738
handlebars.registerHelper("compare", hbhComparison.compare);
39+
handlebars.registerHelper("lowercase", hbhString.lowercase);
3840
}
3941

4042
help(resource) {
@@ -77,24 +79,21 @@ export default class NextGenerator extends BaseGenerator {
7779

7880
// Copy with patterned name
7981
this.createDir(`${dir}/components/${context.lc}`);
80-
this.createDir(`${dir}/pages/${context.name}`);
81-
this.createDir(`${dir}/pages/${context.name}/[id]`);
82+
this.createDir(`${dir}/pages/${context.lc}s`);
83+
this.createDir(`${dir}/pages/${context.lc}s/[id]`);
8284
[
8385
// components
8486
"components/%s/List.tsx",
8587
"components/%s/Show.tsx",
8688
"components/%s/Form.tsx",
87-
].forEach((pattern) =>
88-
this.createFileFromPattern(pattern, dir, context.lc, context)
89-
);
90-
[
89+
9190
// pages
92-
"pages/%s/[id]/index.tsx",
93-
"pages/%s/[id]/edit.tsx",
94-
"pages/%s/index.tsx",
95-
"pages/%s/create.tsx",
91+
"pages/%ss/[id]/index.tsx",
92+
"pages/%ss/[id]/edit.tsx",
93+
"pages/%ss/index.tsx",
94+
"pages/%ss/create.tsx",
9695
].forEach((pattern) =>
97-
this.createFileFromPattern(pattern, dir, context.name, context, "foos")
96+
this.createFileFromPattern(pattern, dir, context.lc, context)
9897
);
9998

10099
// interface pattern should be camel cased

src/generators/NextGenerator.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe("generate", () => {
2929
description: "An URL",
3030
}),
3131
];
32-
const resource = new Resource("abcs", "http://example.com/foos", {
32+
const resource = new Resource("prefix/aBe_cd", "http://example.com/foos", {
3333
id: "abc",
3434
title: "abc",
3535
readableFields: fields,

src/generators/NuxtGenerator.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ export default class NuxtGenerator extends BaseVueGenerator {
2525
"mixins/update.js",
2626

2727
// pages
28-
"pages/foos/new.vue",
28+
"pages/foos/create.vue",
2929
"pages/foos/index.vue",
30-
"pages/foos/_id.vue",
30+
"pages/foos/_id/edit.vue",
31+
"pages/foos/_id/index.vue",
3132

3233
// store
3334
"store/crud.js",
@@ -106,7 +107,11 @@ export default class NuxtGenerator extends BaseVueGenerator {
106107

107108
this.createEntrypoint(api.entrypoint, `${dir}/config/entrypoint.js`);
108109

109-
[`${dir}/components/${lc}`, `${dir}/pages/${lc}s`].forEach((dir) => {
110+
[
111+
`${dir}/components/${lc}`,
112+
`${dir}/pages/${lc}s`,
113+
`${dir}/pages/${lc}s/_id`,
114+
].forEach((dir) => {
110115
this.createDir(dir);
111116
});
112117

@@ -118,9 +123,10 @@ export default class NuxtGenerator extends BaseVueGenerator {
118123
"components/%s/Form.vue",
119124

120125
// pages
121-
"pages/%ss/new.vue",
126+
"pages/%ss/create.vue",
122127
"pages/%ss/index.vue",
123-
"pages/%ss/_id.vue",
128+
"pages/%ss/_id/edit.vue",
129+
"pages/%ss/_id/index.vue",
124130

125131
// service
126132
"services/%s.js",

src/generators/NuxtGenerator.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe("generate", () => {
2929
description: "An URL",
3030
}),
3131
];
32-
const resource = new Resource("abc", "http://example.com/foos", {
32+
const resource = new Resource("prefix/aBe_cd", "http://example.com/foos", {
3333
id: "foo",
3434
title: "Foo",
3535
readableFields: fields,
@@ -60,9 +60,10 @@ describe("generate", () => {
6060
"/utils/dates.js",
6161
"/utils/fetch.js",
6262
"/utils/hydra.js",
63-
"/pages/foos/_id.vue",
63+
"/pages/foos/_id/edit.vue",
64+
"/pages/foos/_id/index.vue",
6465
"/pages/foos/index.vue",
65-
"/pages/foos/new.vue",
66+
"/pages/foos/create.vue",
6667
].forEach((file) => {
6768
expect(fs.existsSync(tmpobj.name + file)).toBe(true);
6869
});

src/generators/QuasarGenerator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default class extends BaseGenerator {
116116
handlebars.registerHelper("ifOdd", hbh_comparison.ifOdd);
117117
handlebars.registerHelper("inArray", hbh_array.inArray);
118118
handlebars.registerHelper("forEach", hbh_array.forEach);
119-
handlebars.registerHelper("downcase", hbh_string.downcase);
119+
handlebars.registerHelper("lowercase", hbh_string.lowercase);
120120
handlebars.registerHelper("capitalize", hbh_string.capitalize);
121121

122122
this.registerSwitchHelper();

src/generators/VueBaseGenerator.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default class extends BaseGenerator {
4343
handlebars.registerHelper("isArray", hbh_array.isArray);
4444
handlebars.registerHelper("inArray", hbh_array.inArray);
4545
handlebars.registerHelper("forEach", hbh_array.forEach);
46-
handlebars.registerHelper("downcase", hbh_string.downcase);
46+
handlebars.registerHelper("lowercase", hbh_string.lowercase);
4747

4848
this.registerSwitchHelper();
4949
}
@@ -105,15 +105,13 @@ export default class extends BaseGenerator {
105105
const lc = resource.title.toLowerCase();
106106
const titleUcFirst =
107107
resource.title.charAt(0).toUpperCase() + resource.title.slice(1);
108+
const fields = this.parseFields(resource);
108109

109110
const formFields = this.buildFields(resource.writableFields);
110111

111112
const dateTypes = ["time", "date", "dateTime"];
112113
const formContainsDate = formFields.some((e) => dateTypes.includes(e.type));
113114

114-
const fields = this.buildFields(resource.readableFields);
115-
const listContainsDate = fields.some((e) => dateTypes.includes(e.type));
116-
117115
const parameters = [];
118116
params.forEach((p) => {
119117
const param = fields.find((field) => field.name === p.variable);
@@ -139,7 +137,6 @@ export default class extends BaseGenerator {
139137
uc: resource.title.toUpperCase(),
140138
fields,
141139
dateTypes,
142-
listContainsDate,
143140
paramsHaveRefs,
144141
parameters,
145142
formFields,
@@ -304,4 +301,31 @@ export default class extends BaseGenerator {
304301
recPerPage: "Records per page:",
305302
};
306303
}
304+
305+
parseFields(resource) {
306+
const fields = [
307+
...resource.writableFields,
308+
...resource.readableFields,
309+
].reduce((list, field) => {
310+
if (list[field.name]) {
311+
return list;
312+
}
313+
314+
const isReferences = field.reference && field.maxCardinality !== 1;
315+
const isEmbeddeds = field.embedded && field.maxCardinality !== 1;
316+
317+
return {
318+
...list,
319+
[field.name]: {
320+
...field,
321+
readonly: false,
322+
isReferences,
323+
isEmbeddeds,
324+
isRelations: isEmbeddeds || isReferences,
325+
},
326+
};
327+
}, {});
328+
329+
return Object.values(fields);
330+
}
307331
}

templates/next/components/common/Pagination.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@ const Pagination = ({ collection }: Props) => {
1919
return (
2020
<nav aria-label="Page navigation">
2121
<Link href={first ? first : '#'}>
22-
<a className={`btn btn-primary${previous ? '' : ' disabled'}`}>
22+
<a className={`btn btn-primary${previous ? '' : ' disabled'}`} aria-label="First page">
2323
<span aria-hidden="true">&lArr;</span> First
2424
</a>
2525
</Link>
2626
<Link href={previous ? previous : '#'}>
27-
<a className={`btn btn-primary${previous ? '' : ' disabled'}`}>
27+
<a className={`btn btn-primary${previous ? '' : ' disabled'}`} aria-label="Previous page">
2828
<span aria-hidden="true">&larr;</span> Previous
2929
</a>
3030
</Link>
3131
<Link href={next ? next : '#'}>
32-
<a className={`btn btn-primary${next ? '' : ' disabled'}`}>
32+
<a className={`btn btn-primary${next ? '' : ' disabled'}`} aria-label="Next page">
3333
Next <span aria-hidden="true">&rarr;</span>
3434
</a>
3535
</Link>
3636
<Link href={last ? last : '#'}>
37-
<a className={`btn btn-primary${next ? '' : ' disabled'}`}>
37+
<a className={`btn btn-primary${next ? '' : ' disabled'}`} aria-label="Last page">
3838
Last <span aria-hidden="true">&rArr;</span>
3939
</a>
4040
</Link>

templates/next/components/common/ReferenceLinks.tsx

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ import Link from "next/link";
22
import { Fragment, FunctionComponent } from "react";
33

44
interface Props {
5-
items: string | string[];
6-
useIcon?: boolean;
5+
items: string | string[] | { href: string; name: string } | { href: string; name: string }[];
76
}
8-
const ReferenceLinks: FunctionComponent<Props> = ({
9-
items,
10-
useIcon = false,
11-
}) => {
7+
8+
const ReferenceLinks: FunctionComponent<Props> = ({ items }) => {
129
if (Array.isArray(items)) {
1310
return (
1411
<Fragment>
@@ -22,16 +19,9 @@ const ReferenceLinks: FunctionComponent<Props> = ({
2219
}
2320

2421
return (
25-
<Link href={items}>
22+
<Link href={typeof items === "string" ? items : items.href}>
2623
<a>
27-
{useIcon ? (
28-
<Fragment>
29-
<i className="bi bi-search" aria-hidden="true"></i>
30-
<span className="sr-only">Show</span>
31-
</Fragment>
32-
) : (
33-
items
34-
)}
24+
{typeof items === "string" ? items : items.name}
3525
</a>
3626
</Link>
3727
);

templates/next/components/foo/Form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const Form: FunctionComponent<Props> = ({ {{{lc}}} }) => {
3535

3636
const deleteMutation = useMutation<FetchResponse<{{ucf}}> | undefined, Error|FetchError, DeleteParams>(({ id }) => delete{{{ucf}}}(id), {
3737
onSuccess: () => {
38-
router.push("/{{{name}}}");
38+
router.push("/{{{lc}}}s");
3939
},
4040
onError: (error)=> {
4141
setError(`Error when deleting the resource: ${error}`);
@@ -193,7 +193,7 @@ export const Form: FunctionComponent<Props> = ({ {{{lc}}} }) => {
193193
</form>
194194
)}
195195
</Formik>
196-
<Link href="/{{{name}}}">
196+
<Link href="/{{{lc}}}s">
197197
<a className="btn btn-primary">Back to list</a>
198198
</Link>
199199
{ {{{lc}}} && (

templates/next/components/foo/List.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,67 @@ import { FunctionComponent } from "react";
22
import Link from "next/link";
33

44
import ReferenceLinks from "../common/ReferenceLinks";
5+
import { getPath } from "../../utils/dataAccess";
56
import { {{{ucf}}} } from '../../types/{{{ucf}}}';
67

78
interface Props {
8-
{{{name}}}: {{{ucf}}}[];
9+
{{{lc}}}s: {{{ucf}}}[];
910
}
1011

11-
export const List: FunctionComponent<Props> = ({ {{{name}}} }) => (
12+
export const List: FunctionComponent<Props> = ({ {{{lc}}}s }) => (
1213
<div>
1314
<h1>{{{ucf}}} List</h1>
14-
<Link href="/{{{name}}}/create">
15+
<Link href="/{{{lc}}}s/create">
1516
<a className="btn btn-primary">Create</a>
1617
</Link>
1718
<table className="table table-responsive table-striped table-hover">
1819
<thead>
1920
<tr>
2021
<th>id</th>
2122
{{#each fields}}
22-
<th>{{name}}</th>
23+
<th>{{name}}</th>
2324
{{/each}}
2425
<th/>
2526
</tr>
2627
</thead>
2728
<tbody>
28-
{ {{{name}}} && ({{{name}}}.length !== 0) && {{{name}}}.map( ( {{{lc}}} ) => (
29+
{ {{{lc}}}s && ({{{lc}}}s.length !== 0) && {{{lc}}}s.map( ( {{{lc}}} ) => (
2930
{{{lc}}}['@id'] &&
3031
<tr key={ {{{lc}}}['@id'] }>
31-
<th scope="row"><ReferenceLinks items={ {{{lc}}}['@id'] } /></th>
32+
<th scope="row">
33+
<ReferenceLinks items={ { href: getPath({{{lc}}}['@id'], '/{{{lc}}}s/[id]'), name: {{{lc}}}['@id'] } } />
34+
</th>
3235
{{#each fields}}
3336
<td>
3437
{{#if reference}}
35-
<ReferenceLinks items={ {{{../lc}}}['{{{name}}}'] } />
38+
<ReferenceLinks items={ { href: getPath({{{../lc}}}['{{{name}}}'], '/{{{lowercase reference.title}}}s/[id]'), name: {{{../lc}}}['{{{name}}}'] } } />
3639
{{else if isEmbeddeds}}
37-
<ReferenceLinks items={ {{{../lc}}}['{{{name}}}'].map((emb: any) => emb['@id']) } />
40+
<ReferenceLinks items={ {{{../lc}}}['{{{name}}}'].map((emb: any) => ({ href: getPath(emb['@id'], '/{{{lowercase embedded.title}}}s/[id]'), name: emb['@id'] })) } />
3841
{{else if embedded}}
39-
<ReferenceLinks items={ {{{../lc}}}['{{{name}}}']['@id'] } />
42+
<ReferenceLinks items={ { href: getPath({{{../lc}}}['{{{name}}}']['@id'], '/{{{lowercase embedded.title}}}s/[id]'), name: {{{../lc}}}['{{{name}}}']['@id'] } } />
4043
{{else if (compare type "==" "Date") }}
4144
{ {{{../lc}}}['{{{name}}}']?.toLocaleString() }
4245
{{else}}
4346
{ {{{../lc}}}['{{{name}}}'] }
4447
{{/if}}
4548
</td>
4649
{{/each}}
47-
<td><ReferenceLinks items={ {{{lc}}}['@id'] } useIcon={true} /></td>
48-
<td>
49-
<Link href={`${ {{~lc}}["@id"]}/edit`}>
50-
<a>
51-
<i className="bi bi-pen" aria-hidden="true" />
52-
<span className="sr-only">Edit</span>
53-
</a>
54-
</Link>
55-
</td>
50+
<td>
51+
<Link href={ getPath({{{lc}}}['@id'], '/{{{lc}}}s/[id]') }>
52+
<a>
53+
<i className="bi bi-search" aria-hidden="true"></i>
54+
<span className="sr-only">Show</span>
55+
</a>
56+
</Link>
57+
</td>
58+
<td>
59+
<Link href={ getPath({{{lc}}}["@id"], '/{{{lc}}}s/[id]/edit') }>
60+
<a>
61+
<i className="bi bi-pen" aria-hidden="true" />
62+
<span className="sr-only">Edit</span>
63+
</a>
64+
</Link>
65+
</td>
5666
</tr>
5767
))}
5868
</tbody>

0 commit comments

Comments
 (0)