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,358 +0,0 @@
1
- /**
2
- * Scheduled Publishing Functions
3
- *
4
- * Provides functionality to schedule content entries for future publication.
5
- * Uses Convex scheduled functions to automatically publish entries at specified times.
6
- *
7
- * Workflow:
8
- * 1. User calls `scheduleEntry` with an entry ID and publish timestamp
9
- * 2. Entry status changes to "scheduled" with `scheduledPublishAt` set
10
- * 3. A Convex scheduled function is created to run at that time
11
- * 4. The scheduled function ID is stored for potential cancellation
12
- * 5. At the scheduled time, the internal `executeScheduledPublish` mutation runs
13
- * 6. The entry is published (or skipped if status/conditions have changed)
14
- *
15
- * Cancellation:
16
- * - User can call `cancelScheduledPublish` to revert to draft and cancel the scheduled job
17
- * - Rescheduling automatically cancels any existing scheduled job
18
- */
19
-
20
- import { v } from "convex/values";
21
- import { isDeleted } from "./lib/softDelete.js";
22
- import { mutation, internalMutation, query } from "./_generated/server.js";
23
- import { internal } from "./_generated/api.js";
24
- import { contentEntryDoc, scheduleEntryArgs } from "./validators.js";
25
-
26
- // =============================================================================
27
- // Schedule Entry Mutation
28
- // =============================================================================
29
-
30
- /**
31
- * Mutation to schedule a content entry for future publication.
32
- *
33
- * This sets the entry status to "scheduled" and creates a Convex scheduled
34
- * function that will automatically publish the entry at the specified time.
35
- *
36
- * Key behaviors:
37
- * 1. **Validation**: The entry must exist, not be deleted, and not already be published
38
- * 2. **Time Validation**: The publish time must be in the future (at least 1 minute)
39
- * 3. **Rescheduling**: If already scheduled, cancels the existing scheduled job first
40
- * 4. **Atomicity**: Status change and job scheduling happen atomically
41
- *
42
- * @param id - The content entry ID to schedule
43
- * @param publishAt - Unix timestamp (ms) when the entry should be published
44
- * @param updatedBy - Optional user ID for audit trail
45
- *
46
- * @returns The updated content entry with scheduled status
47
- *
48
- * @throws Error if the entry does not exist
49
- * @throws Error if the entry has been deleted
50
- * @throws Error if the entry is already published
51
- * @throws Error if the publish time is not in the future
52
- *
53
- * @example
54
- * ```typescript
55
- * // Schedule a post to publish tomorrow at 9 AM
56
- * const tomorrow9am = new Date();
57
- * tomorrow9am.setDate(tomorrow9am.getDate() + 1);
58
- * tomorrow9am.setHours(9, 0, 0, 0);
59
- *
60
- * const scheduled = await ctx.runMutation(api.scheduledPublish.scheduleEntry, {
61
- * id: entryId,
62
- * publishAt: tomorrow9am.getTime(),
63
- * updatedBy: currentUserId,
64
- * });
65
- * ```
66
- */
67
- export const scheduleEntry = mutation({
68
- args: scheduleEntryArgs.fields,
69
- returns: contentEntryDoc,
70
- handler: async (ctx, args) => {
71
- const { id, publishAt, updatedBy } = args;
72
-
73
- const entry = await ctx.db.get(id);
74
- if (!entry) {
75
- throw new Error(`Content entry not found: ${id}`);
76
- }
77
- if (isDeleted(entry)) {
78
- throw new Error(`Content entry has been deleted: ${id}`);
79
- }
80
- if (entry.status === "published") {
81
- throw new Error(`Content entry is already published: ${id}`);
82
- }
83
- if (entry.status === "archived") {
84
- throw new Error(
85
- `Cannot schedule archived content. Restore it first: ${id}`,
86
- );
87
- }
88
-
89
- // Validate the publish time is in the future (at least 1 minute from now)
90
- const now = Date.now();
91
- const minimumScheduleTime = now + 60 * 1000; // 1 minute from now
92
- if (publishAt < minimumScheduleTime) {
93
- throw new Error(
94
- `Scheduled publish time must be at least 1 minute in the future. ` +
95
- `Received: ${new Date(publishAt).toISOString()}, ` +
96
- `Minimum: ${new Date(minimumScheduleTime).toISOString()}`,
97
- );
98
- }
99
-
100
- // If already scheduled, we need to cancel the existing scheduled job
101
- // Note: We can't actually cancel Convex scheduled functions directly,
102
- // but the executeScheduledPublish function will check if the entry
103
- // is still scheduled with the same timestamp before publishing
104
-
105
- // Schedule the publish function to run at the specified time
106
- await ctx.scheduler.runAt(
107
- publishAt,
108
- internal.scheduledPublish.executeScheduledPublish,
109
- {
110
- entryId: id,
111
- expectedPublishAt: publishAt,
112
- },
113
- );
114
-
115
- // Update the entry to scheduled status
116
- await ctx.db.patch(id, {
117
- status: "scheduled",
118
- scheduledPublishAt: publishAt,
119
- // Store the scheduled function ID for reference (as a string since it's a system ID)
120
- updatedBy,
121
- version: entry.version + 1,
122
- });
123
-
124
- const scheduledEntry = await ctx.db.get(id);
125
- if (!scheduledEntry) {
126
- throw new Error("Failed to retrieve scheduled entry");
127
- }
128
-
129
- return scheduledEntry;
130
- },
131
- });
132
-
133
- // =============================================================================
134
- // Cancel Scheduled Publish Mutation
135
- // =============================================================================
136
-
137
- /**
138
- * Mutation to cancel a scheduled publication and revert to draft.
139
- *
140
- * This reverts the entry status to "draft" and clears the scheduled publish time.
141
- * The scheduled function will still run, but it will detect the status change
142
- * and skip the publish operation.
143
- *
144
- * @param id - The content entry ID to cancel scheduling for
145
- * @param updatedBy - Optional user ID for audit trail
146
- *
147
- * @returns The updated content entry (now in draft status)
148
- *
149
- * @throws Error if the entry does not exist
150
- * @throws Error if the entry has been deleted
151
- * @throws Error if the entry is not currently scheduled
152
- *
153
- * @example
154
- * ```typescript
155
- * const draft = await ctx.runMutation(api.scheduledPublish.cancelScheduledPublish, {
156
- * id: entryId,
157
- * updatedBy: currentUserId,
158
- * });
159
- * console.log(draft.status); // "draft"
160
- * ```
161
- */
162
- export const cancelScheduledPublish = mutation({
163
- args: {
164
- /** The ID of the content entry to cancel scheduling for */
165
- id: v.id("contentEntries"),
166
- /** User ID performing the cancellation (for audit trail) */
167
- updatedBy: v.optional(v.string()),
168
- },
169
- returns: contentEntryDoc,
170
- handler: async (ctx, args) => {
171
- const { id, updatedBy } = args;
172
-
173
- const entry = await ctx.db.get(id);
174
- if (!entry) {
175
- throw new Error(`Content entry not found: ${id}`);
176
- }
177
- if (isDeleted(entry)) {
178
- throw new Error(`Content entry has been deleted: ${id}`);
179
- }
180
- if (entry.status !== "scheduled") {
181
- throw new Error(
182
- `Content entry is not scheduled. Current status: ${entry.status}`,
183
- );
184
- }
185
-
186
- // Revert to draft status and clear scheduled time
187
- await ctx.db.patch(id, {
188
- status: "draft",
189
- scheduledPublishAt: undefined,
190
- updatedBy,
191
- version: entry.version + 1,
192
- });
193
-
194
- const updatedEntry = await ctx.db.get(id);
195
- if (!updatedEntry) {
196
- throw new Error("Failed to retrieve updated entry");
197
- }
198
-
199
- return updatedEntry;
200
- },
201
- });
202
-
203
- // =============================================================================
204
- // Execute Scheduled Publish (Internal)
205
- // =============================================================================
206
-
207
- /**
208
- * Internal mutation that executes the scheduled publish.
209
- *
210
- * This is called by Convex's scheduler at the scheduled time.
211
- * It verifies the entry is still in the expected state before publishing.
212
- *
213
- * Safety checks:
214
- * - Entry still exists and is not deleted
215
- * - Entry is still in "scheduled" status (not manually published or cancelled)
216
- * - The scheduled time matches (in case of rescheduling)
217
- *
218
- * If any check fails, the publish is skipped silently (no error thrown).
219
- * This prevents orphaned scheduled jobs from causing issues.
220
- */
221
- export const executeScheduledPublish = internalMutation({
222
- args: {
223
- /** The ID of the content entry to publish */
224
- entryId: v.id("contentEntries"),
225
- /** The expected publish timestamp (for validation) */
226
- expectedPublishAt: v.number(),
227
- },
228
- handler: async (ctx, args) => {
229
- const { entryId, expectedPublishAt } = args;
230
-
231
- const entry = await ctx.db.get(entryId);
232
-
233
- // Safety checks - skip publish if conditions aren't met
234
- if (!entry) {
235
- console.log(
236
- `Scheduled publish skipped: Entry ${entryId} no longer exists`,
237
- );
238
- return;
239
- }
240
- if (isDeleted(entry)) {
241
- console.log(
242
- `Scheduled publish skipped: Entry ${entryId} has been deleted`,
243
- );
244
- return;
245
- }
246
- if (entry.status !== "scheduled") {
247
- console.log(
248
- `Scheduled publish skipped: Entry ${entryId} status is "${entry.status}", not "scheduled"`,
249
- );
250
- return;
251
- }
252
- if (entry.scheduledPublishAt !== expectedPublishAt) {
253
- console.log(
254
- `Scheduled publish skipped: Entry ${entryId} was rescheduled ` +
255
- `(expected ${expectedPublishAt}, found ${entry.scheduledPublishAt})`,
256
- );
257
- return;
258
- }
259
-
260
- const now = Date.now();
261
-
262
- // Create a version snapshot before publishing
263
- await ctx.db.insert("contentVersions", {
264
- entryId,
265
- versionNumber: entry.version,
266
- data: entry.data,
267
- slug: entry.slug,
268
- status: entry.status,
269
- changeDescription: "Scheduled publication",
270
- createdBy: entry.updatedBy,
271
- wasPublished: true,
272
- publishedAt: now,
273
- });
274
-
275
- // Publish the entry
276
- const updates: Record<string, unknown> = {
277
- status: "published",
278
- lastPublishedAt: now,
279
- scheduledPublishAt: undefined,
280
- version: entry.version + 1,
281
- };
282
-
283
- // Set firstPublishedAt only on first publication
284
- if (entry.firstPublishedAt === undefined) {
285
- updates.firstPublishedAt = now;
286
- }
287
-
288
- await ctx.db.patch(entryId, updates);
289
-
290
- console.log(
291
- `Scheduled publish executed: Entry ${entryId} is now published`,
292
- );
293
- },
294
- });
295
-
296
- // =============================================================================
297
- // Query for Scheduled Entries
298
- // =============================================================================
299
-
300
- /**
301
- * Query to get all entries scheduled for publication within a time range.
302
- *
303
- * Useful for:
304
- * - Displaying upcoming scheduled content in the admin UI
305
- * - Monitoring scheduled publishing queue
306
- *
307
- * @param from - Start of time range (optional, defaults to now)
308
- * @param to - End of time range (optional, defaults to 30 days from now)
309
- *
310
- * @returns Array of scheduled content entries, sorted by scheduled time
311
- */
312
- export const getScheduledEntries = query({
313
- args: {
314
- /** Start of time range (ms timestamp). Defaults to now. */
315
- from: v.optional(v.number()),
316
- /** End of time range (ms timestamp). Defaults to 30 days from now. */
317
- to: v.optional(v.number()),
318
- /** Content type ID to filter by (optional) */
319
- contentTypeId: v.optional(v.id("contentTypes")),
320
- },
321
- handler: async (ctx, args) => {
322
- const now = Date.now();
323
- const from = args.from ?? now;
324
- const to = args.to ?? now + 30 * 24 * 60 * 60 * 1000; // 30 days
325
-
326
- // Query scheduled entries using the index
327
- const query = ctx.db
328
- .query("contentEntries")
329
- .withIndex("by_scheduled_publish", (q) => q.eq("status", "scheduled"));
330
-
331
- // Collect and filter by time range and optionally by content type
332
- const entries = await query.collect();
333
-
334
- return entries
335
- .filter((entry) => {
336
- // Must have a scheduled time in range
337
- if (
338
- entry.scheduledPublishAt === undefined ||
339
- entry.scheduledPublishAt < from ||
340
- entry.scheduledPublishAt > to
341
- ) {
342
- return false;
343
- }
344
- // Must not be deleted
345
- if (isDeleted(entry)) {
346
- return false;
347
- }
348
- // Filter by content type if specified
349
- if (args.contentTypeId && entry.contentTypeId !== args.contentTypeId) {
350
- return false;
351
- }
352
- return true;
353
- })
354
- .sort(
355
- (a, b) => (a.scheduledPublishAt ?? 0) - (b.scheduledPublishAt ?? 0),
356
- );
357
- },
358
- });