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

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 (386) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  2. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -2
  5. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  6. package/dist/components/app/layouts/PublicNavbar.js +2 -2
  7. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  8. package/dist/components/app/misc/ThemeToggle.js +4 -1
  9. package/dist/components/auth/DevKeyring.d.ts.map +1 -1
  10. package/dist/components/auth/DevKeyring.js +10 -2
  11. package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
  12. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
  13. package/dist/components/auth/SessionCookieRefresher.js +11 -0
  14. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  15. package/dist/components/auth/forms/LoginForm.js +300 -6
  16. package/dist/components/auth/index.d.ts +1 -0
  17. package/dist/components/auth/index.d.ts.map +1 -1
  18. package/dist/components/auth/index.js +2 -0
  19. package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
  20. package/dist/components/billing/InvoicesTable.js +4 -3
  21. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  22. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  23. package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
  24. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  25. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
  26. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
  27. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
  28. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
  29. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  30. package/dist/components/dashboard/layouts/Sidebar.js +2 -3
  31. package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
  32. package/dist/components/dashboard/layouts/index.d.ts +1 -0
  33. package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
  34. package/dist/components/dashboard/layouts/index.js +2 -0
  35. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  36. package/dist/components/entities/EntityFieldRenderer.js +3 -0
  37. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  38. package/dist/components/entities/EntityTable.js +2 -1
  39. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  40. package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
  41. package/dist/components/ui/combobox.d.ts +8 -1
  42. package/dist/components/ui/combobox.d.ts.map +1 -1
  43. package/dist/components/ui/combobox.js +37 -44
  44. package/dist/components/ui/country-select.d.ts +3 -1
  45. package/dist/components/ui/country-select.d.ts.map +1 -1
  46. package/dist/components/ui/country-select.js +3 -1
  47. package/dist/components/ui/currency-select.d.ts +3 -1
  48. package/dist/components/ui/currency-select.d.ts.map +1 -1
  49. package/dist/components/ui/currency-select.js +3 -1
  50. package/dist/components/ui/sheet.d.ts +5 -0
  51. package/dist/components/ui/sheet.d.ts.map +1 -1
  52. package/dist/components/ui/sheet.js +46 -26
  53. package/dist/components/ui/timezone-select.d.ts +3 -1
  54. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  55. package/dist/components/ui/timezone-select.js +3 -1
  56. package/dist/contexts/TeamContext.d.ts +6 -0
  57. package/dist/contexts/TeamContext.d.ts.map +1 -1
  58. package/dist/contexts/TeamContext.js +8 -1
  59. package/dist/emails/otp-verification.d.ts.map +1 -1
  60. package/dist/emails/reset-password.d.ts.map +1 -1
  61. package/dist/emails/team-invitation.d.ts.map +1 -1
  62. package/dist/emails/team-invitation.js +3 -2
  63. package/dist/emails/verify-email.d.ts.map +1 -1
  64. package/dist/hooks/index.d.ts +1 -0
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +1 -0
  67. package/dist/hooks/useAuth.d.ts +29 -0
  68. package/dist/hooks/useAuth.d.ts.map +1 -1
  69. package/dist/hooks/useAuth.js +24 -0
  70. package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
  71. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  72. package/dist/hooks/useEnsureUserMetadata.js +18 -5
  73. package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
  74. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
  75. package/dist/hooks/useSessionCookieRefresh.js +49 -0
  76. package/dist/lib/api/api-error.d.ts +3 -1
  77. package/dist/lib/api/api-error.d.ts.map +1 -1
  78. package/dist/lib/api/api-error.js +4 -2
  79. package/dist/lib/api/auth/dual-auth.d.ts +38 -3
  80. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  81. package/dist/lib/api/auth/dual-auth.js +42 -7
  82. package/dist/lib/api/auth/permissions.d.ts +7 -2
  83. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  84. package/dist/lib/api/auth/permissions.js +2 -2
  85. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  86. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  87. package/dist/lib/api/auth/scope-policy.js +53 -0
  88. package/dist/lib/api/auth.d.ts +15 -0
  89. package/dist/lib/api/auth.d.ts.map +1 -1
  90. package/dist/lib/api/auth.js +17 -4
  91. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  92. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  93. package/dist/lib/api/entity/audit-log.js +35 -0
  94. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  95. package/dist/lib/api/entity/generic-handler.js +315 -134
  96. package/dist/lib/api/helpers.d.ts +29 -4
  97. package/dist/lib/api/helpers.d.ts.map +1 -1
  98. package/dist/lib/api/helpers.js +30 -6
  99. package/dist/lib/api/keys.d.ts +16 -0
  100. package/dist/lib/api/keys.d.ts.map +1 -1
  101. package/dist/lib/api/keys.js +19 -1
  102. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  103. package/dist/lib/api/routes/patterns-usages.js +3 -11
  104. package/dist/lib/auth/auth-methods.d.ts +52 -0
  105. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  106. package/dist/lib/auth/auth-methods.js +63 -0
  107. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  108. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  109. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  110. package/dist/lib/auth/registration-helpers.js +8 -2
  111. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  112. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  113. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  114. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  115. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  116. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  117. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  118. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  119. package/dist/lib/auth/security-notifications/index.js +22 -0
  120. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  121. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  122. package/dist/lib/auth/security-notifications/registry.js +26 -0
  123. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  124. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  125. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  126. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  127. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  128. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  129. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  130. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  131. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  132. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  133. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  134. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  135. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  136. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  137. package/dist/lib/auth/security-notifications/types.js +0 -0
  138. package/dist/lib/auth/session-config.d.ts +37 -0
  139. package/dist/lib/auth/session-config.d.ts.map +1 -0
  140. package/dist/lib/auth/session-config.js +59 -0
  141. package/dist/lib/auth/session-refresh.d.ts +46 -0
  142. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  143. package/dist/lib/auth/session-refresh.js +11 -0
  144. package/dist/lib/auth-client.d.ts +1639 -0
  145. package/dist/lib/auth-client.d.ts.map +1 -0
  146. package/dist/lib/auth-client.js +4 -0
  147. package/dist/lib/auth.d.ts.map +1 -1
  148. package/dist/lib/auth.js +21 -8
  149. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  150. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  151. package/dist/lib/billing/gateways/factory.js +37 -23
  152. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  153. package/dist/lib/billing/stripe-webhook.js +2 -1
  154. package/dist/lib/config/app.config.d.ts.map +1 -1
  155. package/dist/lib/config/app.config.js +71 -0
  156. package/dist/lib/config/config-sync.d.ts +1 -4
  157. package/dist/lib/config/config-sync.d.ts.map +1 -1
  158. package/dist/lib/config/config-sync.js +6 -2
  159. package/dist/lib/config/types.d.ts +130 -0
  160. package/dist/lib/config/types.d.ts.map +1 -1
  161. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  162. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  163. package/dist/lib/entities/entity-hooks.js +77 -4
  164. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  165. package/dist/lib/entities/schema-generator.js +20 -4
  166. package/dist/lib/entities/serialization.d.ts +2 -2
  167. package/dist/lib/entities/serialization.d.ts.map +1 -1
  168. package/dist/lib/entities/serialization.js +3 -2
  169. package/dist/lib/entities/types.d.ts +8 -0
  170. package/dist/lib/entities/types.d.ts.map +1 -1
  171. package/dist/lib/locale.d.ts.map +1 -1
  172. package/dist/lib/locale.js +4 -1
  173. package/dist/lib/mcp/audit.d.ts +8 -4
  174. package/dist/lib/mcp/audit.d.ts.map +1 -1
  175. package/dist/lib/plugins/hook-system.d.ts +13 -0
  176. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  177. package/dist/lib/plugins/hook-system.js +32 -2
  178. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  179. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  180. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  181. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  182. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  183. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  184. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  185. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  186. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  187. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  188. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  189. package/dist/lib/selectors/selectors.d.ts +24 -0
  190. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  191. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  192. package/dist/lib/services/generic-entity.service.js +16 -32
  193. package/dist/lib/services/membership.service.d.ts +10 -2
  194. package/dist/lib/services/membership.service.d.ts.map +1 -1
  195. package/dist/lib/services/membership.service.js +37 -16
  196. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  197. package/dist/lib/services/subscription.service.js +2 -1
  198. package/dist/lib/services/team-member.service.d.ts +15 -3
  199. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  200. package/dist/lib/services/team-member.service.js +27 -6
  201. package/dist/lib/services/team.service.d.ts.map +1 -1
  202. package/dist/lib/teams/permissions.d.ts +3 -1
  203. package/dist/lib/teams/permissions.d.ts.map +1 -1
  204. package/dist/lib/teams/permissions.js +6 -0
  205. package/dist/lib/teams/schema.d.ts.map +1 -1
  206. package/dist/lib/teams/schema.js +3 -0
  207. package/dist/lib/theme/forced-theme.d.ts +34 -0
  208. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  209. package/dist/lib/theme/forced-theme.js +27 -0
  210. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  211. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  212. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  213. package/dist/messages/de/auth.json +25 -2
  214. package/dist/messages/de/index.d.ts +81 -0
  215. package/dist/messages/de/index.d.ts.map +1 -1
  216. package/dist/messages/de/index.js +2 -0
  217. package/dist/messages/de/index.ts +2 -0
  218. package/dist/messages/en/auth.json +25 -2
  219. package/dist/messages/en/index.d.ts +81 -0
  220. package/dist/messages/en/index.d.ts.map +1 -1
  221. package/dist/messages/en/index.js +2 -0
  222. package/dist/messages/en/index.ts +2 -0
  223. package/dist/messages/es/auth.json +25 -2
  224. package/dist/messages/es/index.d.ts +81 -0
  225. package/dist/messages/es/index.d.ts.map +1 -1
  226. package/dist/messages/es/index.js +2 -0
  227. package/dist/messages/es/index.ts +2 -0
  228. package/dist/messages/fr/auth.json +25 -2
  229. package/dist/messages/fr/index.d.ts +81 -0
  230. package/dist/messages/fr/index.d.ts.map +1 -1
  231. package/dist/messages/fr/index.js +2 -0
  232. package/dist/messages/fr/index.ts +2 -0
  233. package/dist/messages/it/auth.json +25 -2
  234. package/dist/messages/it/index.d.ts +81 -0
  235. package/dist/messages/it/index.d.ts.map +1 -1
  236. package/dist/messages/it/index.js +2 -0
  237. package/dist/messages/it/index.ts +2 -0
  238. package/dist/messages/pt/auth.json +25 -2
  239. package/dist/messages/pt/index.d.ts +81 -0
  240. package/dist/messages/pt/index.d.ts.map +1 -1
  241. package/dist/messages/pt/index.js +2 -0
  242. package/dist/messages/pt/index.ts +2 -0
  243. package/dist/migrations/005_api_audit_log.sql +2 -1
  244. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  245. package/dist/migrations/026_login_events.sql +91 -0
  246. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  247. package/dist/providers/DashboardProviders.d.ts +12 -3
  248. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  249. package/dist/providers/DashboardProviders.js +6 -2
  250. package/dist/providers/theme-provider.d.ts +19 -2
  251. package/dist/providers/theme-provider.d.ts.map +1 -1
  252. package/dist/providers/theme-provider.js +6 -2
  253. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  254. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  255. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  256. package/dist/templates/app/api/health/route.ts +4 -2
  257. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  258. package/dist/templates/app/api/mcp/route.ts +4 -1
  259. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  260. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  261. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  262. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  263. package/dist/templates/app/api/v1/api-keys/route.ts +21 -43
  264. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  265. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  266. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  267. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  268. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  269. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  270. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  271. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  272. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  273. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  274. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  275. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  276. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  277. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  278. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  279. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  280. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  281. package/dist/templates/app/api/v1/media/route.ts +5 -9
  282. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  283. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  284. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  285. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  286. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  287. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  288. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  289. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  290. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  291. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  292. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  293. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  294. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  295. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  296. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  297. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  298. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  299. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  300. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  301. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  302. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  303. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  304. package/dist/templates/app/api/v1/users/route.ts +21 -25
  305. package/dist/templates/app/dashboard/layout.tsx +2 -6
  306. package/dist/templates/app/layout.ppr.tsx +9 -14
  307. package/dist/templates/app/layout.tsx +37 -20
  308. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  309. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  310. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  311. package/dist/templates/proxy.ts +46 -10
  312. package/dist/templates/tsconfig.json +4 -0
  313. package/dist/types/theme.d.ts +9 -0
  314. package/dist/types/theme.d.ts.map +1 -1
  315. package/migrations/005_api_audit_log.sql +2 -1
  316. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  317. package/migrations/026_login_events.sql +91 -0
  318. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  319. package/package.json +14 -11
  320. package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
  321. package/scripts/build/registry/generators/scope-registry.mjs +9 -2
  322. package/templates/app/(auth)/signup/page.tsx +8 -0
  323. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  324. package/templates/app/api/auth/[...all]/route.ts +23 -1
  325. package/templates/app/api/health/route.ts +4 -2
  326. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  327. package/templates/app/api/mcp/route.ts +4 -1
  328. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  329. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  330. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  331. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  332. package/templates/app/api/v1/api-keys/route.ts +21 -43
  333. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  334. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  335. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  336. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  337. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  338. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  339. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  340. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  341. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  342. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  343. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  344. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  345. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  346. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  347. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  348. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  349. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  350. package/templates/app/api/v1/media/route.ts +5 -9
  351. package/templates/app/api/v1/media/upload/route.ts +9 -21
  352. package/templates/app/api/v1/media-tags/route.ts +9 -13
  353. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  354. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  355. package/templates/app/api/v1/post-categories/route.ts +5 -4
  356. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  357. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  358. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  359. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  360. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  361. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  362. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  363. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  364. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  365. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  366. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  367. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  368. package/templates/app/api/v1/teams/route.ts +33 -27
  369. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  370. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  371. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  372. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  373. package/templates/app/api/v1/users/route.ts +21 -25
  374. package/templates/app/dashboard/layout.tsx +2 -6
  375. package/templates/app/layout.ppr.tsx +9 -14
  376. package/templates/app/layout.tsx +37 -20
  377. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  378. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  379. package/templates/contents/themes/starter/styles/globals.css +6 -2
  380. package/templates/proxy.ts +46 -10
  381. package/templates/tsconfig.json +4 -0
  382. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  383. package/tests/jest/__mocks__/next-server.js +24 -0
  384. package/dist/styles/classes.json +0 -1093
  385. package/dist/styles/ui.css +0 -2
  386. package/scripts/build/build-ui-css.mjs +0 -351
