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,66 @@
|
|
|
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 {
|
|
10
|
+
getActivityById,
|
|
11
|
+
type ActivityDatabaseError,
|
|
12
|
+
type ActivityNotFoundError,
|
|
13
|
+
} from '@/infrastructure/database/activity-queries'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Invalid activity ID error
|
|
17
|
+
*/
|
|
18
|
+
export class InvalidActivityIdError {
|
|
19
|
+
readonly _tag = 'InvalidActivityIdError'
|
|
20
|
+
constructor(readonly activityId: string) {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get activity log by ID
|
|
25
|
+
*
|
|
26
|
+
* Validates the activity ID format and fetches activity details with user metadata.
|
|
27
|
+
*
|
|
28
|
+
* @param activityId - Activity ID as string (UUID from URL parameter)
|
|
29
|
+
* @returns Effect program that resolves to activity with user or fails with error
|
|
30
|
+
*/
|
|
31
|
+
export const GetActivityById = (activityId: string) =>
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
// Validate activity ID format (must be non-empty string)
|
|
34
|
+
// In production, activity IDs are UUIDs, but for simplicity we accept any non-empty string
|
|
35
|
+
// Integer validation for backward compatibility with tests expecting numeric IDs
|
|
36
|
+
if (!activityId || activityId.trim() === '') {
|
|
37
|
+
return yield* Effect.fail(new InvalidActivityIdError(activityId))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Try to parse as integer for backward compatibility
|
|
41
|
+
const parsedId = parseInt(activityId, 10)
|
|
42
|
+
const isNumericId = !isNaN(parsedId) && parsedId > 0 && parsedId.toString() === activityId
|
|
43
|
+
|
|
44
|
+
// Reject obviously invalid formats like "invalid-id"
|
|
45
|
+
// Accept UUIDs and numeric strings
|
|
46
|
+
const isValidUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
47
|
+
activityId
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (!isNumericId && !isValidUuid) {
|
|
51
|
+
return yield* Effect.fail(new InvalidActivityIdError(activityId))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Fetch activity from database (accepts both numeric and UUID strings)
|
|
55
|
+
const activity = yield* getActivityById(activityId)
|
|
56
|
+
|
|
57
|
+
return activity
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Export all error types for external use
|
|
62
|
+
*/
|
|
63
|
+
export type GetActivityByIdError =
|
|
64
|
+
| InvalidActivityIdError
|
|
65
|
+
| ActivityNotFoundError
|
|
66
|
+
| ActivityDatabaseError
|
|
@@ -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 { Effect } from 'effect'
|
|
9
|
+
import { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import { parseUserAgent } from './ua-parser'
|
|
11
|
+
import { computeSessionHash, computeVisitorHash } from './visitor-hash'
|
|
12
|
+
import type { AnalyticsDatabaseError } from '../../ports/repositories/analytics-repository'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Input for collecting a page view event
|
|
16
|
+
*/
|
|
17
|
+
export interface CollectPageViewInput {
|
|
18
|
+
readonly appName: string
|
|
19
|
+
readonly pagePath: string
|
|
20
|
+
readonly pageTitle?: string
|
|
21
|
+
readonly referrerUrl?: string
|
|
22
|
+
readonly utmSource?: string
|
|
23
|
+
readonly utmMedium?: string
|
|
24
|
+
readonly utmCampaign?: string
|
|
25
|
+
readonly utmContent?: string
|
|
26
|
+
readonly utmTerm?: string
|
|
27
|
+
readonly screenWidth?: number
|
|
28
|
+
readonly screenHeight?: number
|
|
29
|
+
readonly ip: string
|
|
30
|
+
readonly userAgent: string
|
|
31
|
+
readonly acceptLanguage?: string
|
|
32
|
+
readonly sessionTimeoutMinutes?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract the primary language from Accept-Language header.
|
|
37
|
+
*
|
|
38
|
+
* @example "en-US,en;q=0.9,fr;q=0.8" → "en-US"
|
|
39
|
+
*/
|
|
40
|
+
const extractLanguage = (header: string | undefined): string | undefined => {
|
|
41
|
+
if (!header) return undefined
|
|
42
|
+
const first = header.split(',')[0]
|
|
43
|
+
if (!first) return undefined
|
|
44
|
+
return first.split(';')[0]?.trim()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract domain from a referrer URL.
|
|
49
|
+
*
|
|
50
|
+
* @example "https://www.google.com/search?q=test" → "google.com"
|
|
51
|
+
*/
|
|
52
|
+
const extractReferrerDomain = (referrerUrl: string | undefined): string | undefined => {
|
|
53
|
+
if (!referrerUrl) return undefined
|
|
54
|
+
try {
|
|
55
|
+
const url = new URL(referrerUrl)
|
|
56
|
+
// Strip 'www.' prefix for cleaner domain names
|
|
57
|
+
return url.hostname.replace(/^www\./, '')
|
|
58
|
+
} catch {
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Collect a page view event.
|
|
65
|
+
*
|
|
66
|
+
* Orchestrates visitor hashing, UA parsing, referrer extraction,
|
|
67
|
+
* and persists the page view to the analytics repository.
|
|
68
|
+
*
|
|
69
|
+
* This is an Effect program requiring the AnalyticsRepository service.
|
|
70
|
+
*/
|
|
71
|
+
export const collectPageView = (
|
|
72
|
+
input: CollectPageViewInput
|
|
73
|
+
): Effect.Effect<void, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
74
|
+
Effect.gen(function* () {
|
|
75
|
+
const repo = yield* AnalyticsRepository
|
|
76
|
+
|
|
77
|
+
// Compute privacy-safe visitor and session hashes
|
|
78
|
+
const visitorHash = yield* Effect.promise(() =>
|
|
79
|
+
computeVisitorHash(input.ip, input.userAgent, input.appName)
|
|
80
|
+
)
|
|
81
|
+
const sessionHash = yield* Effect.promise(() =>
|
|
82
|
+
computeSessionHash(visitorHash, input.sessionTimeoutMinutes ?? 30)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Parse device information from User-Agent
|
|
86
|
+
const { deviceType, browserName, osName } = parseUserAgent(input.userAgent)
|
|
87
|
+
|
|
88
|
+
// Extract language and referrer domain
|
|
89
|
+
const language = extractLanguage(input.acceptLanguage)
|
|
90
|
+
const referrerDomain = extractReferrerDomain(input.referrerUrl)
|
|
91
|
+
|
|
92
|
+
// Record the page view
|
|
93
|
+
yield* repo.recordPageView({
|
|
94
|
+
appName: input.appName,
|
|
95
|
+
pagePath: input.pagePath,
|
|
96
|
+
pageTitle: input.pageTitle,
|
|
97
|
+
visitorHash,
|
|
98
|
+
sessionHash,
|
|
99
|
+
isEntrance: false, // TODO: detect entrance based on session history
|
|
100
|
+
referrerUrl: input.referrerUrl,
|
|
101
|
+
referrerDomain,
|
|
102
|
+
utmSource: input.utmSource,
|
|
103
|
+
utmMedium: input.utmMedium,
|
|
104
|
+
utmCampaign: input.utmCampaign,
|
|
105
|
+
utmContent: input.utmContent,
|
|
106
|
+
utmTerm: input.utmTerm,
|
|
107
|
+
deviceType,
|
|
108
|
+
browserName,
|
|
109
|
+
osName,
|
|
110
|
+
language,
|
|
111
|
+
screenWidth: input.screenWidth,
|
|
112
|
+
screenHeight: input.screenHeight,
|
|
113
|
+
})
|
|
114
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import type { AnalyticsDatabaseError } from '../../ports/repositories/analytics-repository'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default retention period in days when not configured
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_RETENTION_DAYS = 365
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Purge analytics data older than the configured retention period.
|
|
19
|
+
*
|
|
20
|
+
* Deletes page view records that exceed the retention window.
|
|
21
|
+
* Returns the number of records deleted.
|
|
22
|
+
*
|
|
23
|
+
* @param appName - Application name to scope deletion
|
|
24
|
+
* @param retentionDays - Number of days to retain (default: 365)
|
|
25
|
+
* @returns Number of deleted records
|
|
26
|
+
*/
|
|
27
|
+
export const purgeOldAnalyticsData = (
|
|
28
|
+
appName: string,
|
|
29
|
+
retentionDays?: number
|
|
30
|
+
): Effect.Effect<number, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const repo = yield* AnalyticsRepository
|
|
33
|
+
|
|
34
|
+
const days = retentionDays ?? DEFAULT_RETENTION_DAYS
|
|
35
|
+
const cutoff = new Date()
|
|
36
|
+
// eslint-disable-next-line functional/no-expression-statements
|
|
37
|
+
cutoff.setDate(cutoff.getDate() - days)
|
|
38
|
+
|
|
39
|
+
return yield* repo.deleteOlderThan(appName, cutoff)
|
|
40
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import type {
|
|
11
|
+
AnalyticsDatabaseError,
|
|
12
|
+
CampaignEntry,
|
|
13
|
+
} from '../../ports/repositories/analytics-repository'
|
|
14
|
+
|
|
15
|
+
export interface QueryCampaignsInput {
|
|
16
|
+
readonly appName: string
|
|
17
|
+
readonly from: Date
|
|
18
|
+
readonly to: Date
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CampaignsResult {
|
|
22
|
+
readonly campaigns: readonly CampaignEntry[]
|
|
23
|
+
readonly total: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Query UTM campaign breakdown.
|
|
28
|
+
*/
|
|
29
|
+
export const queryCampaigns = (
|
|
30
|
+
input: QueryCampaignsInput
|
|
31
|
+
): Effect.Effect<CampaignsResult, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
const repo = yield* AnalyticsRepository
|
|
34
|
+
|
|
35
|
+
const campaigns = yield* repo.getCampaigns({
|
|
36
|
+
appName: input.appName,
|
|
37
|
+
from: input.from,
|
|
38
|
+
to: input.to,
|
|
39
|
+
granularity: 'day',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return { campaigns, total: campaigns.length }
|
|
43
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import type {
|
|
11
|
+
AnalyticsDatabaseError,
|
|
12
|
+
DeviceBreakdown,
|
|
13
|
+
} from '../../ports/repositories/analytics-repository'
|
|
14
|
+
|
|
15
|
+
export interface QueryDevicesInput {
|
|
16
|
+
readonly appName: string
|
|
17
|
+
readonly from: Date
|
|
18
|
+
readonly to: Date
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Query device, browser, and OS breakdown.
|
|
23
|
+
*/
|
|
24
|
+
export const queryDevices = (
|
|
25
|
+
input: QueryDevicesInput
|
|
26
|
+
): Effect.Effect<DeviceBreakdown, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const repo = yield* AnalyticsRepository
|
|
29
|
+
|
|
30
|
+
return yield* repo.getDevices({
|
|
31
|
+
appName: input.appName,
|
|
32
|
+
from: input.from,
|
|
33
|
+
to: input.to,
|
|
34
|
+
granularity: 'day',
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import type {
|
|
11
|
+
AnalyticsDatabaseError,
|
|
12
|
+
AnalyticsSummary,
|
|
13
|
+
TimeSeriesPoint,
|
|
14
|
+
} from '../../ports/repositories/analytics-repository'
|
|
15
|
+
|
|
16
|
+
export interface QueryOverviewInput {
|
|
17
|
+
readonly appName: string
|
|
18
|
+
readonly from: Date
|
|
19
|
+
readonly to: Date
|
|
20
|
+
readonly granularity: 'hour' | 'day' | 'week' | 'month'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface OverviewResult {
|
|
24
|
+
readonly summary: AnalyticsSummary
|
|
25
|
+
readonly timeSeries: readonly TimeSeriesPoint[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Query analytics overview: summary metrics + time series data.
|
|
30
|
+
*/
|
|
31
|
+
export const queryOverview = (
|
|
32
|
+
input: QueryOverviewInput
|
|
33
|
+
): Effect.Effect<OverviewResult, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
34
|
+
Effect.gen(function* () {
|
|
35
|
+
const repo = yield* AnalyticsRepository
|
|
36
|
+
|
|
37
|
+
const params = {
|
|
38
|
+
appName: input.appName,
|
|
39
|
+
from: input.from,
|
|
40
|
+
to: input.to,
|
|
41
|
+
granularity: input.granularity,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [summary, timeSeries] = yield* Effect.all([
|
|
45
|
+
repo.getSummary(params),
|
|
46
|
+
repo.getTimeSeries(params),
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
return { summary, timeSeries }
|
|
50
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import type { AnalyticsDatabaseError, TopPage } from '../../ports/repositories/analytics-repository'
|
|
11
|
+
|
|
12
|
+
export interface QueryPagesInput {
|
|
13
|
+
readonly appName: string
|
|
14
|
+
readonly from: Date
|
|
15
|
+
readonly to: Date
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TopPagesResult {
|
|
19
|
+
readonly pages: readonly TopPage[]
|
|
20
|
+
readonly total: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Query top pages ranked by page views.
|
|
25
|
+
*/
|
|
26
|
+
export const queryPages = (
|
|
27
|
+
input: QueryPagesInput
|
|
28
|
+
): Effect.Effect<TopPagesResult, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const repo = yield* AnalyticsRepository
|
|
31
|
+
|
|
32
|
+
const pages = yield* repo.getTopPages({
|
|
33
|
+
appName: input.appName,
|
|
34
|
+
from: input.from,
|
|
35
|
+
to: input.to,
|
|
36
|
+
granularity: 'day', // Not used for top pages, but required by interface
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return { pages, total: pages.length }
|
|
40
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { AnalyticsRepository } from '../../ports/repositories/analytics-repository'
|
|
10
|
+
import type {
|
|
11
|
+
AnalyticsDatabaseError,
|
|
12
|
+
TopReferrer,
|
|
13
|
+
} from '../../ports/repositories/analytics-repository'
|
|
14
|
+
|
|
15
|
+
export interface QueryReferrersInput {
|
|
16
|
+
readonly appName: string
|
|
17
|
+
readonly from: Date
|
|
18
|
+
readonly to: Date
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TopReferrersResult {
|
|
22
|
+
readonly referrers: readonly TopReferrer[]
|
|
23
|
+
readonly total: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Query top referrer domains ranked by page views.
|
|
28
|
+
*/
|
|
29
|
+
export const queryReferrers = (
|
|
30
|
+
input: QueryReferrersInput
|
|
31
|
+
): Effect.Effect<TopReferrersResult, AnalyticsDatabaseError, AnalyticsRepository> =>
|
|
32
|
+
Effect.gen(function* () {
|
|
33
|
+
const repo = yield* AnalyticsRepository
|
|
34
|
+
|
|
35
|
+
const referrers = yield* repo.getTopReferrers({
|
|
36
|
+
appName: input.appName,
|
|
37
|
+
from: input.from,
|
|
38
|
+
to: input.to,
|
|
39
|
+
granularity: 'day',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return { referrers, total: referrers.length }
|
|
43
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
* Lightweight User-Agent parser — zero external dependencies.
|
|
10
|
+
*
|
|
11
|
+
* Covers the major browsers, OS, and device types.
|
|
12
|
+
* Not exhaustive, but sufficient for analytics breakdown.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ParsedUserAgent {
|
|
16
|
+
readonly deviceType: 'desktop' | 'mobile' | 'tablet'
|
|
17
|
+
readonly browserName: string
|
|
18
|
+
readonly osName: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse device type from User-Agent string.
|
|
23
|
+
*
|
|
24
|
+
* Detection order matters: tablet before mobile (tablets include mobile keywords).
|
|
25
|
+
*/
|
|
26
|
+
const parseDeviceType = (ua: string): 'desktop' | 'mobile' | 'tablet' => {
|
|
27
|
+
// Tablet detection (must come before mobile — iPads include "mobile" sometimes)
|
|
28
|
+
if (/ipad|tablet|playbook|silk/i.test(ua)) return 'tablet'
|
|
29
|
+
if (/android/i.test(ua) && !/mobile/i.test(ua)) return 'tablet'
|
|
30
|
+
// Mobile detection
|
|
31
|
+
if (/mobile|iphone|ipod|android.*mobile|windows phone|blackberry|opera mini|iemobile/i.test(ua))
|
|
32
|
+
return 'mobile'
|
|
33
|
+
// Default to desktop
|
|
34
|
+
return 'desktop'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse browser name from User-Agent string.
|
|
39
|
+
*
|
|
40
|
+
* Detection order matters: more specific browsers first (Edge before Chrome).
|
|
41
|
+
*/
|
|
42
|
+
const parseBrowserName = (ua: string): string => {
|
|
43
|
+
// Edge (Chromium-based includes "Edg/")
|
|
44
|
+
if (/edg\//i.test(ua)) return 'Edge'
|
|
45
|
+
// Opera (includes "OPR/" or "Opera")
|
|
46
|
+
if (/opr\//i.test(ua) || /opera/i.test(ua)) return 'Opera'
|
|
47
|
+
// Samsung Internet
|
|
48
|
+
if (/samsungbrowser/i.test(ua)) return 'Samsung Internet'
|
|
49
|
+
// Chrome (must be after Edge and Opera which also include "Chrome")
|
|
50
|
+
if (/chrome|crios/i.test(ua)) return 'Chrome'
|
|
51
|
+
// Safari (must be after Chrome — Chrome also includes "Safari")
|
|
52
|
+
if (/safari/i.test(ua) && !/chrome/i.test(ua)) return 'Safari'
|
|
53
|
+
// Firefox
|
|
54
|
+
if (/firefox|fxios/i.test(ua)) return 'Firefox'
|
|
55
|
+
// IE
|
|
56
|
+
if (/msie|trident/i.test(ua)) return 'IE'
|
|
57
|
+
return 'Other'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Parse OS name from User-Agent string.
|
|
62
|
+
*/
|
|
63
|
+
const parseOsName = (ua: string): string => {
|
|
64
|
+
// iOS (must be before macOS — iPads can include "Macintosh")
|
|
65
|
+
if (/iphone|ipad|ipod/i.test(ua)) return 'iOS'
|
|
66
|
+
// Android
|
|
67
|
+
if (/android/i.test(ua)) return 'Android'
|
|
68
|
+
// Windows
|
|
69
|
+
if (/windows/i.test(ua)) return 'Windows'
|
|
70
|
+
// macOS
|
|
71
|
+
if (/macintosh|mac os x/i.test(ua)) return 'macOS'
|
|
72
|
+
// Linux (must be after Android which includes "Linux")
|
|
73
|
+
if (/linux/i.test(ua)) return 'Linux'
|
|
74
|
+
// Chrome OS
|
|
75
|
+
if (/cros/i.test(ua)) return 'Chrome OS'
|
|
76
|
+
return 'Other'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parse a User-Agent string into device type, browser, and OS.
|
|
81
|
+
*
|
|
82
|
+
* @param ua - User-Agent header string
|
|
83
|
+
* @returns Parsed device info with deviceType, browserName, osName
|
|
84
|
+
*/
|
|
85
|
+
export const parseUserAgent = (ua: string): ParsedUserAgent => ({
|
|
86
|
+
deviceType: parseDeviceType(ua),
|
|
87
|
+
browserName: parseBrowserName(ua),
|
|
88
|
+
osName: parseOsName(ua),
|
|
89
|
+
})
|
|
@@ -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
|
+
/**
|
|
9
|
+
* Privacy-safe visitor identification using SHA-256 hashing.
|
|
10
|
+
*
|
|
11
|
+
* Hashing strategy:
|
|
12
|
+
* - Visitor hash: SHA-256(date + IP + UA + salt) — rotates daily, no PII stored
|
|
13
|
+
* - Session hash: SHA-256(visitorHash + timeWindow) — groups views into sessions
|
|
14
|
+
*
|
|
15
|
+
* Uses Web Crypto API (crypto.subtle) — zero external dependencies.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert ArrayBuffer to hex string
|
|
20
|
+
*/
|
|
21
|
+
const toHex = (buffer: ArrayBuffer): string =>
|
|
22
|
+
Array.from(new Uint8Array(buffer))
|
|
23
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
24
|
+
.join('')
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute SHA-256 hash of a string using Web Crypto API
|
|
28
|
+
*/
|
|
29
|
+
const sha256 = async (input: string): Promise<string> => {
|
|
30
|
+
const encoder = new TextEncoder()
|
|
31
|
+
const data = encoder.encode(input)
|
|
32
|
+
const hash = await crypto.subtle.digest('SHA-256', data)
|
|
33
|
+
return toHex(hash)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compute a privacy-safe visitor hash.
|
|
38
|
+
*
|
|
39
|
+
* Hash rotates daily (date component), making it impossible to track
|
|
40
|
+
* a visitor across days. No cookies or PII are stored.
|
|
41
|
+
*
|
|
42
|
+
* @param ip - Client IP address (from X-Forwarded-For or connection)
|
|
43
|
+
* @param userAgent - Client User-Agent string
|
|
44
|
+
* @param salt - Application-specific salt for additional privacy
|
|
45
|
+
* @returns SHA-256 hex string (64 characters)
|
|
46
|
+
*/
|
|
47
|
+
export const computeVisitorHash = async (
|
|
48
|
+
ip: string,
|
|
49
|
+
userAgent: string,
|
|
50
|
+
salt: string
|
|
51
|
+
): Promise<string> => {
|
|
52
|
+
const today = new Date().toISOString().slice(0, 10) // YYYY-MM-DD
|
|
53
|
+
return sha256(`${today}|${ip}|${userAgent}|${salt}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compute a session hash from a visitor hash and session timeout.
|
|
58
|
+
*
|
|
59
|
+
* Sessions are grouped by time windows based on the configured timeout.
|
|
60
|
+
* A new session starts after the timeout period of inactivity.
|
|
61
|
+
*
|
|
62
|
+
* @param visitorHash - The visitor's daily hash
|
|
63
|
+
* @param sessionTimeoutMinutes - Session timeout in minutes (default: 30)
|
|
64
|
+
* @returns SHA-256 hex string (64 characters)
|
|
65
|
+
*/
|
|
66
|
+
export const computeSessionHash = async (
|
|
67
|
+
visitorHash: string,
|
|
68
|
+
sessionTimeoutMinutes: number = 30
|
|
69
|
+
): Promise<string> => {
|
|
70
|
+
// Create time windows based on session timeout
|
|
71
|
+
// e.g., with 30min timeout: 0-29min -> window 0, 30-59min -> window 1
|
|
72
|
+
const now = new Date()
|
|
73
|
+
const minutesSinceMidnight = now.getHours() * 60 + now.getMinutes()
|
|
74
|
+
const timeWindow = Math.floor(minutesSinceMidnight / sessionTimeoutMinutes)
|
|
75
|
+
const dateStr = now.toISOString().slice(0, 10)
|
|
76
|
+
return sha256(`${visitorHash}|${dateStr}|${timeWindow}`)
|
|
77
|
+
}
|