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,316 @@
|
|
|
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 { and, between, count, countDistinct, eq, inArray, lt, or, sql } from 'drizzle-orm'
|
|
9
|
+
import { Effect, Layer } from 'effect'
|
|
10
|
+
import {
|
|
11
|
+
AnalyticsRepository,
|
|
12
|
+
AnalyticsDatabaseError,
|
|
13
|
+
} from '@/application/ports/repositories/analytics-repository'
|
|
14
|
+
import { db } from '@/infrastructure/database'
|
|
15
|
+
import { analyticsPageViews } from '@/infrastructure/database/drizzle/schema/analytics-page-views'
|
|
16
|
+
import type { AnalyticsQueryParams } from '@/application/ports/repositories/analytics-repository'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build the common WHERE clause for analytics queries
|
|
20
|
+
*
|
|
21
|
+
* Matches both the specified appName AND 'default' to support
|
|
22
|
+
* direct SQL inserts (which use the schema default 'default').
|
|
23
|
+
*/
|
|
24
|
+
const whereClause = (params: AnalyticsQueryParams) =>
|
|
25
|
+
and(
|
|
26
|
+
inArray(analyticsPageViews.appName, [params.appName, 'default']),
|
|
27
|
+
between(analyticsPageViews.timestamp, params.from, params.to)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Map granularity to PostgreSQL DATE_TRUNC interval
|
|
32
|
+
*/
|
|
33
|
+
const granularityToInterval = (granularity: string): string => {
|
|
34
|
+
const map: Record<string, string> = {
|
|
35
|
+
hour: 'hour',
|
|
36
|
+
day: 'day',
|
|
37
|
+
week: 'week',
|
|
38
|
+
month: 'month',
|
|
39
|
+
}
|
|
40
|
+
return map[granularity] ?? 'day'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compute percentage breakdown from count results
|
|
45
|
+
*/
|
|
46
|
+
const computePercentages = (
|
|
47
|
+
rows: readonly { readonly name: string | null; readonly count: number }[]
|
|
48
|
+
): readonly { readonly name: string; readonly count: number; readonly percentage: number }[] => {
|
|
49
|
+
const total = rows.reduce((sum, row) => sum + row.count, 0)
|
|
50
|
+
if (total === 0) return []
|
|
51
|
+
return rows.map((row) => ({
|
|
52
|
+
name: row.name ?? 'Unknown',
|
|
53
|
+
count: row.count,
|
|
54
|
+
percentage: Math.round((row.count / total) * 10_000) / 100,
|
|
55
|
+
}))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Analytics Repository Implementation
|
|
60
|
+
*
|
|
61
|
+
* Uses Drizzle ORM query builder with raw SQL for aggregations.
|
|
62
|
+
* All queries are parameterized (SQL injection safe).
|
|
63
|
+
*/
|
|
64
|
+
export const AnalyticsRepositoryLive = Layer.succeed(AnalyticsRepository, {
|
|
65
|
+
recordPageView: (input) =>
|
|
66
|
+
Effect.tryPromise({
|
|
67
|
+
try: async () => {
|
|
68
|
+
// eslint-disable-next-line functional/no-expression-statements
|
|
69
|
+
await db.insert(analyticsPageViews).values({
|
|
70
|
+
appName: input.appName,
|
|
71
|
+
pagePath: input.pagePath,
|
|
72
|
+
pageTitle: input.pageTitle,
|
|
73
|
+
visitorHash: input.visitorHash,
|
|
74
|
+
sessionHash: input.sessionHash,
|
|
75
|
+
isEntrance: input.isEntrance,
|
|
76
|
+
referrerUrl: input.referrerUrl,
|
|
77
|
+
referrerDomain: input.referrerDomain,
|
|
78
|
+
utmSource: input.utmSource,
|
|
79
|
+
utmMedium: input.utmMedium,
|
|
80
|
+
utmCampaign: input.utmCampaign,
|
|
81
|
+
utmContent: input.utmContent,
|
|
82
|
+
utmTerm: input.utmTerm,
|
|
83
|
+
deviceType: input.deviceType,
|
|
84
|
+
browserName: input.browserName,
|
|
85
|
+
osName: input.osName,
|
|
86
|
+
language: input.language,
|
|
87
|
+
screenWidth: input.screenWidth,
|
|
88
|
+
screenHeight: input.screenHeight,
|
|
89
|
+
})
|
|
90
|
+
},
|
|
91
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
92
|
+
}),
|
|
93
|
+
|
|
94
|
+
getSummary: (params) =>
|
|
95
|
+
Effect.tryPromise({
|
|
96
|
+
try: async () => {
|
|
97
|
+
const result = await db
|
|
98
|
+
.select({
|
|
99
|
+
pageViews: count(),
|
|
100
|
+
uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
|
|
101
|
+
sessions: countDistinct(analyticsPageViews.sessionHash),
|
|
102
|
+
})
|
|
103
|
+
.from(analyticsPageViews)
|
|
104
|
+
.where(whereClause(params))
|
|
105
|
+
|
|
106
|
+
const row = result[0]
|
|
107
|
+
return {
|
|
108
|
+
pageViews: row?.pageViews ?? 0,
|
|
109
|
+
uniqueVisitors: row?.uniqueVisitors ?? 0,
|
|
110
|
+
sessions: row?.sessions ?? 0,
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
getTimeSeries: (params) =>
|
|
117
|
+
Effect.tryPromise({
|
|
118
|
+
try: async () => {
|
|
119
|
+
const interval = granularityToInterval(params.granularity)
|
|
120
|
+
const rows = await db
|
|
121
|
+
.select({
|
|
122
|
+
period:
|
|
123
|
+
sql<string>`DATE_TRUNC(${sql.raw(`'${interval}'`)}, ${analyticsPageViews.timestamp})::text`.as(
|
|
124
|
+
'period'
|
|
125
|
+
),
|
|
126
|
+
pageViews: count(),
|
|
127
|
+
uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
|
|
128
|
+
sessions: countDistinct(analyticsPageViews.sessionHash),
|
|
129
|
+
})
|
|
130
|
+
.from(analyticsPageViews)
|
|
131
|
+
.where(whereClause(params))
|
|
132
|
+
.groupBy(sql`DATE_TRUNC(${sql.raw(`'${interval}'`)}, ${analyticsPageViews.timestamp})`)
|
|
133
|
+
.orderBy(sql`DATE_TRUNC(${sql.raw(`'${interval}'`)}, ${analyticsPageViews.timestamp})`)
|
|
134
|
+
|
|
135
|
+
return rows.map((row) => ({
|
|
136
|
+
period: row.period,
|
|
137
|
+
pageViews: row.pageViews,
|
|
138
|
+
uniqueVisitors: row.uniqueVisitors,
|
|
139
|
+
sessions: row.sessions,
|
|
140
|
+
}))
|
|
141
|
+
},
|
|
142
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
143
|
+
}),
|
|
144
|
+
|
|
145
|
+
getTopPages: (params) =>
|
|
146
|
+
Effect.tryPromise({
|
|
147
|
+
try: async () => {
|
|
148
|
+
const rows = await db
|
|
149
|
+
.select({
|
|
150
|
+
path: analyticsPageViews.pagePath,
|
|
151
|
+
pageViews: count(),
|
|
152
|
+
uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
|
|
153
|
+
})
|
|
154
|
+
.from(analyticsPageViews)
|
|
155
|
+
.where(whereClause(params))
|
|
156
|
+
.groupBy(analyticsPageViews.pagePath)
|
|
157
|
+
.orderBy(sql`count(*) DESC`)
|
|
158
|
+
|
|
159
|
+
return rows.map((row) => ({
|
|
160
|
+
path: row.path,
|
|
161
|
+
pageViews: row.pageViews,
|
|
162
|
+
uniqueVisitors: row.uniqueVisitors,
|
|
163
|
+
}))
|
|
164
|
+
},
|
|
165
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
getTopReferrers: (params) =>
|
|
169
|
+
Effect.tryPromise({
|
|
170
|
+
try: async () => {
|
|
171
|
+
const rows = await db
|
|
172
|
+
.select({
|
|
173
|
+
domain: analyticsPageViews.referrerDomain,
|
|
174
|
+
pageViews: count(),
|
|
175
|
+
uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
|
|
176
|
+
})
|
|
177
|
+
.from(analyticsPageViews)
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
whereClause(params),
|
|
181
|
+
// Exclude UTM campaign traffic (belongs in /api/analytics/campaigns)
|
|
182
|
+
or(
|
|
183
|
+
// Include rows with referrer_domain set
|
|
184
|
+
sql`${analyticsPageViews.referrerDomain} IS NOT NULL`,
|
|
185
|
+
// Include direct traffic (NULL referrer AND NULL campaign)
|
|
186
|
+
and(
|
|
187
|
+
sql`${analyticsPageViews.referrerDomain} IS NULL`,
|
|
188
|
+
sql`${analyticsPageViews.utmCampaign} IS NULL`
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
.groupBy(analyticsPageViews.referrerDomain)
|
|
194
|
+
.orderBy(sql`count(*) DESC`)
|
|
195
|
+
|
|
196
|
+
return rows.map((row) => ({
|
|
197
|
+
// eslint-disable-next-line unicorn/no-null -- null represents direct traffic (no referrer)
|
|
198
|
+
domain: row.domain ?? null,
|
|
199
|
+
pageViews: row.pageViews,
|
|
200
|
+
uniqueVisitors: row.uniqueVisitors,
|
|
201
|
+
}))
|
|
202
|
+
},
|
|
203
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
204
|
+
}),
|
|
205
|
+
|
|
206
|
+
getDevices: (params) =>
|
|
207
|
+
Effect.tryPromise({
|
|
208
|
+
try: async () => {
|
|
209
|
+
const condition = whereClause(params)
|
|
210
|
+
|
|
211
|
+
// Device type breakdown
|
|
212
|
+
const deviceRows = await db
|
|
213
|
+
.select({
|
|
214
|
+
name: analyticsPageViews.deviceType,
|
|
215
|
+
count: count(),
|
|
216
|
+
})
|
|
217
|
+
.from(analyticsPageViews)
|
|
218
|
+
.where(condition)
|
|
219
|
+
.groupBy(analyticsPageViews.deviceType)
|
|
220
|
+
.orderBy(sql`count(*) DESC`)
|
|
221
|
+
|
|
222
|
+
// Browser breakdown
|
|
223
|
+
const browserRows = await db
|
|
224
|
+
.select({
|
|
225
|
+
name: analyticsPageViews.browserName,
|
|
226
|
+
count: count(),
|
|
227
|
+
})
|
|
228
|
+
.from(analyticsPageViews)
|
|
229
|
+
.where(condition)
|
|
230
|
+
.groupBy(analyticsPageViews.browserName)
|
|
231
|
+
.orderBy(sql`count(*) DESC`)
|
|
232
|
+
|
|
233
|
+
// OS breakdown
|
|
234
|
+
const osRows = await db
|
|
235
|
+
.select({
|
|
236
|
+
name: analyticsPageViews.osName,
|
|
237
|
+
count: count(),
|
|
238
|
+
})
|
|
239
|
+
.from(analyticsPageViews)
|
|
240
|
+
.where(condition)
|
|
241
|
+
.groupBy(analyticsPageViews.osName)
|
|
242
|
+
.orderBy(sql`count(*) DESC`)
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
deviceTypes: computePercentages(deviceRows),
|
|
246
|
+
browsers: computePercentages(browserRows),
|
|
247
|
+
operatingSystems: computePercentages(osRows),
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
251
|
+
}),
|
|
252
|
+
|
|
253
|
+
getCampaigns: (params) =>
|
|
254
|
+
Effect.tryPromise({
|
|
255
|
+
try: async () => {
|
|
256
|
+
const rows = await db
|
|
257
|
+
.select({
|
|
258
|
+
source: analyticsPageViews.utmSource,
|
|
259
|
+
medium: analyticsPageViews.utmMedium,
|
|
260
|
+
campaign: analyticsPageViews.utmCampaign,
|
|
261
|
+
pageViews: count(),
|
|
262
|
+
uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
|
|
263
|
+
})
|
|
264
|
+
.from(analyticsPageViews)
|
|
265
|
+
.where(
|
|
266
|
+
and(
|
|
267
|
+
whereClause(params),
|
|
268
|
+
// Only include rows that have at least one UTM parameter
|
|
269
|
+
sql`(${analyticsPageViews.utmSource} IS NOT NULL OR ${analyticsPageViews.utmMedium} IS NOT NULL OR ${analyticsPageViews.utmCampaign} IS NOT NULL)`
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
.groupBy(
|
|
273
|
+
analyticsPageViews.utmSource,
|
|
274
|
+
analyticsPageViews.utmMedium,
|
|
275
|
+
analyticsPageViews.utmCampaign
|
|
276
|
+
)
|
|
277
|
+
.orderBy(sql`count(*) DESC`)
|
|
278
|
+
|
|
279
|
+
return rows.map((row) => ({
|
|
280
|
+
// eslint-disable-next-line unicorn/no-null -- null represents missing UTM parameter
|
|
281
|
+
source: row.source ?? null,
|
|
282
|
+
// eslint-disable-next-line unicorn/no-null -- null represents missing UTM parameter
|
|
283
|
+
medium: row.medium ?? null,
|
|
284
|
+
// eslint-disable-next-line unicorn/no-null -- null represents missing UTM parameter
|
|
285
|
+
campaign: row.campaign ?? null,
|
|
286
|
+
pageViews: row.pageViews,
|
|
287
|
+
uniqueVisitors: row.uniqueVisitors,
|
|
288
|
+
}))
|
|
289
|
+
},
|
|
290
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
291
|
+
}),
|
|
292
|
+
|
|
293
|
+
deleteOlderThan: (appName, cutoff) =>
|
|
294
|
+
Effect.tryPromise({
|
|
295
|
+
try: async () => {
|
|
296
|
+
const result = await db
|
|
297
|
+
.delete(analyticsPageViews)
|
|
298
|
+
.where(
|
|
299
|
+
and(
|
|
300
|
+
// Delete records for this app OR records with the default app name
|
|
301
|
+
// This ensures test data inserted via the view (which gets 'default' app_name)
|
|
302
|
+
// is cleaned up by retention logic regardless of the actual app name
|
|
303
|
+
or(
|
|
304
|
+
eq(analyticsPageViews.appName, appName),
|
|
305
|
+
eq(analyticsPageViews.appName, 'default')
|
|
306
|
+
),
|
|
307
|
+
lt(analyticsPageViews.timestamp, cutoff)
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
.returning({ id: analyticsPageViews.id })
|
|
311
|
+
|
|
312
|
+
return result.length
|
|
313
|
+
},
|
|
314
|
+
catch: (error) => new AnalyticsDatabaseError({ cause: error }),
|
|
315
|
+
}),
|
|
316
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
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 { eq } from 'drizzle-orm'
|
|
9
|
+
import { Effect, Layer } from 'effect'
|
|
10
|
+
import { AuthRepository, AuthDatabaseError } from '@/application/ports/repositories/auth-repository'
|
|
11
|
+
import { users } from '@/infrastructure/auth/better-auth/schema'
|
|
12
|
+
import { db } from '@/infrastructure/database'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Auth Repository Implementation
|
|
16
|
+
*
|
|
17
|
+
* Provides database operations for auth-related user management.
|
|
18
|
+
* Both methods operate on the Better Auth `users` table via Drizzle ORM.
|
|
19
|
+
*/
|
|
20
|
+
export const AuthRepositoryLive = Layer.succeed(AuthRepository, {
|
|
21
|
+
verifyUserEmail: (userId: string) =>
|
|
22
|
+
Effect.tryPromise({
|
|
23
|
+
try: () => db.update(users).set({ emailVerified: true }).where(eq(users.id, userId)),
|
|
24
|
+
catch: (error) => new AuthDatabaseError({ cause: error }),
|
|
25
|
+
}).pipe(Effect.asVoid),
|
|
26
|
+
|
|
27
|
+
getUserRole: (userId: string) =>
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
const result = yield* Effect.tryPromise({
|
|
30
|
+
try: async () => {
|
|
31
|
+
return await db
|
|
32
|
+
.select({ role: users.role })
|
|
33
|
+
.from(users)
|
|
34
|
+
.where(eq(users.id, userId))
|
|
35
|
+
.limit(1)
|
|
36
|
+
},
|
|
37
|
+
catch: (error) => new AuthDatabaseError({ cause: error }),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return result[0]?.role ?? undefined
|
|
41
|
+
}),
|
|
42
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { Layer } from 'effect'
|
|
9
|
+
import { BatchRepository } from '@/application/ports/repositories/batch-repository'
|
|
10
|
+
import {
|
|
11
|
+
batchCreateRecords,
|
|
12
|
+
batchUpdateRecords,
|
|
13
|
+
batchDeleteRecords,
|
|
14
|
+
batchRestoreRecords,
|
|
15
|
+
upsertRecords,
|
|
16
|
+
} from '@/infrastructure/database/table-queries'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Live implementation of BatchRepository using table-queries infrastructure
|
|
20
|
+
*
|
|
21
|
+
* Maps port method names to infrastructure function names.
|
|
22
|
+
*/
|
|
23
|
+
export const BatchRepositoryLive = Layer.succeed(BatchRepository, {
|
|
24
|
+
batchCreate: batchCreateRecords,
|
|
25
|
+
batchUpdate: batchUpdateRecords,
|
|
26
|
+
batchDelete: batchDeleteRecords,
|
|
27
|
+
batchRestore: batchRestoreRecords,
|
|
28
|
+
upsert: upsertRecords,
|
|
29
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
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 { Layer } from 'effect'
|
|
9
|
+
import { CommentRepository } from '@/application/ports/repositories/comment-repository'
|
|
10
|
+
import {
|
|
11
|
+
createComment,
|
|
12
|
+
getCommentWithUser,
|
|
13
|
+
getCommentForAuth,
|
|
14
|
+
deleteComment,
|
|
15
|
+
listComments,
|
|
16
|
+
getCommentsCount,
|
|
17
|
+
updateComment,
|
|
18
|
+
} from '@/infrastructure/database/table-queries/query-helpers/comment-queries'
|
|
19
|
+
import {
|
|
20
|
+
checkRecordExists,
|
|
21
|
+
getUserById,
|
|
22
|
+
} from '@/infrastructure/database/table-queries/query-helpers/record-validation-queries'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Live implementation of CommentRepository using comment-queries infrastructure
|
|
26
|
+
*
|
|
27
|
+
* Maps port method names to infrastructure function names.
|
|
28
|
+
*/
|
|
29
|
+
export const CommentRepositoryLive = Layer.succeed(CommentRepository, {
|
|
30
|
+
create: createComment,
|
|
31
|
+
getWithUser: getCommentWithUser,
|
|
32
|
+
checkRecordExists,
|
|
33
|
+
getForAuth: getCommentForAuth,
|
|
34
|
+
getUserById,
|
|
35
|
+
remove: deleteComment,
|
|
36
|
+
list: listComments,
|
|
37
|
+
getCount: getCommentsCount,
|
|
38
|
+
update: updateComment,
|
|
39
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
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 { Layer } from 'effect'
|
|
9
|
+
import { TableRepository } from '@/application/ports/repositories/table-repository'
|
|
10
|
+
import {
|
|
11
|
+
listRecords,
|
|
12
|
+
listTrash,
|
|
13
|
+
getRecord,
|
|
14
|
+
createRecord,
|
|
15
|
+
updateRecord,
|
|
16
|
+
deleteRecord,
|
|
17
|
+
permanentlyDeleteRecord,
|
|
18
|
+
restoreRecord,
|
|
19
|
+
computeAggregations,
|
|
20
|
+
} from '@/infrastructure/database/table-queries'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Live implementation of TableRepository using table-queries infrastructure
|
|
24
|
+
*
|
|
25
|
+
* Delegates all operations to the existing database query functions.
|
|
26
|
+
* Session types are structurally compatible (UserSession ≅ Session).
|
|
27
|
+
*/
|
|
28
|
+
export const TableRepositoryLive = Layer.succeed(TableRepository, {
|
|
29
|
+
listRecords,
|
|
30
|
+
listTrash,
|
|
31
|
+
getRecord,
|
|
32
|
+
createRecord,
|
|
33
|
+
updateRecord,
|
|
34
|
+
deleteRecord,
|
|
35
|
+
permanentlyDeleteRecord,
|
|
36
|
+
restoreRecord,
|
|
37
|
+
computeAggregations,
|
|
38
|
+
})
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
* Schema Dependency Sorting Utilities
|
|
10
|
+
*
|
|
11
|
+
* Functions for topological sorting of tables by foreign key dependencies.
|
|
12
|
+
* Used by schema-initializer.ts to ensure tables are created in correct order.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { isRelationshipField } from '../sql/sql-generators'
|
|
16
|
+
import type { Table } from '@/domain/models/app/table'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detect tables involved in circular dependencies with at least one optional FK.
|
|
20
|
+
* These tables can use the INSERT-UPDATE pattern and should have FK constraints
|
|
21
|
+
* added after all tables are created.
|
|
22
|
+
*
|
|
23
|
+
* @param tables - Array of tables to check
|
|
24
|
+
* @returns Set of table names involved in resolvable circular dependencies
|
|
25
|
+
*/
|
|
26
|
+
export const detectCircularDependenciesWithOptionalFK = (
|
|
27
|
+
tables: readonly Table[]
|
|
28
|
+
): ReadonlySet<string> => {
|
|
29
|
+
const tablesByName = new Map(tables.map((t) => [t.name, t]))
|
|
30
|
+
// Collect circular table names (functional construction)
|
|
31
|
+
const circularTableNames = tables.flatMap((table) => {
|
|
32
|
+
const optionalRelationships = table.fields.filter(
|
|
33
|
+
(field): field is typeof field & { relatedTable: string } =>
|
|
34
|
+
isRelationshipField(field) &&
|
|
35
|
+
field.relatedTable !== table.name && // Exclude self-references
|
|
36
|
+
field.required === false // Explicitly optional FK (allows NULL)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return optionalRelationships.flatMap((field) => {
|
|
40
|
+
const relatedTableName = field.relatedTable
|
|
41
|
+
const relatedTable = tablesByName.get(relatedTableName)
|
|
42
|
+
|
|
43
|
+
if (!relatedTable) return []
|
|
44
|
+
|
|
45
|
+
// Check if related table also has a relationship back to this table
|
|
46
|
+
const hasReverseRelationship = relatedTable.fields.some(
|
|
47
|
+
(f) => isRelationshipField(f) && f.relatedTable === table.name
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return hasReverseRelationship ? [table.name, relatedTableName] : []
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return new Set(circularTableNames)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Sort tables by foreign key dependencies using topological sort
|
|
59
|
+
* Tables with no dependencies come first, tables with dependencies come after their referenced tables
|
|
60
|
+
*
|
|
61
|
+
* This ensures that when we CREATE TABLE statements, referenced tables exist before
|
|
62
|
+
* tables that reference them via foreign keys.
|
|
63
|
+
*
|
|
64
|
+
* Algorithm: Kahn's algorithm for topological sorting (functional implementation)
|
|
65
|
+
* - Build dependency graph (which tables does each table depend on)
|
|
66
|
+
* - Process tables with no dependencies first
|
|
67
|
+
* - Remove processed tables from dependency lists
|
|
68
|
+
* - Repeat until all tables are processed
|
|
69
|
+
*
|
|
70
|
+
* Handles circular dependencies by detecting them and keeping original order for those tables.
|
|
71
|
+
*
|
|
72
|
+
* @param tables - Array of tables to sort
|
|
73
|
+
* @returns Tables sorted by dependency order (no dependencies first)
|
|
74
|
+
*/
|
|
75
|
+
export const sortTablesByDependencies = (tables: readonly Table[]): readonly Table[] => {
|
|
76
|
+
// Build dependency map: tableName -> Set of tables it depends on
|
|
77
|
+
const tableMap = new Map(tables.map((t) => [t.name, t]))
|
|
78
|
+
|
|
79
|
+
const initialDeps = new Map(
|
|
80
|
+
tables.map((table) => {
|
|
81
|
+
const deps = new Set(
|
|
82
|
+
table.fields
|
|
83
|
+
.filter(isRelationshipField)
|
|
84
|
+
.map((f) => f.relatedTable)
|
|
85
|
+
.filter((name): name is string => name !== undefined && name !== table.name)
|
|
86
|
+
)
|
|
87
|
+
return [table.name, deps]
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// Recursive helper to process tables in dependency order
|
|
92
|
+
const processTable = (
|
|
93
|
+
current: string,
|
|
94
|
+
remaining: ReadonlyMap<string, Set<string>>,
|
|
95
|
+
sorted: readonly Table[]
|
|
96
|
+
): readonly Table[] => {
|
|
97
|
+
const table = tableMap.get(current)
|
|
98
|
+
if (!table) return sorted
|
|
99
|
+
|
|
100
|
+
// Add current table to sorted list
|
|
101
|
+
const newSorted = [...sorted, table]
|
|
102
|
+
|
|
103
|
+
// Remove current table from all dependency sets
|
|
104
|
+
const updated = new Map(
|
|
105
|
+
Array.from(remaining.entries()).map(([name, deps]) => {
|
|
106
|
+
const newDeps = new Set(deps)
|
|
107
|
+
// eslint-disable-next-line functional/immutable-data, functional/no-expression-statements, drizzle/enforce-delete-with-where -- Topological sort requires working copy mutation for efficiency; drizzle false positive (Set.delete not DB)
|
|
108
|
+
newDeps.delete(current)
|
|
109
|
+
return [name, newDeps]
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// Remove current table from remaining
|
|
114
|
+
// eslint-disable-next-line functional/immutable-data, functional/no-expression-statements, drizzle/enforce-delete-with-where -- Topological sort requires working copy mutation for efficiency; drizzle false positive (Map.delete not DB)
|
|
115
|
+
updated.delete(current)
|
|
116
|
+
|
|
117
|
+
// Find next table with no dependencies
|
|
118
|
+
const next = Array.from(updated.entries()).find(([, deps]) => deps.size === 0)
|
|
119
|
+
|
|
120
|
+
if (next) {
|
|
121
|
+
return processTable(next[0], updated, newSorted)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// No more tables with zero dependencies - check for remaining tables
|
|
125
|
+
if (updated.size > 0) {
|
|
126
|
+
// Circular dependency or remaining tables - add in original order
|
|
127
|
+
return [...newSorted, ...tables.filter((t) => !newSorted.includes(t) && updated.has(t.name))]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return newSorted
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Find first table with no dependencies
|
|
134
|
+
const first = Array.from(initialDeps.entries()).find(([, deps]) => deps.size === 0)
|
|
135
|
+
|
|
136
|
+
if (first) {
|
|
137
|
+
return processTable(first[0], initialDeps, [])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// All tables have dependencies (circular) - return original order
|
|
141
|
+
return tables
|
|
142
|
+
}
|