sovrium 0.0.2

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 (527) hide show
  1. package/CHANGELOG.md +3497 -0
  2. package/LICENSE.md +147 -0
  3. package/LICENSE_EE.md +297 -0
  4. package/README.md +321 -0
  5. package/drizzle/0000_melted_kabuki.sql +163 -0
  6. package/drizzle/meta/0000_snapshot.json +1216 -0
  7. package/drizzle/meta/_journal.json +13 -0
  8. package/package.json +167 -0
  9. package/schemas/0.0.1/app.openapi.json +70 -0
  10. package/schemas/0.0.1/app.schema.json +7961 -0
  11. package/schemas/0.0.2/app.openapi.json +80 -0
  12. package/schemas/0.0.2/app.schema.json +8829 -0
  13. package/schemas/development/app.openapi.json +70 -0
  14. package/schemas/development/app.schema.json +7456 -0
  15. package/src/application/errors/app-validation-error.ts +14 -0
  16. package/src/application/errors/static-generation-error.ts +16 -0
  17. package/src/application/metadata/favicon-transformer.ts +127 -0
  18. package/src/application/models/server.ts +27 -0
  19. package/src/application/ports/models/user-metadata.ts +36 -0
  20. package/src/application/ports/models/user-session.ts +34 -0
  21. package/src/application/ports/repositories/activity-log-repository.ts +68 -0
  22. package/src/application/ports/repositories/activity-repository.ts +49 -0
  23. package/src/application/ports/repositories/analytics-repository.ts +164 -0
  24. package/src/application/ports/repositories/auth-repository.ts +33 -0
  25. package/src/application/ports/repositories/batch-repository.ts +86 -0
  26. package/src/application/ports/repositories/comment-repository.ts +150 -0
  27. package/src/application/ports/repositories/index.ts +41 -0
  28. package/src/application/ports/repositories/table-repository.ts +139 -0
  29. package/src/application/ports/services/css-compiler.ts +55 -0
  30. package/src/application/ports/services/index.ts +16 -0
  31. package/src/application/ports/services/page-renderer.ts +79 -0
  32. package/src/application/ports/services/server-factory.ts +80 -0
  33. package/src/application/ports/services/static-site-generator.ts +82 -0
  34. package/src/application/use-cases/activity/programs.ts +66 -0
  35. package/src/application/use-cases/analytics/collect-page-view.ts +114 -0
  36. package/src/application/use-cases/analytics/purge-old-data.ts +40 -0
  37. package/src/application/use-cases/analytics/query-campaigns.ts +43 -0
  38. package/src/application/use-cases/analytics/query-devices.ts +36 -0
  39. package/src/application/use-cases/analytics/query-overview.ts +50 -0
  40. package/src/application/use-cases/analytics/query-pages.ts +40 -0
  41. package/src/application/use-cases/analytics/query-referrers.ts +43 -0
  42. package/src/application/use-cases/analytics/ua-parser.ts +89 -0
  43. package/src/application/use-cases/analytics/visitor-hash.ts +77 -0
  44. package/src/application/use-cases/auth/bootstrap-admin.ts +270 -0
  45. package/src/application/use-cases/list-activity-logs.ts +123 -0
  46. package/src/application/use-cases/server/generate-static-helpers.ts +374 -0
  47. package/src/application/use-cases/server/generate-static.ts +287 -0
  48. package/src/application/use-cases/server/start-server.ts +118 -0
  49. package/src/application/use-cases/server/startup-error-handler.ts +69 -0
  50. package/src/application/use-cases/server/static-content-generators.ts +182 -0
  51. package/src/application/use-cases/server/static-language-generators.ts +181 -0
  52. package/src/application/use-cases/server/static-url-rewriter.ts +237 -0
  53. package/src/application/use-cases/server/translation-replacer.ts +164 -0
  54. package/src/application/use-cases/tables/activity-programs.ts +93 -0
  55. package/src/application/use-cases/tables/batch-operations.ts +156 -0
  56. package/src/application/use-cases/tables/comment-programs.ts +436 -0
  57. package/src/application/use-cases/tables/permissions/permissions.ts +25 -0
  58. package/src/application/use-cases/tables/programs.ts +435 -0
  59. package/src/application/use-cases/tables/table-operations.ts +412 -0
  60. package/src/application/use-cases/tables/user-role.ts +52 -0
  61. package/src/application/use-cases/tables/utils/display-formatter.ts +471 -0
  62. package/src/application/use-cases/tables/utils/field-read-filter.ts +189 -0
  63. package/src/application/use-cases/tables/utils/list-helpers.ts +122 -0
  64. package/src/application/use-cases/tables/utils/record-transformer.ts +319 -0
  65. package/src/cli.ts +370 -0
  66. package/src/domain/errors/create-tagged-error.ts +36 -0
  67. package/src/domain/errors/index.ts +78 -0
  68. package/src/domain/models/api/analytics.ts +179 -0
  69. package/src/domain/models/api/auth.ts +231 -0
  70. package/src/domain/models/api/common.ts +60 -0
  71. package/src/domain/models/api/error.ts +89 -0
  72. package/src/domain/models/api/health.ts +38 -0
  73. package/src/domain/models/api/index.ts +42 -0
  74. package/src/domain/models/api/request.ts +132 -0
  75. package/src/domain/models/api/tables.ts +444 -0
  76. package/src/domain/models/app/analytics/index.ts +129 -0
  77. package/src/domain/models/app/auth/config.ts +116 -0
  78. package/src/domain/models/app/auth/index.ts +230 -0
  79. package/src/domain/models/app/auth/methods/email-and-password.ts +67 -0
  80. package/src/domain/models/app/auth/methods/index.ts +11 -0
  81. package/src/domain/models/app/auth/methods/magic-link.ts +54 -0
  82. package/src/domain/models/app/auth/oauth/index.ts +8 -0
  83. package/src/domain/models/app/auth/oauth/providers.ts +105 -0
  84. package/src/domain/models/app/auth/plugins/admin.ts +130 -0
  85. package/src/domain/models/app/auth/plugins/index.ts +74 -0
  86. package/src/domain/models/app/auth/plugins/two-factor.ts +63 -0
  87. package/src/domain/models/app/auth/roles.ts +179 -0
  88. package/src/domain/models/app/auth/strategies.ts +191 -0
  89. package/src/domain/models/app/auth/validation.ts +127 -0
  90. package/src/domain/models/app/common/branded-ids.ts +200 -0
  91. package/src/domain/models/app/common/definitions.ts +187 -0
  92. package/src/domain/models/app/component/common/component-children.ts +119 -0
  93. package/src/domain/models/app/component/common/component-props.ts +89 -0
  94. package/src/domain/models/app/component/common/component-reference.ts +170 -0
  95. package/src/domain/models/app/component/component.ts +81 -0
  96. package/src/domain/models/app/components.ts +65 -0
  97. package/src/domain/models/app/description.ts +83 -0
  98. package/src/domain/models/app/index.ts +258 -0
  99. package/src/domain/models/app/language/language-config.ts +200 -0
  100. package/src/domain/models/app/languages.ts +205 -0
  101. package/src/domain/models/app/name.ts +66 -0
  102. package/src/domain/models/app/page/common/interactions/click-interaction.ts +116 -0
  103. package/src/domain/models/app/page/common/interactions/entrance-animation.ts +84 -0
  104. package/src/domain/models/app/page/common/interactions/hover-interaction.ts +144 -0
  105. package/src/domain/models/app/page/common/interactions/interactions.ts +64 -0
  106. package/src/domain/models/app/page/common/interactions/scroll-interaction.ts +93 -0
  107. package/src/domain/models/app/page/common/responsive.ts +114 -0
  108. package/src/domain/models/app/page/common/url.ts +35 -0
  109. package/src/domain/models/app/page/common/variable-reference.ts +53 -0
  110. package/src/domain/models/app/page/id.ts +44 -0
  111. package/src/domain/models/app/page/index.ts +270 -0
  112. package/src/domain/models/app/page/meta/analytics.ts +248 -0
  113. package/src/domain/models/app/page/meta/custom-elements.ts +180 -0
  114. package/src/domain/models/app/page/meta/dns-prefetch.ts +77 -0
  115. package/src/domain/models/app/page/meta/favicon-set.ts +203 -0
  116. package/src/domain/models/app/page/meta/favicon.ts +50 -0
  117. package/src/domain/models/app/page/meta/favicons-config.ts +73 -0
  118. package/src/domain/models/app/page/meta/index.ts +278 -0
  119. package/src/domain/models/app/page/meta/open-graph.ts +166 -0
  120. package/src/domain/models/app/page/meta/preload.ts +190 -0
  121. package/src/domain/models/app/page/meta/structured-data/article.ts +211 -0
  122. package/src/domain/models/app/page/meta/structured-data/breadcrumb.ts +115 -0
  123. package/src/domain/models/app/page/meta/structured-data/common-fields.ts +201 -0
  124. package/src/domain/models/app/page/meta/structured-data/education-event.ts +256 -0
  125. package/src/domain/models/app/page/meta/structured-data/faq-page.ts +127 -0
  126. package/src/domain/models/app/page/meta/structured-data/index.ts +95 -0
  127. package/src/domain/models/app/page/meta/structured-data/local-business.ts +247 -0
  128. package/src/domain/models/app/page/meta/structured-data/organization.ts +171 -0
  129. package/src/domain/models/app/page/meta/structured-data/person.ts +138 -0
  130. package/src/domain/models/app/page/meta/structured-data/postal-address.ts +106 -0
  131. package/src/domain/models/app/page/meta/structured-data/product.ts +214 -0
  132. package/src/domain/models/app/page/meta/twitter-card.ts +217 -0
  133. package/src/domain/models/app/page/name.ts +38 -0
  134. package/src/domain/models/app/page/path.ts +21 -0
  135. package/src/domain/models/app/page/scripts/external-scripts.ts +163 -0
  136. package/src/domain/models/app/page/scripts/features.ts +135 -0
  137. package/src/domain/models/app/page/scripts/inline-scripts.ts +114 -0
  138. package/src/domain/models/app/page/scripts/scripts.ts +102 -0
  139. package/src/domain/models/app/page/sections.ts +298 -0
  140. package/src/domain/models/app/pages.ts +61 -0
  141. package/src/domain/models/app/permissions/index.ts +61 -0
  142. package/src/domain/models/app/permissions/resource-action.ts +114 -0
  143. package/src/domain/models/app/permissions/roles.ts +120 -0
  144. package/src/domain/models/app/table/check-constraints.ts +105 -0
  145. package/src/domain/models/app/table/cycle-detection.ts +124 -0
  146. package/src/domain/models/app/table/database-identifier.ts +153 -0
  147. package/src/domain/models/app/table/field-name.ts +36 -0
  148. package/src/domain/models/app/table/field-types/advanced/array-field.ts +33 -0
  149. package/src/domain/models/app/table/field-types/advanced/autonumber-field.ts +54 -0
  150. package/src/domain/models/app/table/field-types/advanced/button-field.ts +56 -0
  151. package/src/domain/models/app/table/field-types/advanced/color-field.ts +57 -0
  152. package/src/domain/models/app/table/field-types/advanced/count-field.ts +54 -0
  153. package/src/domain/models/app/table/field-types/advanced/formula-field.ts +58 -0
  154. package/src/domain/models/app/table/field-types/advanced/geolocation-field.ts +49 -0
  155. package/src/domain/models/app/table/field-types/advanced/index.ts +16 -0
  156. package/src/domain/models/app/table/field-types/advanced/json-field.ts +25 -0
  157. package/src/domain/models/app/table/field-types/advanced/unknown-field.ts +85 -0
  158. package/src/domain/models/app/table/field-types/base-field.ts +42 -0
  159. package/src/domain/models/app/table/field-types/date-time/created-at-field.ts +49 -0
  160. package/src/domain/models/app/table/field-types/date-time/date-field.ts +95 -0
  161. package/src/domain/models/app/table/field-types/date-time/deleted-at-field.ts +56 -0
  162. package/src/domain/models/app/table/field-types/date-time/duration-field.ts +73 -0
  163. package/src/domain/models/app/table/field-types/date-time/index.ts +12 -0
  164. package/src/domain/models/app/table/field-types/date-time/updated-at-field.ts +50 -0
  165. package/src/domain/models/app/table/field-types/index.ts +19 -0
  166. package/src/domain/models/app/table/field-types/media/barcode-field.ts +58 -0
  167. package/src/domain/models/app/table/field-types/media/index.ts +10 -0
  168. package/src/domain/models/app/table/field-types/media/multiple-attachments-field.ts +80 -0
  169. package/src/domain/models/app/table/field-types/media/single-attachment-field.ts +81 -0
  170. package/src/domain/models/app/table/field-types/numeric/currency-field.ts +144 -0
  171. package/src/domain/models/app/table/field-types/numeric/decimal-field.ts +113 -0
  172. package/src/domain/models/app/table/field-types/numeric/index.ts +13 -0
  173. package/src/domain/models/app/table/field-types/numeric/integer-field.ts +98 -0
  174. package/src/domain/models/app/table/field-types/numeric/percentage-field.ts +115 -0
  175. package/src/domain/models/app/table/field-types/numeric/progress-field.ts +71 -0
  176. package/src/domain/models/app/table/field-types/numeric/rating-field.ts +74 -0
  177. package/src/domain/models/app/table/field-types/relational/index.ts +10 -0
  178. package/src/domain/models/app/table/field-types/relational/lookup-field.ts +46 -0
  179. package/src/domain/models/app/table/field-types/relational/relationship-field.ts +112 -0
  180. package/src/domain/models/app/table/field-types/relational/rollup-field.ts +58 -0
  181. package/src/domain/models/app/table/field-types/selection/checkbox-field.ts +51 -0
  182. package/src/domain/models/app/table/field-types/selection/index.ts +11 -0
  183. package/src/domain/models/app/table/field-types/selection/multi-select-field.ts +68 -0
  184. package/src/domain/models/app/table/field-types/selection/single-select-field.ts +54 -0
  185. package/src/domain/models/app/table/field-types/selection/status-field.ts +37 -0
  186. package/src/domain/models/app/table/field-types/text/email-field.ts +80 -0
  187. package/src/domain/models/app/table/field-types/text/index.ts +13 -0
  188. package/src/domain/models/app/table/field-types/text/long-text-field.ts +77 -0
  189. package/src/domain/models/app/table/field-types/text/phone-number-field.ts +82 -0
  190. package/src/domain/models/app/table/field-types/text/rich-text-field.ts +66 -0
  191. package/src/domain/models/app/table/field-types/text/single-line-text-field.ts +79 -0
  192. package/src/domain/models/app/table/field-types/text/url-field.ts +81 -0
  193. package/src/domain/models/app/table/field-types/user/created-by-field.ts +50 -0
  194. package/src/domain/models/app/table/field-types/user/deleted-by-field.ts +57 -0
  195. package/src/domain/models/app/table/field-types/user/index.ts +11 -0
  196. package/src/domain/models/app/table/field-types/user/updated-by-field.ts +51 -0
  197. package/src/domain/models/app/table/field-types/user/user-field.ts +52 -0
  198. package/src/domain/models/app/table/field-types/validation-utils.ts +166 -0
  199. package/src/domain/models/app/table/fields.ts +216 -0
  200. package/src/domain/models/app/table/foreign-keys.ts +111 -0
  201. package/src/domain/models/app/table/formula-keywords.ts +326 -0
  202. package/src/domain/models/app/table/id.ts +31 -0
  203. package/src/domain/models/app/table/index.ts +290 -0
  204. package/src/domain/models/app/table/indexes.ts +80 -0
  205. package/src/domain/models/app/table/name.ts +37 -0
  206. package/src/domain/models/app/table/permissions/field-permission.ts +83 -0
  207. package/src/domain/models/app/table/permissions/index.ts +167 -0
  208. package/src/domain/models/app/table/permissions/permission-evaluator.ts +372 -0
  209. package/src/domain/models/app/table/permissions/permission.ts +49 -0
  210. package/src/domain/models/app/table/primary-key.ts +62 -0
  211. package/src/domain/models/app/table/table-formula-validation.ts +168 -0
  212. package/src/domain/models/app/table/table-indexes-validation.ts +38 -0
  213. package/src/domain/models/app/table/table-permissions-validation.ts +77 -0
  214. package/src/domain/models/app/table/table-primary-key-validation.ts +49 -0
  215. package/src/domain/models/app/table/table-views-validation.ts +408 -0
  216. package/src/domain/models/app/table/unique-constraints.ts +79 -0
  217. package/src/domain/models/app/table/views/fields.ts +28 -0
  218. package/src/domain/models/app/table/views/filters.ts +162 -0
  219. package/src/domain/models/app/table/views/group-by.ts +32 -0
  220. package/src/domain/models/app/table/views/id.ts +50 -0
  221. package/src/domain/models/app/table/views/index.ts +177 -0
  222. package/src/domain/models/app/table/views/name.ts +32 -0
  223. package/src/domain/models/app/table/views/permissions.ts +98 -0
  224. package/src/domain/models/app/table/views/sorts.ts +31 -0
  225. package/src/domain/models/app/tables.ts +695 -0
  226. package/src/domain/models/app/theme/animations.ts +208 -0
  227. package/src/domain/models/app/theme/border-radius.ts +58 -0
  228. package/src/domain/models/app/theme/breakpoints.ts +62 -0
  229. package/src/domain/models/app/theme/colors.ts +110 -0
  230. package/src/domain/models/app/theme/fonts.ts +164 -0
  231. package/src/domain/models/app/theme/shadows.ts +61 -0
  232. package/src/domain/models/app/theme/spacing.ts +115 -0
  233. package/src/domain/models/app/theme.ts +66 -0
  234. package/src/domain/models/app/version.ts +87 -0
  235. package/src/domain/models/record-comment.ts +91 -0
  236. package/src/domain/utils/content-parsing.ts +49 -0
  237. package/src/domain/utils/format-detection.ts +69 -0
  238. package/src/domain/utils/index.ts +9 -0
  239. package/src/domain/utils/route-matcher.ts +184 -0
  240. package/src/domain/utils/translation-resolver.ts +170 -0
  241. package/src/index.ts +208 -0
  242. package/src/infrastructure/analytics/tracking-script.ts +48 -0
  243. package/src/infrastructure/auth/better-auth/auth.ts +216 -0
  244. package/src/infrastructure/auth/better-auth/email-handlers.ts +162 -0
  245. package/src/infrastructure/auth/better-auth/index.ts +16 -0
  246. package/src/infrastructure/auth/better-auth/layer.ts +97 -0
  247. package/src/infrastructure/auth/better-auth/plugins/admin.ts +56 -0
  248. package/src/infrastructure/auth/better-auth/plugins/magic-link.ts +31 -0
  249. package/src/infrastructure/auth/better-auth/plugins/two-factor.ts +19 -0
  250. package/src/infrastructure/auth/better-auth/schema.ts +152 -0
  251. package/src/infrastructure/auth/index.ts +27 -0
  252. package/src/infrastructure/css/cache/css-cache-service.ts +130 -0
  253. package/src/infrastructure/css/compiler.ts +210 -0
  254. package/src/infrastructure/css/css-compiler-live.ts +20 -0
  255. package/src/infrastructure/css/index.ts +25 -0
  256. package/src/infrastructure/css/styles/animation-styles-generator.ts +177 -0
  257. package/src/infrastructure/css/styles/click-animations.ts +147 -0
  258. package/src/infrastructure/css/styles/component-layer-generators.ts +147 -0
  259. package/src/infrastructure/css/theme/theme-generators.ts +130 -0
  260. package/src/infrastructure/css/theme/theme-layer-generators.ts +219 -0
  261. package/src/infrastructure/css/theme/theme-token-resolver.ts +76 -0
  262. package/src/infrastructure/database/activity-queries.ts +111 -0
  263. package/src/infrastructure/database/auth/auth-validation.ts +101 -0
  264. package/src/infrastructure/database/drizzle/db-bun.ts +17 -0
  265. package/src/infrastructure/database/drizzle/db.ts +17 -0
  266. package/src/infrastructure/database/drizzle/index.ts +16 -0
  267. package/src/infrastructure/database/drizzle/layer.ts +34 -0
  268. package/src/infrastructure/database/drizzle/migrate.ts +77 -0
  269. package/src/infrastructure/database/drizzle/schema/activity-log.ts +111 -0
  270. package/src/infrastructure/database/drizzle/schema/analytics-page-views.ts +116 -0
  271. package/src/infrastructure/database/drizzle/schema/migration-audit.ts +68 -0
  272. package/src/infrastructure/database/drizzle/schema/record-comments.ts +79 -0
  273. package/src/infrastructure/database/drizzle/schema.ts +12 -0
  274. package/src/infrastructure/database/field-utils.ts +87 -0
  275. package/src/infrastructure/database/filter-operators.ts +136 -0
  276. package/src/infrastructure/database/formula/formula-trigger-generators.ts +114 -0
  277. package/src/infrastructure/database/formula/formula-utils.ts +440 -0
  278. package/src/infrastructure/database/generators/index-generators.ts +152 -0
  279. package/src/infrastructure/database/generators/trigger-generators.ts +154 -0
  280. package/src/infrastructure/database/index.ts +35 -0
  281. package/src/infrastructure/database/lookup/lookup-expression-generators.ts +356 -0
  282. package/src/infrastructure/database/lookup/lookup-expressions.ts +116 -0
  283. package/src/infrastructure/database/lookup/lookup-view-generators.ts +403 -0
  284. package/src/infrastructure/database/lookup/lookup-view-helpers.ts +65 -0
  285. package/src/infrastructure/database/lookup/lookup-view-triggers.ts +121 -0
  286. package/src/infrastructure/database/migration-audit-trail.ts +375 -0
  287. package/src/infrastructure/database/repositories/activity-log-repository-live.ts +99 -0
  288. package/src/infrastructure/database/repositories/activity-repository-live.ts +21 -0
  289. package/src/infrastructure/database/repositories/analytics-repository-live.ts +316 -0
  290. package/src/infrastructure/database/repositories/auth-repository-live.ts +42 -0
  291. package/src/infrastructure/database/repositories/batch-repository-live.ts +29 -0
  292. package/src/infrastructure/database/repositories/comment-repository-live.ts +39 -0
  293. package/src/infrastructure/database/repositories/table-repository-live.ts +38 -0
  294. package/src/infrastructure/database/schema/schema-dependency-sorting.ts +142 -0
  295. package/src/infrastructure/database/schema/schema-initializer.ts +598 -0
  296. package/src/infrastructure/database/schema-migration/column-detection.ts +286 -0
  297. package/src/infrastructure/database/schema-migration/constants.ts +31 -0
  298. package/src/infrastructure/database/schema-migration/constraint-sync.ts +288 -0
  299. package/src/infrastructure/database/schema-migration/index-sync.ts +108 -0
  300. package/src/infrastructure/database/schema-migration/index.ts +66 -0
  301. package/src/infrastructure/database/schema-migration/migration-statements.ts +106 -0
  302. package/src/infrastructure/database/schema-migration/rename-detection.ts +87 -0
  303. package/src/infrastructure/database/schema-migration/table-operations.ts +65 -0
  304. package/src/infrastructure/database/schema-migration/type-utils.ts +98 -0
  305. package/src/infrastructure/database/schema-migration/types.ts +14 -0
  306. package/src/infrastructure/database/schema-migration-helpers.ts +53 -0
  307. package/src/infrastructure/database/session-context.ts +20 -0
  308. package/src/infrastructure/database/sql/sql-check-constraints.ts +252 -0
  309. package/src/infrastructure/database/sql/sql-column-generators.ts +174 -0
  310. package/src/infrastructure/database/sql/sql-execution.ts +245 -0
  311. package/src/infrastructure/database/sql/sql-field-predicates.ts +81 -0
  312. package/src/infrastructure/database/sql/sql-generators.ts +91 -0
  313. package/src/infrastructure/database/sql/sql-junction-tables.ts +79 -0
  314. package/src/infrastructure/database/sql/sql-key-constraints.ts +210 -0
  315. package/src/infrastructure/database/sql/sql-type-mappings.ts +106 -0
  316. package/src/infrastructure/database/sql/sql-utils.ts +53 -0
  317. package/src/infrastructure/database/table-live-layers.ts +30 -0
  318. package/src/infrastructure/database/table-operations/column-generators.ts +82 -0
  319. package/src/infrastructure/database/table-operations/create-table-sql.ts +81 -0
  320. package/src/infrastructure/database/table-operations/index.ts +55 -0
  321. package/src/infrastructure/database/table-operations/migration-utils.ts +157 -0
  322. package/src/infrastructure/database/table-operations/table-effects.ts +234 -0
  323. package/src/infrastructure/database/table-operations/table-features.ts +96 -0
  324. package/src/infrastructure/database/table-operations/type-compatibility.ts +58 -0
  325. package/src/infrastructure/database/table-operations.ts +47 -0
  326. package/src/infrastructure/database/table-queries/batch/batch-create.ts +80 -0
  327. package/src/infrastructure/database/table-queries/batch/batch-delete.ts +212 -0
  328. package/src/infrastructure/database/table-queries/batch/batch-helpers.ts +124 -0
  329. package/src/infrastructure/database/table-queries/batch/batch-restore.ts +161 -0
  330. package/src/infrastructure/database/table-queries/batch/batch-update.ts +146 -0
  331. package/src/infrastructure/database/table-queries/batch/batch-upsert.ts +357 -0
  332. package/src/infrastructure/database/table-queries/batch/batch.ts +14 -0
  333. package/src/infrastructure/database/table-queries/crud/crud-read.ts +351 -0
  334. package/src/infrastructure/database/table-queries/crud/crud-write.ts +399 -0
  335. package/src/infrastructure/database/table-queries/crud/crud.ts +16 -0
  336. package/src/infrastructure/database/table-queries/index.ts +11 -0
  337. package/src/infrastructure/database/table-queries/mutation-helpers/authorship-helpers.ts +152 -0
  338. package/src/infrastructure/database/table-queries/mutation-helpers/create-record-helpers.ts +90 -0
  339. package/src/infrastructure/database/table-queries/mutation-helpers/delete-helpers.ts +163 -0
  340. package/src/infrastructure/database/table-queries/mutation-helpers/record-fetch-helpers.ts +79 -0
  341. package/src/infrastructure/database/table-queries/mutation-helpers/update-helpers.ts +74 -0
  342. package/src/infrastructure/database/table-queries/query-helpers/activity-log-helpers.ts +53 -0
  343. package/src/infrastructure/database/table-queries/query-helpers/activity-queries.ts +106 -0
  344. package/src/infrastructure/database/table-queries/query-helpers/aggregation-helpers.ts +314 -0
  345. package/src/infrastructure/database/table-queries/query-helpers/comment-queries.ts +414 -0
  346. package/src/infrastructure/database/table-queries/query-helpers/record-validation-queries.ts +126 -0
  347. package/src/infrastructure/database/table-queries/query-helpers/trash-helpers.ts +58 -0
  348. package/src/infrastructure/database/table-queries/shared/error-handling.ts +47 -0
  349. package/src/infrastructure/database/table-queries/shared/typed-execute.ts +27 -0
  350. package/src/infrastructure/database/table-queries/shared/user-join-helpers.ts +38 -0
  351. package/src/infrastructure/database/table-queries/shared/validation.ts +39 -0
  352. package/src/infrastructure/database/views/view-generators.ts +258 -0
  353. package/src/infrastructure/devtools/devtools-layer.ts +43 -0
  354. package/src/infrastructure/devtools/index.ts +8 -0
  355. package/src/infrastructure/email/email-config.ts +103 -0
  356. package/src/infrastructure/email/email-service.ts +152 -0
  357. package/src/infrastructure/email/index.ts +107 -0
  358. package/src/infrastructure/email/nodemailer.ts +125 -0
  359. package/src/infrastructure/email/templates.ts +244 -0
  360. package/src/infrastructure/errors/auth-config-required-error.ts +21 -0
  361. package/src/infrastructure/errors/auth-error.ts +16 -0
  362. package/src/infrastructure/errors/css-compilation-error.ts +14 -0
  363. package/src/infrastructure/errors/index.ts +26 -0
  364. package/src/infrastructure/errors/schema-initialization-error.ts +19 -0
  365. package/src/infrastructure/errors/server-creation-error.ts +14 -0
  366. package/src/infrastructure/filesystem/copy-directory.ts +136 -0
  367. package/src/infrastructure/layers/app-layer.ts +61 -0
  368. package/src/infrastructure/layers/page-renderer-layer.ts +41 -0
  369. package/src/infrastructure/logging/index.ts +8 -0
  370. package/src/infrastructure/logging/logger.ts +204 -0
  371. package/src/infrastructure/schema/file-loader.ts +53 -0
  372. package/src/infrastructure/schema/index.ts +15 -0
  373. package/src/infrastructure/schema/remote-loader.ts +48 -0
  374. package/src/infrastructure/server/index.ts +26 -0
  375. package/src/infrastructure/server/language-detection.ts +87 -0
  376. package/src/infrastructure/server/lifecycle.ts +67 -0
  377. package/src/infrastructure/server/route-setup/api-routes.ts +310 -0
  378. package/src/infrastructure/server/route-setup/auth-route-utils.ts +399 -0
  379. package/src/infrastructure/server/route-setup/auth-routes.ts +245 -0
  380. package/src/infrastructure/server/route-setup/openapi-routes.ts +45 -0
  381. package/src/infrastructure/server/route-setup/openapi-schema.ts +120 -0
  382. package/src/infrastructure/server/route-setup/page-routes.ts +219 -0
  383. package/src/infrastructure/server/route-setup/static-assets.ts +191 -0
  384. package/src/infrastructure/server/server-factory-live.ts +45 -0
  385. package/src/infrastructure/server/server.ts +275 -0
  386. package/src/infrastructure/server/ssg-adapter.ts +196 -0
  387. package/src/infrastructure/server/static-site-generator-live.ts +20 -0
  388. package/src/infrastructure/utils/accept-language-parser.ts +106 -0
  389. package/src/infrastructure/utils/glob-matcher.ts +50 -0
  390. package/src/presentation/api/client.ts +114 -0
  391. package/src/presentation/api/middleware/auth.ts +233 -0
  392. package/src/presentation/api/middleware/table.ts +155 -0
  393. package/src/presentation/api/middleware/validation.ts +88 -0
  394. package/src/presentation/api/routes/activity/get-activity-by-id-handler.ts +77 -0
  395. package/src/presentation/api/routes/activity/index.ts +28 -0
  396. package/src/presentation/api/routes/activity.ts +339 -0
  397. package/src/presentation/api/routes/analytics.ts +328 -0
  398. package/src/presentation/api/routes/auth.ts +169 -0
  399. package/src/presentation/api/routes/index.ts +11 -0
  400. package/src/presentation/api/routes/tables/activity-handlers.ts +57 -0
  401. package/src/presentation/api/routes/tables/batch-permission-helpers.ts +163 -0
  402. package/src/presentation/api/routes/tables/batch-routes.ts +355 -0
  403. package/src/presentation/api/routes/tables/comment-handlers.ts +377 -0
  404. package/src/presentation/api/routes/tables/create-record-helpers.ts +179 -0
  405. package/src/presentation/api/routes/tables/effect-runner.ts +58 -0
  406. package/src/presentation/api/routes/tables/error-handlers.ts +53 -0
  407. package/src/presentation/api/routes/tables/field-permission-validation.ts +167 -0
  408. package/src/presentation/api/routes/tables/filter-parser.ts +75 -0
  409. package/src/presentation/api/routes/tables/formula-parser.ts +118 -0
  410. package/src/presentation/api/routes/tables/index.ts +113 -0
  411. package/src/presentation/api/routes/tables/list-records-filter.ts +54 -0
  412. package/src/presentation/api/routes/tables/param-parsers.ts +59 -0
  413. package/src/presentation/api/routes/tables/record-handlers.ts +484 -0
  414. package/src/presentation/api/routes/tables/record-routes.ts +53 -0
  415. package/src/presentation/api/routes/tables/record-update-handler.ts +200 -0
  416. package/src/presentation/api/routes/tables/sort-validation.ts +85 -0
  417. package/src/presentation/api/routes/tables/table-routes.ts +76 -0
  418. package/src/presentation/api/routes/tables/timezone-validation.ts +41 -0
  419. package/src/presentation/api/routes/tables/upsert-helpers.ts +471 -0
  420. package/src/presentation/api/routes/tables/utils.ts +159 -0
  421. package/src/presentation/api/routes/tables/view-routes.ts +51 -0
  422. package/src/presentation/api/routes/tables.ts +9 -0
  423. package/src/presentation/api/utils/context-helpers.ts +43 -0
  424. package/src/presentation/api/utils/error-sanitizer.ts +235 -0
  425. package/src/presentation/api/utils/field-permission-validator.ts +53 -0
  426. package/src/presentation/api/utils/filter-field-validator.ts +90 -0
  427. package/src/presentation/api/utils/index.ts +13 -0
  428. package/src/presentation/api/utils/run-effect.ts +94 -0
  429. package/src/presentation/api/utils/validate-request.ts +89 -0
  430. package/src/presentation/api/validation/index.ts +29 -0
  431. package/src/presentation/api/validation/rules/field-rules.ts +158 -0
  432. package/src/presentation/api/validation/rules/record-rules.ts +73 -0
  433. package/src/presentation/cli/index.ts +19 -0
  434. package/src/presentation/cli/schema-loader.ts +172 -0
  435. package/src/presentation/hooks/use-breakpoint.ts +155 -0
  436. package/src/presentation/rendering/render-error-pages.tsx +60 -0
  437. package/src/presentation/rendering/render-homepage.tsx +137 -0
  438. package/src/presentation/scripts/script-renderers.ts +112 -0
  439. package/src/presentation/styling/animation-composer.ts +117 -0
  440. package/src/presentation/styling/index.ts +13 -0
  441. package/src/presentation/styling/parse-style.ts +243 -0
  442. package/src/presentation/styling/style-utils.ts +50 -0
  443. package/src/presentation/styling/theme-colors.ts +53 -0
  444. package/src/presentation/translations/component-utils.ts +54 -0
  445. package/src/presentation/translations/index.ts +16 -0
  446. package/src/presentation/translations/translation-resolver.ts +22 -0
  447. package/src/presentation/ui/languages/language-switcher.tsx +119 -0
  448. package/src/presentation/ui/metadata/analytics-builders.tsx +174 -0
  449. package/src/presentation/ui/metadata/analytics-head.tsx +39 -0
  450. package/src/presentation/ui/metadata/custom-elements-builders.tsx +157 -0
  451. package/src/presentation/ui/metadata/extract-component-meta.ts +108 -0
  452. package/src/presentation/ui/metadata/head-elements.tsx +164 -0
  453. package/src/presentation/ui/metadata/index.tsx +35 -0
  454. package/src/presentation/ui/metadata/meta-utils.tsx +42 -0
  455. package/src/presentation/ui/metadata/open-graph-meta.tsx +57 -0
  456. package/src/presentation/ui/metadata/structured-data-from-component.tsx +134 -0
  457. package/src/presentation/ui/metadata/structured-data.tsx +88 -0
  458. package/src/presentation/ui/metadata/twitter-card-meta.tsx +80 -0
  459. package/src/presentation/ui/pages/DefaultHomePage.tsx +43 -0
  460. package/src/presentation/ui/pages/DefaultPageConfigs.ts +220 -0
  461. package/src/presentation/ui/pages/DynamicPage.tsx +307 -0
  462. package/src/presentation/ui/pages/ErrorPage.tsx +25 -0
  463. package/src/presentation/ui/pages/NotFoundPage.tsx +25 -0
  464. package/src/presentation/ui/pages/PageBodyScripts.tsx +242 -0
  465. package/src/presentation/ui/pages/PageBodyStyles.ts +52 -0
  466. package/src/presentation/ui/pages/PageHead.tsx +380 -0
  467. package/src/presentation/ui/pages/PageLangResolver.ts +58 -0
  468. package/src/presentation/ui/pages/PageMain.tsx +58 -0
  469. package/src/presentation/ui/pages/PageMetadata.ts +168 -0
  470. package/src/presentation/ui/pages/PageMetadataI18n.ts +169 -0
  471. package/src/presentation/ui/pages/PageScripts.ts +78 -0
  472. package/src/presentation/ui/pages/SectionRenderer.tsx +67 -0
  473. package/src/presentation/ui/pages/SectionSpacing.tsx +131 -0
  474. package/src/presentation/ui/sections/component-renderer.tsx +426 -0
  475. package/src/presentation/ui/sections/component-renderer.types.ts +33 -0
  476. package/src/presentation/ui/sections/components/component-reference-handler.tsx +74 -0
  477. package/src/presentation/ui/sections/components/component-resolution.ts +65 -0
  478. package/src/presentation/ui/sections/components/index.ts +9 -0
  479. package/src/presentation/ui/sections/hero.tsx +394 -0
  480. package/src/presentation/ui/sections/props/component-builder.ts +183 -0
  481. package/src/presentation/ui/sections/props/element-props.ts +179 -0
  482. package/src/presentation/ui/sections/props/index.ts +9 -0
  483. package/src/presentation/ui/sections/props/prop-conversion.ts +171 -0
  484. package/src/presentation/ui/sections/props/props-builder-config.ts +42 -0
  485. package/src/presentation/ui/sections/props/props-builder.ts +296 -0
  486. package/src/presentation/ui/sections/renderers/element-renderers/html-element-renderer.tsx +124 -0
  487. package/src/presentation/ui/sections/renderers/element-renderers/index.ts +59 -0
  488. package/src/presentation/ui/sections/renderers/element-renderers/interactive-renderers.tsx +231 -0
  489. package/src/presentation/ui/sections/renderers/element-renderers/media-renderers.tsx +102 -0
  490. package/src/presentation/ui/sections/renderers/element-renderers/text-content-renderers.tsx +42 -0
  491. package/src/presentation/ui/sections/renderers/element-renderers.ts +53 -0
  492. package/src/presentation/ui/sections/renderers/html-element-helpers.ts +100 -0
  493. package/src/presentation/ui/sections/renderers/specialized-renderers.tsx +212 -0
  494. package/src/presentation/ui/sections/rendering/component-dispatch-config.ts +31 -0
  495. package/src/presentation/ui/sections/rendering/component-registry/index.ts +39 -0
  496. package/src/presentation/ui/sections/rendering/component-registry/interactive-components.ts +54 -0
  497. package/src/presentation/ui/sections/rendering/component-registry/media-components.ts +36 -0
  498. package/src/presentation/ui/sections/rendering/component-registry/special-components.tsx +153 -0
  499. package/src/presentation/ui/sections/rendering/component-registry/structural-components.ts +215 -0
  500. package/src/presentation/ui/sections/rendering/component-registry/text-components.ts +57 -0
  501. package/src/presentation/ui/sections/rendering/component-registry-helpers.tsx +29 -0
  502. package/src/presentation/ui/sections/rendering/component-registry.tsx +21 -0
  503. package/src/presentation/ui/sections/rendering/component-type-dispatcher.tsx +33 -0
  504. package/src/presentation/ui/sections/rendering/index.ts +9 -0
  505. package/src/presentation/ui/sections/responsive/responsive-children-builder.tsx +96 -0
  506. package/src/presentation/ui/sections/responsive/responsive-content-builder.tsx +95 -0
  507. package/src/presentation/ui/sections/responsive/responsive-props-merger.ts +195 -0
  508. package/src/presentation/ui/sections/responsive/responsive-resolver.ts +213 -0
  509. package/src/presentation/ui/sections/styling/animation-composer-wrapper.ts +65 -0
  510. package/src/presentation/ui/sections/styling/class-builders.ts +45 -0
  511. package/src/presentation/ui/sections/styling/color-resolver.ts +43 -0
  512. package/src/presentation/ui/sections/styling/hover-interaction-handler.ts +107 -0
  513. package/src/presentation/ui/sections/styling/index.ts +9 -0
  514. package/src/presentation/ui/sections/styling/interaction-props-builder.ts +55 -0
  515. package/src/presentation/ui/sections/styling/shadow-resolver.ts +83 -0
  516. package/src/presentation/ui/sections/styling/spacing-resolver.ts +104 -0
  517. package/src/presentation/ui/sections/styling/style-processor.ts +170 -0
  518. package/src/presentation/ui/sections/styling/theme-tokens.ts +184 -0
  519. package/src/presentation/ui/sections/translations/i18n-content-resolver.ts +198 -0
  520. package/src/presentation/ui/sections/translations/index.ts +9 -0
  521. package/src/presentation/ui/sections/translations/translation-handler.ts +143 -0
  522. package/src/presentation/ui/sections/translations/variable-substitution.ts +225 -0
  523. package/src/presentation/ui/sections/utils/time-parser.ts +82 -0
  524. package/src/presentation/utils/link-attributes.ts +50 -0
  525. package/src/presentation/utils/string-utils.ts +58 -0
  526. package/src/presentation/utils/styles.ts +50 -0
  527. package/tsconfig.json +46 -0
