ocean-brain 0.3.4 → 0.4.1

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 (255) hide show
  1. package/dist/index.js +5 -0
  2. package/package.json +6 -5
  3. package/scripts/postinstall-prisma.mjs +3 -2
  4. package/server/client/dist/assets/Calendar-D4KkvcU8.js +25 -0
  5. package/server/client/dist/assets/Callout-CaSzc-1y.js +1 -0
  6. package/server/client/dist/assets/Graph-C8pBdJzV.js +1 -0
  7. package/server/client/dist/assets/{Image-ZxF459sC.js → Image-DlJ4MDo3.js} +1 -1
  8. package/server/client/dist/assets/{Image.es-BQfyKc2-.js → Image.es-C8g5NwNt.js} +1 -1
  9. package/server/client/dist/assets/Note-ODOESft3.js +21 -0
  10. package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
  11. package/server/client/dist/assets/Plus.es-Bkl1ZXE0.js +1 -0
  12. package/server/client/dist/assets/Reminders-FrTR9ny8.js +1 -0
  13. package/server/client/dist/assets/Search-BPf2LO1Y.js +1 -0
  14. package/server/client/dist/assets/{SurfaceCard-nZNf9lyE.js → SurfaceCard-WSk7zWeM.js} +1 -1
  15. package/server/client/dist/assets/Tag-uArdLjo1.js +1 -0
  16. package/server/client/dist/assets/TagNotes-DDvXHB9I.js +1 -0
  17. package/server/client/dist/assets/{Trash.es-BGoN5q9L.js → Trash.es-DumPfiUz.js} +1 -1
  18. package/server/client/dist/assets/ViewNotes-BVu9lEs-.js +1 -0
  19. package/server/client/dist/assets/Views-D4LGh30P.js +1 -0
  20. package/server/client/dist/assets/{image.api-0mMkElZk.js → image.api-D3Ei3fbf.js} +1 -1
  21. package/server/client/dist/assets/index-46ZUXbW5.js +232 -0
  22. package/server/client/dist/assets/index-C7YGbIuA.js +1 -0
  23. package/server/client/dist/assets/{index-DL7pUwq2.css → index-D2gRB1ig.css} +1 -1
  24. package/server/client/dist/assets/manage-image-DiuREdeE.js +1 -0
  25. package/server/client/dist/assets/manage-image-detail-D8Eorq3u.js +1 -0
  26. package/server/client/dist/assets/mcp-CvCGaYSz.js +1 -0
  27. package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
  28. package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-B0t5F04o.js} +5 -5
  29. package/server/client/dist/assets/{placeholder-i-uNe71U.js → placeholder-znZDfNAE.js} +2 -2
  30. package/server/client/dist/assets/trash-R0sww0ur.js +1 -0
  31. package/server/client/dist/assets/{useReminderMutate-tNMwwRnx.js → useReminderMutate-Bkj-oIm4.js} +2 -2
  32. package/server/client/dist/assets/view-dashboard-ClpF_qLM.js +90 -0
  33. package/server/client/dist/index.html +4 -4
  34. package/server/dist/app.js +8 -84
  35. package/server/dist/app.js.map +1 -1
  36. package/server/dist/features/auth/http/api.js +40 -0
  37. package/server/dist/features/auth/http/api.js.map +1 -0
  38. package/server/dist/features/auth/http/login-page.js +28 -0
  39. package/server/dist/features/auth/http/login-page.js.map +1 -0
  40. package/server/dist/features/auth/http/pages.js +50 -0
  41. package/server/dist/features/auth/http/pages.js.map +1 -0
  42. package/server/dist/features/auth/service.js +63 -0
  43. package/server/dist/features/auth/service.js.map +1 -0
  44. package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
  45. package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
  46. package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
  47. package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
  48. package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
  49. package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
  50. package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
  51. package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
  52. package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
  53. package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
  54. package/server/dist/features/cache/graphql/index.js +21 -0
  55. package/server/dist/features/cache/graphql/index.js.map +1 -0
  56. package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
  57. package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
  58. package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
  59. package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
  60. package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
  61. package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
  62. package/server/dist/features/image/graphql/image.resolver.js +12 -0
  63. package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
  64. package/server/dist/features/image/graphql/image.type-defs.js +31 -0
  65. package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
  66. package/server/dist/features/image/graphql/index.js +16 -0
  67. package/server/dist/features/image/graphql/index.js.map +1 -0
  68. package/server/dist/features/image/http/upload.js +48 -0
  69. package/server/dist/features/image/http/upload.js.map +1 -0
  70. package/server/dist/features/image/services/delete.js +52 -0
  71. package/server/dist/features/image/services/delete.js.map +1 -0
  72. package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
  73. package/server/dist/features/image/services/remote-fetch.js.map +1 -0
  74. package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
  75. package/server/dist/features/image/services/upload.js.map +1 -0
  76. package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
  77. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
  78. package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
  79. package/server/dist/features/mcp-admin/service.js.map +1 -0
  80. package/server/dist/features/note/graphql/index.js +8 -0
  81. package/server/dist/features/note/graphql/index.js.map +1 -0
  82. package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
  83. package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
  84. package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
  85. package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
  86. package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
  87. package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
  88. package/server/dist/features/note/graphql/note.query.resolver.js +385 -0
  89. package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
  90. package/server/dist/features/note/graphql/note.resolver.js +14 -0
  91. package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
  92. package/server/dist/features/note/graphql/note.type-defs.js +187 -0
  93. package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
  94. package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
  95. package/server/dist/features/note/http/mcp.js.map +1 -0
  96. package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
  97. package/server/dist/features/note/services/authoring.js.map +1 -0
  98. package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
  99. package/server/dist/features/note/services/cleanup.js.map +1 -0
  100. package/server/dist/features/note/services/search-backfill.js +84 -0
  101. package/server/dist/features/note/services/search-backfill.js.map +1 -0
  102. package/server/dist/features/note/services/search.js +197 -0
  103. package/server/dist/features/note/services/search.js.map +1 -0
  104. package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
  105. package/server/dist/features/note/services/snapshot.js.map +1 -0
  106. package/server/dist/{modules/note-tag-filter.js → features/note/services/tag-filter.js} +2 -6
  107. package/server/dist/features/note/services/tag-filter.js.map +1 -0
  108. package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +64 -34
  109. package/server/dist/features/note/services/trash.js.map +1 -0
  110. package/server/dist/features/placeholder/graphql/index.js +27 -0
  111. package/server/dist/features/placeholder/graphql/index.js.map +1 -0
  112. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
  113. package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
  114. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
  115. package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
  116. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
  117. package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
  118. package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
  119. package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
  120. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
  121. package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
  122. package/server/dist/features/reminder/graphql/index.js +27 -0
  123. package/server/dist/features/reminder/graphql/index.js.map +1 -0
  124. package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
  125. package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
  126. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
  127. package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
  128. package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
  129. package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
  130. package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
  131. package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
  132. package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
  133. package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
  134. package/server/dist/features/tag/graphql/index.js +14 -0
  135. package/server/dist/features/tag/graphql/index.js.map +1 -0
  136. package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
  137. package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
  138. package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
  139. package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
  140. package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
  141. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
  142. package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
  143. package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
  144. package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
  145. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
  146. package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
  147. package/server/dist/features/tag/http/mcp.js.map +1 -0
  148. package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
  149. package/server/dist/features/tag/services/organization.js.map +1 -0
  150. package/server/dist/features/view/graphql/index.js +11 -0
  151. package/server/dist/features/view/graphql/index.js.map +1 -0
  152. package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
  153. package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
  154. package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
  155. package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
  156. package/server/dist/features/view/graphql/view.resolver.js +10 -0
  157. package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
  158. package/server/dist/features/view/graphql/view.type-defs.js +63 -0
  159. package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
  160. package/server/dist/features/view/services/workspace.js +432 -0
  161. package/server/dist/features/view/services/workspace.js.map +1 -0
  162. package/server/dist/main.js +14 -0
  163. package/server/dist/main.js.map +1 -1
  164. package/server/dist/modules/auth-guard.js +16 -21
  165. package/server/dist/modules/auth-guard.js.map +1 -1
  166. package/server/dist/modules/auth-mode.js +16 -41
  167. package/server/dist/modules/auth-mode.js.map +1 -1
  168. package/server/dist/modules/blocknote.js +8 -17
  169. package/server/dist/modules/blocknote.js.map +1 -1
  170. package/server/dist/modules/data-maintenance.js +75 -0
  171. package/server/dist/modules/data-maintenance.js.map +1 -0
  172. package/server/dist/modules/error-handler.js.map +1 -1
  173. package/server/dist/modules/logger.js +5 -23
  174. package/server/dist/modules/logger.js.map +1 -1
  175. package/server/dist/modules/mcp-auth.js +4 -5
  176. package/server/dist/modules/mcp-auth.js.map +1 -1
  177. package/server/dist/modules/mcp-token.js.map +1 -1
  178. package/server/dist/modules/prisma-runtime.js +2 -7
  179. package/server/dist/modules/prisma-runtime.js.map +1 -1
  180. package/server/dist/modules/recovery-retention.js +1 -1
  181. package/server/dist/modules/recovery-retention.js.map +1 -1
  182. package/server/dist/modules/server-events-handler.js +34 -0
  183. package/server/dist/modules/server-events-handler.js.map +1 -0
  184. package/server/dist/modules/server-events.js +32 -0
  185. package/server/dist/modules/server-events.js.map +1 -0
  186. package/server/dist/modules/use-async.js.map +1 -1
  187. package/server/dist/paths.js.map +1 -1
  188. package/server/dist/routes/api.js +34 -0
  189. package/server/dist/routes/api.js.map +1 -0
  190. package/server/dist/routes/auth-pages.js +11 -0
  191. package/server/dist/routes/auth-pages.js.map +1 -0
  192. package/server/dist/routes/client.js +27 -0
  193. package/server/dist/routes/client.js.map +1 -0
  194. package/server/dist/routes/graphql.js +33 -0
  195. package/server/dist/routes/graphql.js.map +1 -0
  196. package/server/dist/routes/index.js +11 -0
  197. package/server/dist/routes/index.js.map +1 -0
  198. package/server/dist/routes/mcp.js +30 -0
  199. package/server/dist/routes/mcp.js.map +1 -0
  200. package/server/dist/schema/index.js +11 -8
  201. package/server/dist/schema/index.js.map +1 -1
  202. package/server/dist/types/index.js.map +1 -1
  203. package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
  204. package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
  205. package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
  206. package/server/prisma/schema.prisma +66 -12
  207. package/server/client/dist/assets/Calendar-Kjy1PX6L.js +0 -25
  208. package/server/client/dist/assets/Graph-Bl5K2qUw.js +0 -1
  209. package/server/client/dist/assets/ModalActionRow-DyOb58KJ.js +0 -1
  210. package/server/client/dist/assets/Note-BfGma2Qc.css +0 -1
  211. package/server/client/dist/assets/Note-DN_ko2M3.js +0 -1
  212. package/server/client/dist/assets/Reminders-BK-Uv9Ps.js +0 -1
  213. package/server/client/dist/assets/Search-ClHeWvmI.js +0 -1
  214. package/server/client/dist/assets/Tag-Dph4V_kl.js +0 -1
  215. package/server/client/dist/assets/TagNotes-CxP5qHZj.js +0 -1
  216. package/server/client/dist/assets/index-6lIw6jYW.js +0 -1
  217. package/server/client/dist/assets/index-Ba2XPRI8.js +0 -205
  218. package/server/client/dist/assets/manage-image-Br_kKxbb.js +0 -1
  219. package/server/client/dist/assets/manage-image-detail-9DzqbiqI.js +0 -1
  220. package/server/client/dist/assets/mcp-C_So91eX.js +0 -1
  221. package/server/client/dist/assets/trash-Cg3nJ1m5.js +0 -1
  222. package/server/dist/modules/auth.js +0 -25
  223. package/server/dist/modules/auth.js.map +0 -1
  224. package/server/dist/modules/image-upload.js.map +0 -1
  225. package/server/dist/modules/mcp-admin.js.map +0 -1
  226. package/server/dist/modules/note-authoring.js.map +0 -1
  227. package/server/dist/modules/note-cleanup.js.map +0 -1
  228. package/server/dist/modules/note-snapshot.js.map +0 -1
  229. package/server/dist/modules/note-tag-filter.js.map +0 -1
  230. package/server/dist/modules/note-trash.js.map +0 -1
  231. package/server/dist/modules/remote-image.js.map +0 -1
  232. package/server/dist/modules/tag-organization.js.map +0 -1
  233. package/server/dist/schema/cache/index.js +0 -58
  234. package/server/dist/schema/cache/index.js.map +0 -1
  235. package/server/dist/schema/image/index.js +0 -88
  236. package/server/dist/schema/image/index.js.map +0 -1
  237. package/server/dist/schema/note/index.js +0 -582
  238. package/server/dist/schema/note/index.js.map +0 -1
  239. package/server/dist/schema/placeholder/index.js +0 -105
  240. package/server/dist/schema/placeholder/index.js.map +0 -1
  241. package/server/dist/schema/reminder/index.js +0 -161
  242. package/server/dist/schema/reminder/index.js.map +0 -1
  243. package/server/dist/schema/tag/index.js +0 -81
  244. package/server/dist/schema/tag/index.js.map +0 -1
  245. package/server/dist/urls.js +0 -9
  246. package/server/dist/urls.js.map +0 -1
  247. package/server/dist/views/auth.js +0 -305
  248. package/server/dist/views/auth.js.map +0 -1
  249. package/server/dist/views/image.js +0 -44
  250. package/server/dist/views/image.js.map +0 -1
  251. package/server/dist/views/index.js +0 -6
  252. package/server/dist/views/index.js.map +0 -1
  253. package/server/dist/views/mcp-admin.js.map +0 -1
  254. package/server/dist/views/note.js.map +0 -1
  255. package/server/dist/views/tag.js.map +0 -1
