convex-cms 0.0.2 → 0.0.3
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/admin-dist/nitro.json +15 -0
- package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
- package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
- package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
- package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
- package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
- package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
- package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
- package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
- package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
- package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
- package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
- package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
- package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
- package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
- package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
- package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
- package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
- package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
- package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
- package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
- package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
- package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
- package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
- package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
- package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
- package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
- package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
- package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
- package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
- package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
- package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
- package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
- package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
- package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
- package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
- package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
- package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
- package/admin-dist/public/favicon.ico +0 -0
- package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
- package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
- package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
- package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
- package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
- package/admin-dist/server/_libs/clsx.mjs +16 -0
- package/admin-dist/server/_libs/cmdk.mjs +315 -0
- package/admin-dist/server/_libs/convex.mjs +4841 -0
- package/admin-dist/server/_libs/cookie-es.mjs +58 -0
- package/admin-dist/server/_libs/croner.mjs +1 -0
- package/admin-dist/server/_libs/crossws.mjs +1 -0
- package/admin-dist/server/_libs/date-fns.mjs +1716 -0
- package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
- package/admin-dist/server/_libs/get-nonce.mjs +9 -0
- package/admin-dist/server/_libs/h3-v2.mjs +277 -0
- package/admin-dist/server/_libs/h3.mjs +401 -0
- package/admin-dist/server/_libs/hookable.mjs +1 -0
- package/admin-dist/server/_libs/isbot.mjs +20 -0
- package/admin-dist/server/_libs/lucide-react.mjs +850 -0
- package/admin-dist/server/_libs/ohash.mjs +1 -0
- package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
- package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
- package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
- package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
- package/admin-dist/server/_libs/rou3.mjs +8 -0
- package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
- package/admin-dist/server/_libs/seroval.mjs +1765 -0
- package/admin-dist/server/_libs/srvx.mjs +719 -0
- package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
- package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
- package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
- package/admin-dist/server/_libs/tslib.mjs +39 -0
- package/admin-dist/server/_libs/ufo.mjs +54 -0
- package/admin-dist/server/_libs/unctx.mjs +1 -0
- package/admin-dist/server/_libs/unstorage.mjs +1 -0
- package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
- package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
- package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
- package/admin-dist/server/_libs/zod.mjs +4223 -0
- package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
- package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
- package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
- package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
- package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
- package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
- package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
- package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
- package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
- package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
- package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
- package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
- package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
- package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
- package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
- package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
- package/admin-dist/server/_ssr/index.mjs +1275 -0
- package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
- package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
- package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
- package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
- package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
- package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
- package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
- package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
- package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
- package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
- package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
- package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
- package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
- package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
- package/admin-dist/server/favicon.ico +0 -0
- package/admin-dist/server/index.mjs +627 -0
- package/dist/cli/index.js +0 -0
- package/dist/client/admin-config.d.ts +0 -1
- package/dist/client/admin-config.d.ts.map +1 -1
- package/dist/client/admin-config.js +0 -1
- package/dist/client/admin-config.js.map +1 -1
- package/dist/client/adminApi.d.ts.map +1 -1
- package/dist/client/agentTools.d.ts +1237 -135
- package/dist/client/agentTools.d.ts.map +1 -1
- package/dist/client/agentTools.js +33 -9
- package/dist/client/agentTools.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +9 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/mediaAssets.d.ts +35 -0
- package/dist/component/mediaAssets.d.ts.map +1 -1
- package/dist/component/mediaAssets.js +81 -0
- package/dist/component/mediaAssets.js.map +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +2 -1
- package/dist/test.js.map +1 -1
- package/package.json +9 -5
- package/dist/component/auditLog.d.ts +0 -410
- package/dist/component/auditLog.d.ts.map +0 -1
- package/dist/component/auditLog.js +0 -607
- package/dist/component/auditLog.js.map +0 -1
- package/dist/component/types.d.ts +0 -4
- package/dist/component/types.d.ts.map +0 -1
- package/dist/component/types.js +0 -2
- package/dist/component/types.js.map +0 -1
- package/src/cli/commands/admin.ts +0 -104
- package/src/cli/index.ts +0 -21
- package/src/cli/utils/detectConvexUrl.ts +0 -54
- package/src/cli/utils/openBrowser.ts +0 -16
- package/src/client/admin-config.ts +0 -138
- package/src/client/adminApi.ts +0 -942
- package/src/client/agentTools.ts +0 -1311
- package/src/client/argTypes.ts +0 -316
- package/src/client/field-types.ts +0 -187
- package/src/client/index.ts +0 -1301
- package/src/client/queryBuilder.ts +0 -1100
- package/src/client/schema/codegen.ts +0 -500
- package/src/client/schema/defineContentType.ts +0 -501
- package/src/client/schema/index.ts +0 -169
- package/src/client/schema/schemaDrift.ts +0 -574
- package/src/client/schema/typedClient.ts +0 -688
- package/src/client/schema/types.ts +0 -666
- package/src/client/types.ts +0 -723
- package/src/client/workflows.ts +0 -141
- package/src/client/wrapper.ts +0 -4304
- package/src/component/_generated/api.ts +0 -140
- package/src/component/_generated/component.ts +0 -5029
- package/src/component/_generated/dataModel.ts +0 -60
- package/src/component/_generated/server.ts +0 -156
- package/src/component/authorization.ts +0 -647
- package/src/component/authorizationHooks.ts +0 -668
- package/src/component/bulkOperations.ts +0 -687
- package/src/component/contentEntries.ts +0 -1976
- package/src/component/contentEntryMutations.ts +0 -1223
- package/src/component/contentEntryValidation.ts +0 -707
- package/src/component/contentLock.ts +0 -550
- package/src/component/contentTypeMigration.ts +0 -1064
- package/src/component/contentTypeMutations.ts +0 -969
- package/src/component/contentTypes.ts +0 -346
- package/src/component/convex.config.ts +0 -44
- package/src/component/documentTypes.ts +0 -240
- package/src/component/eventEmitter.ts +0 -485
- package/src/component/exportImport.ts +0 -1169
- package/src/component/index.ts +0 -491
- package/src/component/lib/deepReferenceResolver.ts +0 -999
- package/src/component/lib/errors.ts +0 -816
- package/src/component/lib/index.ts +0 -145
- package/src/component/lib/mediaReferenceResolver.ts +0 -495
- package/src/component/lib/metadataExtractor.ts +0 -792
- package/src/component/lib/mutationAuth.ts +0 -199
- package/src/component/lib/queries.ts +0 -79
- package/src/component/lib/ragContentChunker.ts +0 -1371
- package/src/component/lib/referenceResolver.ts +0 -430
- package/src/component/lib/slugGenerator.ts +0 -262
- package/src/component/lib/slugUniqueness.ts +0 -333
- package/src/component/lib/softDelete.ts +0 -44
- package/src/component/localeFallbackChain.ts +0 -673
- package/src/component/localeFields.ts +0 -896
- package/src/component/mediaAssetMutations.ts +0 -725
- package/src/component/mediaAssets.ts +0 -932
- package/src/component/mediaFolderMutations.ts +0 -1046
- package/src/component/mediaUploadMutations.ts +0 -224
- package/src/component/mediaVariantMutations.ts +0 -900
- package/src/component/mediaVariants.ts +0 -793
- package/src/component/ragContentIndexer.ts +0 -1067
- package/src/component/rateLimitHooks.ts +0 -572
- package/src/component/roles.ts +0 -1360
- package/src/component/scheduledPublish.ts +0 -358
- package/src/component/schema.ts +0 -617
- package/src/component/taxonomies.ts +0 -949
- package/src/component/taxonomyMutations.ts +0 -1210
- package/src/component/trash.ts +0 -724
- package/src/component/userContext.ts +0 -898
- package/src/component/validation.ts +0 -1388
- package/src/component/validators.ts +0 -949
- package/src/component/versionMutations.ts +0 -392
- package/src/component/webhookTrigger.ts +0 -1922
- package/src/react/index.ts +0 -898
- package/src/test.ts +0 -1580
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content Entry Validation Internal Functions
|
|
3
|
-
*
|
|
4
|
-
* Provides internal functions to validate content entry data against its
|
|
5
|
-
* content type schema. Used by mutations before creating/updating entries,
|
|
6
|
-
* and can be called directly for preview validation.
|
|
7
|
-
*
|
|
8
|
-
* Validation includes:
|
|
9
|
-
* - Required field checks
|
|
10
|
-
* - Type correctness validation
|
|
11
|
-
* - Custom field constraints (min/max, pattern, etc.)
|
|
12
|
-
* - Reference validation (existence and content type constraints)
|
|
13
|
-
* - Media reference validation
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { v } from "convex/values";
|
|
17
|
-
import { isDeleted } from "./lib/softDelete.js";
|
|
18
|
-
import { internalQuery, query } from "./_generated/server.js";
|
|
19
|
-
import { Id } from "./_generated/dataModel.js";
|
|
20
|
-
import {
|
|
21
|
-
validateContentData,
|
|
22
|
-
ContentData,
|
|
23
|
-
ContentTypeSchema,
|
|
24
|
-
FieldDefinition,
|
|
25
|
-
ValidationError,
|
|
26
|
-
} from "./validation.js";
|
|
27
|
-
|
|
28
|
-
// =============================================================================
|
|
29
|
-
// Types
|
|
30
|
-
// =============================================================================
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Extended validation result that includes reference validation errors
|
|
34
|
-
*/
|
|
35
|
-
export type ContentEntryValidationResult = {
|
|
36
|
-
/** Whether the validation passed */
|
|
37
|
-
valid: boolean;
|
|
38
|
-
/** Array of validation errors (empty if valid) */
|
|
39
|
-
errors: ValidationError[];
|
|
40
|
-
/** Content type name (if found) */
|
|
41
|
-
contentTypeName?: string;
|
|
42
|
-
/** Content type display name (if found) */
|
|
43
|
-
contentTypeDisplayName?: string;
|
|
44
|
-
/** Whether reference validation was performed */
|
|
45
|
-
referencesValidated: boolean;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Options for content entry validation
|
|
50
|
-
*/
|
|
51
|
-
export interface ValidateContentEntryOptions {
|
|
52
|
-
/**
|
|
53
|
-
* If true, validates that referenced entries exist and belong to allowed content types.
|
|
54
|
-
* This requires additional database queries but provides complete validation.
|
|
55
|
-
* Default: true
|
|
56
|
-
*/
|
|
57
|
-
validateReferences?: boolean;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* If true, reports unknown fields (fields not defined in the content type) as errors.
|
|
61
|
-
* Default: false
|
|
62
|
-
*/
|
|
63
|
-
strictFields?: boolean;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// =============================================================================
|
|
67
|
-
// Validator Definitions
|
|
68
|
-
// =============================================================================
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Argument validator for validateContentEntry
|
|
72
|
-
*/
|
|
73
|
-
const validateContentEntryArgs = v.object({
|
|
74
|
-
/** The content type ID to validate against */
|
|
75
|
-
contentTypeId: v.id("contentTypes"),
|
|
76
|
-
/** The content data to validate */
|
|
77
|
-
data: v.any(),
|
|
78
|
-
/** Validation options */
|
|
79
|
-
options: v.optional(
|
|
80
|
-
v.object({
|
|
81
|
-
validateReferences: v.optional(v.boolean()),
|
|
82
|
-
strictFields: v.optional(v.boolean()),
|
|
83
|
-
}),
|
|
84
|
-
),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Argument validator for validateContentEntryByTypeName
|
|
89
|
-
*/
|
|
90
|
-
const validateContentEntryByTypeNameArgs = v.object({
|
|
91
|
-
/** The content type name to validate against */
|
|
92
|
-
contentTypeName: v.string(),
|
|
93
|
-
/** The content data to validate */
|
|
94
|
-
data: v.any(),
|
|
95
|
-
/** Validation options */
|
|
96
|
-
options: v.optional(
|
|
97
|
-
v.object({
|
|
98
|
-
validateReferences: v.optional(v.boolean()),
|
|
99
|
-
strictFields: v.optional(v.boolean()),
|
|
100
|
-
}),
|
|
101
|
-
),
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Return validator for validation results
|
|
106
|
-
*/
|
|
107
|
-
const validationResultValidator = v.object({
|
|
108
|
-
valid: v.boolean(),
|
|
109
|
-
errors: v.array(
|
|
110
|
-
v.object({
|
|
111
|
-
field: v.string(),
|
|
112
|
-
message: v.string(),
|
|
113
|
-
code: v.string(),
|
|
114
|
-
}),
|
|
115
|
-
),
|
|
116
|
-
contentTypeName: v.optional(v.string()),
|
|
117
|
-
contentTypeDisplayName: v.optional(v.string()),
|
|
118
|
-
referencesValidated: v.boolean(),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// =============================================================================
|
|
122
|
-
// Internal Query: Validate Content Entry
|
|
123
|
-
// =============================================================================
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Internal query to validate content entry data against its content type schema.
|
|
127
|
-
*
|
|
128
|
-
* This function performs comprehensive validation including:
|
|
129
|
-
* 1. Required field checks - ensures all required fields have values
|
|
130
|
-
* 2. Type validation - verifies values match expected types (string, number, etc.)
|
|
131
|
-
* 3. Constraint validation - checks min/max length, patterns, allowed values, etc.
|
|
132
|
-
* 4. Reference validation - optionally verifies referenced entries exist and have correct types
|
|
133
|
-
*
|
|
134
|
-
* @param contentTypeId - The ID of the content type to validate against
|
|
135
|
-
* @param data - The content data to validate
|
|
136
|
-
* @param options - Optional validation options
|
|
137
|
-
*
|
|
138
|
-
* @returns ValidationResult with any errors found
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```typescript
|
|
142
|
-
* // Validate blog post data before creating
|
|
143
|
-
* const result = await ctx.runQuery(internal.contentEntryValidation.validateContentEntry, {
|
|
144
|
-
* contentTypeId: blogTypeId,
|
|
145
|
-
* data: {
|
|
146
|
-
* title: "My Post",
|
|
147
|
-
* content: "<p>Hello world!</p>",
|
|
148
|
-
* author: authorEntryId,
|
|
149
|
-
* },
|
|
150
|
-
* });
|
|
151
|
-
*
|
|
152
|
-
* if (!result.valid) {
|
|
153
|
-
* console.error("Validation errors:", result.errors);
|
|
154
|
-
* }
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
export const validateContentEntry = internalQuery({
|
|
158
|
-
args: validateContentEntryArgs.fields,
|
|
159
|
-
returns: validationResultValidator,
|
|
160
|
-
handler: async (ctx, args): Promise<ContentEntryValidationResult> => {
|
|
161
|
-
const { contentTypeId, data, options } = args;
|
|
162
|
-
const validateReferences = options?.validateReferences ?? true;
|
|
163
|
-
const strictFields = options?.strictFields ?? false;
|
|
164
|
-
|
|
165
|
-
// Fetch the content type
|
|
166
|
-
const contentType = await ctx.db.get(contentTypeId);
|
|
167
|
-
if (!contentType) {
|
|
168
|
-
return {
|
|
169
|
-
valid: false,
|
|
170
|
-
errors: [
|
|
171
|
-
{
|
|
172
|
-
field: "_contentType",
|
|
173
|
-
message: `Content type not found: ${contentTypeId}`,
|
|
174
|
-
code: "INVALID_CONTENT_TYPE",
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
referencesValidated: false,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (isDeleted(contentType)) {
|
|
182
|
-
return {
|
|
183
|
-
valid: false,
|
|
184
|
-
errors: [
|
|
185
|
-
{
|
|
186
|
-
field: "_contentType",
|
|
187
|
-
message: `Content type has been deleted: ${contentType.name}`,
|
|
188
|
-
code: "INVALID_CONTENT_TYPE",
|
|
189
|
-
},
|
|
190
|
-
],
|
|
191
|
-
contentTypeName: contentType.name,
|
|
192
|
-
contentTypeDisplayName: contentType.displayName,
|
|
193
|
-
referencesValidated: false,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!contentType.isActive) {
|
|
198
|
-
return {
|
|
199
|
-
valid: false,
|
|
200
|
-
errors: [
|
|
201
|
-
{
|
|
202
|
-
field: "_contentType",
|
|
203
|
-
message: `Content type is not active: ${contentType.name}`,
|
|
204
|
-
code: "INVALID_CONTENT_TYPE",
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
contentTypeName: contentType.name,
|
|
208
|
-
contentTypeDisplayName: contentType.displayName,
|
|
209
|
-
referencesValidated: false,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Build the schema for validation
|
|
214
|
-
const schema: ContentTypeSchema = {
|
|
215
|
-
name: contentType.name,
|
|
216
|
-
displayName: contentType.displayName,
|
|
217
|
-
description: contentType.description,
|
|
218
|
-
fields: contentType.fields as FieldDefinition[],
|
|
219
|
-
titleField: contentType.titleField,
|
|
220
|
-
slugField: contentType.slugField,
|
|
221
|
-
singleton: contentType.singleton,
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// Perform basic validation
|
|
225
|
-
const contentData = data as ContentData;
|
|
226
|
-
const basicResult = validateContentData(contentData, schema, {
|
|
227
|
-
strictFields,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// Collect all errors
|
|
231
|
-
const errors: ValidationError[] = [...basicResult.errors];
|
|
232
|
-
|
|
233
|
-
// Perform reference validation if enabled
|
|
234
|
-
let referencesValidated = false;
|
|
235
|
-
if (validateReferences) {
|
|
236
|
-
const referenceErrors = await validateReferences_internal(
|
|
237
|
-
ctx,
|
|
238
|
-
contentData,
|
|
239
|
-
schema.fields,
|
|
240
|
-
);
|
|
241
|
-
errors.push(...referenceErrors);
|
|
242
|
-
referencesValidated = true;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
valid: errors.length === 0,
|
|
247
|
-
errors,
|
|
248
|
-
contentTypeName: contentType.name,
|
|
249
|
-
contentTypeDisplayName: contentType.displayName,
|
|
250
|
-
referencesValidated,
|
|
251
|
-
};
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Public query to validate content entry data.
|
|
257
|
-
*
|
|
258
|
-
* Same as the internal version but exposed as a public query for use
|
|
259
|
-
* from the client (e.g., for form validation before submission).
|
|
260
|
-
*/
|
|
261
|
-
export const validateEntry = query({
|
|
262
|
-
args: validateContentEntryArgs.fields,
|
|
263
|
-
returns: validationResultValidator,
|
|
264
|
-
handler: async (ctx, args): Promise<ContentEntryValidationResult> => {
|
|
265
|
-
const { contentTypeId, data, options } = args;
|
|
266
|
-
const validateReferencesOption = options?.validateReferences ?? true;
|
|
267
|
-
const strictFields = options?.strictFields ?? false;
|
|
268
|
-
|
|
269
|
-
// Fetch the content type
|
|
270
|
-
const contentType = await ctx.db.get(contentTypeId);
|
|
271
|
-
if (!contentType) {
|
|
272
|
-
return {
|
|
273
|
-
valid: false,
|
|
274
|
-
errors: [
|
|
275
|
-
{
|
|
276
|
-
field: "_contentType",
|
|
277
|
-
message: `Content type not found: ${contentTypeId}`,
|
|
278
|
-
code: "INVALID_CONTENT_TYPE",
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
referencesValidated: false,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (isDeleted(contentType)) {
|
|
286
|
-
return {
|
|
287
|
-
valid: false,
|
|
288
|
-
errors: [
|
|
289
|
-
{
|
|
290
|
-
field: "_contentType",
|
|
291
|
-
message: `Content type has been deleted: ${contentType.name}`,
|
|
292
|
-
code: "INVALID_CONTENT_TYPE",
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
contentTypeName: contentType.name,
|
|
296
|
-
contentTypeDisplayName: contentType.displayName,
|
|
297
|
-
referencesValidated: false,
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!contentType.isActive) {
|
|
302
|
-
return {
|
|
303
|
-
valid: false,
|
|
304
|
-
errors: [
|
|
305
|
-
{
|
|
306
|
-
field: "_contentType",
|
|
307
|
-
message: `Content type is not active: ${contentType.name}`,
|
|
308
|
-
code: "INVALID_CONTENT_TYPE",
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
contentTypeName: contentType.name,
|
|
312
|
-
contentTypeDisplayName: contentType.displayName,
|
|
313
|
-
referencesValidated: false,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Build the schema for validation
|
|
318
|
-
const schema: ContentTypeSchema = {
|
|
319
|
-
name: contentType.name,
|
|
320
|
-
displayName: contentType.displayName,
|
|
321
|
-
description: contentType.description,
|
|
322
|
-
fields: contentType.fields as FieldDefinition[],
|
|
323
|
-
titleField: contentType.titleField,
|
|
324
|
-
slugField: contentType.slugField,
|
|
325
|
-
singleton: contentType.singleton,
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// Perform basic validation
|
|
329
|
-
const contentData = data as ContentData;
|
|
330
|
-
const basicResult = validateContentData(contentData, schema, {
|
|
331
|
-
strictFields,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Collect all errors
|
|
335
|
-
const errors: ValidationError[] = [...basicResult.errors];
|
|
336
|
-
|
|
337
|
-
// Perform reference validation if enabled
|
|
338
|
-
let referencesValidated = false;
|
|
339
|
-
if (validateReferencesOption) {
|
|
340
|
-
const referenceErrors = await validateReferences_internal(
|
|
341
|
-
ctx,
|
|
342
|
-
contentData,
|
|
343
|
-
schema.fields,
|
|
344
|
-
);
|
|
345
|
-
errors.push(...referenceErrors);
|
|
346
|
-
referencesValidated = true;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
valid: errors.length === 0,
|
|
351
|
-
errors,
|
|
352
|
-
contentTypeName: contentType.name,
|
|
353
|
-
contentTypeDisplayName: contentType.displayName,
|
|
354
|
-
referencesValidated,
|
|
355
|
-
};
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Internal query to validate content entry data by content type name.
|
|
361
|
-
*
|
|
362
|
-
* Same as validateContentEntry but accepts content type name instead of ID.
|
|
363
|
-
* Useful when you know the type name but not the ID.
|
|
364
|
-
*/
|
|
365
|
-
export const validateContentEntryByTypeName = internalQuery({
|
|
366
|
-
args: validateContentEntryByTypeNameArgs.fields,
|
|
367
|
-
returns: validationResultValidator,
|
|
368
|
-
handler: async (ctx, args): Promise<ContentEntryValidationResult> => {
|
|
369
|
-
const { contentTypeName, data, options } = args;
|
|
370
|
-
|
|
371
|
-
// Find the content type by name
|
|
372
|
-
const contentType = await ctx.db
|
|
373
|
-
.query("contentTypes")
|
|
374
|
-
.withIndex("by_name", (q) => q.eq("name", contentTypeName))
|
|
375
|
-
.first();
|
|
376
|
-
|
|
377
|
-
if (!contentType) {
|
|
378
|
-
return {
|
|
379
|
-
valid: false,
|
|
380
|
-
errors: [
|
|
381
|
-
{
|
|
382
|
-
field: "_contentType",
|
|
383
|
-
message: `Content type not found: ${contentTypeName}`,
|
|
384
|
-
code: "INVALID_CONTENT_TYPE",
|
|
385
|
-
},
|
|
386
|
-
],
|
|
387
|
-
referencesValidated: false,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Delegate to the ID-based validation
|
|
392
|
-
const validateReferencesOption = options?.validateReferences ?? true;
|
|
393
|
-
const strictFields = options?.strictFields ?? false;
|
|
394
|
-
|
|
395
|
-
if (isDeleted(contentType)) {
|
|
396
|
-
return {
|
|
397
|
-
valid: false,
|
|
398
|
-
errors: [
|
|
399
|
-
{
|
|
400
|
-
field: "_contentType",
|
|
401
|
-
message: `Content type has been deleted: ${contentType.name}`,
|
|
402
|
-
code: "INVALID_CONTENT_TYPE",
|
|
403
|
-
},
|
|
404
|
-
],
|
|
405
|
-
contentTypeName: contentType.name,
|
|
406
|
-
contentTypeDisplayName: contentType.displayName,
|
|
407
|
-
referencesValidated: false,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!contentType.isActive) {
|
|
412
|
-
return {
|
|
413
|
-
valid: false,
|
|
414
|
-
errors: [
|
|
415
|
-
{
|
|
416
|
-
field: "_contentType",
|
|
417
|
-
message: `Content type is not active: ${contentType.name}`,
|
|
418
|
-
code: "INVALID_CONTENT_TYPE",
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
contentTypeName: contentType.name,
|
|
422
|
-
contentTypeDisplayName: contentType.displayName,
|
|
423
|
-
referencesValidated: false,
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Build the schema for validation
|
|
428
|
-
const schema: ContentTypeSchema = {
|
|
429
|
-
name: contentType.name,
|
|
430
|
-
displayName: contentType.displayName,
|
|
431
|
-
description: contentType.description,
|
|
432
|
-
fields: contentType.fields as FieldDefinition[],
|
|
433
|
-
titleField: contentType.titleField,
|
|
434
|
-
slugField: contentType.slugField,
|
|
435
|
-
singleton: contentType.singleton,
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
// Perform basic validation
|
|
439
|
-
const contentData = data as ContentData;
|
|
440
|
-
const basicResult = validateContentData(contentData, schema, {
|
|
441
|
-
strictFields,
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
// Collect all errors
|
|
445
|
-
const errors: ValidationError[] = [...basicResult.errors];
|
|
446
|
-
|
|
447
|
-
// Perform reference validation if enabled
|
|
448
|
-
let referencesValidated = false;
|
|
449
|
-
if (validateReferencesOption) {
|
|
450
|
-
const referenceErrors = await validateReferences_internal(
|
|
451
|
-
ctx,
|
|
452
|
-
contentData,
|
|
453
|
-
schema.fields,
|
|
454
|
-
);
|
|
455
|
-
errors.push(...referenceErrors);
|
|
456
|
-
referencesValidated = true;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
valid: errors.length === 0,
|
|
461
|
-
errors,
|
|
462
|
-
contentTypeName: contentType.name,
|
|
463
|
-
contentTypeDisplayName: contentType.displayName,
|
|
464
|
-
referencesValidated,
|
|
465
|
-
};
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// =============================================================================
|
|
470
|
-
// Helper Functions
|
|
471
|
-
// =============================================================================
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Validates reference and media fields by checking that referenced entries exist
|
|
475
|
-
* and belong to allowed content types.
|
|
476
|
-
*
|
|
477
|
-
* @param ctx - The query context for database access
|
|
478
|
-
* @param data - The content data being validated
|
|
479
|
-
* @param fields - The field definitions from the content type
|
|
480
|
-
* @returns Array of validation errors for invalid references
|
|
481
|
-
*/
|
|
482
|
-
async function validateReferences_internal(
|
|
483
|
-
ctx: { db: { get: (id: Id<any>) => Promise<any> } },
|
|
484
|
-
data: ContentData,
|
|
485
|
-
fields: FieldDefinition[],
|
|
486
|
-
): Promise<ValidationError[]> {
|
|
487
|
-
const errors: ValidationError[] = [];
|
|
488
|
-
|
|
489
|
-
for (const field of fields) {
|
|
490
|
-
const value = data[field.name];
|
|
491
|
-
|
|
492
|
-
// Skip if no value
|
|
493
|
-
if (value === null || value === undefined) {
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (field.type === "reference") {
|
|
498
|
-
const multiple = field.options?.multiple ?? false;
|
|
499
|
-
const allowedContentTypes = field.options?.allowedContentTypes;
|
|
500
|
-
|
|
501
|
-
if (multiple && Array.isArray(value)) {
|
|
502
|
-
// Multiple references
|
|
503
|
-
for (let i = 0; i < value.length; i++) {
|
|
504
|
-
const refId = value[i];
|
|
505
|
-
if (typeof refId === "string") {
|
|
506
|
-
const refErrors = await validateSingleReference(
|
|
507
|
-
ctx,
|
|
508
|
-
refId,
|
|
509
|
-
field.name,
|
|
510
|
-
allowedContentTypes,
|
|
511
|
-
i,
|
|
512
|
-
);
|
|
513
|
-
errors.push(...refErrors);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
} else if (!multiple && typeof value === "string") {
|
|
517
|
-
// Single reference
|
|
518
|
-
const refErrors = await validateSingleReference(
|
|
519
|
-
ctx,
|
|
520
|
-
value,
|
|
521
|
-
field.name,
|
|
522
|
-
allowedContentTypes,
|
|
523
|
-
);
|
|
524
|
-
errors.push(...refErrors);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (field.type === "media") {
|
|
529
|
-
const multiple = field.options?.multiple ?? false;
|
|
530
|
-
|
|
531
|
-
if (multiple && Array.isArray(value)) {
|
|
532
|
-
// Multiple media assets
|
|
533
|
-
for (let i = 0; i < value.length; i++) {
|
|
534
|
-
const assetId = value[i];
|
|
535
|
-
if (typeof assetId === "string") {
|
|
536
|
-
const mediaErrors = await validateSingleMediaAsset(
|
|
537
|
-
ctx,
|
|
538
|
-
assetId,
|
|
539
|
-
field.name,
|
|
540
|
-
field.options?.allowedMimeTypes,
|
|
541
|
-
field.options?.maxFileSize,
|
|
542
|
-
i,
|
|
543
|
-
);
|
|
544
|
-
errors.push(...mediaErrors);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
} else if (!multiple && typeof value === "string") {
|
|
548
|
-
// Single media asset
|
|
549
|
-
const mediaErrors = await validateSingleMediaAsset(
|
|
550
|
-
ctx,
|
|
551
|
-
value,
|
|
552
|
-
field.name,
|
|
553
|
-
field.options?.allowedMimeTypes,
|
|
554
|
-
field.options?.maxFileSize,
|
|
555
|
-
);
|
|
556
|
-
errors.push(...mediaErrors);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
return errors;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Validates a single reference to a content entry.
|
|
566
|
-
*/
|
|
567
|
-
async function validateSingleReference(
|
|
568
|
-
ctx: { db: { get: (id: Id<any>) => Promise<any> } },
|
|
569
|
-
referenceId: string,
|
|
570
|
-
fieldName: string,
|
|
571
|
-
allowedContentTypes?: string[],
|
|
572
|
-
index?: number,
|
|
573
|
-
): Promise<ValidationError[]> {
|
|
574
|
-
const errors: ValidationError[] = [];
|
|
575
|
-
const fieldLabel = index !== undefined ? `${fieldName}[${index}]` : fieldName;
|
|
576
|
-
|
|
577
|
-
try {
|
|
578
|
-
// Try to get the referenced entry
|
|
579
|
-
const entry = await ctx.db.get(referenceId as Id<"contentEntries">);
|
|
580
|
-
|
|
581
|
-
if (!entry) {
|
|
582
|
-
errors.push({
|
|
583
|
-
field: fieldLabel,
|
|
584
|
-
message: `Referenced entry not found: ${referenceId}`,
|
|
585
|
-
code: "INVALID_CONTENT_TYPE",
|
|
586
|
-
});
|
|
587
|
-
return errors;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Check if entry is deleted
|
|
591
|
-
if (isDeleted(entry)) {
|
|
592
|
-
errors.push({
|
|
593
|
-
field: fieldLabel,
|
|
594
|
-
message: `Referenced entry has been deleted: ${referenceId}`,
|
|
595
|
-
code: "INVALID_CONTENT_TYPE",
|
|
596
|
-
});
|
|
597
|
-
return errors;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Validate content type constraint
|
|
601
|
-
if (allowedContentTypes && allowedContentTypes.length > 0) {
|
|
602
|
-
const contentType = await ctx.db.get(entry.contentTypeId);
|
|
603
|
-
if (contentType) {
|
|
604
|
-
if (!allowedContentTypes.includes(contentType.name)) {
|
|
605
|
-
errors.push({
|
|
606
|
-
field: fieldLabel,
|
|
607
|
-
message: `Reference must be of type: ${allowedContentTypes.join(
|
|
608
|
-
", ",
|
|
609
|
-
)}. Got: ${contentType.name}`,
|
|
610
|
-
code: "INVALID_CONTENT_TYPE",
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
} catch {
|
|
616
|
-
// Invalid ID format
|
|
617
|
-
errors.push({
|
|
618
|
-
field: fieldLabel,
|
|
619
|
-
message: `Invalid reference ID format: ${referenceId}`,
|
|
620
|
-
code: "INVALID_TYPE",
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
return errors;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* Validates a single media asset reference.
|
|
629
|
-
*/
|
|
630
|
-
async function validateSingleMediaAsset(
|
|
631
|
-
ctx: { db: { get: (id: Id<any>) => Promise<any> } },
|
|
632
|
-
assetId: string,
|
|
633
|
-
fieldName: string,
|
|
634
|
-
allowedMimeTypes?: string[],
|
|
635
|
-
maxFileSize?: number,
|
|
636
|
-
index?: number,
|
|
637
|
-
): Promise<ValidationError[]> {
|
|
638
|
-
const errors: ValidationError[] = [];
|
|
639
|
-
const fieldLabel = index !== undefined ? `${fieldName}[${index}]` : fieldName;
|
|
640
|
-
|
|
641
|
-
try {
|
|
642
|
-
// Try to get the media item
|
|
643
|
-
const item = await ctx.db.get(assetId as Id<"mediaItems">);
|
|
644
|
-
|
|
645
|
-
if (!item) {
|
|
646
|
-
errors.push({
|
|
647
|
-
field: fieldLabel,
|
|
648
|
-
message: `Media asset not found: ${assetId}`,
|
|
649
|
-
code: "INVALID_TYPE",
|
|
650
|
-
});
|
|
651
|
-
return errors;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Check if it's an asset (not a folder)
|
|
655
|
-
if (item.kind !== "asset") {
|
|
656
|
-
errors.push({
|
|
657
|
-
field: fieldLabel,
|
|
658
|
-
message: `Media reference must be an asset, not a folder: ${assetId}`,
|
|
659
|
-
code: "INVALID_TYPE",
|
|
660
|
-
});
|
|
661
|
-
return errors;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Check if asset is deleted
|
|
665
|
-
if (isDeleted(item)) {
|
|
666
|
-
errors.push({
|
|
667
|
-
field: fieldLabel,
|
|
668
|
-
message: `Media asset has been deleted: ${assetId}`,
|
|
669
|
-
code: "INVALID_TYPE",
|
|
670
|
-
});
|
|
671
|
-
return errors;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Validate MIME type constraint
|
|
675
|
-
if (allowedMimeTypes && allowedMimeTypes.length > 0) {
|
|
676
|
-
if (!allowedMimeTypes.includes(item.mimeType)) {
|
|
677
|
-
errors.push({
|
|
678
|
-
field: fieldLabel,
|
|
679
|
-
message: `Media type not allowed. Expected: ${allowedMimeTypes.join(
|
|
680
|
-
", ",
|
|
681
|
-
)}. Got: ${item.mimeType}`,
|
|
682
|
-
code: "INVALID_MIME_TYPE",
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Validate file size constraint
|
|
688
|
-
if (maxFileSize !== undefined && item.size > maxFileSize) {
|
|
689
|
-
const maxSizeKB = Math.round(maxFileSize / 1024);
|
|
690
|
-
const actualSizeKB = Math.round(item.size / 1024);
|
|
691
|
-
errors.push({
|
|
692
|
-
field: fieldLabel,
|
|
693
|
-
message: `File too large. Maximum: ${maxSizeKB}KB. Actual: ${actualSizeKB}KB`,
|
|
694
|
-
code: "FILE_TOO_LARGE",
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
} catch {
|
|
698
|
-
// Invalid ID format
|
|
699
|
-
errors.push({
|
|
700
|
-
field: fieldLabel,
|
|
701
|
-
message: `Invalid media asset ID format: ${assetId}`,
|
|
702
|
-
code: "INVALID_TYPE",
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
return errors;
|
|
707
|
-
}
|