@jant/core 0.3.35 → 0.3.37

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 (307) hide show
  1. package/bin/commands/export.js +1 -1
  2. package/bin/commands/import-site.js +529 -0
  3. package/bin/commands/reset-password.js +3 -2
  4. package/dist/client/assets/heic-to-DIRPI3VF.js +1 -0
  5. package/dist/client/assets/module-RjUF93sV.js +716 -0
  6. package/dist/client/assets/native-48B9X9Wg.js +1 -0
  7. package/dist/client/assets/url-FWFqPJPb.js +1 -0
  8. package/dist/client/client.css +1 -1
  9. package/dist/client/client.js +4564 -3013
  10. package/dist/index.js +12885 -8161
  11. package/package.json +23 -6
  12. package/src/__tests__/helpers/app.ts +10 -10
  13. package/src/__tests__/helpers/db.ts +91 -87
  14. package/src/app.tsx +157 -31
  15. package/src/auth.ts +20 -2
  16. package/src/client/archive-nav.js +187 -0
  17. package/src/client/audio-player.ts +478 -0
  18. package/src/client/audio-processor.ts +84 -0
  19. package/src/{lib → client}/avatar-upload.ts +4 -3
  20. package/src/{lib → client}/collection-form-bridge.ts +2 -2
  21. package/src/{ui → client}/components/__tests__/jant-collection-form.test.ts +26 -9
  22. package/src/client/components/__tests__/jant-compose-dialog.test.ts +1140 -0
  23. package/src/client/components/__tests__/jant-compose-editor.test.ts +504 -0
  24. package/src/{ui → client}/components/__tests__/jant-post-form.test.ts +37 -17
  25. package/src/{ui → client}/components/__tests__/jant-settings-avatar.test.ts +2 -2
  26. package/src/{ui → client}/components/__tests__/jant-settings-general.test.ts +3 -3
  27. package/src/client/components/collection-sidebar-types.ts +43 -0
  28. package/src/{ui → client}/components/collection-types.ts +3 -4
  29. package/src/client/components/compose-types.ts +174 -0
  30. package/src/client/components/jant-collection-form.ts +667 -0
  31. package/src/client/components/jant-collection-sidebar.ts +805 -0
  32. package/src/client/components/jant-compose-dialog.ts +2161 -0
  33. package/src/client/components/jant-compose-editor.ts +1813 -0
  34. package/src/client/components/jant-compose-fullscreen.ts +283 -0
  35. package/src/client/components/jant-media-lightbox.ts +259 -0
  36. package/src/{ui → client}/components/jant-nav-manager.ts +97 -298
  37. package/src/{ui → client}/components/jant-post-form.ts +141 -12
  38. package/src/client/components/jant-post-menu.ts +1019 -0
  39. package/src/{ui → client}/components/jant-settings-avatar.ts +3 -3
  40. package/src/{ui → client}/components/jant-settings-general.ts +38 -4
  41. package/src/client/components/jant-text-preview.ts +232 -0
  42. package/src/{ui → client}/components/nav-manager-types.ts +6 -18
  43. package/src/{ui → client}/components/post-form-template.ts +137 -38
  44. package/src/{ui → client}/components/post-form-types.ts +15 -4
  45. package/src/client/compose-bridge.ts +583 -0
  46. package/src/{lib → client}/image-processor.ts +26 -8
  47. package/src/client/lazy-slugify.ts +51 -0
  48. package/src/client/media-metadata.ts +247 -0
  49. package/src/client/multipart-upload.ts +160 -0
  50. package/src/{lib → client}/nav-manager-bridge.ts +1 -1
  51. package/src/{lib → client}/post-form-bridge.ts +53 -2
  52. package/src/{lib → client}/settings-bridge.ts +3 -15
  53. package/src/client/thread-context.ts +140 -0
  54. package/src/client/tiptap/bubble-menu.ts +205 -0
  55. package/src/client/tiptap/create-editor.ts +86 -0
  56. package/src/client/tiptap/exitable-marks.ts +73 -0
  57. package/src/client/tiptap/extensions.ts +65 -0
  58. package/src/client/tiptap/image-node.ts +482 -0
  59. package/src/client/tiptap/link-toolbar.ts +371 -0
  60. package/src/client/tiptap/more-break.ts +50 -0
  61. package/src/client/tiptap/paste-image.ts +129 -0
  62. package/src/client/tiptap/slash-commands.ts +438 -0
  63. package/src/{lib → client}/toast.ts +101 -3
  64. package/src/client/types/sortablejs.d.ts +44 -0
  65. package/src/client/upload-with-metadata.ts +54 -0
  66. package/src/client/video-processor.ts +207 -0
  67. package/src/client.ts +27 -17
  68. package/src/db/__tests__/migrations.test.ts +118 -0
  69. package/src/db/index.ts +52 -0
  70. package/src/db/migrations/0000_baseline.sql +269 -0
  71. package/src/db/migrations/0001_fts_setup.sql +31 -0
  72. package/src/db/migrations/meta/0000_snapshot.json +703 -119
  73. package/src/db/migrations/meta/0001_snapshot.json +1337 -0
  74. package/src/db/migrations/meta/_journal.json +4 -39
  75. package/src/db/schema.ts +409 -140
  76. package/src/i18n/__tests__/detect.test.ts +115 -0
  77. package/src/i18n/context.tsx +2 -2
  78. package/src/i18n/detect.ts +85 -1
  79. package/src/i18n/i18n.ts +1 -1
  80. package/src/i18n/index.ts +2 -1
  81. package/src/i18n/locales/en.po +783 -1087
  82. package/src/i18n/locales/en.ts +1 -1
  83. package/src/i18n/locales/zh-Hans.po +867 -812
  84. package/src/i18n/locales/zh-Hans.ts +1 -1
  85. package/src/i18n/locales/zh-Hant.po +878 -823
  86. package/src/i18n/locales/zh-Hant.ts +1 -1
  87. package/src/i18n/middleware.ts +6 -0
  88. package/src/index.ts +5 -7
  89. package/src/lib/__tests__/blurhash-placeholder.test.ts +75 -0
  90. package/src/lib/__tests__/constants.test.ts +0 -1
  91. package/src/lib/__tests__/markdown-to-tiptap.test.ts +358 -0
  92. package/src/lib/__tests__/nanoid.test.ts +26 -0
  93. package/src/lib/__tests__/resolve-config.test.ts +2 -2
  94. package/src/lib/__tests__/schemas.test.ts +186 -65
  95. package/src/lib/__tests__/slug.test.ts +126 -0
  96. package/src/lib/__tests__/sse.test.ts +6 -6
  97. package/src/lib/__tests__/summary.test.ts +264 -0
  98. package/src/lib/__tests__/theme.test.ts +1 -1
  99. package/src/lib/__tests__/timeline.test.ts +33 -30
  100. package/src/lib/__tests__/tiptap-to-markdown.test.ts +346 -0
  101. package/src/lib/__tests__/url.test.ts +2 -2
  102. package/src/lib/__tests__/view.test.ts +140 -65
  103. package/src/lib/blurhash-placeholder.ts +102 -0
  104. package/src/lib/constants.ts +3 -1
  105. package/src/lib/emoji-catalog.ts +963 -0
  106. package/src/lib/errors.ts +11 -8
  107. package/src/lib/feed.ts +77 -31
  108. package/src/lib/html.ts +2 -1
  109. package/src/lib/icon-catalog.ts +5033 -1
  110. package/src/lib/icons.ts +3 -2
  111. package/src/lib/index.ts +0 -1
  112. package/src/lib/markdown-to-tiptap.ts +286 -0
  113. package/src/lib/media-helpers.ts +22 -12
  114. package/src/lib/nanoid.ts +29 -0
  115. package/src/lib/navigation.ts +1 -1
  116. package/src/lib/render.tsx +24 -5
  117. package/src/lib/resolve-config.ts +13 -2
  118. package/src/lib/schemas.ts +226 -58
  119. package/src/lib/search-snippet.ts +34 -0
  120. package/src/lib/slug.ts +96 -0
  121. package/src/lib/sse.ts +6 -6
  122. package/src/lib/storage.ts +115 -7
  123. package/src/lib/summary.ts +158 -0
  124. package/src/lib/theme.ts +11 -8
  125. package/src/lib/timeline.ts +76 -34
  126. package/src/lib/tiptap-render.ts +191 -0
  127. package/src/lib/tiptap-to-markdown.ts +305 -0
  128. package/src/lib/upload.ts +263 -14
  129. package/src/lib/url.ts +37 -22
  130. package/src/lib/view.ts +236 -55
  131. package/src/middleware/__tests__/auth.test.ts +191 -11
  132. package/src/middleware/__tests__/onboarding.test.ts +12 -10
  133. package/src/middleware/auth.ts +63 -9
  134. package/src/middleware/error-handler.ts +3 -3
  135. package/src/middleware/onboarding.ts +1 -1
  136. package/src/middleware/secure-headers.ts +40 -0
  137. package/src/preset.css +83 -2
  138. package/src/routes/__tests__/compose.test.ts +17 -24
  139. package/src/routes/api/__tests__/collections.test.ts +109 -61
  140. package/src/routes/api/__tests__/nav-items.test.ts +46 -29
  141. package/src/routes/api/__tests__/posts.test.ts +132 -68
  142. package/src/routes/api/__tests__/search.test.ts +15 -2
  143. package/src/routes/api/__tests__/upload-multipart.test.ts +534 -0
  144. package/src/routes/api/collections.ts +57 -31
  145. package/src/routes/api/custom-urls.ts +80 -0
  146. package/src/routes/api/export.ts +31 -0
  147. package/src/routes/api/nav-items.ts +23 -19
  148. package/src/routes/api/posts.ts +81 -62
  149. package/src/routes/api/search.ts +3 -4
  150. package/src/routes/api/upload-multipart.ts +245 -0
  151. package/src/routes/api/upload.ts +92 -24
  152. package/src/routes/auth/__tests__/setup.test.ts +20 -60
  153. package/src/routes/auth/reset.tsx +5 -4
  154. package/src/routes/auth/setup.tsx +39 -31
  155. package/src/routes/auth/signin.tsx +13 -14
  156. package/src/routes/compose.tsx +27 -63
  157. package/src/routes/dash/__tests__/settings-avatar.test.ts +44 -9
  158. package/src/routes/dash/custom-urls.tsx +414 -0
  159. package/src/routes/dash/settings.tsx +475 -99
  160. package/src/routes/feed/__tests__/rss.test.ts +22 -23
  161. package/src/routes/feed/rss.ts +6 -2
  162. package/src/routes/feed/sitemap.ts +2 -12
  163. package/src/routes/pages/__tests__/collections.test.ts +5 -6
  164. package/src/routes/pages/__tests__/featured.test.ts +36 -18
  165. package/src/routes/pages/archive.tsx +177 -37
  166. package/src/routes/pages/collection.tsx +43 -14
  167. package/src/routes/pages/collections.tsx +11 -2
  168. package/src/routes/pages/featured.tsx +27 -3
  169. package/src/routes/pages/home.tsx +15 -14
  170. package/src/routes/pages/latest.tsx +1 -11
  171. package/src/routes/pages/new.tsx +39 -0
  172. package/src/routes/pages/page.tsx +95 -49
  173. package/src/routes/pages/search.tsx +1 -1
  174. package/src/services/__tests__/api-token.test.ts +135 -0
  175. package/src/services/__tests__/collection.test.ts +275 -227
  176. package/src/services/__tests__/custom-url.test.ts +213 -0
  177. package/src/services/__tests__/media.test.ts +162 -22
  178. package/src/services/__tests__/navigation.test.ts +109 -68
  179. package/src/services/__tests__/post-timeline.test.ts +205 -32
  180. package/src/services/__tests__/post.test.ts +800 -230
  181. package/src/services/__tests__/search.test.ts +67 -10
  182. package/src/services/__tests__/settings.test.ts +3 -3
  183. package/src/services/api-token.ts +166 -0
  184. package/src/services/auth.ts +17 -2
  185. package/src/services/collection.ts +397 -131
  186. package/src/services/custom-url.ts +188 -0
  187. package/src/services/export.ts +802 -0
  188. package/src/services/index.ts +26 -19
  189. package/src/services/media.ts +100 -22
  190. package/src/services/navigation.ts +158 -47
  191. package/src/services/path.ts +339 -0
  192. package/src/services/post.ts +764 -172
  193. package/src/services/search.ts +161 -74
  194. package/src/services/settings.ts +6 -2
  195. package/src/styles/components.css +293 -62
  196. package/src/styles/tokens.css +93 -5
  197. package/src/styles/ui.css +4349 -766
  198. package/src/types/bindings.ts +8 -0
  199. package/src/types/config.ts +34 -4
  200. package/src/types/constants.ts +17 -2
  201. package/src/types/entities.ts +83 -37
  202. package/src/types/operations.ts +20 -27
  203. package/src/types/props.ts +52 -17
  204. package/src/types/views.ts +48 -24
  205. package/src/ui/color-themes.ts +133 -23
  206. package/src/ui/compose/ComposeDialog.tsx +255 -16
  207. package/src/ui/compose/ComposePrompt.tsx +1 -1
  208. package/src/ui/dash/CrudPageHeader.tsx +1 -1
  209. package/src/ui/dash/ListItemRow.tsx +1 -1
  210. package/src/ui/dash/StatusBadge.tsx +12 -2
  211. package/src/ui/dash/appearance/AdvancedContent.tsx +71 -59
  212. package/src/ui/dash/appearance/ColorThemeContent.tsx +48 -33
  213. package/src/ui/dash/appearance/FontThemeContent.tsx +65 -73
  214. package/src/ui/dash/appearance/NavigationContent.tsx +106 -135
  215. package/src/ui/dash/index.ts +0 -3
  216. package/src/ui/dash/settings/AccountContent.tsx +87 -146
  217. package/src/ui/dash/settings/AccountMenuContent.tsx +147 -0
  218. package/src/ui/dash/settings/ApiTokensContent.tsx +232 -0
  219. package/src/ui/dash/settings/AvatarContent.tsx +78 -0
  220. package/src/ui/dash/settings/GeneralContent.tsx +3 -62
  221. package/src/ui/dash/settings/SessionsContent.tsx +159 -0
  222. package/src/ui/dash/settings/SettingsRootContent.tsx +266 -0
  223. package/src/ui/feed/LinkCard.tsx +89 -40
  224. package/src/ui/feed/NoteCard.tsx +39 -25
  225. package/src/ui/feed/PostStatusBadges.tsx +67 -0
  226. package/src/ui/feed/QuoteCard.tsx +38 -23
  227. package/src/ui/feed/ThreadPreview.tsx +90 -26
  228. package/src/ui/feed/TimelineFeed.tsx +3 -2
  229. package/src/ui/feed/TimelineItem.tsx +15 -6
  230. package/src/ui/feed/__tests__/thread-preview.test.ts +107 -0
  231. package/src/ui/feed/thread-preview-state.ts +61 -0
  232. package/src/ui/font-themes.ts +2 -2
  233. package/src/ui/layouts/BaseLayout.tsx +2 -2
  234. package/src/ui/layouts/SiteLayout.tsx +116 -103
  235. package/src/ui/pages/ArchivePage.tsx +923 -95
  236. package/src/ui/pages/CollectionPage.tsx +6 -35
  237. package/src/ui/pages/CollectionsPage.tsx +2 -1
  238. package/src/ui/pages/ComposePage.tsx +54 -0
  239. package/src/ui/pages/FeaturedPage.tsx +2 -1
  240. package/src/ui/pages/HomePage.tsx +1 -1
  241. package/src/ui/pages/PostPage.tsx +30 -45
  242. package/src/ui/pages/SearchPage.tsx +182 -38
  243. package/src/ui/shared/AdminBreadcrumb.tsx +29 -0
  244. package/src/ui/shared/CollectionsSidebar.tsx +239 -4
  245. package/src/ui/shared/MediaGallery.tsx +475 -41
  246. package/src/ui/shared/PostFooter.tsx +204 -0
  247. package/src/ui/shared/StarRating.tsx +27 -0
  248. package/src/ui/shared/__tests__/format-chars.test.ts +35 -0
  249. package/src/ui/shared/index.ts +0 -1
  250. package/src/db/migrations/0000_square_wallflower.sql +0 -118
  251. package/src/db/migrations/0001_add_search_fts.sql +0 -34
  252. package/src/db/migrations/0002_add_media_attachments.sql +0 -3
  253. package/src/db/migrations/0003_add_navigation_links.sql +0 -8
  254. package/src/db/migrations/0004_add_storage_provider.sql +0 -3
  255. package/src/db/migrations/0005_v2_schema_migration.sql +0 -268
  256. package/src/db/migrations/0006_rename_slug_to_path.sql +0 -5
  257. package/src/db/migrations/0007_post_collections_m2m.sql +0 -94
  258. package/src/db/migrations/0008_add_collection_dividers.sql +0 -8
  259. package/src/db/migrations/0009_drop_collection_show_divider.sql +0 -2
  260. package/src/db/migrations/0010_add_performance_indexes.sql +0 -16
  261. package/src/db/migrations/0011_add_path_registry.sql +0 -23
  262. package/src/db/migrations/meta/0003_snapshot.json +0 -821
  263. package/src/lib/__tests__/sqid.test.ts +0 -65
  264. package/src/lib/collections-reorder.ts +0 -28
  265. package/src/lib/compose-bridge.ts +0 -280
  266. package/src/lib/media-upload.ts +0 -148
  267. package/src/lib/sqid.ts +0 -79
  268. package/src/routes/api/__tests__/pages.test.ts +0 -218
  269. package/src/routes/api/pages.ts +0 -73
  270. package/src/routes/dash/__tests__/pages.test.ts +0 -226
  271. package/src/routes/dash/appearance.tsx +0 -240
  272. package/src/routes/dash/collections.tsx +0 -211
  273. package/src/routes/dash/index.tsx +0 -103
  274. package/src/routes/dash/media.tsx +0 -132
  275. package/src/routes/dash/pages.tsx +0 -239
  276. package/src/routes/dash/posts.tsx +0 -334
  277. package/src/routes/dash/redirects.tsx +0 -257
  278. package/src/routes/pages/post.tsx +0 -59
  279. package/src/services/__tests__/page.test.ts +0 -298
  280. package/src/services/__tests__/path-registry.test.ts +0 -165
  281. package/src/services/__tests__/redirect.test.ts +0 -159
  282. package/src/services/page.ts +0 -203
  283. package/src/services/path-registry.ts +0 -160
  284. package/src/services/redirect.ts +0 -97
  285. package/src/types/sortablejs.d.ts +0 -29
  286. package/src/ui/components/__tests__/jant-compose-dialog.test.ts +0 -512
  287. package/src/ui/components/__tests__/jant-compose-editor.test.ts +0 -272
  288. package/src/ui/components/compose-types.ts +0 -75
  289. package/src/ui/components/jant-collection-form.ts +0 -512
  290. package/src/ui/components/jant-compose-dialog.ts +0 -495
  291. package/src/ui/components/jant-compose-editor.ts +0 -814
  292. package/src/ui/dash/PageForm.tsx +0 -185
  293. package/src/ui/dash/PostList.tsx +0 -95
  294. package/src/ui/dash/appearance/AppearanceNav.tsx +0 -60
  295. package/src/ui/dash/collections/CollectionForm.tsx +0 -166
  296. package/src/ui/dash/collections/CollectionsListContent.tsx +0 -146
  297. package/src/ui/dash/collections/IconPickerGrid.tsx +0 -50
  298. package/src/ui/dash/collections/ViewCollectionContent.tsx +0 -103
  299. package/src/ui/dash/media/MediaListContent.tsx +0 -201
  300. package/src/ui/dash/media/ViewMediaContent.tsx +0 -208
  301. package/src/ui/dash/pages/PagesContent.tsx +0 -74
  302. package/src/ui/dash/posts/PostForm.tsx +0 -248
  303. package/src/ui/dash/settings/SettingsNav.tsx +0 -52
  304. package/src/ui/layouts/DashLayout.tsx +0 -165
  305. package/src/ui/pages/SinglePage.tsx +0 -23
  306. package/src/ui/shared/ThreadView.tsx +0 -136
  307. /package/src/{ui → client}/components/settings-types.ts +0 -0
