@btst/stack 1.0.0 → 1.1.0

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 (279) hide show
  1. package/README.md +237 -2
  2. package/dist/api/index.cjs +2 -2
  3. package/dist/api/index.d.cts +2 -2
  4. package/dist/api/index.d.mts +2 -2
  5. package/dist/api/index.d.ts +2 -2
  6. package/dist/api/index.mjs +2 -2
  7. package/dist/client/components/compose.cjs +68 -0
  8. package/dist/client/components/compose.mjs +65 -0
  9. package/dist/client/components/error-boundary.cjs +24 -0
  10. package/dist/client/components/error-boundary.mjs +22 -0
  11. package/dist/client/components/index.cjs +10 -0
  12. package/dist/client/components/index.d.cts +52 -0
  13. package/dist/client/components/index.d.mts +52 -0
  14. package/dist/client/components/index.d.ts +52 -0
  15. package/dist/client/components/index.mjs +2 -0
  16. package/dist/client/index.cjs +21 -16
  17. package/dist/client/index.d.cts +102 -14
  18. package/dist/client/index.d.mts +102 -14
  19. package/dist/client/index.d.ts +102 -14
  20. package/dist/client/index.mjs +19 -10
  21. package/dist/client/meta-utils.cjs +162 -0
  22. package/dist/client/meta-utils.mjs +160 -0
  23. package/dist/client/sitemap-utils.cjs +14 -0
  24. package/dist/client/sitemap-utils.mjs +12 -0
  25. package/dist/context/index.cjs +6 -51
  26. package/dist/context/index.d.cts +26 -26
  27. package/dist/context/index.d.mts +26 -26
  28. package/dist/context/index.d.ts +26 -26
  29. package/dist/context/index.mjs +1 -50
  30. package/dist/context/provider.cjs +51 -0
  31. package/dist/context/provider.mjs +46 -0
  32. package/dist/index.cjs +0 -2
  33. package/dist/index.d.cts +1 -1
  34. package/dist/index.d.mts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.mjs +0 -2
  37. package/dist/plugins/api/index.cjs +15 -0
  38. package/dist/plugins/api/index.d.cts +41 -0
  39. package/dist/plugins/api/index.d.mts +41 -0
  40. package/dist/plugins/api/index.d.ts +41 -0
  41. package/dist/plugins/api/index.mjs +9 -0
  42. package/dist/plugins/blog/api/index.cjs +11 -0
  43. package/dist/plugins/blog/api/index.d.cts +7 -0
  44. package/dist/plugins/blog/api/index.d.mts +7 -0
  45. package/dist/plugins/blog/api/index.d.ts +7 -0
  46. package/dist/plugins/blog/api/index.mjs +2 -0
  47. package/dist/plugins/blog/api/plugin.cjs +569 -0
  48. package/dist/plugins/blog/api/plugin.mjs +565 -0
  49. package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
  50. package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
  51. package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  52. package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
  53. package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
  54. package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
  55. package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
  56. package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
  57. package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
  58. package/dist/plugins/blog/client/components/index.cjs +17 -0
  59. package/dist/plugins/blog/client/components/index.d.cts +22 -0
  60. package/dist/plugins/blog/client/components/index.d.mts +22 -0
  61. package/dist/plugins/blog/client/components/index.d.ts +22 -0
  62. package/dist/plugins/blog/client/components/index.mjs +12 -0
  63. package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
  64. package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
  65. package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
  66. package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
  67. package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
  68. package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
  69. package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
  70. package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
  71. package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
  72. package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
  73. package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
  74. package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
  75. package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
  76. package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
  77. package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
  78. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
  79. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
  80. package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
  81. package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
  82. package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
  83. package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
  84. package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
  85. package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
  86. package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
  87. package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
  88. package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
  89. package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
  90. package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
  91. package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
  92. package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
  93. package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
  94. package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
  95. package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
  96. package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
  97. package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
  98. package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
  99. package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
  100. package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
  101. package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
  102. package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
  103. package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
  104. package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
  105. package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
  106. package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
  107. package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
  108. package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
  109. package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  110. package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
  111. package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
  112. package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
  113. package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
  114. package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
  115. package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
  116. package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
  117. package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
  118. package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
  119. package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
  120. package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
  121. package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
  122. package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
  123. package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
  124. package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
  125. package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
  126. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
  127. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
  128. package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
  129. package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
  130. package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
  131. package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
  132. package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
  133. package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
  134. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
  135. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
  136. package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
  137. package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
  138. package/dist/plugins/blog/client/hooks/index.cjs +17 -0
  139. package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
  140. package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
  141. package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
  142. package/dist/plugins/blog/client/hooks/index.mjs +1 -0
  143. package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
  144. package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
  145. package/dist/plugins/blog/client/index.cjs +7 -0
  146. package/dist/plugins/blog/client/index.d.cts +414 -0
  147. package/dist/plugins/blog/client/index.d.mts +414 -0
  148. package/dist/plugins/blog/client/index.d.ts +414 -0
  149. package/dist/plugins/blog/client/index.mjs +1 -0
  150. package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
  151. package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
  152. package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
  153. package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
  154. package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
  155. package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
  156. package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
  157. package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
  158. package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
  159. package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
  160. package/dist/plugins/blog/client/localization/index.cjs +17 -0
  161. package/dist/plugins/blog/client/localization/index.mjs +15 -0
  162. package/dist/plugins/blog/client/plugin.cjs +462 -0
  163. package/dist/plugins/blog/client/plugin.mjs +460 -0
  164. package/dist/plugins/blog/client.css +3 -0
  165. package/dist/plugins/blog/db.cjs +90 -0
  166. package/dist/plugins/blog/db.mjs +88 -0
  167. package/dist/plugins/blog/query-keys.cjs +181 -0
  168. package/dist/plugins/blog/query-keys.d.cts +530 -0
  169. package/dist/plugins/blog/query-keys.d.mts +530 -0
  170. package/dist/plugins/blog/query-keys.d.ts +530 -0
  171. package/dist/plugins/blog/query-keys.mjs +179 -0
  172. package/dist/plugins/blog/schemas.cjs +39 -0
  173. package/dist/plugins/blog/schemas.mjs +35 -0
  174. package/dist/plugins/blog/style.css +22 -0
  175. package/dist/plugins/blog/utils.cjs +97 -0
  176. package/dist/plugins/blog/utils.mjs +87 -0
  177. package/dist/plugins/client/index.cjs +15 -0
  178. package/dist/plugins/client/index.d.cts +57 -0
  179. package/dist/plugins/client/index.d.mts +57 -0
  180. package/dist/plugins/client/index.d.ts +57 -0
  181. package/dist/plugins/client/index.mjs +9 -0
  182. package/dist/{shared/stack.Br2KMECJ.cjs → plugins/utils.cjs} +1 -8
  183. package/dist/{shared/stack.CwGEQ10b.mjs → plugins/utils.mjs} +2 -8
  184. package/dist/shared/{stack.Dva9muUy.d.cts → stack.ByOugz9d.d.cts} +17 -23
  185. package/dist/shared/{stack.Dva9muUy.d.mts → stack.ByOugz9d.d.mts} +17 -23
  186. package/dist/shared/{stack.Dva9muUy.d.ts → stack.ByOugz9d.d.ts} +17 -23
  187. package/dist/shared/stack.Cr2JoQdo.d.cts +76 -0
  188. package/dist/shared/stack.Cr2JoQdo.d.mts +76 -0
  189. package/dist/shared/stack.Cr2JoQdo.d.ts +76 -0
  190. package/package.json +104 -16
  191. package/src/__tests__/plugins.test.tsx +539 -0
  192. package/src/__tests__/sitemap.test.ts +60 -0
  193. package/src/api/index.ts +73 -0
  194. package/src/client/components/compose.tsx +116 -0
  195. package/src/client/components/error-boundary.tsx +30 -0
  196. package/src/client/components/index.tsx +2 -0
  197. package/src/client/index.ts +107 -0
  198. package/src/client/meta-utils.ts +228 -0
  199. package/src/client/sitemap-utils.ts +46 -0
  200. package/src/context/index.ts +1 -0
  201. package/src/context/provider.tsx +157 -0
  202. package/src/index.ts +1 -0
  203. package/src/plugins/api/index.ts +51 -0
  204. package/src/plugins/blog/api/index.ts +2 -0
  205. package/src/plugins/blog/api/plugin.ts +759 -0
  206. package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
  207. package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  208. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
  209. package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
  210. package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
  211. package/src/plugins/blog/client/components/index.tsx +11 -0
  212. package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
  213. package/src/plugins/blog/client/components/loading/index.tsx +27 -0
  214. package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
  215. package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
  216. package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
  217. package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
  218. package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
  219. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
  220. package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
  221. package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
  222. package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
  223. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
  224. package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
  225. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
  226. package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
  227. package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
  228. package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
  229. package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
  230. package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
  231. package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
  232. package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
  233. package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
  234. package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
  235. package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  236. package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
  237. package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
  238. package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
  239. package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
  240. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
  241. package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
  242. package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
  243. package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
  244. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
  245. package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
  246. package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
  247. package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
  248. package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
  249. package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
  250. package/src/plugins/blog/client/hooks/index.tsx +1 -0
  251. package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
  252. package/src/plugins/blog/client/index.ts +9 -0
  253. package/src/plugins/blog/client/localization/blog-card.ts +3 -0
  254. package/src/plugins/blog/client/localization/blog-common.ts +7 -0
  255. package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
  256. package/src/plugins/blog/client/localization/blog-list.ts +14 -0
  257. package/src/plugins/blog/client/localization/blog-post.ts +9 -0
  258. package/src/plugins/blog/client/localization/index.ts +15 -0
  259. package/src/plugins/blog/client/overrides.ts +123 -0
  260. package/src/plugins/blog/client/plugin.tsx +672 -0
  261. package/src/plugins/blog/client.css +3 -0
  262. package/src/plugins/blog/db.ts +90 -0
  263. package/src/plugins/blog/query-keys.ts +267 -0
  264. package/src/plugins/blog/schemas.ts +39 -0
  265. package/src/plugins/blog/style.css +22 -0
  266. package/src/plugins/blog/types.ts +37 -0
  267. package/src/plugins/blog/utils.ts +144 -0
  268. package/src/plugins/client/index.ts +53 -0
  269. package/src/plugins/index.ts +0 -0
  270. package/src/plugins/utils.ts +35 -0
  271. package/src/types.ts +209 -0
  272. package/dist/plugins/index.cjs +0 -16
  273. package/dist/plugins/index.d.cts +0 -64
  274. package/dist/plugins/index.d.mts +0 -64
  275. package/dist/plugins/index.d.ts +0 -64
  276. package/dist/plugins/index.mjs +0 -11
  277. package/dist/shared/stack.DvFqFlOV.d.cts +0 -22
  278. package/dist/shared/stack.DvFqFlOV.d.mts +0 -22
  279. package/dist/shared/stack.DvFqFlOV.d.ts +0 -22
