@nextsparkjs/core 0.1.0-beta.185 → 0.1.0-beta.187

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 (518) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  2. package/dist/components/app/guards/DeveloperGuard.js +8 -4
  3. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  4. package/dist/components/app/guards/SuperAdminGuard.js +8 -4
  5. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  6. package/dist/components/app/layouts/PublicFooter.js +2 -2
  7. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  8. package/dist/components/app/layouts/PublicNavbar.js +2 -2
  9. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  10. package/dist/components/app/misc/ThemeToggle.js +4 -1
  11. package/dist/components/auth/DevKeyring.d.ts.map +1 -1
  12. package/dist/components/auth/DevKeyring.js +10 -2
  13. package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
  14. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
  15. package/dist/components/auth/SessionCookieRefresher.js +11 -0
  16. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  17. package/dist/components/auth/forms/LoginForm.js +300 -6
  18. package/dist/components/auth/index.d.ts +1 -0
  19. package/dist/components/auth/index.d.ts.map +1 -1
  20. package/dist/components/auth/index.js +2 -0
  21. package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
  22. package/dist/components/billing/InvoicesTable.js +4 -3
  23. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  24. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  25. package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
  26. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  27. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
  28. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
  29. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
  30. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
  31. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  32. package/dist/components/dashboard/layouts/Sidebar.js +2 -3
  33. package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
  34. package/dist/components/dashboard/layouts/index.d.ts +1 -0
  35. package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
  36. package/dist/components/dashboard/layouts/index.js +2 -0
  37. package/dist/components/devtools/api-explorer/ApiExplorer.js +4 -4
  38. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  39. package/dist/components/entities/EntityFieldRenderer.js +3 -0
  40. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  41. package/dist/components/entities/EntityTable.js +2 -1
  42. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  43. package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
  44. package/dist/components/patterns/pages/PatternEditPage.d.ts +5 -1
  45. package/dist/components/patterns/pages/PatternEditPage.d.ts.map +1 -1
  46. package/dist/components/patterns/pages/PatternEditPage.js +16 -5
  47. package/dist/components/patterns/pages/PatternsCreatePage.d.ts +2 -1
  48. package/dist/components/patterns/pages/PatternsCreatePage.d.ts.map +1 -1
  49. package/dist/components/patterns/pages/PatternsCreatePage.js +17 -5
  50. package/dist/components/ui/combobox.d.ts +8 -1
  51. package/dist/components/ui/combobox.d.ts.map +1 -1
  52. package/dist/components/ui/combobox.js +37 -44
  53. package/dist/components/ui/country-select.d.ts +3 -1
  54. package/dist/components/ui/country-select.d.ts.map +1 -1
  55. package/dist/components/ui/country-select.js +3 -1
  56. package/dist/components/ui/currency-select.d.ts +3 -1
  57. package/dist/components/ui/currency-select.d.ts.map +1 -1
  58. package/dist/components/ui/currency-select.js +3 -1
  59. package/dist/components/ui/sheet.d.ts +5 -0
  60. package/dist/components/ui/sheet.d.ts.map +1 -1
  61. package/dist/components/ui/sheet.js +46 -26
  62. package/dist/components/ui/timezone-select.d.ts +3 -1
  63. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  64. package/dist/components/ui/timezone-select.js +3 -1
  65. package/dist/contexts/TeamContext.d.ts +6 -0
  66. package/dist/contexts/TeamContext.d.ts.map +1 -1
  67. package/dist/contexts/TeamContext.js +8 -1
  68. package/dist/emails/otp-verification.d.ts.map +1 -1
  69. package/dist/emails/reset-password.d.ts.map +1 -1
  70. package/dist/emails/team-invitation.d.ts.map +1 -1
  71. package/dist/emails/team-invitation.js +3 -2
  72. package/dist/emails/verify-email.d.ts.map +1 -1
  73. package/dist/hooks/index.d.ts +1 -0
  74. package/dist/hooks/index.d.ts.map +1 -1
  75. package/dist/hooks/index.js +1 -0
  76. package/dist/hooks/useAuth.d.ts +29 -0
  77. package/dist/hooks/useAuth.d.ts.map +1 -1
  78. package/dist/hooks/useAuth.js +24 -0
  79. package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
  80. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  81. package/dist/hooks/useEnsureUserMetadata.js +18 -5
  82. package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
  83. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
  84. package/dist/hooks/useSessionCookieRefresh.js +49 -0
  85. package/dist/lib/api/api-error.d.ts +3 -1
  86. package/dist/lib/api/api-error.d.ts.map +1 -1
  87. package/dist/lib/api/api-error.js +4 -2
  88. package/dist/lib/api/auth/dual-auth.d.ts +38 -3
  89. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  90. package/dist/lib/api/auth/dual-auth.js +42 -7
  91. package/dist/lib/api/auth/permissions.d.ts +7 -2
  92. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  93. package/dist/lib/api/auth/permissions.js +2 -2
  94. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  95. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  96. package/dist/lib/api/auth/scope-policy.js +53 -0
  97. package/dist/lib/api/auth.d.ts +15 -0
  98. package/dist/lib/api/auth.d.ts.map +1 -1
  99. package/dist/lib/api/auth.js +17 -4
  100. package/dist/lib/api/entities.js +2 -1
  101. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  102. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  103. package/dist/lib/api/entity/audit-log.js +35 -0
  104. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  105. package/dist/lib/api/entity/generic-handler.js +333 -138
  106. package/dist/lib/api/entity-handler.js +2 -2
  107. package/dist/lib/api/helpers.d.ts +29 -4
  108. package/dist/lib/api/helpers.d.ts.map +1 -1
  109. package/dist/lib/api/helpers.js +30 -6
  110. package/dist/lib/api/keys.d.ts +16 -0
  111. package/dist/lib/api/keys.d.ts.map +1 -1
  112. package/dist/lib/api/keys.js +19 -1
  113. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  114. package/dist/lib/api/routes/patterns-usages.js +3 -11
  115. package/dist/lib/auth/auth-methods.d.ts +52 -0
  116. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  117. package/dist/lib/auth/auth-methods.js +63 -0
  118. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  119. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  120. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  121. package/dist/lib/auth/registration-helpers.js +8 -2
  122. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  123. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  124. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  125. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  126. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  127. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  128. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  129. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  130. package/dist/lib/auth/security-notifications/index.js +22 -0
  131. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  132. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  133. package/dist/lib/auth/security-notifications/registry.js +26 -0
  134. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  135. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  136. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  137. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  138. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  139. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  140. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  141. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  142. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  143. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  144. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  145. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  146. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  147. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  148. package/dist/lib/auth/session-config.d.ts +37 -0
  149. package/dist/lib/auth/session-config.d.ts.map +1 -0
  150. package/dist/lib/auth/session-config.js +59 -0
  151. package/dist/lib/auth/session-refresh.d.ts +46 -0
  152. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  153. package/dist/lib/auth/session-refresh.js +11 -0
  154. package/dist/lib/auth-client.d.ts +1639 -0
  155. package/dist/lib/auth-client.d.ts.map +1 -0
  156. package/dist/lib/auth-client.js +13 -1
  157. package/dist/lib/auth.d.ts.map +1 -1
  158. package/dist/lib/auth.js +45 -11
  159. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  160. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  161. package/dist/lib/billing/gateways/factory.js +37 -23
  162. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  163. package/dist/lib/billing/stripe-webhook.js +2 -1
  164. package/dist/lib/config/app.config.d.ts.map +1 -1
  165. package/dist/lib/config/app.config.js +71 -0
  166. package/dist/lib/config/config-sync.d.ts +1 -4
  167. package/dist/lib/config/config-sync.d.ts.map +1 -1
  168. package/dist/lib/config/config-sync.js +6 -2
  169. package/dist/lib/config/types.d.ts +130 -0
  170. package/dist/lib/config/types.d.ts.map +1 -1
  171. package/dist/lib/email/templates.d.ts +3 -3
  172. package/dist/lib/email/templates.d.ts.map +1 -1
  173. package/dist/lib/entities/api-generator.js +5 -5
  174. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  175. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  176. package/dist/lib/entities/entity-hooks.js +77 -4
  177. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  178. package/dist/lib/entities/schema-generator.js +20 -4
  179. package/dist/lib/entities/serialization.d.ts +2 -2
  180. package/dist/lib/entities/serialization.d.ts.map +1 -1
  181. package/dist/lib/entities/serialization.js +3 -2
  182. package/dist/lib/entities/types.d.ts +20 -0
  183. package/dist/lib/entities/types.d.ts.map +1 -1
  184. package/dist/lib/locale.d.ts.map +1 -1
  185. package/dist/lib/locale.js +4 -1
  186. package/dist/lib/mcp/audit.d.ts +8 -4
  187. package/dist/lib/mcp/audit.d.ts.map +1 -1
  188. package/dist/lib/plugins/hook-system.d.ts +13 -0
  189. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  190. package/dist/lib/plugins/hook-system.js +32 -2
  191. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  192. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  193. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  194. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  195. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  196. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  197. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  198. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  199. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  200. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  201. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  202. package/dist/lib/selectors/selectors.d.ts +24 -0
  203. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  204. package/dist/lib/services/api-docs.service.d.ts +5 -1
  205. package/dist/lib/services/api-docs.service.d.ts.map +1 -1
  206. package/dist/lib/services/api-presets.service.d.ts +6 -1
  207. package/dist/lib/services/api-presets.service.d.ts.map +1 -1
  208. package/dist/lib/services/feature.service.d.ts +58 -4
  209. package/dist/lib/services/feature.service.d.ts.map +1 -1
  210. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  211. package/dist/lib/services/generic-entity.service.js +16 -32
  212. package/dist/lib/services/membership.service.d.ts +10 -2
  213. package/dist/lib/services/membership.service.d.ts.map +1 -1
  214. package/dist/lib/services/membership.service.js +37 -16
  215. package/dist/lib/services/permission.service.d.ts +10 -4
  216. package/dist/lib/services/permission.service.d.ts.map +1 -1
  217. package/dist/lib/services/plugin.service.d.ts +1 -1
  218. package/dist/lib/services/plugin.service.d.ts.map +1 -1
  219. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  220. package/dist/lib/services/subscription.service.js +2 -1
  221. package/dist/lib/services/team-member.service.d.ts +15 -3
  222. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  223. package/dist/lib/services/team-member.service.js +27 -6
  224. package/dist/lib/services/team.service.d.ts.map +1 -1
  225. package/dist/lib/teams/permissions.d.ts +3 -1
  226. package/dist/lib/teams/permissions.d.ts.map +1 -1
  227. package/dist/lib/teams/permissions.js +6 -0
  228. package/dist/lib/teams/schema.d.ts.map +1 -1
  229. package/dist/lib/teams/schema.js +3 -0
  230. package/dist/lib/template-resolver.d.ts +3 -3
  231. package/dist/lib/theme/forced-theme.d.ts +34 -0
  232. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  233. package/dist/lib/theme/forced-theme.js +27 -0
  234. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  235. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  236. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  237. package/dist/lib/utils/cors.d.ts +13 -0
  238. package/dist/lib/utils/cors.d.ts.map +1 -1
  239. package/dist/lib/utils/cors.js +20 -0
  240. package/dist/messages/de/auth.json +25 -2
  241. package/dist/messages/de/index.d.ts +83 -0
  242. package/dist/messages/de/index.d.ts.map +1 -1
  243. package/dist/messages/de/index.js +2 -0
  244. package/dist/messages/de/index.ts +2 -0
  245. package/dist/messages/de/navigation.json +3 -1
  246. package/dist/messages/en/auth.json +25 -2
  247. package/dist/messages/en/index.d.ts +83 -0
  248. package/dist/messages/en/index.d.ts.map +1 -1
  249. package/dist/messages/en/index.js +2 -0
  250. package/dist/messages/en/index.ts +2 -0
  251. package/dist/messages/en/navigation.json +3 -1
  252. package/dist/messages/es/auth.json +25 -2
  253. package/dist/messages/es/index.d.ts +83 -0
  254. package/dist/messages/es/index.d.ts.map +1 -1
  255. package/dist/messages/es/index.js +2 -0
  256. package/dist/messages/es/index.ts +2 -0
  257. package/dist/messages/es/navigation.json +3 -1
  258. package/dist/messages/fr/auth.json +25 -2
  259. package/dist/messages/fr/index.d.ts +83 -0
  260. package/dist/messages/fr/index.d.ts.map +1 -1
  261. package/dist/messages/fr/index.js +2 -0
  262. package/dist/messages/fr/index.ts +2 -0
  263. package/dist/messages/fr/navigation.json +3 -1
  264. package/dist/messages/it/auth.json +25 -2
  265. package/dist/messages/it/index.d.ts +83 -0
  266. package/dist/messages/it/index.d.ts.map +1 -1
  267. package/dist/messages/it/index.js +2 -0
  268. package/dist/messages/it/index.ts +2 -0
  269. package/dist/messages/it/navigation.json +3 -1
  270. package/dist/messages/pt/auth.json +25 -2
  271. package/dist/messages/pt/index.d.ts +83 -0
  272. package/dist/messages/pt/index.d.ts.map +1 -1
  273. package/dist/messages/pt/index.js +2 -0
  274. package/dist/messages/pt/index.ts +2 -0
  275. package/dist/messages/pt/navigation.json +3 -1
  276. package/dist/migrations/005_api_audit_log.sql +2 -1
  277. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  278. package/dist/migrations/026_login_events.sql +91 -0
  279. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  280. package/dist/nextspark-registries.d.ts +580 -236
  281. package/dist/providers/DashboardProviders.d.ts +12 -3
  282. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  283. package/dist/providers/DashboardProviders.js +6 -2
  284. package/dist/providers/theme-provider.d.ts +19 -2
  285. package/dist/providers/theme-provider.d.ts.map +1 -1
  286. package/dist/providers/theme-provider.js +6 -2
  287. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  288. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  289. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  290. package/dist/templates/app/api/health/route.ts +4 -2
  291. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  292. package/dist/templates/app/api/mcp/route.ts +4 -1
  293. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  294. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  295. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  296. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  297. package/dist/templates/app/api/v1/api-keys/route.ts +21 -43
  298. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  299. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  300. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  301. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  302. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  303. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  304. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  305. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  306. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  307. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  308. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  309. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  310. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  311. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  312. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  313. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  314. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  315. package/dist/templates/app/api/v1/media/route.ts +5 -9
  316. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  317. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  318. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  319. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  320. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  321. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  322. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  323. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  324. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  325. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  326. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  327. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  328. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  329. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  330. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  331. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  332. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  333. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  334. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  335. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  336. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  337. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  338. package/dist/templates/app/api/v1/users/route.ts +21 -25
  339. package/dist/templates/app/dashboard/layout.tsx +2 -6
  340. package/dist/templates/app/layout.ppr.tsx +15 -15
  341. package/dist/templates/app/layout.tsx +46 -20
  342. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  343. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  344. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  345. package/dist/templates/proxy.ts +46 -10
  346. package/dist/templates/tsconfig.json +4 -0
  347. package/dist/types/theme.d.ts +9 -0
  348. package/dist/types/theme.d.ts.map +1 -1
  349. package/migrations/005_api_audit_log.sql +2 -1
  350. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  351. package/migrations/026_login_events.sql +91 -0
  352. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  353. package/package.json +14 -11
  354. package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
  355. package/scripts/build/registry/generators/scope-registry.mjs +9 -2
  356. package/templates/app/(auth)/signup/page.tsx +8 -0
  357. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  358. package/templates/app/api/auth/[...all]/route.ts +23 -1
  359. package/templates/app/api/health/route.ts +4 -2
  360. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  361. package/templates/app/api/mcp/route.ts +4 -1
  362. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  363. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  364. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  365. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  366. package/templates/app/api/v1/api-keys/route.ts +21 -43
  367. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  368. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  369. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  370. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  371. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  372. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  373. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  374. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  375. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  376. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  377. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  378. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  379. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  380. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  381. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  382. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  383. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  384. package/templates/app/api/v1/media/route.ts +5 -9
  385. package/templates/app/api/v1/media/upload/route.ts +9 -21
  386. package/templates/app/api/v1/media-tags/route.ts +9 -13
  387. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  388. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  389. package/templates/app/api/v1/post-categories/route.ts +5 -4
  390. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  391. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  392. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  393. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  394. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  395. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  396. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  397. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  398. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  399. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  400. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  401. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  402. package/templates/app/api/v1/teams/route.ts +33 -27
  403. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  404. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  405. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  406. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  407. package/templates/app/api/v1/users/route.ts +21 -25
  408. package/templates/app/dashboard/layout.tsx +2 -6
  409. package/templates/app/layout.ppr.tsx +15 -15
  410. package/templates/app/layout.tsx +46 -20
  411. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  412. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  413. package/templates/contents/themes/starter/styles/globals.css +6 -2
  414. package/templates/proxy.ts +46 -10
  415. package/templates/tsconfig.json +4 -0
  416. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  417. package/tests/jest/__mocks__/next-server.js +24 -0
  418. package/dist/components/teams/TeamSwitchModal.d.js +0 -0
  419. package/dist/components/ui/accordion.d.js +0 -0
  420. package/dist/components/ui/avatar.d.js +0 -0
  421. package/dist/components/ui/badge.d.js +0 -0
  422. package/dist/components/ui/button.d.js +0 -0
  423. package/dist/components/ui/card.d.js +0 -0
  424. package/dist/components/ui/dialog.d.js +0 -0
  425. package/dist/components/ui/dropdown-menu.d.js +0 -0
  426. package/dist/components/ui/input.d.js +0 -0
  427. package/dist/components/ui/progress.d.js +0 -0
  428. package/dist/components/ui/scroll-area.d.js +0 -0
  429. package/dist/components/ui/select.d.js +0 -0
  430. package/dist/components/ui/separator.d.js +0 -0
  431. package/dist/components/ui/table.d.js +0 -0
  432. package/dist/components/ui/textarea.d.js +0 -0
  433. package/dist/contexts/TeamContext.d.js +0 -0
  434. package/dist/hooks/useAuth.d.js +0 -0
  435. package/dist/hooks/useLastAuthMethod.d.js +0 -0
  436. package/dist/hooks/useOrigin.d.js +0 -0
  437. package/dist/lib/api/auth/dual-auth.d.js +0 -0
  438. package/dist/lib/api/auth.d.js +0 -0
  439. package/dist/lib/api/cache.d.js +0 -0
  440. package/dist/lib/api/keys.d.js +0 -0
  441. package/dist/lib/auth-client.d.js +0 -0
  442. package/dist/lib/auth-context.d.js +0 -0
  443. package/dist/lib/auth.d.js +0 -0
  444. package/dist/lib/billing/config-types.d.js +0 -0
  445. package/dist/lib/billing/enforcement.d.js +0 -0
  446. package/dist/lib/billing/helpers.d.js +0 -0
  447. package/dist/lib/billing/jobs.d.js +0 -0
  448. package/dist/lib/billing/types.d.js +0 -0
  449. package/dist/lib/blocks/loader.d.js +0 -0
  450. package/dist/lib/config/app.config.d.js +0 -0
  451. package/dist/lib/config/config-loader.d.js +0 -0
  452. package/dist/lib/config/config-sync.d.js +0 -0
  453. package/dist/lib/config/config-types.d.js +0 -0
  454. package/dist/lib/config/config-utils.d.js +0 -0
  455. package/dist/lib/config/dashboard-types.d.js +0 -0
  456. package/dist/lib/config/dashboard.config.d.js +0 -0
  457. package/dist/lib/config/icon-map.d.js +0 -0
  458. package/dist/lib/config/index.d.js +0 -7
  459. package/dist/lib/config/nextspark-types.d.js +0 -0
  460. package/dist/lib/config/roles-merge.d.js +0 -0
  461. package/dist/lib/config/types.d.js +0 -0
  462. package/dist/lib/db.d.js +0 -0
  463. package/dist/lib/email/factory.d.js +0 -0
  464. package/dist/lib/email/index.d.js +0 -3
  465. package/dist/lib/email/providers/console.d.js +0 -0
  466. package/dist/lib/email/providers/resend.d.js +0 -0
  467. package/dist/lib/email/templates.d.js +0 -0
  468. package/dist/lib/email/types.d.js +0 -0
  469. package/dist/lib/entities/types.d.js +0 -0
  470. package/dist/lib/permissions/system.d.js +0 -0
  471. package/dist/lib/permissions/types.d.js +0 -0
  472. package/dist/lib/plugins/hook-system.d.js +0 -0
  473. package/dist/lib/scheduled-actions/cleanup.d.js +0 -0
  474. package/dist/lib/scheduled-actions/index.d.js +0 -19
  475. package/dist/lib/scheduled-actions/initializer.d.js +0 -1
  476. package/dist/lib/scheduled-actions/processor.d.js +0 -0
  477. package/dist/lib/scheduled-actions/registry.d.js +0 -0
  478. package/dist/lib/scheduled-actions/scheduler.d.js +0 -0
  479. package/dist/lib/scheduled-actions/types.d.js +0 -0
  480. package/dist/lib/services/api-routes.service.d.js +0 -0
  481. package/dist/lib/services/entity-type.service.d.js +0 -0
  482. package/dist/lib/services/index.d.js +0 -40
  483. package/dist/lib/services/invoice.service.d.js +0 -0
  484. package/dist/lib/services/membership.service.d.js +0 -0
  485. package/dist/lib/services/meta.service.d.js +0 -0
  486. package/dist/lib/services/middleware.service.d.js +0 -0
  487. package/dist/lib/services/namespace.service.d.js +0 -0
  488. package/dist/lib/services/permission.service.d.js +0 -0
  489. package/dist/lib/services/plan.service.d.js +0 -0
  490. package/dist/lib/services/plugin.service.d.js +0 -0
  491. package/dist/lib/services/route-handler.service.d.js +0 -0
  492. package/dist/lib/services/scope.service.d.js +0 -0
  493. package/dist/lib/services/subscription.service.d.js +0 -0
  494. package/dist/lib/services/team-member.service.d.js +0 -0
  495. package/dist/lib/services/team.service.d.js +0 -0
  496. package/dist/lib/services/theme.service.d.js +0 -0
  497. package/dist/lib/services/usage.service.d.js +0 -0
  498. package/dist/lib/services/user-flags.service.d.js +0 -0
  499. package/dist/lib/services/user.service.d.js +0 -0
  500. package/dist/lib/teams/helpers.d.js +0 -0
  501. package/dist/lib/teams/permissions.d.js +0 -0
  502. package/dist/lib/teams/types.d.js +0 -0
  503. package/dist/lib/utils/config-merge.d.js +0 -0
  504. package/dist/lib/utils/file-logger.d.js +0 -0
  505. package/dist/lib/utils.d.js +0 -0
  506. package/dist/styles/classes.json +0 -1093
  507. package/dist/styles/ui.css +0 -2
  508. package/dist/types/auth.d.js +0 -0
  509. package/dist/types/blocks.d.js +0 -0
  510. package/dist/types/meta.types.d.js +0 -0
  511. package/dist/types/plugin.d.js +0 -0
  512. package/dist/types/testing.types.d.js +0 -0
  513. package/dist/types/theme.d.js +0 -0
  514. package/dist/types/user.types.d.js +0 -0
  515. package/scripts/build/build-ui-css.mjs +0 -351
  516. /package/dist/{components/app/layouts/PublicFooter.d.js → lib/auth/security-notifications/types.js} +0 -0
  517. /package/dist/{components/app/layouts/PublicNavbar.d.js → nextspark-registries.d.js} +0 -0
  518. /package/dist/{components/app/misc/ThemeToggle.d.js → types/use-intl-react-shim.d.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from "next/server";
2
- import { authenticateRequest, hasRequiredScope, canBypassTeamContext } from "../auth/dual-auth.js";
2
+ import { authenticateRequest, createAuthFailureResponse, canBypassTeamContext } from "../auth/dual-auth.js";
3
3
  import { resolveEntityFromUrl, validateEntityOperation } from "./resolver.js";
4
4
  import { generateEntitySchemas } from "../../entities/schema-generator.js";
5
5
  import { queryWithRLS, mutateWithRLS, queryOneWithRLS } from "../../db.js";
@@ -17,7 +17,8 @@ import {
17
17
  addCorsHeaders,
18
18
  handleCorsPreflightRequest
19
19
  } from "../helpers.js";
20
- import { afterEntityCreate, afterEntityUpdate, afterEntityDelete } from "../../entities/entity-hooks.js";
20
+ import { beforeEntityCreate, afterEntityCreate, beforeEntityUpdate, afterEntityUpdate, beforeEntityDelete, afterEntityDelete, beforeEntityRead, afterEntityRead } from "../../entities/entity-hooks.js";
21
+ import { logGenericHandlerUsage } from "./audit-log.js";
21
22
  import { checkPermission } from "../../permissions/check.js";
22
23
  import { extractPatternIds } from "../../blocks/pattern-resolver.js";
23
24
  import { isPatternReference } from "../../../types/pattern-reference.js";
@@ -169,6 +170,102 @@ function parseTaxonomyFilterParams(url, entityConfig) {
169
170
  function getTableName(entityConfig) {
170
171
  return entityConfig.tableName || entityConfig.slug;
171
172
  }
173
+ function isEntityField(entityConfig, fieldName) {
174
+ return entityConfig.fields.some((field) => field.name === fieldName);
175
+ }
176
+ const LIST_RESERVED_PARAMS = /* @__PURE__ */ new Set([
177
+ "page",
178
+ "limit",
179
+ "fields",
180
+ "distinct",
181
+ "ids",
182
+ "parentId",
183
+ "search",
184
+ "sortBy",
185
+ "sortOrder",
186
+ "sort",
187
+ "order",
188
+ "child",
189
+ "meta",
190
+ "metas",
191
+ "includeMeta",
192
+ "userId",
193
+ "from",
194
+ "to",
195
+ "dateField",
196
+ "userFiltered"
197
+ ]);
198
+ const TAXONOMY_FILTER_PARAMS = ["categoryId", "taxonomyId", "taxonomyType"];
199
+ const SEARCHABLE_FIELDS = ["name", "title", "slug", "content"];
200
+ const SORTABLE_BASE_FIELDS = ["id", "createdAt", "updatedAt", "teamId"];
201
+ const BARE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
202
+ function omitHandlerManagedKeys(entityConfig, data) {
203
+ var _a;
204
+ const managed = /* @__PURE__ */ new Set(["userId", "teamId"]);
205
+ if ((_a = entityConfig.taxonomies) == null ? void 0 : _a.enabled) {
206
+ for (const taxonomyType of entityConfig.taxonomies.types ?? []) {
207
+ managed.add(taxonomyType.field);
208
+ }
209
+ }
210
+ return Object.fromEntries(Object.entries(data).filter(([key]) => !managed.has(key)));
211
+ }
212
+ function validationErrorMessage(issues) {
213
+ const unknownKeys = issues.filter((issue) => issue.code === "unrecognized_keys").flatMap((issue) => issue.keys ?? []);
214
+ return unknownKeys.length > 0 ? `Unknown field(s): ${unknownKeys.join(", ")}` : "Validation error";
215
+ }
216
+ function asPgError(error) {
217
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
218
+ return error;
219
+ }
220
+ return null;
221
+ }
222
+ function mapConstraintViolation(error, operation) {
223
+ const pgError = asPgError(error);
224
+ if (!pgError) return null;
225
+ const { constraint, detail, table } = pgError;
226
+ switch (pgError.code) {
227
+ case "23505":
228
+ return createApiError(
229
+ "A record with this value already exists",
230
+ 409,
231
+ { constraint, detail },
232
+ "UNIQUE_CONSTRAINT_VIOLATION"
233
+ );
234
+ case "23514":
235
+ return createApiError(
236
+ constraint ? `Value rejected by check constraint "${constraint}"` : "Value rejected by a database check constraint",
237
+ 422,
238
+ { constraint, table },
239
+ "CHECK_CONSTRAINT_VIOLATION"
240
+ );
241
+ case "23503":
242
+ if (operation === "delete") {
243
+ return createApiError(
244
+ detail || "Cannot delete: this record is referenced by other records",
245
+ 409,
246
+ { constraint },
247
+ "FOREIGN_KEY_VIOLATION"
248
+ );
249
+ }
250
+ return createApiError(
251
+ detail ? `Referenced record does not exist: ${detail}` : "Referenced record does not exist",
252
+ 422,
253
+ { constraint, detail },
254
+ "FOREIGN_KEY_VIOLATION"
255
+ );
256
+ default:
257
+ return null;
258
+ }
259
+ }
260
+ function hookRejectionStatus(error) {
261
+ if (error && typeof error === "object") {
262
+ const candidate = error.status ?? error.statusCode;
263
+ if (typeof candidate === "number" && candidate >= 400 && candidate < 500) {
264
+ return candidate;
265
+ }
266
+ }
267
+ return 400;
268
+ }
172
269
  async function filterOrphanedPatternReferences(blocks, userId) {
173
270
  if (!Array.isArray(blocks) || blocks.length === 0) return blocks;
174
271
  const patternIds = extractPatternIds(blocks);
@@ -291,7 +388,24 @@ async function validateTeamContextWithBypass(request, authResult, userId) {
291
388
  }
292
389
  return { valid: true, teamId, isBypass: false };
293
390
  }
391
+ async function runWithAuditLog(request, run) {
392
+ const startedAt = Date.now();
393
+ const audit = { auth: null };
394
+ let statusCode = 500;
395
+ try {
396
+ const response = await run(audit);
397
+ statusCode = response.status;
398
+ return response;
399
+ } finally {
400
+ logGenericHandlerUsage(audit.auth, request, statusCode, Date.now() - startedAt).catch((error) => {
401
+ console.error("[generic-handler:audit] unexpected audit failure:", error);
402
+ });
403
+ }
404
+ }
294
405
  async function handleGenericList(request) {
406
+ return runWithAuditLog(request, (audit) => handleGenericListImpl(request, audit));
407
+ }
408
+ async function handleGenericListImpl(request, audit) {
295
409
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
296
410
  try {
297
411
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -303,25 +417,22 @@ async function handleGenericList(request) {
303
417
  const response2 = createApiError("List operation not supported for this entity", 405);
304
418
  return addCorsHeaders(response2, request);
305
419
  }
306
- const authResult = await authenticateRequest(request);
420
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
421
+ audit.auth = authResult;
307
422
  let userId = null;
308
423
  let teamId = null;
309
424
  let isBypass = false;
425
+ if (!authResult.success && authResult.type === "api-key") {
426
+ return addCorsHeaders(createAuthFailureResponse(authResult), request);
427
+ }
310
428
  if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
311
429
  teamId = request.headers.get("x-team-id") ?? null;
312
430
  } else if (!authResult.success) {
313
- return NextResponse.json(
314
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
315
- { status: 401 }
316
- );
431
+ return createAuthFailureResponse(authResult);
317
432
  } else {
318
433
  if (authResult.rateLimitResponse) {
319
434
  return authResult.rateLimitResponse;
320
435
  }
321
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
322
- const response2 = createApiError("Insufficient permissions", 403);
323
- return addCorsHeaders(response2, request);
324
- }
325
436
  userId = authResult.user.id;
326
437
  const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
327
438
  if (!teamValidation.valid) {
@@ -341,10 +452,9 @@ async function handleGenericList(request) {
341
452
  const distinctParam = url.searchParams.get("distinct") === "true";
342
453
  const idsFromParams = url.searchParams.getAll("ids");
343
454
  const specificIds = idsFromParams.length > 0 ? idsFromParams.flatMap((param) => param.split(",").map((id) => id.trim())).filter(Boolean) : null;
344
- const knownParams = /* @__PURE__ */ new Set(["page", "limit", "fields", "distinct", "ids", "parentId", "search", "sortBy", "sortOrder", "child", "meta", "from", "to", "dateField"]);
345
455
  const customFilters = {};
346
456
  url.searchParams.forEach((value, key) => {
347
- if (!knownParams.has(key) && value) {
457
+ if (!LIST_RESERVED_PARAMS.has(key) && value) {
348
458
  if (!customFilters[key]) {
349
459
  customFilters[key] = [];
350
460
  }
@@ -353,9 +463,59 @@ async function handleGenericList(request) {
353
463
  }
354
464
  });
355
465
  const entityConfig = resolution.entityConfig;
466
+ const shapedQuery = await beforeEntityRead(entityConfig.slug, { ...customFilters }, userId ?? void 0);
467
+ for (const [key, value] of Object.entries(shapedQuery)) {
468
+ customFilters[key] = Array.isArray(value) ? value.map(String) : [String(value)];
469
+ }
470
+ const taxonomyFilterParams = ((_b = entityConfig.taxonomies) == null ? void 0 : _b.enabled) ? TAXONOMY_FILTER_PARAMS : [];
471
+ const invalidFilterKeys = Object.keys(customFilters).filter(
472
+ (key) => !isEntityField(entityConfig, key) && !taxonomyFilterParams.includes(key)
473
+ );
474
+ if (invalidFilterKeys.length > 0) {
475
+ const response2 = createApiError(
476
+ `Unknown filter parameter(s) for ${entityConfig.slug}: ${invalidFilterKeys.join(", ")}`,
477
+ 400,
478
+ {
479
+ invalidKeys: invalidFilterKeys,
480
+ allowedKeys: [...entityConfig.fields.map((f) => f.name), ...taxonomyFilterParams]
481
+ },
482
+ "INVALID_FILTER"
483
+ );
484
+ return addCorsHeaders(response2, request);
485
+ }
486
+ const searchParam = url.searchParams.get("search");
487
+ const searchableFields = SEARCHABLE_FIELDS.filter((name) => isEntityField(entityConfig, name));
488
+ if (searchParam && searchParam.trim() !== "" && searchableFields.length === 0) {
489
+ const response2 = createApiError(
490
+ `Entity ${entityConfig.slug} has no searchable field (${SEARCHABLE_FIELDS.join(", ")}); the search parameter is not supported`,
491
+ 400,
492
+ { searchableFields: SEARCHABLE_FIELDS },
493
+ "SEARCH_NOT_SUPPORTED"
494
+ );
495
+ return addCorsHeaders(response2, request);
496
+ }
497
+ const sortByParam = url.searchParams.get("sortBy") || url.searchParams.get("sort");
498
+ if (sortByParam && !isEntityField(entityConfig, sortByParam) && !SORTABLE_BASE_FIELDS.includes(sortByParam)) {
499
+ const response2 = createApiError(
500
+ `Invalid sortBy field for ${entityConfig.slug}: ${sortByParam}`,
501
+ 400,
502
+ { allowedFields: [...SORTABLE_BASE_FIELDS, ...entityConfig.fields.map((f) => f.name)] },
503
+ "INVALID_SORT_FIELD"
504
+ );
505
+ return addCorsHeaders(response2, request);
506
+ }
356
507
  let fields;
357
508
  if (fieldsParam && distinctParam) {
358
509
  const fieldName = fieldsParam;
510
+ if (!isEntityField(entityConfig, fieldName)) {
511
+ const response2 = createApiError(
512
+ `Invalid field for distinct query: ${fieldName}`,
513
+ 400,
514
+ void 0,
515
+ "INVALID_FIELD"
516
+ );
517
+ return addCorsHeaders(response2, request);
518
+ }
359
519
  const isJsonbField = fieldName === "contentLanguages" || fieldName === "brandValues" || fieldName === "hashtags";
360
520
  if (isJsonbField) {
361
521
  return handleDistinctJsonbField(entityConfig, fieldName, url, userId);
@@ -364,19 +524,17 @@ async function handleGenericList(request) {
364
524
  }
365
525
  } else if (fieldsParam) {
366
526
  const requestedFields = fieldsParam.split(",").map((f) => f.trim());
367
- const validFields = requestedFields.filter(
368
- (fieldName) => entityConfig.fields.some((field) => field.name === fieldName)
369
- );
527
+ const validFields = requestedFields.filter((fieldName) => isEntityField(entityConfig, fieldName));
370
528
  fields = ["id", ...validFields].map((fieldName) => {
371
529
  const columnName = /[A-Z]/.test(fieldName) ? `"${fieldName}"` : fieldName;
372
530
  return `t.${columnName}`;
373
531
  }).join(", ");
374
532
  } else {
375
533
  const systemFields = ["id", "userId", "teamId", "createdAt", "updatedAt"];
376
- if ((_b = entityConfig.builder) == null ? void 0 : _b.enabled) {
534
+ if ((_c = entityConfig.builder) == null ? void 0 : _c.enabled) {
377
535
  systemFields.push("blocks");
378
536
  }
379
- if ((_c = entityConfig.table) == null ? void 0 : _c.softDelete) {
537
+ if ((_d = entityConfig.table) == null ? void 0 : _d.softDelete) {
380
538
  systemFields.push("deletedAt", "deletedBy");
381
539
  }
382
540
  const configFields = entityConfig.fields.map((field) => {
@@ -395,16 +553,16 @@ async function handleGenericList(request) {
395
553
  let query;
396
554
  let queryParams;
397
555
  let paramIndex = 1;
398
- const searchParam = url.searchParams.get("search");
399
556
  const fromDate = url.searchParams.get("from");
400
557
  const toDate = url.searchParams.get("to");
401
558
  const dateFieldName = url.searchParams.get("dateField");
402
- const isPublicEntity = ((_d = entityConfig.access) == null ? void 0 : _d.public) === true;
403
- const requestingPublishedOnly = (_e = customFilters["status"]) == null ? void 0 : _e.includes("published");
559
+ const isPublicEntity = ((_e = entityConfig.access) == null ? void 0 : _e.public) === true;
560
+ const requestingPublishedOnly = (_f = customFilters["status"]) == null ? void 0 : _f.includes("published");
404
561
  const skipUserFilter = isPublicEntity && requestingPublishedOnly;
405
562
  if (skipUserFilter && process.env.NODE_ENV === "development") {
406
563
  console.log(`[GenericHandler] Public access mode: skipping userId filter for ${entityConfig.slug} (status=published)`);
407
564
  }
565
+ const forcePublishedOnly = isPublicEntity && userId === null && entityConfig.fields.some((f) => f.name === "status");
408
566
  const tableName = getTableName(entityConfig);
409
567
  const taxonomyFilter = parseTaxonomyFilterParams(url, entityConfig);
410
568
  let ownershipFilter = { applies: false };
@@ -429,7 +587,10 @@ async function handleGenericList(request) {
429
587
  query += ` AND t."teamId" = $${paramIndex++}`;
430
588
  queryParams.push(teamId);
431
589
  }
432
- if (userId && !((_f = entityConfig.access) == null ? void 0 : _f.shared) && !skipUserFilter && !isBypass) {
590
+ if (forcePublishedOnly) {
591
+ query += ` AND t."status" = 'published'`;
592
+ }
593
+ if (userId && !((_g = entityConfig.access) == null ? void 0 : _g.shared) && !skipUserFilter && !isBypass) {
433
594
  query += ` AND t."userId" = $${paramIndex++}`;
434
595
  queryParams.push(userId);
435
596
  }
@@ -437,7 +598,7 @@ async function handleGenericList(request) {
437
598
  query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
438
599
  queryParams.push(ownershipFilter.value);
439
600
  }
440
- if (((_g = entityConfig.table) == null ? void 0 : _g.softDelete) && !isBypass) {
601
+ if (((_h = entityConfig.table) == null ? void 0 : _h.softDelete) && !isBypass) {
441
602
  query += ` AND t."deletedAt" IS NULL`;
442
603
  }
443
604
  } else if (fieldsParam && distinctParam) {
@@ -453,7 +614,7 @@ async function handleGenericList(request) {
453
614
  query += ` AND t."parentId" = $${paramIndex++}`;
454
615
  queryParams.push(parentId);
455
616
  }
456
- if (userId && !((_h = entityConfig.access) == null ? void 0 : _h.shared) && !skipUserFilter && !isBypass) {
617
+ if (userId && !((_i = entityConfig.access) == null ? void 0 : _i.shared) && !skipUserFilter && !isBypass) {
457
618
  query += ` AND t."userId" = $${paramIndex++}`;
458
619
  queryParams.push(userId);
459
620
  }
@@ -461,9 +622,12 @@ async function handleGenericList(request) {
461
622
  query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
462
623
  queryParams.push(ownershipFilter.value);
463
624
  }
464
- if (((_i = entityConfig.table) == null ? void 0 : _i.softDelete) && !isBypass) {
625
+ if (((_j = entityConfig.table) == null ? void 0 : _j.softDelete) && !isBypass) {
465
626
  query += ` AND t."deletedAt" IS NULL`;
466
627
  }
628
+ if (forcePublishedOnly) {
629
+ query += ` AND t."status" = 'published'`;
630
+ }
467
631
  query += ` ORDER BY "${fieldName}" ASC LIMIT $${paramIndex++}`;
468
632
  queryParams.push(pagination.limit);
469
633
  } else {
@@ -474,7 +638,7 @@ async function handleGenericList(request) {
474
638
  whereConditions.push(`t."teamId" = $${paramIndex++}`);
475
639
  queryParams.push(teamId);
476
640
  }
477
- if (userId && !((_j = entityConfig.access) == null ? void 0 : _j.shared) && !skipUserFilter && !isBypass) {
641
+ if (userId && !((_k = entityConfig.access) == null ? void 0 : _k.shared) && !skipUserFilter && !isBypass) {
478
642
  whereConditions.push(`t."userId" = $${paramIndex++}`);
479
643
  queryParams.push(userId);
480
644
  }
@@ -482,33 +646,18 @@ async function handleGenericList(request) {
482
646
  whereConditions.push(`t."${ownershipFilter.field}" = $${paramIndex++}`);
483
647
  queryParams.push(ownershipFilter.value);
484
648
  }
485
- if (((_k = entityConfig.table) == null ? void 0 : _k.softDelete) && !isBypass) {
649
+ if (((_l = entityConfig.table) == null ? void 0 : _l.softDelete) && !isBypass) {
486
650
  whereConditions.push(`t."deletedAt" IS NULL`);
487
651
  }
488
- if (searchParam && searchParam.trim() !== "") {
652
+ if (forcePublishedOnly) {
653
+ whereConditions.push(`t."status" = 'published'`);
654
+ }
655
+ if (searchParam && searchParam.trim() !== "" && searchableFields.length > 0) {
489
656
  const searchTerm = searchParam.trim();
490
- const hasName = entityConfig.fields.some((f) => f.name === "name");
491
- const hasTitle = entityConfig.fields.some((f) => f.name === "title");
492
- const hasSlug = entityConfig.fields.some((f) => f.name === "slug");
493
- const hasContent = entityConfig.fields.some((f) => f.name === "content");
494
- if (hasName || hasTitle || hasSlug || hasContent) {
495
- const searchConditions = [];
496
- if (hasName) {
497
- searchConditions.push(`t.name ILIKE $${paramIndex}`);
498
- }
499
- if (hasTitle) {
500
- searchConditions.push(`t.title ILIKE $${paramIndex}`);
501
- }
502
- if (hasSlug) {
503
- searchConditions.push(`t.slug ILIKE $${paramIndex}`);
504
- }
505
- if (hasContent) {
506
- searchConditions.push(`t.content ILIKE $${paramIndex}`);
507
- }
508
- whereConditions.push(`(${searchConditions.join(" OR ")})`);
509
- queryParams.push(`%${searchTerm}%`);
510
- paramIndex++;
511
- }
657
+ const searchConditions = searchableFields.map((name) => `t.${name} ILIKE $${paramIndex}`);
658
+ whereConditions.push(`(${searchConditions.join(" OR ")})`);
659
+ queryParams.push(`%${searchTerm}%`);
660
+ paramIndex++;
512
661
  }
513
662
  if (dateFieldName && (fromDate || toDate)) {
514
663
  const field = entityConfig.fields.find((f) => f.name === dateFieldName);
@@ -536,6 +685,18 @@ async function handleGenericList(request) {
536
685
  queryParams.push(JSON.stringify([value]));
537
686
  });
538
687
  whereConditions.push(`(${orConditions.join(" OR ")})`);
688
+ } else if (field.type === "date" || field.type === "datetime") {
689
+ const orConditions = values.map((value) => {
690
+ if (BARE_DATE_RE.test(value)) {
691
+ const lower = paramIndex++;
692
+ const upper = paramIndex++;
693
+ queryParams.push(value, value);
694
+ return `(t.${columnName} >= $${lower}::date AND t.${columnName} < $${upper}::date + 1)`;
695
+ }
696
+ queryParams.push(value);
697
+ return `t.${columnName} = $${paramIndex++}`;
698
+ });
699
+ whereConditions.push(`(${orConditions.join(" OR ")})`);
539
700
  } else {
540
701
  const orConditions = values.map(() => {
541
702
  const condition = `t.${columnName} = $${paramIndex++}`;
@@ -568,16 +729,9 @@ async function handleGenericList(request) {
568
729
  ) pu ON t.id = pu."patternId"
569
730
  `;
570
731
  }
571
- const sortByParam = url.searchParams.get("sortBy");
572
- const sortOrderParam = ((_l = url.searchParams.get("sortOrder")) == null ? void 0 : _l.toUpperCase()) === "ASC" ? "ASC" : "DESC";
573
- let orderByClause = 't."createdAt" DESC';
574
- if (sortByParam) {
575
- const baseFields = ["id", "createdAt", "updatedAt", "teamId"];
576
- const isValidField = entityConfig.fields.some((f) => f.name === sortByParam) || baseFields.includes(sortByParam);
577
- if (isValidField) {
578
- orderByClause = `t."${sortByParam}" ${sortOrderParam}`;
579
- }
580
- }
732
+ const sortOrderRaw = url.searchParams.get("sortOrder") || url.searchParams.get("order");
733
+ const sortOrderParam = (sortOrderRaw == null ? void 0 : sortOrderRaw.toUpperCase()) === "ASC" ? "ASC" : "DESC";
734
+ const orderByClause = sortByParam ? `t."${sortByParam}" ${sortOrderParam}` : 't."createdAt" DESC';
581
735
  query = `
582
736
  SELECT ${fields}, COUNT(*) OVER() as total_count
583
737
  FROM "${tableName}" t
@@ -624,7 +778,8 @@ async function handleGenericList(request) {
624
778
  );
625
779
  }
626
780
  const finalData = filterPublicFields(dataWithTaxonomies, entityConfig, userId);
627
- const response = createApiResponse(finalData, paginationMeta);
781
+ const shapedData = await afterEntityRead(entityConfig.slug, finalData, { ...customFilters }, userId ?? void 0);
782
+ const response = createApiResponse(shapedData, paginationMeta);
628
783
  return addCorsHeaders(response, request);
629
784
  } catch (error) {
630
785
  console.error("Error in generic list handler:", error);
@@ -633,6 +788,9 @@ async function handleGenericList(request) {
633
788
  }
634
789
  }
635
790
  async function handleGenericCreate(request) {
791
+ return runWithAuditLog(request, (audit) => handleGenericCreateImpl(request, audit));
792
+ }
793
+ async function handleGenericCreateImpl(request, audit) {
636
794
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
637
795
  try {
638
796
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -644,20 +802,14 @@ async function handleGenericCreate(request) {
644
802
  const response2 = createApiError("Create operation not supported for this entity", 405);
645
803
  return addCorsHeaders(response2, request);
646
804
  }
647
- const authResult = await authenticateRequest(request);
805
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
806
+ audit.auth = authResult;
648
807
  if (!authResult.success) {
649
- return NextResponse.json(
650
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
651
- { status: 401 }
652
- );
808
+ return createAuthFailureResponse(authResult);
653
809
  }
654
810
  if (authResult.rateLimitResponse) {
655
811
  return authResult.rateLimitResponse;
656
812
  }
657
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
658
- const response2 = createApiError("Insufficient permissions", 403);
659
- return addCorsHeaders(response2, request);
660
- }
661
813
  let body;
662
814
  try {
663
815
  body = await request.json();
@@ -670,16 +822,21 @@ async function handleGenericCreate(request) {
670
822
  const entityConfig = resolution.entityConfig;
671
823
  const tableName = getTableName(entityConfig);
672
824
  const schemas = generateEntitySchemas(entityConfig);
673
- const validation = schemas.create.safeParse(entityData);
825
+ const validation = schemas.create.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
674
826
  if (!validation.success) {
675
827
  console.error(`[${entityConfig.slug}] Validation failed:`, {
676
828
  entityData,
677
829
  errors: validation.error.issues
678
830
  });
679
- const response2 = createApiError("Validation error", 400, validation.error.issues, "VALIDATION_ERROR");
831
+ const response2 = createApiError(
832
+ validationErrorMessage(validation.error.issues),
833
+ 400,
834
+ validation.error.issues,
835
+ "VALIDATION_ERROR"
836
+ );
680
837
  return addCorsHeaders(response2, request);
681
838
  }
682
- const validatedData = validation.data;
839
+ let validatedData = validation.data;
683
840
  const xssField = checkNameFieldXss(entityConfig, validatedData);
684
841
  if (xssField) {
685
842
  const xssResponse = createApiError(
@@ -715,6 +872,22 @@ async function handleGenericCreate(request) {
715
872
  }
716
873
  const permDenied = await checkAuthPermission(authResult, entityConfig.slug, "create", teamId, request);
717
874
  if (permDenied) return permDenied;
875
+ try {
876
+ validatedData = await beforeEntityCreate(
877
+ entityConfig.slug,
878
+ { ...validatedData, userId: userIdToUse, teamId },
879
+ authResult.user.id
880
+ );
881
+ } catch (hookError) {
882
+ console.error(`[generic-handler] beforeEntityCreate rejected ${entityConfig.slug} create:`, hookError);
883
+ const response2 = createApiError(
884
+ hookError instanceof Error && hookError.message ? hookError.message : `Create rejected by ${entityConfig.slug} before_create hook`,
885
+ hookRejectionStatus(hookError),
886
+ void 0,
887
+ "BEFORE_CREATE_REJECTED"
888
+ );
889
+ return addCorsHeaders(response2, request);
890
+ }
718
891
  if (idStrategy === "serial") {
719
892
  insertFields = ['"userId"', '"teamId"'];
720
893
  placeholders = ["$1", "$2"];
@@ -800,6 +973,7 @@ async function handleGenericCreate(request) {
800
973
  if (!actionResult.allowed) {
801
974
  const statusCode = actionResult.reason === "quota_exceeded" ? 429 : 403;
802
975
  const response2 = createApiError(
976
+ // @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
803
977
  actionResult.message || `Quota exceeded for ${entityConfig.slug}`,
804
978
  statusCode,
805
979
  void 0,
@@ -882,23 +1056,19 @@ async function handleGenericCreate(request) {
882
1056
  return addCorsHeaders(response, request);
883
1057
  } catch (error) {
884
1058
  console.error("Error in generic create handler:", error);
885
- if (error && typeof error === "object" && "code" in error && error.code === "23505") {
886
- const detail = "detail" in error ? error.detail : void 0;
887
- const constraint = "constraint" in error ? error.constraint : void 0;
888
- const response2 = createApiError(
889
- "A record with this value already exists",
890
- 409,
891
- { constraint, detail },
892
- "UNIQUE_CONSTRAINT_VIOLATION"
893
- );
894
- return addCorsHeaders(response2, request);
1059
+ const constraintResponse = mapConstraintViolation(error, "create");
1060
+ if (constraintResponse) {
1061
+ return addCorsHeaders(constraintResponse, request);
895
1062
  }
896
1063
  const response = createApiError("Internal server error", 500);
897
1064
  return addCorsHeaders(response, request);
898
1065
  }
899
1066
  }
900
1067
  async function handleGenericRead(request, { params }) {
901
- var _a, _b, _c, _d, _e, _f;
1068
+ return runWithAuditLog(request, (audit) => handleGenericReadImpl(request, audit, { params }));
1069
+ }
1070
+ async function handleGenericReadImpl(request, audit, { params }) {
1071
+ var _a, _b, _c, _d, _e, _f, _g;
902
1072
  try {
903
1073
  const { id } = await params;
904
1074
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -910,25 +1080,22 @@ async function handleGenericRead(request, { params }) {
910
1080
  const response2 = createApiError("Read operation not supported for this entity", 405);
911
1081
  return addCorsHeaders(response2, request);
912
1082
  }
913
- const authResult = await authenticateRequest(request);
1083
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
1084
+ audit.auth = authResult;
914
1085
  let userId = null;
915
1086
  let teamId = null;
916
1087
  let isBypass = false;
1088
+ if (!authResult.success && authResult.type === "api-key") {
1089
+ return addCorsHeaders(createAuthFailureResponse(authResult), request);
1090
+ }
917
1091
  if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
918
1092
  console.log(`[GenericHandler] Public access allowed for ${resolution.entityConfig.slug} read`);
919
1093
  } else if (!authResult.success) {
920
- return NextResponse.json(
921
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
922
- { status: 401 }
923
- );
1094
+ return createAuthFailureResponse(authResult);
924
1095
  } else {
925
1096
  if (authResult.rateLimitResponse) {
926
1097
  return authResult.rateLimitResponse;
927
1098
  }
928
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
929
- const response2 = createApiError("Insufficient permissions", 403);
930
- return addCorsHeaders(response2, request);
931
- }
932
1099
  userId = authResult.user.id;
933
1100
  const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
934
1101
  if (!teamValidation.valid) {
@@ -959,6 +1126,7 @@ async function handleGenericRead(request, { params }) {
959
1126
  return `t.${columnName}`;
960
1127
  });
961
1128
  const fields = [...systemFieldsFormatted, ...configFields].join(", ");
1129
+ const forcePublishedOnly = ((_d = entityConfig.access) == null ? void 0 : _d.public) === true && userId === null && entityConfig.fields.some((f) => f.name === "status");
962
1130
  let query;
963
1131
  let queryParams;
964
1132
  let paramIndex = 1;
@@ -972,7 +1140,7 @@ async function handleGenericRead(request, { params }) {
972
1140
  query += ` AND t."teamId" = $${paramIndex++}`;
973
1141
  queryParams.push(teamId);
974
1142
  }
975
- if (userId && !((_d = entityConfig.access) == null ? void 0 : _d.shared) && !isBypass) {
1143
+ if (userId && !((_e = entityConfig.access) == null ? void 0 : _e.shared) && !isBypass) {
976
1144
  query += ` AND t."userId" = $${paramIndex++}`;
977
1145
  queryParams.push(userId);
978
1146
  }
@@ -992,9 +1160,12 @@ async function handleGenericRead(request, { params }) {
992
1160
  queryParams.push(readOwnershipFilter.value);
993
1161
  }
994
1162
  }
995
- if (((_e = entityConfig.table) == null ? void 0 : _e.softDelete) && !isBypass) {
1163
+ if (((_f = entityConfig.table) == null ? void 0 : _f.softDelete) && !isBypass) {
996
1164
  query += ` AND t."deletedAt" IS NULL`;
997
1165
  }
1166
+ if (forcePublishedOnly) {
1167
+ query += ` AND t."status" = 'published'`;
1168
+ }
998
1169
  const items = await queryWithRLS(query, queryParams, userId);
999
1170
  if (!items[0]) {
1000
1171
  const response2 = createApiError("Item not found", 404);
@@ -1025,7 +1196,7 @@ async function handleGenericRead(request, { params }) {
1025
1196
  itemWithChild = itemsWithChild[0] || itemWithMeta;
1026
1197
  }
1027
1198
  let itemWithTaxonomies = itemWithChild;
1028
- if ((_f = entityConfig.taxonomies) == null ? void 0 : _f.enabled) {
1199
+ if ((_g = entityConfig.taxonomies) == null ? void 0 : _g.enabled) {
1029
1200
  const itemsWithTaxonomies = await includeTaxonomiesInData(
1030
1201
  entityConfig,
1031
1202
  [itemWithChild],
@@ -1034,7 +1205,8 @@ async function handleGenericRead(request, { params }) {
1034
1205
  itemWithTaxonomies = itemsWithTaxonomies[0] || itemWithChild;
1035
1206
  }
1036
1207
  const finalItem = filterPublicFields(itemWithTaxonomies, entityConfig, userId);
1037
- const response = createApiResponse(finalItem);
1208
+ const [shapedItem] = await afterEntityRead(entityConfig.slug, [finalItem], { id }, userId ?? void 0);
1209
+ const response = createApiResponse(shapedItem ?? finalItem);
1038
1210
  return addCorsHeaders(response, request);
1039
1211
  } catch (error) {
1040
1212
  console.error("Error in generic read handler:", error);
@@ -1043,6 +1215,9 @@ async function handleGenericRead(request, { params }) {
1043
1215
  }
1044
1216
  }
1045
1217
  async function handleGenericUpdate(request, { params }) {
1218
+ return runWithAuditLog(request, (audit) => handleGenericUpdateImpl(request, audit, { params }));
1219
+ }
1220
+ async function handleGenericUpdateImpl(request, audit, { params }) {
1046
1221
  var _a, _b, _c, _d, _e, _f, _g;
1047
1222
  try {
1048
1223
  const { id } = await params;
@@ -1055,20 +1230,14 @@ async function handleGenericUpdate(request, { params }) {
1055
1230
  const response2 = createApiError("Update operation not supported for this entity", 405);
1056
1231
  return addCorsHeaders(response2, request);
1057
1232
  }
1058
- const authResult = await authenticateRequest(request);
1233
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
1234
+ audit.auth = authResult;
1059
1235
  if (!authResult.success) {
1060
- return NextResponse.json(
1061
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
1062
- { status: 401 }
1063
- );
1236
+ return createAuthFailureResponse(authResult);
1064
1237
  }
1065
1238
  if (authResult.rateLimitResponse) {
1066
1239
  return authResult.rateLimitResponse;
1067
1240
  }
1068
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
1069
- const response2 = createApiError("Insufficient permissions", 403);
1070
- return addCorsHeaders(response2, request);
1071
- }
1072
1241
  const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
1073
1242
  if (!teamValidation.valid) {
1074
1243
  return teamValidation.error;
@@ -1115,16 +1284,21 @@ async function handleGenericUpdate(request, { params }) {
1115
1284
  }
1116
1285
  const tableName = getTableName(entityConfig);
1117
1286
  const schemas = generateEntitySchemas(entityConfig);
1118
- const validation = schemas.update.safeParse(entityData);
1287
+ const validation = schemas.update.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
1119
1288
  if (!validation.success) {
1120
1289
  console.log(`[${entityConfig.slug}] Validation failed:`, {
1121
1290
  entityData,
1122
1291
  errors: validation.error.issues
1123
1292
  });
1124
- const response2 = createApiError("Validation error", 400, validation.error.issues, "VALIDATION_ERROR");
1293
+ const response2 = createApiError(
1294
+ validationErrorMessage(validation.error.issues),
1295
+ 400,
1296
+ validation.error.issues,
1297
+ "VALIDATION_ERROR"
1298
+ );
1125
1299
  return addCorsHeaders(response2, request);
1126
1300
  }
1127
- const validatedData = validation.data;
1301
+ let validatedData = validation.data;
1128
1302
  const updateXssField = checkNameFieldXss(entityConfig, validatedData);
1129
1303
  if (updateXssField) {
1130
1304
  const updateXssResponse = createApiError(
@@ -1135,6 +1309,23 @@ async function handleGenericUpdate(request, { params }) {
1135
1309
  );
1136
1310
  return addCorsHeaders(updateXssResponse, request);
1137
1311
  }
1312
+ try {
1313
+ validatedData = await beforeEntityUpdate(
1314
+ entityConfig.slug,
1315
+ id,
1316
+ validatedData,
1317
+ authResult.user.id
1318
+ );
1319
+ } catch (hookError) {
1320
+ console.error(`[generic-handler] beforeEntityUpdate rejected ${entityConfig.slug} update:`, hookError);
1321
+ const response2 = createApiError(
1322
+ hookError instanceof Error && hookError.message ? hookError.message : `Update rejected by ${entityConfig.slug} before_update hook`,
1323
+ hookRejectionStatus(hookError),
1324
+ void 0,
1325
+ "BEFORE_UPDATE_REJECTED"
1326
+ );
1327
+ return addCorsHeaders(response2, request);
1328
+ }
1138
1329
  const updates = [];
1139
1330
  const values = [];
1140
1331
  let paramCount = 1;
@@ -1294,22 +1485,18 @@ async function handleGenericUpdate(request, { params }) {
1294
1485
  return addCorsHeaders(response, request);
1295
1486
  } catch (error) {
1296
1487
  console.error("Error in generic update handler:", error);
1297
- if (error && typeof error === "object" && "code" in error && error.code === "23505") {
1298
- const detail = "detail" in error ? error.detail : void 0;
1299
- const constraint = "constraint" in error ? error.constraint : void 0;
1300
- const response2 = createApiError(
1301
- "A record with this value already exists",
1302
- 409,
1303
- { constraint, detail },
1304
- "UNIQUE_CONSTRAINT_VIOLATION"
1305
- );
1306
- return addCorsHeaders(response2, request);
1488
+ const constraintResponse = mapConstraintViolation(error, "update");
1489
+ if (constraintResponse) {
1490
+ return addCorsHeaders(constraintResponse, request);
1307
1491
  }
1308
1492
  const response = createApiError("Internal server error", 500);
1309
1493
  return addCorsHeaders(response, request);
1310
1494
  }
1311
1495
  }
1312
1496
  async function handleGenericDelete(request, { params }) {
1497
+ return runWithAuditLog(request, (audit) => handleGenericDeleteImpl(request, audit, { params }));
1498
+ }
1499
+ async function handleGenericDeleteImpl(request, audit, { params }) {
1313
1500
  var _a, _b, _c, _d, _e;
1314
1501
  try {
1315
1502
  const { id } = await params;
@@ -1322,20 +1509,14 @@ async function handleGenericDelete(request, { params }) {
1322
1509
  const response2 = createApiError("Delete operation not supported for this entity", 405);
1323
1510
  return addCorsHeaders(response2, request);
1324
1511
  }
1325
- const authResult = await authenticateRequest(request);
1512
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:delete` });
1513
+ audit.auth = authResult;
1326
1514
  if (!authResult.success) {
1327
- return NextResponse.json(
1328
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
1329
- { status: 401 }
1330
- );
1515
+ return createAuthFailureResponse(authResult);
1331
1516
  }
1332
1517
  if (authResult.rateLimitResponse) {
1333
1518
  return authResult.rateLimitResponse;
1334
1519
  }
1335
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:delete`)) {
1336
- const response2 = createApiError("Insufficient permissions", 403);
1337
- return addCorsHeaders(response2, request);
1338
- }
1339
1520
  const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
1340
1521
  if (!teamValidation.valid) {
1341
1522
  return teamValidation.error;
@@ -1349,6 +1530,27 @@ async function handleGenericDelete(request, { params }) {
1349
1530
  if (permDenied) return permDenied;
1350
1531
  const entityConfig = resolution.entityConfig;
1351
1532
  const tableName = getTableName(entityConfig);
1533
+ try {
1534
+ const allowDelete = await beforeEntityDelete(entityConfig.slug, id, authResult.user.id);
1535
+ if (!allowDelete) {
1536
+ const response2 = createApiError(
1537
+ `Delete rejected by ${entityConfig.slug} before_delete hook`,
1538
+ 403,
1539
+ void 0,
1540
+ "BEFORE_DELETE_REJECTED"
1541
+ );
1542
+ return addCorsHeaders(response2, request);
1543
+ }
1544
+ } catch (hookError) {
1545
+ console.error(`[generic-handler] beforeEntityDelete rejected ${entityConfig.slug} delete:`, hookError);
1546
+ const response2 = createApiError(
1547
+ hookError instanceof Error && hookError.message ? hookError.message : `Delete rejected by ${entityConfig.slug} before_delete hook`,
1548
+ hookRejectionStatus(hookError),
1549
+ void 0,
1550
+ "BEFORE_DELETE_REJECTED"
1551
+ );
1552
+ return addCorsHeaders(response2, request);
1553
+ }
1352
1554
  const whereConditions = ["id = $1"];
1353
1555
  const deleteParams = [id];
1354
1556
  let paramIndex = 2;
@@ -1405,16 +1607,9 @@ async function handleGenericDelete(request, { params }) {
1405
1607
  return addCorsHeaders(response, request);
1406
1608
  } catch (error) {
1407
1609
  console.error("Error in generic delete handler:", error);
1408
- if (error && typeof error === "object" && "code" in error && error.code === "23503") {
1409
- const detail = "detail" in error ? error.detail : void 0;
1410
- const constraint = "constraint" in error ? error.constraint : void 0;
1411
- const response2 = createApiError(
1412
- detail || "Cannot delete: this record is referenced by other records",
1413
- 409,
1414
- { constraint },
1415
- "FOREIGN_KEY_VIOLATION"
1416
- );
1417
- return addCorsHeaders(response2, request);
1610
+ const constraintResponse = mapConstraintViolation(error, "delete");
1611
+ if (constraintResponse) {
1612
+ return addCorsHeaders(constraintResponse, request);
1418
1613
  }
1419
1614
  const response = createApiError("Internal server error", 500);
1420
1615
  return addCorsHeaders(response, request);