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.
- package/CHANGELOG.md +3497 -0
- package/LICENSE.md +147 -0
- package/LICENSE_EE.md +297 -0
- package/README.md +321 -0
- package/drizzle/0000_melted_kabuki.sql +163 -0
- package/drizzle/meta/0000_snapshot.json +1216 -0
- package/drizzle/meta/_journal.json +13 -0
- package/package.json +167 -0
- package/schemas/0.0.1/app.openapi.json +70 -0
- package/schemas/0.0.1/app.schema.json +7961 -0
- package/schemas/0.0.2/app.openapi.json +80 -0
- package/schemas/0.0.2/app.schema.json +8829 -0
- package/schemas/development/app.openapi.json +70 -0
- package/schemas/development/app.schema.json +7456 -0
- package/src/application/errors/app-validation-error.ts +14 -0
- package/src/application/errors/static-generation-error.ts +16 -0
- package/src/application/metadata/favicon-transformer.ts +127 -0
- package/src/application/models/server.ts +27 -0
- package/src/application/ports/models/user-metadata.ts +36 -0
- package/src/application/ports/models/user-session.ts +34 -0
- package/src/application/ports/repositories/activity-log-repository.ts +68 -0
- package/src/application/ports/repositories/activity-repository.ts +49 -0
- package/src/application/ports/repositories/analytics-repository.ts +164 -0
- package/src/application/ports/repositories/auth-repository.ts +33 -0
- package/src/application/ports/repositories/batch-repository.ts +86 -0
- package/src/application/ports/repositories/comment-repository.ts +150 -0
- package/src/application/ports/repositories/index.ts +41 -0
- package/src/application/ports/repositories/table-repository.ts +139 -0
- package/src/application/ports/services/css-compiler.ts +55 -0
- package/src/application/ports/services/index.ts +16 -0
- package/src/application/ports/services/page-renderer.ts +79 -0
- package/src/application/ports/services/server-factory.ts +80 -0
- package/src/application/ports/services/static-site-generator.ts +82 -0
- package/src/application/use-cases/activity/programs.ts +66 -0
- package/src/application/use-cases/analytics/collect-page-view.ts +114 -0
- package/src/application/use-cases/analytics/purge-old-data.ts +40 -0
- package/src/application/use-cases/analytics/query-campaigns.ts +43 -0
- package/src/application/use-cases/analytics/query-devices.ts +36 -0
- package/src/application/use-cases/analytics/query-overview.ts +50 -0
- package/src/application/use-cases/analytics/query-pages.ts +40 -0
- package/src/application/use-cases/analytics/query-referrers.ts +43 -0
- package/src/application/use-cases/analytics/ua-parser.ts +89 -0
- package/src/application/use-cases/analytics/visitor-hash.ts +77 -0
- package/src/application/use-cases/auth/bootstrap-admin.ts +270 -0
- package/src/application/use-cases/list-activity-logs.ts +123 -0
- package/src/application/use-cases/server/generate-static-helpers.ts +374 -0
- package/src/application/use-cases/server/generate-static.ts +287 -0
- package/src/application/use-cases/server/start-server.ts +118 -0
- package/src/application/use-cases/server/startup-error-handler.ts +69 -0
- package/src/application/use-cases/server/static-content-generators.ts +182 -0
- package/src/application/use-cases/server/static-language-generators.ts +181 -0
- package/src/application/use-cases/server/static-url-rewriter.ts +237 -0
- package/src/application/use-cases/server/translation-replacer.ts +164 -0
- package/src/application/use-cases/tables/activity-programs.ts +93 -0
- package/src/application/use-cases/tables/batch-operations.ts +156 -0
- package/src/application/use-cases/tables/comment-programs.ts +436 -0
- package/src/application/use-cases/tables/permissions/permissions.ts +25 -0
- package/src/application/use-cases/tables/programs.ts +435 -0
- package/src/application/use-cases/tables/table-operations.ts +412 -0
- package/src/application/use-cases/tables/user-role.ts +52 -0
- package/src/application/use-cases/tables/utils/display-formatter.ts +471 -0
- package/src/application/use-cases/tables/utils/field-read-filter.ts +189 -0
- package/src/application/use-cases/tables/utils/list-helpers.ts +122 -0
- package/src/application/use-cases/tables/utils/record-transformer.ts +319 -0
- package/src/cli.ts +370 -0
- package/src/domain/errors/create-tagged-error.ts +36 -0
- package/src/domain/errors/index.ts +78 -0
- package/src/domain/models/api/analytics.ts +179 -0
- package/src/domain/models/api/auth.ts +231 -0
- package/src/domain/models/api/common.ts +60 -0
- package/src/domain/models/api/error.ts +89 -0
- package/src/domain/models/api/health.ts +38 -0
- package/src/domain/models/api/index.ts +42 -0
- package/src/domain/models/api/request.ts +132 -0
- package/src/domain/models/api/tables.ts +444 -0
- package/src/domain/models/app/analytics/index.ts +129 -0
- package/src/domain/models/app/auth/config.ts +116 -0
- package/src/domain/models/app/auth/index.ts +230 -0
- package/src/domain/models/app/auth/methods/email-and-password.ts +67 -0
- package/src/domain/models/app/auth/methods/index.ts +11 -0
- package/src/domain/models/app/auth/methods/magic-link.ts +54 -0
- package/src/domain/models/app/auth/oauth/index.ts +8 -0
- package/src/domain/models/app/auth/oauth/providers.ts +105 -0
- package/src/domain/models/app/auth/plugins/admin.ts +130 -0
- package/src/domain/models/app/auth/plugins/index.ts +74 -0
- package/src/domain/models/app/auth/plugins/two-factor.ts +63 -0
- package/src/domain/models/app/auth/roles.ts +179 -0
- package/src/domain/models/app/auth/strategies.ts +191 -0
- package/src/domain/models/app/auth/validation.ts +127 -0
- package/src/domain/models/app/common/branded-ids.ts +200 -0
- package/src/domain/models/app/common/definitions.ts +187 -0
- package/src/domain/models/app/component/common/component-children.ts +119 -0
- package/src/domain/models/app/component/common/component-props.ts +89 -0
- package/src/domain/models/app/component/common/component-reference.ts +170 -0
- package/src/domain/models/app/component/component.ts +81 -0
- package/src/domain/models/app/components.ts +65 -0
- package/src/domain/models/app/description.ts +83 -0
- package/src/domain/models/app/index.ts +258 -0
- package/src/domain/models/app/language/language-config.ts +200 -0
- package/src/domain/models/app/languages.ts +205 -0
- package/src/domain/models/app/name.ts +66 -0
- package/src/domain/models/app/page/common/interactions/click-interaction.ts +116 -0
- package/src/domain/models/app/page/common/interactions/entrance-animation.ts +84 -0
- package/src/domain/models/app/page/common/interactions/hover-interaction.ts +144 -0
- package/src/domain/models/app/page/common/interactions/interactions.ts +64 -0
- package/src/domain/models/app/page/common/interactions/scroll-interaction.ts +93 -0
- package/src/domain/models/app/page/common/responsive.ts +114 -0
- package/src/domain/models/app/page/common/url.ts +35 -0
- package/src/domain/models/app/page/common/variable-reference.ts +53 -0
- package/src/domain/models/app/page/id.ts +44 -0
- package/src/domain/models/app/page/index.ts +270 -0
- package/src/domain/models/app/page/meta/analytics.ts +248 -0
- package/src/domain/models/app/page/meta/custom-elements.ts +180 -0
- package/src/domain/models/app/page/meta/dns-prefetch.ts +77 -0
- package/src/domain/models/app/page/meta/favicon-set.ts +203 -0
- package/src/domain/models/app/page/meta/favicon.ts +50 -0
- package/src/domain/models/app/page/meta/favicons-config.ts +73 -0
- package/src/domain/models/app/page/meta/index.ts +278 -0
- package/src/domain/models/app/page/meta/open-graph.ts +166 -0
- package/src/domain/models/app/page/meta/preload.ts +190 -0
- package/src/domain/models/app/page/meta/structured-data/article.ts +211 -0
- package/src/domain/models/app/page/meta/structured-data/breadcrumb.ts +115 -0
- package/src/domain/models/app/page/meta/structured-data/common-fields.ts +201 -0
- package/src/domain/models/app/page/meta/structured-data/education-event.ts +256 -0
- package/src/domain/models/app/page/meta/structured-data/faq-page.ts +127 -0
- package/src/domain/models/app/page/meta/structured-data/index.ts +95 -0
- package/src/domain/models/app/page/meta/structured-data/local-business.ts +247 -0
- package/src/domain/models/app/page/meta/structured-data/organization.ts +171 -0
- package/src/domain/models/app/page/meta/structured-data/person.ts +138 -0
- package/src/domain/models/app/page/meta/structured-data/postal-address.ts +106 -0
- package/src/domain/models/app/page/meta/structured-data/product.ts +214 -0
- package/src/domain/models/app/page/meta/twitter-card.ts +217 -0
- package/src/domain/models/app/page/name.ts +38 -0
- package/src/domain/models/app/page/path.ts +21 -0
- package/src/domain/models/app/page/scripts/external-scripts.ts +163 -0
- package/src/domain/models/app/page/scripts/features.ts +135 -0
- package/src/domain/models/app/page/scripts/inline-scripts.ts +114 -0
- package/src/domain/models/app/page/scripts/scripts.ts +102 -0
- package/src/domain/models/app/page/sections.ts +298 -0
- package/src/domain/models/app/pages.ts +61 -0
- package/src/domain/models/app/permissions/index.ts +61 -0
- package/src/domain/models/app/permissions/resource-action.ts +114 -0
- package/src/domain/models/app/permissions/roles.ts +120 -0
- package/src/domain/models/app/table/check-constraints.ts +105 -0
- package/src/domain/models/app/table/cycle-detection.ts +124 -0
- package/src/domain/models/app/table/database-identifier.ts +153 -0
- package/src/domain/models/app/table/field-name.ts +36 -0
- package/src/domain/models/app/table/field-types/advanced/array-field.ts +33 -0
- package/src/domain/models/app/table/field-types/advanced/autonumber-field.ts +54 -0
- package/src/domain/models/app/table/field-types/advanced/button-field.ts +56 -0
- package/src/domain/models/app/table/field-types/advanced/color-field.ts +57 -0
- package/src/domain/models/app/table/field-types/advanced/count-field.ts +54 -0
- package/src/domain/models/app/table/field-types/advanced/formula-field.ts +58 -0
- package/src/domain/models/app/table/field-types/advanced/geolocation-field.ts +49 -0
- package/src/domain/models/app/table/field-types/advanced/index.ts +16 -0
- package/src/domain/models/app/table/field-types/advanced/json-field.ts +25 -0
- package/src/domain/models/app/table/field-types/advanced/unknown-field.ts +85 -0
- package/src/domain/models/app/table/field-types/base-field.ts +42 -0
- package/src/domain/models/app/table/field-types/date-time/created-at-field.ts +49 -0
- package/src/domain/models/app/table/field-types/date-time/date-field.ts +95 -0
- package/src/domain/models/app/table/field-types/date-time/deleted-at-field.ts +56 -0
- package/src/domain/models/app/table/field-types/date-time/duration-field.ts +73 -0
- package/src/domain/models/app/table/field-types/date-time/index.ts +12 -0
- package/src/domain/models/app/table/field-types/date-time/updated-at-field.ts +50 -0
- package/src/domain/models/app/table/field-types/index.ts +19 -0
- package/src/domain/models/app/table/field-types/media/barcode-field.ts +58 -0
- package/src/domain/models/app/table/field-types/media/index.ts +10 -0
- package/src/domain/models/app/table/field-types/media/multiple-attachments-field.ts +80 -0
- package/src/domain/models/app/table/field-types/media/single-attachment-field.ts +81 -0
- package/src/domain/models/app/table/field-types/numeric/currency-field.ts +144 -0
- package/src/domain/models/app/table/field-types/numeric/decimal-field.ts +113 -0
- package/src/domain/models/app/table/field-types/numeric/index.ts +13 -0
- package/src/domain/models/app/table/field-types/numeric/integer-field.ts +98 -0
- package/src/domain/models/app/table/field-types/numeric/percentage-field.ts +115 -0
- package/src/domain/models/app/table/field-types/numeric/progress-field.ts +71 -0
- package/src/domain/models/app/table/field-types/numeric/rating-field.ts +74 -0
- package/src/domain/models/app/table/field-types/relational/index.ts +10 -0
- package/src/domain/models/app/table/field-types/relational/lookup-field.ts +46 -0
- package/src/domain/models/app/table/field-types/relational/relationship-field.ts +112 -0
- package/src/domain/models/app/table/field-types/relational/rollup-field.ts +58 -0
- package/src/domain/models/app/table/field-types/selection/checkbox-field.ts +51 -0
- package/src/domain/models/app/table/field-types/selection/index.ts +11 -0
- package/src/domain/models/app/table/field-types/selection/multi-select-field.ts +68 -0
- package/src/domain/models/app/table/field-types/selection/single-select-field.ts +54 -0
- package/src/domain/models/app/table/field-types/selection/status-field.ts +37 -0
- package/src/domain/models/app/table/field-types/text/email-field.ts +80 -0
- package/src/domain/models/app/table/field-types/text/index.ts +13 -0
- package/src/domain/models/app/table/field-types/text/long-text-field.ts +77 -0
- package/src/domain/models/app/table/field-types/text/phone-number-field.ts +82 -0
- package/src/domain/models/app/table/field-types/text/rich-text-field.ts +66 -0
- package/src/domain/models/app/table/field-types/text/single-line-text-field.ts +79 -0
- package/src/domain/models/app/table/field-types/text/url-field.ts +81 -0
- package/src/domain/models/app/table/field-types/user/created-by-field.ts +50 -0
- package/src/domain/models/app/table/field-types/user/deleted-by-field.ts +57 -0
- package/src/domain/models/app/table/field-types/user/index.ts +11 -0
- package/src/domain/models/app/table/field-types/user/updated-by-field.ts +51 -0
- package/src/domain/models/app/table/field-types/user/user-field.ts +52 -0
- package/src/domain/models/app/table/field-types/validation-utils.ts +166 -0
- package/src/domain/models/app/table/fields.ts +216 -0
- package/src/domain/models/app/table/foreign-keys.ts +111 -0
- package/src/domain/models/app/table/formula-keywords.ts +326 -0
- package/src/domain/models/app/table/id.ts +31 -0
- package/src/domain/models/app/table/index.ts +290 -0
- package/src/domain/models/app/table/indexes.ts +80 -0
- package/src/domain/models/app/table/name.ts +37 -0
- package/src/domain/models/app/table/permissions/field-permission.ts +83 -0
- package/src/domain/models/app/table/permissions/index.ts +167 -0
- package/src/domain/models/app/table/permissions/permission-evaluator.ts +372 -0
- package/src/domain/models/app/table/permissions/permission.ts +49 -0
- package/src/domain/models/app/table/primary-key.ts +62 -0
- package/src/domain/models/app/table/table-formula-validation.ts +168 -0
- package/src/domain/models/app/table/table-indexes-validation.ts +38 -0
- package/src/domain/models/app/table/table-permissions-validation.ts +77 -0
- package/src/domain/models/app/table/table-primary-key-validation.ts +49 -0
- package/src/domain/models/app/table/table-views-validation.ts +408 -0
- package/src/domain/models/app/table/unique-constraints.ts +79 -0
- package/src/domain/models/app/table/views/fields.ts +28 -0
- package/src/domain/models/app/table/views/filters.ts +162 -0
- package/src/domain/models/app/table/views/group-by.ts +32 -0
- package/src/domain/models/app/table/views/id.ts +50 -0
- package/src/domain/models/app/table/views/index.ts +177 -0
- package/src/domain/models/app/table/views/name.ts +32 -0
- package/src/domain/models/app/table/views/permissions.ts +98 -0
- package/src/domain/models/app/table/views/sorts.ts +31 -0
- package/src/domain/models/app/tables.ts +695 -0
- package/src/domain/models/app/theme/animations.ts +208 -0
- package/src/domain/models/app/theme/border-radius.ts +58 -0
- package/src/domain/models/app/theme/breakpoints.ts +62 -0
- package/src/domain/models/app/theme/colors.ts +110 -0
- package/src/domain/models/app/theme/fonts.ts +164 -0
- package/src/domain/models/app/theme/shadows.ts +61 -0
- package/src/domain/models/app/theme/spacing.ts +115 -0
- package/src/domain/models/app/theme.ts +66 -0
- package/src/domain/models/app/version.ts +87 -0
- package/src/domain/models/record-comment.ts +91 -0
- package/src/domain/utils/content-parsing.ts +49 -0
- package/src/domain/utils/format-detection.ts +69 -0
- package/src/domain/utils/index.ts +9 -0
- package/src/domain/utils/route-matcher.ts +184 -0
- package/src/domain/utils/translation-resolver.ts +170 -0
- package/src/index.ts +208 -0
- package/src/infrastructure/analytics/tracking-script.ts +48 -0
- package/src/infrastructure/auth/better-auth/auth.ts +216 -0
- package/src/infrastructure/auth/better-auth/email-handlers.ts +162 -0
- package/src/infrastructure/auth/better-auth/index.ts +16 -0
- package/src/infrastructure/auth/better-auth/layer.ts +97 -0
- package/src/infrastructure/auth/better-auth/plugins/admin.ts +56 -0
- package/src/infrastructure/auth/better-auth/plugins/magic-link.ts +31 -0
- package/src/infrastructure/auth/better-auth/plugins/two-factor.ts +19 -0
- package/src/infrastructure/auth/better-auth/schema.ts +152 -0
- package/src/infrastructure/auth/index.ts +27 -0
- package/src/infrastructure/css/cache/css-cache-service.ts +130 -0
- package/src/infrastructure/css/compiler.ts +210 -0
- package/src/infrastructure/css/css-compiler-live.ts +20 -0
- package/src/infrastructure/css/index.ts +25 -0
- package/src/infrastructure/css/styles/animation-styles-generator.ts +177 -0
- package/src/infrastructure/css/styles/click-animations.ts +147 -0
- package/src/infrastructure/css/styles/component-layer-generators.ts +147 -0
- package/src/infrastructure/css/theme/theme-generators.ts +130 -0
- package/src/infrastructure/css/theme/theme-layer-generators.ts +219 -0
- package/src/infrastructure/css/theme/theme-token-resolver.ts +76 -0
- package/src/infrastructure/database/activity-queries.ts +111 -0
- package/src/infrastructure/database/auth/auth-validation.ts +101 -0
- package/src/infrastructure/database/drizzle/db-bun.ts +17 -0
- package/src/infrastructure/database/drizzle/db.ts +17 -0
- package/src/infrastructure/database/drizzle/index.ts +16 -0
- package/src/infrastructure/database/drizzle/layer.ts +34 -0
- package/src/infrastructure/database/drizzle/migrate.ts +77 -0
- package/src/infrastructure/database/drizzle/schema/activity-log.ts +111 -0
- package/src/infrastructure/database/drizzle/schema/analytics-page-views.ts +116 -0
- package/src/infrastructure/database/drizzle/schema/migration-audit.ts +68 -0
- package/src/infrastructure/database/drizzle/schema/record-comments.ts +79 -0
- package/src/infrastructure/database/drizzle/schema.ts +12 -0
- package/src/infrastructure/database/field-utils.ts +87 -0
- package/src/infrastructure/database/filter-operators.ts +136 -0
- package/src/infrastructure/database/formula/formula-trigger-generators.ts +114 -0
- package/src/infrastructure/database/formula/formula-utils.ts +440 -0
- package/src/infrastructure/database/generators/index-generators.ts +152 -0
- package/src/infrastructure/database/generators/trigger-generators.ts +154 -0
- package/src/infrastructure/database/index.ts +35 -0
- package/src/infrastructure/database/lookup/lookup-expression-generators.ts +356 -0
- package/src/infrastructure/database/lookup/lookup-expressions.ts +116 -0
- package/src/infrastructure/database/lookup/lookup-view-generators.ts +403 -0
- package/src/infrastructure/database/lookup/lookup-view-helpers.ts +65 -0
- package/src/infrastructure/database/lookup/lookup-view-triggers.ts +121 -0
- package/src/infrastructure/database/migration-audit-trail.ts +375 -0
- package/src/infrastructure/database/repositories/activity-log-repository-live.ts +99 -0
- package/src/infrastructure/database/repositories/activity-repository-live.ts +21 -0
- package/src/infrastructure/database/repositories/analytics-repository-live.ts +316 -0
- package/src/infrastructure/database/repositories/auth-repository-live.ts +42 -0
- package/src/infrastructure/database/repositories/batch-repository-live.ts +29 -0
- package/src/infrastructure/database/repositories/comment-repository-live.ts +39 -0
- package/src/infrastructure/database/repositories/table-repository-live.ts +38 -0
- package/src/infrastructure/database/schema/schema-dependency-sorting.ts +142 -0
- package/src/infrastructure/database/schema/schema-initializer.ts +598 -0
- package/src/infrastructure/database/schema-migration/column-detection.ts +286 -0
- package/src/infrastructure/database/schema-migration/constants.ts +31 -0
- package/src/infrastructure/database/schema-migration/constraint-sync.ts +288 -0
- package/src/infrastructure/database/schema-migration/index-sync.ts +108 -0
- package/src/infrastructure/database/schema-migration/index.ts +66 -0
- package/src/infrastructure/database/schema-migration/migration-statements.ts +106 -0
- package/src/infrastructure/database/schema-migration/rename-detection.ts +87 -0
- package/src/infrastructure/database/schema-migration/table-operations.ts +65 -0
- package/src/infrastructure/database/schema-migration/type-utils.ts +98 -0
- package/src/infrastructure/database/schema-migration/types.ts +14 -0
- package/src/infrastructure/database/schema-migration-helpers.ts +53 -0
- package/src/infrastructure/database/session-context.ts +20 -0
- package/src/infrastructure/database/sql/sql-check-constraints.ts +252 -0
- package/src/infrastructure/database/sql/sql-column-generators.ts +174 -0
- package/src/infrastructure/database/sql/sql-execution.ts +245 -0
- package/src/infrastructure/database/sql/sql-field-predicates.ts +81 -0
- package/src/infrastructure/database/sql/sql-generators.ts +91 -0
- package/src/infrastructure/database/sql/sql-junction-tables.ts +79 -0
- package/src/infrastructure/database/sql/sql-key-constraints.ts +210 -0
- package/src/infrastructure/database/sql/sql-type-mappings.ts +106 -0
- package/src/infrastructure/database/sql/sql-utils.ts +53 -0
- package/src/infrastructure/database/table-live-layers.ts +30 -0
- package/src/infrastructure/database/table-operations/column-generators.ts +82 -0
- package/src/infrastructure/database/table-operations/create-table-sql.ts +81 -0
- package/src/infrastructure/database/table-operations/index.ts +55 -0
- package/src/infrastructure/database/table-operations/migration-utils.ts +157 -0
- package/src/infrastructure/database/table-operations/table-effects.ts +234 -0
- package/src/infrastructure/database/table-operations/table-features.ts +96 -0
- package/src/infrastructure/database/table-operations/type-compatibility.ts +58 -0
- package/src/infrastructure/database/table-operations.ts +47 -0
- package/src/infrastructure/database/table-queries/batch/batch-create.ts +80 -0
- package/src/infrastructure/database/table-queries/batch/batch-delete.ts +212 -0
- package/src/infrastructure/database/table-queries/batch/batch-helpers.ts +124 -0
- package/src/infrastructure/database/table-queries/batch/batch-restore.ts +161 -0
- package/src/infrastructure/database/table-queries/batch/batch-update.ts +146 -0
- package/src/infrastructure/database/table-queries/batch/batch-upsert.ts +357 -0
- package/src/infrastructure/database/table-queries/batch/batch.ts +14 -0
- package/src/infrastructure/database/table-queries/crud/crud-read.ts +351 -0
- package/src/infrastructure/database/table-queries/crud/crud-write.ts +399 -0
- package/src/infrastructure/database/table-queries/crud/crud.ts +16 -0
- package/src/infrastructure/database/table-queries/index.ts +11 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/authorship-helpers.ts +152 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/create-record-helpers.ts +90 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/delete-helpers.ts +163 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/record-fetch-helpers.ts +79 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/update-helpers.ts +74 -0
- package/src/infrastructure/database/table-queries/query-helpers/activity-log-helpers.ts +53 -0
- package/src/infrastructure/database/table-queries/query-helpers/activity-queries.ts +106 -0
- package/src/infrastructure/database/table-queries/query-helpers/aggregation-helpers.ts +314 -0
- package/src/infrastructure/database/table-queries/query-helpers/comment-queries.ts +414 -0
- package/src/infrastructure/database/table-queries/query-helpers/record-validation-queries.ts +126 -0
- package/src/infrastructure/database/table-queries/query-helpers/trash-helpers.ts +58 -0
- package/src/infrastructure/database/table-queries/shared/error-handling.ts +47 -0
- package/src/infrastructure/database/table-queries/shared/typed-execute.ts +27 -0
- package/src/infrastructure/database/table-queries/shared/user-join-helpers.ts +38 -0
- package/src/infrastructure/database/table-queries/shared/validation.ts +39 -0
- package/src/infrastructure/database/views/view-generators.ts +258 -0
- package/src/infrastructure/devtools/devtools-layer.ts +43 -0
- package/src/infrastructure/devtools/index.ts +8 -0
- package/src/infrastructure/email/email-config.ts +103 -0
- package/src/infrastructure/email/email-service.ts +152 -0
- package/src/infrastructure/email/index.ts +107 -0
- package/src/infrastructure/email/nodemailer.ts +125 -0
- package/src/infrastructure/email/templates.ts +244 -0
- package/src/infrastructure/errors/auth-config-required-error.ts +21 -0
- package/src/infrastructure/errors/auth-error.ts +16 -0
- package/src/infrastructure/errors/css-compilation-error.ts +14 -0
- package/src/infrastructure/errors/index.ts +26 -0
- package/src/infrastructure/errors/schema-initialization-error.ts +19 -0
- package/src/infrastructure/errors/server-creation-error.ts +14 -0
- package/src/infrastructure/filesystem/copy-directory.ts +136 -0
- package/src/infrastructure/layers/app-layer.ts +61 -0
- package/src/infrastructure/layers/page-renderer-layer.ts +41 -0
- package/src/infrastructure/logging/index.ts +8 -0
- package/src/infrastructure/logging/logger.ts +204 -0
- package/src/infrastructure/schema/file-loader.ts +53 -0
- package/src/infrastructure/schema/index.ts +15 -0
- package/src/infrastructure/schema/remote-loader.ts +48 -0
- package/src/infrastructure/server/index.ts +26 -0
- package/src/infrastructure/server/language-detection.ts +87 -0
- package/src/infrastructure/server/lifecycle.ts +67 -0
- package/src/infrastructure/server/route-setup/api-routes.ts +310 -0
- package/src/infrastructure/server/route-setup/auth-route-utils.ts +399 -0
- package/src/infrastructure/server/route-setup/auth-routes.ts +245 -0
- package/src/infrastructure/server/route-setup/openapi-routes.ts +45 -0
- package/src/infrastructure/server/route-setup/openapi-schema.ts +120 -0
- package/src/infrastructure/server/route-setup/page-routes.ts +219 -0
- package/src/infrastructure/server/route-setup/static-assets.ts +191 -0
- package/src/infrastructure/server/server-factory-live.ts +45 -0
- package/src/infrastructure/server/server.ts +275 -0
- package/src/infrastructure/server/ssg-adapter.ts +196 -0
- package/src/infrastructure/server/static-site-generator-live.ts +20 -0
- package/src/infrastructure/utils/accept-language-parser.ts +106 -0
- package/src/infrastructure/utils/glob-matcher.ts +50 -0
- package/src/presentation/api/client.ts +114 -0
- package/src/presentation/api/middleware/auth.ts +233 -0
- package/src/presentation/api/middleware/table.ts +155 -0
- package/src/presentation/api/middleware/validation.ts +88 -0
- package/src/presentation/api/routes/activity/get-activity-by-id-handler.ts +77 -0
- package/src/presentation/api/routes/activity/index.ts +28 -0
- package/src/presentation/api/routes/activity.ts +339 -0
- package/src/presentation/api/routes/analytics.ts +328 -0
- package/src/presentation/api/routes/auth.ts +169 -0
- package/src/presentation/api/routes/index.ts +11 -0
- package/src/presentation/api/routes/tables/activity-handlers.ts +57 -0
- package/src/presentation/api/routes/tables/batch-permission-helpers.ts +163 -0
- package/src/presentation/api/routes/tables/batch-routes.ts +355 -0
- package/src/presentation/api/routes/tables/comment-handlers.ts +377 -0
- package/src/presentation/api/routes/tables/create-record-helpers.ts +179 -0
- package/src/presentation/api/routes/tables/effect-runner.ts +58 -0
- package/src/presentation/api/routes/tables/error-handlers.ts +53 -0
- package/src/presentation/api/routes/tables/field-permission-validation.ts +167 -0
- package/src/presentation/api/routes/tables/filter-parser.ts +75 -0
- package/src/presentation/api/routes/tables/formula-parser.ts +118 -0
- package/src/presentation/api/routes/tables/index.ts +113 -0
- package/src/presentation/api/routes/tables/list-records-filter.ts +54 -0
- package/src/presentation/api/routes/tables/param-parsers.ts +59 -0
- package/src/presentation/api/routes/tables/record-handlers.ts +484 -0
- package/src/presentation/api/routes/tables/record-routes.ts +53 -0
- package/src/presentation/api/routes/tables/record-update-handler.ts +200 -0
- package/src/presentation/api/routes/tables/sort-validation.ts +85 -0
- package/src/presentation/api/routes/tables/table-routes.ts +76 -0
- package/src/presentation/api/routes/tables/timezone-validation.ts +41 -0
- package/src/presentation/api/routes/tables/upsert-helpers.ts +471 -0
- package/src/presentation/api/routes/tables/utils.ts +159 -0
- package/src/presentation/api/routes/tables/view-routes.ts +51 -0
- package/src/presentation/api/routes/tables.ts +9 -0
- package/src/presentation/api/utils/context-helpers.ts +43 -0
- package/src/presentation/api/utils/error-sanitizer.ts +235 -0
- package/src/presentation/api/utils/field-permission-validator.ts +53 -0
- package/src/presentation/api/utils/filter-field-validator.ts +90 -0
- package/src/presentation/api/utils/index.ts +13 -0
- package/src/presentation/api/utils/run-effect.ts +94 -0
- package/src/presentation/api/utils/validate-request.ts +89 -0
- package/src/presentation/api/validation/index.ts +29 -0
- package/src/presentation/api/validation/rules/field-rules.ts +158 -0
- package/src/presentation/api/validation/rules/record-rules.ts +73 -0
- package/src/presentation/cli/index.ts +19 -0
- package/src/presentation/cli/schema-loader.ts +172 -0
- package/src/presentation/hooks/use-breakpoint.ts +155 -0
- package/src/presentation/rendering/render-error-pages.tsx +60 -0
- package/src/presentation/rendering/render-homepage.tsx +137 -0
- package/src/presentation/scripts/script-renderers.ts +112 -0
- package/src/presentation/styling/animation-composer.ts +117 -0
- package/src/presentation/styling/index.ts +13 -0
- package/src/presentation/styling/parse-style.ts +243 -0
- package/src/presentation/styling/style-utils.ts +50 -0
- package/src/presentation/styling/theme-colors.ts +53 -0
- package/src/presentation/translations/component-utils.ts +54 -0
- package/src/presentation/translations/index.ts +16 -0
- package/src/presentation/translations/translation-resolver.ts +22 -0
- package/src/presentation/ui/languages/language-switcher.tsx +119 -0
- package/src/presentation/ui/metadata/analytics-builders.tsx +174 -0
- package/src/presentation/ui/metadata/analytics-head.tsx +39 -0
- package/src/presentation/ui/metadata/custom-elements-builders.tsx +157 -0
- package/src/presentation/ui/metadata/extract-component-meta.ts +108 -0
- package/src/presentation/ui/metadata/head-elements.tsx +164 -0
- package/src/presentation/ui/metadata/index.tsx +35 -0
- package/src/presentation/ui/metadata/meta-utils.tsx +42 -0
- package/src/presentation/ui/metadata/open-graph-meta.tsx +57 -0
- package/src/presentation/ui/metadata/structured-data-from-component.tsx +134 -0
- package/src/presentation/ui/metadata/structured-data.tsx +88 -0
- package/src/presentation/ui/metadata/twitter-card-meta.tsx +80 -0
- package/src/presentation/ui/pages/DefaultHomePage.tsx +43 -0
- package/src/presentation/ui/pages/DefaultPageConfigs.ts +220 -0
- package/src/presentation/ui/pages/DynamicPage.tsx +307 -0
- package/src/presentation/ui/pages/ErrorPage.tsx +25 -0
- package/src/presentation/ui/pages/NotFoundPage.tsx +25 -0
- package/src/presentation/ui/pages/PageBodyScripts.tsx +242 -0
- package/src/presentation/ui/pages/PageBodyStyles.ts +52 -0
- package/src/presentation/ui/pages/PageHead.tsx +380 -0
- package/src/presentation/ui/pages/PageLangResolver.ts +58 -0
- package/src/presentation/ui/pages/PageMain.tsx +58 -0
- package/src/presentation/ui/pages/PageMetadata.ts +168 -0
- package/src/presentation/ui/pages/PageMetadataI18n.ts +169 -0
- package/src/presentation/ui/pages/PageScripts.ts +78 -0
- package/src/presentation/ui/pages/SectionRenderer.tsx +67 -0
- package/src/presentation/ui/pages/SectionSpacing.tsx +131 -0
- package/src/presentation/ui/sections/component-renderer.tsx +426 -0
- package/src/presentation/ui/sections/component-renderer.types.ts +33 -0
- package/src/presentation/ui/sections/components/component-reference-handler.tsx +74 -0
- package/src/presentation/ui/sections/components/component-resolution.ts +65 -0
- package/src/presentation/ui/sections/components/index.ts +9 -0
- package/src/presentation/ui/sections/hero.tsx +394 -0
- package/src/presentation/ui/sections/props/component-builder.ts +183 -0
- package/src/presentation/ui/sections/props/element-props.ts +179 -0
- package/src/presentation/ui/sections/props/index.ts +9 -0
- package/src/presentation/ui/sections/props/prop-conversion.ts +171 -0
- package/src/presentation/ui/sections/props/props-builder-config.ts +42 -0
- package/src/presentation/ui/sections/props/props-builder.ts +296 -0
- package/src/presentation/ui/sections/renderers/element-renderers/html-element-renderer.tsx +124 -0
- package/src/presentation/ui/sections/renderers/element-renderers/index.ts +59 -0
- package/src/presentation/ui/sections/renderers/element-renderers/interactive-renderers.tsx +231 -0
- package/src/presentation/ui/sections/renderers/element-renderers/media-renderers.tsx +102 -0
- package/src/presentation/ui/sections/renderers/element-renderers/text-content-renderers.tsx +42 -0
- package/src/presentation/ui/sections/renderers/element-renderers.ts +53 -0
- package/src/presentation/ui/sections/renderers/html-element-helpers.ts +100 -0
- package/src/presentation/ui/sections/renderers/specialized-renderers.tsx +212 -0
- package/src/presentation/ui/sections/rendering/component-dispatch-config.ts +31 -0
- package/src/presentation/ui/sections/rendering/component-registry/index.ts +39 -0
- package/src/presentation/ui/sections/rendering/component-registry/interactive-components.ts +54 -0
- package/src/presentation/ui/sections/rendering/component-registry/media-components.ts +36 -0
- package/src/presentation/ui/sections/rendering/component-registry/special-components.tsx +153 -0
- package/src/presentation/ui/sections/rendering/component-registry/structural-components.ts +215 -0
- package/src/presentation/ui/sections/rendering/component-registry/text-components.ts +57 -0
- package/src/presentation/ui/sections/rendering/component-registry-helpers.tsx +29 -0
- package/src/presentation/ui/sections/rendering/component-registry.tsx +21 -0
- package/src/presentation/ui/sections/rendering/component-type-dispatcher.tsx +33 -0
- package/src/presentation/ui/sections/rendering/index.ts +9 -0
- package/src/presentation/ui/sections/responsive/responsive-children-builder.tsx +96 -0
- package/src/presentation/ui/sections/responsive/responsive-content-builder.tsx +95 -0
- package/src/presentation/ui/sections/responsive/responsive-props-merger.ts +195 -0
- package/src/presentation/ui/sections/responsive/responsive-resolver.ts +213 -0
- package/src/presentation/ui/sections/styling/animation-composer-wrapper.ts +65 -0
- package/src/presentation/ui/sections/styling/class-builders.ts +45 -0
- package/src/presentation/ui/sections/styling/color-resolver.ts +43 -0
- package/src/presentation/ui/sections/styling/hover-interaction-handler.ts +107 -0
- package/src/presentation/ui/sections/styling/index.ts +9 -0
- package/src/presentation/ui/sections/styling/interaction-props-builder.ts +55 -0
- package/src/presentation/ui/sections/styling/shadow-resolver.ts +83 -0
- package/src/presentation/ui/sections/styling/spacing-resolver.ts +104 -0
- package/src/presentation/ui/sections/styling/style-processor.ts +170 -0
- package/src/presentation/ui/sections/styling/theme-tokens.ts +184 -0
- package/src/presentation/ui/sections/translations/i18n-content-resolver.ts +198 -0
- package/src/presentation/ui/sections/translations/index.ts +9 -0
- package/src/presentation/ui/sections/translations/translation-handler.ts +143 -0
- package/src/presentation/ui/sections/translations/variable-substitution.ts +225 -0
- package/src/presentation/ui/sections/utils/time-parser.ts +82 -0
- package/src/presentation/utils/link-attributes.ts +50 -0
- package/src/presentation/utils/string-utils.ts +58 -0
- package/src/presentation/utils/styles.ts +50 -0
- package/tsconfig.json +46 -0
|
@@ -0,0 +1,114 @@
|
|
|
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 { hc } from 'hono/client'
|
|
9
|
+
import type { ApiType } from '@/infrastructure/server/route-setup/api-routes'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hono RPC Client Factory
|
|
13
|
+
*
|
|
14
|
+
* Creates a fully typed API client for making requests to the Sovrium API.
|
|
15
|
+
* The client provides:
|
|
16
|
+
* - Full TypeScript autocomplete for all endpoints
|
|
17
|
+
* - Type-safe request parameters and response data
|
|
18
|
+
* - Automatic URL construction
|
|
19
|
+
* - Native fetch-based implementation
|
|
20
|
+
*
|
|
21
|
+
* **Benefits of Hono RPC**:
|
|
22
|
+
* - Zero code generation - types extracted directly from server routes
|
|
23
|
+
* - No OpenAPI client generation step required
|
|
24
|
+
* - Real-time type safety - changes to server routes immediately reflected
|
|
25
|
+
* - Minimal bundle size - uses native fetch
|
|
26
|
+
*
|
|
27
|
+
* @param baseUrl - Base URL of the API server (e.g., 'http://localhost:3000')
|
|
28
|
+
* @returns Typed RPC client for making API requests
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* Basic usage:
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { createApiClient } from '@/presentation/api/client'
|
|
34
|
+
*
|
|
35
|
+
* const client = createApiClient('http://localhost:3000')
|
|
36
|
+
*
|
|
37
|
+
* // GET /api/health - fully typed!
|
|
38
|
+
* const res = await client.api.health.$get()
|
|
39
|
+
* const data = await res.json()
|
|
40
|
+
* // data is typed as HealthResponse { status: 'ok', timestamp: string, app: { name: string } }
|
|
41
|
+
* console.log(data.status) // 'ok'
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* With error handling:
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const client = createApiClient('http://localhost:3000')
|
|
48
|
+
*
|
|
49
|
+
* try {
|
|
50
|
+
* const res = await client.api.health.$get()
|
|
51
|
+
*
|
|
52
|
+
* if (!res.ok) {
|
|
53
|
+
* throw new Error(`HTTP ${res.status}: ${res.statusText}`)
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* const data = await res.json()
|
|
57
|
+
* console.log('Health check:', data)
|
|
58
|
+
* } catch (error) {
|
|
59
|
+
* console.error('Health check failed:', error)
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* Future usage with path parameters (when tables API is implemented):
|
|
65
|
+
* ```typescript
|
|
66
|
+
* // GET /api/tables/:id
|
|
67
|
+
* const res = await client.api.tables[':id'].$get({
|
|
68
|
+
* param: { id: '123' }
|
|
69
|
+
* })
|
|
70
|
+
* const table = await res.json()
|
|
71
|
+
*
|
|
72
|
+
* // POST /api/tables/:id/records
|
|
73
|
+
* const res = await client.api.tables[':id'].records.$post({
|
|
74
|
+
* param: { id: '123' },
|
|
75
|
+
* json: { name: 'John', email: 'john@example.com' }
|
|
76
|
+
* })
|
|
77
|
+
* const record = await res.json()
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* Using in React components with TanStack Query:
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { useQuery } from '@tanstack/react-query'
|
|
84
|
+
* import { createApiClient } from '@/presentation/api/client'
|
|
85
|
+
*
|
|
86
|
+
* const client = createApiClient('http://localhost:3000')
|
|
87
|
+
*
|
|
88
|
+
* function HealthStatus() {
|
|
89
|
+
* const { data, isLoading, error } = useQuery({
|
|
90
|
+
* queryKey: ['health'],
|
|
91
|
+
* queryFn: async () => {
|
|
92
|
+
* const res = await client.api.health.$get()
|
|
93
|
+
* if (!res.ok) throw new Error('Health check failed')
|
|
94
|
+
* return res.json()
|
|
95
|
+
* }
|
|
96
|
+
* })
|
|
97
|
+
*
|
|
98
|
+
* if (isLoading) return <div>Loading...</div>
|
|
99
|
+
* if (error) return <div>Error: {error.message}</div>
|
|
100
|
+
*
|
|
101
|
+
* return <div>Status: {data?.status}</div>
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export const createApiClient = (baseUrl: string) => {
|
|
106
|
+
return hc<ApiType>(baseUrl)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Re-export ApiType for convenience
|
|
111
|
+
*
|
|
112
|
+
* Allows consumers to import both the client and type from the same file.
|
|
113
|
+
*/
|
|
114
|
+
export type { ApiType }
|
|
@@ -0,0 +1,233 @@
|
|
|
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 { logError, logWarning } from '@/infrastructure/logging/logger'
|
|
9
|
+
import type { Session } from '@/application/ports/models/user-session'
|
|
10
|
+
import type { Context, Next } from 'hono'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal Better Auth instance interface
|
|
14
|
+
*
|
|
15
|
+
* Typed to the subset of the Better Auth API actually used by the middleware.
|
|
16
|
+
* Better Auth's full type varies by configuration (plugins, providers)
|
|
17
|
+
* and uses optional fields (ipAddress?) that conflict with our Session type
|
|
18
|
+
* (ipAddress: string | null), so getSession returns Promise<unknown>.
|
|
19
|
+
* The middleware handles extraction with runtime checks and explicit casts.
|
|
20
|
+
*/
|
|
21
|
+
interface BetterAuthLike {
|
|
22
|
+
readonly api: {
|
|
23
|
+
readonly getSession: (options: { readonly headers: Headers }) => Promise<unknown>
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Hono context with session attached
|
|
29
|
+
*
|
|
30
|
+
* Used by auth middleware to store extracted session for route handlers
|
|
31
|
+
*/
|
|
32
|
+
export type ContextWithSession = Context & {
|
|
33
|
+
readonly var: {
|
|
34
|
+
readonly session?: Session
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract client IP address from request headers
|
|
40
|
+
*
|
|
41
|
+
* Checks common proxy headers in priority order:
|
|
42
|
+
* 1. X-Forwarded-For (most common)
|
|
43
|
+
* 2. X-Real-IP (nginx)
|
|
44
|
+
* 3. CF-Connecting-IP (Cloudflare)
|
|
45
|
+
* 4. Falls back to direct connection IP
|
|
46
|
+
*/
|
|
47
|
+
function getClientIP(c: Context): string | undefined {
|
|
48
|
+
const xForwardedFor = c.req.header('x-forwarded-for')
|
|
49
|
+
if (xForwardedFor) {
|
|
50
|
+
// X-Forwarded-For can contain multiple IPs, take the first one (client)
|
|
51
|
+
return xForwardedFor.split(',')[0]?.trim()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const xRealIp = c.req.header('x-real-ip')
|
|
55
|
+
if (xRealIp) {
|
|
56
|
+
return xRealIp
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cfConnectingIp = c.req.header('cf-connecting-ip')
|
|
60
|
+
if (cfConnectingIp) {
|
|
61
|
+
return cfConnectingIp
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fallback to undefined if no IP headers found
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate session binding to original IP and User-Agent
|
|
70
|
+
*
|
|
71
|
+
* When strict mode is enabled, sessions are bound to the IP address and
|
|
72
|
+
* User-Agent that created them. This prevents session hijacking attacks.
|
|
73
|
+
*
|
|
74
|
+
* @param session - Session object from database
|
|
75
|
+
* @param currentIP - Current request IP address
|
|
76
|
+
* @param currentUserAgent - Current request User-Agent
|
|
77
|
+
* @returns true if session is valid, false if binding validation fails
|
|
78
|
+
*/
|
|
79
|
+
function validateSessionBinding(
|
|
80
|
+
session: Session,
|
|
81
|
+
currentIP: string | undefined,
|
|
82
|
+
currentUserAgent: string | undefined
|
|
83
|
+
): boolean {
|
|
84
|
+
// For now, allow sessions without binding metadata (backward compatibility)
|
|
85
|
+
// This will be tightened when strict mode is explicitly enabled
|
|
86
|
+
if (!session.ipAddress && !session.userAgent) {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If session has IP binding, validate it matches
|
|
91
|
+
if (session.ipAddress && currentIP && session.ipAddress !== currentIP) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If session has User-Agent binding, validate it matches
|
|
96
|
+
if (session.userAgent && currentUserAgent && session.userAgent !== currentUserAgent) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Process session result and attach to context if valid
|
|
105
|
+
*
|
|
106
|
+
* Validates session binding and logs security warnings for failed validations.
|
|
107
|
+
*/
|
|
108
|
+
function processSessionResult(
|
|
109
|
+
c: Context,
|
|
110
|
+
sessionResult: { readonly session?: Session } | null
|
|
111
|
+
): void {
|
|
112
|
+
if (!sessionResult?.session) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const currentIP = getClientIP(c)
|
|
117
|
+
const currentUserAgent = c.req.header('user-agent')
|
|
118
|
+
|
|
119
|
+
if (validateSessionBinding(sessionResult.session as Session, currentIP, currentUserAgent)) {
|
|
120
|
+
c.set('session', sessionResult.session as Session)
|
|
121
|
+
} else {
|
|
122
|
+
// Session binding validation failed - log for security monitoring
|
|
123
|
+
logWarning(
|
|
124
|
+
`[AUTH] Session binding validation failed: ${JSON.stringify({
|
|
125
|
+
sessionId: sessionResult.session.id,
|
|
126
|
+
expectedIP: sessionResult.session.ipAddress,
|
|
127
|
+
currentIP,
|
|
128
|
+
expectedUserAgent: sessionResult.session.userAgent,
|
|
129
|
+
currentUserAgent,
|
|
130
|
+
})}`
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Auth middleware for Hono routes
|
|
137
|
+
*
|
|
138
|
+
* Extracts Better Auth session from request and attaches to context.
|
|
139
|
+
* Routes can access session via `c.var.session`.
|
|
140
|
+
*
|
|
141
|
+
* **Session Extraction Strategy**:
|
|
142
|
+
* 1. Check for Authorization header (Bearer token)
|
|
143
|
+
* 2. Query Better Auth session table to validate token
|
|
144
|
+
* 3. Validate session binding (IP/User-Agent) if strict mode enabled
|
|
145
|
+
* 4. Attach session to context if valid
|
|
146
|
+
* 5. Continue to route handler (session may be undefined for public routes)
|
|
147
|
+
*
|
|
148
|
+
* **Session Binding (Strict Mode)**:
|
|
149
|
+
* When enabled, sessions are bound to the IP address and User-Agent that
|
|
150
|
+
* created them. Requests from different IP/User-Agent will be rejected.
|
|
151
|
+
*
|
|
152
|
+
* **Usage**:
|
|
153
|
+
* ```typescript
|
|
154
|
+
* app.use('/api/tables/*', authMiddleware(auth))
|
|
155
|
+
* app.get('/api/tables/:id', async (c) => {
|
|
156
|
+
* const session = c.var.session
|
|
157
|
+
* if (!session) return c.json({ error: 'Unauthorized' }, 401)
|
|
158
|
+
* // Use session for database queries
|
|
159
|
+
* })
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* @param auth - Better Auth instance with api.getSession method
|
|
163
|
+
* @returns Hono middleware function
|
|
164
|
+
*/
|
|
165
|
+
export function authMiddleware(auth: BetterAuthLike) {
|
|
166
|
+
return async (c: Context, next: Next) => {
|
|
167
|
+
try {
|
|
168
|
+
const authHeader = c.req.header('authorization')
|
|
169
|
+
|
|
170
|
+
if (authHeader?.toLowerCase().startsWith('bearer ')) {
|
|
171
|
+
const apiKey = authHeader.slice(7)
|
|
172
|
+
const result = (await auth.api.getSession({
|
|
173
|
+
headers: new Headers({ authorization: apiKey }),
|
|
174
|
+
})) as { readonly session?: Session } | null
|
|
175
|
+
processSessionResult(c, result)
|
|
176
|
+
} else {
|
|
177
|
+
const result = (await auth.api.getSession({
|
|
178
|
+
headers: c.req.raw.headers,
|
|
179
|
+
})) as { readonly session?: Session } | null
|
|
180
|
+
processSessionResult(c, result)
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logError('[AUTH] Session extraction failed', error)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// eslint-disable-next-line functional/no-expression-statements -- Required for middleware to continue to next handler
|
|
187
|
+
await next()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Middleware handler for requiring authentication
|
|
193
|
+
*/
|
|
194
|
+
async function requireAuthHandler(c: ContextWithSession, next: Next) {
|
|
195
|
+
const { session } = c.var
|
|
196
|
+
|
|
197
|
+
if (!session) {
|
|
198
|
+
return c.json(
|
|
199
|
+
{
|
|
200
|
+
success: false,
|
|
201
|
+
error: 'Unauthorized',
|
|
202
|
+
message: 'Authentication required',
|
|
203
|
+
code: 'UNAUTHORIZED',
|
|
204
|
+
},
|
|
205
|
+
401
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// eslint-disable-next-line functional/no-expression-statements -- Required for middleware to continue to next handler
|
|
210
|
+
await next()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Require authentication middleware
|
|
215
|
+
*
|
|
216
|
+
* Returns 401 if session is not present.
|
|
217
|
+
* Use this for protected routes that require authentication.
|
|
218
|
+
*
|
|
219
|
+
* **Usage**:
|
|
220
|
+
* ```typescript
|
|
221
|
+
* app.use('/api/tables/*', authMiddleware(auth))
|
|
222
|
+
* app.use('/api/tables/*', requireAuth())
|
|
223
|
+
* app.get('/api/tables/:id', async (c) => {
|
|
224
|
+
* const session = c.var.session! // Safe to use non-null assertion
|
|
225
|
+
* // Session is guaranteed to exist
|
|
226
|
+
* })
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @returns Hono middleware function
|
|
230
|
+
*/
|
|
231
|
+
export function requireAuth() {
|
|
232
|
+
return requireAuthHandler
|
|
233
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
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 { getUserRole } from '@/application/use-cases/tables/user-role'
|
|
9
|
+
import type { ContextWithSession } from './auth'
|
|
10
|
+
import type { Session } from '@/application/ports/models/user-session'
|
|
11
|
+
import type { App } from '@/domain/models/app'
|
|
12
|
+
import type { Context, Next } from 'hono'
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Extended Context Types
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hono context with validated table information attached
|
|
20
|
+
*
|
|
21
|
+
* Available after `validateTable()` middleware runs.
|
|
22
|
+
*/
|
|
23
|
+
export type ContextWithValidatedTable = ContextWithSession & {
|
|
24
|
+
readonly var: {
|
|
25
|
+
readonly tableName: string
|
|
26
|
+
readonly tableId: string
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hono context with user role attached
|
|
32
|
+
*
|
|
33
|
+
* Available after `enrichUserRole()` middleware runs.
|
|
34
|
+
* Requires session to exist (use after `requireAuth()`).
|
|
35
|
+
*/
|
|
36
|
+
export type ContextWithUserRole = ContextWithSession & {
|
|
37
|
+
readonly var: {
|
|
38
|
+
readonly userRole: string
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Combined context with session, validated table, and user role
|
|
44
|
+
*
|
|
45
|
+
* Available after full middleware chain:
|
|
46
|
+
* `authMiddleware` → `requireAuth` → `validateTable` → `enrichUserRole`
|
|
47
|
+
*
|
|
48
|
+
* **Session is guaranteed to exist** because `requireAuth()` rejects
|
|
49
|
+
* requests without a valid session.
|
|
50
|
+
*/
|
|
51
|
+
export type ContextWithTableAndRole = Context & {
|
|
52
|
+
readonly var: {
|
|
53
|
+
readonly session: Session // Non-optional - guaranteed by requireAuth()
|
|
54
|
+
readonly tableName: string
|
|
55
|
+
readonly tableId: string
|
|
56
|
+
readonly userRole: string
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Middleware Functions
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Middleware to validate table exists and resolve table name
|
|
66
|
+
*
|
|
67
|
+
* Extracts :tableId param, validates table exists in app schema,
|
|
68
|
+
* and attaches resolved table name to context.
|
|
69
|
+
*
|
|
70
|
+
* **Context Variables Set**:
|
|
71
|
+
* - `tableName`: The resolved table name
|
|
72
|
+
* - `tableId`: The original tableId parameter
|
|
73
|
+
*
|
|
74
|
+
* **Usage**:
|
|
75
|
+
* ```typescript
|
|
76
|
+
* app.use('/api/tables/:tableId/*', validateTable(app))
|
|
77
|
+
* app.get('/api/tables/:tableId/records', (c: ContextWithValidatedTable) => {
|
|
78
|
+
* const { tableName } = c.var // Already validated!
|
|
79
|
+
* })
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @param app - Application configuration containing table definitions
|
|
83
|
+
* @returns Hono middleware function
|
|
84
|
+
*/
|
|
85
|
+
export function validateTable(app: App) {
|
|
86
|
+
return async (c: Context, next: Next) => {
|
|
87
|
+
const tableId = c.req.param('tableId')
|
|
88
|
+
|
|
89
|
+
if (!tableId) {
|
|
90
|
+
return c.json(
|
|
91
|
+
{ success: false, message: 'Table ID parameter required', code: 'VALIDATION_ERROR' },
|
|
92
|
+
400
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const table = app.tables?.find((t) => String(t.id) === tableId || t.name === tableId)
|
|
97
|
+
|
|
98
|
+
if (!table) {
|
|
99
|
+
return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Attach to context for downstream handlers
|
|
103
|
+
c.set('tableName', table.name)
|
|
104
|
+
c.set('tableId', tableId)
|
|
105
|
+
|
|
106
|
+
// eslint-disable-next-line functional/no-expression-statements -- Required for middleware to continue
|
|
107
|
+
await next()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Middleware to enrich context with user role
|
|
113
|
+
*
|
|
114
|
+
* Fetches user role from database and attaches to context.
|
|
115
|
+
* Requires session to exist (use after `requireAuth()` middleware).
|
|
116
|
+
*
|
|
117
|
+
* **Context Variables Set**:
|
|
118
|
+
* - `userRole`: The user's role in the organization
|
|
119
|
+
*
|
|
120
|
+
* **Usage**:
|
|
121
|
+
* ```typescript
|
|
122
|
+
* app.use('/api/tables/*', authMiddleware(auth))
|
|
123
|
+
* app.use('/api/tables/*', requireAuth())
|
|
124
|
+
* app.use('/api/tables/:tableId/*', enrichUserRole())
|
|
125
|
+
* app.get('/api/tables/:tableId/records', (c: ContextWithUserRole) => {
|
|
126
|
+
* const { userRole } = c.var // Already fetched!
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @param getUserRoleFn - Optional function to resolve user role (for unit tests).
|
|
131
|
+
* Defaults to the real getUserRole from application layer.
|
|
132
|
+
* @returns Hono middleware function
|
|
133
|
+
*/
|
|
134
|
+
export function enrichUserRole(getUserRoleFn?: (userId: string) => Promise<string>) {
|
|
135
|
+
const resolveRole = getUserRoleFn ?? getUserRole
|
|
136
|
+
|
|
137
|
+
return async (c: Context, next: Next) => {
|
|
138
|
+
const { session } = (c as ContextWithSession).var
|
|
139
|
+
|
|
140
|
+
// Defensive check (should not happen if requireAuth() used before)
|
|
141
|
+
if (!session) {
|
|
142
|
+
return c.json(
|
|
143
|
+
{ success: false, message: 'Authentication required', code: 'UNAUTHORIZED' },
|
|
144
|
+
401
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const userRole = await resolveRole(session.userId)
|
|
149
|
+
|
|
150
|
+
c.set('userRole', userRole)
|
|
151
|
+
|
|
152
|
+
// eslint-disable-next-line functional/no-expression-statements -- Required for middleware to continue
|
|
153
|
+
await next()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { Context, Layer } from 'effect'
|
|
9
|
+
import type { App } from '@/domain/models/app'
|
|
10
|
+
import type { Effect } from 'effect'
|
|
11
|
+
import type { Context as HonoContext } from 'hono'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validation error types
|
|
15
|
+
*/
|
|
16
|
+
export class ValidationError {
|
|
17
|
+
readonly _tag = 'ValidationError'
|
|
18
|
+
constructor(
|
|
19
|
+
readonly message: string,
|
|
20
|
+
readonly field?: string
|
|
21
|
+
) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class PermissionError {
|
|
25
|
+
readonly _tag = 'PermissionError'
|
|
26
|
+
constructor(
|
|
27
|
+
readonly message: string,
|
|
28
|
+
readonly field?: string
|
|
29
|
+
) {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validation context - provides app configuration and request context
|
|
34
|
+
*/
|
|
35
|
+
export class ValidationContext extends Context.Tag('ValidationContext')<
|
|
36
|
+
ValidationContext,
|
|
37
|
+
{
|
|
38
|
+
readonly app: App
|
|
39
|
+
readonly tableName: string
|
|
40
|
+
readonly userRole: string
|
|
41
|
+
}
|
|
42
|
+
>() {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validation result type
|
|
46
|
+
*/
|
|
47
|
+
export type ValidationResult<T> = Effect.Effect<T, ValidationError | PermissionError, never>
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a validation layer from app, tableName, and userRole
|
|
51
|
+
*/
|
|
52
|
+
export function createValidationLayer(app: App, tableName: string, userRole: string) {
|
|
53
|
+
return Layer.succeed(ValidationContext, {
|
|
54
|
+
app,
|
|
55
|
+
tableName,
|
|
56
|
+
userRole,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validation error response helper
|
|
62
|
+
*/
|
|
63
|
+
export function formatValidationError(
|
|
64
|
+
error: ValidationError | PermissionError,
|
|
65
|
+
c: HonoContext
|
|
66
|
+
): Response {
|
|
67
|
+
if (error._tag === 'ValidationError') {
|
|
68
|
+
return c.json(
|
|
69
|
+
{
|
|
70
|
+
success: false,
|
|
71
|
+
message: error.message,
|
|
72
|
+
code: 'VALIDATION_ERROR',
|
|
73
|
+
...(error.field ? { field: error.field } : {}),
|
|
74
|
+
},
|
|
75
|
+
400
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return c.json(
|
|
80
|
+
{
|
|
81
|
+
success: false,
|
|
82
|
+
message: error.message,
|
|
83
|
+
code: 'FORBIDDEN',
|
|
84
|
+
...(error.field ? { field: error.field } : {}),
|
|
85
|
+
},
|
|
86
|
+
403
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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 { Effect } from 'effect'
|
|
9
|
+
import { GetActivityById } from '@/application/use-cases/activity/programs'
|
|
10
|
+
import { DatabaseLive } from '@/infrastructure/database'
|
|
11
|
+
import type { Context } from 'hono'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* GET /api/activity/:activityId handler
|
|
15
|
+
*
|
|
16
|
+
* Fetches activity log details by ID with user metadata.
|
|
17
|
+
*
|
|
18
|
+
* **Authentication**: Requires authenticated session
|
|
19
|
+
* **Authorization**: All authenticated users can view activity logs
|
|
20
|
+
*
|
|
21
|
+
* **Response Structure**:
|
|
22
|
+
* - 200: Activity details with user metadata
|
|
23
|
+
* - 400: Invalid activity ID format
|
|
24
|
+
* - 401: Unauthorized (no session)
|
|
25
|
+
* - 404: Activity not found
|
|
26
|
+
*
|
|
27
|
+
* @param c - Hono context
|
|
28
|
+
* @returns JSON response with activity details or error
|
|
29
|
+
*/
|
|
30
|
+
export async function getActivityByIdHandler(c: Context) {
|
|
31
|
+
const activityId = c.req.param('activityId')
|
|
32
|
+
|
|
33
|
+
const program = GetActivityById(activityId).pipe(Effect.provide(DatabaseLive))
|
|
34
|
+
|
|
35
|
+
const result = await Effect.runPromise(program.pipe(Effect.either))
|
|
36
|
+
|
|
37
|
+
if (result._tag === 'Left') {
|
|
38
|
+
const error = result.left
|
|
39
|
+
|
|
40
|
+
// Handle specific error types
|
|
41
|
+
if (error._tag === 'InvalidActivityIdError') {
|
|
42
|
+
return c.json(
|
|
43
|
+
{
|
|
44
|
+
success: false,
|
|
45
|
+
message: 'Invalid activity ID format',
|
|
46
|
+
code: 'INVALID_ACTIVITY_ID',
|
|
47
|
+
},
|
|
48
|
+
400
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (error._tag === 'ActivityNotFoundError') {
|
|
53
|
+
return c.json(
|
|
54
|
+
{
|
|
55
|
+
success: false,
|
|
56
|
+
message: 'Activity not found',
|
|
57
|
+
code: 'ACTIVITY_NOT_FOUND',
|
|
58
|
+
},
|
|
59
|
+
404
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Database error
|
|
64
|
+
return c.json(
|
|
65
|
+
{
|
|
66
|
+
success: false,
|
|
67
|
+
message: 'Failed to fetch activity',
|
|
68
|
+
code: 'DATABASE_ERROR',
|
|
69
|
+
},
|
|
70
|
+
500
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const activity = result.right
|
|
75
|
+
|
|
76
|
+
return c.json(activity, 200)
|
|
77
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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 { getActivityByIdHandler } from './get-activity-by-id-handler'
|
|
9
|
+
import type { Hono } from 'hono'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Chain activity routes onto a Hono app
|
|
13
|
+
*
|
|
14
|
+
* **Authentication Middleware** (applied in api-routes.ts):
|
|
15
|
+
* - All activity endpoints require authentication
|
|
16
|
+
* - Middleware chain: requireAuth() → Handler
|
|
17
|
+
* - Authentication is ALWAYS required, even when app.auth is not configured
|
|
18
|
+
*
|
|
19
|
+
* **Routes**:
|
|
20
|
+
* - GET /api/activity/:activityId - Get activity log details
|
|
21
|
+
*
|
|
22
|
+
* @param honoApp - Hono instance to chain routes onto
|
|
23
|
+
* @returns Hono app with activity routes chained
|
|
24
|
+
*/
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Hono type inference with middleware requires flexible typing
|
|
26
|
+
export function chainActivityRoutes<T extends Hono<any, any, any>>(honoApp: T) {
|
|
27
|
+
return honoApp.get('/api/activity/:activityId', getActivityByIdHandler)
|
|
28
|
+
}
|