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.
Files changed (267) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +99 -0
  3. package/admin-dist/nitro.json +15 -0
  4. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  5. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  6. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  7. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  8. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  9. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  10. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  11. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  12. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  13. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  14. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  15. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  16. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  17. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  18. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  19. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  20. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  21. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  22. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  23. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  24. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  25. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  26. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  27. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  28. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  29. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  30. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  31. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  32. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  33. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  34. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  35. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  36. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  37. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  38. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  39. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  40. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  41. package/admin-dist/public/favicon.ico +0 -0
  42. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  45. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  46. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  83. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  84. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  88. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  89. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  90. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  91. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  92. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  93. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  94. package/admin-dist/server/_libs/clsx.mjs +16 -0
  95. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  96. package/admin-dist/server/_libs/convex.mjs +4841 -0
  97. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  98. package/admin-dist/server/_libs/croner.mjs +1 -0
  99. package/admin-dist/server/_libs/crossws.mjs +1 -0
  100. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  101. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  102. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  103. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  104. package/admin-dist/server/_libs/h3.mjs +401 -0
  105. package/admin-dist/server/_libs/hookable.mjs +1 -0
  106. package/admin-dist/server/_libs/isbot.mjs +20 -0
  107. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  108. package/admin-dist/server/_libs/ohash.mjs +1 -0
  109. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  110. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  111. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  112. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  113. package/admin-dist/server/_libs/rou3.mjs +8 -0
  114. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  115. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  116. package/admin-dist/server/_libs/srvx.mjs +719 -0
  117. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  118. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  119. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  120. package/admin-dist/server/_libs/tslib.mjs +39 -0
  121. package/admin-dist/server/_libs/ufo.mjs +54 -0
  122. package/admin-dist/server/_libs/unctx.mjs +1 -0
  123. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  124. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  125. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  126. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  127. package/admin-dist/server/_libs/zod.mjs +4223 -0
  128. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  129. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  130. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  131. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  132. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  133. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  134. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  135. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  136. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  137. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  138. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  139. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  140. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  141. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  142. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  143. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  144. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  145. package/admin-dist/server/_ssr/index.mjs +1275 -0
  146. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  147. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  148. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  149. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  150. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  151. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  152. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  153. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  154. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  155. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  156. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  157. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  158. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  159. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  160. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  161. package/admin-dist/server/favicon.ico +0 -0
  162. package/admin-dist/server/index.mjs +627 -0
  163. package/dist/cli/index.js +0 -0
  164. package/dist/client/admin-config.d.ts +0 -1
  165. package/dist/client/admin-config.d.ts.map +1 -1
  166. package/dist/client/admin-config.js +0 -1
  167. package/dist/client/admin-config.js.map +1 -1
  168. package/dist/client/adminApi.d.ts.map +1 -1
  169. package/dist/client/agentTools.d.ts +1237 -135
  170. package/dist/client/agentTools.d.ts.map +1 -1
  171. package/dist/client/agentTools.js +33 -9
  172. package/dist/client/agentTools.js.map +1 -1
  173. package/dist/client/index.d.ts +1 -1
  174. package/dist/client/index.d.ts.map +1 -1
  175. package/dist/client/index.js.map +1 -1
  176. package/dist/component/_generated/component.d.ts +9 -0
  177. package/dist/component/_generated/component.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.d.ts +35 -0
  179. package/dist/component/mediaAssets.d.ts.map +1 -1
  180. package/dist/component/mediaAssets.js +81 -0
  181. package/dist/component/mediaAssets.js.map +1 -1
  182. package/dist/test.d.ts.map +1 -1
  183. package/dist/test.js +2 -1
  184. package/dist/test.js.map +1 -1
  185. package/package.json +24 -9
  186. package/dist/component/auditLog.d.ts +0 -410
  187. package/dist/component/auditLog.d.ts.map +0 -1
  188. package/dist/component/auditLog.js +0 -607
  189. package/dist/component/auditLog.js.map +0 -1
  190. package/dist/component/types.d.ts +0 -4
  191. package/dist/component/types.d.ts.map +0 -1
  192. package/dist/component/types.js +0 -2
  193. package/dist/component/types.js.map +0 -1
  194. package/src/cli/commands/admin.ts +0 -104
  195. package/src/cli/index.ts +0 -21
  196. package/src/cli/utils/detectConvexUrl.ts +0 -54
  197. package/src/cli/utils/openBrowser.ts +0 -16
  198. package/src/client/admin-config.ts +0 -138
  199. package/src/client/adminApi.ts +0 -942
  200. package/src/client/agentTools.ts +0 -1311
  201. package/src/client/argTypes.ts +0 -316
  202. package/src/client/field-types.ts +0 -187
  203. package/src/client/index.ts +0 -1301
  204. package/src/client/queryBuilder.ts +0 -1100
  205. package/src/client/schema/codegen.ts +0 -500
  206. package/src/client/schema/defineContentType.ts +0 -501
  207. package/src/client/schema/index.ts +0 -169
  208. package/src/client/schema/schemaDrift.ts +0 -574
  209. package/src/client/schema/typedClient.ts +0 -688
  210. package/src/client/schema/types.ts +0 -666
  211. package/src/client/types.ts +0 -723
  212. package/src/client/workflows.ts +0 -141
  213. package/src/client/wrapper.ts +0 -4304
  214. package/src/component/_generated/api.ts +0 -140
  215. package/src/component/_generated/component.ts +0 -5029
  216. package/src/component/_generated/dataModel.ts +0 -60
  217. package/src/component/_generated/server.ts +0 -156
  218. package/src/component/authorization.ts +0 -647
  219. package/src/component/authorizationHooks.ts +0 -668
  220. package/src/component/bulkOperations.ts +0 -687
  221. package/src/component/contentEntries.ts +0 -1976
  222. package/src/component/contentEntryMutations.ts +0 -1223
  223. package/src/component/contentEntryValidation.ts +0 -707
  224. package/src/component/contentLock.ts +0 -550
  225. package/src/component/contentTypeMigration.ts +0 -1064
  226. package/src/component/contentTypeMutations.ts +0 -969
  227. package/src/component/contentTypes.ts +0 -346
  228. package/src/component/convex.config.ts +0 -44
  229. package/src/component/documentTypes.ts +0 -240
  230. package/src/component/eventEmitter.ts +0 -485
  231. package/src/component/exportImport.ts +0 -1169
  232. package/src/component/index.ts +0 -491
  233. package/src/component/lib/deepReferenceResolver.ts +0 -999
  234. package/src/component/lib/errors.ts +0 -816
  235. package/src/component/lib/index.ts +0 -145
  236. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  237. package/src/component/lib/metadataExtractor.ts +0 -792
  238. package/src/component/lib/mutationAuth.ts +0 -199
  239. package/src/component/lib/queries.ts +0 -79
  240. package/src/component/lib/ragContentChunker.ts +0 -1371
  241. package/src/component/lib/referenceResolver.ts +0 -430
  242. package/src/component/lib/slugGenerator.ts +0 -262
  243. package/src/component/lib/slugUniqueness.ts +0 -333
  244. package/src/component/lib/softDelete.ts +0 -44
  245. package/src/component/localeFallbackChain.ts +0 -673
  246. package/src/component/localeFields.ts +0 -896
  247. package/src/component/mediaAssetMutations.ts +0 -725
  248. package/src/component/mediaAssets.ts +0 -932
  249. package/src/component/mediaFolderMutations.ts +0 -1046
  250. package/src/component/mediaUploadMutations.ts +0 -224
  251. package/src/component/mediaVariantMutations.ts +0 -900
  252. package/src/component/mediaVariants.ts +0 -793
  253. package/src/component/ragContentIndexer.ts +0 -1067
  254. package/src/component/rateLimitHooks.ts +0 -572
  255. package/src/component/roles.ts +0 -1360
  256. package/src/component/scheduledPublish.ts +0 -358
  257. package/src/component/schema.ts +0 -617
  258. package/src/component/taxonomies.ts +0 -949
  259. package/src/component/taxonomyMutations.ts +0 -1210
  260. package/src/component/trash.ts +0 -724
  261. package/src/component/userContext.ts +0 -898
  262. package/src/component/validation.ts +0 -1388
  263. package/src/component/validators.ts +0 -949
  264. package/src/component/versionMutations.ts +0 -392
  265. package/src/component/webhookTrigger.ts +0 -1922
  266. package/src/react/index.ts +0 -898
  267. package/src/test.ts +0 -1580