@@ -0,0 +1,49 @@
1
+ import models from "../../../models.js";
2
+ const createCacheMutationService = () => ({
3
+ findCacheByKey: (key) => models.cache.findFirst({ where: { key } }),
4
+ createCache: ({ key, value }) => models.cache.create({
5
+ data: {
6
+ key,
7
+ value
8
+ }
9
+ }),
10
+ updateCache: ({ key, value }) => models.cache.update({
11
+ where: { key },
12
+ data: { value }
13
+ }),
14
+ deleteCache: async (key) => {
15
+ await models.cache.delete({ where: { key } });
16
+ }
17
+ });
18
+ const createSetCacheMutationResolver = (service = createCacheMutationService()) => {
19
+ return async (_, { key, value }) => {
20
+ const decodedValue = decodeURIComponent(value);
21
+ const existingCache = await service.findCacheByKey(key);
22
+ if (existingCache) {
23
+ return service.updateCache({
24
+ key,
25
+ value: decodedValue
26
+ });
27
+ }
28
+ return service.createCache({
29
+ key,
30
+ value: decodedValue
31
+ });
32
+ };
33
+ };
34
+ const createDeleteCacheMutationResolver = (service = createCacheMutationService()) => {
35
+ return async (_, { key }) => {
36
+ await service.deleteCache(key);
37
+ return true;
38
+ };
39
+ };
40
+ const cacheMutationResolvers = {
41
+ setCache: createSetCacheMutationResolver(),
42
+ deleteCache: createDeleteCacheMutationResolver()
43
+ };
44
+ export {
45
+ cacheMutationResolvers,
46
+ createDeleteCacheMutationResolver,
47
+ createSetCacheMutationResolver
48
+ };
49
+ //# sourceMappingURL=cache.mutation.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/cache/graphql/cache.mutation.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport models, { type Cache } from '~/models.js';\n\ntype CacheRecord = Pick<Cache, 'id' | 'key' | 'value'>;\ntype CacheMutationInput = Pick<Cache, 'key' | 'value'>;\n\ntype CacheMutationService = {\n findCacheByKey: (key: string) => Promise<CacheRecord | null>;\n createCache: (input: CacheMutationInput) => Promise<CacheRecord>;\n updateCache: (input: CacheMutationInput) => Promise<CacheRecord>;\n deleteCache: (key: string) => Promise<void>;\n};\n\nconst createCacheMutationService = (): CacheMutationService => ({\n findCacheByKey: (key) => models.cache.findFirst({ where: { key } }),\n createCache: ({ key, value }) =>\n models.cache.create({\n data: {\n key,\n value,\n },\n }),\n updateCache: ({ key, value }) =>\n models.cache.update({\n where: { key },\n data: { value },\n }),\n deleteCache: async (key) => {\n await models.cache.delete({ where: { key } });\n },\n});\n\nexport const createSetCacheMutationResolver = (service: CacheMutationService = createCacheMutationService()) => {\n return async (_: unknown, { key, value }: CacheMutationInput) => {\n const decodedValue = decodeURIComponent(value);\n const existingCache = await service.findCacheByKey(key);\n\n if (existingCache) {\n return service.updateCache({\n key,\n value: decodedValue,\n });\n }\n\n return service.createCache({\n key,\n value: decodedValue,\n });\n };\n};\n\nexport const createDeleteCacheMutationResolver = (service: CacheMutationService = createCacheMutationService()) => {\n return async (_: unknown, { key }: Pick<Cache, 'key'>) => {\n await service.deleteCache(key);\n return true;\n };\n};\n\nexport const cacheMutationResolvers: NonNullable<IResolvers['Mutation']> = {\n setCache: createSetCacheMutationResolver(),\n deleteCache: createDeleteCacheMutationResolver(),\n};\n"],"mappings":"AAEA,OAAO,YAA4B;AAYnC,MAAM,6BAA6B,OAA6B;AAAA,EAC5D,gBAAgB,CAAC,QAAQ,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA,EAClE,aAAa,CAAC,EAAE,KAAK,MAAM,MACvB,OAAO,MAAM,OAAO;AAAA,IAChB,MAAM;AAAA,MACF;AAAA,MACA;AAAA,IACJ;AAAA,EACJ,CAAC;AAAA,EACL,aAAa,CAAC,EAAE,KAAK,MAAM,MACvB,OAAO,MAAM,OAAO;AAAA,IAChB,OAAO,EAAE,IAAI;AAAA,IACb,MAAM,EAAE,MAAM;AAAA,EAClB,CAAC;AAAA,EACL,aAAa,OAAO,QAAQ;AACxB,UAAM,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAAA,EAChD;AACJ;AAEO,MAAM,iCAAiC,CAAC,UAAgC,2BAA2B,MAAM;AAC5G,SAAO,OAAO,GAAY,EAAE,KAAK,MAAM,MAA0B;AAC7D,UAAM,eAAe,mBAAmB,KAAK;AAC7C,UAAM,gBAAgB,MAAM,QAAQ,eAAe,GAAG;AAEtD,QAAI,eAAe;AACf,aAAO,QAAQ,YAAY;AAAA,QACvB;AAAA,QACA,OAAO;AAAA,MACX,CAAC;AAAA,IACL;AAEA,WAAO,QAAQ,YAAY;AAAA,MACvB;AAAA,MACA,OAAO;AAAA,IACX,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,oCAAoC,CAAC,UAAgC,2BAA2B,MAAM;AAC/G,SAAO,OAAO,GAAY,EAAE,IAAI,MAA0B;AACtD,UAAM,QAAQ,YAAY,GAAG;AAC7B,WAAO;AAAA,EACX;AACJ;AAEO,MAAM,yBAA8D;AAAA,EACvE,UAAU,+BAA+B;AAAA,EACzC,aAAa,kCAAkC;AACnD;","names":[]}
@@ -0,0 +1,21 @@
1
+ import models from "../../../models.js";
2
+ const createCacheQueryService = () => ({
3
+ findCaches: () => models.cache.findMany(),
4
+ findCacheByKey: (key) => models.cache.findUnique({ where: { key } })
5
+ });
6
+ const createAllCachesQueryResolver = (service = createCacheQueryService()) => {
7
+ return async () => service.findCaches();
8
+ };
9
+ const createCacheQueryResolver = (service = createCacheQueryService()) => {
10
+ return async (_, { key }) => service.findCacheByKey(key);
11
+ };
12
+ const cacheQueryResolvers = {
13
+ allCaches: createAllCachesQueryResolver(),
14
+ cache: createCacheQueryResolver()
15
+ };
16
+ export {
17
+ cacheQueryResolvers,
18
+ createAllCachesQueryResolver,
19
+ createCacheQueryResolver
20
+ };
21
+ //# sourceMappingURL=cache.query.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/cache/graphql/cache.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport models, { type Cache } from '~/models.js';\n\ntype CacheRecord = Pick<Cache, 'id' | 'key' | 'value'>;\n\ntype CacheQueryService = {\n findCaches: () => Promise<CacheRecord[]>;\n findCacheByKey: (key: string) => Promise<CacheRecord | null>;\n};\n\nconst createCacheQueryService = (): CacheQueryService => ({\n findCaches: () => models.cache.findMany(),\n findCacheByKey: (key) => models.cache.findUnique({ where: { key } }),\n});\n\nexport const createAllCachesQueryResolver = (service: CacheQueryService = createCacheQueryService()) => {\n return async () => service.findCaches();\n};\n\nexport const createCacheQueryResolver = (service: CacheQueryService = createCacheQueryService()) => {\n return async (_: unknown, { key }: { key: string }) => service.findCacheByKey(key);\n};\n\nexport const cacheQueryResolvers: NonNullable<IResolvers['Query']> = {\n allCaches: createAllCachesQueryResolver(),\n cache: createCacheQueryResolver(),\n};\n"],"mappings":"AAEA,OAAO,YAA4B;AASnC,MAAM,0BAA0B,OAA0B;AAAA,EACtD,YAAY,MAAM,OAAO,MAAM,SAAS;AAAA,EACxC,gBAAgB,CAAC,QAAQ,OAAO,MAAM,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACvE;AAEO,MAAM,+BAA+B,CAAC,UAA6B,wBAAwB,MAAM;AACpG,SAAO,YAAY,QAAQ,WAAW;AAC1C;AAEO,MAAM,2BAA2B,CAAC,UAA6B,wBAAwB,MAAM;AAChG,SAAO,OAAO,GAAY,EAAE,IAAI,MAAuB,QAAQ,eAAe,GAAG;AACrF;AAEO,MAAM,sBAAwD;AAAA,EACjE,WAAW,6BAA6B;AAAA,EACxC,OAAO,yBAAyB;AACpC;","names":[]}
@@ -0,0 +1,12 @@
1
+ import { cacheFieldResolvers } from "./cache.field.resolver.js";
2
+ import { cacheMutationResolvers } from "./cache.mutation.resolver.js";
3
+ import { cacheQueryResolvers } from "./cache.query.resolver.js";
4
+ const cacheResolvers = {
5
+ Cache: cacheFieldResolvers,
6
+ Query: cacheQueryResolvers,
7
+ Mutation: cacheMutationResolvers
8
+ };
9
+ export {
10
+ cacheResolvers
11
+ };
12
+ //# sourceMappingURL=cache.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/cache/graphql/cache.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\n\nimport { cacheFieldResolvers } from './cache.field.resolver.js';\nimport { cacheMutationResolvers } from './cache.mutation.resolver.js';\nimport { cacheQueryResolvers } from './cache.query.resolver.js';\n\nexport const cacheResolvers: IResolvers = {\n Cache: cacheFieldResolvers,\n Query: cacheQueryResolvers,\n Mutation: cacheMutationResolvers,\n};\n"],"mappings":"AAEA,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAE7B,MAAM,iBAA6B;AAAA,EACtC,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AACd;","names":[]}
@@ -0,0 +1,32 @@
1
+ import { gql } from "../../../modules/graphql.js";
2
+ const cacheType = gql`
3
+ type Cache {
4
+ id: ID!
5
+ key: String!
6
+ value: String!
7
+ }
8
+ `;
9
+ const cacheQuery = gql`
10
+ type Query {
11
+ allCaches: [Cache!]!
12
+ cache(key: String!): Cache!
13
+ }
14
+ `;
15
+ const cacheMutation = gql`
16
+ type Mutation {
17
+ setCache(key: String, value: String): Cache!
18
+ deleteCache(key: String!): Boolean!
19
+ }
20
+ `;
21
+ const cacheTypeDefs = `
22
+ ${cacheType}
23
+ ${cacheQuery}
24
+ ${cacheMutation}
25
+ `;
26
+ export {
27
+ cacheMutation,
28
+ cacheQuery,
29
+ cacheType,
30
+ cacheTypeDefs
31
+ };
32
+ //# sourceMappingURL=cache.type-defs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/cache/graphql/cache.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const cacheType = gql`\n type Cache {\n id: ID!\n key: String!\n value: String!\n }\n`;\n\nexport const cacheQuery = gql`\n type Query {\n allCaches: [Cache!]!\n cache(key: String!): Cache!\n }\n`;\n\nexport const cacheMutation = gql`\n type Mutation {\n setCache(key: String, value: String): Cache!\n deleteCache(key: String!): Boolean!\n }\n`;\n\nexport const cacheTypeDefs = `\n ${cacheType}\n ${cacheQuery}\n ${cacheMutation}\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlB,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnB,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAOtB,MAAM,gBAAgB;AAAA,MACvB,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA;","names":[]}
@@ -0,0 +1,21 @@
1
+ import { cacheFieldResolvers } from "./cache.field.resolver.js";
2
+ import {
3
+ cacheMutationResolvers,
4
+ createDeleteCacheMutationResolver,
5
+ createSetCacheMutationResolver
6
+ } from "./cache.mutation.resolver.js";
7
+ import { cacheQueryResolvers, createAllCachesQueryResolver, createCacheQueryResolver } from "./cache.query.resolver.js";
8
+ import { cacheResolvers } from "./cache.resolver.js";
9
+ import { cacheTypeDefs } from "./cache.type-defs.js";
10
+ export {
11
+ cacheFieldResolvers,
12
+ cacheMutationResolvers,
13
+ cacheQueryResolvers,
14
+ cacheResolvers,
15
+ cacheTypeDefs,
16
+ createAllCachesQueryResolver,
17
+ createCacheQueryResolver,
18
+ createDeleteCacheMutationResolver,
19
+ createSetCacheMutationResolver
20
+ };
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/cache/graphql/index.ts"],"sourcesContent":["export { cacheFieldResolvers } from './cache.field.resolver.js';\nexport {\n cacheMutationResolvers,\n createDeleteCacheMutationResolver,\n createSetCacheMutationResolver,\n} from './cache.mutation.resolver.js';\nexport { cacheQueryResolvers, createAllCachesQueryResolver, createCacheQueryResolver } from './cache.query.resolver.js';\nexport { cacheResolvers } from './cache.resolver.js';\nexport { cacheTypeDefs } from './cache.type-defs.js';\n"],"mappings":"AAAA,SAAS,2BAA2B;AACpC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,qBAAqB,8BAA8B,gCAAgC;AAC5F,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;","names":[]}
@@ -0,0 +1,14 @@
1
+ import models from "../../../models.js";
2
+ const createImageReferenceCountFieldResolver = (countReferences = async (url) => models.note.count({ where: { content: { contains: url } } })) => {
3
+ return async (image) => {
4
+ return countReferences(image.url);
5
+ };
6
+ };
7
+ const imageFieldResolvers = {
8
+ referenceCount: createImageReferenceCountFieldResolver()
9
+ };
10
+ export {
11
+ createImageReferenceCountFieldResolver,
12
+ imageFieldResolvers
13
+ };
14
+ //# sourceMappingURL=image.field.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/graphql/image.field.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport models, { type Image } from '~/models.js';\n\ntype ImageFieldResolvers = NonNullable<IResolvers['Image']>;\n\ntype CountImageReferences = (url: string) => Promise<number>;\n\nexport const createImageReferenceCountFieldResolver = (\n countReferences: CountImageReferences = async (url) => models.note.count({ where: { content: { contains: url } } }),\n) => {\n return async (image: Image) => {\n return countReferences(image.url);\n };\n};\n\nexport const imageFieldResolvers: ImageFieldResolvers = {\n referenceCount: createImageReferenceCountFieldResolver(),\n};\n"],"mappings":"AACA,OAAO,YAA4B;AAM5B,MAAM,yCAAyC,CAClD,kBAAwC,OAAO,QAAQ,OAAO,KAAK,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,IAAI,EAAE,EAAE,CAAC,MACjH;AACD,SAAO,OAAO,UAAiB;AAC3B,WAAO,gBAAgB,MAAM,GAAG;AAAA,EACpC;AACJ;AAEO,MAAM,sBAA2C;AAAA,EACpD,gBAAgB,uCAAuC;AAC3D;","names":[]}
@@ -0,0 +1,21 @@
1
+ import { deleteImageById } from "../services/delete.js";
2
+ const createDeleteImageMutationResolver = (deleteImage = deleteImageById) => {
3
+ return async (_, { id }) => {
4
+ try {
5
+ return await deleteImage(Number(id));
6
+ } catch (error) {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ process.stderr.write(`[image] Delete failed: ${message}
9
+ `);
10
+ return false;
11
+ }
12
+ };
13
+ };
14
+ const imageMutationResolvers = {
15
+ deleteImage: createDeleteImageMutationResolver()
16
+ };
17
+ export {
18
+ createDeleteImageMutationResolver,
19
+ imageMutationResolvers
20
+ };
21
+ //# sourceMappingURL=image.mutation.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/graphql/image.mutation.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { deleteImageById } from '../services/delete.js';\n\ntype ImageMutationResolvers = NonNullable<IResolvers['Mutation']>;\n\ntype DeleteImage = (id: number) => Promise<boolean>;\n\nexport const createDeleteImageMutationResolver = (deleteImage: DeleteImage = deleteImageById) => {\n return async (_: unknown, { id }: { id: string | number }) => {\n try {\n return await deleteImage(Number(id));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[image] Delete failed: ${message}\\n`);\n return false;\n }\n };\n};\n\nexport const imageMutationResolvers: ImageMutationResolvers = {\n deleteImage: createDeleteImageMutationResolver(),\n};\n"],"mappings":"AACA,SAAS,uBAAuB;AAMzB,MAAM,oCAAoC,CAAC,cAA2B,oBAAoB;AAC7F,SAAO,OAAO,GAAY,EAAE,GAAG,MAA+B;AAC1D,QAAI;AACA,aAAO,MAAM,YAAY,OAAO,EAAE,CAAC;AAAA,IACvC,SAAS,OAAO;AACZ,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,OAAO,MAAM,0BAA0B,OAAO;AAAA,CAAI;AAC1D,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEO,MAAM,yBAAiD;AAAA,EAC1D,aAAa,kCAAkC;AACnD;","names":[]}
@@ -0,0 +1,40 @@
1
+ import models from "../../../models.js";
2
+ const createAllImagesQueryResolver = (deps = {
3
+ countImages: async () => models.image.count(),
4
+ findImageById: async (id) => models.image.findFirst({ where: { id } }),
5
+ findImages: async (input) => models.image.findMany(input)
6
+ }) => {
7
+ return async (_, {
8
+ pagination
9
+ }) => {
10
+ const [totalCount, images] = await Promise.all([
11
+ deps.countImages(),
12
+ deps.findImages({
13
+ skip: pagination.offset,
14
+ take: pagination.limit,
15
+ orderBy: { createdAt: "desc" }
16
+ })
17
+ ]);
18
+ return {
19
+ totalCount,
20
+ images
21
+ };
22
+ };
23
+ };
24
+ const createImageQueryResolver = (deps = {
25
+ findImageById: async (id) => models.image.findFirst({ where: { id } })
26
+ }) => {
27
+ return async (_, { id }) => {
28
+ return deps.findImageById(Number(id));
29
+ };
30
+ };
31
+ const imageQueryResolvers = {
32
+ allImages: createAllImagesQueryResolver(),
33
+ image: createImageQueryResolver()
34
+ };
35
+ export {
36
+ createAllImagesQueryResolver,
37
+ createImageQueryResolver,
38
+ imageQueryResolvers
39
+ };
40
+ //# sourceMappingURL=image.query.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/graphql/image.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport models from '~/models.js';\nimport type { Pagination } from '~/types/index.js';\n\ntype ImageQueryResolvers = NonNullable<IResolvers['Query']>;\n\ninterface ImageQueryDeps {\n countImages: () => Promise<number>;\n findImageById: (id: number) => Promise<unknown>;\n findImages: (input: { orderBy: { createdAt: 'desc' }; skip: number; take: number }) => Promise<unknown[]>;\n}\n\nexport const createAllImagesQueryResolver = (\n deps: ImageQueryDeps = {\n countImages: async () => models.image.count(),\n findImageById: async (id) => models.image.findFirst({ where: { id } }),\n findImages: async (input) => models.image.findMany(input),\n },\n) => {\n return async (\n _: unknown,\n {\n pagination,\n }: {\n pagination: Pagination;\n },\n ) => {\n const [totalCount, images] = await Promise.all([\n deps.countImages(),\n deps.findImages({\n skip: pagination.offset,\n take: pagination.limit,\n orderBy: { createdAt: 'desc' },\n }),\n ]);\n\n return {\n totalCount,\n images,\n };\n };\n};\n\nexport const createImageQueryResolver = (\n deps: Pick<ImageQueryDeps, 'findImageById'> = {\n findImageById: async (id) => models.image.findFirst({ where: { id } }),\n },\n) => {\n return async (_: unknown, { id }: { id: string | number }) => {\n return deps.findImageById(Number(id));\n };\n};\n\nexport const imageQueryResolvers: ImageQueryResolvers = {\n allImages: createAllImagesQueryResolver(),\n image: createImageQueryResolver(),\n};\n"],"mappings":"AACA,OAAO,YAAY;AAWZ,MAAM,+BAA+B,CACxC,OAAuB;AAAA,EACnB,aAAa,YAAY,OAAO,MAAM,MAAM;AAAA,EAC5C,eAAe,OAAO,OAAO,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAAA,EACrE,YAAY,OAAO,UAAU,OAAO,MAAM,SAAS,KAAK;AAC5D,MACC;AACD,SAAO,OACH,GACA;AAAA,IACI;AAAA,EACJ,MAGC;AACD,UAAM,CAAC,YAAY,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3C,KAAK,YAAY;AAAA,MACjB,KAAK,WAAW;AAAA,QACZ,MAAM,WAAW;AAAA,QACjB,MAAM,WAAW;AAAA,QACjB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAAA,IACL,CAAC;AAED,WAAO;AAAA,MACH;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AACJ;AAEO,MAAM,2BAA2B,CACpC,OAA8C;AAAA,EAC1C,eAAe,OAAO,OAAO,OAAO,MAAM,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AACzE,MACC;AACD,SAAO,OAAO,GAAY,EAAE,GAAG,MAA+B;AAC1D,WAAO,KAAK,cAAc,OAAO,EAAE,CAAC;AAAA,EACxC;AACJ;AAEO,MAAM,sBAA2C;AAAA,EACpD,WAAW,6BAA6B;AAAA,EACxC,OAAO,yBAAyB;AACpC;","names":[]}
@@ -0,0 +1,12 @@
1
+ import { imageFieldResolvers } from "./image.field.resolver.js";
2
+ import { imageMutationResolvers } from "./image.mutation.resolver.js";
3
+ import { imageQueryResolvers } from "./image.query.resolver.js";
4
+ const imageResolvers = {
5
+ Query: imageQueryResolvers,
6
+ Mutation: imageMutationResolvers,
7
+ Image: imageFieldResolvers
8
+ };
9
+ export {
10
+ imageResolvers
11
+ };
12
+ //# sourceMappingURL=image.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/graphql/image.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { imageFieldResolvers } from './image.field.resolver.js';\nimport { imageMutationResolvers } from './image.mutation.resolver.js';\nimport { imageQueryResolvers } from './image.query.resolver.js';\n\nexport const imageResolvers: IResolvers = {\n Query: imageQueryResolvers,\n Mutation: imageMutationResolvers,\n Image: imageFieldResolvers,\n};\n"],"mappings":"AACA,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAE7B,MAAM,iBAA6B;AAAA,EACtC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,OAAO;AACX;","names":[]}
@@ -0,0 +1,31 @@
1
+ import { gql } from "../../../modules/graphql.js";
2
+ const imageTypeDefs = gql`
3
+ input PaginationInput {
4
+ limit: Int!
5
+ offset: Int!
6
+ }
7
+
8
+ type Image {
9
+ id: ID!
10
+ url: String!
11
+ referenceCount: Int!
12
+ }
13
+
14
+ type Images {
15
+ totalCount: Int!
16
+ images: [Image!]!
17
+ }
18
+
19
+ type Query {
20
+ allImages(pagination: PaginationInput): Images!
21
+ image(id: ID!): Image!
22
+ }
23
+
24
+ type Mutation {
25
+ deleteImage(id: ID!): Boolean!
26
+ }
27
+ `;
28
+ export {
29
+ imageTypeDefs
30
+ };
31
+ //# sourceMappingURL=image.type-defs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/graphql/image.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const imageTypeDefs = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n type Image {\n id: ID!\n url: String!\n referenceCount: Int!\n }\n\n type Images {\n totalCount: Int!\n images: [Image!]!\n }\n\n type Query {\n allImages(pagination: PaginationInput): Images!\n image(id: ID!): Image!\n }\n\n type Mutation {\n deleteImage(id: ID!): Boolean!\n }\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { imageFieldResolvers } from "./image.field.resolver.js";
2
+ import { createDeleteImageMutationResolver, imageMutationResolvers } from "./image.mutation.resolver.js";
3
+ import { createAllImagesQueryResolver, createImageQueryResolver, imageQueryResolvers } from "./image.query.resolver.js";
4
+ import { imageResolvers } from "./image.resolver.js";
5
+ import { imageTypeDefs } from "./image.type-defs.js";
6
+ export {
7
+ createAllImagesQueryResolver,
8
+ createDeleteImageMutationResolver,
9
+ createImageQueryResolver,
10
+ imageFieldResolvers,
11
+ imageMutationResolvers,
12
+ imageQueryResolvers,
13
+ imageResolvers,
14
+ imageTypeDefs
15
+ };
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/graphql/index.ts"],"sourcesContent":["export { imageFieldResolvers } from './image.field.resolver.js';\nexport { createDeleteImageMutationResolver, imageMutationResolvers } from './image.mutation.resolver.js';\nexport { createAllImagesQueryResolver, createImageQueryResolver, imageQueryResolvers } from './image.query.resolver.js';\nexport { imageResolvers } from './image.resolver.js';\nexport { imageTypeDefs } from './image.type-defs.js';\n"],"mappings":"AAAA,SAAS,2BAA2B;AACpC,SAAS,mCAAmC,8BAA8B;AAC1E,SAAS,8BAA8B,0BAA0B,2BAA2B;AAC5F,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;","names":[]}
@@ -0,0 +1,48 @@
1
+ import { createAppError } from "../../../modules/error-handler.js";
2
+ import { fetchRemoteImage, RemoteImageFetchError } from "../services/remote-fetch.js";
3
+ import { persistUploadedImage } from "../services/upload.js";
4
+ const createUploadResponse = (image) => {
5
+ return {
6
+ id: image.id,
7
+ url: image.url
8
+ };
9
+ };
10
+ const createUploadImageHandler = (persistImage = persistUploadedImage) => {
11
+ return async (req, res) => {
12
+ const { image } = req.body ?? {};
13
+ if (typeof image !== "string" || !image.match(/data:image\/\s*(\w+);base64,/)) {
14
+ throw createAppError(400, "INVALID_IMAGE_UPLOAD", "No image uploaded");
15
+ }
16
+ const [info, data] = image.split(",");
17
+ const buffer = Buffer.from(data, "base64");
18
+ const extension = info.split(";")[0]?.split("/")[1] ?? "";
19
+ const uploadedImage = await persistImage({
20
+ buffer,
21
+ extension
22
+ });
23
+ res.status(200).json(createUploadResponse(uploadedImage)).end();
24
+ };
25
+ };
26
+ const createUploadImageFromSrcHandler = (fetchImage = fetchRemoteImage, persistImage = persistUploadedImage) => {
27
+ return async (req, res) => {
28
+ const src = String(req.body?.src ?? "");
29
+ try {
30
+ const remoteImage = await fetchImage(src);
31
+ const uploadedImage = await persistImage({
32
+ buffer: remoteImage.buffer,
33
+ extension: remoteImage.extension
34
+ });
35
+ res.status(200).json(createUploadResponse(uploadedImage)).end();
36
+ } catch (error) {
37
+ if (error instanceof RemoteImageFetchError) {
38
+ throw createAppError(error.status, error.code, error.message);
39
+ }
40
+ throw error;
41
+ }
42
+ };
43
+ };
44
+ export {
45
+ createUploadImageFromSrcHandler,
46
+ createUploadImageHandler
47
+ };
48
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/http/upload.ts"],"sourcesContent":["import { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport { fetchRemoteImage, type RemoteImage, RemoteImageFetchError } from '../services/remote-fetch.js';\nimport { type PersistedImageSummary, type PersistImageInput, persistUploadedImage } from '../services/upload.js';\n\ntype PersistImage = (input: PersistImageInput) => Promise<PersistedImageSummary>;\ntype FetchImage = (src: string) => Promise<RemoteImage>;\n\nconst createUploadResponse = (image: PersistedImageSummary) => {\n return {\n id: image.id,\n url: image.url,\n };\n};\n\nexport const createUploadImageHandler = (persistImage: PersistImage = persistUploadedImage): Controller => {\n return async (req, res) => {\n const { image } = req.body ?? {};\n\n if (typeof image !== 'string' || !image.match(/data:image\\/\\s*(\\w+);base64,/)) {\n throw createAppError(400, 'INVALID_IMAGE_UPLOAD', 'No image uploaded');\n }\n\n const [info, data] = image.split(',');\n const buffer = Buffer.from(data, 'base64');\n const extension = info.split(';')[0]?.split('/')[1] ?? '';\n const uploadedImage = await persistImage({\n buffer,\n extension,\n });\n\n res.status(200).json(createUploadResponse(uploadedImage)).end();\n };\n};\n\nexport const createUploadImageFromSrcHandler = (\n fetchImage: FetchImage = fetchRemoteImage,\n persistImage: PersistImage = persistUploadedImage,\n): Controller => {\n return async (req, res) => {\n const src = String(req.body?.src ?? '');\n\n try {\n const remoteImage = await fetchImage(src);\n const uploadedImage = await persistImage({\n buffer: remoteImage.buffer,\n extension: remoteImage.extension,\n });\n\n res.status(200).json(createUploadResponse(uploadedImage)).end();\n } catch (error) {\n if (error instanceof RemoteImageFetchError) {\n throw createAppError(error.status, error.code, error.message);\n }\n\n throw error;\n }\n };\n};\n"],"mappings":"AAAA,SAAS,sBAAsB;AAE/B,SAAS,kBAAoC,6BAA6B;AAC1E,SAA6D,4BAA4B;AAKzF,MAAM,uBAAuB,CAAC,UAAiC;AAC3D,SAAO;AAAA,IACH,IAAI,MAAM;AAAA,IACV,KAAK,MAAM;AAAA,EACf;AACJ;AAEO,MAAM,2BAA2B,CAAC,eAA6B,yBAAqC;AACvG,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,EAAE,MAAM,IAAI,IAAI,QAAQ,CAAC;AAE/B,QAAI,OAAO,UAAU,YAAY,CAAC,MAAM,MAAM,8BAA8B,GAAG;AAC3E,YAAM,eAAe,KAAK,wBAAwB,mBAAmB;AAAA,IACzE;AAEA,UAAM,CAAC,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG;AACpC,UAAM,SAAS,OAAO,KAAK,MAAM,QAAQ;AACzC,UAAM,YAAY,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC,KAAK;AACvD,UAAM,gBAAgB,MAAM,aAAa;AAAA,MACrC;AAAA,MACA;AAAA,IACJ,CAAC;AAED,QAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,aAAa,CAAC,EAAE,IAAI;AAAA,EAClE;AACJ;AAEO,MAAM,kCAAkC,CAC3C,aAAyB,kBACzB,eAA6B,yBAChB;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,MAAM,OAAO,IAAI,MAAM,OAAO,EAAE;AAEtC,QAAI;AACA,YAAM,cAAc,MAAM,WAAW,GAAG;AACxC,YAAM,gBAAgB,MAAM,aAAa;AAAA,QACrC,QAAQ,YAAY;AAAA,QACpB,WAAW,YAAY;AAAA,MAC3B,CAAC;AAED,UAAI,OAAO,GAAG,EAAE,KAAK,qBAAqB,aAAa,CAAC,EAAE,IAAI;AAAA,IAClE,SAAS,OAAO;AACZ,UAAI,iBAAiB,uBAAuB;AACxC,cAAM,eAAe,MAAM,QAAQ,MAAM,MAAM,MAAM,OAAO;AAAA,MAChE;AAEA,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;","names":[]}
@@ -0,0 +1,52 @@
1
+ import fs from "fs";
2
+ import fsPromises from "fs/promises";
3
+ import path from "path";
4
+ import models from "../../../models.js";
5
+ import { paths } from "../../../paths.js";
6
+ const resolveStoredImagePath = (url) => {
7
+ return path.resolve(paths.imageDir, url.replace("/assets/images/", ""));
8
+ };
9
+ const createImageDeleteService = (deps) => {
10
+ return {
11
+ deleteImageById: async (id) => {
12
+ const image = await deps.findImageById(id);
13
+ if (!image) {
14
+ return false;
15
+ }
16
+ const imagePath = deps.resolveImagePath(image.url);
17
+ if (deps.fileExists(imagePath)) {
18
+ await deps.removeFile(imagePath);
19
+ }
20
+ await deps.deleteImageRecord(id);
21
+ return true;
22
+ }
23
+ };
24
+ };
25
+ const defaultImageDeleteService = createImageDeleteService({
26
+ deleteImageRecord: async (id) => {
27
+ await models.image.delete({ where: { id } });
28
+ },
29
+ fileExists: (filePath) => fs.existsSync(filePath),
30
+ findImageById: async (id) => {
31
+ return models.image.findFirst({
32
+ where: { id },
33
+ select: {
34
+ id: true,
35
+ url: true
36
+ }
37
+ });
38
+ },
39
+ removeFile: async (filePath) => {
40
+ await fsPromises.unlink(filePath);
41
+ },
42
+ resolveImagePath: resolveStoredImagePath
43
+ });
44
+ const deleteImageById = async (id) => {
45
+ return defaultImageDeleteService.deleteImageById(id);
46
+ };
47
+ export {
48
+ createImageDeleteService,
49
+ deleteImageById,
50
+ resolveStoredImagePath
51
+ };
52
+ //# sourceMappingURL=delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/services/delete.ts"],"sourcesContent":["import fs from 'fs';\nimport fsPromises from 'fs/promises';\nimport path from 'path';\n\nimport models from '~/models.js';\nimport { paths } from '~/paths.js';\n\ninterface DeleteImageRecord {\n id: number;\n url: string;\n}\n\ninterface ImageDeleteDeps {\n deleteImageRecord: (id: number) => Promise<void>;\n fileExists: (filePath: string) => boolean;\n findImageById: (id: number) => Promise<DeleteImageRecord | null>;\n removeFile: (filePath: string) => Promise<void>;\n resolveImagePath: (url: string) => string;\n}\n\nexport const resolveStoredImagePath = (url: string) => {\n return path.resolve(paths.imageDir, url.replace('/assets/images/', ''));\n};\n\nexport const createImageDeleteService = (deps: ImageDeleteDeps) => {\n return {\n deleteImageById: async (id: number) => {\n const image = await deps.findImageById(id);\n\n if (!image) {\n return false;\n }\n\n const imagePath = deps.resolveImagePath(image.url);\n\n if (deps.fileExists(imagePath)) {\n await deps.removeFile(imagePath);\n }\n\n await deps.deleteImageRecord(id);\n\n return true;\n },\n };\n};\n\nconst defaultImageDeleteService = createImageDeleteService({\n deleteImageRecord: async (id) => {\n await models.image.delete({ where: { id } });\n },\n fileExists: (filePath) => fs.existsSync(filePath),\n findImageById: async (id) => {\n return models.image.findFirst({\n where: { id },\n select: {\n id: true,\n url: true,\n },\n });\n },\n removeFile: async (filePath) => {\n await fsPromises.unlink(filePath);\n },\n resolveImagePath: resolveStoredImagePath,\n});\n\nexport const deleteImageById = async (id: number) => {\n return defaultImageDeleteService.deleteImageById(id);\n};\n"],"mappings":"AAAA,OAAO,QAAQ;AACf,OAAO,gBAAgB;AACvB,OAAO,UAAU;AAEjB,OAAO,YAAY;AACnB,SAAS,aAAa;AAef,MAAM,yBAAyB,CAAC,QAAgB;AACnD,SAAO,KAAK,QAAQ,MAAM,UAAU,IAAI,QAAQ,mBAAmB,EAAE,CAAC;AAC1E;AAEO,MAAM,2BAA2B,CAAC,SAA0B;AAC/D,SAAO;AAAA,IACH,iBAAiB,OAAO,OAAe;AACnC,YAAM,QAAQ,MAAM,KAAK,cAAc,EAAE;AAEzC,UAAI,CAAC,OAAO;AACR,eAAO;AAAA,MACX;AAEA,YAAM,YAAY,KAAK,iBAAiB,MAAM,GAAG;AAEjD,UAAI,KAAK,WAAW,SAAS,GAAG;AAC5B,cAAM,KAAK,WAAW,SAAS;AAAA,MACnC;AAEA,YAAM,KAAK,kBAAkB,EAAE;AAE/B,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEA,MAAM,4BAA4B,yBAAyB;AAAA,EACvD,mBAAmB,OAAO,OAAO;AAC7B,UAAM,OAAO,MAAM,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAAA,EAC/C;AAAA,EACA,YAAY,CAAC,aAAa,GAAG,WAAW,QAAQ;AAAA,EAChD,eAAe,OAAO,OAAO;AACzB,WAAO,OAAO,MAAM,UAAU;AAAA,MAC1B,OAAO,EAAE,GAAG;AAAA,MACZ,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,KAAK;AAAA,MACT;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,YAAY,OAAO,aAAa;AAC5B,UAAM,WAAW,OAAO,QAAQ;AAAA,EACpC;AAAA,EACA,kBAAkB;AACtB,CAAC;AAEM,MAAM,kBAAkB,OAAO,OAAe;AACjD,SAAO,0BAA0B,gBAAgB,EAAE;AACvD;","names":[]}
@@ -173,4 +173,4 @@ export {
173
173
  RemoteImageFetchError,
174
174
  fetchRemoteImage
175
175
  };
176
- //# sourceMappingURL=remote-image.js.map
176
+ //# sourceMappingURL=remote-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/services/remote-fetch.ts"],"sourcesContent":["import { lookup as dnsLookup } from 'node:dns/promises';\nimport { isIP } from 'node:net';\n\nexport interface RemoteImage {\n buffer: Buffer;\n contentType: string;\n extension: string;\n}\n\nexport interface RemoteImageFetchOptions {\n fetchImpl?: typeof fetch;\n lookupHostname?: (hostname: string) => Promise<Array<{ address: string }>>;\n maxBytes?: number;\n timeoutMs?: number;\n}\n\nexport class RemoteImageFetchError extends Error {\n public readonly code: string;\n public readonly status: number;\n\n constructor(code: string, status: number, message: string) {\n super(message);\n this.name = 'RemoteImageFetchError';\n this.code = code;\n this.status = status;\n }\n}\n\nconst DEFAULT_MAX_BYTES = 10 * 1024 * 1024;\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst SUPPORTED_IMAGE_TYPES = new Map([\n ['image/png', 'png'],\n ['image/jpeg', 'jpg'],\n ['image/gif', 'gif'],\n ['image/webp', 'webp'],\n ['image/bmp', 'bmp'],\n ['image/avif', 'avif'],\n]);\n\nconst createRemoteImageFetchError = (code: string, status: number, message: string) => {\n return new RemoteImageFetchError(code, status, message);\n};\n\nconst normalizeIp = (address: string) => {\n return address.toLowerCase().replace(/^::ffff:/, '');\n};\n\nconst isBlockedIpv4 = (address: string) => {\n const octets = address.split('.').map((segment) => Number(segment));\n\n if (octets.length !== 4 || octets.some((segment) => Number.isNaN(segment))) {\n return false;\n }\n\n if (octets[0] === 127 || octets[0] === 10) {\n return true;\n }\n\n if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {\n return true;\n }\n\n if (octets[0] === 192 && octets[1] === 168) {\n return true;\n }\n\n return octets[0] === 169 && octets[1] === 254;\n};\n\nconst isBlockedIpv6 = (address: string) => {\n if (address === '::1') {\n return true;\n }\n\n const firstSegment = address.split(':')[0];\n\n if (!firstSegment) {\n return false;\n }\n\n const firstValue = Number.parseInt(firstSegment, 16);\n\n if (Number.isNaN(firstValue)) {\n return false;\n }\n\n return (firstValue & 0xfe00) === 0xfc00 || (firstValue & 0xffc0) === 0xfe80;\n};\n\nconst isBlockedAddress = (address: string) => {\n const normalizedAddress = normalizeIp(address);\n const family = isIP(normalizedAddress);\n\n if (family === 4) {\n return isBlockedIpv4(normalizedAddress);\n }\n\n if (family === 6) {\n return isBlockedIpv6(normalizedAddress);\n }\n\n return false;\n};\n\nconst assertSafeRemoteUrl = async (\n src: string,\n lookupHostname: NonNullable<RemoteImageFetchOptions['lookupHostname']>,\n) => {\n let remoteUrl: URL;\n\n try {\n remoteUrl = new URL(src);\n } catch {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL is invalid.');\n }\n\n if (!['http:', 'https:'].includes(remoteUrl.protocol)) {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL must use http or https.');\n }\n\n if (!remoteUrl.hostname || remoteUrl.username || remoteUrl.password) {\n throw createRemoteImageFetchError('INVALID_REMOTE_URL', 400, 'Remote image URL is invalid.');\n }\n\n if (\n remoteUrl.port &&\n !(\n (remoteUrl.protocol === 'http:' && remoteUrl.port === '80') ||\n (remoteUrl.protocol === 'https:' && remoteUrl.port === '443')\n )\n ) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n if (remoteUrl.hostname.toLowerCase() === 'localhost') {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n if (isIP(remoteUrl.hostname)) {\n if (isBlockedAddress(remoteUrl.hostname)) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n return remoteUrl;\n }\n\n let addresses: Array<{ address: string }>;\n\n try {\n addresses = await lookupHostname(remoteUrl.hostname);\n } catch {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n if (addresses.length === 0 || addresses.some(({ address }) => isBlockedAddress(address))) {\n throw createRemoteImageFetchError('REMOTE_URL_BLOCKED', 403, 'Remote image host is not allowed.');\n }\n\n return remoteUrl;\n};\n\nconst readImageBody = async (response: Response, maxBytes: number) => {\n if (!response.body) {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image response body was empty.');\n }\n\n const reader = response.body.getReader();\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw createRemoteImageFetchError('REMOTE_IMAGE_TOO_LARGE', 413, 'Remote image exceeded the size limit.');\n }\n\n chunks.push(Buffer.from(value));\n }\n\n return Buffer.concat(chunks);\n};\n\nexport const fetchRemoteImage = async (src: string, options: RemoteImageFetchOptions = {}): Promise<RemoteImage> => {\n const fetchImpl = options.fetchImpl ?? fetch;\n const lookupHostname =\n options.lookupHostname ??\n ((hostname: string) => {\n return dnsLookup(hostname, {\n all: true,\n verbatim: true,\n });\n });\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n const remoteUrl = await assertSafeRemoteUrl(src, lookupHostname);\n\n let response: Response;\n\n try {\n response = await fetchImpl(remoteUrl, {\n method: 'GET',\n redirect: 'error',\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (error instanceof RemoteImageFetchError) {\n throw error;\n }\n\n if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {\n throw createRemoteImageFetchError('REMOTE_FETCH_TIMEOUT', 504, 'Remote image fetch timed out.');\n }\n\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n if (!response.ok) {\n throw createRemoteImageFetchError('REMOTE_FETCH_FAILED', 502, 'Remote image could not be fetched.');\n }\n\n const contentTypeHeader = response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';\n const extension = SUPPORTED_IMAGE_TYPES.get(contentTypeHeader);\n\n if (!extension) {\n throw createRemoteImageFetchError(\n 'REMOTE_IMAGE_UNSUPPORTED_CONTENT_TYPE',\n 415,\n 'Remote image content type is not supported.',\n );\n }\n\n return {\n buffer: await readImageBody(response, maxBytes),\n contentType: contentTypeHeader,\n extension,\n };\n};\n"],"mappings":"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","names":[]}
@@ -1,18 +1,14 @@
1
1
  import crypto from "crypto";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
- import models from "../models.js";
5
- import { paths } from "../paths.js";
4
+ import models from "../../../models.js";
5
+ import { paths } from "../../../paths.js";
6
6
  const hashImageBuffer = (buffer) => {
7
7
  return crypto.createHash("sha512").update(buffer).digest("hex");
8
8
  };
9
9
  const buildImageTarget = (hash, extension, now = /* @__PURE__ */ new Date()) => {
10
10
  const normalizedExtension = extension.trim().toLowerCase().replace(/^\./, "");
11
- const currentPath = [
12
- now.getFullYear().toString(),
13
- (now.getMonth() + 1).toString(),
14
- now.getDate().toString()
15
- ];
11
+ const currentPath = [now.getFullYear().toString(), (now.getMonth() + 1).toString(), now.getDate().toString()];
16
12
  const targetDir = path.resolve(paths.imageDir, ...currentPath);
17
13
  const fileName = `${hash}.${normalizedExtension}`;
18
14
  return {
@@ -111,4 +107,4 @@ export {
111
107
  hashImageBuffer,
112
108
  persistUploadedImage
113
109
  };
114
- //# sourceMappingURL=image-upload.js.map
110
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/image/services/upload.ts"],"sourcesContent":["import crypto from 'crypto';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nimport models from '~/models.js';\nimport { paths } from '~/paths.js';\n\ninterface ImageRecord {\n id: number;\n url: string;\n}\n\ninterface ImageUploadDeps {\n createImage: (input: { hash: string; url: string }) => Promise<ImageRecord>;\n ensureDir: (dirPath: string) => Promise<void>;\n findImageByHash: (hash: string) => Promise<ImageRecord | null>;\n removeFile: (filePath: string) => Promise<void>;\n writeFile: (filePath: string, buffer: Buffer) => Promise<void>;\n}\n\nexport interface PersistImageInput {\n buffer: Buffer;\n extension: string;\n}\n\nexport interface PersistedImageSummary {\n id: number;\n url: string;\n}\n\nexport const hashImageBuffer = (buffer: Buffer) => {\n return crypto.createHash('sha512').update(buffer).digest('hex');\n};\n\nconst buildImageTarget = (hash: string, extension: string, now = new Date()) => {\n const normalizedExtension = extension.trim().toLowerCase().replace(/^\\./, '');\n const currentPath = [now.getFullYear().toString(), (now.getMonth() + 1).toString(), now.getDate().toString()];\n const targetDir = path.resolve(paths.imageDir, ...currentPath);\n const fileName = `${hash}.${normalizedExtension}`;\n\n return {\n targetDir,\n absolutePath: path.resolve(targetDir, fileName),\n url: `/assets/images/${currentPath.join('/')}/${fileName}`,\n };\n};\n\nconst isImageHashUniqueConflict = (error: unknown) => {\n if (typeof error !== 'object' || error === null || !('code' in error)) {\n return false;\n }\n\n if (error.code !== 'P2002') {\n return false;\n }\n\n if (!('meta' in error) || typeof error.meta !== 'object' || error.meta === null) {\n return true;\n }\n\n const rawTarget = 'target' in error.meta ? error.meta.target : undefined;\n const targets = Array.isArray(rawTarget)\n ? rawTarget.filter((value): value is string => typeof value === 'string')\n : typeof rawTarget === 'string'\n ? [rawTarget]\n : [];\n\n return targets.length === 0 || targets.includes('hash');\n};\n\nconst removeFileIfPresent = async (deps: Pick<ImageUploadDeps, 'removeFile'>, filePath: string) => {\n try {\n await deps.removeFile(filePath);\n } catch (error) {\n if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {\n return;\n }\n\n throw error;\n }\n};\n\nexport const createImageUploadService = (deps: ImageUploadDeps) => {\n return {\n persistImage: async (input: PersistImageInput): Promise<PersistedImageSummary> => {\n const hash = hashImageBuffer(input.buffer);\n const existingImage = await deps.findImageByHash(hash);\n\n if (existingImage) {\n return existingImage;\n }\n\n const target = buildImageTarget(hash, input.extension);\n\n await deps.ensureDir(target.targetDir);\n await deps.writeFile(target.absolutePath, input.buffer);\n\n try {\n return await deps.createImage({\n hash,\n url: target.url,\n });\n } catch (error) {\n await removeFileIfPresent(deps, target.absolutePath);\n\n if (isImageHashUniqueConflict(error)) {\n const conflictedImage = await deps.findImageByHash(hash);\n\n if (conflictedImage) {\n return conflictedImage;\n }\n }\n\n throw error;\n }\n },\n };\n};\n\nconst defaultImageUploadService = createImageUploadService({\n createImage: async (input) => {\n return models.image.create({\n data: input,\n select: {\n id: true,\n url: true,\n },\n });\n },\n ensureDir: async (dirPath) => {\n await fs.mkdir(dirPath, { recursive: true });\n },\n findImageByHash: async (hash) => {\n return models.image.findFirst({\n where: { hash },\n select: {\n id: true,\n url: true,\n },\n });\n },\n removeFile: async (filePath) => {\n await fs.unlink(filePath);\n },\n writeFile: async (filePath, buffer) => {\n await fs.writeFile(filePath, buffer);\n },\n});\n\nexport const persistUploadedImage = async (input: PersistImageInput) => {\n return defaultImageUploadService.persistImage(input);\n};\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { createAppError } from "../modules/error-handler.js";
2
- import { createMcpAdminService } from "../modules/mcp-admin.js";
1
+ import { createAppError } from "../../../modules/error-handler.js";
2
+ import { createMcpAdminService } from "../service.js";
3
3
  const createMcpAdminStatusHandler = (service = createMcpAdminService()) => {
4
4
  return async (_req, res) => {
5
5
  const status = await service.getStatus();
@@ -39,4 +39,4 @@ export {
39
39
  createMcpAdminSetEnabledHandler,
40
40
  createMcpAdminStatusHandler
41
41
  };
42
- //# sourceMappingURL=mcp-admin.js.map
42
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/mcp-admin/http/handlers.ts"],"sourcesContent":["import { createAppError } from '~/modules/error-handler.js';\nimport type { Controller } from '~/types/index.js';\nimport { createMcpAdminService, type McpAdminService } from '../service.js';\n\ntype McpAdminControllerService = Pick<\n McpAdminService,\n 'getStatus' | 'setEnabled' | 'rotateToken' | 'revokeActiveToken'\n>;\n\nexport const createMcpAdminStatusHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (_req, res) => {\n const status = await service.getStatus();\n res.status(200).json(status).end();\n };\n};\n\nexport const createMcpAdminSetEnabledHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (req, res) => {\n const enabled = req.body?.enabled;\n if (typeof enabled !== 'boolean') {\n throw createAppError(400, 'INVALID_MCP_ENABLED', 'enabled must be a boolean.');\n }\n\n await service.setEnabled(enabled);\n const status = await service.getStatus();\n res.status(200).json(status).end();\n };\n};\n\nexport const createMcpAdminRotateTokenHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (_req, res) => {\n const result = await service.rotateToken();\n res.status(200)\n .json({\n token: result.token,\n message: 'Save this token now. It is shown only once.',\n })\n .end();\n };\n};\n\nexport const createMcpAdminRevokeTokenHandler = (\n service: McpAdminControllerService = createMcpAdminService(),\n): Controller => {\n return async (_req, res) => {\n await service.revokeActiveToken();\n const status = await service.getStatus();\n res.status(200).json(status).end();\n };\n};\n"],"mappings":"AAAA,SAAS,sBAAsB;AAE/B,SAAS,6BAAmD;AAOrD,MAAM,8BAA8B,CACvC,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,MAAM,QAAQ;AACxB,UAAM,SAAS,MAAM,QAAQ,UAAU;AACvC,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,kCAAkC,CAC3C,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,KAAK,QAAQ;AACvB,UAAM,UAAU,IAAI,MAAM;AAC1B,QAAI,OAAO,YAAY,WAAW;AAC9B,YAAM,eAAe,KAAK,uBAAuB,4BAA4B;AAAA,IACjF;AAEA,UAAM,QAAQ,WAAW,OAAO;AAChC,UAAM,SAAS,MAAM,QAAQ,UAAU;AACvC,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;AAEO,MAAM,mCAAmC,CAC5C,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,MAAM,QAAQ;AACxB,UAAM,SAAS,MAAM,QAAQ,YAAY;AACzC,QAAI,OAAO,GAAG,EACT,KAAK;AAAA,MACF,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,IACb,CAAC,EACA,IAAI;AAAA,EACb;AACJ;AAEO,MAAM,mCAAmC,CAC5C,UAAqC,sBAAsB,MAC9C;AACb,SAAO,OAAO,MAAM,QAAQ;AACxB,UAAM,QAAQ,kBAAkB;AAChC,UAAM,SAAS,MAAM,QAAQ,UAAU;AACvC,QAAI,OAAO,GAAG,EAAE,KAAK,MAAM,EAAE,IAAI;AAAA,EACrC;AACJ;","names":[]}
@@ -1,5 +1,5 @@
1
- import models from "../models.js";
2
- import { issueMcpToken, verifyMcpToken } from "./mcp-token.js";
1
+ import models from "../../models.js";
2
+ import { issueMcpToken, verifyMcpToken } from "../../modules/mcp-token.js";
3
3
  const MCP_ENABLED_CACHE_KEY = "MCP_ENABLED";
4
4
  const toStatus = (enabled, activeToken) => {
5
5
  if (!activeToken) {
@@ -85,4 +85,4 @@ const createMcpAdminService = (db = models) => {
85
85
  export {
86
86
  createMcpAdminService
87
87
  };
88
- //# sourceMappingURL=mcp-admin.js.map
88
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/features/mcp-admin/service.ts"],"sourcesContent":["import models from '~/models.js';\nimport { issueMcpToken, verifyMcpToken } from '~/modules/mcp-token.js';\n\nconst MCP_ENABLED_CACHE_KEY = 'MCP_ENABLED';\n\nexport interface McpTokenSummary {\n id: string;\n createdAt: string;\n lastUsedAt: string | null;\n}\n\nexport interface McpAdminStatus {\n enabled: boolean;\n hasActiveToken: boolean;\n token: McpTokenSummary | null;\n}\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminService {\n getStatus: () => Promise<McpAdminStatus>;\n setEnabled: (enabled: boolean) => Promise<void>;\n rotateToken: () => Promise<{ token: string }>;\n revokeActiveToken: () => Promise<void>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\ninterface McpAdminDb {\n cache: {\n findUnique: (args: { where: { key: string } }) => Promise<{ value: string } | null>;\n upsert: (args: {\n where: { key: string };\n update: { value: string };\n create: { key: string; value: string };\n }) => Promise<unknown>;\n };\n mcpToken: {\n findFirst: (args: { where: { revokedAt: Date | null }; orderBy: { createdAt: 'desc' | 'asc' } }) => Promise<{\n id: number;\n tokenHash: string;\n createdAt: Date;\n lastUsedAt: Date | null;\n revokedAt: Date | null;\n } | null>;\n updateMany: (args: {\n where: { revokedAt: Date | null };\n data: { revokedAt: Date };\n }) => Promise<{ count: number }>;\n create: (args: { data: { tokenHash: string } }) => Promise<unknown>;\n update: (args: { where: { id: number }; data: { lastUsedAt: Date } }) => Promise<unknown>;\n };\n $transaction: <T>(callback: (tx: McpAdminDb) => Promise<T>) => Promise<T>;\n}\n\nconst toStatus = (\n enabled: boolean,\n activeToken: {\n id: number;\n createdAt: Date;\n lastUsedAt: Date | null;\n } | null,\n): McpAdminStatus => {\n if (!activeToken) {\n return {\n enabled,\n hasActiveToken: false,\n token: null,\n };\n }\n\n return {\n enabled,\n hasActiveToken: true,\n token: {\n id: String(activeToken.id),\n createdAt: activeToken.createdAt.toISOString(),\n lastUsedAt: activeToken.lastUsedAt ? activeToken.lastUsedAt.toISOString() : null,\n },\n };\n};\n\nexport const createMcpAdminService = (db: McpAdminDb = models as unknown as McpAdminDb): McpAdminService => {\n return {\n async getStatus() {\n const enabledCache = await db.cache.findUnique({ where: { key: MCP_ENABLED_CACHE_KEY } });\n const activeToken = await db.mcpToken.findFirst({\n where: { revokedAt: null },\n orderBy: { createdAt: 'desc' },\n });\n\n return toStatus(enabledCache?.value === 'true', activeToken);\n },\n async setEnabled(enabled) {\n await db.cache.upsert({\n where: { key: MCP_ENABLED_CACHE_KEY },\n update: { value: String(enabled) },\n create: {\n key: MCP_ENABLED_CACHE_KEY,\n value: String(enabled),\n },\n });\n },\n async rotateToken() {\n const now = new Date();\n const issued = issueMcpToken();\n\n await db.$transaction(async (transaction) => {\n await transaction.mcpToken.updateMany({\n where: { revokedAt: null },\n data: { revokedAt: now },\n });\n await transaction.mcpToken.create({ data: { tokenHash: issued.hash } });\n });\n\n return { token: issued.plaintext };\n },\n async revokeActiveToken() {\n await db.mcpToken.updateMany({\n where: { revokedAt: null },\n data: { revokedAt: new Date() },\n });\n },\n async validatePresentedToken(token) {\n const activeToken = await db.mcpToken.findFirst({\n where: { revokedAt: null },\n orderBy: { createdAt: 'desc' },\n });\n\n if (!activeToken) {\n return {\n ok: false,\n reason: 'not_configured',\n };\n }\n\n if (!verifyMcpToken(activeToken.tokenHash, token)) {\n return {\n ok: false,\n reason: 'forbidden',\n };\n }\n\n await db.mcpToken.update({\n where: { id: activeToken.id },\n data: { lastUsedAt: new Date() },\n });\n\n return { ok: true };\n },\n };\n};\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,SAAS,eAAe,sBAAsB;AAE9C,MAAM,wBAAwB;AAsD9B,MAAM,WAAW,CACb,SACA,gBAKiB;AACjB,MAAI,CAAC,aAAa;AACd,WAAO;AAAA,MACH;AAAA,MACA,gBAAgB;AAAA,MAChB,OAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,IACA,gBAAgB;AAAA,IAChB,OAAO;AAAA,MACH,IAAI,OAAO,YAAY,EAAE;AAAA,MACzB,WAAW,YAAY,UAAU,YAAY;AAAA,MAC7C,YAAY,YAAY,aAAa,YAAY,WAAW,YAAY,IAAI;AAAA,IAChF;AAAA,EACJ;AACJ;AAEO,MAAM,wBAAwB,CAAC,KAAiB,WAAqD;AACxG,SAAO;AAAA,IACH,MAAM,YAAY;AACd,YAAM,eAAe,MAAM,GAAG,MAAM,WAAW,EAAE,OAAO,EAAE,KAAK,sBAAsB,EAAE,CAAC;AACxF,YAAM,cAAc,MAAM,GAAG,SAAS,UAAU;AAAA,QAC5C,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAED,aAAO,SAAS,cAAc,UAAU,QAAQ,WAAW;AAAA,IAC/D;AAAA,IACA,MAAM,WAAW,SAAS;AACtB,YAAM,GAAG,MAAM,OAAO;AAAA,QAClB,OAAO,EAAE,KAAK,sBAAsB;AAAA,QACpC,QAAQ,EAAE,OAAO,OAAO,OAAO,EAAE;AAAA,QACjC,QAAQ;AAAA,UACJ,KAAK;AAAA,UACL,OAAO,OAAO,OAAO;AAAA,QACzB;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,IACA,MAAM,cAAc;AAChB,YAAM,MAAM,oBAAI,KAAK;AACrB,YAAM,SAAS,cAAc;AAE7B,YAAM,GAAG,aAAa,OAAO,gBAAgB;AACzC,cAAM,YAAY,SAAS,WAAW;AAAA,UAClC,OAAO,EAAE,WAAW,KAAK;AAAA,UACzB,MAAM,EAAE,WAAW,IAAI;AAAA,QAC3B,CAAC;AACD,cAAM,YAAY,SAAS,OAAO,EAAE,MAAM,EAAE,WAAW,OAAO,KAAK,EAAE,CAAC;AAAA,MAC1E,CAAC;AAED,aAAO,EAAE,OAAO,OAAO,UAAU;AAAA,IACrC;AAAA,IACA,MAAM,oBAAoB;AACtB,YAAM,GAAG,SAAS,WAAW;AAAA,QACzB,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,MAAM,EAAE,WAAW,oBAAI,KAAK,EAAE;AAAA,MAClC,CAAC;AAAA,IACL;AAAA,IACA,MAAM,uBAAuB,OAAO;AAChC,YAAM,cAAc,MAAM,GAAG,SAAS,UAAU;AAAA,QAC5C,OAAO,EAAE,WAAW,KAAK;AAAA,QACzB,SAAS,EAAE,WAAW,OAAO;AAAA,MACjC,CAAC;AAED,UAAI,CAAC,aAAa;AACd,eAAO;AAAA,UACH,IAAI;AAAA,UACJ,QAAQ;AAAA,QACZ;AAAA,MACJ;AAEA,UAAI,CAAC,eAAe,YAAY,WAAW,KAAK,GAAG;AAC/C,eAAO;AAAA,UACH,IAAI;AAAA,UACJ,QAAQ;AAAA,QACZ;AAAA,MACJ;AAEA,YAAM,GAAG,SAAS,OAAO;AAAA,QACrB,OAAO,EAAE,IAAI,YAAY,GAAG;AAAA,QAC5B,MAAM,EAAE,YAAY,oBAAI,KAAK,EAAE;AAAA,MACnC,CAAC;AAED,aAAO,EAAE,IAAI,KAAK;AAAA,IACtB;AAAA,EACJ;AACJ;","names":[]}
@@ -0,0 +1,8 @@
1
+ import { createAllNotesQueryResolver, noteResolvers } from "./note.resolver.js";
2
+ import { noteTypeDefs } from "./note.type-defs.js";
3
+ export {
4
+ createAllNotesQueryResolver,
5
+ noteResolvers,
6
+ noteTypeDefs
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/graphql/index.ts"],"sourcesContent":["export { createAllNotesQueryResolver, noteResolvers } from './note.resolver.js';\nexport { noteTypeDefs } from './note.type-defs.js';\n"],"mappings":"AAAA,SAAS,6BAA6B,qBAAqB;AAC3D,SAAS,oBAAoB;","names":[]}