@@ -0,0 +1,569 @@
1
+ 'use strict';
2
+
3
+ const api = require('@btst/stack/plugins/api');
4
+ const zod = require('zod');
5
+ const db = require('../db.cjs');
6
+ const utils = require('../utils.cjs');
7
+ const schemas = require('../schemas.cjs');
8
+
9
+ const PostListQuerySchema = zod.z.object({
10
+ slug: zod.z.string().optional(),
11
+ tagSlug: zod.z.string().optional(),
12
+ offset: zod.z.coerce.number().int().min(0).optional(),
13
+ limit: zod.z.coerce.number().int().min(1).max(100).optional(),
14
+ query: zod.z.string().optional(),
15
+ published: zod.z.string().optional().transform((val) => {
16
+ if (val === void 0) return void 0;
17
+ if (val === "true") return true;
18
+ if (val === "false") return false;
19
+ return void 0;
20
+ })
21
+ });
22
+ const NextPreviousPostsQuerySchema = zod.z.object({
23
+ date: zod.z.coerce.date()
24
+ });
25
+ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
26
+ name: "blog",
27
+ dbPlugin: db.blogSchema,
28
+ routes: (adapter) => {
29
+ const createTagCache = () => {
30
+ let cache = null;
31
+ return {
32
+ getAllTags: async () => {
33
+ if (!cache) {
34
+ cache = await adapter.findMany({
35
+ model: "tag"
36
+ });
37
+ }
38
+ return cache;
39
+ },
40
+ invalidate: () => {
41
+ cache = null;
42
+ },
43
+ addTag: (tag) => {
44
+ if (cache) {
45
+ cache.push(tag);
46
+ }
47
+ }
48
+ };
49
+ };
50
+ const createPostTagCache = () => {
51
+ let cache = null;
52
+ const getAllPostTags = async () => {
53
+ if (!cache) {
54
+ cache = await adapter.findMany({
55
+ model: "postTag"
56
+ });
57
+ }
58
+ return cache;
59
+ };
60
+ return {
61
+ getAllPostTags,
62
+ invalidate: () => {
63
+ cache = null;
64
+ },
65
+ getByTagId: async (tagId) => {
66
+ const allPostTags = await getAllPostTags();
67
+ return allPostTags.filter((pt) => pt.tagId === tagId);
68
+ },
69
+ getByPostId: async (postId) => {
70
+ const allPostTags = await getAllPostTags();
71
+ return allPostTags.filter((pt) => pt.postId === postId);
72
+ }
73
+ };
74
+ };
75
+ const findOrCreateTags = async (tagInputs, tagCache) => {
76
+ if (tagInputs.length === 0) return [];
77
+ const normalizeTagName = (name) => {
78
+ return name.trim();
79
+ };
80
+ const tagsWithIds = [];
81
+ const tagsToFindOrCreate = [];
82
+ for (const tagInput of tagInputs) {
83
+ if ("id" in tagInput && tagInput.id) {
84
+ tagsWithIds.push({
85
+ id: tagInput.id,
86
+ name: normalizeTagName(tagInput.name),
87
+ slug: tagInput.slug,
88
+ createdAt: /* @__PURE__ */ new Date(),
89
+ updatedAt: /* @__PURE__ */ new Date()
90
+ });
91
+ } else {
92
+ tagsToFindOrCreate.push({ name: normalizeTagName(tagInput.name) });
93
+ }
94
+ }
95
+ if (tagsToFindOrCreate.length === 0) {
96
+ return tagsWithIds;
97
+ }
98
+ const allTags = await tagCache.getAllTags();
99
+ const tagMapBySlug = /* @__PURE__ */ new Map();
100
+ for (const tag of allTags) {
101
+ tagMapBySlug.set(tag.slug, tag);
102
+ }
103
+ const tagSlugs = tagsToFindOrCreate.map((tag) => utils.slugify(tag.name));
104
+ const foundTags = [];
105
+ for (const slug of tagSlugs) {
106
+ const tag = tagMapBySlug.get(slug);
107
+ if (tag) {
108
+ foundTags.push(tag);
109
+ }
110
+ }
111
+ const existingSlugs = /* @__PURE__ */ new Set([
112
+ ...tagsWithIds.map((tag) => tag.slug),
113
+ ...foundTags.map((tag) => tag.slug)
114
+ ]);
115
+ const tagsToCreate = tagsToFindOrCreate.filter(
116
+ (tag) => !existingSlugs.has(utils.slugify(tag.name))
117
+ );
118
+ const createdTags = [];
119
+ for (const tag of tagsToCreate) {
120
+ const normalizedName = normalizeTagName(tag.name);
121
+ const newTag = await adapter.create({
122
+ model: "tag",
123
+ data: {
124
+ name: normalizedName,
125
+ slug: utils.slugify(normalizedName),
126
+ createdAt: /* @__PURE__ */ new Date(),
127
+ updatedAt: /* @__PURE__ */ new Date()
128
+ }
129
+ });
130
+ createdTags.push(newTag);
131
+ tagCache.addTag(newTag);
132
+ }
133
+ return [...tagsWithIds, ...foundTags, ...createdTags];
134
+ };
135
+ const loadTagsForPosts = async (postIds, tagCache, postTagCache) => {
136
+ if (postIds.length === 0) return /* @__PURE__ */ new Map();
137
+ const allPostTags = await postTagCache.getAllPostTags();
138
+ const relevantPostTags = allPostTags.filter(
139
+ (pt) => postIds.includes(pt.postId)
140
+ );
141
+ const tagIds = [...new Set(relevantPostTags.map((pt) => pt.tagId))];
142
+ if (tagIds.length === 0) return /* @__PURE__ */ new Map();
143
+ const allTags = await tagCache.getAllTags();
144
+ const tagMap = /* @__PURE__ */ new Map();
145
+ for (const tag of allTags) {
146
+ tagMap.set(tag.id, tag);
147
+ }
148
+ const postTagsMap = /* @__PURE__ */ new Map();
149
+ for (const postTag of relevantPostTags) {
150
+ const tag = tagMap.get(postTag.tagId);
151
+ if (tag) {
152
+ const existing = postTagsMap.get(postTag.postId) || [];
153
+ postTagsMap.set(postTag.postId, [...existing, { ...tag }]);
154
+ }
155
+ }
156
+ return postTagsMap;
157
+ };
158
+ const listPosts = api.createEndpoint(
159
+ "/posts",
160
+ {
161
+ method: "GET",
162
+ query: PostListQuerySchema
163
+ },
164
+ async (ctx) => {
165
+ const { query } = ctx;
166
+ const context = { query };
167
+ const tagCache = createTagCache();
168
+ const postTagCache = createPostTagCache();
169
+ try {
170
+ if (hooks?.onBeforeListPosts) {
171
+ const canList = await hooks.onBeforeListPosts(query, context);
172
+ if (!canList) {
173
+ throw ctx.error(403, {
174
+ message: "Unauthorized: Cannot list posts"
175
+ });
176
+ }
177
+ }
178
+ let tagFilterPostIds = null;
179
+ if (query.tagSlug) {
180
+ const allTags = await tagCache.getAllTags();
181
+ const tag = allTags.find((t) => t.slug === query.tagSlug);
182
+ if (!tag) {
183
+ return [];
184
+ }
185
+ const postTags = await postTagCache.getByTagId(tag.id);
186
+ tagFilterPostIds = new Set(postTags.map((pt) => pt.postId));
187
+ if (tagFilterPostIds.size === 0) {
188
+ return [];
189
+ }
190
+ }
191
+ const whereConditions = [];
192
+ if (query.published !== void 0) {
193
+ whereConditions.push({
194
+ field: "published",
195
+ value: query.published,
196
+ operator: "eq"
197
+ });
198
+ }
199
+ if (query.slug) {
200
+ whereConditions.push({
201
+ field: "slug",
202
+ value: query.slug,
203
+ operator: "eq"
204
+ });
205
+ }
206
+ const posts = await adapter.findMany({
207
+ model: "post",
208
+ limit: query.query || query.tagSlug ? void 0 : query.limit ?? 10,
209
+ offset: query.query || query.tagSlug ? void 0 : query.offset ?? 0,
210
+ where: whereConditions,
211
+ sortBy: {
212
+ field: "createdAt",
213
+ direction: "desc"
214
+ }
215
+ });
216
+ const postTagsMap = await loadTagsForPosts(
217
+ posts.map((post) => post.id),
218
+ tagCache,
219
+ postTagCache
220
+ );
221
+ let result = posts.map((post) => ({
222
+ ...post,
223
+ tags: postTagsMap.get(post.id) || []
224
+ }));
225
+ if (tagFilterPostIds) {
226
+ result = result.filter((post) => tagFilterPostIds.has(post.id));
227
+ }
228
+ if (query.query) {
229
+ const searchLower = query.query.toLowerCase();
230
+ result = result.filter((post) => {
231
+ const titleMatch = post.title?.toLowerCase().includes(searchLower);
232
+ const contentMatch = post.content?.toLowerCase().includes(searchLower);
233
+ const excerptMatch = post.excerpt?.toLowerCase().includes(searchLower);
234
+ return titleMatch || contentMatch || excerptMatch;
235
+ });
236
+ }
237
+ if (query.tagSlug || query.query) {
238
+ const offset = query.offset ?? 0;
239
+ const limit = query.limit ?? 10;
240
+ result = result.slice(offset, offset + limit);
241
+ }
242
+ if (hooks?.onPostsRead) {
243
+ await hooks.onPostsRead(result, query, context);
244
+ }
245
+ return result;
246
+ } catch (error) {
247
+ if (hooks?.onListPostsError) {
248
+ await hooks.onListPostsError(error, context);
249
+ }
250
+ throw error;
251
+ }
252
+ }
253
+ );
254
+ const createPost = api.createEndpoint(
255
+ "/posts",
256
+ {
257
+ method: "POST",
258
+ body: schemas.createPostSchema
259
+ },
260
+ async (ctx) => {
261
+ const context = { body: ctx.body };
262
+ const tagCache = createTagCache();
263
+ try {
264
+ if (hooks?.onBeforeCreatePost) {
265
+ const canCreate = await hooks.onBeforeCreatePost(
266
+ ctx.body,
267
+ context
268
+ );
269
+ if (!canCreate) {
270
+ throw ctx.error(403, {
271
+ message: "Unauthorized: Cannot create post"
272
+ });
273
+ }
274
+ }
275
+ const { tags, ...postData } = ctx.body;
276
+ const tagNames = tags || [];
277
+ const newPost = await adapter.create({
278
+ model: "post",
279
+ data: {
280
+ ...postData,
281
+ slug: postData.slug ? postData.slug : utils.slugify(postData.title),
282
+ tags: [],
283
+ createdAt: /* @__PURE__ */ new Date(),
284
+ updatedAt: /* @__PURE__ */ new Date()
285
+ }
286
+ });
287
+ if (tagNames.length > 0) {
288
+ const createdTags = await findOrCreateTags(tagNames, tagCache);
289
+ await adapter.transaction(async (tx) => {
290
+ for (const tag of createdTags) {
291
+ await tx.create({
292
+ model: "postTag",
293
+ data: {
294
+ postId: newPost.id,
295
+ tagId: tag.id
296
+ }
297
+ });
298
+ }
299
+ });
300
+ newPost.tags = createdTags.map((tag) => ({ ...tag }));
301
+ } else {
302
+ newPost.tags = [];
303
+ }
304
+ if (hooks?.onPostCreated) {
305
+ await hooks.onPostCreated(newPost, context);
306
+ }
307
+ return newPost;
308
+ } catch (error) {
309
+ if (hooks?.onCreatePostError) {
310
+ await hooks.onCreatePostError(error, context);
311
+ }
312
+ throw error;
313
+ }
314
+ }
315
+ );
316
+ const updatePost = api.createEndpoint(
317
+ "/posts/:id",
318
+ {
319
+ method: "PUT",
320
+ body: schemas.updatePostSchema
321
+ },
322
+ async (ctx) => {
323
+ const context = {
324
+ body: ctx.body,
325
+ params: ctx.params
326
+ };
327
+ const tagCache = createTagCache();
328
+ try {
329
+ if (hooks?.onBeforeUpdatePost) {
330
+ const canUpdate = await hooks.onBeforeUpdatePost(
331
+ ctx.params.id,
332
+ ctx.body,
333
+ context
334
+ );
335
+ if (!canUpdate) {
336
+ throw ctx.error(403, {
337
+ message: "Unauthorized: Cannot update post"
338
+ });
339
+ }
340
+ }
341
+ const { tags, ...postData } = ctx.body;
342
+ const tagNames = tags || [];
343
+ const updated = await adapter.transaction(async (tx) => {
344
+ const existingPostTags = await tx.findMany({
345
+ model: "postTag",
346
+ where: [
347
+ {
348
+ field: "postId",
349
+ value: ctx.params.id,
350
+ operator: "eq"
351
+ }
352
+ ]
353
+ });
354
+ const updatedPost = await tx.update({
355
+ model: "post",
356
+ where: [{ field: "id", value: ctx.params.id }],
357
+ update: {
358
+ ...postData,
359
+ updatedAt: /* @__PURE__ */ new Date()
360
+ }
361
+ });
362
+ if (!updatedPost) {
363
+ throw ctx.error(404, {
364
+ message: "Post not found"
365
+ });
366
+ }
367
+ for (const postTag of existingPostTags) {
368
+ await tx.delete({
369
+ model: "postTag",
370
+ where: [
371
+ {
372
+ field: "postId",
373
+ value: postTag.postId,
374
+ operator: "eq"
375
+ },
376
+ {
377
+ field: "tagId",
378
+ value: postTag.tagId,
379
+ operator: "eq"
380
+ }
381
+ ]
382
+ });
383
+ }
384
+ if (tagNames.length > 0) {
385
+ const createdTags = await findOrCreateTags(tagNames, tagCache);
386
+ for (const tag of createdTags) {
387
+ await tx.create({
388
+ model: "postTag",
389
+ data: {
390
+ postId: ctx.params.id,
391
+ tagId: tag.id
392
+ }
393
+ });
394
+ }
395
+ updatedPost.tags = createdTags.map((tag) => ({ ...tag }));
396
+ } else {
397
+ updatedPost.tags = [];
398
+ }
399
+ return updatedPost;
400
+ });
401
+ if (hooks?.onPostUpdated) {
402
+ await hooks.onPostUpdated(updated, context);
403
+ }
404
+ return updated;
405
+ } catch (error) {
406
+ if (hooks?.onUpdatePostError) {
407
+ await hooks.onUpdatePostError(error, context);
408
+ }
409
+ throw error;
410
+ }
411
+ }
412
+ );
413
+ const deletePost = api.createEndpoint(
414
+ "/posts/:id",
415
+ {
416
+ method: "DELETE"
417
+ },
418
+ async (ctx) => {
419
+ const context = { params: ctx.params };
420
+ try {
421
+ if (hooks?.onBeforeDeletePost) {
422
+ const canDelete = await hooks.onBeforeDeletePost(
423
+ ctx.params.id,
424
+ context
425
+ );
426
+ if (!canDelete) {
427
+ throw ctx.error(403, {
428
+ message: "Unauthorized: Cannot delete post"
429
+ });
430
+ }
431
+ }
432
+ await adapter.transaction(async (tx) => {
433
+ await tx.delete({
434
+ model: "postTag",
435
+ where: [{ field: "postId", value: ctx.params.id }]
436
+ });
437
+ await tx.delete({
438
+ model: "post",
439
+ where: [{ field: "id", value: ctx.params.id }]
440
+ });
441
+ });
442
+ if (hooks?.onPostDeleted) {
443
+ await hooks.onPostDeleted(ctx.params.id, context);
444
+ }
445
+ return { success: true };
446
+ } catch (error) {
447
+ if (hooks?.onDeletePostError) {
448
+ await hooks.onDeletePostError(error, context);
449
+ }
450
+ throw error;
451
+ }
452
+ }
453
+ );
454
+ const getNextPreviousPosts = api.createEndpoint(
455
+ "/posts/next-previous",
456
+ {
457
+ method: "GET",
458
+ query: NextPreviousPostsQuerySchema
459
+ },
460
+ async (ctx) => {
461
+ const { query } = ctx;
462
+ const context = { query };
463
+ const tagCache = createTagCache();
464
+ const postTagCache = createPostTagCache();
465
+ try {
466
+ if (hooks?.onBeforeListPosts) {
467
+ const canList = await hooks.onBeforeListPosts(
468
+ { published: true },
469
+ context
470
+ );
471
+ if (!canList) {
472
+ throw ctx.error(403, {
473
+ message: "Unauthorized: Cannot list posts"
474
+ });
475
+ }
476
+ }
477
+ const date = query.date;
478
+ const previousPost = await adapter.findMany({
479
+ model: "post",
480
+ limit: 1,
481
+ where: [
482
+ {
483
+ field: "createdAt",
484
+ value: date,
485
+ operator: "lt"
486
+ },
487
+ {
488
+ field: "published",
489
+ value: true,
490
+ operator: "eq"
491
+ }
492
+ ],
493
+ sortBy: {
494
+ field: "createdAt",
495
+ direction: "desc"
496
+ }
497
+ });
498
+ const nextPost = await adapter.findMany({
499
+ model: "post",
500
+ limit: 1,
501
+ where: [
502
+ {
503
+ field: "createdAt",
504
+ value: date,
505
+ operator: "gt"
506
+ },
507
+ {
508
+ field: "published",
509
+ value: true,
510
+ operator: "eq"
511
+ }
512
+ ],
513
+ sortBy: {
514
+ field: "createdAt",
515
+ direction: "asc"
516
+ }
517
+ });
518
+ const postIds = [
519
+ ...previousPost?.[0] ? [previousPost[0].id] : [],
520
+ ...nextPost?.[0] ? [nextPost[0].id] : []
521
+ ];
522
+ const postTagsMap = await loadTagsForPosts(
523
+ postIds,
524
+ tagCache,
525
+ postTagCache
526
+ );
527
+ return {
528
+ previous: previousPost?.[0] ? {
529
+ ...previousPost[0],
530
+ tags: postTagsMap.get(previousPost[0].id) || []
531
+ } : null,
532
+ next: nextPost?.[0] ? {
533
+ ...nextPost[0],
534
+ tags: postTagsMap.get(nextPost[0].id) || []
535
+ } : null
536
+ };
537
+ } catch (error) {
538
+ if (hooks?.onListPostsError) {
539
+ await hooks.onListPostsError(error, context);
540
+ }
541
+ throw error;
542
+ }
543
+ }
544
+ );
545
+ const listTags = api.createEndpoint(
546
+ "/tags",
547
+ {
548
+ method: "GET"
549
+ },
550
+ async () => {
551
+ return await adapter.findMany({
552
+ model: "tag"
553
+ });
554
+ }
555
+ );
556
+ return {
557
+ listPosts,
558
+ createPost,
559
+ updatePost,
560
+ deletePost,
561
+ getNextPreviousPosts,
562
+ listTags
563
+ };
564
+ }
565
+ });
566
+
567
+ exports.NextPreviousPostsQuerySchema = NextPreviousPostsQuerySchema;
568
+ exports.PostListQuerySchema = PostListQuerySchema;
569
+ exports.blogBackendPlugin = blogBackendPlugin;