@@ -1,725 +0,0 @@
1
- /**
2
- * Media Asset Mutation Functions
3
- *
4
- * Provides mutation functions for creating, updating, and deleting media assets.
5
- * Media assets are records that link file storage references (storageId) with
6
- * metadata like filename, MIME type, dimensions, and organizational tags.
7
- *
8
- * Upload Flow:
9
- * 1. Client calls generateUploadUrl to get a temporary upload URL
10
- * 2. Client POSTs the file directly to the upload URL
11
- * 3. Client receives a storageId from the upload response
12
- * 4. Client calls createMediaAsset to save the metadata with the storageId
13
- */
14
-
15
- import { mutation, query, type MutationCtx } from "./_generated/server.js";
16
- import type { Id } from "./_generated/dataModel.js";
17
- import { v } from "convex/values";
18
- import {
19
- createMediaAssetArgs,
20
- updateMediaAssetArgs,
21
- mediaItemDoc,
22
- mediaAssetItemValidator,
23
- deleteMediaAssetArgs,
24
- restoreMediaAssetArgs,
25
- mediaAssetReference,
26
- moveMediaAssetsArgs,
27
- moveMediaAssetsResult,
28
- BULK_OPERATION_BATCH_SIZE,
29
- mutationAuthContext,
30
- } from "./validators.js";
31
- import {
32
- emitEvent,
33
- mediaAssetEventType,
34
- MediaAssetEventPayload,
35
- } from "./eventEmitter.js";
36
- import {
37
- mediaFolderNotFound,
38
- mediaFolderDeleted,
39
- mediaAssetNotFound,
40
- mediaAssetDeleted,
41
- mediaAssetNotDeleted,
42
- mediaAssetHasReferences,
43
- mediaAssetCreateFailed,
44
- mediaAssetUpdateFailed,
45
- batchSizeExceeded,
46
- internalError,
47
- } from "./lib/errors.js";
48
- import { requireMutationAuth, withResourceOwner } from "./lib/mutationAuth.js";
49
- import { classifyMimeType } from "./lib/metadataExtractor.js";
50
- import { isDeleted } from "./lib/softDelete.js";
51
-
52
- // =============================================================================
53
- // Delete Media Asset Result Type
54
- // =============================================================================
55
-
56
- const deleteMediaAssetResult = v.object({
57
- ...mediaAssetItemValidator.fields,
58
- _id: v.id("mediaItems"),
59
- _creationTime: v.number(),
60
- deletedAt: v.optional(v.number()),
61
- storageFileDeleted: v.optional(v.boolean()),
62
- });
63
-
64
- // =============================================================================
65
- // Create Media Asset Mutation
66
- // =============================================================================
67
-
68
- export const createMediaAsset = mutation({
69
- args: {
70
- ...createMediaAssetArgs.fields,
71
- _auth: v.optional(mutationAuthContext),
72
- },
73
- returns: mediaItemDoc,
74
- handler: async (ctx, args) => {
75
- const {
76
- storageId,
77
- name,
78
- mimeType,
79
- size,
80
- title,
81
- description,
82
- altText,
83
- parentId,
84
- width,
85
- height,
86
- duration,
87
- metadata,
88
- tags,
89
- createdBy,
90
- _auth,
91
- } = args;
92
-
93
- requireMutationAuth(_auth, "mediaItems", "create");
94
-
95
- // Validate folder exists if provided
96
- if (parentId !== undefined) {
97
- const folder = await ctx.db.get(parentId);
98
- if (!folder) {
99
- throw mediaFolderNotFound((parentId as unknown) as string);
100
- }
101
- if (isDeleted(folder)) {
102
- throw mediaFolderDeleted((parentId as unknown) as string);
103
- }
104
- }
105
-
106
- // Generate searchable text
107
- const searchParts: string[] = [];
108
- searchParts.push(name);
109
- if (title) searchParts.push(title);
110
- if (description) searchParts.push(description);
111
- if (tags && tags.length > 0) searchParts.push(...tags);
112
- const searchText = searchParts.join(" ").trim() || undefined;
113
-
114
- // Compute the path for the asset
115
- let path = "/";
116
- if (parentId) {
117
- const folder = await ctx.db.get(parentId);
118
- if (folder && folder.kind === "folder") {
119
- path = folder.path + name;
120
- } else {
121
- path = "/" + name;
122
- }
123
- } else {
124
- path = "/" + name;
125
- }
126
-
127
- // Create the media asset record
128
- const assetId = await ctx.db.insert("mediaItems", {
129
- kind: "asset",
130
- storageId,
131
- name,
132
- mimeType,
133
- size,
134
- title,
135
- description,
136
- altText,
137
- parentId,
138
- path,
139
- width,
140
- height,
141
- duration,
142
- metadata,
143
- tags,
144
- createdBy,
145
- searchText,
146
- });
147
-
148
- const asset = await ctx.db.get(assetId);
149
- if (!asset) {
150
- throw mediaAssetCreateFailed();
151
- }
152
-
153
- // Get folder path for event
154
- let folderPath: string | undefined;
155
- if (parentId) {
156
- const folder = await ctx.db.get(parentId);
157
- if (folder && folder.kind === "folder") {
158
- folderPath = folder.path;
159
- }
160
- }
161
-
162
- await emitEvent(ctx, {
163
- eventType: mediaAssetEventType("created"),
164
- resourceType: "mediaAsset",
165
- resourceId: (assetId as unknown) as string,
166
- action: "created",
167
- payload: {
168
- name,
169
- mimeType,
170
- type: classifyMimeType(mimeType),
171
- size: size ?? 0,
172
- parentId: (parentId as unknown) as string | undefined,
173
- path: folderPath,
174
- } as MediaAssetEventPayload,
175
- userId: createdBy,
176
- });
177
-
178
- return asset;
179
- },
180
- });
181
-
182
- // =============================================================================
183
- // Update Media Asset Mutation
184
- // =============================================================================
185
-
186
- export const updateMediaAsset = mutation({
187
- args: {
188
- ...updateMediaAssetArgs.fields,
189
- _auth: v.optional(mutationAuthContext),
190
- },
191
- returns: mediaItemDoc,
192
- handler: async (ctx, args) => {
193
- const {
194
- id,
195
- name,
196
- title,
197
- description,
198
- altText,
199
- parentId,
200
- tags,
201
- _auth,
202
- } = args;
203
-
204
- const item = await ctx.db.get(id);
205
- if (!item || item.kind !== "asset") {
206
- throw mediaAssetNotFound((id as unknown) as string);
207
- }
208
- const asset = item;
209
-
210
- if (isDeleted(asset)) {
211
- throw mediaAssetDeleted((id as unknown) as string);
212
- }
213
-
214
- requireMutationAuth(
215
- withResourceOwner(_auth, asset.createdBy),
216
- "mediaItems",
217
- "update",
218
- );
219
-
220
- // Validate folder if provided
221
- if (parentId !== undefined && parentId !== asset.parentId) {
222
- const folder = await ctx.db.get(parentId);
223
- if (!folder || folder.kind !== "folder") {
224
- throw mediaFolderNotFound((parentId as unknown) as string);
225
- }
226
- if (isDeleted(folder)) {
227
- throw mediaFolderDeleted((parentId as unknown) as string);
228
- }
229
- }
230
-
231
- const updates: Record<string, unknown> = {};
232
- if (name !== undefined) updates.name = name;
233
- if (title !== undefined) updates.title = title;
234
- if (description !== undefined) updates.description = description;
235
- if (altText !== undefined) updates.altText = altText;
236
- if (parentId !== undefined) updates.parentId = parentId;
237
- if (tags !== undefined) updates.tags = tags;
238
-
239
- // Regenerate search text if needed
240
- if (name !== undefined || title !== undefined || description !== undefined || tags !== undefined) {
241
- const searchParts: string[] = [];
242
- const effectiveName = name ?? asset.name;
243
- const effectiveTitle = title ?? asset.title;
244
- const effectiveDescription = description ?? asset.description;
245
- const effectiveTags = tags ?? asset.tags;
246
-
247
- searchParts.push(effectiveName);
248
- if (effectiveTitle) searchParts.push(effectiveTitle);
249
- if (effectiveDescription) searchParts.push(effectiveDescription);
250
- if (effectiveTags && effectiveTags.length > 0) searchParts.push(...effectiveTags);
251
-
252
- updates.searchText = searchParts.join(" ").trim() || undefined;
253
- }
254
-
255
- await ctx.db.patch(id, updates);
256
-
257
- const updatedItem = await ctx.db.get(id);
258
- if (!updatedItem || updatedItem.kind !== "asset") {
259
- throw mediaAssetUpdateFailed((id as unknown) as string);
260
- }
261
-
262
- // Emit event
263
- let folderPath: string | undefined;
264
- if (updatedItem.parentId) {
265
- const folder = await ctx.db.get(updatedItem.parentId);
266
- if (folder && folder.kind === "folder") {
267
- folderPath = folder.path;
268
- }
269
- }
270
-
271
- await emitEvent(ctx, {
272
- eventType: mediaAssetEventType("updated"),
273
- resourceType: "mediaAsset",
274
- resourceId: (id as unknown) as string,
275
- action: "updated",
276
- payload: {
277
- name: updatedItem.name,
278
- mimeType: updatedItem.mimeType,
279
- type: classifyMimeType(updatedItem.mimeType),
280
- size: updatedItem.size ?? 0,
281
- parentId: (updatedItem.parentId as unknown) as string | undefined,
282
- path: folderPath,
283
- } as MediaAssetEventPayload,
284
- userId: asset.createdBy,
285
- });
286
-
287
- return updatedItem;
288
- },
289
- });
290
-
291
- // =============================================================================
292
- // Find Media Asset References
293
- // =============================================================================
294
-
295
- export const findMediaAssetReferences = query({
296
- args: {
297
- mediaAssetId: v.id("mediaItems"),
298
- limit: v.optional(v.number()),
299
- },
300
- returns: v.array(mediaAssetReference),
301
- handler: async (ctx, args) => {
302
- const { mediaAssetId, limit = 100 } = args;
303
- const mediaIdStr = (mediaAssetId as unknown) as string;
304
- const references: Array<{
305
- entryId: Id<"contentEntries">;
306
- slug: string;
307
- contentTypeName: string;
308
- fields: string[];
309
- }> = [];
310
-
311
- const contentTypes = await ctx.db.query("contentTypes").collect();
312
- const contentTypeMap = new Map(
313
- contentTypes.map((ct) => [ct._id.toString(), ct]),
314
- );
315
-
316
- const entries = await ctx.db
317
- .query("contentEntries")
318
- .filter((q) => q.eq(q.field("deletedAt"), undefined))
319
- .take(10000);
320
-
321
- for (const entry of entries) {
322
- if (references.length >= limit) break;
323
-
324
- const contentType = contentTypeMap.get(entry.contentTypeId.toString());
325
- if (!contentType) continue;
326
-
327
- const mediaFields = contentType.fields.filter((f) => f.type === "media");
328
- const matchingFields: string[] = [];
329
-
330
- for (const field of mediaFields) {
331
- const fieldValue = (entry.data as Record<string, unknown>)?.[field.name];
332
- if (!fieldValue) continue;
333
-
334
- const isMultiple = (field.options as { multiple?: boolean } | undefined)?.multiple ?? false;
335
- if (isMultiple && Array.isArray(fieldValue)) {
336
- if (fieldValue.includes(mediaIdStr)) {
337
- matchingFields.push(field.name);
338
- }
339
- } else if (fieldValue === mediaIdStr) {
340
- matchingFields.push(field.name);
341
- }
342
- }
343
-
344
- if (matchingFields.length > 0) {
345
- references.push({
346
- entryId: entry._id,
347
- slug: entry.slug,
348
- contentTypeName: contentType.name,
349
- fields: matchingFields,
350
- });
351
- }
352
- }
353
-
354
- return references;
355
- },
356
- });
357
-
358
- // =============================================================================
359
- // Internal helper for reference checking
360
- // =============================================================================
361
-
362
- async function findReferencesInternal(
363
- ctx: MutationCtx,
364
- mediaAssetId: Id<"mediaItems">,
365
- ): Promise<Array<{
366
- entryId: Id<"contentEntries">;
367
- slug: string;
368
- contentTypeName: string;
369
- fields: string[];
370
- }>> {
371
- const mediaIdStr = mediaAssetId.toString();
372
- const references: Array<{
373
- entryId: Id<"contentEntries">;
374
- slug: string;
375
- contentTypeName: string;
376
- fields: string[];
377
- }> = [];
378
-
379
- const contentTypes = await ctx.db.query("contentTypes").collect();
380
- const contentTypeMap = new Map(
381
- contentTypes.map((ct) => [ct._id.toString(), ct]),
382
- );
383
-
384
- const entries = await ctx.db
385
- .query("contentEntries")
386
- .filter((q) => q.eq(q.field("deletedAt"), undefined))
387
- .take(10000);
388
-
389
- for (const entry of entries) {
390
- if (references.length >= 100) break;
391
-
392
- const contentType = contentTypeMap.get(entry.contentTypeId.toString());
393
- if (!contentType) continue;
394
-
395
- const mediaFields = contentType.fields.filter((f: { type: string }) => f.type === "media");
396
- const matchingFields: string[] = [];
397
-
398
- for (const field of mediaFields) {
399
- const fieldValue = (entry.data as Record<string, unknown>)?.[field.name];
400
- if (!fieldValue) continue;
401
-
402
- const isMultiple = (field as { options?: { multiple?: boolean } }).options?.multiple ?? false;
403
- if (isMultiple && Array.isArray(fieldValue)) {
404
- if (fieldValue.includes(mediaIdStr)) {
405
- matchingFields.push(field.name);
406
- }
407
- } else if (fieldValue === mediaIdStr) {
408
- matchingFields.push(field.name);
409
- }
410
- }
411
-
412
- if (matchingFields.length > 0) {
413
- references.push({
414
- entryId: entry._id,
415
- slug: entry.slug,
416
- contentTypeName: contentType.name,
417
- fields: matchingFields,
418
- });
419
- }
420
- }
421
-
422
- return references;
423
- }
424
-
425
- // =============================================================================
426
- // Delete Media Asset Mutation
427
- // =============================================================================
428
-
429
- export const deleteMediaAsset = mutation({
430
- args: {
431
- ...deleteMediaAssetArgs.fields,
432
- _auth: v.optional(mutationAuthContext),
433
- },
434
- returns: deleteMediaAssetResult,
435
- handler: async (ctx, args) => {
436
- const {
437
- id,
438
- deletedBy,
439
- hardDelete = false,
440
- forceDelete = false,
441
- _auth,
442
- } = args;
443
-
444
- const item = await ctx.db.get(id);
445
- if (!item || item.kind !== "asset") {
446
- throw mediaAssetNotFound((id as unknown) as string);
447
- }
448
- const asset = item;
449
-
450
- requireMutationAuth(
451
- withResourceOwner(_auth, asset.createdBy),
452
- "mediaItems",
453
- "delete",
454
- );
455
-
456
- if (!hardDelete && isDeleted(asset)) {
457
- throw mediaAssetDeleted((id as unknown) as string);
458
- }
459
-
460
- // Check for references
461
- if (!forceDelete) {
462
- const references = await findReferencesInternal(ctx, id);
463
- if (references.length > 0) {
464
- throw mediaAssetHasReferences(
465
- (id as unknown) as string,
466
- references.map((r) => ({
467
- type: "contentEntry",
468
- id: (r.entryId as unknown) as string,
469
- name: `${r.contentTypeName}/${r.slug}`,
470
- })),
471
- );
472
- }
473
- }
474
-
475
- // Get folder path for event
476
- let folderPath: string | undefined;
477
- if (asset.parentId) {
478
- const folder = await ctx.db.get(asset.parentId);
479
- if (folder && folder.kind === "folder") {
480
- folderPath = folder.path;
481
- }
482
- }
483
-
484
- if (hardDelete) {
485
- let storageFileDeleted = false;
486
- try {
487
- await ctx.storage.delete(asset.storageId);
488
- storageFileDeleted = true;
489
- } catch (error) {
490
- console.warn(
491
- `Could not delete storage file for asset ${id}:`,
492
- error instanceof Error ? error.message : error,
493
- );
494
- }
495
-
496
- await ctx.db.delete(id);
497
-
498
- await emitEvent(ctx, {
499
- eventType: mediaAssetEventType("deleted"),
500
- resourceType: "mediaAsset",
501
- resourceId: (id as unknown) as string,
502
- action: "deleted",
503
- payload: {
504
- name: asset.name,
505
- mimeType: asset.mimeType,
506
- type: classifyMimeType(asset.mimeType),
507
- size: asset.size ?? 0,
508
- parentId: (asset.parentId as unknown) as string | undefined,
509
- path: folderPath,
510
- } as MediaAssetEventPayload,
511
- userId: deletedBy,
512
- metadata: { hardDelete: true, storageFileDeleted },
513
- });
514
-
515
- return {
516
- ...asset,
517
- deletedAt: Date.now(),
518
- storageFileDeleted,
519
- };
520
- } else {
521
- const now = Date.now();
522
- await ctx.db.patch(id, { deletedAt: now });
523
-
524
- await emitEvent(ctx, {
525
- eventType: mediaAssetEventType("deleted"),
526
- resourceType: "mediaAsset",
527
- resourceId: (id as unknown) as string,
528
- action: "deleted",
529
- payload: {
530
- name: asset.name,
531
- mimeType: asset.mimeType,
532
- type: classifyMimeType(asset.mimeType),
533
- size: asset.size ?? 0,
534
- parentId: (asset.parentId as unknown) as string | undefined,
535
- path: folderPath,
536
- } as MediaAssetEventPayload,
537
- userId: deletedBy,
538
- metadata: { hardDelete: false },
539
- });
540
-
541
- return {
542
- ...asset,
543
- deletedAt: now,
544
- storageFileDeleted: undefined,
545
- };
546
- }
547
- },
548
- });
549
-
550
- // =============================================================================
551
- // Restore Media Asset Mutation
552
- // =============================================================================
553
-
554
- export const restoreMediaAsset = mutation({
555
- args: {
556
- ...restoreMediaAssetArgs.fields,
557
- _auth: v.optional(mutationAuthContext),
558
- },
559
- returns: mediaItemDoc,
560
- handler: async (ctx, args) => {
561
- const { id, restoredBy, _auth } = args;
562
-
563
- const item = await ctx.db.get(id);
564
- if (!item || item.kind !== "asset") {
565
- throw mediaAssetNotFound((id as unknown) as string);
566
- }
567
- const asset = item;
568
-
569
- requireMutationAuth(
570
- withResourceOwner(_auth, asset.createdBy),
571
- "mediaItems",
572
- "update",
573
- );
574
-
575
- if (asset.deletedAt === undefined) {
576
- throw mediaAssetNotDeleted((id as unknown) as string);
577
- }
578
-
579
- await ctx.db.patch(id, { deletedAt: undefined });
580
-
581
- const restoredItem = await ctx.db.get(id);
582
- if (!restoredItem || restoredItem.kind !== "asset") {
583
- throw internalError("Failed to restore media asset");
584
- }
585
-
586
- let folderPath: string | undefined;
587
- if (restoredItem.parentId) {
588
- const folder = await ctx.db.get(restoredItem.parentId);
589
- if (folder && folder.kind === "folder") {
590
- folderPath = folder.path;
591
- }
592
- }
593
-
594
- await emitEvent(ctx, {
595
- eventType: mediaAssetEventType("restored"),
596
- resourceType: "mediaAsset",
597
- resourceId: (id as unknown) as string,
598
- action: "restored",
599
- payload: {
600
- name: restoredItem.name,
601
- mimeType: restoredItem.mimeType,
602
- type: classifyMimeType(restoredItem.mimeType),
603
- size: restoredItem.size ?? 0,
604
- parentId: (restoredItem.parentId as unknown) as string | undefined,
605
- path: folderPath,
606
- } as MediaAssetEventPayload,
607
- userId: restoredBy,
608
- });
609
-
610
- return restoredItem;
611
- },
612
- });
613
-
614
- // =============================================================================
615
- // Move Media Assets Mutation
616
- // =============================================================================
617
-
618
- export const moveMediaAssets = mutation({
619
- args: {
620
- ...moveMediaAssetsArgs.fields,
621
- _auth: v.optional(mutationAuthContext),
622
- },
623
- returns: moveMediaAssetsResult,
624
- handler: async (ctx, args) => {
625
- const { assetIds, targetFolderId, movedBy, _auth } = args;
626
-
627
- requireMutationAuth(_auth, "mediaItems", "update");
628
-
629
- if (assetIds.length > BULK_OPERATION_BATCH_SIZE) {
630
- throw batchSizeExceeded(BULK_OPERATION_BATCH_SIZE, assetIds.length);
631
- }
632
-
633
- if (assetIds.length === 0) {
634
- return {
635
- total: 0,
636
- succeeded: 0,
637
- failed: 0,
638
- targetFolderId,
639
- targetFolderPath: undefined,
640
- results: [],
641
- };
642
- }
643
-
644
- let targetFolderPath: string | undefined;
645
- if (targetFolderId !== undefined) {
646
- const targetFolder = await ctx.db.get(targetFolderId);
647
- if (!targetFolder) {
648
- throw mediaFolderNotFound((targetFolderId as unknown) as string);
649
- }
650
- if (isDeleted(targetFolder)) {
651
- throw mediaFolderDeleted((targetFolderId as unknown) as string);
652
- }
653
- targetFolderPath = targetFolder.path;
654
- }
655
-
656
- const results: Array<{
657
- id: Id<"mediaItems">;
658
- success: boolean;
659
- error?: string;
660
- previousFolderId?: Id<"mediaItems">;
661
- }> = [];
662
-
663
- for (const assetId of assetIds) {
664
- try {
665
- const item = await ctx.db.get(assetId);
666
- if (!item || item.kind !== "asset") {
667
- results.push({ id: assetId, success: false, error: "Asset not found" });
668
- continue;
669
- }
670
-
671
- if (isDeleted(item)) {
672
- results.push({ id: assetId, success: false, error: "Asset has been deleted" });
673
- continue;
674
- }
675
-
676
- if (item.parentId === targetFolderId) {
677
- results.push({ id: assetId, success: true, previousFolderId: item.parentId });
678
- continue;
679
- }
680
-
681
- const previousFolderId = item.parentId;
682
- await ctx.db.patch(assetId, { parentId: targetFolderId });
683
-
684
- await emitEvent(ctx, {
685
- eventType: mediaAssetEventType("updated"),
686
- resourceType: "mediaAsset",
687
- resourceId: (assetId as unknown) as string,
688
- action: "updated",
689
- payload: {
690
- name: item.name,
691
- mimeType: item.mimeType,
692
- type: classifyMimeType(item.mimeType),
693
- size: item.size ?? 0,
694
- parentId: (targetFolderId as unknown) as string | undefined,
695
- path: targetFolderPath,
696
- } as MediaAssetEventPayload,
697
- userId: movedBy,
698
- metadata: {
699
- moveOperation: true,
700
- previousFolderId: (previousFolderId as unknown) as string | undefined,
701
- },
702
- });
703
-
704
- results.push({ id: assetId, success: true, previousFolderId });
705
- } catch (error) {
706
- results.push({
707
- id: assetId,
708
- success: false,
709
- error: error instanceof Error ? error.message : "Unknown error",
710
- });
711
- }
712
- }
713
-
714
- const succeeded = results.filter((r) => r.success).length;
715
-
716
- return {
717
- total: assetIds.length,
718
- succeeded,
719
- failed: assetIds.length - succeeded,
720
- targetFolderId,
721
- targetFolderPath,
722
- results,
723
- };
724
- },
725
- });