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,414 @@
|
|
|
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, eq, and, isNull, desc, asc } 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 { recordComments } from '@/infrastructure/database/drizzle/schema/record-comments'
|
|
14
|
+
import { wrapDatabaseError } from '../shared/error-handling'
|
|
15
|
+
import { extractUserFromRow } from '../shared/user-join-helpers'
|
|
16
|
+
import type { UserMetadataWithOptionalImage } from '@/application/ports/models/user-metadata'
|
|
17
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a comment on a record
|
|
21
|
+
*/
|
|
22
|
+
export function createComment(config: {
|
|
23
|
+
readonly session: Readonly<Session>
|
|
24
|
+
readonly tableId: string
|
|
25
|
+
readonly recordId: string
|
|
26
|
+
readonly content: string
|
|
27
|
+
}): Effect.Effect<
|
|
28
|
+
{
|
|
29
|
+
readonly id: string
|
|
30
|
+
readonly tableId: string
|
|
31
|
+
readonly recordId: string
|
|
32
|
+
readonly userId: string
|
|
33
|
+
readonly content: string
|
|
34
|
+
readonly createdAt: Date
|
|
35
|
+
},
|
|
36
|
+
SessionContextError
|
|
37
|
+
> {
|
|
38
|
+
const { session, tableId, recordId, content } = config
|
|
39
|
+
return Effect.tryPromise({
|
|
40
|
+
try: async () => {
|
|
41
|
+
const now = new Date()
|
|
42
|
+
const values = {
|
|
43
|
+
id: crypto.randomUUID(),
|
|
44
|
+
tableId,
|
|
45
|
+
recordId,
|
|
46
|
+
userId: session.userId,
|
|
47
|
+
content,
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const result = await db.insert(recordComments).values(values).returning()
|
|
53
|
+
|
|
54
|
+
if (result.length === 0) {
|
|
55
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required inside Effect.tryPromise for error propagation
|
|
56
|
+
throw new SessionContextError('Failed to create comment')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const comment = result[0]!
|
|
60
|
+
return {
|
|
61
|
+
id: comment.id,
|
|
62
|
+
tableId: comment.tableId,
|
|
63
|
+
recordId: comment.recordId,
|
|
64
|
+
userId: comment.userId,
|
|
65
|
+
content: comment.content,
|
|
66
|
+
createdAt: comment.createdAt,
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
catch: wrapDatabaseError('Failed to create comment'),
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Transform comment query result to domain model
|
|
75
|
+
*/
|
|
76
|
+
function transformCommentRow(row: {
|
|
77
|
+
readonly id: string
|
|
78
|
+
readonly tableId: string
|
|
79
|
+
readonly recordId: string
|
|
80
|
+
readonly userId: string
|
|
81
|
+
readonly content: string
|
|
82
|
+
readonly createdAt: Date
|
|
83
|
+
readonly updatedAt: Date
|
|
84
|
+
readonly userName: string | undefined
|
|
85
|
+
readonly userEmail: string | undefined
|
|
86
|
+
readonly userImage: string | undefined
|
|
87
|
+
}): {
|
|
88
|
+
readonly id: string
|
|
89
|
+
readonly tableId: string
|
|
90
|
+
readonly recordId: string
|
|
91
|
+
readonly userId: string
|
|
92
|
+
readonly content: string
|
|
93
|
+
readonly createdAt: Date
|
|
94
|
+
readonly updatedAt: Date
|
|
95
|
+
readonly user: UserMetadataWithOptionalImage | undefined
|
|
96
|
+
} {
|
|
97
|
+
return {
|
|
98
|
+
id: row.id,
|
|
99
|
+
tableId: row.tableId,
|
|
100
|
+
recordId: row.recordId,
|
|
101
|
+
userId: row.userId,
|
|
102
|
+
content: row.content,
|
|
103
|
+
createdAt: row.createdAt,
|
|
104
|
+
updatedAt: row.updatedAt,
|
|
105
|
+
user: extractUserFromRow(row),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Comment query result type
|
|
111
|
+
*/
|
|
112
|
+
type CommentQueryRow = {
|
|
113
|
+
readonly id: string
|
|
114
|
+
readonly tableId: string
|
|
115
|
+
readonly recordId: string
|
|
116
|
+
readonly userId: string
|
|
117
|
+
readonly content: string
|
|
118
|
+
readonly createdAt: Date
|
|
119
|
+
readonly updatedAt: Date
|
|
120
|
+
readonly userName: string | null
|
|
121
|
+
readonly userEmail: string | null
|
|
122
|
+
readonly userImage: string | null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Comment select fields with user join
|
|
127
|
+
*/
|
|
128
|
+
const commentSelectFields = {
|
|
129
|
+
id: recordComments.id,
|
|
130
|
+
tableId: recordComments.tableId,
|
|
131
|
+
recordId: recordComments.recordId,
|
|
132
|
+
userId: recordComments.userId,
|
|
133
|
+
content: recordComments.content,
|
|
134
|
+
createdAt: recordComments.createdAt,
|
|
135
|
+
updatedAt: recordComments.updatedAt,
|
|
136
|
+
userName: users.name,
|
|
137
|
+
userEmail: users.email,
|
|
138
|
+
userImage: users.image,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Execute comment query with user join
|
|
143
|
+
*/
|
|
144
|
+
function executeCommentQuery(commentId: string) {
|
|
145
|
+
return db
|
|
146
|
+
.select(commentSelectFields)
|
|
147
|
+
.from(recordComments)
|
|
148
|
+
.leftJoin(users, eq(recordComments.userId, users.id))
|
|
149
|
+
.where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
|
|
150
|
+
.limit(1)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get comment with user metadata
|
|
155
|
+
*/
|
|
156
|
+
export function getCommentWithUser(config: {
|
|
157
|
+
readonly session: Readonly<Session>
|
|
158
|
+
readonly commentId: string
|
|
159
|
+
}): Effect.Effect<
|
|
160
|
+
| {
|
|
161
|
+
readonly id: string
|
|
162
|
+
readonly tableId: string
|
|
163
|
+
readonly recordId: string
|
|
164
|
+
readonly userId: string
|
|
165
|
+
readonly content: string
|
|
166
|
+
readonly createdAt: Date
|
|
167
|
+
readonly updatedAt: Date
|
|
168
|
+
readonly user: UserMetadataWithOptionalImage | undefined
|
|
169
|
+
}
|
|
170
|
+
| undefined,
|
|
171
|
+
SessionContextError
|
|
172
|
+
> {
|
|
173
|
+
const { commentId } = config
|
|
174
|
+
return Effect.gen(function* () {
|
|
175
|
+
const result = yield* Effect.tryPromise<Array<CommentQueryRow>, SessionContextError>({
|
|
176
|
+
try: () => executeCommentQuery(commentId),
|
|
177
|
+
catch: (error) => new SessionContextError('Failed to get comment', error),
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
if (result.length === 0 || !result[0]) {
|
|
181
|
+
return undefined
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const row = result[0]
|
|
185
|
+
return transformCommentRow({
|
|
186
|
+
...row,
|
|
187
|
+
userName: row.userName ?? undefined,
|
|
188
|
+
userEmail: row.userEmail ?? undefined,
|
|
189
|
+
userImage: row.userImage ?? undefined,
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Delete (soft delete) a comment
|
|
196
|
+
*/
|
|
197
|
+
export function deleteComment(config: {
|
|
198
|
+
readonly session: Readonly<Session>
|
|
199
|
+
readonly commentId: string
|
|
200
|
+
}): Effect.Effect<void, SessionContextError> {
|
|
201
|
+
const { commentId } = config
|
|
202
|
+
return Effect.tryPromise({
|
|
203
|
+
try: async () => {
|
|
204
|
+
const now = new Date()
|
|
205
|
+
|
|
206
|
+
const result = await db
|
|
207
|
+
.update(recordComments)
|
|
208
|
+
.set({ deletedAt: now, updatedAt: now })
|
|
209
|
+
.where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
|
|
210
|
+
.returning()
|
|
211
|
+
|
|
212
|
+
if (result.length === 0) {
|
|
213
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required inside Effect.tryPromise for error propagation
|
|
214
|
+
throw new SessionContextError('Comment not found')
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
catch: wrapDatabaseError('Failed to delete comment'),
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get comment by ID for authorization check
|
|
223
|
+
*/
|
|
224
|
+
export function getCommentForAuth(config: {
|
|
225
|
+
readonly session: Readonly<Session>
|
|
226
|
+
readonly commentId: string
|
|
227
|
+
}): Effect.Effect<
|
|
228
|
+
| {
|
|
229
|
+
readonly id: string
|
|
230
|
+
readonly userId: string
|
|
231
|
+
readonly recordId: string
|
|
232
|
+
readonly tableId: string
|
|
233
|
+
}
|
|
234
|
+
| undefined,
|
|
235
|
+
SessionContextError
|
|
236
|
+
> {
|
|
237
|
+
const { commentId } = config
|
|
238
|
+
return Effect.gen(function* () {
|
|
239
|
+
const result = yield* Effect.tryPromise({
|
|
240
|
+
try: () =>
|
|
241
|
+
db
|
|
242
|
+
.select({
|
|
243
|
+
id: recordComments.id,
|
|
244
|
+
userId: recordComments.userId,
|
|
245
|
+
recordId: recordComments.recordId,
|
|
246
|
+
tableId: recordComments.tableId,
|
|
247
|
+
})
|
|
248
|
+
.from(recordComments)
|
|
249
|
+
.where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
|
|
250
|
+
.limit(1),
|
|
251
|
+
catch: (error) => new SessionContextError('Failed to get comment', error),
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
if (result.length === 0 || !result[0]) {
|
|
255
|
+
return undefined
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return result[0]
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Build base comments query with user join
|
|
264
|
+
*/
|
|
265
|
+
function buildCommentsQuery(recordId: string) {
|
|
266
|
+
return db
|
|
267
|
+
.select(commentSelectFields)
|
|
268
|
+
.from(recordComments)
|
|
269
|
+
.leftJoin(users, eq(recordComments.userId, users.id))
|
|
270
|
+
.where(and(eq(recordComments.recordId, recordId), isNull(recordComments.deletedAt)))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Execute list comments query with sorting and pagination
|
|
275
|
+
*/
|
|
276
|
+
function executeListCommentsQuery(
|
|
277
|
+
recordId: string,
|
|
278
|
+
options?: {
|
|
279
|
+
readonly limit?: number
|
|
280
|
+
readonly offset?: number
|
|
281
|
+
readonly sortOrder?: 'asc' | 'desc'
|
|
282
|
+
}
|
|
283
|
+
) {
|
|
284
|
+
const query = buildCommentsQuery(recordId)
|
|
285
|
+
|
|
286
|
+
// Apply sorting (default: DESC for newest first)
|
|
287
|
+
const sortedQuery =
|
|
288
|
+
options?.sortOrder === 'asc'
|
|
289
|
+
? query.orderBy(asc(recordComments.createdAt), asc(recordComments.id))
|
|
290
|
+
: query.orderBy(desc(recordComments.createdAt), desc(recordComments.id))
|
|
291
|
+
|
|
292
|
+
// Apply pagination
|
|
293
|
+
if (options?.limit !== undefined) {
|
|
294
|
+
const paginatedQuery = sortedQuery.limit(options.limit)
|
|
295
|
+
return options.offset !== undefined ? paginatedQuery.offset(options.offset) : paginatedQuery
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return sortedQuery
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* List all comments for a record
|
|
303
|
+
*/
|
|
304
|
+
export function listComments(config: {
|
|
305
|
+
readonly session: Readonly<Session>
|
|
306
|
+
readonly recordId: string
|
|
307
|
+
readonly limit?: number
|
|
308
|
+
readonly offset?: number
|
|
309
|
+
readonly sortOrder?: 'asc' | 'desc'
|
|
310
|
+
}): Effect.Effect<
|
|
311
|
+
readonly {
|
|
312
|
+
readonly id: string
|
|
313
|
+
readonly tableId: string
|
|
314
|
+
readonly recordId: string
|
|
315
|
+
readonly userId: string
|
|
316
|
+
readonly content: string
|
|
317
|
+
readonly createdAt: Date
|
|
318
|
+
readonly updatedAt: Date
|
|
319
|
+
readonly user: UserMetadataWithOptionalImage | undefined
|
|
320
|
+
}[],
|
|
321
|
+
SessionContextError
|
|
322
|
+
> {
|
|
323
|
+
const { recordId, limit, offset, sortOrder } = config
|
|
324
|
+
return Effect.gen(function* () {
|
|
325
|
+
const result = yield* Effect.tryPromise<Array<CommentQueryRow>, SessionContextError>({
|
|
326
|
+
try: () => executeListCommentsQuery(recordId, { limit, offset, sortOrder }),
|
|
327
|
+
catch: (error) => new SessionContextError('Failed to list comments', error),
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
return result.map((row) =>
|
|
331
|
+
transformCommentRow({
|
|
332
|
+
...row,
|
|
333
|
+
userName: row.userName ?? undefined,
|
|
334
|
+
userEmail: row.userEmail ?? undefined,
|
|
335
|
+
userImage: row.userImage ?? undefined,
|
|
336
|
+
})
|
|
337
|
+
)
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get total count of comments for a record
|
|
343
|
+
*/
|
|
344
|
+
export function getCommentsCount(config: {
|
|
345
|
+
readonly session: Readonly<Session>
|
|
346
|
+
readonly recordId: string
|
|
347
|
+
}): Effect.Effect<number, SessionContextError> {
|
|
348
|
+
const { recordId } = config
|
|
349
|
+
return Effect.gen(function* () {
|
|
350
|
+
const result = yield* Effect.tryPromise<Array<{ count: number }>, SessionContextError>({
|
|
351
|
+
try: () =>
|
|
352
|
+
db
|
|
353
|
+
.select({ count: sql<number>`count(*)::int` })
|
|
354
|
+
.from(recordComments)
|
|
355
|
+
.where(and(eq(recordComments.recordId, recordId), isNull(recordComments.deletedAt))),
|
|
356
|
+
catch: (error) => new SessionContextError('Failed to count comments', error),
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
return result[0]?.count ?? 0
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Update a comment's content
|
|
365
|
+
*/
|
|
366
|
+
export function updateComment(config: {
|
|
367
|
+
readonly session: Readonly<Session>
|
|
368
|
+
readonly commentId: string
|
|
369
|
+
readonly content: string
|
|
370
|
+
}): Effect.Effect<
|
|
371
|
+
{
|
|
372
|
+
readonly id: string
|
|
373
|
+
readonly tableId: string
|
|
374
|
+
readonly recordId: string
|
|
375
|
+
readonly userId: string
|
|
376
|
+
readonly content: string
|
|
377
|
+
readonly createdAt: Date
|
|
378
|
+
readonly updatedAt: Date
|
|
379
|
+
},
|
|
380
|
+
SessionContextError
|
|
381
|
+
> {
|
|
382
|
+
const { commentId, content } = config
|
|
383
|
+
return Effect.tryPromise({
|
|
384
|
+
try: async () => {
|
|
385
|
+
const now = new Date()
|
|
386
|
+
|
|
387
|
+
const result = await db
|
|
388
|
+
.update(recordComments)
|
|
389
|
+
.set({ content, updatedAt: now })
|
|
390
|
+
.where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
|
|
391
|
+
.returning()
|
|
392
|
+
|
|
393
|
+
if (result.length === 0) {
|
|
394
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required inside Effect.tryPromise for error propagation
|
|
395
|
+
throw new SessionContextError('Comment not found')
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const comment = result[0]!
|
|
399
|
+
return {
|
|
400
|
+
id: comment.id,
|
|
401
|
+
tableId: comment.tableId,
|
|
402
|
+
recordId: comment.recordId,
|
|
403
|
+
userId: comment.userId,
|
|
404
|
+
content: comment.content,
|
|
405
|
+
createdAt: comment.createdAt,
|
|
406
|
+
updatedAt: comment.updatedAt,
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
catch: (error) =>
|
|
410
|
+
error instanceof SessionContextError
|
|
411
|
+
? error
|
|
412
|
+
: new SessionContextError('Failed to update comment', error),
|
|
413
|
+
})
|
|
414
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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, eq } 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 type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build SQL query to check record existence with optional deleted_at filter and owner_id check
|
|
17
|
+
* Admins bypass owner_id filtering to access all records
|
|
18
|
+
*
|
|
19
|
+
* owner_id filtering logic:
|
|
20
|
+
* - Records with owner_id = NULL are accessible to all users (unowned records)
|
|
21
|
+
* - Records with owner_id = <userId> are accessible only to that user (owned records)
|
|
22
|
+
* - Admins can access all records regardless of owner_id
|
|
23
|
+
*/
|
|
24
|
+
function buildRecordCheckQuery(params: {
|
|
25
|
+
readonly tableName: string
|
|
26
|
+
readonly recordId: string
|
|
27
|
+
readonly userId: string
|
|
28
|
+
readonly hasDeletedAt: boolean
|
|
29
|
+
readonly hasOwnerId: boolean
|
|
30
|
+
readonly isAdmin: boolean
|
|
31
|
+
}) {
|
|
32
|
+
const { tableName, recordId, userId, hasDeletedAt, hasOwnerId, isAdmin } = params
|
|
33
|
+
// Admins bypass owner_id filtering
|
|
34
|
+
const shouldFilterOwner = hasOwnerId && !isAdmin
|
|
35
|
+
|
|
36
|
+
if (hasDeletedAt && shouldFilterOwner) {
|
|
37
|
+
return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId} AND deleted_at IS NULL AND (owner_id = ${userId} OR owner_id IS NULL)`
|
|
38
|
+
}
|
|
39
|
+
if (hasDeletedAt) {
|
|
40
|
+
return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId} AND deleted_at IS NULL`
|
|
41
|
+
}
|
|
42
|
+
if (shouldFilterOwner) {
|
|
43
|
+
return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId} AND (owner_id = ${userId} OR owner_id IS NULL)`
|
|
44
|
+
}
|
|
45
|
+
return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a record exists in the given table (with owner_id isolation for non-admins)
|
|
50
|
+
* Admins can access all records regardless of owner_id
|
|
51
|
+
*/
|
|
52
|
+
export function checkRecordExists(config: {
|
|
53
|
+
readonly session: Readonly<Session>
|
|
54
|
+
readonly tableName: string
|
|
55
|
+
readonly recordId: string
|
|
56
|
+
readonly isAdmin?: boolean
|
|
57
|
+
}): Effect.Effect<boolean, SessionContextError> {
|
|
58
|
+
const { session, tableName, recordId, isAdmin = false } = config
|
|
59
|
+
return Effect.gen(function* () {
|
|
60
|
+
// Check if table has deleted_at column
|
|
61
|
+
const columnsResult = yield* Effect.tryPromise({
|
|
62
|
+
try: () =>
|
|
63
|
+
db.execute(
|
|
64
|
+
sql`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ${tableName} AND column_name IN ('deleted_at', 'owner_id')`
|
|
65
|
+
),
|
|
66
|
+
catch: (error) => new SessionContextError('Failed to check table columns', error),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const columns = columnsResult as readonly Record<string, unknown>[]
|
|
70
|
+
const hasDeletedAt = columns.some((row) => row.column_name === 'deleted_at')
|
|
71
|
+
const hasOwnerId = columns.some((row) => row.column_name === 'owner_id')
|
|
72
|
+
|
|
73
|
+
// Check if record exists (with owner_id check for multi-tenancy isolation, bypassed for admins)
|
|
74
|
+
const query = buildRecordCheckQuery({
|
|
75
|
+
tableName,
|
|
76
|
+
recordId,
|
|
77
|
+
userId: session.userId,
|
|
78
|
+
hasDeletedAt,
|
|
79
|
+
hasOwnerId,
|
|
80
|
+
isAdmin,
|
|
81
|
+
})
|
|
82
|
+
const result = yield* Effect.tryPromise({
|
|
83
|
+
try: () => db.execute(query),
|
|
84
|
+
catch: (error) => new SessionContextError('Failed to check record existence', error),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return result.length > 0
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get user by ID
|
|
93
|
+
*/
|
|
94
|
+
export function getUserById(config: {
|
|
95
|
+
readonly session: Readonly<Session>
|
|
96
|
+
readonly userId: string
|
|
97
|
+
}): Effect.Effect<
|
|
98
|
+
| {
|
|
99
|
+
readonly id: string
|
|
100
|
+
readonly role: string | undefined
|
|
101
|
+
}
|
|
102
|
+
| undefined,
|
|
103
|
+
SessionContextError
|
|
104
|
+
> {
|
|
105
|
+
const { userId } = config
|
|
106
|
+
return Effect.gen(function* () {
|
|
107
|
+
const result = yield* Effect.tryPromise({
|
|
108
|
+
try: () =>
|
|
109
|
+
db
|
|
110
|
+
.select({ id: users.id, role: users.role })
|
|
111
|
+
.from(users)
|
|
112
|
+
.where(eq(users.id, userId))
|
|
113
|
+
.limit(1),
|
|
114
|
+
catch: (error) => new SessionContextError('Failed to get user', error),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
if (result.length === 0 || !result[0]) {
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
id: result[0].id,
|
|
123
|
+
role: result[0].role ?? undefined,
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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 type { SQL } from 'drizzle-orm'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build filter conditions for trash list query
|
|
13
|
+
*/
|
|
14
|
+
export function buildTrashFilters(
|
|
15
|
+
baseQuery: Readonly<SQL>,
|
|
16
|
+
filters?: readonly {
|
|
17
|
+
readonly field: string
|
|
18
|
+
readonly operator: string
|
|
19
|
+
readonly value: unknown
|
|
20
|
+
}[]
|
|
21
|
+
): Readonly<SQL> {
|
|
22
|
+
return (filters ?? []).reduce((query, condition) => {
|
|
23
|
+
const { field, operator, value } = condition
|
|
24
|
+
const fieldIdentifier = sql.identifier(field)
|
|
25
|
+
|
|
26
|
+
switch (operator) {
|
|
27
|
+
case 'equals':
|
|
28
|
+
return sql`${query} AND ${fieldIdentifier} = ${value}`
|
|
29
|
+
case 'notEquals':
|
|
30
|
+
return sql`${query} AND ${fieldIdentifier} != ${value}`
|
|
31
|
+
case 'contains':
|
|
32
|
+
return sql`${query} AND ${fieldIdentifier} ILIKE ${'%' + String(value) + '%'}`
|
|
33
|
+
case 'greaterThan':
|
|
34
|
+
return sql`${query} AND ${fieldIdentifier} > ${value}`
|
|
35
|
+
case 'lessThan':
|
|
36
|
+
return sql`${query} AND ${fieldIdentifier} < ${value}`
|
|
37
|
+
case 'greaterThanOrEqual':
|
|
38
|
+
return sql`${query} AND ${fieldIdentifier} >= ${value}`
|
|
39
|
+
case 'lessThanOrEqual':
|
|
40
|
+
return sql`${query} AND ${fieldIdentifier} <= ${value}`
|
|
41
|
+
default:
|
|
42
|
+
return query
|
|
43
|
+
}
|
|
44
|
+
}, baseQuery)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Add sorting to trash list query
|
|
49
|
+
*/
|
|
50
|
+
export function addTrashSorting(query: Readonly<SQL>, sort?: string): Readonly<SQL> {
|
|
51
|
+
if (!sort) return query
|
|
52
|
+
|
|
53
|
+
const [field, order] = sort.split(':')
|
|
54
|
+
if (!field) return query
|
|
55
|
+
|
|
56
|
+
const direction = order?.toLowerCase() === 'desc' ? sql`DESC` : sql`ASC`
|
|
57
|
+
return sql`${query} ORDER BY ${sql.identifier(field)} ${direction}`
|
|
58
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { SessionContextError, ValidationError } from '@/infrastructure/database'
|
|
9
|
+
|
|
10
|
+
/* eslint-disable functional/prefer-immutable-types -- Error handler factories for Effect.tryPromise catch: returns mutable Error class instances, parameter signature fixed as (error: unknown) by Effect API */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a catch handler that preserves known error types and wraps unknowns in SessionContextError.
|
|
14
|
+
*
|
|
15
|
+
* Replaces the repeated pattern:
|
|
16
|
+
* ```
|
|
17
|
+
* catch: (error) =>
|
|
18
|
+
* error instanceof SessionContextError ? error
|
|
19
|
+
* : new SessionContextError(message, error)
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @param message - Error message for wrapping unknown errors
|
|
23
|
+
* @returns A catch handler suitable for Effect.tryPromise
|
|
24
|
+
*/
|
|
25
|
+
export function wrapDatabaseError(message: string): (error: unknown) => SessionContextError {
|
|
26
|
+
return (error: unknown): SessionContextError =>
|
|
27
|
+
error instanceof SessionContextError ? error : new SessionContextError(message, error)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a catch handler that preserves SessionContextError and ValidationError,
|
|
32
|
+
* wrapping all other errors in SessionContextError.
|
|
33
|
+
*
|
|
34
|
+
* Used in batch operations where ValidationError may propagate from inner Effect programs.
|
|
35
|
+
*
|
|
36
|
+
* @param message - Error message for wrapping unknown errors
|
|
37
|
+
* @returns A catch handler suitable for Effect.tryPromise
|
|
38
|
+
*/
|
|
39
|
+
export function wrapDatabaseErrorWithValidation(
|
|
40
|
+
message: string
|
|
41
|
+
): (error: unknown) => SessionContextError | ValidationError {
|
|
42
|
+
return (error: unknown): SessionContextError | ValidationError => {
|
|
43
|
+
if (error instanceof SessionContextError) return error
|
|
44
|
+
if (error instanceof ValidationError) return error
|
|
45
|
+
return new SessionContextError(message, error)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DrizzleTransaction } from '@/infrastructure/database'
|
|
9
|
+
import type { SQL } from 'drizzle-orm'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute a SQL query within a transaction and return typed results.
|
|
13
|
+
*
|
|
14
|
+
* Centralizes the type assertion needed because Drizzle's tx.execute()
|
|
15
|
+
* return type doesn't match Record<string, unknown>[]. By keeping the
|
|
16
|
+
* single `as unknown as` cast here, call sites remain cast-free.
|
|
17
|
+
*
|
|
18
|
+
* @param tx - Drizzle transaction
|
|
19
|
+
* @param query - SQL query (from drizzle-orm sql template tag)
|
|
20
|
+
* @returns Typed array of results
|
|
21
|
+
*/
|
|
22
|
+
export async function typedExecute<T = Record<string, unknown>>(
|
|
23
|
+
tx: Readonly<DrizzleTransaction>,
|
|
24
|
+
query: Readonly<SQL>
|
|
25
|
+
): Promise<readonly T[]> {
|
|
26
|
+
return (await tx.execute(query)) as unknown as readonly T[]
|
|
27
|
+
}
|