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,163 @@
|
|
|
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 { sql } from 'drizzle-orm'
|
|
9
|
+
import { SessionContextError } from '@/domain/errors'
|
|
10
|
+
import { validateTableName, validateColumnName } from '../shared/validation'
|
|
11
|
+
import { hasDeletedByColumn, getDeletedByValue } from './authorship-helpers'
|
|
12
|
+
import type { DrizzleTransaction } from '@/infrastructure/database/drizzle/db'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Cascade soft delete to related records
|
|
16
|
+
*
|
|
17
|
+
* Helper function to cascade soft delete to child records based on onDelete: 'cascade' configuration
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line max-params, max-lines-per-function -- Cascade delete requires app config, userId, and complex conditional logic for deleted_by
|
|
20
|
+
export async function cascadeSoftDelete(
|
|
21
|
+
tx: Readonly<DrizzleTransaction>,
|
|
22
|
+
tableName: string,
|
|
23
|
+
recordId: string,
|
|
24
|
+
app: {
|
|
25
|
+
readonly tables?: ReadonlyArray<{
|
|
26
|
+
readonly name: string
|
|
27
|
+
readonly fields: ReadonlyArray<{
|
|
28
|
+
readonly name: string
|
|
29
|
+
readonly type: string
|
|
30
|
+
readonly relatedTable?: string
|
|
31
|
+
readonly onDelete?: string
|
|
32
|
+
}>
|
|
33
|
+
}>
|
|
34
|
+
},
|
|
35
|
+
userId?: string
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
if (!app.tables) return
|
|
38
|
+
|
|
39
|
+
// Find all tables with relationship fields that reference this table with onDelete: 'cascade'
|
|
40
|
+
const relatedTables = app.tables.flatMap((table) =>
|
|
41
|
+
table.fields
|
|
42
|
+
.filter(
|
|
43
|
+
(field) =>
|
|
44
|
+
field.type === 'relationship' &&
|
|
45
|
+
field.relatedTable === tableName &&
|
|
46
|
+
field.onDelete === 'cascade'
|
|
47
|
+
)
|
|
48
|
+
.map((field) => ({
|
|
49
|
+
tableName: table.name,
|
|
50
|
+
fieldName: field.name,
|
|
51
|
+
}))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const deletedByValue = getDeletedByValue(userId)
|
|
55
|
+
|
|
56
|
+
// Cascade soft delete to each related table
|
|
57
|
+
// eslint-disable-next-line functional/no-expression-statements -- Database updates for cascade delete are required side effects
|
|
58
|
+
await Promise.all(
|
|
59
|
+
relatedTables.map(async (relatedInfo) => {
|
|
60
|
+
const childTable = relatedInfo.tableName
|
|
61
|
+
const childColumn = relatedInfo.fieldName
|
|
62
|
+
|
|
63
|
+
validateTableName(childTable)
|
|
64
|
+
validateColumnName(childColumn)
|
|
65
|
+
|
|
66
|
+
// Check if child table has deleted_at column
|
|
67
|
+
const childColumnCheck = (await tx.execute(
|
|
68
|
+
sql`SELECT column_name FROM information_schema.columns WHERE table_name = ${childTable} AND column_name = 'deleted_at'`
|
|
69
|
+
)) as readonly Record<string, unknown>[]
|
|
70
|
+
|
|
71
|
+
if (childColumnCheck.length > 0) {
|
|
72
|
+
// Check if child table has deleted_by column (using helper)
|
|
73
|
+
const hasDeletedByCol = await hasDeletedByColumn(tx, childTable)
|
|
74
|
+
|
|
75
|
+
// Cascade soft delete to related records with deleted_by if column exists
|
|
76
|
+
if (hasDeletedByCol) {
|
|
77
|
+
// eslint-disable-next-line functional/no-expression-statements -- Database update for cascade is required
|
|
78
|
+
await tx.execute(
|
|
79
|
+
sql`UPDATE ${sql.identifier(childTable)} SET deleted_at = NOW(), deleted_by = ${deletedByValue} WHERE ${sql.identifier(childColumn)} = ${recordId} AND deleted_at IS NULL`
|
|
80
|
+
)
|
|
81
|
+
} else {
|
|
82
|
+
// eslint-disable-next-line functional/no-expression-statements -- Database update for cascade is required
|
|
83
|
+
await tx.execute(
|
|
84
|
+
sql`UPDATE ${sql.identifier(childTable)} SET deleted_at = NOW() WHERE ${sql.identifier(childColumn)} = ${recordId} AND deleted_at IS NULL`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Execute soft delete operation
|
|
94
|
+
* Promise-based for transaction use
|
|
95
|
+
*/
|
|
96
|
+
export async function executeSoftDelete(
|
|
97
|
+
tx: Readonly<DrizzleTransaction>,
|
|
98
|
+
tableName: string,
|
|
99
|
+
recordId: string,
|
|
100
|
+
userId?: string
|
|
101
|
+
): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
// Check if table has deleted_by column using helper
|
|
104
|
+
const hasDeletedByCol = await hasDeletedByColumn(tx, tableName)
|
|
105
|
+
const deletedByValue = getDeletedByValue(userId)
|
|
106
|
+
|
|
107
|
+
const tableIdent = sql.identifier(tableName)
|
|
108
|
+
|
|
109
|
+
// Build UPDATE query with or without deleted_by
|
|
110
|
+
const result = hasDeletedByCol
|
|
111
|
+
? ((await tx.execute(
|
|
112
|
+
sql`UPDATE ${tableIdent} SET deleted_at = NOW(), deleted_by = ${deletedByValue} WHERE id = ${recordId} AND deleted_at IS NULL RETURNING id`
|
|
113
|
+
)) as readonly Record<string, unknown>[])
|
|
114
|
+
: ((await tx.execute(
|
|
115
|
+
sql`UPDATE ${tableIdent} SET deleted_at = NOW() WHERE id = ${recordId} AND deleted_at IS NULL RETURNING id`
|
|
116
|
+
)) as readonly Record<string, unknown>[])
|
|
117
|
+
|
|
118
|
+
return result.length > 0
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
121
|
+
throw new SessionContextError(`Failed to delete record ${recordId} from ${tableName}`, error)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Execute hard delete operation
|
|
127
|
+
* Promise-based for transaction use
|
|
128
|
+
*/
|
|
129
|
+
export async function executeHardDelete(
|
|
130
|
+
tx: Readonly<DrizzleTransaction>,
|
|
131
|
+
tableName: string,
|
|
132
|
+
recordId: string
|
|
133
|
+
): Promise<boolean> {
|
|
134
|
+
try {
|
|
135
|
+
const tableIdent = sql.identifier(tableName)
|
|
136
|
+
const result = (await tx.execute(
|
|
137
|
+
sql`DELETE FROM ${tableIdent} WHERE id = ${recordId} RETURNING id`
|
|
138
|
+
)) as readonly Record<string, unknown>[]
|
|
139
|
+
return result.length > 0
|
|
140
|
+
} catch (error) {
|
|
141
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
142
|
+
throw new SessionContextError(`Failed to delete record ${recordId} from ${tableName}`, error)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if table has deleted_at column for soft delete support
|
|
148
|
+
* Promise-based for transaction use
|
|
149
|
+
*/
|
|
150
|
+
export async function checkDeletedAtColumn(
|
|
151
|
+
tx: Readonly<DrizzleTransaction>,
|
|
152
|
+
tableName: string
|
|
153
|
+
): Promise<boolean> {
|
|
154
|
+
try {
|
|
155
|
+
const columnCheck = (await tx.execute(
|
|
156
|
+
sql`SELECT column_name FROM information_schema.columns WHERE table_name = ${tableName} AND column_name = 'deleted_at'`
|
|
157
|
+
)) as readonly Record<string, unknown>[]
|
|
158
|
+
return columnCheck.length > 0
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
161
|
+
throw new SessionContextError(`Failed to check columns for ${tableName}`, error)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { sql } from 'drizzle-orm'
|
|
9
|
+
import { Effect } from 'effect'
|
|
10
|
+
import { SessionContextError } from '@/domain/errors'
|
|
11
|
+
import type { DrizzleTransaction } from '@/infrastructure/database/drizzle/db'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fetch a single record by ID (Promise-based for transaction use)
|
|
15
|
+
*
|
|
16
|
+
* Used before mutations (update/delete) for activity logging.
|
|
17
|
+
* Throws SessionContextError on database failure.
|
|
18
|
+
*/
|
|
19
|
+
export async function fetchRecordById(
|
|
20
|
+
tx: Readonly<DrizzleTransaction>,
|
|
21
|
+
tableName: string,
|
|
22
|
+
recordId: string
|
|
23
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
24
|
+
try {
|
|
25
|
+
const result = (await tx.execute(
|
|
26
|
+
sql`SELECT * FROM ${sql.identifier(tableName)} WHERE id = ${recordId} LIMIT 1`
|
|
27
|
+
)) as readonly Record<string, unknown>[]
|
|
28
|
+
return result[0]
|
|
29
|
+
} catch (error) {
|
|
30
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
31
|
+
throw new SessionContextError(`Failed to fetch record ${recordId}`, error)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fetch a single record by ID (Effect-based for batch operations)
|
|
37
|
+
*
|
|
38
|
+
* Returns undefined on error — non-critical for batch activity logging.
|
|
39
|
+
*/
|
|
40
|
+
export function fetchRecordByIdEffect(
|
|
41
|
+
tx: Readonly<DrizzleTransaction>,
|
|
42
|
+
tableName: string,
|
|
43
|
+
recordId: string
|
|
44
|
+
): Effect.Effect<Record<string, unknown> | undefined, never> {
|
|
45
|
+
return Effect.tryPromise({
|
|
46
|
+
try: async () => {
|
|
47
|
+
const result = (await tx.execute(
|
|
48
|
+
sql`SELECT * FROM ${sql.identifier(tableName)} WHERE id = ${recordId} LIMIT 1`
|
|
49
|
+
)) as readonly Record<string, unknown>[]
|
|
50
|
+
return result[0]
|
|
51
|
+
},
|
|
52
|
+
catch: () => undefined,
|
|
53
|
+
}).pipe(Effect.orElseSucceed(() => undefined))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch multiple records by IDs (Effect-based for batch operations)
|
|
58
|
+
*
|
|
59
|
+
* Fails with SessionContextError on database failure.
|
|
60
|
+
*/
|
|
61
|
+
export function fetchRecordsByIds(
|
|
62
|
+
tx: Readonly<DrizzleTransaction>,
|
|
63
|
+
tableName: string,
|
|
64
|
+
recordIds: readonly string[]
|
|
65
|
+
): Effect.Effect<readonly Record<string, unknown>[], SessionContextError> {
|
|
66
|
+
return Effect.tryPromise({
|
|
67
|
+
try: async () => {
|
|
68
|
+
const idParams = sql.join(
|
|
69
|
+
recordIds.map((id) => sql`${id}`),
|
|
70
|
+
sql.raw(', ')
|
|
71
|
+
)
|
|
72
|
+
const result = (await tx.execute(
|
|
73
|
+
sql`SELECT * FROM ${sql.identifier(tableName)} WHERE id IN (${idParams})`
|
|
74
|
+
)) as readonly Record<string, unknown>[]
|
|
75
|
+
return result
|
|
76
|
+
},
|
|
77
|
+
catch: (error) => new SessionContextError('Failed to fetch records before operation', error),
|
|
78
|
+
})
|
|
79
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { sql } from 'drizzle-orm'
|
|
9
|
+
import { SessionContextError, type DrizzleTransaction } from '@/infrastructure/database'
|
|
10
|
+
import { validateColumnName } from '../shared/validation'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate fields object is not empty
|
|
14
|
+
* Returns entries directly or throws error (Promise-based for transaction use)
|
|
15
|
+
*/
|
|
16
|
+
export async function validateFieldsNotEmpty(
|
|
17
|
+
fields: Readonly<Record<string, unknown>>
|
|
18
|
+
): Promise<readonly [string, unknown][]> {
|
|
19
|
+
const entries = Object.entries(fields)
|
|
20
|
+
|
|
21
|
+
if (entries.length === 0) {
|
|
22
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
23
|
+
throw new SessionContextError('Cannot update record with no fields', undefined)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return entries
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build UPDATE SET clause with validated columns (CRUD version)
|
|
31
|
+
*/
|
|
32
|
+
export function buildUpdateSetClauseCRUD(
|
|
33
|
+
entries: readonly [string, unknown][]
|
|
34
|
+
): Readonly<ReturnType<typeof sql.join>> {
|
|
35
|
+
const setClauses = entries.map(([key, value]) => {
|
|
36
|
+
validateColumnName(key)
|
|
37
|
+
return sql`${sql.identifier(key)} = ${value}`
|
|
38
|
+
})
|
|
39
|
+
return sql.join(setClauses, sql.raw(', '))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute UPDATE query with permission enforcement
|
|
44
|
+
* Promise-based for transaction use
|
|
45
|
+
*/
|
|
46
|
+
export async function executeRecordUpdateCRUD(
|
|
47
|
+
tx: Readonly<DrizzleTransaction>,
|
|
48
|
+
tableName: string,
|
|
49
|
+
recordId: string,
|
|
50
|
+
setClause: Readonly<ReturnType<typeof sql.join>>
|
|
51
|
+
): Promise<Record<string, unknown>> {
|
|
52
|
+
try {
|
|
53
|
+
const result = (await tx.execute(
|
|
54
|
+
sql`UPDATE ${sql.identifier(tableName)} SET ${setClause} WHERE id = ${recordId} RETURNING *`
|
|
55
|
+
)) as readonly Record<string, unknown>[]
|
|
56
|
+
|
|
57
|
+
// If no rows were updated, record not found or access denied
|
|
58
|
+
if (result.length === 0) {
|
|
59
|
+
// eslint-disable-next-line functional/no-throw-statements -- Permission blocking requires error propagation
|
|
60
|
+
throw new Error(`Record not found or access denied`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result[0]!
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Preserve "not found" or "access denied" in wrapper message for API error handling
|
|
66
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
67
|
+
if (errorMsg.includes('not found') || errorMsg.includes('access denied')) {
|
|
68
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
69
|
+
throw new SessionContextError(errorMsg, error)
|
|
70
|
+
}
|
|
71
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
72
|
+
throw new SessionContextError(`Failed to update record ${recordId} in ${tableName}`, error)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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 { db, SessionContextError } from '@/infrastructure/database'
|
|
10
|
+
import { activityLogs } from '@/infrastructure/database/drizzle/schema/activity-log'
|
|
11
|
+
import type { App } from '@/domain/models/app'
|
|
12
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Common activity logging helper
|
|
16
|
+
*
|
|
17
|
+
* Logs database operations (create, update, delete) for audit trail.
|
|
18
|
+
* This is a non-critical operation that should not fail the main operation.
|
|
19
|
+
*/
|
|
20
|
+
export function logActivity(config: {
|
|
21
|
+
readonly session: Readonly<Session>
|
|
22
|
+
readonly tableName: string
|
|
23
|
+
readonly action: 'create' | 'update' | 'delete' | 'restore'
|
|
24
|
+
readonly recordId: string
|
|
25
|
+
readonly changes: {
|
|
26
|
+
readonly before?: Record<string, unknown>
|
|
27
|
+
readonly after?: Record<string, unknown>
|
|
28
|
+
}
|
|
29
|
+
readonly app?: App
|
|
30
|
+
}): Effect.Effect<void, never> {
|
|
31
|
+
const { session, tableName, action, recordId, changes, app } = config
|
|
32
|
+
return Effect.ignore(
|
|
33
|
+
Effect.tryPromise({
|
|
34
|
+
try: async () => {
|
|
35
|
+
// Get table ID from app schema if available
|
|
36
|
+
const table = app?.tables?.find((t) => t.name === tableName)
|
|
37
|
+
const tableId = table?.id ? String(table.id) : '1'
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line functional/no-expression-statements -- Database insert for logging is an acceptable side effect
|
|
40
|
+
await db.insert(activityLogs).values({
|
|
41
|
+
id: crypto.randomUUID(),
|
|
42
|
+
userId: session.userId,
|
|
43
|
+
action,
|
|
44
|
+
tableName,
|
|
45
|
+
tableId,
|
|
46
|
+
recordId,
|
|
47
|
+
changes,
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
catch: (error) => new SessionContextError('Failed to log activity', error),
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
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, and, asc, gte, sql } from 'drizzle-orm'
|
|
9
|
+
import { Effect } from 'effect'
|
|
10
|
+
import { users } from '@/infrastructure/auth/better-auth/schema'
|
|
11
|
+
import { SessionContextError } from '@/infrastructure/database'
|
|
12
|
+
import { db } from '@/infrastructure/database/drizzle'
|
|
13
|
+
import { activityLogs } from '@/infrastructure/database/drizzle/schema/activity-log'
|
|
14
|
+
import { extractUserFromRow } from '../shared/user-join-helpers'
|
|
15
|
+
import type { ActivityHistoryEntry } from '@/application/ports/repositories/activity-repository'
|
|
16
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build where condition for activity log queries (with 1-year retention policy)
|
|
20
|
+
*/
|
|
21
|
+
function buildActivityWhereCondition(tableName: string, recordId: string) {
|
|
22
|
+
const now = new Date()
|
|
23
|
+
const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate())
|
|
24
|
+
return and(
|
|
25
|
+
eq(activityLogs.tableName, tableName),
|
|
26
|
+
eq(activityLogs.recordId, recordId),
|
|
27
|
+
gte(activityLogs.createdAt, oneYearAgo)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Transform an activity log row into an ActivityHistoryEntry
|
|
33
|
+
*/
|
|
34
|
+
function transformActivityRow(row: {
|
|
35
|
+
readonly action: string
|
|
36
|
+
readonly createdAt: Date
|
|
37
|
+
readonly changes: unknown
|
|
38
|
+
readonly userId: string | null
|
|
39
|
+
readonly userName: string | null
|
|
40
|
+
readonly userEmail: string | null
|
|
41
|
+
readonly userImage: string | null
|
|
42
|
+
}): ActivityHistoryEntry {
|
|
43
|
+
return {
|
|
44
|
+
action: row.action,
|
|
45
|
+
createdAt: row.createdAt,
|
|
46
|
+
changes: row.changes,
|
|
47
|
+
user: extractUserFromRow(row),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetch activity history for a specific record with optional pagination
|
|
53
|
+
*/
|
|
54
|
+
export function getRecordHistory(config: {
|
|
55
|
+
readonly session: Readonly<Session>
|
|
56
|
+
readonly tableName: string
|
|
57
|
+
readonly recordId: string
|
|
58
|
+
readonly limit?: number
|
|
59
|
+
readonly offset?: number
|
|
60
|
+
}): Effect.Effect<
|
|
61
|
+
{
|
|
62
|
+
readonly entries: readonly ActivityHistoryEntry[]
|
|
63
|
+
readonly total: number
|
|
64
|
+
},
|
|
65
|
+
SessionContextError
|
|
66
|
+
> {
|
|
67
|
+
const { tableName, recordId, limit, offset } = config
|
|
68
|
+
|
|
69
|
+
return Effect.tryPromise({
|
|
70
|
+
try: async () => {
|
|
71
|
+
const whereCondition = buildActivityWhereCondition(tableName, recordId)
|
|
72
|
+
|
|
73
|
+
const countResult = await db
|
|
74
|
+
.select({ count: sql<number>`count(*)::int` })
|
|
75
|
+
.from(activityLogs)
|
|
76
|
+
.where(whereCondition)
|
|
77
|
+
const total = countResult[0]?.count ?? 0
|
|
78
|
+
|
|
79
|
+
const baseQuery = db
|
|
80
|
+
.select({
|
|
81
|
+
action: activityLogs.action,
|
|
82
|
+
createdAt: activityLogs.createdAt,
|
|
83
|
+
changes: activityLogs.changes,
|
|
84
|
+
userId: activityLogs.userId,
|
|
85
|
+
userName: users.name,
|
|
86
|
+
userEmail: users.email,
|
|
87
|
+
userImage: users.image,
|
|
88
|
+
})
|
|
89
|
+
.from(activityLogs)
|
|
90
|
+
.leftJoin(users, eq(activityLogs.userId, users.id))
|
|
91
|
+
.where(whereCondition)
|
|
92
|
+
.orderBy(asc(activityLogs.createdAt))
|
|
93
|
+
|
|
94
|
+
const paginatedQuery =
|
|
95
|
+
limit !== undefined
|
|
96
|
+
? offset !== undefined
|
|
97
|
+
? baseQuery.limit(limit).offset(offset)
|
|
98
|
+
: baseQuery.limit(limit)
|
|
99
|
+
: baseQuery
|
|
100
|
+
|
|
101
|
+
const results = await paginatedQuery
|
|
102
|
+
return { entries: results.map(transformActivityRow), total }
|
|
103
|
+
},
|
|
104
|
+
catch: (error) => new SessionContextError('Failed to fetch activity history', error),
|
|
105
|
+
})
|
|
106
|
+
}
|