agent-cms 0.1.0 → 0.3.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/README.md +113 -2
- package/dist/codemode-handler-Bgu2utjN.mjs +18697 -0
- package/dist/{handler-ClOW1ldA.mjs → handler-B5jgfPOY.mjs} +2 -2
- package/dist/http-transport-DVKhbbe1.mjs +17 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +431 -34
- package/dist/{token-service-BDjccMmz.mjs → preview-service-C9Tmhdye.mjs} +177 -38
- package/dist/{http-transport-DbFCI6Cs.mjs → server-D0XqvDjU.mjs} +200 -241
- package/dist/{structured-text-service-B4xSlUg_.mjs → structured-text-service-BJkqWRkq.mjs} +187 -8
- package/migrations/0000_genesis.sql +11 -0
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as isSearchable, B as encodeJson, C as findUniqueConstraintViolations, D as getLinksTargets, E as getLinkTargets, F as parseMediaGalleryReferences, G as ReferenceConflictError, H as getFieldTypeDef, I as decodeJsonIfString, J as ValidationError, L as decodeJsonRecordStringOr, M as supportsUniqueValidation, O as getSlugSource, P as parseMediaFieldReference, R as decodeJsonString, S as computeIsValid, T as getBlocksOnly, U as DuplicateError, W as NotFoundError, a as materializeStructuredTextValue, b as isContentRow, c as FIELD_TYPES, i as materializeRecordStructuredTextFields, j as isUnique, k as isRequired, l as isFieldType, n as deleteBlocksForField, q as UnauthorizedError, r as getStructuredTextStorageKey, s as writeStructuredText, t as deleteBlockSubtrees, v as pruneBlockNodes, w as getBlockWhitelist, x as parseFieldValidators, y as StructuredTextWriteInput } from "./structured-text-service-
|
|
1
|
+
import { A as isSearchable, B as encodeJson, C as findUniqueConstraintViolations, D as getLinksTargets, E as getLinkTargets, F as parseMediaGalleryReferences, G as ReferenceConflictError, H as getFieldTypeDef, I as decodeJsonIfString, J as ValidationError, L as decodeJsonRecordStringOr, M as supportsUniqueValidation, O as getSlugSource, P as parseMediaFieldReference, R as decodeJsonString, S as computeIsValid, T as getBlocksOnly, U as DuplicateError, W as NotFoundError, a as materializeStructuredTextValue, b as isContentRow, c as FIELD_TYPES, i as materializeRecordStructuredTextFields, j as isUnique, k as isRequired, l as isFieldType, m as expandStructuredTextShorthand, n as deleteBlocksForField, q as UnauthorizedError, r as getStructuredTextStorageKey, s as writeStructuredText, t as deleteBlockSubtrees, v as pruneBlockNodes, w as getBlockWhitelist, x as parseFieldValidators, y as StructuredTextWriteInput } from "./structured-text-service-BJkqWRkq.mjs";
|
|
2
2
|
import { Context, Data, Effect, Option, Schema } from "effect";
|
|
3
3
|
import { SqlClient } from "@effect/sql";
|
|
4
4
|
import { customAlphabet } from "nanoid";
|
|
@@ -746,6 +746,21 @@ function getModel(id) {
|
|
|
746
746
|
};
|
|
747
747
|
});
|
|
748
748
|
}
|
|
749
|
+
function getModelByApiKey$1(apiKey) {
|
|
750
|
+
return Effect.gen(function* () {
|
|
751
|
+
const sql = yield* SqlClient.SqlClient;
|
|
752
|
+
const models = yield* sql.unsafe("SELECT * FROM models WHERE api_key = ?", [apiKey]);
|
|
753
|
+
if (models.length === 0) return yield* new NotFoundError({
|
|
754
|
+
entity: "Model",
|
|
755
|
+
id: apiKey
|
|
756
|
+
});
|
|
757
|
+
const fields = yield* sql.unsafe("SELECT * FROM fields WHERE model_id = ? ORDER BY position", [models[0].id]);
|
|
758
|
+
return {
|
|
759
|
+
...models[0],
|
|
760
|
+
fields: fields.map(parseFieldValidators)
|
|
761
|
+
};
|
|
762
|
+
});
|
|
763
|
+
}
|
|
749
764
|
function createModel(body) {
|
|
750
765
|
return Effect.gen(function* () {
|
|
751
766
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -753,8 +768,8 @@ function createModel(body) {
|
|
|
753
768
|
if ((yield* sql.unsafe("SELECT id FROM models WHERE api_key = ?", [body.apiKey])).length > 0) return yield* new DuplicateError({ message: `Model with apiKey '${body.apiKey}' already exists` });
|
|
754
769
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
755
770
|
const id = generateId();
|
|
756
|
-
yield* sql.unsafe(`INSERT INTO models (id, name, api_key, is_block, singleton, sortable, tree, has_draft, all_locales_required, ordering, created_at, updated_at)
|
|
757
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
771
|
+
yield* sql.unsafe(`INSERT INTO models (id, name, api_key, is_block, singleton, sortable, tree, has_draft, all_locales_required, ordering, canonical_path_template, created_at, updated_at)
|
|
772
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
758
773
|
id,
|
|
759
774
|
body.name,
|
|
760
775
|
body.apiKey,
|
|
@@ -765,6 +780,7 @@ function createModel(body) {
|
|
|
765
780
|
body.hasDraft ? 1 : 0,
|
|
766
781
|
body.allLocalesRequired ? 1 : 0,
|
|
767
782
|
body.ordering ?? null,
|
|
783
|
+
body.canonicalPathTemplate ?? null,
|
|
768
784
|
now,
|
|
769
785
|
now
|
|
770
786
|
]);
|
|
@@ -784,6 +800,7 @@ function createModel(body) {
|
|
|
784
800
|
hasDraft: body.hasDraft,
|
|
785
801
|
allLocalesRequired: body.allLocalesRequired,
|
|
786
802
|
ordering: body.ordering ?? null,
|
|
803
|
+
canonicalPathTemplate: body.canonicalPathTemplate ?? null,
|
|
787
804
|
createdAt: now,
|
|
788
805
|
updatedAt: now
|
|
789
806
|
};
|
|
@@ -825,6 +842,10 @@ function updateModel(id, body) {
|
|
|
825
842
|
sets.push("ordering = ?");
|
|
826
843
|
values.push(body.ordering);
|
|
827
844
|
}
|
|
845
|
+
if (body.canonicalPathTemplate !== void 0) {
|
|
846
|
+
sets.push("canonical_path_template = ?");
|
|
847
|
+
values.push(body.canonicalPathTemplate);
|
|
848
|
+
}
|
|
828
849
|
if (body.apiKey !== void 0 && body.apiKey !== model.api_key) {
|
|
829
850
|
const newApiKey = body.apiKey;
|
|
830
851
|
if (!/^[a-z][a-z0-9_]*$/.test(newApiKey)) return yield* new ValidationError({ message: "apiKey must start with a lowercase letter and contain only lowercase letters, numbers, and underscores" });
|
|
@@ -1794,7 +1815,8 @@ function processCreateLikeRecordFields({ modelApiKey, tableName, recordId, data,
|
|
|
1794
1815
|
localizedDast[localeCode] = null;
|
|
1795
1816
|
continue;
|
|
1796
1817
|
}
|
|
1797
|
-
const
|
|
1818
|
+
const expandedLocale = expandStructuredTextShorthand(localeValue);
|
|
1819
|
+
const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(scopeStructuredTextIds(expandedLocale, `${field.api_key}:${localeCode}`)).pipe(Effect.mapError((e) => new ValidationError({
|
|
1798
1820
|
message: createFieldErrorMessage(errorPrefix, `Invalid StructuredText for field '${field.api_key}' locale '${localeCode}': ${e.message}`),
|
|
1799
1821
|
field: field.api_key
|
|
1800
1822
|
})));
|
|
@@ -1815,7 +1837,8 @@ function processCreateLikeRecordFields({ modelApiKey, tableName, recordId, data,
|
|
|
1815
1837
|
record[field.api_key] = localizedDast;
|
|
1816
1838
|
continue;
|
|
1817
1839
|
}
|
|
1818
|
-
const
|
|
1840
|
+
const expanded = expandStructuredTextShorthand(data[field.api_key]);
|
|
1841
|
+
const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(expanded).pipe(Effect.mapError((e) => new ValidationError({
|
|
1819
1842
|
message: createFieldErrorMessage(errorPrefix, `Invalid StructuredText for field '${field.api_key}': ${e.message}`),
|
|
1820
1843
|
field: field.api_key
|
|
1821
1844
|
})));
|
|
@@ -2022,7 +2045,12 @@ function getRecord(modelApiKey, id) {
|
|
|
2022
2045
|
entity: "Record",
|
|
2023
2046
|
id
|
|
2024
2047
|
});
|
|
2025
|
-
|
|
2048
|
+
const fields = yield* getModelFields(model.id);
|
|
2049
|
+
return yield* materializeRecordStructuredTextFields({
|
|
2050
|
+
modelApiKey: model.api_key,
|
|
2051
|
+
record: normalizeBooleanFields(record, fields),
|
|
2052
|
+
fields
|
|
2053
|
+
});
|
|
2026
2054
|
});
|
|
2027
2055
|
}
|
|
2028
2056
|
function updateSingletonRecord(modelApiKey, data, actor) {
|
|
@@ -2106,7 +2134,8 @@ function patchRecord(id, body, actor) {
|
|
|
2106
2134
|
nextLocaleMap[localeCode] = null;
|
|
2107
2135
|
continue;
|
|
2108
2136
|
}
|
|
2109
|
-
const
|
|
2137
|
+
const expandedLocale = expandStructuredTextShorthand(localeValue);
|
|
2138
|
+
const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(scopeStructuredTextIds(expandedLocale, `${field.api_key}:${localeCode}`)).pipe(Effect.mapError((e) => new ValidationError({
|
|
2110
2139
|
message: `Invalid StructuredText for field '${field.api_key}' locale '${localeCode}': ${e.message}`,
|
|
2111
2140
|
field: field.api_key
|
|
2112
2141
|
})));
|
|
@@ -2127,7 +2156,8 @@ function patchRecord(id, body, actor) {
|
|
|
2127
2156
|
updates[field.api_key] = nextLocaleMap;
|
|
2128
2157
|
continue;
|
|
2129
2158
|
}
|
|
2130
|
-
const
|
|
2159
|
+
const expanded = expandStructuredTextShorthand(data[field.api_key]);
|
|
2160
|
+
const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(expanded).pipe(Effect.mapError((e) => new ValidationError({
|
|
2131
2161
|
message: `Invalid StructuredText for field '${field.api_key}': ${e.message}`,
|
|
2132
2162
|
field: field.api_key
|
|
2133
2163
|
})));
|
|
@@ -2484,23 +2514,11 @@ function patchBlocksForField(body, actor) {
|
|
|
2484
2514
|
message: `Block '${blockId}' does not exist in field '${body.fieldApiKey}'.`,
|
|
2485
2515
|
field: body.fieldApiKey
|
|
2486
2516
|
});
|
|
2487
|
-
} else if (typeof patchValue === "string") {
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
if (result.ambiguous) return yield* new ValidationError({
|
|
2493
|
-
message: `Block '${blockId}' matched multiple nested structured_text locations in field '${body.fieldApiKey}'. Patch the parent block explicitly instead.`,
|
|
2494
|
-
field: body.fieldApiKey
|
|
2495
|
-
});
|
|
2496
|
-
nestedMatched = nestedMatched || result.applied;
|
|
2497
|
-
}
|
|
2498
|
-
if (!nestedMatched) return yield* new ValidationError({
|
|
2499
|
-
message: `Block '${blockId}' does not exist in field '${body.fieldApiKey}'.`,
|
|
2500
|
-
field: body.fieldApiKey
|
|
2501
|
-
});
|
|
2502
|
-
}
|
|
2503
|
-
} else if (typeof patchValue === "object" && !Array.isArray(patchValue)) {
|
|
2517
|
+
} else if (typeof patchValue === "string") return yield* new ValidationError({
|
|
2518
|
+
message: `Invalid patch value for block '${blockId}': use an object to update fields, null to delete, or omit the key to keep unchanged.`,
|
|
2519
|
+
field: body.fieldApiKey
|
|
2520
|
+
});
|
|
2521
|
+
else if (typeof patchValue === "object" && !Array.isArray(patchValue)) {
|
|
2504
2522
|
if (!Object.hasOwn(existingBlocks, blockId)) {
|
|
2505
2523
|
let nestedMatched = false;
|
|
2506
2524
|
for (const topLevelBlock of Object.values(mergedBlocks)) {
|
|
@@ -2530,11 +2548,53 @@ function patchBlocksForField(body, actor) {
|
|
|
2530
2548
|
message: `Invalid patch value for block '${blockId}': expected string, object, or null`,
|
|
2531
2549
|
field: body.fieldApiKey
|
|
2532
2550
|
});
|
|
2551
|
+
const appendedIds = [];
|
|
2552
|
+
for (const entry of body.append ?? []) {
|
|
2553
|
+
const id = generateId();
|
|
2554
|
+
appendedIds.push(id);
|
|
2555
|
+
mergedBlocks[id] = entry;
|
|
2556
|
+
}
|
|
2533
2557
|
let finalDastValue;
|
|
2534
|
-
if (body.
|
|
2535
|
-
|
|
2558
|
+
if (body.order) {
|
|
2559
|
+
if (!getBlocksOnly(field.validators)) return yield* new ValidationError({
|
|
2560
|
+
message: "The 'order' parameter is only supported on blocks_only structured_text fields. Use the 'value' parameter to provide a custom DAST for mixed prose+block fields.",
|
|
2561
|
+
field: body.fieldApiKey
|
|
2562
|
+
});
|
|
2563
|
+
if (body.value !== void 0) return yield* new ValidationError({
|
|
2564
|
+
message: "Cannot use both 'order' and 'value' — they both control the DAST document structure.",
|
|
2565
|
+
field: body.fieldApiKey
|
|
2566
|
+
});
|
|
2567
|
+
finalDastValue = {
|
|
2568
|
+
schema: "dast",
|
|
2569
|
+
document: {
|
|
2570
|
+
type: "root",
|
|
2571
|
+
children: body.order.map((id) => ({
|
|
2572
|
+
type: "block",
|
|
2573
|
+
item: id
|
|
2574
|
+
}))
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
} else if (body.value !== void 0 && appendedIds.length > 0) return yield* new ValidationError({
|
|
2578
|
+
message: "Cannot use both 'value' and 'append' — appended blocks need auto-generated DAST nodes which conflict with a custom DAST value.",
|
|
2579
|
+
field: body.fieldApiKey
|
|
2580
|
+
});
|
|
2581
|
+
else if (body.value !== void 0) finalDastValue = body.value;
|
|
2582
|
+
else if (blockIdsToDelete.size > 0 || appendedIds.length > 0) {
|
|
2536
2583
|
const existingDast = existingEnvelope.value;
|
|
2537
|
-
|
|
2584
|
+
const pruned = blockIdsToDelete.size > 0 ? pruneBlockNodes(existingDast, blockIdsToDelete) : existingDast;
|
|
2585
|
+
if (appendedIds.length > 0) {
|
|
2586
|
+
const prunedDoc = pruned;
|
|
2587
|
+
finalDastValue = {
|
|
2588
|
+
...prunedDoc,
|
|
2589
|
+
document: {
|
|
2590
|
+
...prunedDoc.document,
|
|
2591
|
+
children: [...prunedDoc.document.children, ...appendedIds.map((id) => ({
|
|
2592
|
+
type: "block",
|
|
2593
|
+
item: id
|
|
2594
|
+
}))]
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
} else finalDastValue = pruned;
|
|
2538
2598
|
} else finalDastValue = existingEnvelope.value;
|
|
2539
2599
|
yield* deleteBlocksForField({
|
|
2540
2600
|
rootRecordId: body.recordId,
|
|
@@ -2587,11 +2647,16 @@ function patchBlocksForField(body, actor) {
|
|
|
2587
2647
|
});
|
|
2588
2648
|
const updatedRecord = yield* selectById(tableName, body.recordId);
|
|
2589
2649
|
if (!updatedRecord) return null;
|
|
2590
|
-
|
|
2650
|
+
const materialized = yield* materializeRecordStructuredTextFields({
|
|
2591
2651
|
modelApiKey: model.api_key,
|
|
2592
2652
|
record: normalizeBooleanFields(updatedRecord, modelFields),
|
|
2593
2653
|
fields: modelFields
|
|
2594
2654
|
});
|
|
2655
|
+
if (materialized && appendedIds.length > 0) return {
|
|
2656
|
+
...materialized,
|
|
2657
|
+
_appendedIds: appendedIds
|
|
2658
|
+
};
|
|
2659
|
+
return materialized;
|
|
2595
2660
|
});
|
|
2596
2661
|
}
|
|
2597
2662
|
/**
|
|
@@ -3419,7 +3484,8 @@ const CreateModelInput = Schema.Struct({
|
|
|
3419
3484
|
tree: Schema.optionalWith(Schema.Boolean, { default: () => false }),
|
|
3420
3485
|
hasDraft: Schema.optionalWith(Schema.Boolean, { default: () => true }),
|
|
3421
3486
|
allLocalesRequired: Schema.optionalWith(Schema.Boolean, { default: () => false }),
|
|
3422
|
-
ordering: Schema.optional(Schema.String)
|
|
3487
|
+
ordering: Schema.optional(Schema.String),
|
|
3488
|
+
canonicalPathTemplate: Schema.optional(Schema.NullOr(Schema.String))
|
|
3423
3489
|
});
|
|
3424
3490
|
const CreateFieldInput = Schema.Struct({
|
|
3425
3491
|
label: Schema.NonEmptyString,
|
|
@@ -3530,7 +3596,8 @@ const UpdateModelInput = Schema.Struct({
|
|
|
3530
3596
|
sortable: Schema.optional(Schema.Boolean),
|
|
3531
3597
|
hasDraft: Schema.optional(Schema.Boolean),
|
|
3532
3598
|
allLocalesRequired: Schema.optional(Schema.Boolean),
|
|
3533
|
-
ordering: Schema.optional(Schema.NullOr(Schema.String))
|
|
3599
|
+
ordering: Schema.optional(Schema.NullOr(Schema.String)),
|
|
3600
|
+
canonicalPathTemplate: Schema.optional(Schema.NullOr(Schema.String))
|
|
3534
3601
|
});
|
|
3535
3602
|
const UpdateFieldInput = Schema.Struct({
|
|
3536
3603
|
label: Schema.optional(Schema.NonEmptyString),
|
|
@@ -3573,6 +3640,7 @@ const SchemaExportModelSchema = Schema.Struct({
|
|
|
3573
3640
|
tree: Schema.Boolean,
|
|
3574
3641
|
hasDraft: Schema.Boolean,
|
|
3575
3642
|
ordering: Schema.optionalWith(Schema.NullOr(Schema.String), { default: () => null }),
|
|
3643
|
+
canonicalPathTemplate: Schema.optionalWith(Schema.NullOr(Schema.String), { default: () => null }),
|
|
3576
3644
|
fields: Schema.Array(SchemaExportFieldSchema)
|
|
3577
3645
|
});
|
|
3578
3646
|
const SchemaExportLocaleSchema = Schema.Struct({
|
|
@@ -3585,10 +3653,15 @@ const PatchBlocksInput = Schema.Struct({
|
|
|
3585
3653
|
modelApiKey: Schema.NonEmptyString,
|
|
3586
3654
|
fieldApiKey: Schema.NonEmptyString,
|
|
3587
3655
|
value: Schema.optional(Schema.Unknown),
|
|
3656
|
+
order: Schema.optional(Schema.Array(Schema.NonEmptyString)),
|
|
3588
3657
|
blocks: Schema.Record({
|
|
3589
3658
|
key: Schema.String,
|
|
3590
3659
|
value: Schema.NullOr(Schema.Unknown)
|
|
3591
|
-
})
|
|
3660
|
+
}),
|
|
3661
|
+
append: Schema.optional(Schema.Array(Schema.Record({
|
|
3662
|
+
key: Schema.String,
|
|
3663
|
+
value: Schema.Unknown
|
|
3664
|
+
})))
|
|
3592
3665
|
});
|
|
3593
3666
|
const ImportSchemaInput = Schema.Struct({
|
|
3594
3667
|
version: Schema.Literal(1),
|
|
@@ -3642,6 +3715,7 @@ function exportSchema() {
|
|
|
3642
3715
|
tree: !!m.tree,
|
|
3643
3716
|
hasDraft: !!m.has_draft,
|
|
3644
3717
|
ordering: m.ordering,
|
|
3718
|
+
canonicalPathTemplate: m.canonical_path_template ?? null,
|
|
3645
3719
|
fields: (fieldsByModelId.get(m.id) ?? []).map((f) => ({
|
|
3646
3720
|
label: f.label,
|
|
3647
3721
|
apiKey: f.api_key,
|
|
@@ -3693,7 +3767,8 @@ function importSchema(s) {
|
|
|
3693
3767
|
tree: model.tree,
|
|
3694
3768
|
hasDraft: model.hasDraft,
|
|
3695
3769
|
allLocalesRequired: false,
|
|
3696
|
-
ordering: model.ordering ?? void 0
|
|
3770
|
+
ordering: model.ordering ?? void 0,
|
|
3771
|
+
canonicalPathTemplate: model.canonicalPathTemplate ?? void 0
|
|
3697
3772
|
});
|
|
3698
3773
|
modelApiKeyToId.set(model.apiKey, result.id);
|
|
3699
3774
|
stats.models++;
|
|
@@ -3733,7 +3808,7 @@ function getTokenPrefix(token) {
|
|
|
3733
3808
|
function isLegacyStoredToken(row) {
|
|
3734
3809
|
return row.secret_hash === null;
|
|
3735
3810
|
}
|
|
3736
|
-
function hashToken(token) {
|
|
3811
|
+
function hashToken$1(token) {
|
|
3737
3812
|
return Effect.tryPromise({
|
|
3738
3813
|
try: async () => {
|
|
3739
3814
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
@@ -3752,7 +3827,7 @@ function createEditorToken(input) {
|
|
|
3752
3827
|
if (expiresIn !== void 0 && expiresIn <= 0) return yield* new ValidationError({ message: "expiresIn must be a positive integer" });
|
|
3753
3828
|
const expiresAt = expiresIn !== void 0 ? new Date(Date.now() + expiresIn * 1e3).toISOString() : null;
|
|
3754
3829
|
const tokenPrefix = getTokenPrefix(token);
|
|
3755
|
-
const secretHash = yield* hashToken(token);
|
|
3830
|
+
const secretHash = yield* hashToken$1(token);
|
|
3756
3831
|
yield* sql.unsafe(`INSERT INTO editor_tokens (id, name, token_prefix, secret_hash, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
3757
3832
|
id,
|
|
3758
3833
|
input.name,
|
|
@@ -3795,7 +3870,7 @@ function revokeEditorToken(id) {
|
|
|
3795
3870
|
function validateEditorToken(token) {
|
|
3796
3871
|
return Effect.gen(function* () {
|
|
3797
3872
|
const sql = yield* SqlClient.SqlClient;
|
|
3798
|
-
const secretHash = yield* hashToken(token);
|
|
3873
|
+
const secretHash = yield* hashToken$1(token);
|
|
3799
3874
|
const hashedRows = yield* sql.unsafe("SELECT * FROM editor_tokens WHERE secret_hash = ?", [secretHash]);
|
|
3800
3875
|
const legacyRows = hashedRows.length === 0 ? yield* sql.unsafe("SELECT * FROM editor_tokens WHERE id = ?", [token]) : [];
|
|
3801
3876
|
if (hashedRows.length === 0 && legacyRows.length === 0) return yield* new UnauthorizedError({ message: "Invalid editor token" });
|
|
@@ -3815,6 +3890,70 @@ function validateEditorToken(token) {
|
|
|
3815
3890
|
});
|
|
3816
3891
|
}
|
|
3817
3892
|
//#endregion
|
|
3818
|
-
|
|
3893
|
+
//#region src/services/preview-service.ts
|
|
3894
|
+
/**
|
|
3895
|
+
* Preview token service — short-lived tokens for draft preview access.
|
|
3896
|
+
* Follows the same SHA-256 hashing pattern as token-service.ts.
|
|
3897
|
+
*/
|
|
3898
|
+
function hashToken(token) {
|
|
3899
|
+
return Effect.tryPromise({
|
|
3900
|
+
try: async () => {
|
|
3901
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
3902
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3903
|
+
},
|
|
3904
|
+
catch: (cause) => new ValidationError({ message: `Failed to hash preview token: ${cause instanceof Error ? cause.message : String(cause)}` })
|
|
3905
|
+
});
|
|
3906
|
+
}
|
|
3907
|
+
function generatePreviewToken() {
|
|
3908
|
+
const bytes = new Uint8Array(32);
|
|
3909
|
+
crypto.getRandomValues(bytes);
|
|
3910
|
+
return `pvt_${btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")}`;
|
|
3911
|
+
}
|
|
3912
|
+
const DEFAULT_EXPIRY_SECONDS = 1440 * 60;
|
|
3913
|
+
function createPreviewToken(expiresIn) {
|
|
3914
|
+
return Effect.gen(function* () {
|
|
3915
|
+
const sql = yield* SqlClient.SqlClient;
|
|
3916
|
+
const seconds = expiresIn ?? DEFAULT_EXPIRY_SECONDS;
|
|
3917
|
+
if (seconds <= 0) return yield* new ValidationError({ message: "expiresIn must be a positive number" });
|
|
3918
|
+
const id = generateId();
|
|
3919
|
+
const token = generatePreviewToken();
|
|
3920
|
+
const tokenHash = yield* hashToken(token);
|
|
3921
|
+
const expiresAt = new Date(Date.now() + seconds * 1e3).toISOString();
|
|
3922
|
+
yield* sql.unsafe(`INSERT INTO preview_tokens (id, token_hash, expires_at) VALUES (?, ?, ?)`, [
|
|
3923
|
+
id,
|
|
3924
|
+
tokenHash,
|
|
3925
|
+
expiresAt
|
|
3926
|
+
]);
|
|
3927
|
+
return {
|
|
3928
|
+
id,
|
|
3929
|
+
token,
|
|
3930
|
+
expiresAt
|
|
3931
|
+
};
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3934
|
+
function validatePreviewToken(token) {
|
|
3935
|
+
return Effect.gen(function* () {
|
|
3936
|
+
const sql = yield* SqlClient.SqlClient;
|
|
3937
|
+
const tokenHash = yield* hashToken(token);
|
|
3938
|
+
const rows = yield* sql.unsafe("SELECT id, expires_at FROM preview_tokens WHERE token_hash = ?", [tokenHash]);
|
|
3939
|
+
if (rows.length === 0) return { valid: false };
|
|
3940
|
+
const row = rows[0];
|
|
3941
|
+
if (new Date(row.expires_at) < /* @__PURE__ */ new Date()) return { valid: false };
|
|
3942
|
+
yield* Effect.fork(sql.unsafe(`DELETE FROM preview_tokens WHERE id IN (SELECT id FROM preview_tokens WHERE expires_at < datetime('now') LIMIT 100)`).pipe(Effect.ignore));
|
|
3943
|
+
return {
|
|
3944
|
+
valid: true,
|
|
3945
|
+
expiresAt: row.expires_at
|
|
3946
|
+
};
|
|
3947
|
+
});
|
|
3948
|
+
}
|
|
3949
|
+
function resolvePreviewPath(canonicalPathTemplate, recordData) {
|
|
3950
|
+
return canonicalPathTemplate.replace(/\{([^}]+)\}/g, (_match, fieldName) => {
|
|
3951
|
+
const value = recordData[fieldName];
|
|
3952
|
+
if (value === void 0 || value === null) return "";
|
|
3953
|
+
return encodeURIComponent(String(value));
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
//#endregion
|
|
3957
|
+
export { bulkCreateRecords as $, clearSchedule as A, AssetImportContext as B, ReorderInput as C, search as Ct, UpdateAssetMetadataInput as D, SearchInput as E, deleteLocale as F, listAssets as G, deleteAsset as H, listLocales as I, updateAssetMetadata as J, replaceAsset as K, removeBlockFromWhitelist as L, schedulePublish as M, scheduleUnpublish as N, UpdateFieldInput as O, createLocale as P, unpublishRecord as Q, removeBlockType as R, ReindexSearchInput as S, reindexAll as St, SearchAssetsInput as T, getAsset as U, createAsset as V, importAssetFromUrl as W, bulkUnpublishRecords as X, bulkPublishRecords as Y, publishRecord as Z, CreateUploadUrlInput as _, deleteModel as _t, listEditorTokens as a, removeRecord as at, PatchBlocksInput as b, listModels as bt, exportSchema as c, getVersion as ct, CreateAssetInput as d, HooksContext as dt, createRecord as et, CreateEditorTokenInput as f, createField as ft, CreateRecordInput as g, createModel as gt, CreateModelInput as h, updateField as ht, createEditorToken as i, patchRecord as it, runScheduledTransitions as j, UpdateModelInput as k, importSchema as l, listVersions as lt, CreateLocaleInput as m, listFields as mt, resolvePreviewPath as n, listRecords as nt, revokeEditorToken as o, reorderRecords as ot, CreateFieldInput as p, deleteField as pt, searchAssets as q, validatePreviewToken as r, patchBlocksForField as rt, validateEditorToken as s, updateSingletonRecord as st, createPreviewToken as t, getRecord as tt, BulkCreateRecordsInput as u, restoreVersion as ut, ImportAssetFromUrlInput as v, getModel as vt, ScheduleRecordInput as w, VectorizeContext as wt, PatchRecordInput as x, updateModel as xt, ImportSchemaInput as y, getModelByApiKey$1 as yt, removeLocale as z };
|
|
3819
3958
|
|
|
3820
|
-
//# sourceMappingURL=
|
|
3959
|
+
//# sourceMappingURL=preview-service-C9Tmhdye.mjs.map
|