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,942 +0,0 @@
1
- /**
2
- * Admin API Helper for Convex CMS
3
- *
4
- * This module provides the `defineAdminAPI` function that creates typed
5
- * Convex functions for the admin UI to call. Users export these functions
6
- * from their `convex/` directory, which the admin UI then calls.
7
- *
8
- * This follows the standard Convex component pattern used by:
9
- * - @convex-dev/agent's `definePlaygroundAPI`
10
- * - template-component's `exposeApi`
11
- * - @dodopayments/convex's class-based API
12
- *
13
- * @example
14
- * ```typescript
15
- * // convex/admin.ts
16
- * import { defineAdminAPI } from "@convex-cms/core";
17
- * import { components } from "./_generated/api";
18
- *
19
- * export const {
20
- * contentTypes,
21
- * entries,
22
- * media,
23
- * stats,
24
- * } = defineAdminAPI(components.convexCms, {
25
- * auth: async (ctx, operation) => {
26
- * // Optional: validate user has admin access
27
- * const identity = await ctx.auth.getUserIdentity();
28
- * if (!identity) throw new Error("Unauthorized");
29
- * return identity.subject;
30
- * },
31
- * });
32
- * ```
33
- */
34
-
35
- import {
36
- queryGeneric,
37
- mutationGeneric,
38
- type Auth,
39
- } from "convex/server";
40
- import { v } from "convex/values";
41
- import type { ComponentApi } from "../component/_generated/component.js";
42
- import type { Id } from "../component/_generated/dataModel.js";
43
- import {
44
- fieldDefinitionValidator,
45
- contentStatusValidator,
46
- mediaTypeValidator,
47
- } from "../component/schema.js";
48
-
49
- // =============================================================================
50
- // Types
51
- // =============================================================================
52
-
53
- /**
54
- * Operation context passed to the auth callback.
55
- */
56
- export type AdminOperation =
57
- | { type: "contentTypes.list" }
58
- | { type: "contentTypes.get"; id: string }
59
- | { type: "contentTypes.create" }
60
- | { type: "contentTypes.update"; id: string }
61
- | { type: "contentTypes.delete"; id: string }
62
- | { type: "entries.list"; contentTypeId: string }
63
- | { type: "entries.get"; id: string }
64
- | { type: "entries.create"; contentTypeId: string }
65
- | { type: "entries.update"; id: string }
66
- | { type: "entries.publish"; id: string }
67
- | { type: "entries.unpublish"; id: string }
68
- | { type: "entries.delete"; id: string }
69
- | { type: "entries.duplicate"; id: string }
70
- | { type: "entries.schedule"; id: string }
71
- | { type: "entries.cancelSchedule"; id: string }
72
- | { type: "entries.getScheduled" }
73
- | { type: "media.assets.list" }
74
- | { type: "media.assets.get"; id: string }
75
- | { type: "media.assets.create" }
76
- | { type: "media.assets.update"; id: string }
77
- | { type: "media.assets.delete"; id: string }
78
- | { type: "media.assets.restore"; id: string }
79
- | { type: "media.assets.move" }
80
- | { type: "media.folders.list" }
81
- | { type: "media.folders.get"; id: string }
82
- | { type: "media.folders.getTree" }
83
- | { type: "media.folders.create" }
84
- | { type: "media.folders.update"; id: string }
85
- | { type: "media.folders.move"; id: string }
86
- | { type: "media.folders.delete"; id: string }
87
- | { type: "media.folders.restore"; id: string }
88
- | { type: "media.generateUploadUrl" }
89
- | { type: "stats.getDashboardStats" };
90
-
91
- /**
92
- * Options for configuring the admin API.
93
- */
94
- export interface AdminApiOptions {
95
- /**
96
- * Optional authentication callback.
97
- *
98
- * Called before each operation to validate access. Should throw if
99
- * unauthorized. Returns the authenticated user's ID (or null for anonymous).
100
- *
101
- * If not provided, all operations are allowed (useful for development).
102
- *
103
- * @example
104
- * ```typescript
105
- * auth: async (ctx, operation) => {
106
- * const identity = await ctx.auth.getUserIdentity();
107
- * if (!identity) throw new Error("Unauthorized");
108
- * // Could also check operation.type for fine-grained access control
109
- * return identity.subject;
110
- * }
111
- * ```
112
- */
113
- auth?: (
114
- ctx: { auth: Auth },
115
- operation: AdminOperation
116
- ) => Promise<string | null>;
117
- }
118
-
119
- // =============================================================================
120
- // Validators (reused across functions)
121
- // =============================================================================
122
-
123
- const paginationOptsValidator = v.object({
124
- numItems: v.number(),
125
- cursor: v.union(v.string(), v.null()),
126
- });
127
-
128
- // =============================================================================
129
- // defineAdminAPI
130
- // =============================================================================
131
-
132
- /**
133
- * Creates typed Convex functions for the CMS admin UI.
134
- *
135
- * This function returns an object containing query and mutation functions
136
- * that the admin UI calls. Users export these from their `convex/` directory.
137
- *
138
- * @param component - The CMS component API from `components.convexCms`
139
- * @param options - Optional configuration including auth callback
140
- * @returns Object with namespaced admin functions
141
- *
142
- * @example
143
- * ```typescript
144
- * // convex/admin.ts
145
- * import { defineAdminAPI } from "@convex-cms/core";
146
- * import { components } from "./_generated/api";
147
- *
148
- * export const { contentTypes, entries, media, stats } = defineAdminAPI(
149
- * components.convexCms
150
- * );
151
- * ```
152
- */
153
- export function defineAdminAPI(
154
- component: ComponentApi,
155
- options: AdminApiOptions = {}
156
- ) {
157
- const { auth } = options;
158
-
159
- // Helper to run auth check if configured
160
- const checkAuth = async (
161
- ctx: { auth: Auth },
162
- operation: AdminOperation
163
- ): Promise<string | null> => {
164
- if (auth) {
165
- return await auth(ctx, operation);
166
- }
167
- return null;
168
- };
169
-
170
- return {
171
- // =========================================================================
172
- // Content Types
173
- // =========================================================================
174
- contentTypes: {
175
- list: queryGeneric({
176
- args: {
177
- isActive: v.optional(v.boolean()),
178
- },
179
- returns: v.any(),
180
- handler: async (ctx, args) => {
181
- await checkAuth(ctx, { type: "contentTypes.list" });
182
- // Returns paginated result: { page, continueCursor, isDone }
183
- return await ctx.runQuery(component.contentTypes.list, {
184
- isActive: args.isActive,
185
- });
186
- },
187
- }),
188
-
189
- get: queryGeneric({
190
- args: {
191
- id: v.optional(v.string()),
192
- name: v.optional(v.string()),
193
- },
194
- returns: v.union(v.any(), v.null()),
195
- handler: async (ctx, args) => {
196
- await checkAuth(ctx, { type: "contentTypes.get", id: args.id ?? "" });
197
- return await ctx.runQuery(component.contentTypes.get, {
198
- id: args.id,
199
- name: args.name,
200
- });
201
- },
202
- }),
203
-
204
- create: mutationGeneric({
205
- args: {
206
- name: v.string(),
207
- displayName: v.string(),
208
- fields: v.array(fieldDefinitionValidator),
209
- description: v.optional(v.string()),
210
- icon: v.optional(v.string()),
211
- singleton: v.optional(v.boolean()),
212
- slugField: v.optional(v.string()),
213
- titleField: v.optional(v.string()),
214
- sortOrder: v.optional(v.number()),
215
- /** Required for audit tracking - the user ID creating this content type */
216
- createdBy: v.string(),
217
- },
218
- returns: v.any(),
219
- handler: async (ctx, args) => {
220
- await checkAuth(ctx, { type: "contentTypes.create" });
221
- return await ctx.runMutation(
222
- component.contentTypeMutations.createContentType,
223
- {
224
- name: args.name,
225
- displayName: args.displayName,
226
- fields: args.fields,
227
- description: args.description,
228
- icon: args.icon,
229
- singleton: args.singleton,
230
- slugField: args.slugField,
231
- titleField: args.titleField,
232
- sortOrder: args.sortOrder,
233
- createdBy: args.createdBy,
234
- }
235
- );
236
- },
237
- }),
238
-
239
- update: mutationGeneric({
240
- args: {
241
- id: v.string(),
242
- displayName: v.optional(v.string()),
243
- fields: v.optional(v.array(fieldDefinitionValidator)),
244
- description: v.optional(v.string()),
245
- icon: v.optional(v.string()),
246
- singleton: v.optional(v.boolean()),
247
- slugField: v.optional(v.string()),
248
- titleField: v.optional(v.string()),
249
- sortOrder: v.optional(v.number()),
250
- isActive: v.optional(v.boolean()),
251
- updatedBy: v.optional(v.string()),
252
- },
253
- returns: v.any(),
254
- handler: async (ctx, args) => {
255
- await checkAuth(ctx, { type: "contentTypes.update", id: args.id });
256
- return await ctx.runMutation(
257
- component.contentTypeMutations.updateContentType,
258
- {
259
- id: args.id,
260
- displayName: args.displayName,
261
- fields: args.fields,
262
- description: args.description,
263
- icon: args.icon,
264
- singleton: args.singleton,
265
- slugField: args.slugField,
266
- titleField: args.titleField,
267
- sortOrder: args.sortOrder,
268
- isActive: args.isActive,
269
- updatedBy: args.updatedBy,
270
- }
271
- );
272
- },
273
- }),
274
-
275
- delete: mutationGeneric({
276
- args: {
277
- id: v.string(),
278
- cascade: v.optional(v.boolean()),
279
- hardDelete: v.optional(v.boolean()),
280
- deletedBy: v.optional(v.string()),
281
- },
282
- returns: v.any(),
283
- handler: async (ctx, args) => {
284
- await checkAuth(ctx, { type: "contentTypes.delete", id: args.id });
285
- return await ctx.runMutation(
286
- component.contentTypeMutations.deleteContentType,
287
- {
288
- id: args.id ,
289
- cascade: args.cascade,
290
- hardDelete: args.hardDelete,
291
- deletedBy: args.deletedBy,
292
- }
293
- );
294
- },
295
- }),
296
- },
297
-
298
- // =========================================================================
299
- // Content Entries
300
- // =========================================================================
301
- entries: {
302
- list: queryGeneric({
303
- args: {
304
- contentTypeId: v.optional(v.string()),
305
- status: v.optional(contentStatusValidator),
306
- search: v.optional(v.string()),
307
- locale: v.optional(v.string()),
308
- paginationOpts: paginationOptsValidator,
309
- },
310
- returns: v.any(),
311
- handler: async (ctx, args) => {
312
- await checkAuth(ctx, {
313
- type: "entries.list",
314
- contentTypeId: args.contentTypeId ?? "",
315
- });
316
- return await ctx.runQuery(component.contentEntries.list, {
317
- contentTypeId: args.contentTypeId ,
318
- status: args.status,
319
- search: args.search,
320
- locale: args.locale,
321
- paginationOpts: args.paginationOpts,
322
- });
323
- },
324
- }),
325
-
326
- get: queryGeneric({
327
- args: {
328
- id: v.string(),
329
- },
330
- returns: v.union(v.any(), v.null()),
331
- handler: async (ctx, args) => {
332
- await checkAuth(ctx, { type: "entries.get", id: args.id });
333
- return await ctx.runQuery(component.contentEntries.get, {
334
- id: args.id ,
335
- });
336
- },
337
- }),
338
-
339
- create: mutationGeneric({
340
- args: {
341
- contentTypeId: v.string(),
342
- data: v.any(),
343
- slug: v.optional(v.string()),
344
- status: v.optional(contentStatusValidator),
345
- locale: v.optional(v.string()),
346
- primaryEntryId: v.optional(v.string()),
347
- createdBy: v.optional(v.string()),
348
- },
349
- returns: v.any(),
350
- handler: async (ctx, args) => {
351
- await checkAuth(ctx, {
352
- type: "entries.create",
353
- contentTypeId: args.contentTypeId,
354
- });
355
- return await ctx.runMutation(
356
- component.contentEntryMutations.createEntry,
357
- {
358
- contentTypeId: args.contentTypeId ,
359
- data: args.data,
360
- slug: args.slug,
361
- status: args.status,
362
- locale: args.locale,
363
- primaryEntryId: args.primaryEntryId ,
364
- createdBy: args.createdBy,
365
- }
366
- );
367
- },
368
- }),
369
-
370
- update: mutationGeneric({
371
- args: {
372
- id: v.string(),
373
- data: v.optional(v.any()),
374
- slug: v.optional(v.string()),
375
- status: v.optional(contentStatusValidator),
376
- scheduledPublishAt: v.optional(v.number()),
377
- updatedBy: v.optional(v.string()),
378
- regenerateSlug: v.optional(v.boolean()),
379
- },
380
- returns: v.any(),
381
- handler: async (ctx, args) => {
382
- await checkAuth(ctx, { type: "entries.update", id: args.id });
383
- return await ctx.runMutation(
384
- component.contentEntryMutations.updateEntry,
385
- {
386
- id: args.id ,
387
- data: args.data,
388
- slug: args.slug,
389
- status: args.status,
390
- scheduledPublishAt: args.scheduledPublishAt,
391
- updatedBy: args.updatedBy,
392
- regenerateSlug: args.regenerateSlug,
393
- }
394
- );
395
- },
396
- }),
397
-
398
- publish: mutationGeneric({
399
- args: {
400
- id: v.string(),
401
- changeDescription: v.optional(v.string()),
402
- updatedBy: v.optional(v.string()),
403
- },
404
- returns: v.any(),
405
- handler: async (ctx, args) => {
406
- await checkAuth(ctx, { type: "entries.publish", id: args.id });
407
- return await ctx.runMutation(
408
- component.contentEntryMutations.publishEntry,
409
- {
410
- id: args.id ,
411
- changeDescription: args.changeDescription,
412
- updatedBy: args.updatedBy,
413
- }
414
- );
415
- },
416
- }),
417
-
418
- unpublish: mutationGeneric({
419
- args: {
420
- id: v.string(),
421
- updatedBy: v.optional(v.string()),
422
- },
423
- returns: v.any(),
424
- handler: async (ctx, args) => {
425
- await checkAuth(ctx, { type: "entries.unpublish", id: args.id });
426
- return await ctx.runMutation(
427
- component.contentEntryMutations.unpublishEntry,
428
- {
429
- id: args.id ,
430
- updatedBy: args.updatedBy,
431
- }
432
- );
433
- },
434
- }),
435
-
436
- delete: mutationGeneric({
437
- args: {
438
- id: v.string(),
439
- hardDelete: v.optional(v.boolean()),
440
- deletedBy: v.optional(v.string()),
441
- },
442
- returns: v.any(),
443
- handler: async (ctx, args) => {
444
- await checkAuth(ctx, { type: "entries.delete", id: args.id });
445
- return await ctx.runMutation(
446
- component.contentEntryMutations.deleteEntry,
447
- {
448
- id: args.id ,
449
- hardDelete: args.hardDelete,
450
- deletedBy: args.deletedBy,
451
- }
452
- );
453
- },
454
- }),
455
-
456
- duplicate: mutationGeneric({
457
- args: {
458
- id: v.string(),
459
- copyMediaReferences: v.optional(v.boolean()),
460
- createdBy: v.optional(v.string()),
461
- },
462
- returns: v.any(),
463
- handler: async (ctx, args) => {
464
- await checkAuth(ctx, { type: "entries.duplicate", id: args.id });
465
- return await ctx.runMutation(
466
- component.contentEntryMutations.duplicateEntry,
467
- {
468
- sourceEntryId: args.id ,
469
- copyMediaReferences: args.copyMediaReferences,
470
- createdBy: args.createdBy,
471
- }
472
- );
473
- },
474
- }),
475
-
476
- schedule: mutationGeneric({
477
- args: {
478
- id: v.string(),
479
- publishAt: v.number(),
480
- updatedBy: v.optional(v.string()),
481
- },
482
- returns: v.any(),
483
- handler: async (ctx, args) => {
484
- await checkAuth(ctx, { type: "entries.schedule", id: args.id });
485
- return await ctx.runMutation(
486
- component.scheduledPublish.scheduleEntry,
487
- {
488
- id: args.id ,
489
- publishAt: args.publishAt,
490
- updatedBy: args.updatedBy,
491
- }
492
- );
493
- },
494
- }),
495
-
496
- cancelSchedule: mutationGeneric({
497
- args: {
498
- id: v.string(),
499
- updatedBy: v.optional(v.string()),
500
- },
501
- returns: v.any(),
502
- handler: async (ctx, args) => {
503
- await checkAuth(ctx, { type: "entries.cancelSchedule", id: args.id });
504
- return await ctx.runMutation(
505
- component.scheduledPublish.cancelScheduledPublish,
506
- {
507
- id: args.id ,
508
- updatedBy: args.updatedBy,
509
- }
510
- );
511
- },
512
- }),
513
-
514
- getScheduled: queryGeneric({
515
- args: {},
516
- returns: v.array(v.any()),
517
- handler: async (ctx) => {
518
- await checkAuth(ctx, { type: "entries.getScheduled" });
519
- return await ctx.runQuery(
520
- component.scheduledPublish.getScheduledEntries,
521
- {}
522
- );
523
- },
524
- }),
525
- },
526
-
527
- // =========================================================================
528
- // Media (Assets and Folders)
529
- // =========================================================================
530
- media: {
531
- // --- Assets ---
532
- listAssets: queryGeneric({
533
- args: {
534
- folderId: v.optional(v.string()),
535
- type: v.optional(mediaTypeValidator),
536
- search: v.optional(v.string()),
537
- paginationOpts: paginationOptsValidator,
538
- },
539
- returns: v.any(),
540
- handler: async (ctx, args) => {
541
- await checkAuth(ctx, { type: "media.assets.list" });
542
- return await ctx.runQuery(component.mediaAssets.list, {
543
- folderId: args.folderId ,
544
- type: args.type,
545
- search: args.search,
546
- paginationOpts: args.paginationOpts,
547
- });
548
- },
549
- }),
550
-
551
- getAsset: queryGeneric({
552
- args: {
553
- id: v.string(),
554
- },
555
- returns: v.union(v.any(), v.null()),
556
- handler: async (ctx, args) => {
557
- await checkAuth(ctx, { type: "media.assets.get", id: args.id });
558
- return await ctx.runQuery(component.mediaAssets.get, {
559
- id: args.id ,
560
- });
561
- },
562
- }),
563
-
564
- createAsset: mutationGeneric({
565
- args: {
566
- storageId: v.string(),
567
- name: v.string(),
568
- mimeType: v.string(),
569
- size: v.number(),
570
- parentId: v.optional(v.string()),
571
- width: v.optional(v.number()),
572
- height: v.optional(v.number()),
573
- title: v.optional(v.string()),
574
- description: v.optional(v.string()),
575
- altText: v.optional(v.string()),
576
- createdBy: v.optional(v.string()),
577
- },
578
- returns: v.any(),
579
- handler: async (ctx, args) => {
580
- await checkAuth(ctx, { type: "media.assets.create" });
581
- return await ctx.runMutation(
582
- component.mediaAssetMutations.createMediaAsset,
583
- {
584
- storageId: args.storageId as Id<"_storage">,
585
- name: args.name,
586
- mimeType: args.mimeType,
587
- size: args.size,
588
- parentId: args.parentId as Id<"mediaItems"> | undefined,
589
- width: args.width,
590
- height: args.height,
591
- title: args.title,
592
- description: args.description,
593
- altText: args.altText,
594
- createdBy: args.createdBy,
595
- }
596
- );
597
- },
598
- }),
599
-
600
- updateAsset: mutationGeneric({
601
- args: {
602
- id: v.string(),
603
- name: v.optional(v.string()),
604
- title: v.optional(v.string()),
605
- description: v.optional(v.string()),
606
- altText: v.optional(v.string()),
607
- parentId: v.optional(v.string()),
608
- tags: v.optional(v.array(v.string())),
609
- },
610
- returns: v.any(),
611
- handler: async (ctx, args) => {
612
- await checkAuth(ctx, { type: "media.assets.update", id: args.id });
613
- return await ctx.runMutation(
614
- component.mediaAssetMutations.updateMediaAsset,
615
- {
616
- id: args.id as Id<"mediaItems">,
617
- name: args.name,
618
- title: args.title,
619
- description: args.description,
620
- altText: args.altText,
621
- parentId: args.parentId as Id<"mediaItems"> | undefined,
622
- tags: args.tags,
623
- }
624
- );
625
- },
626
- }),
627
-
628
- deleteAsset: mutationGeneric({
629
- args: {
630
- id: v.string(),
631
- hardDelete: v.optional(v.boolean()),
632
- deletedBy: v.optional(v.string()),
633
- },
634
- returns: v.any(),
635
- handler: async (ctx, args) => {
636
- await checkAuth(ctx, { type: "media.assets.delete", id: args.id });
637
- return await ctx.runMutation(
638
- component.mediaAssetMutations.deleteMediaAsset,
639
- {
640
- id: args.id ,
641
- hardDelete: args.hardDelete,
642
- deletedBy: args.deletedBy,
643
- }
644
- );
645
- },
646
- }),
647
-
648
- restoreAsset: mutationGeneric({
649
- args: {
650
- id: v.string(),
651
- },
652
- returns: v.any(),
653
- handler: async (ctx, args) => {
654
- await checkAuth(ctx, { type: "media.assets.restore", id: args.id });
655
- return await ctx.runMutation(
656
- component.mediaAssetMutations.restoreMediaAsset,
657
- {
658
- id: args.id ,
659
- }
660
- );
661
- },
662
- }),
663
-
664
- moveAssets: mutationGeneric({
665
- args: {
666
- assetIds: v.array(v.string()),
667
- targetFolderId: v.optional(v.string()),
668
- },
669
- returns: v.any(),
670
- handler: async (ctx, args) => {
671
- await checkAuth(ctx, { type: "media.assets.move" });
672
- return await ctx.runMutation(
673
- component.mediaAssetMutations.moveMediaAssets,
674
- {
675
- assetIds: args.assetIds ,
676
- targetFolderId: args.targetFolderId ,
677
- }
678
- );
679
- },
680
- }),
681
-
682
- // --- Folders ---
683
- listFolders: queryGeneric({
684
- args: {
685
- parentId: v.optional(v.string()),
686
- },
687
- returns: v.array(v.any()),
688
- handler: async (ctx, args) => {
689
- await checkAuth(ctx, { type: "media.folders.list" });
690
- return await ctx.runQuery(
691
- component.mediaFolderMutations.listMediaFolders,
692
- {
693
- parentId: args.parentId ,
694
- }
695
- );
696
- },
697
- }),
698
-
699
- getFolder: queryGeneric({
700
- args: {
701
- id: v.string(),
702
- },
703
- returns: v.union(v.any(), v.null()),
704
- handler: async (ctx, args) => {
705
- await checkAuth(ctx, { type: "media.folders.get", id: args.id });
706
- return await ctx.runQuery(
707
- component.mediaFolderMutations.getMediaFolder,
708
- {
709
- id: args.id ,
710
- }
711
- );
712
- },
713
- }),
714
-
715
- getFolderTree: queryGeneric({
716
- args: {},
717
- returns: v.array(v.any()),
718
- handler: async (ctx) => {
719
- await checkAuth(ctx, { type: "media.folders.getTree" });
720
- return await ctx.runQuery(
721
- component.mediaFolderMutations.getFolderTree,
722
- {}
723
- );
724
- },
725
- }),
726
-
727
- createFolder: mutationGeneric({
728
- args: {
729
- name: v.string(),
730
- parentId: v.optional(v.string()),
731
- description: v.optional(v.string()),
732
- createdBy: v.optional(v.string()),
733
- },
734
- returns: v.any(),
735
- handler: async (ctx, args) => {
736
- await checkAuth(ctx, { type: "media.folders.create" });
737
- return await ctx.runMutation(
738
- component.mediaFolderMutations.createMediaFolder,
739
- {
740
- name: args.name,
741
- parentId: args.parentId ,
742
- description: args.description,
743
- createdBy: args.createdBy,
744
- }
745
- );
746
- },
747
- }),
748
-
749
- updateFolder: mutationGeneric({
750
- args: {
751
- id: v.string(),
752
- name: v.optional(v.string()),
753
- description: v.optional(v.string()),
754
- sortOrder: v.optional(v.number()),
755
- },
756
- returns: v.any(),
757
- handler: async (ctx, args) => {
758
- await checkAuth(ctx, { type: "media.folders.update", id: args.id });
759
- return await ctx.runMutation(
760
- component.mediaFolderMutations.updateMediaFolder,
761
- {
762
- id: args.id ,
763
- name: args.name,
764
- description: args.description,
765
- sortOrder: args.sortOrder,
766
- }
767
- );
768
- },
769
- }),
770
-
771
- moveFolder: mutationGeneric({
772
- args: {
773
- id: v.string(),
774
- newParentId: v.optional(v.string()),
775
- },
776
- returns: v.any(),
777
- handler: async (ctx, args) => {
778
- await checkAuth(ctx, { type: "media.folders.move", id: args.id });
779
- return await ctx.runMutation(
780
- component.mediaFolderMutations.moveMediaFolder,
781
- {
782
- id: args.id ,
783
- newParentId: args.newParentId ,
784
- }
785
- );
786
- },
787
- }),
788
-
789
- deleteFolder: mutationGeneric({
790
- args: {
791
- id: v.string(),
792
- recursive: v.optional(v.boolean()),
793
- hardDelete: v.optional(v.boolean()),
794
- deletedBy: v.optional(v.string()),
795
- },
796
- returns: v.any(),
797
- handler: async (ctx, args) => {
798
- await checkAuth(ctx, { type: "media.folders.delete", id: args.id });
799
- return await ctx.runMutation(
800
- component.mediaFolderMutations.deleteMediaFolder,
801
- {
802
- id: args.id ,
803
- recursive: args.recursive,
804
- hardDelete: args.hardDelete,
805
- deletedBy: args.deletedBy,
806
- }
807
- );
808
- },
809
- }),
810
-
811
- restoreFolder: mutationGeneric({
812
- args: {
813
- id: v.string(),
814
- recursive: v.optional(v.boolean()),
815
- },
816
- returns: v.any(),
817
- handler: async (ctx, args) => {
818
- await checkAuth(ctx, { type: "media.folders.restore", id: args.id });
819
- return await ctx.runMutation(
820
- component.mediaFolderMutations.restoreMediaFolder,
821
- {
822
- id: args.id ,
823
- recursive: args.recursive,
824
- }
825
- );
826
- },
827
- }),
828
-
829
- // --- Upload ---
830
- generateUploadUrl: mutationGeneric({
831
- args: {},
832
- returns: v.string(),
833
- handler: async (ctx) => {
834
- await checkAuth(ctx, { type: "media.generateUploadUrl" });
835
- return await ctx.storage.generateUploadUrl();
836
- },
837
- }),
838
- },
839
-
840
- // =========================================================================
841
- // Dashboard Stats
842
- // =========================================================================
843
- stats: {
844
- getDashboardStats: queryGeneric({
845
- args: {},
846
- returns: v.object({
847
- contentTypes: v.object({
848
- total: v.number(),
849
- active: v.number(),
850
- }),
851
- entries: v.object({
852
- total: v.number(),
853
- published: v.number(),
854
- draft: v.number(),
855
- scheduled: v.number(),
856
- }),
857
- media: v.object({
858
- total: v.number(),
859
- images: v.number(),
860
- videos: v.number(),
861
- documents: v.number(),
862
- }),
863
- }),
864
- handler: async (ctx) => {
865
- await checkAuth(ctx, { type: "stats.getDashboardStats" });
866
-
867
- // Fetch all content types (returns paginated result)
868
- const contentTypesResult = await ctx.runQuery(
869
- component.contentTypes.list,
870
- {}
871
- );
872
- const contentTypes = contentTypesResult.page || [];
873
-
874
- // Fetch all entries (paginated, get counts)
875
- const entriesResult = await ctx.runQuery(
876
- component.contentEntries.list,
877
- {
878
- paginationOpts: { numItems: 1000, cursor: null },
879
- }
880
- );
881
-
882
- // Fetch all media assets
883
- const mediaResult = await ctx.runQuery(component.mediaAssets.list, {
884
- paginationOpts: { numItems: 1000, cursor: null },
885
- });
886
-
887
- // Calculate content type stats
888
- const activeContentTypes = contentTypes.filter(
889
- (ct: { isActive: boolean }) => ct.isActive
890
- ).length;
891
-
892
- // Calculate entry stats
893
- const entries = entriesResult.page || [];
894
- const publishedEntries = entries.filter(
895
- (e: { status: string }) => e.status === "published"
896
- ).length;
897
- const draftEntries = entries.filter(
898
- (e: { status: string }) => e.status === "draft"
899
- ).length;
900
- const scheduledEntries = entries.filter(
901
- (e: { status: string }) => e.status === "scheduled"
902
- ).length;
903
-
904
- // Calculate media stats by filtering on mimeType prefix
905
- const mediaAssets = (mediaResult.page || []) as Array<{ kind: string; mimeType?: string }>;
906
- const assets = mediaAssets.filter((m) => m.kind === "asset");
907
- const images = assets.filter(
908
- (m) => m.mimeType?.startsWith("image/")
909
- ).length;
910
- const videos = assets.filter(
911
- (m) => m.mimeType?.startsWith("video/")
912
- ).length;
913
- const documents = assets.filter(
914
- (m) => m.mimeType?.startsWith("application/pdf") ||
915
- m.mimeType?.includes("document") ||
916
- m.mimeType?.includes("sheet") ||
917
- m.mimeType?.includes("presentation")
918
- ).length;
919
-
920
- return {
921
- contentTypes: {
922
- total: contentTypes.length,
923
- active: activeContentTypes,
924
- },
925
- entries: {
926
- total: entries.length,
927
- published: publishedEntries,
928
- draft: draftEntries,
929
- scheduled: scheduledEntries,
930
- },
931
- media: {
932
- total: mediaAssets.length,
933
- images,
934
- videos,
935
- documents,
936
- },
937
- };
938
- },
939
- }),
940
- },
941
- };
942
- }