@cosmicdrift/kumiko-bundled-features 0.374.0 → 0.378.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.
Files changed (260) hide show
  1. package/dist/account-security/constants.d.ts +1 -0
  2. package/dist/account-security/constants.js +1 -0
  3. package/dist/account-security/index.d.ts +1 -1
  4. package/dist/account-security/index.js +1 -1
  5. package/dist/admin-shell/web/client-plugin.d.ts +1 -1
  6. package/dist/audit/web/client-plugin.d.ts +1 -1
  7. package/dist/auth-email-password/mail-notifications.js +1 -1
  8. package/dist/auth-email-password/web/auth-client.js +1 -1
  9. package/dist/auth-email-password/web/auth-form-primitives.js +1 -1
  10. package/dist/auth-email-password/web/default-topbar-actions.js +1 -1
  11. package/dist/auth-email-password/web/index.js +1 -1
  12. package/dist/auth-email-password/web/session.js +1 -1
  13. package/dist/auth-email-password/web/tenant-menu-items.js +1 -1
  14. package/dist/auth-email-password/web/tenant-switcher.js +1 -1
  15. package/dist/auth-email-password/web/user-menu.js +1 -1
  16. package/dist/auth-foundation/env.d.ts +9 -0
  17. package/dist/auth-foundation/env.js +21 -0
  18. package/dist/auth-foundation/index.d.ts +1 -0
  19. package/dist/auth-foundation/index.js +1 -0
  20. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -6
  21. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +1 -1
  22. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -1
  23. package/dist/billing-foundation/contact-card.d.ts +2 -0
  24. package/dist/billing-foundation/contact-card.js +16 -0
  25. package/dist/billing-foundation/entities.d.ts +8 -8
  26. package/dist/billing-foundation/handlers/billing-plans.query.js +1 -1
  27. package/dist/billing-foundation/index.d.ts +1 -1
  28. package/dist/billing-foundation/plan-catalog.js +1 -0
  29. package/dist/billing-foundation/projection.d.ts +8 -8
  30. package/dist/billing-foundation/types.d.ts +11 -0
  31. package/dist/billing-foundation/validate-options.js +13 -0
  32. package/dist/billing-foundation/web/billing-contact-plan.d.ts +5 -0
  33. package/dist/billing-foundation/web/billing-contact-plan.js +17 -0
  34. package/dist/billing-foundation/web/billing-plans-panel.js +5 -13
  35. package/dist/billing-foundation/web/cancel-contract-dialog.js +2 -2
  36. package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +1 -1
  37. package/dist/billing-foundation/web/checkout-consent-dialog.js +1 -0
  38. package/dist/billing-foundation/web/client-plugin.d.ts +1 -1
  39. package/dist/billing-foundation/web/localized-params.d.ts +1 -0
  40. package/dist/billing-foundation/web/localized-params.js +11 -0
  41. package/dist/cap-counter/entity.d.ts +1 -1
  42. package/dist/cap-counter/stock-cap-guard.js +8 -0
  43. package/dist/cap-overview/web/cap-cards-panel.js +3 -3
  44. package/dist/cap-overview/web/client-plugin.d.ts +1 -1
  45. package/dist/channel-email/email-channel.d.ts +2 -1
  46. package/dist/compliance-profiles/web/client-plugin.d.ts +1 -1
  47. package/dist/config/feature.js +2 -2
  48. package/dist/config/table.d.ts +6 -6
  49. package/dist/config/web/i18n.js +1 -0
  50. package/dist/custom-fields/entity.d.ts +4 -4
  51. package/dist/custom-fields/web/client-plugin.d.ts +1 -1
  52. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -4
  53. package/dist/delivery/tables.d.ts +6 -6
  54. package/dist/delivery/unsubscribe-page.js +1 -1
  55. package/dist/delivery/web/client-plugin.d.ts +1 -1
  56. package/dist/delivery/web/delivery-status-cell.js +1 -1
  57. package/dist/document-ingest-foundation/entity.d.ts +4 -4
  58. package/dist/feature-toggles/web/client-plugin.d.ts +1 -1
  59. package/dist/file-provider-s3/env-named-feature.d.ts +17 -0
  60. package/dist/file-provider-s3/env-named-feature.js +23 -0
  61. package/dist/file-provider-s3/env-schema.d.ts +7 -0
  62. package/dist/file-provider-s3/env-schema.js +15 -10
  63. package/dist/file-provider-s3/index.d.ts +1 -0
  64. package/dist/file-provider-s3/index.js +1 -0
  65. package/dist/folders/entity.d.ts +8 -8
  66. package/dist/folders/web/client-plugin.d.ts +1 -1
  67. package/dist/form-draft/entity.d.ts +2 -2
  68. package/dist/host-routing/constants.d.ts +2 -0
  69. package/dist/host-routing/constants.js +3 -0
  70. package/dist/host-routing/dispatch.d.ts +1 -1
  71. package/dist/host-routing/dispatch.js +35 -25
  72. package/dist/host-routing/feature.js +100 -27
  73. package/dist/host-routing/host.d.ts +9 -0
  74. package/dist/host-routing/host.js +24 -2
  75. package/dist/host-routing/index.d.ts +2 -1
  76. package/dist/host-routing/index.js +1 -0
  77. package/dist/host-routing/options.d.ts +14 -0
  78. package/dist/host-routing/redirects.d.ts +6 -0
  79. package/dist/host-routing/redirects.js +27 -0
  80. package/dist/host-routing/tenant-lookup.d.ts +7 -0
  81. package/dist/host-routing/tenant-lookup.js +39 -0
  82. package/dist/host-routing/validate-options.js +34 -1
  83. package/dist/inbound-mail-foundation/entities.d.ts +25 -25
  84. package/dist/inbound-mail-foundation/feature.js +7 -0
  85. package/dist/inbound-mail-foundation/handlers/list-accounts.query.d.ts +1 -1
  86. package/dist/inbound-mail-foundation/handlers/list-accounts.query.js +11 -3
  87. package/dist/inbound-mail-foundation/i18n.d.ts +3 -0
  88. package/dist/inbound-mail-foundation/i18n.js +35 -0
  89. package/dist/inbound-mail-foundation/index.d.ts +1 -0
  90. package/dist/inbound-mail-foundation/index.js +1 -0
  91. package/dist/inbound-mail-foundation/projection.d.ts +20 -20
  92. package/dist/inbound-mail-foundation/screens.d.ts +7 -0
  93. package/dist/inbound-mail-foundation/screens.js +86 -0
  94. package/dist/inbound-mail-foundation/types.d.ts +2 -2
  95. package/dist/jobs/job-run-table.d.ts +6 -6
  96. package/dist/jobs/web/client-plugin.d.ts +1 -1
  97. package/dist/ledger/entity.d.ts +13 -13
  98. package/dist/legal-pages/feature.js +11 -1
  99. package/dist/legal-pages/layout.d.ts +2 -2
  100. package/dist/legal-pages/layout.js +4 -4
  101. package/dist/legal-pages/web/client-plugin.d.ts +1 -1
  102. package/dist/mail-foundation/feature.d.ts +20 -21
  103. package/dist/mail-foundation/feature.js +1 -1
  104. package/dist/mail-foundation/i18n.d.ts +5 -2
  105. package/dist/mail-foundation/i18n.js +3 -2
  106. package/dist/mail-transport-smtp/i18n.d.ts +1 -1
  107. package/dist/mail-transport-smtp/i18n.js +1 -1
  108. package/dist/managed-pages/table.d.ts +12 -12
  109. package/dist/managed-pages/web/client-plugin.d.ts +1 -1
  110. package/dist/notes-history/entity.d.ts +12 -12
  111. package/dist/notes-history/web/client-plugin.d.ts +1 -1
  112. package/dist/page-render/branding.js +1 -1
  113. package/dist/page-render/layout.d.ts +1 -1
  114. package/dist/page-render/layout.js +2 -2
  115. package/dist/page-render/markdown.js +1 -1
  116. package/dist/personal-access-tokens/i18n.js +1 -1
  117. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -6
  118. package/dist/personal-access-tokens/screens.js +1 -1
  119. package/dist/secrets/feature.d.ts +1 -1
  120. package/dist/secrets/feature.js +3 -3
  121. package/dist/secrets/table.d.ts +1 -1
  122. package/dist/seo/sitemap.js +1 -1
  123. package/dist/sessions/schema/user-session.d.ts +4 -4
  124. package/dist/shared/mail-template-body.js +1 -1
  125. package/dist/shared/purge-files-reaching-subject.js +98 -3
  126. package/dist/tags/entity.d.ts +9 -9
  127. package/dist/tags/web/client-plugin.d.ts +1 -1
  128. package/dist/template-resolver/compile-document-body.d.ts +3 -0
  129. package/dist/template-resolver/compile-document-body.js +50 -3
  130. package/dist/template-resolver/constants.d.ts +3 -0
  131. package/dist/template-resolver/constants.js +2 -0
  132. package/dist/template-resolver/document-blocks.d.ts +5 -0
  133. package/dist/template-resolver/document-blocks.js +22 -0
  134. package/dist/template-resolver/document-images.d.ts +2 -0
  135. package/dist/template-resolver/document-images.js +28 -0
  136. package/dist/template-resolver/document-ops.d.ts +167 -0
  137. package/dist/template-resolver/document-ops.js +390 -0
  138. package/dist/template-resolver/document-sanitizer.d.ts +4 -1
  139. package/dist/template-resolver/document-sanitizer.js +26 -0
  140. package/dist/template-resolver/document-schema.d.ts +2 -1
  141. package/dist/template-resolver/document-schema.js +4 -0
  142. package/dist/template-resolver/feature.d.ts +11 -0
  143. package/dist/template-resolver/feature.js +60 -31
  144. package/dist/template-resolver/handlers/bundle-import.js +1 -0
  145. package/dist/template-resolver/handlers/collection-item.query.js +2 -1
  146. package/dist/template-resolver/handlers/collection-list.query.js +2 -1
  147. package/dist/template-resolver/handlers/collection-set.write.js +2 -2
  148. package/dist/template-resolver/handlers/collection-shared.d.ts +1 -2
  149. package/dist/template-resolver/handlers/collection-shared.js +0 -4
  150. package/dist/template-resolver/handlers/copy-locale.write.d.ts +2 -1
  151. package/dist/template-resolver/handlers/copy-locale.write.js +67 -63
  152. package/dist/template-resolver/handlers/discard-draft.write.d.ts +3 -2
  153. package/dist/template-resolver/handlers/discard-draft.write.js +18 -6
  154. package/dist/template-resolver/handlers/document-base.d.ts +23 -0
  155. package/dist/template-resolver/handlers/document-base.js +55 -0
  156. package/dist/template-resolver/handlers/document-blocks.query.d.ts +22 -0
  157. package/dist/template-resolver/handlers/document-blocks.query.js +46 -0
  158. package/dist/template-resolver/handlers/export-bundle.query.d.ts +1 -0
  159. package/dist/template-resolver/handlers/export-bundle.query.js +9 -2
  160. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -0
  161. package/dist/template-resolver/handlers/import-bundle.write.d.ts +3 -1
  162. package/dist/template-resolver/handlers/import-bundle.write.js +30 -25
  163. package/dist/template-resolver/handlers/overview.query.d.ts +2 -1
  164. package/dist/template-resolver/handlers/overview.query.js +46 -42
  165. package/dist/template-resolver/handlers/patch-draft.write.d.ts +67 -0
  166. package/dist/template-resolver/handlers/patch-draft.write.js +111 -0
  167. package/dist/template-resolver/handlers/preview.query.d.ts +1 -0
  168. package/dist/template-resolver/handlers/preview.query.js +2 -1
  169. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +2 -1
  170. package/dist/template-resolver/handlers/reset-to-system.write.js +51 -44
  171. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +2 -1
  172. package/dist/template-resolver/handlers/resource-link-pdf.write.js +34 -28
  173. package/dist/template-resolver/handlers/resource-remove.write.d.ts +2 -1
  174. package/dist/template-resolver/handlers/resource-remove.write.js +39 -33
  175. package/dist/template-resolver/handlers/resource-upload.write.d.ts +2 -1
  176. package/dist/template-resolver/handlers/resource-upload.write.js +104 -96
  177. package/dist/template-resolver/handlers/send-test-shared.d.ts +5 -0
  178. package/dist/template-resolver/handlers/send-test-shared.js +19 -0
  179. package/dist/template-resolver/handlers/send-test-status.query.d.ts +9 -0
  180. package/dist/template-resolver/handlers/send-test-status.query.js +18 -0
  181. package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
  182. package/dist/template-resolver/handlers/send-test.write.js +87 -88
  183. package/dist/template-resolver/handlers/set.write.d.ts +2 -1
  184. package/dist/template-resolver/handlers/set.write.js +94 -90
  185. package/dist/template-resolver/handlers/template-write.d.ts +2 -0
  186. package/dist/template-resolver/handlers/template-write.js +14 -2
  187. package/dist/template-resolver/handlers/toggle-status.write.d.ts +6 -7
  188. package/dist/template-resolver/handlers/toggle-status.write.js +28 -10
  189. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +2 -1
  190. package/dist/template-resolver/handlers/upsert-tenant.write.js +30 -24
  191. package/dist/template-resolver/handlers/validate.query.d.ts +10 -4
  192. package/dist/template-resolver/handlers/validate.query.js +12 -7
  193. package/dist/template-resolver/index.d.ts +4 -2
  194. package/dist/template-resolver/index.js +3 -1
  195. package/dist/template-resolver/{web/placeholder-text.js → placeholder-text.js} +1 -1
  196. package/dist/template-resolver/qualified-names.d.ts +4 -0
  197. package/dist/template-resolver/qualified-names.js +4 -0
  198. package/dist/template-resolver/seeding.d.ts +1 -0
  199. package/dist/template-resolver/seeding.js +2 -1
  200. package/dist/template-resolver/table.d.ts +10 -10
  201. package/dist/template-resolver/template-bundle.d.ts +1 -0
  202. package/dist/template-resolver/template-bundle.js +6 -1
  203. package/dist/template-resolver/template-edit-policy.d.ts +22 -0
  204. package/dist/template-resolver/template-edit-policy.js +45 -0
  205. package/dist/template-resolver/template-preview.d.ts +4 -1
  206. package/dist/template-resolver/template-preview.js +23 -5
  207. package/dist/template-resolver/template-validation.d.ts +2 -1
  208. package/dist/template-resolver/template-validation.js +34 -14
  209. package/dist/template-resolver/user-content-table.d.ts +10 -10
  210. package/dist/template-resolver/web/client-plugin.d.ts +1 -1
  211. package/dist/template-resolver/web/client-plugin.js +3 -3
  212. package/dist/template-resolver/web/document-editor-extensions.d.ts +1 -0
  213. package/dist/template-resolver/web/document-editor-extensions.js +21 -1
  214. package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
  215. package/dist/template-resolver/web/document-tiptap-editor.js +23 -7
  216. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +4 -5
  217. package/dist/template-resolver/web/document-toolbar-blocks.js +1 -21
  218. package/dist/template-resolver/web/i18n.js +18 -0
  219. package/dist/template-resolver/web/placeholder-line-editor.js +2 -2
  220. package/dist/tenant/invitation-table.d.ts +6 -6
  221. package/dist/tenant/membership-table.d.ts +4 -4
  222. package/dist/tenant/schema/tenant.d.ts +6 -6
  223. package/dist/tenant/web/client-plugin.d.ts +1 -1
  224. package/dist/tenant/web/member-status-cell.js +1 -1
  225. package/dist/tier-engine/entity.d.ts +1 -1
  226. package/dist/tier-engine/tier-resolver.d.ts +1 -1
  227. package/dist/tier-engine/web/client-plugin.d.ts +1 -1
  228. package/dist/user/schema/user.d.ts +12 -12
  229. package/dist/user-data-rights/env.d.ts +5 -0
  230. package/dist/user-data-rights/env.js +11 -0
  231. package/dist/user-data-rights/feature.js +4 -2
  232. package/dist/user-data-rights/index.d.ts +1 -0
  233. package/dist/user-data-rights/index.js +1 -0
  234. package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
  235. package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
  236. package/dist/user-data-rights/mail-notifications.js +1 -1
  237. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -14
  238. package/dist/user-data-rights/schema/download-token.d.ts +8 -8
  239. package/dist/user-data-rights/schema/export-job.d.ts +6 -6
  240. package/dist/user-data-rights/web/client-plugin.d.ts +1 -1
  241. package/dist/user-data-rights/web/privacy-center-screen.js +2 -2
  242. package/dist/waitlist/entity.d.ts +14 -14
  243. package/dist/waitlist/legacy-migrate.d.ts +2 -0
  244. package/dist/waitlist/legacy-migrate.js +38 -3
  245. package/package.json +16 -11
  246. package/src/account-security/changes.json +6 -0
  247. package/src/auth-foundation/changes.json +6 -0
  248. package/src/billing-foundation/changes.json +6 -0
  249. package/src/cap-counter/changes.json +12 -0
  250. package/src/channel-email/changes.json +6 -0
  251. package/src/crypto-shredding/changes.json +6 -0
  252. package/src/file-provider-s3/changes.json +6 -0
  253. package/src/host-routing/changes.json +6 -0
  254. package/src/inbound-mail-foundation/changes.json +13 -0
  255. package/src/legal-pages/changes.json +6 -0
  256. package/src/mail-foundation/changes.json +6 -0
  257. package/src/template-resolver/changes.json +38 -0
  258. package/src/user-data-rights/changes.json +6 -0
  259. package/src/waitlist/changes.json +6 -0
  260. /package/dist/template-resolver/{web/placeholder-text.d.ts → placeholder-text.d.ts} +0 -0