@@ -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,12 +553,11 @@ 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)`);
@@ -429,7 +586,7 @@ async function handleGenericList(request) {
429
586
  query += ` AND t."teamId" = $${paramIndex++}`;
430
587
  queryParams.push(teamId);
431
588
  }
432
- if (userId && !((_f = entityConfig.access) == null ? void 0 : _f.shared) && !skipUserFilter && !isBypass) {
589
+ if (userId && !((_g = entityConfig.access) == null ? void 0 : _g.shared) && !skipUserFilter && !isBypass) {
433
590
  query += ` AND t."userId" = $${paramIndex++}`;
434
591
  queryParams.push(userId);
435
592
  }
@@ -437,7 +594,7 @@ async function handleGenericList(request) {
437
594
  query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
438
595
  queryParams.push(ownershipFilter.value);
439
596
  }
440
- if (((_g = entityConfig.table) == null ? void 0 : _g.softDelete) && !isBypass) {
597
+ if (((_h = entityConfig.table) == null ? void 0 : _h.softDelete) && !isBypass) {
441
598
  query += ` AND t."deletedAt" IS NULL`;
442
599
  }
443
600
  } else if (fieldsParam && distinctParam) {
@@ -453,7 +610,7 @@ async function handleGenericList(request) {
453
610
  query += ` AND t."parentId" = $${paramIndex++}`;
454
611
  queryParams.push(parentId);
455
612
  }
456
- if (userId && !((_h = entityConfig.access) == null ? void 0 : _h.shared) && !skipUserFilter && !isBypass) {
613
+ if (userId && !((_i = entityConfig.access) == null ? void 0 : _i.shared) && !skipUserFilter && !isBypass) {
457
614
  query += ` AND t."userId" = $${paramIndex++}`;
458
615
  queryParams.push(userId);
459
616
  }
@@ -461,7 +618,7 @@ async function handleGenericList(request) {
461
618
  query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
462
619
  queryParams.push(ownershipFilter.value);
463
620
  }
464
- if (((_i = entityConfig.table) == null ? void 0 : _i.softDelete) && !isBypass) {
621
+ if (((_j = entityConfig.table) == null ? void 0 : _j.softDelete) && !isBypass) {
465
622
  query += ` AND t."deletedAt" IS NULL`;
466
623
  }
467
624
  query += ` ORDER BY "${fieldName}" ASC LIMIT $${paramIndex++}`;
@@ -474,7 +631,7 @@ async function handleGenericList(request) {
474
631
  whereConditions.push(`t."teamId" = $${paramIndex++}`);
475
632
  queryParams.push(teamId);
476
633
  }
477
- if (userId && !((_j = entityConfig.access) == null ? void 0 : _j.shared) && !skipUserFilter && !isBypass) {
634
+ if (userId && !((_k = entityConfig.access) == null ? void 0 : _k.shared) && !skipUserFilter && !isBypass) {
478
635
  whereConditions.push(`t."userId" = $${paramIndex++}`);
479
636
  queryParams.push(userId);
480
637
  }
@@ -482,33 +639,15 @@ async function handleGenericList(request) {
482
639
  whereConditions.push(`t."${ownershipFilter.field}" = $${paramIndex++}`);
483
640
  queryParams.push(ownershipFilter.value);
484
641
  }
485
- if (((_k = entityConfig.table) == null ? void 0 : _k.softDelete) && !isBypass) {
642
+ if (((_l = entityConfig.table) == null ? void 0 : _l.softDelete) && !isBypass) {
486
643
  whereConditions.push(`t."deletedAt" IS NULL`);
487
644
  }
488
- if (searchParam && searchParam.trim() !== "") {
645
+ if (searchParam && searchParam.trim() !== "" && searchableFields.length > 0) {
489
646
  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
- }
647
+ const searchConditions = searchableFields.map((name) => `t.${name} ILIKE $${paramIndex}`);
648
+ whereConditions.push(`(${searchConditions.join(" OR ")})`);
649
+ queryParams.push(`%${searchTerm}%`);
650
+ paramIndex++;
512
651
  }
513
652
  if (dateFieldName && (fromDate || toDate)) {
514
653
  const field = entityConfig.fields.find((f) => f.name === dateFieldName);
@@ -536,6 +675,18 @@ async function handleGenericList(request) {
536
675
  queryParams.push(JSON.stringify([value]));
537
676
  });
538
677
  whereConditions.push(`(${orConditions.join(" OR ")})`);
678
+ } else if (field.type === "date" || field.type === "datetime") {
679
+ const orConditions = values.map((value) => {
680
+ if (BARE_DATE_RE.test(value)) {
681
+ const lower = paramIndex++;
682
+ const upper = paramIndex++;
683
+ queryParams.push(value, value);
684
+ return `(t.${columnName} >= $${lower}::date AND t.${columnName} < $${upper}::date + 1)`;
685
+ }
686
+ queryParams.push(value);
687
+ return `t.${columnName} = $${paramIndex++}`;
688
+ });
689
+ whereConditions.push(`(${orConditions.join(" OR ")})`);
539
690
  } else {
540
691
  const orConditions = values.map(() => {
541
692
  const condition = `t.${columnName} = $${paramIndex++}`;
@@ -568,16 +719,9 @@ async function handleGenericList(request) {
568
719
  ) pu ON t.id = pu."patternId"
569
720
  `;