@@ -0,0 +1,399 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ /**
9
+ * Rate limiting state for admin endpoints
10
+ * Maps IP addresses to request timestamps
11
+ * In production, this should use Redis or similar distributed storage
12
+ */
13
+ const adminRateLimitState = new Map<string, number[]>()
14
+
15
+ /**
16
+ * Rate limiting configuration constants
17
+ */
18
+ const RATE_LIMIT_WINDOW_MS = 1000 // 1 second window
19
+ const RATE_LIMIT_MAX_REQUESTS = 10 // Maximum requests per window
20
+
21
+ /**
22
+ * Get rate limit window duration in milliseconds from environment variable
23
+ * Defaults to 60 seconds (production) if not set
24
+ * Tests should set RATE_LIMIT_WINDOW_SECONDS=5 for faster execution
25
+ */
26
+ const getRateLimitWindowMs = (): number => {
27
+ const windowSeconds = process.env.RATE_LIMIT_WINDOW_SECONDS
28
+ return windowSeconds ? parseInt(windowSeconds, 10) * 1000 : 60 * 1000
29
+ }
30
+
31
+ /**
32
+ * Get recent requests within the rate limit window for an IP address
33
+ * Returns filtered array of timestamps within RATE_LIMIT_WINDOW_MS
34
+ * Single source of truth for request filtering (DRY principle)
35
+ */
36
+ export const getRecentRequests = (ip: string): readonly number[] => {
37
+ const now = Date.now()
38
+ const requestHistory = adminRateLimitState.get(ip) ?? []
39
+ return requestHistory.filter((timestamp) => now - timestamp < RATE_LIMIT_WINDOW_MS)
40
+ }
41
+
42
+ /**
43
+ * Check rate limit for an IP address
44
+ * Returns true if rate limit is exceeded, false otherwise
45
+ */
46
+ export const isRateLimitExceeded = (ip: string): boolean => {
47
+ return getRecentRequests(ip).length >= RATE_LIMIT_MAX_REQUESTS
48
+ }
49
+
50
+ /**
51
+ * Record a request for rate limiting
52
+ * Updates the request history for the given IP address
53
+ * Returns the updated request history for testing/debugging
54
+ */
55
+ export const recordRateLimitRequest = (ip: string): readonly number[] => {
56
+ const recentRequests = getRecentRequests(ip)
57
+ const now = Date.now()
58
+ const updated = [...recentRequests, now]
59
+ // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
60
+ adminRateLimitState.set(ip, updated)
61
+ return updated
62
+ }
63
+
64
+ /**
65
+ * Extract IP address from request headers
66
+ * Returns the client IP from x-forwarded-for or defaults to localhost
67
+ */
68
+ export const extractClientIp = (forwardedFor: string | undefined): string => {
69
+ return forwardedFor ? (forwardedFor.split(',')[0]?.trim() ?? '127.0.0.1') : '127.0.0.1'
70
+ }
71
+
72
+ /**
73
+ * Rate limiting configuration for authentication endpoints
74
+ * Security-critical endpoints to prevent brute force attacks
75
+ */
76
+ interface EndpointRateLimitConfig {
77
+ readonly windowMs: number
78
+ readonly maxRequests: number
79
+ }
80
+
81
+ /**
82
+ * Get authentication rate limit configurations
83
+ * Uses configurable window from environment variable
84
+ */
85
+ const getAuthRateLimitConfigs = (): Record<string, EndpointRateLimitConfig> => {
86
+ const windowMs = getRateLimitWindowMs()
87
+
88
+ return {
89
+ '/api/auth/sign-in/email': {
90
+ windowMs,
91
+ maxRequests: 20, // 20 attempts per window (prevents brute force while allowing legitimate retries)
92
+ },
93
+ '/api/auth/sign-up/email': {
94
+ windowMs,
95
+ maxRequests: 20, // 20 signups per window (prevents abuse while allowing test scenarios)
96
+ },
97
+ '/api/auth/request-password-reset': {
98
+ windowMs,
99
+ maxRequests: 10, // 10 attempts per window (prevents enumeration while allowing legitimate use)
100
+ },
101
+ }
102
+ }
103
+
104
+ const AUTH_RATE_LIMIT_CONFIGS: Record<string, EndpointRateLimitConfig> = getAuthRateLimitConfigs()
105
+
106
+ /**
107
+ * Rate limiting state for authentication endpoints
108
+ * Maps endpoint + IP to request timestamps
109
+ */
110
+ const authRateLimitState = new Map<string, number[]>()
111
+
112
+ /**
113
+ * Get rate limit key for endpoint + IP combination
114
+ */
115
+ const getRateLimitKey = (endpoint: string, ip: string): string => {
116
+ return `${endpoint}:${ip}`
117
+ }
118
+
119
+ /**
120
+ * Get recent requests within the rate limit window for an endpoint + IP
121
+ */
122
+ export const getAuthRecentRequests = (endpoint: string, ip: string): readonly number[] => {
123
+ const config = AUTH_RATE_LIMIT_CONFIGS[endpoint]
124
+ if (!config) return []
125
+
126
+ const now = Date.now()
127
+ const key = getRateLimitKey(endpoint, ip)
128
+ const requestHistory = authRateLimitState.get(key) ?? []
129
+ return requestHistory.filter((timestamp) => now - timestamp < config.windowMs)
130
+ }
131
+
132
+ /**
133
+ * Check if auth endpoint rate limit is exceeded for an IP
134
+ */
135
+ export const isAuthRateLimitExceeded = (endpoint: string, ip: string): boolean => {
136
+ const config = AUTH_RATE_LIMIT_CONFIGS[endpoint]
137
+ if (!config) return false
138
+
139
+ return getAuthRecentRequests(endpoint, ip).length >= config.maxRequests
140
+ }
141
+
142
+ /**
143
+ * Record a request for auth endpoint rate limiting
144
+ */
145
+ export const recordAuthRateLimitRequest = (endpoint: string, ip: string): readonly number[] => {
146
+ const recentRequests = getAuthRecentRequests(endpoint, ip)
147
+ const now = Date.now()
148
+ const key = getRateLimitKey(endpoint, ip)
149
+ const updated = [...recentRequests, now]
150
+ // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
151
+ authRateLimitState.set(key, updated)
152
+ return updated
153
+ }
154
+
155
+ /**
156
+ * Calculate seconds until auth rate limit window resets
157
+ */
158
+ export const getAuthRateLimitRetryAfter = (endpoint: string, ip: string): number => {
159
+ const config = AUTH_RATE_LIMIT_CONFIGS[endpoint]
160
+ if (!config) return 0
161
+
162
+ const recentRequests = getAuthRecentRequests(endpoint, ip)
163
+ if (recentRequests.length === 0) return 0
164
+
165
+ const oldestRequest = Math.min(...recentRequests)
166
+ const resetTime = oldestRequest + config.windowMs
167
+ const now = Date.now()
168
+ const retryAfterMs = Math.max(0, resetTime - now)
169
+
170
+ return Math.ceil(retryAfterMs / 1000) // Convert to seconds
171
+ }
172
+
173
+ /**
174
+ * Rate limiting configuration for table API endpoints
175
+ * Higher limits than auth endpoints since legitimate usage involves frequent data operations
176
+ */
177
+ const getTablesRateLimitConfigs = (): Record<string, EndpointRateLimitConfig> => {
178
+ const windowMs = getRateLimitWindowMs()
179
+
180
+ return {
181
+ // List tables endpoint
182
+ 'GET:/api/tables': {
183
+ windowMs,
184
+ maxRequests: 100, // 100 requests per window (frequent polling for table lists)
185
+ },
186
+ // Get table records endpoint (read operations)
187
+ 'GET:/api/tables/*': {
188
+ windowMs,
189
+ maxRequests: 100, // 100 requests per window (frequent data fetching)
190
+ },
191
+ // Create record endpoint (write operations - stricter limits)
192
+ 'POST:/api/tables/*': {
193
+ windowMs,
194
+ maxRequests: 50, // 50 requests per window (prevent data flooding)
195
+ },
196
+ }
197
+ }
198
+
199
+ const TABLES_RATE_LIMIT_CONFIGS: Record<string, EndpointRateLimitConfig> =
200
+ getTablesRateLimitConfigs()
201
+
202
+ /**
203
+ * Rate limiting state for table endpoints
204
+ * Maps method:endpoint + IP to request timestamps
205
+ */
206
+ const tablesRateLimitState = new Map<string, number[]>()
207
+
208
+ /**
209
+ * Get rate limit key for table endpoint (includes HTTP method)
210
+ */
211
+ const getTablesRateLimitKey = (method: string, path: string, ip: string): string => {
212
+ // Match exact path or wildcard pattern
213
+ const configKey = TABLES_RATE_LIMIT_CONFIGS[`${method}:${path}`]
214
+ ? `${method}:${path}`
215
+ : `${method}:/api/tables/*`
216
+
217
+ return `${configKey}:${ip}`
218
+ }
219
+
220
+ /**
221
+ * Get recent requests for table endpoint
222
+ */
223
+ export const getTablesRecentRequests = (
224
+ method: string,
225
+ path: string,
226
+ ip: string
227
+ ): readonly number[] => {
228
+ const configKey = TABLES_RATE_LIMIT_CONFIGS[`${method}:${path}`]
229
+ ? `${method}:${path}`
230
+ : `${method}:/api/tables/*`
231
+
232
+ const config = TABLES_RATE_LIMIT_CONFIGS[configKey]
233
+ if (!config) return []
234
+
235
+ const now = Date.now()
236
+ const key = getTablesRateLimitKey(method, path, ip)
237
+ const requestHistory = tablesRateLimitState.get(key) ?? []
238
+ return requestHistory.filter((timestamp) => now - timestamp < config.windowMs)
239
+ }
240
+
241
+ /**
242
+ * Check if table endpoint rate limit is exceeded
243
+ */
244
+ export const isTablesRateLimitExceeded = (method: string, path: string, ip: string): boolean => {
245
+ const configKey = TABLES_RATE_LIMIT_CONFIGS[`${method}:${path}`]
246
+ ? `${method}:${path}`
247
+ : `${method}:/api/tables/*`
248
+
249
+ const config = TABLES_RATE_LIMIT_CONFIGS[configKey]
250
+ if (!config) return false
251
+
252
+ return getTablesRecentRequests(method, path, ip).length >= config.maxRequests
253
+ }
254
+
255
+ /**
256
+ * Record a request for table endpoint rate limiting
257
+ */
258
+ export const recordTablesRateLimitRequest = (
259
+ method: string,
260
+ path: string,
261
+ ip: string
262
+ ): readonly number[] => {
263
+ const recentRequests = getTablesRecentRequests(method, path, ip)
264
+ const now = Date.now()
265
+ const key = getTablesRateLimitKey(method, path, ip)
266
+ const updated = [...recentRequests, now]
267
+ // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
268
+ tablesRateLimitState.set(key, updated)
269
+ return updated
270
+ }
271
+
272
+ /**
273
+ * Calculate seconds until rate limit window resets
274
+ */
275
+ export const getTablesRateLimitRetryAfter = (method: string, path: string, ip: string): number => {
276
+ const recentRequests = getTablesRecentRequests(method, path, ip)
277
+ if (recentRequests.length === 0) return 0
278
+
279
+ const windowMs = getRateLimitWindowMs()
280
+ const oldestRequest = Math.min(...recentRequests)
281
+ const resetTime = oldestRequest + windowMs
282
+ const now = Date.now()
283
+ const retryAfterMs = Math.max(0, resetTime - now)
284
+
285
+ return Math.ceil(retryAfterMs / 1000) // Convert to seconds
286
+ }
287
+
288
+ /**
289
+ * Rate limiting configuration for activity API endpoints
290
+ * Moderate limits since activity logs can be polled frequently for real-time updates
291
+ */
292
+ const getActivityRateLimitConfigs = (): Record<string, EndpointRateLimitConfig> => {
293
+ const windowMs = getRateLimitWindowMs()
294
+
295
+ return {
296
+ // List activity endpoint
297
+ 'GET:/api/activity': {
298
+ windowMs,
299
+ maxRequests: 60, // 60 requests per window (polling for activity updates)
300
+ },
301
+ // Get activity detail endpoint
302
+ 'GET:/api/activity/*': {
303
+ windowMs,
304
+ maxRequests: 60, // 60 requests per window (activity log detail fetching)
305
+ },
306
+ }
307
+ }
308
+
309
+ const ACTIVITY_RATE_LIMIT_CONFIGS: Record<string, EndpointRateLimitConfig> =
310
+ getActivityRateLimitConfigs()
311
+
312
+ /**
313
+ * Rate limiting state for activity endpoints
314
+ * Maps method:endpoint + IP to request timestamps
315
+ */
316
+ const activityRateLimitState = new Map<string, number[]>()
317
+
318
+ /**
319
+ * Get rate limit key for activity endpoint (includes HTTP method)
320
+ */
321
+ const getActivityRateLimitKey = (method: string, path: string, ip: string): string => {
322
+ const configKey = ACTIVITY_RATE_LIMIT_CONFIGS[`${method}:${path}`]
323
+ ? `${method}:${path}`
324
+ : `${method}:/api/activity/*`
325
+
326
+ return `${configKey}:${ip}`
327
+ }
328
+
329
+ /**
330
+ * Get recent requests for activity endpoint
331
+ */
332
+ export const getActivityRecentRequests = (
333
+ method: string,
334
+ path: string,
335
+ ip: string
336
+ ): readonly number[] => {
337
+ const configKey = ACTIVITY_RATE_LIMIT_CONFIGS[`${method}:${path}`]
338
+ ? `${method}:${path}`
339
+ : `${method}:/api/activity/*`
340
+
341
+ const config = ACTIVITY_RATE_LIMIT_CONFIGS[configKey]
342
+ if (!config) return []
343
+
344
+ const now = Date.now()
345
+ const key = getActivityRateLimitKey(method, path, ip)
346
+ const requestHistory = activityRateLimitState.get(key) ?? []
347
+ return requestHistory.filter((timestamp) => now - timestamp < config.windowMs)
348
+ }
349
+
350
+ /**
351
+ * Check if activity endpoint rate limit is exceeded
352
+ */
353
+ export const isActivityRateLimitExceeded = (method: string, path: string, ip: string): boolean => {
354
+ const configKey = ACTIVITY_RATE_LIMIT_CONFIGS[`${method}:${path}`]
355
+ ? `${method}:${path}`
356
+ : `${method}:/api/activity/*`
357
+
358
+ const config = ACTIVITY_RATE_LIMIT_CONFIGS[configKey]
359
+ if (!config) return false
360
+
361
+ return getActivityRecentRequests(method, path, ip).length >= config.maxRequests
362
+ }
363
+
364
+ /**
365
+ * Record a request for activity endpoint rate limiting
366
+ */
367
+ export const recordActivityRateLimitRequest = (
368
+ method: string,
369
+ path: string,
370
+ ip: string
371
+ ): readonly number[] => {
372
+ const recentRequests = getActivityRecentRequests(method, path, ip)
373
+ const now = Date.now()
374
+ const key = getActivityRateLimitKey(method, path, ip)
375
+ const updated = [...recentRequests, now]
376
+ // eslint-disable-next-line functional/no-expression-statements, functional/immutable-data -- Rate limiting requires mutable state
377
+ activityRateLimitState.set(key, updated)
378
+ return updated
379
+ }
380
+
381
+ /**
382
+ * Calculate seconds until activity rate limit window resets
383
+ */
384
+ export const getActivityRateLimitRetryAfter = (
385
+ method: string,
386
+ path: string,
387
+ ip: string
388
+ ): number => {
389
+ const recentRequests = getActivityRecentRequests(method, path, ip)
390
+ if (recentRequests.length === 0) return 0
391
+
392
+ const windowMs = getRateLimitWindowMs()
393
+ const oldestRequest = Math.min(...recentRequests)
394
+ const resetTime = oldestRequest + windowMs
395
+ const now = Date.now()
396
+ const retryAfterMs = Math.max(0, resetTime - now)
397
+
398
+ return Math.ceil(retryAfterMs / 1000) // Convert to seconds
399
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type Hono } from 'hono'
9
+ import { cors } from 'hono/cors'
10
+ import { createAuthInstance } from '@/infrastructure/auth/better-auth/auth'
11
+ import { logError } from '@/infrastructure/logging/logger'
12
+ import {
13
+ isRateLimitExceeded,
14
+ recordRateLimitRequest,
15
+ extractClientIp,
16
+ isAuthRateLimitExceeded,
17
+ recordAuthRateLimitRequest,
18
+ getAuthRateLimitRetryAfter,
19
+ } from './auth-route-utils'
20
+ import type { App } from '@/domain/models/app'
21
+
22
+ /**
23
+ * Apply authentication check middleware for admin endpoints
24
+ *
25
+ * This middleware ensures authentication is checked BEFORE parameter validation,
26
+ * preventing information leakage through error responses (400/404/403 vs 401).
27
+ *
28
+ * Without this middleware, Better Auth's admin endpoints validate parameters first,
29
+ * allowing unauthenticated users to probe for valid user IDs by observing response codes.
30
+ *
31
+ * Returns a Hono app with authentication middleware applied
32
+ */
33
+ const applyAuthCheckMiddleware = (
34
+ honoApp: Readonly<Hono>,
35
+ authInstance: Readonly<ReturnType<typeof createAuthInstance>>
36
+ ): Readonly<Hono> => {
37
+ return honoApp.use('/api/auth/admin/*', async (c, next) => {
38
+ try {
39
+ // Check if request has a valid session cookie
40
+ const session = await authInstance.api.getSession({
41
+ headers: c.req.raw.headers,
42
+ })
43
+
44
+ // Return 401 if no valid session (BEFORE any parameter validation)
45
+ if (!session) {
46
+ return c.json(
47
+ { success: false, message: 'Authentication required', code: 'UNAUTHORIZED' },
48
+ 401
49
+ )
50
+ }
51
+
52
+ // Session exists - proceed to next handler
53
+ // eslint-disable-next-line functional/no-expression-statements -- Hono middleware requires calling next()
54
+ await next()
55
+ } catch (error) {
56
+ // If session check fails, return 401 (assume unauthenticated)
57
+ logError('[Auth Middleware] Session check error', error)
58
+ return c.json(
59
+ { success: false, message: 'Authentication required', code: 'UNAUTHORIZED' },
60
+ 401
61
+ )
62
+ }
63
+ })
64
+ }
65
+
66
+ /**
67
+ * Apply rate limiting middleware for admin endpoints
68
+ * Returns a Hono app with rate limiting middleware applied
69
+ */
70
+ const applyRateLimitMiddleware = (honoApp: Readonly<Hono>): Readonly<Hono> => {
71
+ return honoApp.use('/api/auth/admin/*', async (c, next) => {
72
+ const ip = extractClientIp(c.req.header('x-forwarded-for'))
73
+
74
+ if (isRateLimitExceeded(ip)) {
75
+ return c.json(
76
+ {
77
+ success: false,
78
+ message: 'Too many requests. Please try again later.',
79
+ code: 'RATE_LIMITED',
80
+ },
81
+ 429
82
+ )
83
+ }
84
+
85
+ recordRateLimitRequest(ip) // eslint-disable-line functional/no-expression-statements -- Rate limiting state update
86
+
87
+ // eslint-disable-next-line functional/no-expression-statements -- Hono middleware requires calling next()
88
+ await next()
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Apply rate limiting middleware for authentication endpoints
94
+ *
95
+ * Protects security-critical authentication endpoints from brute force attacks:
96
+ * - sign-in: 5 attempts per 60 seconds (prevent credential stuffing)
97
+ * - sign-up: 5 attempts per 60 seconds (prevent account creation abuse)
98
+ * - request-password-reset: 3 attempts per 60 seconds (prevent email enumeration)
99
+ *
100
+ * Returns a Hono app with rate limiting middleware applied
101
+ */
102
+ const applyAuthRateLimitMiddleware = (honoApp: Readonly<Hono>): Readonly<Hono> => {
103
+ const endpoints = [
104
+ '/api/auth/sign-in/email',
105
+ '/api/auth/sign-up/email',
106
+ '/api/auth/request-password-reset',
107
+ ]
108
+
109
+ const result = endpoints.reduce((app, endpoint) => {
110
+ return app.use(endpoint, async (c, next) => {
111
+ const ip = extractClientIp(c.req.header('x-forwarded-for'))
112
+ const { path } = c.req
113
+
114
+ if (isAuthRateLimitExceeded(path, ip)) {
115
+ const retryAfter = getAuthRateLimitRetryAfter(path, ip)
116
+ return c.json(
117
+ {
118
+ success: false,
119
+ message: 'Too many requests. Please try again later.',
120
+ code: 'RATE_LIMITED',
121
+ },
122
+ 429,
123
+ { 'Retry-After': retryAfter.toString() }
124
+ )
125
+ }
126
+
127
+ recordAuthRateLimitRequest(path, ip) // eslint-disable-line functional/no-expression-statements -- Rate limiting state update
128
+
129
+ // eslint-disable-next-line functional/no-expression-statements -- Hono middleware requires calling next()
130
+ await next()
131
+ })
132
+ }, honoApp)
133
+
134
+ return result
135
+ }
136
+
137
+ /**
138
+ * Setup CORS middleware for Better Auth endpoints
139
+ *
140
+ * Configures CORS to allow:
141
+ * - All localhost origins for development/testing
142
+ * - Credentials for cookie-based authentication
143
+ * - Common headers and methods
144
+ *
145
+ * If no auth configuration is provided in the app, middleware is not applied.
146
+ *
147
+ * @param honoApp - Hono application instance
148
+ * @param app - Application configuration with auth settings
149
+ * @returns Hono app with CORS middleware configured (or unchanged if auth is disabled)
150
+ */
151
+ export function setupAuthMiddleware(honoApp: Readonly<Hono>, app?: App): Readonly<Hono> {
152
+ // If no auth config is provided, don't apply CORS middleware
153
+ if (!app?.auth) {
154
+ return honoApp
155
+ }
156
+
157
+ return honoApp.use(
158
+ '/api/auth/*',
159
+ cors({
160
+ origin: (origin) => {
161
+ // Allow all localhost origins for development and testing
162
+ if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) {
163
+ return origin
164
+ }
165
+ // In production, this should be configured with specific allowed origins
166
+ return origin
167
+ },
168
+ allowHeaders: ['Content-Type', 'Authorization'],
169
+ allowMethods: ['POST', 'GET', 'OPTIONS'],
170
+ exposeHeaders: ['Content-Length'],
171
+ maxAge: 600,
172
+ credentials: true, // Required for cookie-based authentication
173
+ })
174
+ )
175
+ }
176
+
177
+ /**
178
+ * Setup Better Auth routes with dynamic configuration
179
+ *
180
+ * Mounts Better Auth handler at /api/auth/* which provides all authentication endpoints.
181
+ *
182
+ * Creates a Better Auth instance dynamically based on the app's auth configuration,
183
+ * allowing features like requireEmailVerification to be controlled per app.
184
+ *
185
+ * IMPORTANT: Better Auth instance is created once per app configuration and reused
186
+ * across all requests to maintain internal state consistency.
187
+ *
188
+ * If no auth configuration is provided, no auth routes are registered and all
189
+ * /api/auth/* requests will return 404 Not Found.
190
+ *
191
+ * Better Auth natively provides:
192
+ * - Authentication: sign-up, sign-in, sign-out, verify-email, send-verification-email
193
+ * - Admin Plugin: list-users, get-user, set-role, ban-user, unban-user, impersonate-user, stop-impersonating
194
+ * - Organization Plugin: create-organization, list-organizations, get-organization, set-active-organization
195
+ * - Two-Factor: enable, disable, verify
196
+ * - Magic Link: send, verify
197
+ *
198
+ * Native Better Auth handles:
199
+ * - Banned user rejection (automatic in admin plugin)
200
+ * - Single-use verification tokens (automatic)
201
+ * - Admin role validation (via adminRoles/adminUserIds config)
202
+ * - Organization membership validation (automatic)
203
+ *
204
+ * @param honoApp - Hono application instance
205
+ * @param app - Application configuration with auth settings
206
+ * @returns Hono app with auth routes configured (or unchanged if auth is disabled)
207
+ */
208
+ export function setupAuthRoutes(honoApp: Readonly<Hono>, app?: App): Readonly<Hono> {
209
+ // If no auth config is provided, don't register any auth routes
210
+ // This causes all /api/auth/* requests to return 404 (not found)
211
+ if (!app?.auth) {
212
+ return honoApp
213
+ }
214
+
215
+ // Create Better Auth instance with app-specific configuration (once per app startup)
216
+ // This instance is reused across all requests to maintain internal state
217
+ const authInstance = createAuthInstance(app.auth)
218
+
219
+ // Apply authentication check middleware (admin features always enabled when auth is configured)
220
+ // This ensures 401 is returned before any parameter validation, preventing information leakage
221
+ const appWithAuthCheck = applyAuthCheckMiddleware(honoApp, authInstance)
222
+
223
+ // Apply rate limiting middleware to admin routes
224
+ const appWithAdminRateLimit = applyRateLimitMiddleware(appWithAuthCheck)
225
+
226
+ // Apply rate limiting middleware to authentication endpoints (sign-in, sign-up, password reset)
227
+ const appWithAuthRateLimit = applyAuthRateLimitMiddleware(appWithAdminRateLimit)
228
+
229
+ // Mount Better Auth handler for all /api/auth/* routes
230
+ // Better Auth natively handles:
231
+ // - Authentication flows (sign-up, sign-in, sign-out, email verification)
232
+ // - Admin operations (list-users, get-user, set-role, ban-user, impersonation)
233
+ // - Organization management (create, list, get, set-active, invite members)
234
+ // - Two-factor authentication (enable, disable, verify)
235
+ // - Magic link authentication (send, verify)
236
+ // - Banned user rejection (automatic for admin plugin)
237
+ // - Single-use verification tokens (automatic)
238
+ // - Team operations (create-team, add-team-member, etc.) when teams plugin enabled
239
+ //
240
+ // IMPORTANT: Better Auth handles its own routing and expects the FULL request path
241
+ // including the /api/auth prefix. We pass the original request without modification.
242
+ return appWithAuthRateLimit.on(['POST', 'GET'], '/api/auth/*', async (c) => {
243
+ return authInstance.handler(c.req.raw)
244
+ })
245
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { Scalar } from '@scalar/hono-api-reference'
9
+ import { type Hono } from 'hono'
10
+ import { auth } from '@/infrastructure/auth/better-auth/auth'
11
+ import { getOpenAPIDocument } from '@/infrastructure/server/route-setup/openapi-schema'
12
+
13
+ /**
14
+ * Setup OpenAPI documentation routes
15
+ *
16
+ * Mounts:
17
+ * - GET /api/openapi.json - Application API schema
18
+ * - GET /api/auth/openapi.json - Better Auth API schema
19
+ * - GET /api/scalar - Unified Scalar API documentation UI
20
+ *
21
+ * @param honoApp - Hono application instance
22
+ * @returns Hono app with OpenAPI routes configured
23
+ */
24
+ export function setupOpenApiRoutes(honoApp: Readonly<Hono>): Readonly<Hono> {
25
+ return honoApp
26
+ .get('/api/openapi.json', (c) => {
27
+ const openApiDoc = getOpenAPIDocument()
28
+ return c.json(openApiDoc)
29
+ })
30
+ .get('/api/auth/openapi.json', async (c) => {
31
+ const authOpenApiDoc = await auth.api.generateOpenAPISchema()
32
+ return c.json(authOpenApiDoc)
33
+ })
34
+ .get(
35
+ '/api/scalar',
36
+ Scalar({
37
+ pageTitle: 'Sovrium API Documentation',
38
+ theme: 'default',
39
+ sources: [
40
+ { url: '/api/openapi.json', title: 'API' },
41
+ { url: '/api/auth/openapi.json', title: 'Auth' },
42
+ ],
43
+ })
44
+ )
45
+ }