@@ -0,0 +1,46 @@
1
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { NotFoundError } from "@cosmicdrift/kumiko-framework/errors";
3
+ import * as z from "zod";
4
+ import { DOCUMENT_HTML_KINDS } from "../constants.js";
5
+ import { documentBlocksForKind } from "../document-blocks.js";
6
+ import { documentBlocksOf } from "../document-ops.js";
7
+ import { parseDocumentJson } from "../document-sanitizer.js";
8
+ import { resolveTemplateVariables } from "../template-validation.js";
9
+ import { loadDocumentBase } from "./document-base.js";
10
+ import { localeSchema, templateSlugSchema } from "./shared.js";
11
+ import { knownResourceKeysFor } from "./validate.query.js";
12
+ export const documentBlocksQuery = defineQueryHandler({
13
+ name: "document-blocks",
14
+ schema: z.object({
15
+ slug: templateSlugSchema,
16
+ kind: z.enum(DOCUMENT_HTML_KINDS),
17
+ locale: localeSchema,
18
+ }),
19
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
20
+ description: "Reads a mail or PDF document template as a list of blocks with stable ids (b0, b1, ...) for structured editing: each block has its type (paragraph, heading, list, table, repeat, image, button, divider, pageBreak, steps), its text with {{name}} placeholders and its settings, plus the meta, the stored and effective variables (variablesDeclaredByFeature is true when a notification declares them, so they cannot be changed), the block types allowed for the kind and the uploaded image keys. It reads the caller's own version (the open draft if there is one), else the served system default; base carries the scope and version that patch-draft expects back.",
21
+ handler: async (query, ctx) => {
22
+ const { slug, kind, locale } = query.payload;
23
+ const loaded = await loadDocumentBase(ctx, query.user, { slug, kind, locale });
24
+ if (loaded === null)
25
+ throw new NotFoundError("template-resource", slug);
26
+ return {
27
+ slug,
28
+ kind,
29
+ locale,
30
+ base: loaded.base,
31
+ status: loaded.status,
32
+ blocks: documentBlocksOf(parseDocumentJson(loaded.content)),
33
+ meta: loaded.meta,
34
+ variableSchema: loaded.variableSchema,
35
+ variables: resolveTemplateVariables(ctx.registry, slug, loaded.variableSchema),
36
+ variablesDeclaredByFeature: ctx.registry.getAllNotifications().get(slug)?.variables !== undefined,
37
+ allowedBlocks: documentBlocksForKind(kind),
38
+ resourceKeys: await knownResourceKeysFor(ctx, query.user, {
39
+ slug,
40
+ kind,
41
+ scope: "tenant",
42
+ locale,
43
+ }),
44
+ };
45
+ },
46
+ });
@@ -21,6 +21,7 @@ export declare const exportBundleQuery: import("@cosmicdrift/kumiko-types/define
21
21
  locales: Record<string, {
22
22
  body: string;
23
23
  meta?: unknown;
24
+ title?: string;
24
25
  }>;
25
26
  resources: Record<string, {
26
27
  fileName: string;
@@ -55,6 +55,14 @@ function assertLocalesShareFormat(rows) {
55
55
  ],
56
56
  });