570
721
  }
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
- }
722
+ const sortOrderRaw = url.searchParams.get("sortOrder") || url.searchParams.get("order");
723
+ const sortOrderParam = (sortOrderRaw == null ? void 0 : sortOrderRaw.toUpperCase()) === "ASC" ? "ASC" : "DESC";
724
+ const orderByClause = sortByParam ? `t."${sortByParam}" ${sortOrderParam}` : 't."createdAt" DESC';
581
725
  query = `
582
726
  SELECT ${fields}, COUNT(*) OVER() as total_count
583
727
  FROM "${tableName}" t
@@ -624,7 +768,8 @@ async function handleGenericList(request) {
624
768
  );
625
769
  }
626
770
  const finalData = filterPublicFields(dataWithTaxonomies, entityConfig, userId);
627
- const response = createApiResponse(finalData, paginationMeta);
771
+ const shapedData = await afterEntityRead(entityConfig.slug, finalData, { ...customFilters }, userId ?? void 0);
772
+ const response = createApiResponse(shapedData, paginationMeta);
628
773
  return addCorsHeaders(response, request);
629
774
  } catch (error) {
630
775
  console.error("Error in generic list handler:", error);
@@ -633,6 +778,9 @@ async function handleGenericList(request) {
633
778
  }
634
779
  }
635
780
  async function handleGenericCreate(request) {
781
+ return runWithAuditLog(request, (audit) => handleGenericCreateImpl(request, audit));
782
+ }
783
+ async function handleGenericCreateImpl(request, audit) {
636
784
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
637
785
  try {
638
786
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -644,20 +792,14 @@ async function handleGenericCreate(request) {
644
792
  const response2 = createApiError("Create operation not supported for this entity", 405);
645
793
  return addCorsHeaders(response2, request);
646
794
  }
647
- const authResult = await authenticateRequest(request);
795
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
796
+ audit.auth = authResult;
648
797
  if (!authResult.success) {
649
- return NextResponse.json(
650
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
651
- { status: 401 }
652
- );
798
+ return createAuthFailureResponse(authResult);
653
799
  }
654
800
  if (authResult.rateLimitResponse) {
655
801
  return authResult.rateLimitResponse;
656
802
  }
657
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
658
- const response2 = createApiError("Insufficient permissions", 403);
659
- return addCorsHeaders(response2, request);
660
- }
661
803
  let body;
662
804
  try {
663
805
  body = await request.json();
@@ -670,16 +812,21 @@ async function handleGenericCreate(request) {
670
812
  const entityConfig = resolution.entityConfig;
671
813
  const tableName = getTableName(entityConfig);
672
814
  const schemas = generateEntitySchemas(entityConfig);
673
- const validation = schemas.create.safeParse(entityData);
815
+ const validation = schemas.create.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
674
816
  if (!validation.success) {
675
817
  console.error(`[${entityConfig.slug}] Validation failed:`, {
676
818
  entityData,
677
819
  errors: validation.error.issues
678
820
  });
679
- const response2 = createApiError("Validation error", 400, validation.error.issues, "VALIDATION_ERROR");
821
+ const response2 = createApiError(
822
+ validationErrorMessage(validation.error.issues),
823
+ 400,
824
+ validation.error.issues,
825
+ "VALIDATION_ERROR"
826
+ );
680
827
  return addCorsHeaders(response2, request);
681
828
  }
682
- const validatedData = validation.data;
829
+ let validatedData = validation.data;
683
830
  const xssField = checkNameFieldXss(entityConfig, validatedData);
684
831
  if (xssField) {
685
832
  const xssResponse = createApiError(
@@ -715,6 +862,22 @@ async function handleGenericCreate(request) {
715
862
  }
716
863
  const permDenied = await checkAuthPermission(authResult, entityConfig.slug, "create", teamId, request);
717
864
  if (permDenied) return permDenied;
865
+ try {
866
+ validatedData = await beforeEntityCreate(
867
+ entityConfig.slug,
868
+ { ...validatedData, userId: userIdToUse, teamId },
869
+ authResult.user.id
870
+ );
871
+ } catch (hookError) {
872
+ console.error(`[generic-handler] beforeEntityCreate rejected ${entityConfig.slug} create:`, hookError);
873
+ const response2 = createApiError(
874
+ hookError instanceof Error && hookError.message ? hookError.message : `Create rejected by ${entityConfig.slug} before_create hook`,
875
+ hookRejectionStatus(hookError),
876
+ void 0,
877
+ "BEFORE_CREATE_REJECTED"
878
+ );
879
+ return addCorsHeaders(response2, request);
880
+ }
718
881
  if (idStrategy === "serial") {
719
882
  insertFields = ['"userId"', '"teamId"'];
720
883
  placeholders = ["$1", "$2"];
@@ -800,6 +963,7 @@ async function handleGenericCreate(request) {
800
963
  if (!actionResult.allowed) {
801
964
  const statusCode = actionResult.reason === "quota_exceeded" ? 429 : 403;
802
965
  const response2 = createApiError(
966
+ // @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
803
967
  actionResult.message || `Quota exceeded for ${entityConfig.slug}`,
804
968
  statusCode,
805
969
  void 0,
@@ -882,22 +1046,18 @@ async function handleGenericCreate(request) {
882
1046
  return addCorsHeaders(response, request);
883
1047
  } catch (error) {
884
1048
  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);
1049
+ const constraintResponse = mapConstraintViolation(error, "create");
1050
+ if (constraintResponse) {
1051
+ return addCorsHeaders(constraintResponse, request);
895
1052
  }
896
1053
  const response = createApiError("Internal server error", 500);
897
1054
  return addCorsHeaders(response, request);
898
1055
  }
899
1056
  }
900
1057
  async function handleGenericRead(request, { params }) {
1058
+ return runWithAuditLog(request, (audit) => handleGenericReadImpl(request, audit, { params }));
1059
+ }
1060
+ async function handleGenericReadImpl(request, audit, { params }) {
901
1061
  var _a, _b, _c, _d, _e, _f;
902
1062
  try {
903
1063
  const { id } = await params;
@@ -910,25 +1070,22 @@ async function handleGenericRead(request, { params }) {
910
1070
  const response2 = createApiError("Read operation not supported for this entity", 405);
911
1071
  return addCorsHeaders(response2, request);
912
1072
  }
913
- const authResult = await authenticateRequest(request);
1073
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
1074
+ audit.auth = authResult;
914
1075
  let userId = null;
915
1076
  let teamId = null;
916
1077
  let isBypass = false;
1078
+ if (!authResult.success && authResult.type === "api-key") {
1079
+ return addCorsHeaders(createAuthFailureResponse(authResult), request);
1080
+ }
917
1081
  if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
918
1082
  console.log(`[GenericHandler] Public access allowed for ${resolution.entityConfig.slug} read`);
919
1083
  } else if (!authResult.success) {
920
- return NextResponse.json(
921
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
922
- { status: 401 }
923
- );
1084
+ return createAuthFailureResponse(authResult);
924
1085
  } else {
925
1086
  if (authResult.rateLimitResponse) {
926
1087
  return authResult.rateLimitResponse;
927
1088
  }
928
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
929
- const response2 = createApiError("Insufficient permissions", 403);
930
- return addCorsHeaders(response2, request);
931
- }
932
1089
  userId = authResult.user.id;
933
1090
  const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
934
1091
  if (!teamValidation.valid) {
@@ -1034,7 +1191,8 @@ async function handleGenericRead(request, { params }) {
1034
1191
  itemWithTaxonomies = itemsWithTaxonomies[0] || itemWithChild;
1035
1192
  }
1036
1193
  const finalItem = filterPublicFields(itemWithTaxonomies, entityConfig, userId);
1037
- const response = createApiResponse(finalItem);
1194
+ const [shapedItem] = await afterEntityRead(entityConfig.slug, [finalItem], { id }, userId ?? void 0);
1195
+ const response = createApiResponse(shapedItem ?? finalItem);
1038
1196
  return addCorsHeaders(response, request);
1039
1197
  } catch (error) {
1040
1198
  console.error("Error in generic read handler:", error);
@@ -1043,6 +1201,9 @@ async function handleGenericRead(request, { params }) {
1043
1201
  }
1044
1202
  }
1045
1203
  async function handleGenericUpdate(request, { params }) {
1204
+ return runWithAuditLog(request, (audit) => handleGenericUpdateImpl(request, audit, { params }));
1205
+ }
1206
+ async function handleGenericUpdateImpl(request, audit, { params }) {
1046
1207
  var _a, _b, _c, _d, _e, _f, _g;
1047
1208
  try {
1048
1209
  const { id } = await params;
@@ -1055,20 +1216,14 @@ async function handleGenericUpdate(request, { params }) {
1055
1216
  const response2 = createApiError("Update operation not supported for this entity", 405);
1056
1217
  return addCorsHeaders(response2, request);
1057
1218
  }
1058
- const authResult = await authenticateRequest(request);
1219
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
1220
+ audit.auth = authResult;
1059
1221
  if (!authResult.success) {
1060
- return NextResponse.json(
1061
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
1062
- { status: 401 }
1063
- );
1222
+ return createAuthFailureResponse(authResult);
1064
1223
  }
1065
1224
  if (authResult.rateLimitResponse) {
1066
1225
  return authResult.rateLimitResponse;
1067
1226
  }
1068
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
1069
- const response2 = createApiError("Insufficient permissions", 403);
1070
- return addCorsHeaders(response2, request);
1071
- }
1072
1227
  const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
1073
1228
  if (!teamValidation.valid) {
1074
1229
  return teamValidation.error;
@@ -1115,16 +1270,21 @@ async function handleGenericUpdate(request, { params }) {
1115
1270
  }
1116
1271
  const tableName = getTableName(entityConfig);
1117
1272
  const schemas = generateEntitySchemas(entityConfig);
1118
- const validation = schemas.update.safeParse(entityData);
1273
+ const validation = schemas.update.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
1119
1274
  if (!validation.success) {
1120
1275
  console.log(`[${entityConfig.slug}] Validation failed:`, {
1121
1276
  entityData,
1122
1277
  errors: validation.error.issues
1123
1278
  });
1124
- const response2 = createApiError("Validation error", 400, validation.error.issues, "VALIDATION_ERROR");
1279
+ const response2 = createApiError(
1280
+ validationErrorMessage(validation.error.issues),
1281
+ 400,
1282
+ validation.error.issues,
1283
+ "VALIDATION_ERROR"
1284
+ );
1125
1285
  return addCorsHeaders(response2, request);
1126
1286
  }
1127
- const validatedData = validation.data;
1287
+ let validatedData = validation.data;
1128
1288
  const updateXssField = checkNameFieldXss(entityConfig, validatedData);
1129
1289
  if (updateXssField) {
1130
1290
  const updateXssResponse = createApiError(
@@ -1135,6 +1295,23 @@ async function handleGenericUpdate(request, { params }) {
1135
1295
  );
1136
1296
  return addCorsHeaders(updateXssResponse, request);
1137
1297
  }
1298
+ try {
1299
+ validatedData = await beforeEntityUpdate(
1300
+ entityConfig.slug,
1301
+ id,
1302
+ validatedData,
1303
+ authResult.user.id
1304
+ );
1305
+ } catch (hookError) {
1306
+ console.error(`[generic-handler] beforeEntityUpdate rejected ${entityConfig.slug} update:`, hookError);
1307
+ const response2 = createApiError(
1308
+ hookError instanceof Error && hookError.message ? hookError.message : `Update rejected by ${entityConfig.slug} before_update hook`,
1309
+ hookRejectionStatus(hookError),
1310
+ void 0,
1311
+ "BEFORE_UPDATE_REJECTED"
1312
+ );
1313
+ return addCorsHeaders(response2, request);
1314
+ }
1138
1315
  const updates = [];
1139
1316
  const values = [];
1140
1317
  let paramCount = 1;
@@ -1294,22 +1471,18 @@ async function handleGenericUpdate(request, { params }) {
1294
1471
  return addCorsHeaders(response, request);
1295
1472
  } catch (error) {
1296
1473
  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);
1474
+ const constraintResponse = mapConstraintViolation(error, "update");
1475
+ if (constraintResponse) {
1476
+ return addCorsHeaders(constraintResponse, request);
1307
1477
  }
1308
1478
  const response = createApiError("Internal server error", 500);
1309
1479
  return addCorsHeaders(response, request);
1310
1480
  }
1311
1481
  }
1312
1482
  async function handleGenericDelete(request, { params }) {
1483
+ return runWithAuditLog(request, (audit) => handleGenericDeleteImpl(request, audit, { params }));
1484
+ }
1485
+ async function handleGenericDeleteImpl(request, audit, { params }) {
1313
1486
  var _a, _b, _c, _d, _e;
1314
1487
  try {
1315
1488
  const { id } = await params;
@@ -1322,20 +1495,14 @@ async function handleGenericDelete(request, { params }) {
1322
1495
  const response2 = createApiError("Delete operation not supported for this entity", 405);
1323
1496
  return addCorsHeaders(response2, request);
1324
1497
  }
1325
- const authResult = await authenticateRequest(request);
1498
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:delete` });
1499
+ audit.auth = authResult;
1326
1500
  if (!authResult.success) {
1327
- return NextResponse.json(
1328
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
1329
- { status: 401 }
1330
- );
1501
+ return createAuthFailureResponse(authResult);
1331
1502
  }
