emdash 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-DoNJiveC.d.mts → adapters-BktHA7EO.d.mts} +1 -1
- package/dist/{adapters-DoNJiveC.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
- package/dist/{apply-BzltprvY.mjs → apply-UsrFuO7l.mjs} +156 -254
- package/dist/apply-UsrFuO7l.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.mjs +10 -2
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.mjs +5 -5
- package/dist/astro/middleware/request-context.mjs +4 -4
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +35 -34
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +8 -9
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-BRICGH2l.mjs → base64-MBPo9ozB.mjs} +1 -1
- package/dist/{base64-BRICGH2l.mjs.map → base64-MBPo9ozB.mjs.map} +1 -1
- package/dist/{byline-BSaNL1w7.mjs → byline-C3vnhIpU.mjs} +4 -4
- package/dist/{byline-BSaNL1w7.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
- package/dist/{bylines-CvJ3PYz2.mjs → bylines-esI7ioa9.mjs} +5 -5
- package/dist/{bylines-CvJ3PYz2.mjs.map → bylines-esI7ioa9.mjs.map} +1 -1
- package/dist/{cache-C6N_hhN7.mjs → cache-fTzxgMFJ.mjs} +3 -3
- package/dist/{cache-C6N_hhN7.mjs.map → cache-fTzxgMFJ.mjs.map} +1 -1
- package/dist/{chunks-NBQVDOci.mjs → chunks-Da2-b-oA.mjs} +2 -2
- package/dist/{chunks-NBQVDOci.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
- package/dist/cli/index.mjs +251 -79
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{config-BI0V3ICQ.mjs → config-CVssduLe.mjs} +1 -1
- package/dist/{config-BI0V3ICQ.mjs.map → config-CVssduLe.mjs.map} +1 -1
- package/dist/{content-8lOYF0pr.mjs → content-C7G4QXkK.mjs} +14 -3
- package/dist/content-C7G4QXkK.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-WRezodiz.mjs → db-errors-B7P2pSCn.mjs} +1 -1
- package/dist/{db-errors-WRezodiz.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
- package/dist/{default-D8ksjWhO.mjs → default-pHuz9WF6.mjs} +1 -1
- package/dist/{default-D8ksjWhO.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
- package/dist/{error-D_-tqP-I.mjs → error-DqnRMM5z.mjs} +1 -1
- package/dist/{error-D_-tqP-I.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
- package/dist/{index-BFRaVcD6.d.mts → index-DjPMOfO0.d.mts} +82 -67
- package/dist/index-DjPMOfO0.d.mts.map +1 -0
- package/dist/index.d.mts +10 -10
- package/dist/index.mjs +28 -27
- package/dist/{load-DDqMMvZL.mjs → load-sXRuM7Us.mjs} +2 -2
- package/dist/{load-DDqMMvZL.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
- package/dist/{loader-CKLbBnhK.mjs → loader-Bx2_9-5e.mjs} +31 -6
- package/dist/loader-Bx2_9-5e.mjs.map +1 -0
- package/dist/{manifest-schema-DqWNC3lM.mjs → manifest-schema-CXAbd1vH.mjs} +1 -1
- package/dist/{manifest-schema-DqWNC3lM.mjs.map → manifest-schema-CXAbd1vH.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-BW32b4gi.mjs → media-D8FbNsl0.mjs} +2 -2
- package/dist/{media-BW32b4gi.mjs.map → media-D8FbNsl0.mjs.map} +1 -1
- package/dist/{mode-ier8jbBk.mjs → mode-YhqNVef_.mjs} +1 -1
- package/dist/{mode-ier8jbBk.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
- package/dist/{options-BVp3UsTS.mjs → options-nPxWnrya.mjs} +1 -1
- package/dist/{options-BVp3UsTS.mjs.map → options-nPxWnrya.mjs.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
- package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
- package/dist/{placeholder-BE4o_2dc.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
- package/dist/{placeholder-BE4o_2dc.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
- package/dist/{placeholder-CIJejMlK.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
- package/dist/{placeholder-CIJejMlK.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{public-url-DByxYjUw.mjs → public-url-B1AxbbbQ.mjs} +1 -1
- package/dist/{public-url-DByxYjUw.mjs.map → public-url-B1AxbbbQ.mjs.map} +1 -1
- package/dist/{query-Cg9ZKRQ0.mjs → query-Bo-msrmu.mjs} +13 -13
- package/dist/{query-Cg9ZKRQ0.mjs.map → query-Bo-msrmu.mjs.map} +1 -1
- package/dist/{redirect-BhUBKRc1.mjs → redirect-C5H7VGIX.mjs} +3 -3
- package/dist/{redirect-BhUBKRc1.mjs.map → redirect-C5H7VGIX.mjs.map} +1 -1
- package/dist/{registry-Dw70ChxB.mjs → registry-Beb7wxFc.mjs} +5 -5
- package/dist/{registry-Dw70ChxB.mjs.map → registry-Beb7wxFc.mjs.map} +1 -1
- package/dist/{request-cache-B-bmkipQ.mjs → request-cache-C-tIpYIw.mjs} +1 -1
- package/dist/{request-cache-B-bmkipQ.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
- package/dist/{runner-Bnoj7vjK.d.mts → runner-Clwe4Mme.d.mts} +2 -2
- package/dist/{runner-Bnoj7vjK.d.mts.map → runner-Clwe4Mme.d.mts.map} +1 -1
- package/dist/{runner-C7ADox5q.mjs → runner-DMnlIkh4.mjs} +433 -138
- package/dist/runner-DMnlIkh4.mjs.map +1 -0
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +3 -3
- package/dist/{search-dOGEccMa.mjs → search-DkN-BqsS.mjs} +164 -92
- package/dist/search-DkN-BqsS.mjs.map +1 -0
- package/dist/{secrets-CW3reAnU.mjs → secrets-CZ8rxLX3.mjs} +3 -3
- package/dist/{secrets-CW3reAnU.mjs.map → secrets-CZ8rxLX3.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/taxonomies-CTtewrSQ.mjs +407 -0
- package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
- package/dist/taxonomy-DSxx2K2L.mjs +218 -0
- package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
- package/dist/{tokens-D7zMmWi2.mjs → tokens-CyRDPVW2.mjs} +2 -2
- package/dist/{tokens-D7zMmWi2.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
- package/dist/{transport-DNEfeMaU.d.mts → transport-DX_5rpsq.d.mts} +1 -1
- package/dist/{transport-DNEfeMaU.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
- package/dist/{transport-BeMCmin1.mjs → transport-xpzIjCIB.mjs} +1 -1
- package/dist/{transport-BeMCmin1.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
- package/dist/{types-CRxNbK-Z.mjs → types-BIgulNsW.mjs} +2 -2
- package/dist/{types-CRxNbK-Z.mjs.map → types-BIgulNsW.mjs.map} +1 -1
- package/dist/{types-CJsYGpco.d.mts → types-B_CXXnzh.d.mts} +1 -1
- package/dist/{types-CJsYGpco.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
- package/dist/{types-M78DQ1lx.d.mts → types-C-aFbqmA.d.mts} +1 -1
- package/dist/{types-M78DQ1lx.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
- package/dist/{types-4fVtCIm0.mjs → types-CoO6mpV3.mjs} +1 -1
- package/dist/{types-4fVtCIm0.mjs.map → types-CoO6mpV3.mjs.map} +1 -1
- package/dist/{types-BuBIptGk.d.mts → types-D19uBYWn.d.mts} +149 -4
- package/dist/types-D19uBYWn.d.mts.map +1 -0
- package/dist/{types-BSyXeCFW.d.mts → types-Dl1fgFjn.d.mts} +1 -1
- package/dist/{types-BSyXeCFW.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
- package/dist/{types-CrtWgIvl.d.mts → types-Dtx1mSMX.d.mts} +9 -1
- package/dist/types-Dtx1mSMX.d.mts.map +1 -0
- package/dist/{types-CIOg5AR8.mjs → types-Eg829jj9.mjs} +1 -1
- package/dist/{types-CIOg5AR8.mjs.map → types-Eg829jj9.mjs.map} +1 -1
- package/dist/{types-CDbKp7ND.mjs → types-K-EkEQCI.mjs} +1 -1
- package/dist/{types-CDbKp7ND.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
- package/dist/{validate-Baqf0slj.mjs → validate-CBIbxM3L.mjs} +14 -10
- package/dist/validate-CBIbxM3L.mjs.map +1 -0
- package/dist/{validate-BfQh_C_y.d.mts → validate-DHGwADqO.d.mts} +18 -5
- package/dist/validate-DHGwADqO.d.mts.map +1 -0
- package/dist/{validation-BfEI7tNe.mjs → validation-B1NYiEos.mjs} +5 -5
- package/dist/{validation-BfEI7tNe.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
- package/dist/version-CMD42IRC.mjs +7 -0
- package/dist/{version-DoxrVdYf.mjs.map → version-CMD42IRC.mjs.map} +1 -1
- package/dist/{zod-generator-CC0xNe_K.mjs → zod-generator-BNJDQBSZ.mjs} +8 -3
- package/dist/zod-generator-BNJDQBSZ.mjs.map +1 -0
- package/package.json +6 -6
- package/src/api/handlers/content.ts +11 -0
- package/src/api/handlers/dashboard.ts +29 -36
- package/src/api/handlers/menus.ts +256 -75
- package/src/api/handlers/taxonomies.ts +273 -97
- package/src/api/schemas/common.ts +7 -0
- package/src/api/schemas/menus.ts +23 -0
- package/src/api/schemas/taxonomies.ts +39 -0
- package/src/astro/integration/routes.ts +10 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
- package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
- package/src/astro/routes/api/menus/[name]/items.ts +16 -6
- package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
- package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
- package/src/astro/routes/api/menus/[name].ts +19 -10
- package/src/astro/routes/api/menus/index.ts +9 -6
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
- package/src/astro/routes/api/taxonomies/index.ts +9 -6
- package/src/cli/commands/export-seed.ts +82 -21
- package/src/cli/commands/plugin-init.ts +216 -90
- package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
- package/src/database/migrations/runner.ts +2 -0
- package/src/database/repositories/content.ts +11 -0
- package/src/database/repositories/taxonomy.ts +193 -89
- package/src/database/types.ts +10 -2
- package/src/i18n/resolve.ts +37 -0
- package/src/loader.ts +49 -2
- package/src/mcp/server.ts +77 -18
- package/src/menus/index.ts +143 -124
- package/src/menus/types.ts +15 -1
- package/src/schema/zod-generator.ts +12 -2
- package/src/seed/apply.ts +140 -54
- package/src/seed/types.ts +14 -1
- package/src/seed/validate.ts +27 -13
- package/src/taxonomies/index.ts +230 -213
- package/src/taxonomies/types.ts +10 -0
- package/dist/apply-BzltprvY.mjs.map +0 -1
- package/dist/content-8lOYF0pr.mjs.map +0 -1
- package/dist/index-BFRaVcD6.d.mts.map +0 -1
- package/dist/loader-CKLbBnhK.mjs.map +0 -1
- package/dist/runner-C7ADox5q.mjs.map +0 -1
- package/dist/search-dOGEccMa.mjs.map +0 -1
- package/dist/taxonomies-ZlRtD6AG.mjs +0 -315
- package/dist/taxonomies-ZlRtD6AG.mjs.map +0 -1
- package/dist/types-BuBIptGk.d.mts.map +0 -1
- package/dist/types-CrtWgIvl.d.mts.map +0 -1
- package/dist/validate-Baqf0slj.mjs.map +0 -1
- package/dist/validate-BfQh_C_y.d.mts.map +0 -1
- package/dist/version-DoxrVdYf.mjs +0 -7
- package/dist/zod-generator-CC0xNe_K.mjs.map +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu translation endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/menus/:name/translations — list translations for a menu (uses any locale row)
|
|
5
|
+
* POST /_emdash/api/menus/:name/translations — create a new locale translation (body: { locale, label })
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { requirePerm } from "#api/authorize.js";
|
|
12
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
13
|
+
import { handleMenuCreate, handleMenuGet, handleMenuTranslations } from "#api/handlers/menus.js";
|
|
14
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
15
|
+
import { localeFilterQuery } from "#api/schemas.js";
|
|
16
|
+
|
|
17
|
+
export const prerender = false;
|
|
18
|
+
|
|
19
|
+
const createTranslationBody = z
|
|
20
|
+
.object({
|
|
21
|
+
locale: z.string().min(1),
|
|
22
|
+
label: z.string().min(1).optional(),
|
|
23
|
+
})
|
|
24
|
+
.meta({ id: "CreateMenuTranslationBody" });
|
|
25
|
+
|
|
26
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
27
|
+
const { emdash, user } = locals;
|
|
28
|
+
const name = params.name!;
|
|
29
|
+
|
|
30
|
+
const dbErr = requireDb(emdash?.db);
|
|
31
|
+
if (dbErr) return dbErr;
|
|
32
|
+
|
|
33
|
+
const denied = requirePerm(user, "menus:read");
|
|
34
|
+
if (denied) return denied;
|
|
35
|
+
|
|
36
|
+
const localeQ = parseQuery(new URL(request.url), localeFilterQuery);
|
|
37
|
+
if (isParseError(localeQ)) return localeQ;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Look up any menu row matching the name so we can get its translation_group.
|
|
41
|
+
const anchor = await handleMenuGet(emdash.db, name, { locale: localeQ.locale });
|
|
42
|
+
if (!anchor.success) return unwrapResult(anchor);
|
|
43
|
+
const result = await handleMenuTranslations(emdash.db, anchor.data.id);
|
|
44
|
+
return unwrapResult(result);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return handleError(error, "Failed to fetch menu translations", "MENU_TRANSLATIONS_ERROR");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
51
|
+
const { emdash, user } = locals;
|
|
52
|
+
const name = params.name!;
|
|
53
|
+
|
|
54
|
+
const dbErr = requireDb(emdash?.db);
|
|
55
|
+
if (dbErr) return dbErr;
|
|
56
|
+
|
|
57
|
+
const denied = requirePerm(user, "menus:manage");
|
|
58
|
+
if (denied) return denied;
|
|
59
|
+
|
|
60
|
+
const localeQ = parseQuery(new URL(request.url), localeFilterQuery);
|
|
61
|
+
if (isParseError(localeQ)) return localeQ;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const body = await parseBody(request, createTranslationBody);
|
|
65
|
+
if (isParseError(body)) return body;
|
|
66
|
+
|
|
67
|
+
// Resolve the source menu (either by explicit locale in query, or the
|
|
68
|
+
// first matching row). Its id becomes the `translationOf` for the new row.
|
|
69
|
+
const source = await handleMenuGet(emdash.db, name, { locale: localeQ.locale });
|
|
70
|
+
if (!source.success) return unwrapResult(source);
|
|
71
|
+
|
|
72
|
+
const result = await handleMenuCreate(emdash.db, {
|
|
73
|
+
name,
|
|
74
|
+
label: body.label ?? source.data.label,
|
|
75
|
+
locale: body.locale,
|
|
76
|
+
translationOf: source.data.id,
|
|
77
|
+
});
|
|
78
|
+
return unwrapResult(result, 201);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return handleError(error, "Failed to create menu translation", "MENU_TRANSLATION_CREATE_ERROR");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single menu endpoint
|
|
3
3
|
*
|
|
4
|
-
* GET /_emdash/api/menus/:name
|
|
5
|
-
* PUT /_emdash/api/menus/:name
|
|
6
|
-
* DELETE /_emdash/api/menus/:name
|
|
4
|
+
* GET /_emdash/api/menus/:name[?locale=xx]
|
|
5
|
+
* PUT /_emdash/api/menus/:name[?locale=xx]
|
|
6
|
+
* DELETE /_emdash/api/menus/:name[?locale=xx]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
@@ -11,20 +11,23 @@ import type { APIRoute } from "astro";
|
|
|
11
11
|
import { requirePerm } from "#api/authorize.js";
|
|
12
12
|
import { handleError, unwrapResult } from "#api/error.js";
|
|
13
13
|
import { handleMenuDelete, handleMenuGet, handleMenuUpdate } from "#api/handlers/menus.js";
|
|
14
|
-
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
|
-
import { updateMenuBody } from "#api/schemas.js";
|
|
14
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
15
|
+
import { localeFilterQuery, updateMenuBody } from "#api/schemas.js";
|
|
16
16
|
|
|
17
17
|
export const prerender = false;
|
|
18
18
|
|
|
19
|
-
export const GET: APIRoute = async ({ params, locals }) => {
|
|
19
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
20
20
|
const { emdash, user } = locals;
|
|
21
21
|
const name = params.name!;
|
|
22
22
|
|
|
23
23
|
const denied = requirePerm(user, "menus:read");
|
|
24
24
|
if (denied) return denied;
|
|
25
25
|
|
|
26
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
27
|
+
if (isParseError(query)) return query;
|
|
28
|
+
|
|
26
29
|
try {
|
|
27
|
-
const result = await handleMenuGet(emdash.db, name);
|
|
30
|
+
const result = await handleMenuGet(emdash.db, name, { locale: query.locale });
|
|
28
31
|
return unwrapResult(result);
|
|
29
32
|
} catch (error) {
|
|
30
33
|
return handleError(error, "Failed to fetch menu", "MENU_GET_ERROR");
|
|
@@ -38,26 +41,32 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
38
41
|
const denied = requirePerm(user, "menus:manage");
|
|
39
42
|
if (denied) return denied;
|
|
40
43
|
|
|
44
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
45
|
+
if (isParseError(query)) return query;
|
|
46
|
+
|
|
41
47
|
try {
|
|
42
48
|
const body = await parseBody(request, updateMenuBody);
|
|
43
49
|
if (isParseError(body)) return body;
|
|
44
50
|
|
|
45
|
-
const result = await handleMenuUpdate(emdash.db, name, body);
|
|
51
|
+
const result = await handleMenuUpdate(emdash.db, name, { ...body, locale: query.locale });
|
|
46
52
|
return unwrapResult(result);
|
|
47
53
|
} catch (error) {
|
|
48
54
|
return handleError(error, "Failed to update menu", "MENU_UPDATE_ERROR");
|
|
49
55
|
}
|
|
50
56
|
};
|
|
51
57
|
|
|
52
|
-
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
58
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
53
59
|
const { emdash, user } = locals;
|
|
54
60
|
const name = params.name!;
|
|
55
61
|
|
|
56
62
|
const denied = requirePerm(user, "menus:manage");
|
|
57
63
|
if (denied) return denied;
|
|
58
64
|
|
|
65
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
66
|
+
if (isParseError(query)) return query;
|
|
67
|
+
|
|
59
68
|
try {
|
|
60
|
-
const result = await handleMenuDelete(emdash.db, name);
|
|
69
|
+
const result = await handleMenuDelete(emdash.db, name, { locale: query.locale });
|
|
61
70
|
return unwrapResult(result);
|
|
62
71
|
} catch (error) {
|
|
63
72
|
return handleError(error, "Failed to delete menu", "MENU_DELETE_ERROR");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Menus list and create endpoints
|
|
3
3
|
*
|
|
4
|
-
* GET /_emdash/api/menus - List
|
|
5
|
-
* POST /_emdash/api/menus
|
|
4
|
+
* GET /_emdash/api/menus[?locale=xx] - List menus (optionally filtered by locale)
|
|
5
|
+
* POST /_emdash/api/menus - Create menu (body may include locale & translationOf)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
@@ -10,19 +10,22 @@ import type { APIRoute } from "astro";
|
|
|
10
10
|
import { requirePerm } from "#api/authorize.js";
|
|
11
11
|
import { handleError, unwrapResult } from "#api/error.js";
|
|
12
12
|
import { handleMenuCreate, handleMenuList } from "#api/handlers/menus.js";
|
|
13
|
-
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
|
-
import { createMenuBody } from "#api/schemas.js";
|
|
13
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
14
|
+
import { createMenuBody, localeFilterQuery } from "#api/schemas.js";
|
|
15
15
|
|
|
16
16
|
export const prerender = false;
|
|
17
17
|
|
|
18
|
-
export const GET: APIRoute = async ({ locals }) => {
|
|
18
|
+
export const GET: APIRoute = async ({ request, locals }) => {
|
|
19
19
|
const { emdash, user } = locals;
|
|
20
20
|
|
|
21
21
|
const denied = requirePerm(user, "menus:read");
|
|
22
22
|
if (denied) return denied;
|
|
23
23
|
|
|
24
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
25
|
+
if (isParseError(query)) return query;
|
|
26
|
+
|
|
24
27
|
try {
|
|
25
|
-
const result = await handleMenuList(emdash.db);
|
|
28
|
+
const result = await handleMenuList(emdash.db, { locale: query.locale });
|
|
26
29
|
return unwrapResult(result);
|
|
27
30
|
} catch (error) {
|
|
28
31
|
return handleError(error, "Failed to fetch menus", "MENU_LIST_ERROR");
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Term translation endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/taxonomies/:name/terms/:slug/translations[?locale=xx]
|
|
5
|
+
* POST /_emdash/api/taxonomies/:name/terms/:slug/translations
|
|
6
|
+
* body: { locale, label?, slug? }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import { requirePerm } from "#api/authorize.js";
|
|
13
|
+
import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
14
|
+
import {
|
|
15
|
+
handleTermCreate,
|
|
16
|
+
handleTermGet,
|
|
17
|
+
handleTermTranslations,
|
|
18
|
+
} from "#api/handlers/taxonomies.js";
|
|
19
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
20
|
+
import { localeFilterQuery } from "#api/schemas.js";
|
|
21
|
+
|
|
22
|
+
export const prerender = false;
|
|
23
|
+
|
|
24
|
+
const createTermTranslationBody = z
|
|
25
|
+
.object({
|
|
26
|
+
locale: z.string().min(1),
|
|
27
|
+
label: z.string().min(1).optional(),
|
|
28
|
+
slug: z.string().min(1).optional(),
|
|
29
|
+
})
|
|
30
|
+
.meta({ id: "CreateTermTranslationBody" });
|
|
31
|
+
|
|
32
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
33
|
+
const { emdash, user } = locals;
|
|
34
|
+
const { name, slug } = params;
|
|
35
|
+
if (!name || !slug) return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
36
|
+
|
|
37
|
+
const dbErr = requireDb(emdash?.db);
|
|
38
|
+
if (dbErr) return dbErr;
|
|
39
|
+
|
|
40
|
+
const denied = requirePerm(user, "taxonomies:read");
|
|
41
|
+
if (denied) return denied;
|
|
42
|
+
|
|
43
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
44
|
+
if (isParseError(query)) return query;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const anchor = await handleTermGet(emdash.db, name, slug, { locale: query.locale });
|
|
48
|
+
if (!anchor.success) return unwrapResult(anchor);
|
|
49
|
+
const result = await handleTermTranslations(emdash.db, anchor.data.term.id);
|
|
50
|
+
return unwrapResult(result);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return handleError(error, "Failed to list term translations", "TERM_TRANSLATIONS_ERROR");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
57
|
+
const { emdash, user } = locals;
|
|
58
|
+
const { name, slug } = params;
|
|
59
|
+
if (!name || !slug) return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
60
|
+
|
|
61
|
+
const dbErr = requireDb(emdash?.db);
|
|
62
|
+
if (dbErr) return dbErr;
|
|
63
|
+
|
|
64
|
+
const denied = requirePerm(user, "taxonomies:manage");
|
|
65
|
+
if (denied) return denied;
|
|
66
|
+
|
|
67
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
68
|
+
if (isParseError(query)) return query;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const body = await parseBody(request, createTermTranslationBody);
|
|
72
|
+
if (isParseError(body)) return body;
|
|
73
|
+
|
|
74
|
+
const source = await handleTermGet(emdash.db, name, slug, { locale: query.locale });
|
|
75
|
+
if (!source.success) return unwrapResult(source);
|
|
76
|
+
|
|
77
|
+
const result = await handleTermCreate(emdash.db, name, {
|
|
78
|
+
slug: body.slug ?? source.data.term.slug,
|
|
79
|
+
label: body.label ?? source.data.term.label,
|
|
80
|
+
parentId: source.data.term.parentId,
|
|
81
|
+
description: source.data.term.description,
|
|
82
|
+
locale: body.locale,
|
|
83
|
+
translationOf: source.data.term.id,
|
|
84
|
+
});
|
|
85
|
+
return unwrapResult(result, 201);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return handleError(error, "Failed to create term translation", "TERM_TRANSLATION_CREATE_ERROR");
|
|
88
|
+
}
|
|
89
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single term endpoint
|
|
3
3
|
*
|
|
4
|
-
* GET
|
|
5
|
-
* PUT
|
|
6
|
-
* DELETE /_emdash/api/taxonomies/:name/terms/:slug
|
|
4
|
+
* GET /_emdash/api/taxonomies/:name/terms/:slug[?locale=xx]
|
|
5
|
+
* PUT /_emdash/api/taxonomies/:name/terms/:slug[?locale=xx]
|
|
6
|
+
* DELETE /_emdash/api/taxonomies/:name/terms/:slug[?locale=xx]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
@@ -11,21 +11,18 @@ import type { APIRoute } from "astro";
|
|
|
11
11
|
import { requirePerm } from "#api/authorize.js";
|
|
12
12
|
import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
13
13
|
import { handleTermDelete, handleTermGet, handleTermUpdate } from "#api/handlers/taxonomies.js";
|
|
14
|
-
import { isParseError, parseBody } from "#api/parse.js";
|
|
15
|
-
import { updateTermBody } from "#api/schemas.js";
|
|
14
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
15
|
+
import { localeFilterQuery, updateTermBody } from "#api/schemas.js";
|
|
16
16
|
|
|
17
17
|
export const prerender = false;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Get a single term
|
|
21
21
|
*/
|
|
22
|
-
export const GET: APIRoute = async ({ params, locals }) => {
|
|
22
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
23
23
|
const { emdash, user } = locals;
|
|
24
24
|
const { name, slug } = params;
|
|
25
|
-
|
|
26
|
-
if (!name || !slug) {
|
|
27
|
-
return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
28
|
-
}
|
|
25
|
+
if (!name || !slug) return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
29
26
|
|
|
30
27
|
const dbErr = requireDb(emdash?.db);
|
|
31
28
|
if (dbErr) return dbErr;
|
|
@@ -33,8 +30,11 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
33
30
|
const denied = requirePerm(user, "taxonomies:read");
|
|
34
31
|
if (denied) return denied;
|
|
35
32
|
|
|
33
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
34
|
+
if (isParseError(query)) return query;
|
|
35
|
+
|
|
36
36
|
try {
|
|
37
|
-
const result = await handleTermGet(emdash.db, name, slug);
|
|
37
|
+
const result = await handleTermGet(emdash.db, name, slug, { locale: query.locale });
|
|
38
38
|
return unwrapResult(result);
|
|
39
39
|
} catch (error) {
|
|
40
40
|
return handleError(error, "Failed to get term", "TERM_GET_ERROR");
|
|
@@ -47,10 +47,7 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
47
47
|
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
48
48
|
const { emdash, user } = locals;
|
|
49
49
|
const { name, slug } = params;
|
|
50
|
-
|
|
51
|
-
if (!name || !slug) {
|
|
52
|
-
return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
53
|
-
}
|
|
50
|
+
if (!name || !slug) return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
54
51
|
|
|
55
52
|
const dbErr = requireDb(emdash?.db);
|
|
56
53
|
if (dbErr) return dbErr;
|
|
@@ -58,11 +55,14 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
58
55
|
const denied = requirePerm(user, "taxonomies:manage");
|
|
59
56
|
if (denied) return denied;
|
|
60
57
|
|
|
58
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
59
|
+
if (isParseError(query)) return query;
|
|
60
|
+
|
|
61
61
|
try {
|
|
62
62
|
const body = await parseBody(request, updateTermBody);
|
|
63
63
|
if (isParseError(body)) return body;
|
|
64
64
|
|
|
65
|
-
const result = await handleTermUpdate(emdash.db, name, slug, body);
|
|
65
|
+
const result = await handleTermUpdate(emdash.db, name, slug, body, { locale: query.locale });
|
|
66
66
|
return unwrapResult(result);
|
|
67
67
|
} catch (error) {
|
|
68
68
|
return handleError(error, "Failed to update term", "TERM_UPDATE_ERROR");
|
|
@@ -72,13 +72,10 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
72
72
|
/**
|
|
73
73
|
* Delete a term
|
|
74
74
|
*/
|
|
75
|
-
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
75
|
+
export const DELETE: APIRoute = async ({ params, request, locals }) => {
|
|
76
76
|
const { emdash, user } = locals;
|
|
77
77
|
const { name, slug } = params;
|
|
78
|
-
|
|
79
|
-
if (!name || !slug) {
|
|
80
|
-
return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
81
|
-
}
|
|
78
|
+
if (!name || !slug) return apiError("VALIDATION_ERROR", "Taxonomy name and slug required", 400);
|
|
82
79
|
|
|
83
80
|
const dbErr = requireDb(emdash?.db);
|
|
84
81
|
if (dbErr) return dbErr;
|
|
@@ -86,8 +83,11 @@ export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
|
86
83
|
const denied = requirePerm(user, "taxonomies:manage");
|
|
87
84
|
if (denied) return denied;
|
|
88
85
|
|
|
86
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
87
|
+
if (isParseError(query)) return query;
|
|
88
|
+
|
|
89
89
|
try {
|
|
90
|
-
const result = await handleTermDelete(emdash.db, name, slug);
|
|
90
|
+
const result = await handleTermDelete(emdash.db, name, slug, { locale: query.locale });
|
|
91
91
|
return unwrapResult(result);
|
|
92
92
|
} catch (error) {
|
|
93
93
|
return handleError(error, "Failed to delete term", "TERM_DELETE_ERROR");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Taxonomy terms list and create endpoint
|
|
3
3
|
*
|
|
4
|
-
* GET
|
|
5
|
-
* POST /_emdash/api/taxonomies/:name/terms
|
|
4
|
+
* GET /_emdash/api/taxonomies/:name/terms[?locale=xx] - List terms (tree for hierarchical)
|
|
5
|
+
* POST /_emdash/api/taxonomies/:name/terms - Create a new term (body may include locale & translationOf)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
@@ -10,21 +10,18 @@ import type { APIRoute } from "astro";
|
|
|
10
10
|
import { requirePerm } from "#api/authorize.js";
|
|
11
11
|
import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
12
12
|
import { handleTermCreate, handleTermList } from "#api/handlers/taxonomies.js";
|
|
13
|
-
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
|
-
import { createTermBody } from "#api/schemas.js";
|
|
13
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
14
|
+
import { createTermBody, localeFilterQuery } from "#api/schemas.js";
|
|
15
15
|
|
|
16
16
|
export const prerender = false;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* List all terms for a taxonomy
|
|
20
20
|
*/
|
|
21
|
-
export const GET: APIRoute = async ({ params, locals }) => {
|
|
21
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
22
22
|
const { emdash, user } = locals;
|
|
23
23
|
const { name } = params;
|
|
24
|
-
|
|
25
|
-
if (!name) {
|
|
26
|
-
return apiError("VALIDATION_ERROR", "Taxonomy name required", 400);
|
|
27
|
-
}
|
|
24
|
+
if (!name) return apiError("VALIDATION_ERROR", "Taxonomy name required", 400);
|
|
28
25
|
|
|
29
26
|
const dbErr = requireDb(emdash?.db);
|
|
30
27
|
if (dbErr) return dbErr;
|
|
@@ -32,8 +29,11 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
32
29
|
const denied = requirePerm(user, "taxonomies:read");
|
|
33
30
|
if (denied) return denied;
|
|
34
31
|
|
|
32
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
33
|
+
if (isParseError(query)) return query;
|
|
34
|
+
|
|
35
35
|
try {
|
|
36
|
-
const result = await handleTermList(emdash.db, name);
|
|
36
|
+
const result = await handleTermList(emdash.db, name, { locale: query.locale });
|
|
37
37
|
return unwrapResult(result);
|
|
38
38
|
} catch (error) {
|
|
39
39
|
return handleError(error, "Failed to list terms", "TERM_LIST_ERROR");
|
|
@@ -46,10 +46,7 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
46
46
|
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
47
47
|
const { emdash, user } = locals;
|
|
48
48
|
const { name } = params;
|
|
49
|
-
|
|
50
|
-
if (!name) {
|
|
51
|
-
return apiError("VALIDATION_ERROR", "Taxonomy name required", 400);
|
|
52
|
-
}
|
|
49
|
+
if (!name) return apiError("VALIDATION_ERROR", "Taxonomy name required", 400);
|
|
53
50
|
|
|
54
51
|
const dbErr = requireDb(emdash?.db);
|
|
55
52
|
if (dbErr) return dbErr;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Taxonomy definitions endpoint
|
|
3
3
|
*
|
|
4
|
-
* GET /_emdash/api/taxonomies - List
|
|
5
|
-
* POST /_emdash/api/taxonomies
|
|
4
|
+
* GET /_emdash/api/taxonomies[?locale=xx] - List taxonomy definitions
|
|
5
|
+
* POST /_emdash/api/taxonomies - Create a custom taxonomy definition
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
@@ -10,15 +10,15 @@ import type { APIRoute } from "astro";
|
|
|
10
10
|
import { requirePerm } from "#api/authorize.js";
|
|
11
11
|
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
12
12
|
import { handleTaxonomyCreate, handleTaxonomyList } from "#api/handlers/taxonomies.js";
|
|
13
|
-
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
|
-
import { createTaxonomyDefBody } from "#api/schemas.js";
|
|
13
|
+
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
14
|
+
import { createTaxonomyDefBody, localeFilterQuery } from "#api/schemas.js";
|
|
15
15
|
|
|
16
16
|
export const prerender = false;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* List taxonomy definitions
|
|
20
20
|
*/
|
|
21
|
-
export const GET: APIRoute = async ({ locals }) => {
|
|
21
|
+
export const GET: APIRoute = async ({ request, locals }) => {
|
|
22
22
|
const { emdash, user } = locals;
|
|
23
23
|
|
|
24
24
|
const dbErr = requireDb(emdash?.db);
|
|
@@ -27,8 +27,11 @@ export const GET: APIRoute = async ({ locals }) => {
|
|
|
27
27
|
const denied = requirePerm(user, "taxonomies:read");
|
|
28
28
|
if (denied) return denied;
|
|
29
29
|
|
|
30
|
+
const query = parseQuery(new URL(request.url), localeFilterQuery);
|
|
31
|
+
if (isParseError(query)) return query;
|
|
32
|
+
|
|
30
33
|
try {
|
|
31
|
-
const result = await handleTaxonomyList(emdash.db);
|
|
34
|
+
const result = await handleTaxonomyList(emdash.db, { locale: query.locale });
|
|
32
35
|
return unwrapResult(result);
|
|
33
36
|
} catch (error) {
|
|
34
37
|
return handleError(error, "Failed to list taxonomies", "TAXONOMY_LIST_ERROR");
|
|
@@ -212,41 +212,69 @@ async function exportCollections(db: Kysely<Database>): Promise<SeedCollection[]
|
|
|
212
212
|
* Export taxonomy definitions and terms
|
|
213
213
|
*/
|
|
214
214
|
async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
const i18nEnabled = isI18nEnabled();
|
|
216
|
+
|
|
217
|
+
// Mirrors the content export pattern: one entry per (name, locale), stable
|
|
218
|
+
// seed-local id, translations linked via `translationOf` to the anchor's id.
|
|
219
|
+
const defs = await db
|
|
220
|
+
.selectFrom("_emdash_taxonomy_defs")
|
|
221
|
+
.selectAll()
|
|
222
|
+
.orderBy(["name", "locale"])
|
|
223
|
+
.execute();
|
|
217
224
|
|
|
218
225
|
const result: SeedTaxonomy[] = [];
|
|
219
226
|
const termRepo = new TaxonomyRepository(db);
|
|
220
227
|
|
|
228
|
+
// translation_group -> seed-local id of first def we emitted in that group.
|
|
229
|
+
const defGroupToSeedId = new Map<string, string>();
|
|
230
|
+
|
|
221
231
|
for (const def of defs) {
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
const defSeedId =
|
|
233
|
+
i18nEnabled && def.locale ? `tax:${def.name}:${def.locale}` : `tax:${def.name}`;
|
|
224
234
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
235
|
+
// Terms in this def's locale.
|
|
236
|
+
const terms = await termRepo.findByName(def.name, { locale: def.locale });
|
|
227
237
|
|
|
228
|
-
//
|
|
238
|
+
// id -> slug for parent resolution within this locale.
|
|
229
239
|
const idToSlug = new Map<string, string>();
|
|
230
|
-
for (const term of terms)
|
|
231
|
-
|
|
232
|
-
|
|
240
|
+
for (const term of terms) idToSlug.set(term.id, term.slug);
|
|
241
|
+
|
|
242
|
+
// translation_group -> seed id of the anchor term.
|
|
243
|
+
const termGroupToSeedId = new Map<string, string>();
|
|
233
244
|
|
|
245
|
+
const seedTerms: SeedTaxonomyTerm[] = [];
|
|
234
246
|
for (const term of terms) {
|
|
247
|
+
const termSeedId =
|
|
248
|
+
i18nEnabled && term.locale
|
|
249
|
+
? `term:${def.name}:${term.slug}:${term.locale}`
|
|
250
|
+
: `term:${def.name}:${term.slug}`;
|
|
251
|
+
|
|
235
252
|
const seedTerm: SeedTaxonomyTerm = {
|
|
253
|
+
id: termSeedId,
|
|
236
254
|
slug: term.slug,
|
|
237
255
|
label: term.label,
|
|
238
256
|
description: typeof term.data?.description === "string" ? term.data.description : undefined,
|
|
239
257
|
};
|
|
240
258
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
259
|
+
if (term.parentId) seedTerm.parent = idToSlug.get(term.parentId);
|
|
260
|
+
|
|
261
|
+
if (i18nEnabled && term.locale) {
|
|
262
|
+
seedTerm.locale = term.locale;
|
|
263
|
+
if (term.translationGroup) {
|
|
264
|
+
const anchor = termGroupToSeedId.get(term.translationGroup);
|
|
265
|
+
if (anchor) seedTerm.translationOf = anchor;
|
|
266
|
+
else termGroupToSeedId.set(term.translationGroup, termSeedId);
|
|
267
|
+
}
|
|
244
268
|
}
|
|
245
269
|
|
|
246
270
|
seedTerms.push(seedTerm);
|
|
247
271
|
}
|
|
248
272
|
|
|
273
|
+
// Anchors first so import can resolve `translationOf`.
|
|
274
|
+
seedTerms.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
275
|
+
|
|
249
276
|
const taxonomy: SeedTaxonomy = {
|
|
277
|
+
id: defSeedId,
|
|
250
278
|
name: def.name,
|
|
251
279
|
label: def.label,
|
|
252
280
|
labelSingular: def.label_singular || undefined,
|
|
@@ -254,13 +282,23 @@ async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
|
254
282
|
collections: def.collections ? JSON.parse(def.collections) : [],
|
|
255
283
|
};
|
|
256
284
|
|
|
257
|
-
if (
|
|
258
|
-
taxonomy.
|
|
285
|
+
if (i18nEnabled && def.locale) {
|
|
286
|
+
taxonomy.locale = def.locale;
|
|
287
|
+
if (def.translation_group) {
|
|
288
|
+
const anchor = defGroupToSeedId.get(def.translation_group);
|
|
289
|
+
if (anchor) taxonomy.translationOf = anchor;
|
|
290
|
+
else defGroupToSeedId.set(def.translation_group, defSeedId);
|
|
291
|
+
}
|
|
259
292
|
}
|
|
260
293
|
|
|
294
|
+
if (seedTerms.length > 0) taxonomy.terms = seedTerms;
|
|
295
|
+
|
|
261
296
|
result.push(taxonomy);
|
|
262
297
|
}
|
|
263
298
|
|
|
299
|
+
// Anchors first at def level too.
|
|
300
|
+
result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
301
|
+
|
|
264
302
|
return result;
|
|
265
303
|
}
|
|
266
304
|
|
|
@@ -268,13 +306,22 @@ async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
|
268
306
|
* Export menus with their items
|
|
269
307
|
*/
|
|
270
308
|
async function exportMenus(db: Kysely<Database>): Promise<SeedMenu[]> {
|
|
271
|
-
|
|
272
|
-
|
|
309
|
+
const i18nEnabled = isI18nEnabled();
|
|
310
|
+
|
|
311
|
+
const menus = await db
|
|
312
|
+
.selectFrom("_emdash_menus")
|
|
313
|
+
.selectAll()
|
|
314
|
+
.orderBy(["name", "locale"])
|
|
315
|
+
.execute();
|
|
273
316
|
|
|
274
317
|
const result: SeedMenu[] = [];
|
|
318
|
+
// translation_group -> seed-local id of the anchor menu in that group.
|
|
319
|
+
const groupToSeedId = new Map<string, string>();
|
|
275
320
|
|
|
276
321
|
for (const menu of menus) {
|
|
277
|
-
|
|
322
|
+
const seedId =
|
|
323
|
+
i18nEnabled && menu.locale ? `menu:${menu.name}:${menu.locale}` : `menu:${menu.name}`;
|
|
324
|
+
|
|
278
325
|
const items = await db
|
|
279
326
|
.selectFrom("_emdash_menu_items")
|
|
280
327
|
.selectAll()
|
|
@@ -282,16 +329,30 @@ async function exportMenus(db: Kysely<Database>): Promise<SeedMenu[]> {
|
|
|
282
329
|
.orderBy("sort_order", "asc")
|
|
283
330
|
.execute();
|
|
284
331
|
|
|
285
|
-
// Build item tree
|
|
286
332
|
const seedItems = buildMenuItemTree(items);
|
|
287
333
|
|
|
288
|
-
|
|
334
|
+
const seedMenu: SeedMenu = {
|
|
335
|
+
id: seedId,
|
|
289
336
|
name: menu.name,
|
|
290
337
|
label: menu.label,
|
|
291
338
|
items: seedItems,
|
|
292
|
-
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
if (i18nEnabled && menu.locale) {
|
|
342
|
+
seedMenu.locale = menu.locale;
|
|
343
|
+
if (menu.translation_group) {
|
|
344
|
+
const anchor = groupToSeedId.get(menu.translation_group);
|
|
345
|
+
if (anchor) seedMenu.translationOf = anchor;
|
|
346
|
+
else groupToSeedId.set(menu.translation_group, seedId);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
result.push(seedMenu);
|
|
293
351
|
}
|
|
294
352
|
|
|
353
|
+
// Anchors first so import can resolve `translationOf`.
|
|
354
|
+
result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
355
|
+
|
|
295
356
|
return result;
|
|
296
357
|
}
|
|
297
358
|
|