57
57
  }
58
+ function localeEntryOf(row) {
59
+ const meta = parseStoredJson(row.meta);
60
+ return {
61
+ body: row.content ?? "",
62
+ ...(meta === undefined ? {} : { meta }),
63
+ ...(row.title === null ? {} : { title: row.title }),
64
+ };
65
+ }
58
66
  export const exportBundleQuery = defineQueryHandler({
59
67
  name: "export-bundle",
60
68
  schema: z.object({
@@ -79,8 +87,7 @@ export const exportBundleQuery = defineQueryHandler({
79
87
  const locales = {};
80
88
  const entryByKey = new Map();
81
89
  for (const row of rows) {
82
- const meta = parseStoredJson(row.meta);
83
- locales[row.locale] = { body: row.content ?? "", ...(meta === undefined ? {} : { meta }) };
90
+ locales[row.locale] = localeEntryOf(row);
84
91
  for (const [key, entry] of Object.entries(parseLinkedResourceEntries(row.linkedResources))) {
85
92
  if (!entryByKey.has(key))
86
93
  entryByKey.set(key, entry);
@@ -20,6 +20,7 @@ export declare const importBundleSystemWrite: import("@cosmicdrift/kumiko-types/
20
20
  locales: z.ZodRecord<z.ZodString, z.ZodObject<{
21
21
  body: z.ZodString;
22
22
  meta: z.ZodOptional<z.ZodUnknown>;
23
+ title: z.ZodOptional<z.ZodString>;
23
24
  }, z.core.$strict>>;
24
25
  resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
25
26
  fileName: z.ZodString;
@@ -1,5 +1,6 @@
1
1
  import * as z from "zod";
2
- export declare const importBundleWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"import-bundle", z.ZodObject<{
2
+ import { type TemplateEditPolicy } from "../template-edit-policy.ts";
3
+ export declare function makeImportBundleWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"import-bundle", z.ZodObject<{
3
4
  bundle: z.ZodObject<{
4
5
  slug: z.ZodString;
5
6
  kind: z.ZodEnum<{
@@ -20,6 +21,7 @@ export declare const importBundleWrite: import("@cosmicdrift/kumiko-types/define
20
21
  locales: z.ZodRecord<z.ZodString, z.ZodObject<{
21
22
  body: z.ZodString;
22
23
  meta: z.ZodOptional<z.ZodUnknown>;
24
+ title: z.ZodOptional<z.ZodString>;
23
25
  }, z.core.$strict>>;
24
26
  resources: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
25
27
  fileName: z.ZodString;
@@ -1,29 +1,34 @@
1
1
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import * as z from "zod";
3
3
  import { templateBundleSchema } from "../template-bundle.js";
4
+ import { refuseTemplateEdit, templateEditAccess, } from "../template-edit-policy.js";
4
5
  import { importTemplateBundle, prepareTemplateBundleBeforeTransaction } from "./bundle-import.js";
5
- export const importBundleWrite = defineWriteHandler({
6
- name: "import-bundle",
7
- schema: z.object({ bundle: templateBundleSchema, overwrite: z.boolean().default(false) }),
8
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
9
- description: "Imports a template bundle (all locales plus resource files, as produced by export-bundle) into the caller's tenant as drafts; it refuses a locale that already has an override unless overwrite is set, and publish makes the drafts live.",
10
- beforeTransaction: (event, ctx) => prepareTemplateBundleBeforeTransaction({
11
- ctx,
12
- user: event.user,
13
- tenantId: event.user.tenantId,
14
- scope: "tenant",
15
- status: "draft",
16
- bundle: event.payload.bundle,
17
- overwrite: event.payload.overwrite,
18
- }),
19
- handler: (event, ctx) => importTemplateBundle({
20
- ctx,
21
- user: event.user,
22
- tenantId: event.user.tenantId,
23
- scope: "tenant",
24
- status: "draft",
25
- bundle: event.payload.bundle,
26
- overwrite: event.payload.overwrite,
27
- prepared: event.prepared,
28
- }),
29
- });
6
+ export function makeImportBundleWrite(policy) {
7
+ return defineWriteHandler({
8
+ name: "import-bundle",
9
+ schema: z.object({ bundle: templateBundleSchema, overwrite: z.boolean().default(false) }),
10
+ access: templateEditAccess(policy.tenantEditable),
11
+ description: "Imports a template bundle (all locales plus resource files, as produced by export-bundle) into the caller's tenant as drafts; it refuses a locale that already has an override unless overwrite is set, and publish makes the drafts live. Outside the templates the app lists in tenantEditable only a SystemAdmin may use it.",
12
+ beforeTransaction: async (event, ctx) => refuseTemplateEdit(policy, event.user, event.payload.bundle) ??
13
+ prepareTemplateBundleBeforeTransaction({
14
+ ctx,
15
+ user: event.user,
16
+ tenantId: event.user.tenantId,
17
+ scope: "tenant",
18
+ status: "draft",
19
+ bundle: event.payload.bundle,
20
+ overwrite: event.payload.overwrite,
21
+ }),
22
+ handler: async (event, ctx) => refuseTemplateEdit(policy, event.user, event.payload.bundle) ??
23
+ importTemplateBundle({
24
+ ctx,
25
+ user: event.user,
26
+ tenantId: event.user.tenantId,
27
+ scope: "tenant",
28
+ status: "draft",
29
+ bundle: event.payload.bundle,
30
+ overwrite: event.payload.overwrite,
31
+ prepared: event.prepared,
32
+ }),
33
+ });
34
+ }
@@ -1,6 +1,7 @@
1
1
  import * as z from "zod";
2
2
  import { type TemplateStatus } from "../constants.ts";
3
3
  import { type TemplateResourceRow } from "../table.ts";
4
+ import { type TemplateEditPolicy } from "../template-edit-policy.ts";
4
5
  import { type EffectiveMailLayoutSlugs } from "../template-usage.ts";
5
6
  declare const OVERVIEW_KINDS: readonly ["mail", "document-pdf", "mail-layout", "pdf-layout"];
6
7
  type OverviewKind = (typeof OVERVIEW_KINDS)[number];
@@ -38,7 +39,7 @@ export type OverviewResponse = {
38
39
  readonly truncated: boolean;
39
40
  };
40
41
  export declare function effectiveMailLayoutSlugs(rows: readonly TemplateResourceRow[]): EffectiveMailLayoutSlugs;
41
- export declare const overviewQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"overview", z.ZodObject<{
42
+ export declare function makeOverviewQuery(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"overview", z.ZodObject<{
42
43
  search: z.ZodOptional<z.ZodString>;
43
44
  filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
45
  field: z.ZodEnum<{
@@ -4,6 +4,7 @@ import * as z from "zod";
4
4
  import { DEFAULT_MAIL_LAYOUT_SLUG, FALLBACK_LOCALE } from "../constants.js";
5
5
  import { templateResourcesTable } from "../table.js";
6
6
  import { activeSinceOf, activeVersionOf, draftViewerTenantId, isHiddenSystemDraft, parseTemplateDraft, servedVersionOf, } from "../template-draft.js";
7
+ import { mayEditTemplate } from "../template-edit-policy.js";
7
8
  import { isSystemOnlyTemplateHiddenFrom } from "../template-system-only.js";
8
9
  import { collectTemplateUsage } from "../template-usage.js";
9
10
  import { storedRecord } from "./template-gates.js";
@@ -171,45 +172,48 @@ const filterSchema = z.object({
171
172
  op: z.literal("in"),
172
173
  value: z.array(z.string()),
173
174
  });
174
- export const overviewQuery = defineQueryHandler({
175
- name: "overview",
176
- schema: z.object({
177
- search: z.string().optional(),
178
- filters: z.array(filterSchema).optional(),
179
- layoutSlug: z.string().optional(),
180
- sort: z.string().optional(),
181
- sortDirection: z.enum(["asc", "desc"]).optional(),
182
- limit: z.number().int().min(1).max(200).default(50),
183
- offset: z.number().int().min(0).optional(),
184
- totalCount: z.boolean().optional(),
185
- }),
186
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
187
- description: "Lists mail, PDF and layout templates as one row per slug and kind for the caller's tenant (optionally only the mails and PDFs using one layout slug): where the effective version comes from (own override or system), its locales, status and version (the served one; `draftVersion` is set when an unpublished draft waits beside it, `activeSince` says since when the served version applies), the system version it overrides, the layout it uses and which notifications or templates use it; templates of notifications registered with `systemOnly` are listed for SystemAdmins only; use it to browse and filter all templates at a glance.",
188
- handler: async (query, ctx) => {
189
- const payload = query.payload;
190
- const fetchedRows = await selectMany(ctx.db, templateResourcesTable, { kind: { in: [...OVERVIEW_KINDS] } }, { orderBy: { col: "id", direction: "asc" }, limit: OVERVIEW_ROW_LIMIT + 1 });
191
- const truncated = fetchedRows.length > OVERVIEW_ROW_LIMIT;
192
- const rows = truncated ? fetchedRows.slice(0, OVERVIEW_ROW_LIMIT) : fetchedRows;
193
- if (truncated) {
194
- ctx.log?.warn("template overview: row limit reached, result is incomplete", {
195
- limit: OVERVIEW_ROW_LIMIT,
196
- });
197
- }
198
- const visibleRows = rows.filter((row) => !isHiddenSystemDraft(query.user, row) &&
199
- !isSystemOnlyTemplateHiddenFrom(query.user, ctx.registry, row));
200
- const { drafts, layoutSlugByDocument } = buildDrafts(groupBySlugAndKind(visibleRows), query.user);
201
- const effectiveLayouts = effectiveMailLayoutSlugs(visibleRows);
202
- const overviewRows = drafts.map((draft) => withUsage(draft, layoutSlugByDocument, ctx.registry, effectiveLayouts));
203
- const filtered = filterRows(overviewRows, payload.search, payload.filters ?? [], payload.layoutSlug);
204
- const sortField = payload.sort !== undefined && isSortField(payload.sort) ? payload.sort : "modifiedAt";
205
- const direction = (payload.sortDirection ?? "desc") === "desc" ? -1 : 1;
206
- filtered.sort((a, b) => direction * compareBy(sortField, a, b) || a.id.localeCompare(b.id));
207
- const offset = payload.offset ?? 0;
208
- return {
209
- rows: filtered.slice(offset, offset + payload.limit),
210
- nextCursor: null,
211
- total: filtered.length,
212
- truncated,
213
- };
214
- },
215
- });
175
+ export function makeOverviewQuery(policy) {
176
+ return defineQueryHandler({
177
+ name: "overview",
178
+ schema: z.object({
179
+ search: z.string().optional(),
180
+ filters: z.array(filterSchema).optional(),
181
+ layoutSlug: z.string().optional(),
182
+ sort: z.string().optional(),
183
+ sortDirection: z.enum(["asc", "desc"]).optional(),
184
+ limit: z.number().int().min(1).max(200).default(50),
185
+ offset: z.number().int().min(0).optional(),
186
+ totalCount: z.boolean().optional(),
187
+ }),
188
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
189
+ description: "Lists mail, PDF and layout templates as one row per slug and kind for the caller's tenant (optionally only the mails and PDFs using one layout slug): where the effective version comes from (own override or system), its locales, status and version (the served one; `draftVersion` is set when an unpublished draft waits beside it, `activeSince` says since when the served version applies), the system version it overrides, the layout it uses and which notifications or templates use it; templates of notifications registered with `systemOnly` are listed for SystemAdmins only, and a TenantAdmin sees only the templates the app lists in tenantEditable; use it to browse and filter all templates at a glance.",
190
+ handler: async (query, ctx) => {
191
+ const payload = query.payload;
192
+ const fetchedRows = await selectMany(ctx.db, templateResourcesTable, { kind: { in: [...OVERVIEW_KINDS] } }, { orderBy: { col: "id", direction: "asc" }, limit: OVERVIEW_ROW_LIMIT + 1 });
193
+ const truncated = fetchedRows.length > OVERVIEW_ROW_LIMIT;
194
+ const rows = truncated ? fetchedRows.slice(0, OVERVIEW_ROW_LIMIT) : fetchedRows;
195
+ if (truncated) {
196
+ ctx.log?.warn("template overview: row limit reached, result is incomplete", {
197
+ limit: OVERVIEW_ROW_LIMIT,
198
+ });
199
+ }
200
+ const visibleRows = rows.filter((row) => !isHiddenSystemDraft(query.user, row) &&
201
+ !isSystemOnlyTemplateHiddenFrom(query.user, ctx.registry, row) &&
202
+ mayEditTemplate(policy, query.user, row));
203
+ const { drafts, layoutSlugByDocument } = buildDrafts(groupBySlugAndKind(visibleRows), query.user);
204
+ const effectiveLayouts = effectiveMailLayoutSlugs(visibleRows);
205
+ const overviewRows = drafts.map((draft) => withUsage(draft, layoutSlugByDocument, ctx.registry, effectiveLayouts));
206
+ const filtered = filterRows(overviewRows, payload.search, payload.filters ?? [], payload.layoutSlug);
207
+ const sortField = payload.sort !== undefined && isSortField(payload.sort) ? payload.sort : "modifiedAt";
208
+ const direction = (payload.sortDirection ?? "desc") === "desc" ? -1 : 1;
209
+ filtered.sort((a, b) => direction * compareBy(sortField, a, b) || a.id.localeCompare(b.id));
210
+ const offset = payload.offset ?? 0;
211
+ return {
212
+ rows: filtered.slice(offset, offset + payload.limit),
213
+ nextCursor: null,
214
+ total: filtered.length,
215
+ truncated,
216
+ };
217
+ },
218
+ });
219
+ }
@@ -0,0 +1,67 @@
1
+ import * as z from "zod";
2
+ import { type TemplateEditPolicy } from "../template-edit-policy.ts";
3
+ export declare function makePatchDraftWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"patch-draft", z.ZodObject<{
4
+ slug: z.ZodString;
5
+ kind: z.ZodEnum<{
6
+ "document-pdf": "document-pdf";
7
+ mail: "mail";
8
+ }>;
9
+ locale: z.ZodString;
10
+ base: z.ZodObject<{
11
+ scope: z.ZodEnum<{
12
+ system: "system";
13
+ tenant: "tenant";
14
+ }>;
15
+ version: z.ZodNumber;
16
+ }, z.core.$strip>;
17
+ ops: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
18
+ op: z.ZodLiteral<"insertBlock">;
19
+ after: z.ZodNullable<z.ZodString>;
20
+ block: z.ZodDiscriminatedUnion<[z.ZodObject<{
21
+ type: z.ZodLiteral<"paragraph">;
22
+ text: z.ZodString;
23
+ align: z.ZodOptional<z.ZodEnum<{
24
+ center: "center";
25
+ justify: "justify";
26
+ left: "left";
27
+ right: "right";
28
+ }>>;
29
+ }, z.core.$strip>, z.ZodObject<{
30
+ type: z.ZodLiteral<"heading">;
31
+ level: z.ZodNumber;
32
+ text: z.ZodString;
33
+ }, z.core.$strip>, z.ZodObject<{
34
+ type: z.ZodLiteral<"image">;
35
+ resource: z.ZodString;
36
+ alt: z.ZodOptional<z.ZodString>;
37
+ }, z.core.$strip>, z.ZodObject<{
38
+ type: z.ZodLiteral<"button">;
39
+ text: z.ZodString;
40
+ href: z.ZodString;
41
+ }, z.core.$strip>, z.ZodObject<{
42
+ type: z.ZodLiteral<"divider">;
43
+ }, z.core.$strip>, z.ZodObject<{
44
+ type: z.ZodLiteral<"pageBreak">;
45
+ }, z.core.$strip>], "type">;
46
+ }, z.core.$strip>, z.ZodObject<{
47
+ op: z.ZodLiteral<"replaceText">;
48
+ blockId: z.ZodString;
49
+ find: z.ZodString;
50
+ replace: z.ZodString;
51
+ }, z.core.$strip>, z.ZodObject<{
52
+ op: z.ZodLiteral<"removeBlock">;
53
+ blockId: z.ZodString;
54
+ }, z.core.$strip>, z.ZodObject<{
55
+ op: z.ZodLiteral<"setResource">;
56
+ blockId: z.ZodString;
57
+ resource: z.ZodString;
58
+ alt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
59
+ }, z.core.$strip>, z.ZodObject<{
60
+ op: z.ZodLiteral<"setMeta">;
61
+ meta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
62
+ }, z.core.$strip>, z.ZodObject<{
63
+ op: z.ZodLiteral<"setVariable">;
64
+ name: z.ZodString;
65
+ definition: z.ZodNullable<z.ZodUnknown>;
66
+ }, z.core.$strip>], "op">>;
67
+ }, z.core.$strip>, import("./template-write.ts").WrittenTemplateRow, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -0,0 +1,111 @@
1
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { NotFoundError, ValidationError, VersionConflictError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
3
+ import * as z from "zod";
4
+ import { DOCUMENT_HTML_KINDS } from "../constants.js";
5
+ import { applyDocumentOps, documentOpsSchema } from "../document-ops.js";
6
+ import { parseDocumentJson, serializeDocumentJson } from "../document-sanitizer.js";
7
+ import { refuseTemplateEdit, templateEditAccess, } from "../template-edit-policy.js";
8
+ import { resolveTemplateVariables } from "../template-validation.js";
9
+ import { loadDocumentBase } from "./document-base.js";
10
+ import { localeSchema, templateSlugSchema } from "./shared.js";
11
+ import { writeTemplateRow } from "./template-write.js";
12
+ import { validateTemplateDraft } from "./validate.query.js";
13
+ function validationFailure(issue) {
14
+ return writeFailure(new ValidationError({ fields: [issue] }));
15
+ }
16
+ export function makePatchDraftWrite(policy) {
17
+ return defineWriteHandler({
18
+ name: "patch-draft",
19
+ schema: z.object({
20
+ slug: templateSlugSchema,
21
+ kind: z.enum(DOCUMENT_HTML_KINDS),
22
+ locale: localeSchema,
23
+ base: z.object({
24
+ scope: z.enum(["tenant", "system"]),
25
+ version: z.number().int().nonnegative(),
26
+ }),
27
+ ops: documentOpsSchema,
28
+ }),
29
+ access: templateEditAccess(policy.tenantEditable),
30
+ description: "Changes a mail or PDF document template with structured operations instead of a full rewrite and saves the result as the caller's tenant draft (never published, an active version keeps being served until publish): insertBlock after a block id (null = at the start; paragraph, heading, image, button, divider, pageBreak), replaceText inside one block (the text must occur exactly once), removeBlock, setResource on an image block, setMeta (flat merge, null removes a key) and setVariable (adds, replaces or with null removes a variable definition; refused when a notification declares the variables). Block ids and base come from document-blocks and refer to the state that was read; if the template changed since, the call fails with version_conflict and nothing is written. A patch that removes a required placeholder is refused. A TenantAdmin may patch only templates the app lists in tenantEditable, otherwise SystemAdmin only. Returns the written row.",
31
+ handler: async (event, ctx) => {
32
+ const { slug, kind, locale, base, ops } = event.payload;
33
+ const refused = refuseTemplateEdit(policy, event.user, { slug, kind });
34
+ if (refused !== null)
35
+ return refused;
36
+ const loaded = await loadDocumentBase(ctx, event.user, { slug, kind, locale });
37
+ if (loaded === null)
38
+ return writeFailure(new NotFoundError("template-resource", slug));
39
+ if (loaded.base.scope !== base.scope || loaded.base.version !== base.version) {
40
+ return writeFailure(new VersionConflictError({
41
+ entityId: loaded.rowId,
42
+ expectedVersion: base.version,
43
+ currentVersion: loaded.base.version,
44
+ }));
45
+ }
46
+ const variableOpIndex = ops.findIndex((op) => op.op === "setVariable");
47
+ if (variableOpIndex !== -1 &&
48
+ ctx.registry.getAllNotifications().get(slug)?.variables !== undefined) {
49
+ return validationFailure({
50
+ path: `ops.${variableOpIndex}`,
51
+ code: "variables_declared_by_feature",
52
+ i18nKey: "templateResolver.errors.patch.variablesDeclaredByFeature",
53
+ params: { slug },
54
+ });
55
+ }
56
+ const applied = applyDocumentOps({
57
+ doc: parseDocumentJson(loaded.content),
58
+ meta: loaded.meta,
59
+ variableSchema: loaded.variableSchema,
60
+ kind,
61
+ ops,
62
+ });
63
+ if (!applied.ok)
64
+ return writeFailure(new ValidationError({ fields: applied.issues }));
65
+ const content = serializeDocumentJson(applied.doc);
66
+ const draftOf = (draftContent, meta, variableSchema) => validateTemplateDraft(ctx, event.user, {
67
+ slug,
68
+ kind,
69
+ contentFormat: "html",
70
+ content: draftContent,
71
+ meta,
72
+ variableSchema,
73
+ locale,
74
+ scope: "tenant",
75
+ });
76
+ const before = await draftOf(loaded.content, loaded.meta, loaded.variableSchema);
77
+ const after = await draftOf(content, applied.meta, applied.variableSchema);
78
+ const alreadyMissing = new Set(before.response.missingRequired);
79
+ const removed = after.response.missingRequired.filter((name) => !alreadyMissing.has(name));
80
+ if (removed.length > 0) {
81
+ return validationFailure({
82
+ path: "ops",
83
+ code: "required_placeholder_removed",
84
+ i18nKey: "templateResolver.errors.patch.requiredPlaceholderRemoved",
85
+ params: { names: removed },
86
+ });
87
+ }
88
+ return writeTemplateRow({
89
+ ctx,
90
+ user: event.user,
91
+ tenantId: event.user.tenantId,
92
+ scope: "tenant",
93
+ status: "draft",
94
+ variables: resolveTemplateVariables(ctx.registry, slug, applied.variableSchema),
95
+ payload: {
96
+ slug,
97
+ kind,
98
+ locale,
99
+ content,
100
+ contentFormat: "html",
101
+ variableSchema: applied.variableSchema,
102
+ meta: applied.meta,
103
+ title: loaded.title,
104
+ folder: loaded.folder,
105
+ parentTemplateId: loaded.parentTemplateId,
106
+ },
107
+ expectedVersion: loaded.base.scope === "tenant" ? loaded.base.version : null,
108
+ });
109
+ },
110
+ });
111
+ }
@@ -9,4 +9,5 @@ export declare const previewQuery: import("@cosmicdrift/kumiko-types/define-hand
9
9
  content: z.ZodOptional<z.ZodString>;
10
10
  meta: z.ZodOptional<z.ZodUnknown>;
11
11
  variableSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
+ variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
13
  }, z.core.$strip>, import("../template-preview.ts").TemplatePreview, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -12,9 +12,10 @@ export const previewQuery = defineQueryHandler({
12
12
  content: z.string().max(200_000).optional(),
13
13
  meta: z.unknown().optional(),
14
14
  variableSchema: z.record(z.string(), z.unknown()).default({}),
15
+ variables: z.record(z.string(), z.unknown()).optional(),
15
16
  }),
16
17
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
17
- description: "Renders a template draft, or the stored template when no content is given, with sample values taken from the declared variable examples, without saving or sending anything: returns the mail subject, html and plain text with its inline images (a PDF template returns its body and layout regions instead), the sample values used, and checks for unresolved images, missing plain text, mail size and unused required placeholders; a draft that fails the write rules is rejected with the same problems as a save.",
18
+ description: "Renders a template draft, or the stored template when no content is given, with sample values taken from the declared variable examples (values passed in variables replace them and are checked against the variable schema), without saving or sending anything: returns the mail subject, html and plain text with its inline images (a PDF template returns its body and layout regions instead), the sample values used, and checks for unresolved images, missing plain text, mail size and unused required placeholders; a draft that fails the write rules is rejected with the same problems as a save.",
18
19
  handler: async (query, ctx) => {
19
20
  const outcome = await renderTemplatePreview(ctx, query.user, query.payload);
20
21
  if (!outcome.ok)
@@ -1,5 +1,6 @@
1
1
  import * as z from "zod";
2
- export declare const resetToSystemWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"reset-to-system", z.ZodObject<{
2
+ import { type TemplateEditPolicy } from "../template-edit-policy.ts";
3
+ export declare function makeResetToSystemWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"reset-to-system", z.ZodObject<{
3
4
  id: z.ZodString;
4
5
  }, z.core.$strip>, {
5
6
  id: string;
@@ -3,48 +3,55 @@ import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framew
3
3
  import { NotFoundError, ValidationError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
5
  import { templateResourcesTable } from "../table.js";
6
+ import { refuseMissingTemplate, refuseTemplateEdit, templateEditAccess, } from "../template-edit-policy.js";
6
7
  import { executor } from "./shared.js";
7
- export const resetToSystemWrite = defineWriteHandler({
8
- name: "reset-to-system",
9
- schema: z.object({ id: z.string().min(1) }),
10
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
11
- description: "Retires a tenant override by archiving it so the resolver serves the active system template again; the override and its content survive and can be published again. Refused when there is no active system template to fall back to.",
12
- handler: async (event, ctx) => {
13
- const existing = await fetchOne(ctx.db, templateResourcesTable, {
14
- id: event.payload.id,
15
- });
16
- if (!existing || existing.tenantId !== event.user.tenantId || existing.scope !== "tenant") {
17
- return writeFailure(new NotFoundError("template-resource", event.payload.id));
18
- }
19
- const systemRow = await fetchOne(ctx.db, templateResourcesTable, {
20
- tenantId: SYSTEM_TENANT_ID,
21
- slug: existing.slug,
22
- kind: existing.kind,
23
- locale: existing.locale,
24
- status: "active",
25
- });
26
- if (!systemRow) {
27
- return writeFailure(new ValidationError({
28
- fields: [
29
- {
30
- path: "id",
31
- code: "no_system_template",
32
- i18nKey: "templateResolver.errors.noSystemTemplate",
33
- },
34
- ],
35
- }));
36
- }
37
- const response = {
38
- isSuccess: true,
39
- data: {
40
- id: String(existing.id),
41
- status: "archived",
42
- systemTemplateId: String(systemRow.id),
43
- },
44
- };
45
- if (existing.status === "archived")
46
- return response;
47
- const result = await executor.update({ id: existing.id, version: existing.version, changes: { status: "archived" } }, event.user, ctx.db);
48
- return result.isSuccess ? response : result;
49
- },
50
- });
8
+ export function makeResetToSystemWrite(policy) {
9
+ return defineWriteHandler({
10
+ name: "reset-to-system",
11
+ schema: z.object({ id: z.string().min(1) }),
12
+ access: templateEditAccess(policy.tenantEditable),
13
+ description: "Retires a tenant override by archiving it so the resolver serves the active system template again; the override and its content survive and can be published again. Refused when there is no active system template to fall back to. Outside the templates the app lists in tenantEditable only a SystemAdmin may use it.",
14
+ handler: async (event, ctx) => {
15
+ const existing = await fetchOne(ctx.db, templateResourcesTable, {
16
+ id: event.payload.id,
17
+ });
18
+ if (!existing || existing.tenantId !== event.user.tenantId || existing.scope !== "tenant") {
19
+ return (refuseMissingTemplate(policy, event.user) ??
20
+ writeFailure(new NotFoundError("template-resource", event.payload.id)));
21
+ }
22
+ const refused = refuseTemplateEdit(policy, event.user, existing);
23
+ if (refused !== null)
24
+ return refused;
25
+ const systemRow = await fetchOne(ctx.db, templateResourcesTable, {
26
+ tenantId: SYSTEM_TENANT_ID,
27
+ slug: existing.slug,
28
+ kind: existing.kind,
29
+ locale: existing.locale,
30
+ status: "active",
31
+ });
32
+ if (!systemRow) {
33
+ return writeFailure(new ValidationError({
34
+ fields: [
35
+ {
36
+ path: "id",
37
+ code: "no_system_template",
38
+ i18nKey: "templateResolver.errors.noSystemTemplate",
39
+ },
40
+ ],
41
+ }));
42
+ }
43
+ const response = {
44
+ isSuccess: true,
45
+ data: {
46
+ id: String(existing.id),
47
+ status: "archived",
48
+ systemTemplateId: String(systemRow.id),
49
+ },
50
+ };
51
+ if (existing.status === "archived")
52
+ return response;
53
+ const result = await executor.update({ id: existing.id, version: existing.version, changes: { status: "archived" } }, event.user, ctx.db);
54
+ return result.isSuccess ? response : result;
55
+ },
56
+ });
57
+ }
@@ -1,5 +1,6 @@
1
1
  import * as z from "zod";
2
- export declare const resourceLinkPdfWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"resource-link-pdf", z.ZodObject<{
2
+ import { type TemplateEditPolicy } from "../template-edit-policy.ts";
3
+ export declare function makeResourceLinkPdfWrite(policy: TemplateEditPolicy): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"resource-link-pdf", z.ZodObject<{
3
4
  templateId: z.ZodString;
4
5
  key: z.ZodString;
5
6
  templateSlug: z.ZodString;