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.
Files changed (265) hide show
  1. package/admin-dist/nitro.json +15 -0
  2. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  3. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  4. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  5. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  6. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  7. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  8. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  9. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  10. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  11. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  12. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  13. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  14. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  15. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  16. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  17. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  18. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  19. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  20. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  21. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  22. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  23. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  24. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  25. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  26. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  27. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  28. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  29. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  30. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  31. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  32. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  33. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  34. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  35. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  36. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  37. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  38. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  39. package/admin-dist/public/favicon.ico +0 -0
  40. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  41. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  42. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  45. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  46. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  83. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  84. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  88. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  89. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  90. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  91. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  92. package/admin-dist/server/_libs/clsx.mjs +16 -0
  93. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  94. package/admin-dist/server/_libs/convex.mjs +4841 -0
  95. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  96. package/admin-dist/server/_libs/croner.mjs +1 -0
  97. package/admin-dist/server/_libs/crossws.mjs +1 -0
  98. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  99. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  100. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  101. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  102. package/admin-dist/server/_libs/h3.mjs +401 -0
  103. package/admin-dist/server/_libs/hookable.mjs +1 -0
  104. package/admin-dist/server/_libs/isbot.mjs +20 -0
  105. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  106. package/admin-dist/server/_libs/ohash.mjs +1 -0
  107. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  108. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  109. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  110. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  111. package/admin-dist/server/_libs/rou3.mjs +8 -0
  112. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  113. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  114. package/admin-dist/server/_libs/srvx.mjs +719 -0
  115. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  116. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  117. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  118. package/admin-dist/server/_libs/tslib.mjs +39 -0
  119. package/admin-dist/server/_libs/ufo.mjs +54 -0
  120. package/admin-dist/server/_libs/unctx.mjs +1 -0
  121. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  122. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  123. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  124. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  125. package/admin-dist/server/_libs/zod.mjs +4223 -0
  126. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  127. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  128. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  129. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  130. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  131. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  132. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  133. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  134. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  135. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  136. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  137. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  138. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  139. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  140. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  141. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  142. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  143. package/admin-dist/server/_ssr/index.mjs +1275 -0
  144. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  145. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  146. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  147. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  148. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  149. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  150. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  151. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  152. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  153. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  154. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  155. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  156. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  157. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  158. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  159. package/admin-dist/server/favicon.ico +0 -0
  160. package/admin-dist/server/index.mjs +627 -0
  161. package/dist/cli/index.js +0 -0
  162. package/dist/client/admin-config.d.ts +0 -1
  163. package/dist/client/admin-config.d.ts.map +1 -1
  164. package/dist/client/admin-config.js +0 -1
  165. package/dist/client/admin-config.js.map +1 -1
  166. package/dist/client/adminApi.d.ts.map +1 -1
  167. package/dist/client/agentTools.d.ts +1237 -135
  168. package/dist/client/agentTools.d.ts.map +1 -1
  169. package/dist/client/agentTools.js +33 -9
  170. package/dist/client/agentTools.js.map +1 -1
  171. package/dist/client/index.d.ts +1 -1
  172. package/dist/client/index.d.ts.map +1 -1
  173. package/dist/client/index.js.map +1 -1
  174. package/dist/component/_generated/component.d.ts +9 -0
  175. package/dist/component/_generated/component.d.ts.map +1 -1
  176. package/dist/component/mediaAssets.d.ts +35 -0
  177. package/dist/component/mediaAssets.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.js +81 -0
  179. package/dist/component/mediaAssets.js.map +1 -1
  180. package/dist/test.d.ts.map +1 -1
  181. package/dist/test.js +2 -1
  182. package/dist/test.js.map +1 -1
  183. package/package.json +9 -5
  184. package/dist/component/auditLog.d.ts +0 -410
  185. package/dist/component/auditLog.d.ts.map +0 -1
  186. package/dist/component/auditLog.js +0 -607
  187. package/dist/component/auditLog.js.map +0 -1
  188. package/dist/component/types.d.ts +0 -4
  189. package/dist/component/types.d.ts.map +0 -1
  190. package/dist/component/types.js +0 -2
  191. package/dist/component/types.js.map +0 -1
  192. package/src/cli/commands/admin.ts +0 -104
  193. package/src/cli/index.ts +0 -21
  194. package/src/cli/utils/detectConvexUrl.ts +0 -54
  195. package/src/cli/utils/openBrowser.ts +0 -16
  196. package/src/client/admin-config.ts +0 -138
  197. package/src/client/adminApi.ts +0 -942
  198. package/src/client/agentTools.ts +0 -1311
  199. package/src/client/argTypes.ts +0 -316
  200. package/src/client/field-types.ts +0 -187
  201. package/src/client/index.ts +0 -1301
  202. package/src/client/queryBuilder.ts +0 -1100
  203. package/src/client/schema/codegen.ts +0 -500
  204. package/src/client/schema/defineContentType.ts +0 -501
  205. package/src/client/schema/index.ts +0 -169
  206. package/src/client/schema/schemaDrift.ts +0 -574
  207. package/src/client/schema/typedClient.ts +0 -688
  208. package/src/client/schema/types.ts +0 -666
  209. package/src/client/types.ts +0 -723
  210. package/src/client/workflows.ts +0 -141
  211. package/src/client/wrapper.ts +0 -4304
  212. package/src/component/_generated/api.ts +0 -140
  213. package/src/component/_generated/component.ts +0 -5029
  214. package/src/component/_generated/dataModel.ts +0 -60
  215. package/src/component/_generated/server.ts +0 -156
  216. package/src/component/authorization.ts +0 -647
  217. package/src/component/authorizationHooks.ts +0 -668
  218. package/src/component/bulkOperations.ts +0 -687
  219. package/src/component/contentEntries.ts +0 -1976
  220. package/src/component/contentEntryMutations.ts +0 -1223
  221. package/src/component/contentEntryValidation.ts +0 -707
  222. package/src/component/contentLock.ts +0 -550
  223. package/src/component/contentTypeMigration.ts +0 -1064
  224. package/src/component/contentTypeMutations.ts +0 -969
  225. package/src/component/contentTypes.ts +0 -346
  226. package/src/component/convex.config.ts +0 -44
  227. package/src/component/documentTypes.ts +0 -240
  228. package/src/component/eventEmitter.ts +0 -485
  229. package/src/component/exportImport.ts +0 -1169
  230. package/src/component/index.ts +0 -491
  231. package/src/component/lib/deepReferenceResolver.ts +0 -999
  232. package/src/component/lib/errors.ts +0 -816
  233. package/src/component/lib/index.ts +0 -145
  234. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  235. package/src/component/lib/metadataExtractor.ts +0 -792
  236. package/src/component/lib/mutationAuth.ts +0 -199
  237. package/src/component/lib/queries.ts +0 -79
  238. package/src/component/lib/ragContentChunker.ts +0 -1371
  239. package/src/component/lib/referenceResolver.ts +0 -430
  240. package/src/component/lib/slugGenerator.ts +0 -262
  241. package/src/component/lib/slugUniqueness.ts +0 -333
  242. package/src/component/lib/softDelete.ts +0 -44
  243. package/src/component/localeFallbackChain.ts +0 -673
  244. package/src/component/localeFields.ts +0 -896
  245. package/src/component/mediaAssetMutations.ts +0 -725
  246. package/src/component/mediaAssets.ts +0 -932
  247. package/src/component/mediaFolderMutations.ts +0 -1046
  248. package/src/component/mediaUploadMutations.ts +0 -224
  249. package/src/component/mediaVariantMutations.ts +0 -900
  250. package/src/component/mediaVariants.ts +0 -793
  251. package/src/component/ragContentIndexer.ts +0 -1067
  252. package/src/component/rateLimitHooks.ts +0 -572
  253. package/src/component/roles.ts +0 -1360
  254. package/src/component/scheduledPublish.ts +0 -358
  255. package/src/component/schema.ts +0 -617
  256. package/src/component/taxonomies.ts +0 -949
  257. package/src/component/taxonomyMutations.ts +0 -1210
  258. package/src/component/trash.ts +0 -724
  259. package/src/component/userContext.ts +0 -898
  260. package/src/component/validation.ts +0 -1388
  261. package/src/component/validators.ts +0 -949
  262. package/src/component/versionMutations.ts +0 -392
  263. package/src/component/webhookTrigger.ts +0 -1922
  264. package/src/react/index.ts +0 -898
  265. 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
- });