@cosmicdrift/kumiko-bundled-features 0.192.0 → 0.193.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/package.json +7 -7
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +43 -1
- package/src/file-derivatives/__tests__/public-variant-route.integration.test.ts +237 -0
- package/src/file-derivatives/feature.ts +142 -21
- package/src/file-derivatives/handlers/public-variant.query.ts +122 -0
- package/src/file-derivatives/index.ts +3 -2
- package/src/file-derivatives/presets.ts +7 -0
- package/src/form-draft/__tests__/cleanup.integration.test.ts +30 -0
- package/src/form-draft/__tests__/discard-file-refs.integration.test.ts +26 -1
- package/src/form-draft/__tests__/feature-optional-config.integration.test.ts +90 -0
- package/src/form-draft/db/queries/cleanup.ts +15 -14
- package/src/form-draft/db/queries/owned-file-refs.ts +13 -2
- package/src/form-draft/feature.ts +7 -1
- package/src/form-draft/handlers/__tests__/cleanup.job.test.ts +55 -0
- package/src/form-draft/handlers/cleanup.job.ts +57 -11
- package/src/form-draft/handlers/discard.write.ts +1 -0
- package/src/form-draft/lookup.ts +2 -0
- package/src/form-draft-user-data/__tests__/hooks-delete-failure.integration.test.ts +60 -0
- package/src/form-draft-user-data/hooks.ts +12 -1
- package/src/template-resolver/handlers/by-slug.query.ts +1 -0
- package/src/template-resolver/handlers/collection-shared.ts +2 -0
- package/src/template-resolver/web/__tests__/editor-read-only.test.tsx +25 -0
- package/src/template-resolver/web/client-plugin.tsx +22 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +21 -6
|
@@ -256,10 +256,30 @@ type TextBlock = {
|
|
|
256
256
|
readonly locale: string;
|
|
257
257
|
readonly title: string | null;
|
|
258
258
|
readonly content: string | null;
|
|
259
|
+
readonly contentFormat: string;
|
|
259
260
|
readonly folder: string | null;
|
|
260
261
|
readonly updatedAt: string;
|
|
261
262
|
};
|
|
262
263
|
|
|
264
|
+
// The collection's declared contentFormat ("plain" | "rich" | "markdown",
|
|
265
|
+
// see ContentCollectionDefinition.contentFormat) uses a different vocabulary
|
|
266
|
+
// than the stored entry's contentFormat ("plain" | "html" | "markdown" |
|
|
267
|
+
// "mjml", see CONTENT_FORMATS) — "rich" always stores HTML. Without this,
|
|
268
|
+
// handleSave omitting contentFormat let the write schema's
|
|
269
|
+
// contentFormatSchema.default("markdown") silently stamp every save as
|
|
270
|
+
// markdown, clobbering e.g. a "plain" entry. Undefined (no collection, or
|
|
271
|
+
// the collection didn't declare a format) keeps whatever was already saved
|
|
272
|
+
// instead of guessing.
|
|
273
|
+
function resolveWriteContentFormat(
|
|
274
|
+
collectionContentFormat: string | undefined,
|
|
275
|
+
loadedContentFormat: string | undefined,
|
|
276
|
+
): string | undefined {
|
|
277
|
+
if (collectionContentFormat === "plain") return "plain";
|
|
278
|
+
if (collectionContentFormat === "rich") return "html";
|
|
279
|
+
if (collectionContentFormat === "markdown") return "markdown";
|
|
280
|
+
return loadedContentFormat;
|
|
281
|
+
}
|
|
282
|
+
|
|
263
283
|
type SetResponse = { readonly slug: string; readonly locale: string; readonly isNew: boolean };
|
|
264
284
|
|
|
265
285
|
function TextBlockEditor({
|
|
@@ -337,12 +357,14 @@ function TextBlockEditor({
|
|
|
337
357
|
setSaveError(null);
|
|
338
358
|
setSavedMsg(null);
|
|
339
359
|
try {
|
|
360
|
+
const resolvedContentFormat = resolveWriteContentFormat(contentFormat, loaded?.contentFormat);
|
|
340
361
|
const result = await dispatcher.write<SetResponse>(writeHandler, {
|
|
341
362
|
slug,
|
|
342
363
|
locale,
|
|
343
364
|
title,
|
|
344
365
|
content: content.length > 0 ? content : null,
|
|
345
366
|
folder: loaded?.folder ?? null,
|
|
367
|
+
...(resolvedContentFormat !== undefined && { contentFormat: resolvedContentFormat }),
|
|
346
368
|
...(tenantIdOverride !== undefined && { tenantIdOverride }),
|
|
347
369
|
});
|
|
348
370
|
if (result.isSuccess) {
|
|
@@ -98,13 +98,28 @@ export const tenantInvitationDeleteHook: UserDataDeleteHook = async (ctx, strate
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
// `invitedBy` has no `lookupable`/blind-index column — it's ciphertext
|
|
102
|
+
// under an active KMS, so `{ invitedBy: ctx.userId }` would compare a
|
|
103
|
+
// plaintext userId against an encrypted column and always match zero
|
|
104
|
+
// rows. Load every invitation in the tenant instead (small per-tenant
|
|
105
|
+
// row count) and decrypt `invitedBy` per row to find this user's own
|
|
106
|
+
// invites-sent. See tenant/handlers/invitations.query.ts for the same
|
|
107
|
+
// decrypt-on-read pattern.
|
|
108
|
+
const tenantInvitations = await selectMany<Record<string, unknown>>(
|
|
109
|
+
ctx.db,
|
|
110
|
+
tenantInvitationsTable,
|
|
111
|
+
{ tenantId: ctx.tenantId },
|
|
112
|
+
);
|
|
113
|
+
for (const row of tenantInvitations) {
|
|
106
114
|
const id = row["id"]; // @cast-boundary db-row
|
|
107
|
-
|
|
115
|
+
const invitedByRaw = row["invitedBy"];
|
|
116
|
+
if (typeof id !== "string" || typeof invitedByRaw !== "string") continue;
|
|
117
|
+
const invitedBy = await decryptStoredPii(
|
|
118
|
+
invitedByRaw,
|
|
119
|
+
"invitedBy",
|
|
120
|
+
"user-data-rights:invitation-hook",
|
|
121
|
+
);
|
|
122
|
+
if (invitedBy !== ctx.userId) continue;
|
|
108
123
|
await crud.update({ id, changes: { invitedBy: INVITED_BY_ANONYMIZED } }, systemUser, tdb, {
|
|
109
124
|
skipOptimisticLock: true,
|
|
110
125
|
});
|