convex-cms 0.0.1 → 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/LICENSE +190 -0
- package/README.md +99 -0
- 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 +24 -9
- 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,900 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Media Variant Mutation Functions
|
|
3
|
-
*
|
|
4
|
-
* Provides mutation functions for creating, updating, and deleting media variants.
|
|
5
|
-
* Variants are optimized versions of media assets (thumbnails, responsive sizes,
|
|
6
|
-
* format conversions).
|
|
7
|
-
*
|
|
8
|
-
* Key operations:
|
|
9
|
-
* - Create variant: Register a completed variant after external processing
|
|
10
|
-
* - Request generation: Queue a variant for async processing
|
|
11
|
-
* - Update status: Update processing status (for job queue integration)
|
|
12
|
-
* - Delete variant: Soft or hard delete variants
|
|
13
|
-
* - Bulk operations: Generate multiple variants from presets
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { v } from "convex/values";
|
|
17
|
-
import { isDeleted } from "./lib/softDelete.js";
|
|
18
|
-
import type { Id } from "./_generated/dataModel.js";
|
|
19
|
-
import { mutation, internalMutation } from "./_generated/server.js";
|
|
20
|
-
import {
|
|
21
|
-
createMediaVariantArgs,
|
|
22
|
-
requestVariantGenerationArgs,
|
|
23
|
-
updateVariantStatusArgs,
|
|
24
|
-
deleteMediaVariantArgs,
|
|
25
|
-
deleteAssetVariantsArgs,
|
|
26
|
-
mediaVariantDoc,
|
|
27
|
-
mediaVariantWithUrlDoc,
|
|
28
|
-
// variantTypeValidator,
|
|
29
|
-
// variantStatusValidator,
|
|
30
|
-
generateVariantsResult,
|
|
31
|
-
} from "./validators.js";
|
|
32
|
-
import { classifyMimeType } from "./lib/metadataExtractor.js";
|
|
33
|
-
import { emitEvent } from "./eventEmitter.js";
|
|
34
|
-
import { DEFAULT_VARIANT_PRESETS } from "./mediaVariants.js";
|
|
35
|
-
|
|
36
|
-
// =============================================================================
|
|
37
|
-
// Create Variant (completed)
|
|
38
|
-
// =============================================================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Mutation to create a media variant that has already been processed.
|
|
42
|
-
*
|
|
43
|
-
* Use this when variant processing happens externally (e.g., in a serverless
|
|
44
|
-
* function or image processing service) and you need to register the
|
|
45
|
-
* completed variant in the CMS.
|
|
46
|
-
*
|
|
47
|
-
* @param assetId - Parent media asset ID
|
|
48
|
-
* @param storageId - Storage ID of the variant file
|
|
49
|
-
* @param variantType - Type of variant (thumbnail, responsive, format)
|
|
50
|
-
* @param width - Width in pixels
|
|
51
|
-
* @param height - Height in pixels
|
|
52
|
-
* @param format - Output format (webp, avif, jpeg, etc.)
|
|
53
|
-
* @param mimeType - MIME type of the variant
|
|
54
|
-
* @param size - File size in bytes
|
|
55
|
-
* @param quality - Quality setting used (0-100)
|
|
56
|
-
* @param preset - Named preset if applicable
|
|
57
|
-
* @param autoGenerated - Whether this was auto-generated
|
|
58
|
-
* @param createdBy - User ID who created this variant
|
|
59
|
-
* @returns The created variant document with URL
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```typescript
|
|
63
|
-
* // After processing an image externally and uploading the result
|
|
64
|
-
* const variant = await ctx.runMutation(api.mediaVariantMutations.createMediaVariant, {
|
|
65
|
-
* assetId: assetId,
|
|
66
|
-
* storageId: uploadedStorageId,
|
|
67
|
-
* variantType: "responsive",
|
|
68
|
-
* width: 480,
|
|
69
|
-
* height: 320,
|
|
70
|
-
* format: "webp",
|
|
71
|
-
* mimeType: "image/webp",
|
|
72
|
-
* size: 25600,
|
|
73
|
-
* quality: 80,
|
|
74
|
-
* preset: "small",
|
|
75
|
-
* autoGenerated: true,
|
|
76
|
-
* });
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
export const createMediaVariant = mutation({
|
|
80
|
-
args: createMediaVariantArgs.fields,
|
|
81
|
-
returns: mediaVariantWithUrlDoc,
|
|
82
|
-
handler: async (ctx, args) => {
|
|
83
|
-
const {
|
|
84
|
-
assetId,
|
|
85
|
-
storageId,
|
|
86
|
-
variantType,
|
|
87
|
-
width,
|
|
88
|
-
height,
|
|
89
|
-
format,
|
|
90
|
-
mimeType,
|
|
91
|
-
size,
|
|
92
|
-
quality,
|
|
93
|
-
preset,
|
|
94
|
-
autoGenerated = false,
|
|
95
|
-
createdBy,
|
|
96
|
-
} = args;
|
|
97
|
-
|
|
98
|
-
// Validate parent asset exists and is not deleted
|
|
99
|
-
const item = await ctx.db.get(assetId);
|
|
100
|
-
if (!item || item.kind !== "asset") {
|
|
101
|
-
throw new Error(`Media asset not found: ${assetId}`);
|
|
102
|
-
}
|
|
103
|
-
if (isDeleted(item)) {
|
|
104
|
-
throw new Error(`Cannot create variant for deleted asset: ${assetId}`);
|
|
105
|
-
}
|
|
106
|
-
const asset = item;
|
|
107
|
-
|
|
108
|
-
// Check for duplicate variant (same dimensions, format, and preset)
|
|
109
|
-
const existingVariants = await ctx.db
|
|
110
|
-
.query("mediaVariants")
|
|
111
|
-
.withIndex("by_asset", (q) => q.eq("assetId", assetId))
|
|
112
|
-
.filter((q) =>
|
|
113
|
-
q.and(
|
|
114
|
-
q.eq(q.field("variantType"), variantType),
|
|
115
|
-
q.eq(q.field("width"), width),
|
|
116
|
-
q.eq(q.field("height"), height),
|
|
117
|
-
q.eq(q.field("format"), format),
|
|
118
|
-
q.eq(q.field("deletedAt"), undefined),
|
|
119
|
-
),
|
|
120
|
-
)
|
|
121
|
-
.first();
|
|
122
|
-
|
|
123
|
-
if (existingVariants) {
|
|
124
|
-
throw new Error(
|
|
125
|
-
`Variant already exists for asset ${assetId} with type=${variantType}, ` +
|
|
126
|
-
`dimensions=${width}x${height}, format=${format}`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Create the variant record
|
|
131
|
-
const variantId = await ctx.db.insert("mediaVariants", {
|
|
132
|
-
assetId,
|
|
133
|
-
storageId,
|
|
134
|
-
variantType,
|
|
135
|
-
width,
|
|
136
|
-
height,
|
|
137
|
-
format,
|
|
138
|
-
mimeType,
|
|
139
|
-
size,
|
|
140
|
-
quality,
|
|
141
|
-
preset,
|
|
142
|
-
autoGenerated,
|
|
143
|
-
status: "completed",
|
|
144
|
-
processingCompletedAt: Date.now(),
|
|
145
|
-
createdBy,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Get the created variant
|
|
149
|
-
const variant = await ctx.db.get(variantId);
|
|
150
|
-
if (!variant) {
|
|
151
|
-
throw new Error("Failed to create variant");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Resolve URL
|
|
155
|
-
const url = await ctx.storage.getUrl(storageId);
|
|
156
|
-
|
|
157
|
-
// Emit event
|
|
158
|
-
await emitEvent(ctx, {
|
|
159
|
-
eventType: "mediaAsset.updated",
|
|
160
|
-
resourceType: "mediaAsset",
|
|
161
|
-
resourceId: assetId.toString(),
|
|
162
|
-
action: "updated",
|
|
163
|
-
payload: {
|
|
164
|
-
name: asset.name,
|
|
165
|
-
mimeType: asset.mimeType,
|
|
166
|
-
type: classifyMimeType(asset.mimeType),
|
|
167
|
-
size: asset.size ?? 0,
|
|
168
|
-
},
|
|
169
|
-
userId: createdBy,
|
|
170
|
-
metadata: {
|
|
171
|
-
variantCreated: true,
|
|
172
|
-
variantId: variantId.toString(),
|
|
173
|
-
variantType,
|
|
174
|
-
variantFormat: format,
|
|
175
|
-
variantPreset: preset,
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
...variant,
|
|
181
|
-
url,
|
|
182
|
-
};
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// =============================================================================
|
|
187
|
-
// Request Variant Generation
|
|
188
|
-
// =============================================================================
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Mutation to request async generation of a variant.
|
|
192
|
-
*
|
|
193
|
-
* Creates a variant record with "pending" status. An external processing
|
|
194
|
-
* system should pick up pending variants, process them, upload the result,
|
|
195
|
-
* and update the status to "completed".
|
|
196
|
-
*
|
|
197
|
-
* @param assetId - Parent media asset ID
|
|
198
|
-
* @param variantType - Type of variant to generate
|
|
199
|
-
* @param width - Target width (optional)
|
|
200
|
-
* @param height - Target height (optional)
|
|
201
|
-
* @param format - Output format
|
|
202
|
-
* @param quality - Quality setting (0-100)
|
|
203
|
-
* @param preset - Named preset to use
|
|
204
|
-
* @param requestedBy - User ID requesting the variant
|
|
205
|
-
* @returns The created variant document (pending status)
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* ```typescript
|
|
209
|
-
* // Request a thumbnail variant be generated
|
|
210
|
-
* const pendingVariant = await ctx.runMutation(api.mediaVariantMutations.requestVariantGeneration, {
|
|
211
|
-
* assetId: assetId,
|
|
212
|
-
* variantType: "thumbnail",
|
|
213
|
-
* width: 150,
|
|
214
|
-
* height: 150,
|
|
215
|
-
* format: "webp",
|
|
216
|
-
* quality: 80,
|
|
217
|
-
* preset: "thumbnail",
|
|
218
|
-
* });
|
|
219
|
-
*
|
|
220
|
-
* // The variant now has status: "pending"
|
|
221
|
-
* // An external processor should pick it up and generate the image
|
|
222
|
-
* ```
|
|
223
|
-
*/
|
|
224
|
-
export const requestVariantGeneration = mutation({
|
|
225
|
-
args: requestVariantGenerationArgs.fields,
|
|
226
|
-
returns: mediaVariantDoc,
|
|
227
|
-
handler: async (ctx, args) => {
|
|
228
|
-
const {
|
|
229
|
-
assetId,
|
|
230
|
-
variantType,
|
|
231
|
-
width,
|
|
232
|
-
height,
|
|
233
|
-
format,
|
|
234
|
-
quality,
|
|
235
|
-
preset,
|
|
236
|
-
requestedBy,
|
|
237
|
-
} = args;
|
|
238
|
-
|
|
239
|
-
// Validate parent asset exists and is not deleted
|
|
240
|
-
const item = await ctx.db.get(assetId);
|
|
241
|
-
if (!item || item.kind !== "asset") {
|
|
242
|
-
throw new Error(`Media asset not found: ${assetId}`);
|
|
243
|
-
}
|
|
244
|
-
if (isDeleted(item)) {
|
|
245
|
-
throw new Error(`Cannot create variant for deleted asset: ${assetId}`);
|
|
246
|
-
}
|
|
247
|
-
const asset = item;
|
|
248
|
-
|
|
249
|
-
// Validate asset is an image (variants only make sense for images)
|
|
250
|
-
const assetType = classifyMimeType(asset.mimeType);
|
|
251
|
-
if (assetType !== "image") {
|
|
252
|
-
throw new Error(
|
|
253
|
-
`Variants can only be generated for images. Asset type: ${assetType}`,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Check for existing pending/processing variant with same parameters
|
|
258
|
-
const existingPending = await ctx.db
|
|
259
|
-
.query("mediaVariants")
|
|
260
|
-
.withIndex("by_asset", (q) => q.eq("assetId", assetId))
|
|
261
|
-
.filter((q) =>
|
|
262
|
-
q.and(
|
|
263
|
-
q.eq(q.field("variantType"), variantType),
|
|
264
|
-
q.eq(q.field("width"), width),
|
|
265
|
-
q.eq(q.field("height"), height),
|
|
266
|
-
q.eq(q.field("format"), format),
|
|
267
|
-
q.or(
|
|
268
|
-
q.eq(q.field("status"), "pending"),
|
|
269
|
-
q.eq(q.field("status"), "processing"),
|
|
270
|
-
),
|
|
271
|
-
),
|
|
272
|
-
)
|
|
273
|
-
.first();
|
|
274
|
-
|
|
275
|
-
if (existingPending) {
|
|
276
|
-
// Return existing pending variant instead of creating duplicate
|
|
277
|
-
return existingPending;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Check for existing completed variant
|
|
281
|
-
const existingCompleted = await ctx.db
|
|
282
|
-
.query("mediaVariants")
|
|
283
|
-
.withIndex("by_asset", (q) => q.eq("assetId", assetId))
|
|
284
|
-
.filter((q) =>
|
|
285
|
-
q.and(
|
|
286
|
-
q.eq(q.field("variantType"), variantType),
|
|
287
|
-
q.eq(q.field("width"), width),
|
|
288
|
-
q.eq(q.field("height"), height),
|
|
289
|
-
q.eq(q.field("format"), format),
|
|
290
|
-
q.eq(q.field("status"), "completed"),
|
|
291
|
-
q.eq(q.field("deletedAt"), undefined),
|
|
292
|
-
),
|
|
293
|
-
)
|
|
294
|
-
.first();
|
|
295
|
-
|
|
296
|
-
if (existingCompleted) {
|
|
297
|
-
// Variant already exists, return it
|
|
298
|
-
return existingCompleted;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Create placeholder storage ID (will be updated when processing completes)
|
|
302
|
-
// For pending variants, we use a placeholder that will be replaced
|
|
303
|
-
const variantId = await ctx.db.insert("mediaVariants", {
|
|
304
|
-
assetId,
|
|
305
|
-
storageId: asset.storageId, // Placeholder - will be replaced
|
|
306
|
-
variantType,
|
|
307
|
-
width,
|
|
308
|
-
height,
|
|
309
|
-
format,
|
|
310
|
-
mimeType: getMimeTypeFromFormat(format),
|
|
311
|
-
size: 0, // Unknown until processed
|
|
312
|
-
quality,
|
|
313
|
-
preset,
|
|
314
|
-
autoGenerated: true,
|
|
315
|
-
status: "pending",
|
|
316
|
-
createdBy: requestedBy,
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const variant = await ctx.db.get(variantId);
|
|
320
|
-
if (!variant) {
|
|
321
|
-
throw new Error("Failed to create pending variant");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return variant;
|
|
325
|
-
},
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// =============================================================================
|
|
329
|
-
// Update Variant Status
|
|
330
|
-
// =============================================================================
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Mutation to update the status of a variant during processing.
|
|
334
|
-
*
|
|
335
|
-
* Used by the processing system to:
|
|
336
|
-
* - Mark a variant as "processing" when it starts
|
|
337
|
-
* - Mark as "completed" with final storage ID and dimensions
|
|
338
|
-
* - Mark as "failed" with error message
|
|
339
|
-
*
|
|
340
|
-
* @param id - Variant ID to update
|
|
341
|
-
* @param status - New status
|
|
342
|
-
* @param storageId - Storage ID of completed variant
|
|
343
|
-
* @param size - Final file size
|
|
344
|
-
* @param mimeType - Final MIME type
|
|
345
|
-
* @param width - Final width
|
|
346
|
-
* @param height - Final height
|
|
347
|
-
* @param errorMessage - Error message if failed
|
|
348
|
-
* @returns Updated variant document
|
|
349
|
-
*
|
|
350
|
-
* @example
|
|
351
|
-
* ```typescript
|
|
352
|
-
* // Mark as processing
|
|
353
|
-
* await ctx.runMutation(api.mediaVariantMutations.updateVariantStatus, {
|
|
354
|
-
* id: variantId,
|
|
355
|
-
* status: "processing",
|
|
356
|
-
* });
|
|
357
|
-
*
|
|
358
|
-
* // ... process the image ...
|
|
359
|
-
*
|
|
360
|
-
* // Mark as completed
|
|
361
|
-
* await ctx.runMutation(api.mediaVariantMutations.updateVariantStatus, {
|
|
362
|
-
* id: variantId,
|
|
363
|
-
* status: "completed",
|
|
364
|
-
* storageId: processedStorageId,
|
|
365
|
-
* size: 25600,
|
|
366
|
-
* width: 480,
|
|
367
|
-
* height: 320,
|
|
368
|
-
* });
|
|
369
|
-
* ```
|
|
370
|
-
*/
|
|
371
|
-
export const updateVariantStatus = mutation({
|
|
372
|
-
args: updateVariantStatusArgs.fields,
|
|
373
|
-
returns: mediaVariantDoc,
|
|
374
|
-
handler: async (ctx, args) => {
|
|
375
|
-
const {
|
|
376
|
-
id,
|
|
377
|
-
status,
|
|
378
|
-
storageId,
|
|
379
|
-
size,
|
|
380
|
-
mimeType,
|
|
381
|
-
width,
|
|
382
|
-
height,
|
|
383
|
-
errorMessage,
|
|
384
|
-
} = args;
|
|
385
|
-
|
|
386
|
-
const variant = await ctx.db.get(id);
|
|
387
|
-
if (!variant) {
|
|
388
|
-
throw new Error(`Variant not found: ${id}`);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const updates: Record<string, unknown> = { status };
|
|
392
|
-
|
|
393
|
-
if (status === "processing") {
|
|
394
|
-
updates.processingStartedAt = Date.now();
|
|
395
|
-
} else if (status === "completed") {
|
|
396
|
-
updates.processingCompletedAt = Date.now();
|
|
397
|
-
if (storageId) updates.storageId = storageId;
|
|
398
|
-
if (size !== undefined) updates.size = size;
|
|
399
|
-
if (mimeType) updates.mimeType = mimeType;
|
|
400
|
-
if (width !== undefined) updates.width = width;
|
|
401
|
-
if (height !== undefined) updates.height = height;
|
|
402
|
-
} else if (status === "failed") {
|
|
403
|
-
updates.processingCompletedAt = Date.now();
|
|
404
|
-
if (errorMessage) updates.errorMessage = errorMessage;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
await ctx.db.patch(id, updates);
|
|
408
|
-
|
|
409
|
-
const updatedVariant = await ctx.db.get(id);
|
|
410
|
-
if (!updatedVariant) {
|
|
411
|
-
throw new Error("Failed to update variant");
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return updatedVariant;
|
|
415
|
-
},
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// =============================================================================
|
|
419
|
-
// Delete Variant
|
|
420
|
-
// =============================================================================
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Mutation to delete a media variant.
|
|
424
|
-
*
|
|
425
|
-
* Supports soft delete (default) and hard delete.
|
|
426
|
-
* Hard delete also removes the storage file.
|
|
427
|
-
*
|
|
428
|
-
* @param id - Variant ID to delete
|
|
429
|
-
* @param hardDelete - If true, permanently delete and remove storage file
|
|
430
|
-
* @param deletedBy - User ID performing the deletion
|
|
431
|
-
* @returns Deleted variant document
|
|
432
|
-
*
|
|
433
|
-
* @example
|
|
434
|
-
* ```typescript
|
|
435
|
-
* // Soft delete (can be recovered)
|
|
436
|
-
* await ctx.runMutation(api.mediaVariantMutations.deleteMediaVariant, {
|
|
437
|
-
* id: variantId,
|
|
438
|
-
* });
|
|
439
|
-
*
|
|
440
|
-
* // Hard delete (permanent)
|
|
441
|
-
* await ctx.runMutation(api.mediaVariantMutations.deleteMediaVariant, {
|
|
442
|
-
* id: variantId,
|
|
443
|
-
* hardDelete: true,
|
|
444
|
-
* });
|
|
445
|
-
* ```
|
|
446
|
-
*/
|
|
447
|
-
export const deleteMediaVariant = mutation({
|
|
448
|
-
args: deleteMediaVariantArgs.fields,
|
|
449
|
-
returns: mediaVariantDoc,
|
|
450
|
-
handler: async (ctx, args) => {
|
|
451
|
-
const { id, hardDelete = false } = args;
|
|
452
|
-
|
|
453
|
-
const variant = await ctx.db.get(id);
|
|
454
|
-
if (!variant) {
|
|
455
|
-
throw new Error(`Variant not found: ${id}`);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (hardDelete) {
|
|
459
|
-
// Delete storage file if it's not a placeholder
|
|
460
|
-
if (variant.status === "completed") {
|
|
461
|
-
try {
|
|
462
|
-
await ctx.storage.delete(variant.storageId);
|
|
463
|
-
} catch {
|
|
464
|
-
// Storage file might already be deleted
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Hard delete the variant record
|
|
469
|
-
await ctx.db.delete(id);
|
|
470
|
-
} else {
|
|
471
|
-
// Soft delete
|
|
472
|
-
await ctx.db.patch(id, { deletedAt: Date.now() });
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return variant;
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// =============================================================================
|
|
480
|
-
// Delete All Variants for Asset
|
|
481
|
-
// =============================================================================
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Mutation to delete all variants for a media asset.
|
|
485
|
-
*
|
|
486
|
-
* Useful when deleting the parent asset or when regenerating
|
|
487
|
-
* all variants from scratch.
|
|
488
|
-
*
|
|
489
|
-
* @param assetId - Parent asset ID
|
|
490
|
-
* @param hardDelete - If true, permanently delete and remove storage files
|
|
491
|
-
* @param deletedBy - User ID performing the deletion
|
|
492
|
-
* @returns Summary of deleted variants
|
|
493
|
-
*
|
|
494
|
-
* @example
|
|
495
|
-
* ```typescript
|
|
496
|
-
* // Soft delete all variants
|
|
497
|
-
* const result = await ctx.runMutation(api.mediaVariantMutations.deleteAssetVariants, {
|
|
498
|
-
* assetId: assetId,
|
|
499
|
-
* });
|
|
500
|
-
* console.log(`Deleted ${result.deleted} variants`);
|
|
501
|
-
*
|
|
502
|
-
* // Hard delete all variants
|
|
503
|
-
* await ctx.runMutation(api.mediaVariantMutations.deleteAssetVariants, {
|
|
504
|
-
* assetId: assetId,
|
|
505
|
-
* hardDelete: true,
|
|
506
|
-
* });
|
|
507
|
-
* ```
|
|
508
|
-
*/
|
|
509
|
-
export const deleteAssetVariants = mutation({
|
|
510
|
-
args: deleteAssetVariantsArgs.fields,
|
|
511
|
-
returns: v.object({
|
|
512
|
-
deleted: v.number(),
|
|
513
|
-
assetId: v.id("mediaItems"),
|
|
514
|
-
}),
|
|
515
|
-
handler: async (ctx, args) => {
|
|
516
|
-
const { assetId, hardDelete = false } = args;
|
|
517
|
-
|
|
518
|
-
const variants = await ctx.db
|
|
519
|
-
.query("mediaVariants")
|
|
520
|
-
.withIndex("by_asset", (q) => q.eq("assetId", assetId))
|
|
521
|
-
.filter((q) => q.eq(q.field("deletedAt"), undefined))
|
|
522
|
-
.collect();
|
|
523
|
-
|
|
524
|
-
let deletedCount = 0;
|
|
525
|
-
|
|
526
|
-
for (const variant of variants) {
|
|
527
|
-
if (hardDelete) {
|
|
528
|
-
// Delete storage file
|
|
529
|
-
if (variant.status === "completed") {
|
|
530
|
-
try {
|
|
531
|
-
await ctx.storage.delete(variant.storageId);
|
|
532
|
-
} catch {
|
|
533
|
-
// Storage file might already be deleted
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
await ctx.db.delete(variant._id);
|
|
537
|
-
} else {
|
|
538
|
-
await ctx.db.patch(variant._id, { deletedAt: Date.now() });
|
|
539
|
-
}
|
|
540
|
-
deletedCount++;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return {
|
|
544
|
-
deleted: deletedCount,
|
|
545
|
-
assetId,
|
|
546
|
-
};
|
|
547
|
-
},
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
// =============================================================================
|
|
551
|
-
// Generate Variants from Presets
|
|
552
|
-
// =============================================================================
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Mutation to request generation of multiple variants from presets.
|
|
556
|
-
*
|
|
557
|
-
* Creates pending variant records for each preset. An external processor
|
|
558
|
-
* should pick up and process these variants.
|
|
559
|
-
*
|
|
560
|
-
* @param assetId - Parent asset ID
|
|
561
|
-
* @param presets - Array of preset names to generate
|
|
562
|
-
* @param requestedBy - User ID requesting the variants
|
|
563
|
-
* @returns Summary of created variant requests
|
|
564
|
-
*
|
|
565
|
-
* @example
|
|
566
|
-
* ```typescript
|
|
567
|
-
* // Generate thumbnail and responsive sizes
|
|
568
|
-
* const result = await ctx.runMutation(api.mediaVariantMutations.generateFromPresets, {
|
|
569
|
-
* assetId: assetId,
|
|
570
|
-
* presets: ["thumbnail", "small", "medium", "large"],
|
|
571
|
-
* });
|
|
572
|
-
*
|
|
573
|
-
* console.log(`Queued ${result.succeeded} variants for processing`);
|
|
574
|
-
* ```
|
|
575
|
-
*/
|
|
576
|
-
export const generateFromPresets = mutation({
|
|
577
|
-
args: {
|
|
578
|
-
assetId: v.id("mediaItems"),
|
|
579
|
-
presets: v.array(v.string()),
|
|
580
|
-
requestedBy: v.optional(v.string()),
|
|
581
|
-
},
|
|
582
|
-
returns: generateVariantsResult,
|
|
583
|
-
handler: async (ctx, args) => {
|
|
584
|
-
const { assetId, presets, requestedBy } = args;
|
|
585
|
-
|
|
586
|
-
// Validate asset exists and is an image
|
|
587
|
-
const item = await ctx.db.get(assetId);
|
|
588
|
-
if (!item || item.kind !== "asset") {
|
|
589
|
-
throw new Error(`Media asset not found: ${assetId}`);
|
|
590
|
-
}
|
|
591
|
-
if (isDeleted(item)) {
|
|
592
|
-
throw new Error(`Cannot generate variants for deleted asset: ${assetId}`);
|
|
593
|
-
}
|
|
594
|
-
const asset = item;
|
|
595
|
-
const assetMediaType = classifyMimeType(asset.mimeType);
|
|
596
|
-
if (assetMediaType !== "image") {
|
|
597
|
-
throw new Error(
|
|
598
|
-
`Variants can only be generated for images. Asset type: ${assetMediaType}`,
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const results: {
|
|
603
|
-
preset: string;
|
|
604
|
-
success: boolean;
|
|
605
|
-
variantId?: Id<"mediaVariants">;
|
|
606
|
-
error?: string;
|
|
607
|
-
}[] = [];
|
|
608
|
-
|
|
609
|
-
for (const presetName of presets) {
|
|
610
|
-
const preset =
|
|
611
|
-
DEFAULT_VARIANT_PRESETS[
|
|
612
|
-
presetName as keyof typeof DEFAULT_VARIANT_PRESETS
|
|
613
|
-
];
|
|
614
|
-
|
|
615
|
-
if (!preset) {
|
|
616
|
-
results.push({
|
|
617
|
-
preset: presetName,
|
|
618
|
-
success: false,
|
|
619
|
-
error: `Unknown preset: ${presetName}`,
|
|
620
|
-
});
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
try {
|
|
625
|
-
// Check for existing variant
|
|
626
|
-
const existing = await ctx.db
|
|
627
|
-
.query("mediaVariants")
|
|
628
|
-
.withIndex("by_asset_and_preset", (q) =>
|
|
629
|
-
q.eq("assetId", assetId).eq("preset", presetName),
|
|
630
|
-
)
|
|
631
|
-
.filter((q) =>
|
|
632
|
-
q.and(
|
|
633
|
-
q.eq(q.field("deletedAt"), undefined),
|
|
634
|
-
q.or(
|
|
635
|
-
q.eq(q.field("status"), "pending"),
|
|
636
|
-
q.eq(q.field("status"), "processing"),
|
|
637
|
-
q.eq(q.field("status"), "completed"),
|
|
638
|
-
),
|
|
639
|
-
),
|
|
640
|
-
)
|
|
641
|
-
.first();
|
|
642
|
-
|
|
643
|
-
if (existing) {
|
|
644
|
-
results.push({
|
|
645
|
-
preset: presetName,
|
|
646
|
-
success: true,
|
|
647
|
-
variantId: existing._id,
|
|
648
|
-
});
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Create pending variant
|
|
653
|
-
const variantId = await ctx.db.insert("mediaVariants", {
|
|
654
|
-
assetId,
|
|
655
|
-
storageId: asset.storageId, // Placeholder
|
|
656
|
-
variantType: preset.variantType,
|
|
657
|
-
width: "width" in preset ? preset.width : undefined,
|
|
658
|
-
height: "height" in preset ? preset.height : undefined,
|
|
659
|
-
format: preset.format,
|
|
660
|
-
mimeType: getMimeTypeFromFormat(preset.format),
|
|
661
|
-
size: 0,
|
|
662
|
-
quality: preset.quality,
|
|
663
|
-
preset: presetName,
|
|
664
|
-
autoGenerated: true,
|
|
665
|
-
status: "pending",
|
|
666
|
-
createdBy: requestedBy,
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
results.push({
|
|
670
|
-
preset: presetName,
|
|
671
|
-
success: true,
|
|
672
|
-
variantId,
|
|
673
|
-
});
|
|
674
|
-
} catch (error) {
|
|
675
|
-
results.push({
|
|
676
|
-
preset: presetName,
|
|
677
|
-
success: false,
|
|
678
|
-
error: error instanceof Error ? error.message : String(error),
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const succeeded = results.filter((r) => r.success).length;
|
|
684
|
-
const failed = results.filter((r) => !r.success).length;
|
|
685
|
-
|
|
686
|
-
// Emit event for bulk variant generation request
|
|
687
|
-
await emitEvent(ctx, {
|
|
688
|
-
eventType: "mediaAsset.updated",
|
|
689
|
-
resourceType: "mediaAsset",
|
|
690
|
-
resourceId: assetId.toString(),
|
|
691
|
-
action: "updated",
|
|
692
|
-
payload: {
|
|
693
|
-
name: asset.name,
|
|
694
|
-
mimeType: asset.mimeType,
|
|
695
|
-
type: classifyMimeType(asset.mimeType),
|
|
696
|
-
size: asset.size ?? 0,
|
|
697
|
-
},
|
|
698
|
-
userId: requestedBy,
|
|
699
|
-
metadata: {
|
|
700
|
-
variantsRequested: true,
|
|
701
|
-
presetsRequested: presets,
|
|
702
|
-
succeeded,
|
|
703
|
-
failed,
|
|
704
|
-
},
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
return {
|
|
708
|
-
total: presets.length,
|
|
709
|
-
succeeded,
|
|
710
|
-
failed,
|
|
711
|
-
results: results.map((r) => ({
|
|
712
|
-
...r,
|
|
713
|
-
variantId: r.variantId ? r.variantId : undefined,
|
|
714
|
-
})),
|
|
715
|
-
};
|
|
716
|
-
},
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
// =============================================================================
|
|
720
|
-
// Restore Variant (undo soft delete)
|
|
721
|
-
// =============================================================================
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Mutation to restore a soft-deleted variant.
|
|
725
|
-
*
|
|
726
|
-
* @param id - Variant ID to restore
|
|
727
|
-
* @param restoredBy - User ID performing the restoration
|
|
728
|
-
* @returns Restored variant document
|
|
729
|
-
*
|
|
730
|
-
* @example
|
|
731
|
-
* ```typescript
|
|
732
|
-
* await ctx.runMutation(api.mediaVariantMutations.restoreMediaVariant, {
|
|
733
|
-
* id: variantId,
|
|
734
|
-
* restoredBy: userId,
|
|
735
|
-
* });
|
|
736
|
-
* ```
|
|
737
|
-
*/
|
|
738
|
-
export const restoreMediaVariant = mutation({
|
|
739
|
-
args: {
|
|
740
|
-
id: v.id("mediaVariants"),
|
|
741
|
-
restoredBy: v.optional(v.string()),
|
|
742
|
-
},
|
|
743
|
-
returns: mediaVariantDoc,
|
|
744
|
-
handler: async (ctx, args) => {
|
|
745
|
-
const { id } = args;
|
|
746
|
-
|
|
747
|
-
const variant = await ctx.db.get(id);
|
|
748
|
-
if (!variant) {
|
|
749
|
-
throw new Error(`Variant not found: ${id}`);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (!isDeleted(variant)) {
|
|
753
|
-
// Already restored, return as-is
|
|
754
|
-
return variant;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Verify parent asset is not deleted
|
|
758
|
-
const asset = await ctx.db.get(variant.assetId);
|
|
759
|
-
if (!asset) {
|
|
760
|
-
throw new Error(
|
|
761
|
-
`Cannot restore variant: parent asset not found: ${variant.assetId}`,
|
|
762
|
-
);
|
|
763
|
-
}
|
|
764
|
-
if (isDeleted(asset)) {
|
|
765
|
-
throw new Error(
|
|
766
|
-
`Cannot restore variant: parent asset is deleted: ${variant.assetId}`,
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
await ctx.db.patch(id, { deletedAt: undefined });
|
|
771
|
-
|
|
772
|
-
const restoredVariant = await ctx.db.get(id);
|
|
773
|
-
if (!restoredVariant) {
|
|
774
|
-
throw new Error("Failed to restore variant");
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
return restoredVariant;
|
|
778
|
-
},
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
// =============================================================================
|
|
782
|
-
// Internal: Process Pending Variant
|
|
783
|
-
// =============================================================================
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Internal mutation for the processing system to mark a variant as processing.
|
|
787
|
-
*
|
|
788
|
-
* This is called when a processor picks up a pending variant from the queue.
|
|
789
|
-
*/
|
|
790
|
-
export const markVariantProcessing = internalMutation({
|
|
791
|
-
args: {
|
|
792
|
-
id: v.id("mediaVariants"),
|
|
793
|
-
},
|
|
794
|
-
returns: v.union(mediaVariantDoc, v.null()),
|
|
795
|
-
handler: async (ctx, args) => {
|
|
796
|
-
const { id } = args;
|
|
797
|
-
|
|
798
|
-
const variant = await ctx.db.get(id);
|
|
799
|
-
if (!variant || variant.status !== "pending") {
|
|
800
|
-
return null;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
await ctx.db.patch(id, {
|
|
804
|
-
status: "processing",
|
|
805
|
-
processingStartedAt: Date.now(),
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
return await ctx.db.get(id);
|
|
809
|
-
},
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* Internal mutation to complete a variant after processing.
|
|
814
|
-
*/
|
|
815
|
-
export const completeVariant = internalMutation({
|
|
816
|
-
args: {
|
|
817
|
-
id: v.id("mediaVariants"),
|
|
818
|
-
storageId: v.id("_storage"),
|
|
819
|
-
size: v.number(),
|
|
820
|
-
width: v.optional(v.number()),
|
|
821
|
-
height: v.optional(v.number()),
|
|
822
|
-
},
|
|
823
|
-
returns: mediaVariantDoc,
|
|
824
|
-
handler: async (ctx, args) => {
|
|
825
|
-
const { id, storageId, size, width, height } = args;
|
|
826
|
-
|
|
827
|
-
const variant = await ctx.db.get(id);
|
|
828
|
-
if (!variant) {
|
|
829
|
-
throw new Error(`Variant not found: ${id}`);
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
await ctx.db.patch(id, {
|
|
833
|
-
status: "completed",
|
|
834
|
-
storageId,
|
|
835
|
-
size,
|
|
836
|
-
width: width ?? variant.width,
|
|
837
|
-
height: height ?? variant.height,
|
|
838
|
-
processingCompletedAt: Date.now(),
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
const completed = await ctx.db.get(id);
|
|
842
|
-
if (!completed) {
|
|
843
|
-
throw new Error("Failed to complete variant");
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
return completed;
|
|
847
|
-
},
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Internal mutation to mark a variant as failed.
|
|
852
|
-
*/
|
|
853
|
-
export const failVariant = internalMutation({
|
|
854
|
-
args: {
|
|
855
|
-
id: v.id("mediaVariants"),
|
|
856
|
-
errorMessage: v.string(),
|
|
857
|
-
},
|
|
858
|
-
returns: mediaVariantDoc,
|
|
859
|
-
handler: async (ctx, args) => {
|
|
860
|
-
const { id, errorMessage } = args;
|
|
861
|
-
|
|
862
|
-
const variant = await ctx.db.get(id);
|
|
863
|
-
if (!variant) {
|
|
864
|
-
throw new Error(`Variant not found: ${id}`);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
await ctx.db.patch(id, {
|
|
868
|
-
status: "failed",
|
|
869
|
-
errorMessage,
|
|
870
|
-
processingCompletedAt: Date.now(),
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
const failed = await ctx.db.get(id);
|
|
874
|
-
if (!failed) {
|
|
875
|
-
throw new Error("Failed to update variant");
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
return failed;
|
|
879
|
-
},
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
// =============================================================================
|
|
883
|
-
// Helper Functions
|
|
884
|
-
// =============================================================================
|
|
885
|
-
|
|
886
|
-
/**
|
|
887
|
-
* Get MIME type from format string.
|
|
888
|
-
*/
|
|
889
|
-
function getMimeTypeFromFormat(format: string): string {
|
|
890
|
-
const mimeTypes: Record<string, string> = {
|
|
891
|
-
jpeg: "image/jpeg",
|
|
892
|
-
jpg: "image/jpeg",
|
|
893
|
-
png: "image/png",
|
|
894
|
-
webp: "image/webp",
|
|
895
|
-
avif: "image/avif",
|
|
896
|
-
gif: "image/gif",
|
|
897
|
-
svg: "image/svg+xml",
|
|
898
|
-
};
|
|
899
|
-
return mimeTypes[format.toLowerCase()] || `image/${format}`;
|
|
900
|
-
}
|