@@ -1,218 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createTestApp } from "../../../__tests__/helpers/app.js";
3
- import { pagesApiRoutes } from "../pages.js";
4
-
5
- describe("Pages API Routes", () => {
6
- describe("GET /api/pages", () => {
7
- it("returns empty list when no pages exist", async () => {
8
- const { app } = createTestApp();
9
- app.route("/api/pages", pagesApiRoutes);
10
-
11
- const res = await app.request("/api/pages");
12
- expect(res.status).toBe(200);
13
-
14
- const body = await res.json();
15
- expect(body.pages).toEqual([]);
16
- });
17
-
18
- it("returns pages list", async () => {
19
- const { app, services } = createTestApp();
20
- app.route("/api/pages", pagesApiRoutes);
21
-
22
- await services.pages.create({ slug: "about", title: "About" });
23
- await services.pages.create({ slug: "contact", title: "Contact" });
24
-
25
- const res = await app.request("/api/pages");
26
- const body = await res.json();
27
-
28
- expect(body.pages).toHaveLength(2);
29
- });
30
- });
31
-
32
- describe("GET /api/pages/:id", () => {
33
- it("returns a page by id", async () => {
34
- const { app, services } = createTestApp();
35
- app.route("/api/pages", pagesApiRoutes);
36
-
37
- const page = await services.pages.create({
38
- slug: "about",
39
- title: "About Us",
40
- });
41
-
42
- const res = await app.request(`/api/pages/${page.id}`);
43
- expect(res.status).toBe(200);
44
-
45
- const body = await res.json();
46
- expect(body.title).toBe("About Us");
47
- expect(body.slug).toBe("about");
48
- });
49
-
50
- it("returns 400 for invalid id", async () => {
51
- const { app } = createTestApp();
52
- app.route("/api/pages", pagesApiRoutes);
53
-
54
- const res = await app.request("/api/pages/abc");
55
- expect(res.status).toBe(400);
56
- });
57
-
58
- it("returns 404 for non-existent page", async () => {
59
- const { app } = createTestApp();
60
- app.route("/api/pages", pagesApiRoutes);
61
-
62
- const res = await app.request("/api/pages/9999");
63
- expect(res.status).toBe(404);
64
- });
65
- });
66
-
67
- describe("POST /api/pages", () => {
68
- it("returns 401 when not authenticated", async () => {
69
- const { app } = createTestApp({ authenticated: false });
70
- app.route("/api/pages", pagesApiRoutes);
71
-
72
- const res = await app.request("/api/pages", {
73
- method: "POST",
74
- headers: { "Content-Type": "application/json" },
75
- body: JSON.stringify({ slug: "about", title: "About" }),
76
- });
77
-
78
- expect(res.status).toBe(401);
79
- });
80
-
81
- it("creates a page when authenticated", async () => {
82
- const { app } = createTestApp({ authenticated: true });
83
- app.route("/api/pages", pagesApiRoutes);
84
-
85
- const res = await app.request("/api/pages", {
86
- method: "POST",
87
- headers: { "Content-Type": "application/json" },
88
- body: JSON.stringify({
89
- slug: "about",
90
- title: "About Us",
91
- body: "We are Jant.",
92
- status: "published",
93
- }),
94
- });
95
-
96
- expect(res.status).toBe(201);
97
- const body = await res.json();
98
- expect(body.slug).toBe("about");
99
- expect(body.title).toBe("About Us");
100
- });
101
-
102
- it("returns 400 for missing slug", async () => {
103
- const { app } = createTestApp({ authenticated: true });
104
- app.route("/api/pages", pagesApiRoutes);
105
-
106
- const res = await app.request("/api/pages", {
107
- method: "POST",
108
- headers: { "Content-Type": "application/json" },
109
- body: JSON.stringify({ title: "No Slug" }),
110
- });
111
-
112
- expect(res.status).toBe(400);
113
- });
114
- });
115
-
116
- describe("PUT /api/pages/:id", () => {
117
- it("returns 401 when not authenticated", async () => {
118
- const { app, services } = createTestApp({ authenticated: false });
119
- app.route("/api/pages", pagesApiRoutes);
120
-
121
- const page = await services.pages.create({
122
- slug: "about",
123
- title: "About",
124
- });
125
-
126
- const res = await app.request(`/api/pages/${page.id}`, {
127
- method: "PUT",
128
- headers: { "Content-Type": "application/json" },
129
- body: JSON.stringify({ title: "Updated" }),
130
- });
131
-
132
- expect(res.status).toBe(401);
133
- });
134
-
135
- it("updates a page when authenticated", async () => {
136
- const { app, services } = createTestApp({ authenticated: true });
137
- app.route("/api/pages", pagesApiRoutes);
138
-
139
- const page = await services.pages.create({
140
- slug: "about",
141
- title: "About",
142
- });
143
-
144
- const res = await app.request(`/api/pages/${page.id}`, {
145
- method: "PUT",
146
- headers: { "Content-Type": "application/json" },
147
- body: JSON.stringify({ title: "Updated About" }),
148
- });
149
-
150
- expect(res.status).toBe(200);
151
- const body = await res.json();
152
- expect(body.title).toBe("Updated About");
153
- });
154
-
155
- it("returns 404 for non-existent page", async () => {
156
- const { app } = createTestApp({ authenticated: true });
157
- app.route("/api/pages", pagesApiRoutes);
158
-
159
- const res = await app.request("/api/pages/9999", {
160
- method: "PUT",
161
- headers: { "Content-Type": "application/json" },
162
- body: JSON.stringify({ title: "test" }),
163
- });
164
-
165
- expect(res.status).toBe(404);
166
- });
167
- });
168
-
169
- describe("DELETE /api/pages/:id", () => {
170
- it("returns 401 when not authenticated", async () => {
171
- const { app, services } = createTestApp({ authenticated: false });
172
- app.route("/api/pages", pagesApiRoutes);
173
-
174
- const page = await services.pages.create({
175
- slug: "about",
176
- title: "About",
177
- });
178
-
179
- const res = await app.request(`/api/pages/${page.id}`, {
180
- method: "DELETE",
181
- });
182
-
183
- expect(res.status).toBe(401);
184
- });
185
-
186
- it("deletes a page when authenticated", async () => {
187
- const { app, services } = createTestApp({ authenticated: true });
188
- app.route("/api/pages", pagesApiRoutes);
189
-
190
- const page = await services.pages.create({
191
- slug: "about",
192
- title: "About",
193
- });
194
-
195
- const res = await app.request(`/api/pages/${page.id}`, {
196
- method: "DELETE",
197
- });
198
-
199
- expect(res.status).toBe(200);
200
- const body = await res.json();
201
- expect(body.success).toBe(true);
202
-
203
- const found = await services.pages.getById(page.id);
204
- expect(found).toBeNull();
205
- });
206
-
207
- it("returns 404 for non-existent page", async () => {
208
- const { app } = createTestApp({ authenticated: true });
209
- app.route("/api/pages", pagesApiRoutes);
210
-
211
- const res = await app.request("/api/pages/9999", {
212
- method: "DELETE",
213
- });
214
-
215
- expect(res.status).toBe(404);
216
- });
217
- });
218
- });
@@ -1,73 +0,0 @@
1
- /**
2
- * Pages API Routes
3
- */
4
-
5
- import { Hono } from "hono";
6
- import { z } from "zod";
7
- import type { Bindings } from "../../types.js";
8
- import type { AppVariables } from "../../types/app-context.js";
9
- import { requireAuthApi } from "../../middleware/auth.js";
10
- import {
11
- CreatePageSchema,
12
- StatusSchema,
13
- parseValidated,
14
- } from "../../lib/schemas.js";
15
- import { assertFound, parseIntParam, NotFoundError } from "../../lib/errors.js";
16
-
17
- type Env = { Bindings: Bindings; Variables: AppVariables };
18
-
19
- export const pagesApiRoutes = new Hono<Env>();
20
-
21
- // API update schema extends shared schema with nullable fields for explicit clearing
22
- const UpdatePageSchema = CreatePageSchema.partial().extend({
23
- title: z.string().nullable().optional(),
24
- body: z.string().nullable().optional(),
25
- status: StatusSchema.optional(),
26
- });
27
-
28
- // List pages
29
- pagesApiRoutes.get("/", async (c) => {
30
- const pages = await c.var.services.pages.list();
31
- return c.json({ pages });
32
- });
33
-
34
- // Get single page
35
- pagesApiRoutes.get("/:id", async (c) => {
36
- const id = parseIntParam(c.req.param("id"));
37
- const page = assertFound(await c.var.services.pages.getById(id), "Page");
38
- return c.json(page);
39
- });
40
-
41
- // Create page (requires auth)
42
- pagesApiRoutes.post("/", requireAuthApi(), async (c) => {
43
- const body = parseValidated(CreatePageSchema, await c.req.json());
44
-
45
- const page = await c.var.services.pages.create({
46
- slug: body.slug,
47
- title: body.title,
48
- body: body.body,
49
- status: body.status,
50
- });
51
-
52
- return c.json(page, 201);
53
- });
54
-
55
- // Update page (requires auth)
56
- pagesApiRoutes.put("/:id", requireAuthApi(), async (c) => {
57
- const id = parseIntParam(c.req.param("id"));
58
- const body = parseValidated(UpdatePageSchema, await c.req.json());
59
-
60
- const page = assertFound(await c.var.services.pages.update(id, body), "Page");
61
-
62
- return c.json(page);
63
- });
64
-
65
- // Delete page (requires auth)
66
- pagesApiRoutes.delete("/:id", requireAuthApi(), async (c) => {
67
- const id = parseIntParam(c.req.param("id"));
68
-
69
- const success = await c.var.services.pages.delete(id);
70
- if (!success) throw new NotFoundError("Page");
71
-
72
- return c.json({ success: true });
73
- });
@@ -1,226 +0,0 @@
1
- /**
2
- * Tests for the page/nav management logic used by dashboard pages routes.
3
- *
4
- * Note: Route handler tests that import JSX components with @lingui/react/macro
5
- * cannot run in vitest (requires SWC plugin). These tests verify the service
6
- * layer operations that the routes orchestrate.
7
- */
8
-
9
- import { describe, it, expect, beforeEach } from "vitest";
10
- import { createTestDatabase } from "../../../__tests__/helpers/db.js";
11
- import { createPageService } from "../../../services/page.js";
12
- import { createNavItemService } from "../../../services/navigation.js";
13
- import { createPathRegistryService } from "../../../services/path-registry.js";
14
- import type { Database } from "../../../db/index.js";
15
-
16
- describe("Dashboard Pages - Nav Management Logic", () => {
17
- let db: Database;
18
- let pageService: ReturnType<typeof createPageService>;
19
- let navItemService: ReturnType<typeof createNavItemService>;
20
-
21
- beforeEach(() => {
22
- const testDb = createTestDatabase();
23
- db = testDb.db as unknown as Database;
24
- pageService = createPageService(db, createPathRegistryService(db));
25
- navItemService = createNavItemService(db);
26
- });
27
-
28
- describe("add page to nav", () => {
29
- it("creates a page-type nav item for the page", async () => {
30
- const page = await pageService.create({
31
- slug: "about",
32
- title: "About Us",
33
- });
34
-
35
- // Simulate what the route handler does
36
- await navItemService.create({
37
- type: "page",
38
- label: page.title || page.slug,
39
- url: `/${page.slug}`,
40
- pageId: page.id,
41
- });
42
-
43
- const navItems = await navItemService.list();
44
- expect(navItems).toHaveLength(1);
45
- expect(navItems[0]?.type).toBe("page");
46
- expect(navItems[0]?.label).toBe("About Us");
47
- expect(navItems[0]?.url).toBe("/about");
48
- expect(navItems[0]?.pageId).toBe(page.id);
49
- });
50
-
51
- it("uses slug as label when page has no title", async () => {
52
- const page = await pageService.create({ slug: "about" });
53
-
54
- await navItemService.create({
55
- type: "page",
56
- label: page.title || page.slug,
57
- url: `/${page.slug}`,
58
- pageId: page.id,
59
- });
60
-
61
- const navItems = await navItemService.list();
62
- expect(navItems[0]?.label).toBe("about");
63
- });
64
-
65
- it("page appears in nav and not in listNotInNav after adding", async () => {
66
- const page = await pageService.create({
67
- slug: "about",
68
- title: "About",
69
- });
70
-
71
- // Before adding to nav
72
- let notInNav = await pageService.listNotInNav();
73
- expect(notInNav).toHaveLength(1);
74
-
75
- // Add to nav
76
- await navItemService.create({
77
- type: "page",
78
- label: page.title || page.slug,
79
- url: `/${page.slug}`,
80
- pageId: page.id,
81
- });
82
-
83
- // After adding to nav
84
- notInNav = await pageService.listNotInNav();
85
- expect(notInNav).toHaveLength(0);
86
-
87
- const navItems = await navItemService.list();
88
- expect(navItems).toHaveLength(1);
89
- });
90
- });
91
-
92
- describe("remove page from nav", () => {
93
- it("removes the nav item but keeps the page", async () => {
94
- const page = await pageService.create({
95
- slug: "about",
96
- title: "About",
97
- });
98
- await navItemService.create({
99
- type: "page",
100
- label: "About",
101
- url: "/about",
102
- pageId: page.id,
103
- });
104
-
105
- // Simulate what the route handler does: find and delete nav item by pageId
106
- const allNavItems = await navItemService.list();
107
- const found = allNavItems.find((item) => item.pageId === page.id);
108
- expect(found).toBeDefined();
109
- await navItemService.delete(found?.id as number);
110
-
111
- // Nav item should be gone
112
- const navItems = await navItemService.list();
113
- expect(navItems).toHaveLength(0);
114
-
115
- // Page should still exist
116
- const foundPage = await pageService.getById(page.id);
117
- expect(foundPage).not.toBeNull();
118
-
119
- // Page should appear in "not in nav" list
120
- const notInNav = await pageService.listNotInNav();
121
- expect(notInNav).toHaveLength(1);
122
- expect(notInNav[0]?.slug).toBe("about");
123
- });
124
- });
125
-
126
- describe("reorder nav items", () => {
127
- it("reorders nav items by position", async () => {
128
- const a = await navItemService.create({
129
- type: "link",
130
- label: "A",
131
- url: "/a",
132
- });
133
- const b = await navItemService.create({
134
- type: "link",
135
- label: "B",
136
- url: "/b",
137
- });
138
-
139
- // Reverse order
140
- await navItemService.reorder([b.id, a.id]);
141
-
142
- const items = await navItemService.list();
143
- expect(items[0]?.label).toBe("B");
144
- expect(items[1]?.label).toBe("A");
145
- });
146
- });
147
-
148
- describe("link CRUD", () => {
149
- it("creates a link nav item", async () => {
150
- await navItemService.create({
151
- type: "link",
152
- label: "Blog",
153
- url: "/blog",
154
- });
155
-
156
- const navItems = await navItemService.list();
157
- expect(navItems).toHaveLength(1);
158
- expect(navItems[0]?.type).toBe("link");
159
- expect(navItems[0]?.label).toBe("Blog");
160
- expect(navItems[0]?.url).toBe("/blog");
161
- });
162
-
163
- it("updates a link nav item", async () => {
164
- const item = await navItemService.create({
165
- type: "link",
166
- label: "Blog",
167
- url: "/blog",
168
- });
169
-
170
- await navItemService.update(item.id, {
171
- label: "Posts",
172
- url: "/posts",
173
- });
174
-
175
- const updated = await navItemService.getById(item.id);
176
- expect(updated?.label).toBe("Posts");
177
- expect(updated?.url).toBe("/posts");
178
- });
179
-
180
- it("deletes a link nav item", async () => {
181
- const item = await navItemService.create({
182
- type: "link",
183
- label: "Blog",
184
- url: "/blog",
185
- });
186
-
187
- await navItemService.delete(item.id);
188
-
189
- const found = await navItemService.getById(item.id);
190
- expect(found).toBeNull();
191
- });
192
- });
193
-
194
- describe("unified page listing", () => {
195
- it("separates pages into nav and non-nav groups", async () => {
196
- const aboutPage = await pageService.create({
197
- slug: "about",
198
- title: "About",
199
- });
200
- await pageService.create({ slug: "contact", title: "Contact" });
201
-
202
- // Add about to nav
203
- await navItemService.create({
204
- type: "page",
205
- label: "About",
206
- url: "/about",
207
- pageId: aboutPage.id,
208
- });
209
-
210
- // Also add a link nav item
211
- await navItemService.create({
212
- type: "link",
213
- label: "External",
214
- url: "https://example.com",
215
- });
216
-
217
- // Simulate the unified page view data fetch
218
- const navItems = await navItemService.list();
219
- const otherPages = await pageService.listNotInNav();
220
-
221
- expect(navItems).toHaveLength(2); // page + link
222
- expect(otherPages).toHaveLength(1); // only contact
223
- expect(otherPages[0]?.slug).toBe("contact");
224
- });
225
- });
226
- });