1332
1503
  if (authResult.rateLimitResponse) {
1333
1504
  return authResult.rateLimitResponse;
1334
1505
  }
1335
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:delete`)) {
1336
- const response2 = createApiError("Insufficient permissions", 403);
1337
- return addCorsHeaders(response2, request);
1338
- }
1339
1506
  const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
1340
1507
  if (!teamValidation.valid) {
1341
1508
  return teamValidation.error;
@@ -1349,6 +1516,27 @@ async function handleGenericDelete(request, { params }) {
1349
1516
  if (permDenied) return permDenied;
1350
1517
  const entityConfig = resolution.entityConfig;
1351
1518
  const tableName = getTableName(entityConfig);
1519
+ try {
1520
+ const allowDelete = await beforeEntityDelete(entityConfig.slug, id, authResult.user.id);
1521
+ if (!allowDelete) {
1522
+ const response2 = createApiError(
1523
+ `Delete rejected by ${entityConfig.slug} before_delete hook`,
1524
+ 403,
1525
+ void 0,
1526
+ "BEFORE_DELETE_REJECTED"
1527
+ );
1528
+ return addCorsHeaders(response2, request);
1529
+ }
1530
+ } catch (hookError) {
1531
+ console.error(`[generic-handler] beforeEntityDelete rejected ${entityConfig.slug} delete:`, hookError);
1532
+ const response2 = createApiError(
1533
+ hookError instanceof Error && hookError.message ? hookError.message : `Delete rejected by ${entityConfig.slug} before_delete hook`,
1534
+ hookRejectionStatus(hookError),
1535
+ void 0,
1536
+ "BEFORE_DELETE_REJECTED"
1537
+ );
1538
+ return addCorsHeaders(response2, request);
1539
+ }
1352
1540
  const whereConditions = ["id = $1"];
1353
1541
  const deleteParams = [id];
1354
1542
  let paramIndex = 2;
@@ -1405,16 +1593,9 @@ async function handleGenericDelete(request, { params }) {
1405
1593
  return addCorsHeaders(response, request);
1406
1594
  } catch (error) {
1407
1595
  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);
1596
+ const constraintResponse = mapConstraintViolation(error, "delete");
1597
+ if (constraintResponse) {
1598
+ return addCorsHeaders(constraintResponse, request);
1418
1599
  }
1419
1600
  const response = createApiError("Internal server error", 500);
1420
1601
  return addCorsHeaders(response, request);