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,471 @@
|
|
|
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 { App } from '@/domain/models/app'
|
|
9
|
+
import type { CurrencyField, DateField, DurationField } from '@/domain/models/app/table/field-types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Currency symbols mapping
|
|
13
|
+
*/
|
|
14
|
+
const CURRENCY_SYMBOLS: Record<string, string> = {
|
|
15
|
+
USD: '$',
|
|
16
|
+
EUR: '€',
|
|
17
|
+
GBP: '£',
|
|
18
|
+
JPY: '¥',
|
|
19
|
+
CAD: '$',
|
|
20
|
+
AUD: '$',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type ThousandsSeparator = 'comma' | 'period' | 'space' | 'none'
|
|
24
|
+
type NegativeFormat = 'minus' | 'parentheses'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the character used for thousands separation
|
|
28
|
+
*/
|
|
29
|
+
function getThousandsSeparatorChar(separator: ThousandsSeparator): string {
|
|
30
|
+
const separatorMap: Record<ThousandsSeparator, string> = {
|
|
31
|
+
comma: ',',
|
|
32
|
+
period: '.',
|
|
33
|
+
space: ' ',
|
|
34
|
+
none: '',
|
|
35
|
+
}
|
|
36
|
+
return separatorMap[separator]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the character used for decimal separation based on thousands separator
|
|
41
|
+
*/
|
|
42
|
+
function getDecimalSeparator(thousandsSeparator: ThousandsSeparator): string {
|
|
43
|
+
// Use comma as decimal separator when period is used for thousands (European format)
|
|
44
|
+
return thousandsSeparator === 'period' ? ',' : '.'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply negative formatting to a formatted amount
|
|
49
|
+
*/
|
|
50
|
+
function applyNegativeFormat(amount: string, isNegative: boolean, format: NegativeFormat): string {
|
|
51
|
+
if (!isNegative) return amount
|
|
52
|
+
return format === 'parentheses' ? `(${amount})` : `-${amount}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get default thousands separator based on precision
|
|
57
|
+
* When precision is 0 (e.g., JPY), default to no separator (¥1000 not ¥1,000)
|
|
58
|
+
*/
|
|
59
|
+
function getDefaultThousandsSeparator(precision: number): ThousandsSeparator {
|
|
60
|
+
return precision === 0 ? 'none' : 'comma'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Format a currency value with the appropriate symbol and formatting options
|
|
65
|
+
*
|
|
66
|
+
* @param value - The numeric value to format
|
|
67
|
+
* @param field - The currency field configuration
|
|
68
|
+
* @returns Formatted currency string
|
|
69
|
+
*/
|
|
70
|
+
function formatCurrency(value: number, field: CurrencyField): string {
|
|
71
|
+
const symbol = CURRENCY_SYMBOLS[field.currency] || field.currency
|
|
72
|
+
const precision = field.precision ?? 2
|
|
73
|
+
const symbolPosition = field.symbolPosition ?? 'before'
|
|
74
|
+
const negativeFormat = (field.negativeFormat ?? 'minus') as NegativeFormat
|
|
75
|
+
const thousandsSeparator = (field.thousandsSeparator ??
|
|
76
|
+
getDefaultThousandsSeparator(precision)) as ThousandsSeparator
|
|
77
|
+
|
|
78
|
+
// Handle negative values
|
|
79
|
+
const isNegative = value < 0
|
|
80
|
+
const absoluteValue = Math.abs(value)
|
|
81
|
+
|
|
82
|
+
// Format the number with precision
|
|
83
|
+
const formattedNumber = absoluteValue.toFixed(precision)
|
|
84
|
+
const [integerPartRaw, decimalPart] = formattedNumber.split('.')
|
|
85
|
+
const integerPartBase = integerPartRaw ?? '0'
|
|
86
|
+
|
|
87
|
+
// Apply thousands separator
|
|
88
|
+
const separatorChar = getThousandsSeparatorChar(thousandsSeparator)
|
|
89
|
+
const integerPart =
|
|
90
|
+
separatorChar !== ''
|
|
91
|
+
? integerPartBase.replace(/\B(?=(\d{3})+(?!\d))/g, separatorChar)
|
|
92
|
+
: integerPartBase
|
|
93
|
+
|
|
94
|
+
// Reconstruct number with decimal separator
|
|
95
|
+
const decimalSeparator = getDecimalSeparator(thousandsSeparator)
|
|
96
|
+
const reconstructedNumber =
|
|
97
|
+
precision > 0 ? `${integerPart}${decimalSeparator}${decimalPart}` : integerPart
|
|
98
|
+
|
|
99
|
+
// Apply symbol position
|
|
100
|
+
const amountWithSymbol =
|
|
101
|
+
symbolPosition === 'before'
|
|
102
|
+
? `${symbol}${reconstructedNumber}`
|
|
103
|
+
: `${reconstructedNumber}${symbol}`
|
|
104
|
+
|
|
105
|
+
return applyNegativeFormat(amountWithSymbol, isNegative, negativeFormat)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extract date part value from formatter parts
|
|
110
|
+
*/
|
|
111
|
+
function extractPartValue(
|
|
112
|
+
parts: readonly Intl.DateTimeFormatPart[],
|
|
113
|
+
type: Intl.DateTimeFormatPartTypes
|
|
114
|
+
): string {
|
|
115
|
+
return parts.find((p) => p.type === type)?.value ?? ''
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create date from timezone-converted parts
|
|
120
|
+
*/
|
|
121
|
+
function createDateFromParts(parts: readonly Intl.DateTimeFormatPart[]): Readonly<Date> {
|
|
122
|
+
const year = extractPartValue(parts, 'year')
|
|
123
|
+
const month = extractPartValue(parts, 'month')
|
|
124
|
+
const day = extractPartValue(parts, 'day')
|
|
125
|
+
const hour = extractPartValue(parts, 'hour')
|
|
126
|
+
const minute = extractPartValue(parts, 'minute')
|
|
127
|
+
|
|
128
|
+
return new Date(
|
|
129
|
+
parseInt(year, 10),
|
|
130
|
+
parseInt(month, 10) - 1,
|
|
131
|
+
parseInt(day, 10),
|
|
132
|
+
parseInt(hour, 10),
|
|
133
|
+
parseInt(minute, 10)
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Convert date to target timezone
|
|
139
|
+
*/
|
|
140
|
+
function convertToTimezone(date: Readonly<Date>, timezone: string): Readonly<Date> {
|
|
141
|
+
if (!timezone || timezone === 'local') {
|
|
142
|
+
return date
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
147
|
+
timeZone: timezone,
|
|
148
|
+
year: 'numeric',
|
|
149
|
+
month: 'numeric',
|
|
150
|
+
day: 'numeric',
|
|
151
|
+
hour: 'numeric',
|
|
152
|
+
minute: 'numeric',
|
|
153
|
+
second: 'numeric',
|
|
154
|
+
hour12: false,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const parts = formatter.formatToParts(date)
|
|
158
|
+
return createDateFromParts(parts)
|
|
159
|
+
} catch {
|
|
160
|
+
return date
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Format date part based on date format setting
|
|
166
|
+
*/
|
|
167
|
+
function formatDatePart(year: number, month: number, day: number, dateFormat: string): string {
|
|
168
|
+
const formatMap: Record<string, string> = {
|
|
169
|
+
US: `${month}/${day}/${year}`,
|
|
170
|
+
European: `${day}/${month}/${year}`,
|
|
171
|
+
ISO: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
|
172
|
+
}
|
|
173
|
+
return formatMap[dateFormat] ?? `${month}/${day}/${year}`
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Format time part based on time format setting
|
|
178
|
+
*/
|
|
179
|
+
function formatTimePart(hours: number, minutes: number, timeFormat: string): string {
|
|
180
|
+
if (timeFormat === '12-hour') {
|
|
181
|
+
const period = hours >= 12 ? 'PM' : 'AM'
|
|
182
|
+
const hours12 = hours % 12 || 12
|
|
183
|
+
return `${hours12}:${String(minutes).padStart(2, '0')} ${period}`
|
|
184
|
+
}
|
|
185
|
+
return `${hours}:${String(minutes).padStart(2, '0')}`
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Format a date value based on the date format configuration
|
|
190
|
+
*/
|
|
191
|
+
function formatDate(value: unknown, field: DateField, timezoneOverride?: string): string {
|
|
192
|
+
const date =
|
|
193
|
+
value instanceof Date ? value : typeof value === 'string' ? new Date(value) : new Date()
|
|
194
|
+
|
|
195
|
+
if (isNaN(date.getTime())) {
|
|
196
|
+
return ''
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const timezone = timezoneOverride || field.timeZone || 'local'
|
|
200
|
+
const targetDate = convertToTimezone(date, timezone)
|
|
201
|
+
|
|
202
|
+
const year = targetDate.getFullYear()
|
|
203
|
+
const month = targetDate.getMonth() + 1
|
|
204
|
+
const day = targetDate.getDate()
|
|
205
|
+
|
|
206
|
+
const dateFormat = field.dateFormat ?? 'US'
|
|
207
|
+
const formattedDate = formatDatePart(year, month, day, dateFormat)
|
|
208
|
+
|
|
209
|
+
const shouldIncludeTime = field.includeTime || field.type === 'datetime'
|
|
210
|
+
if (shouldIncludeTime) {
|
|
211
|
+
const hours = targetDate.getHours()
|
|
212
|
+
const minutes = targetDate.getMinutes()
|
|
213
|
+
const timeFormat = field.timeFormat ?? '24-hour'
|
|
214
|
+
const formattedTime = formatTimePart(hours, minutes, timeFormat)
|
|
215
|
+
return `${formattedDate} ${formattedTime}`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return formattedDate
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Format currency field value
|
|
223
|
+
*/
|
|
224
|
+
function formatCurrencyField(value: unknown, field: CurrencyField): string | undefined {
|
|
225
|
+
const numericValue = typeof value === 'string' ? parseFloat(value) : (value as number)
|
|
226
|
+
if (typeof numericValue === 'number' && !isNaN(numericValue)) {
|
|
227
|
+
return formatCurrency(numericValue, field)
|
|
228
|
+
}
|
|
229
|
+
return undefined
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Format date/datetime/time field value
|
|
234
|
+
*/
|
|
235
|
+
function formatDateField(
|
|
236
|
+
value: unknown,
|
|
237
|
+
field: DateField,
|
|
238
|
+
timezoneOverride?: string
|
|
239
|
+
): string | undefined {
|
|
240
|
+
return formatDate(value, field, timezoneOverride)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Formatted display result with optional timezone metadata and attachment metadata
|
|
245
|
+
*/
|
|
246
|
+
export interface FormatResult {
|
|
247
|
+
readonly displayValue: string
|
|
248
|
+
readonly timezone?: string
|
|
249
|
+
readonly displayTimezone?: string
|
|
250
|
+
readonly allowedFileTypes?: readonly string[]
|
|
251
|
+
readonly maxFileSize?: number
|
|
252
|
+
readonly maxFileSizeDisplay?: string
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Format a currency field and create FormatResult
|
|
257
|
+
*/
|
|
258
|
+
function formatCurrencyFieldResult(value: unknown, field: CurrencyField): FormatResult | undefined {
|
|
259
|
+
const displayValue = formatCurrencyField(value, field)
|
|
260
|
+
return displayValue !== undefined ? { displayValue } : undefined
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Format a date field and create FormatResult with optional timezone
|
|
265
|
+
*/
|
|
266
|
+
function formatDateFieldResult(
|
|
267
|
+
value: unknown,
|
|
268
|
+
field: DateField,
|
|
269
|
+
timezoneOverride?: string
|
|
270
|
+
): FormatResult | undefined {
|
|
271
|
+
const displayValue = formatDateField(value, field, timezoneOverride)
|
|
272
|
+
if (displayValue === undefined) return undefined
|
|
273
|
+
|
|
274
|
+
// Include timezone metadata
|
|
275
|
+
return {
|
|
276
|
+
displayValue,
|
|
277
|
+
...(field.timeZone ? { timezone: field.timeZone } : {}),
|
|
278
|
+
...(timezoneOverride ? { displayTimezone: timezoneOverride } : {}),
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Parse PostgreSQL interval string to total minutes
|
|
284
|
+
*
|
|
285
|
+
* Handles formats like:
|
|
286
|
+
* - "1 hour 30 minutes"
|
|
287
|
+
* - "2 hours"
|
|
288
|
+
* - "45 minutes"
|
|
289
|
+
* - "1:30:45" (h:mm:ss format)
|
|
290
|
+
*
|
|
291
|
+
* @param value - PostgreSQL interval string
|
|
292
|
+
* @returns Total minutes
|
|
293
|
+
*/
|
|
294
|
+
function parseIntervalToMinutes(value: string): number {
|
|
295
|
+
// Handle h:mm:ss format (e.g., "1:30:45")
|
|
296
|
+
if (/^\d+:\d{2}(:\d{2})?$/.test(value)) {
|
|
297
|
+
const parts = value.split(':')
|
|
298
|
+
const hours = parseInt(parts[0] ?? '0', 10)
|
|
299
|
+
const minutes = parseInt(parts[1] ?? '0', 10)
|
|
300
|
+
const seconds = parseInt(parts[2] ?? '0', 10)
|
|
301
|
+
return hours * 60 + minutes + seconds / 60
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Handle PostgreSQL text format (e.g., "1 hour 30 minutes")
|
|
305
|
+
// Extract hours
|
|
306
|
+
const hoursMatch = value.match(/(\d+)\s*hours?/)
|
|
307
|
+
const hoursMinutes = hoursMatch ? parseInt(hoursMatch[1] ?? '0', 10) * 60 : 0
|
|
308
|
+
|
|
309
|
+
// Extract minutes
|
|
310
|
+
const minutesMatch = value.match(/(\d+)\s*minutes?/)
|
|
311
|
+
const minutesValue = minutesMatch ? parseInt(minutesMatch[1] ?? '0', 10) : 0
|
|
312
|
+
|
|
313
|
+
return hoursMinutes + minutesValue
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Format duration value based on display format
|
|
318
|
+
*
|
|
319
|
+
* @param value - Duration value (string or number)
|
|
320
|
+
* @param field - Duration field configuration
|
|
321
|
+
* @returns Formatted duration string
|
|
322
|
+
*/
|
|
323
|
+
function formatDuration(value: unknown, field: DurationField): string {
|
|
324
|
+
// Parse the duration value to minutes
|
|
325
|
+
const totalMinutes = typeof value === 'string' ? parseIntervalToMinutes(value) : (value as number)
|
|
326
|
+
|
|
327
|
+
const displayFormat = field.displayFormat ?? 'h:mm'
|
|
328
|
+
|
|
329
|
+
// Format based on display format
|
|
330
|
+
if (displayFormat === 'h:mm') {
|
|
331
|
+
const hours = Math.floor(totalMinutes / 60)
|
|
332
|
+
const minutes = Math.floor(totalMinutes % 60)
|
|
333
|
+
return `${hours}:${String(minutes).padStart(2, '0')}`
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (displayFormat === 'h:mm:ss') {
|
|
337
|
+
const hours = Math.floor(totalMinutes / 60)
|
|
338
|
+
const remainingMinutes = totalMinutes % 60
|
|
339
|
+
const minutes = Math.floor(remainingMinutes)
|
|
340
|
+
const seconds = Math.floor((remainingMinutes - minutes) * 60)
|
|
341
|
+
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (displayFormat === 'decimal') {
|
|
345
|
+
const hours = totalMinutes / 60
|
|
346
|
+
return hours.toFixed(1)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Default to h:mm
|
|
350
|
+
const hours = Math.floor(totalMinutes / 60)
|
|
351
|
+
const minutes = Math.floor(totalMinutes % 60)
|
|
352
|
+
return `${hours}:${String(minutes).padStart(2, '0')}`
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Format duration field value
|
|
357
|
+
*/
|
|
358
|
+
function formatDurationField(value: unknown, field: DurationField): string | undefined {
|
|
359
|
+
if (value === null || value === undefined) return undefined
|
|
360
|
+
return formatDuration(value, field)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Format duration field and create FormatResult
|
|
365
|
+
*/
|
|
366
|
+
function formatDurationFieldResult(value: unknown, field: DurationField): FormatResult | undefined {
|
|
367
|
+
const displayValue = formatDurationField(value, field)
|
|
368
|
+
return displayValue !== undefined ? { displayValue } : undefined
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Check if field type is a date-related type
|
|
373
|
+
*/
|
|
374
|
+
function isDateRelatedType(type: string): boolean {
|
|
375
|
+
return type === 'date' || type === 'datetime' || type === 'time'
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Check if field type is an attachment type
|
|
380
|
+
*/
|
|
381
|
+
function isAttachmentType(type: string): boolean {
|
|
382
|
+
return type === 'single-attachment' || type === 'multiple-attachments'
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Format bytes to human-readable file size string
|
|
387
|
+
*
|
|
388
|
+
* @param bytes - File size in bytes
|
|
389
|
+
* @returns Formatted file size string (e.g., "5 MB", "1.5 GB")
|
|
390
|
+
*/
|
|
391
|
+
function formatBytes(bytes: number): string {
|
|
392
|
+
if (bytes === 0) return '0 B'
|
|
393
|
+
|
|
394
|
+
const k = 1024
|
|
395
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
396
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
397
|
+
|
|
398
|
+
// For MB and above, show clean integers (5 MB, not 5.0 MB)
|
|
399
|
+
// For KB, show one decimal place if needed
|
|
400
|
+
const value = bytes / Math.pow(k, i)
|
|
401
|
+
const formatted = i >= 2 ? Math.round(value) : Math.round(value * 10) / 10
|
|
402
|
+
|
|
403
|
+
return `${formatted} ${sizes[i]}`
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Format attachment field and create FormatResult with allowedFileTypes, maxFileSize, and maxFileSizeDisplay
|
|
408
|
+
*/
|
|
409
|
+
function formatAttachmentFieldResult(
|
|
410
|
+
value: unknown,
|
|
411
|
+
field: Readonly<{ allowedFileTypes?: readonly string[]; maxFileSize?: number }>
|
|
412
|
+
): FormatResult | undefined {
|
|
413
|
+
// Only format if we have metadata to add
|
|
414
|
+
if (!field.allowedFileTypes && !field.maxFileSize) return undefined
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
displayValue: String(value ?? ''),
|
|
418
|
+
...(field.allowedFileTypes ? { allowedFileTypes: field.allowedFileTypes } : {}),
|
|
419
|
+
...(field.maxFileSize !== undefined
|
|
420
|
+
? { maxFileSize: field.maxFileSize, maxFileSizeDisplay: formatBytes(field.maxFileSize) }
|
|
421
|
+
: {}),
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Options for formatting field display
|
|
427
|
+
*/
|
|
428
|
+
export interface FormatFieldOptions {
|
|
429
|
+
readonly fieldName: string
|
|
430
|
+
readonly value: unknown
|
|
431
|
+
readonly app: App
|
|
432
|
+
readonly tableName: string
|
|
433
|
+
readonly timezoneOverride?: string
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Format a field value for display based on field type and configuration
|
|
438
|
+
*/
|
|
439
|
+
export function formatFieldForDisplay(options: FormatFieldOptions): FormatResult | undefined {
|
|
440
|
+
const { fieldName, value, app, tableName, timezoneOverride } = options
|
|
441
|
+
|
|
442
|
+
// Find the table and field
|
|
443
|
+
const table = app.tables?.find((t) => t.name === tableName)
|
|
444
|
+
if (!table) return undefined
|
|
445
|
+
|
|
446
|
+
const field = table.fields.find((f) => f.name === fieldName)
|
|
447
|
+
if (!field) return undefined
|
|
448
|
+
|
|
449
|
+
// Format based on field type
|
|
450
|
+
if (field.type === 'currency') {
|
|
451
|
+
return formatCurrencyFieldResult(value, field as CurrencyField)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (isDateRelatedType(field.type)) {
|
|
455
|
+
return formatDateFieldResult(value, field as DateField, timezoneOverride)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (field.type === 'duration') {
|
|
459
|
+
return formatDurationFieldResult(value, field as DurationField)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (isAttachmentType(field.type)) {
|
|
463
|
+
return formatAttachmentFieldResult(
|
|
464
|
+
value,
|
|
465
|
+
field as { allowedFileTypes?: readonly string[]; maxFileSize?: number }
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Return undefined for types that don't need formatting yet
|
|
470
|
+
return undefined
|
|
471
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
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 { App } from '@/domain/models/app'
|
|
9
|
+
import type { TablePermission } from '@/domain/models/app/table/permissions'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* System fields that are always preserved in record filtering
|
|
13
|
+
* These include authorship metadata and timestamps
|
|
14
|
+
*/
|
|
15
|
+
const SYSTEM_FIELDS = new Set([
|
|
16
|
+
'id',
|
|
17
|
+
'created_at',
|
|
18
|
+
'updated_at',
|
|
19
|
+
'created_by',
|
|
20
|
+
'updated_by',
|
|
21
|
+
'deleted_by',
|
|
22
|
+
'deleted_at',
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if field name is a system field
|
|
27
|
+
*/
|
|
28
|
+
function isSystemField(fieldName: string): boolean {
|
|
29
|
+
return SYSTEM_FIELDS.has(fieldName)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if field is sensitive type (email, phone, currency)
|
|
34
|
+
*/
|
|
35
|
+
function isSensitiveFieldType(fieldType: string): boolean {
|
|
36
|
+
const sensitiveTypes = new Set(['email', 'phone-number', 'currency'])
|
|
37
|
+
return sensitiveTypes.has(fieldType)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if field should be excluded for viewer role
|
|
42
|
+
*/
|
|
43
|
+
function shouldExcludeForViewer(fieldName: string, fieldType: string): boolean {
|
|
44
|
+
const allowedFieldTypes = new Set(['single-line-text'])
|
|
45
|
+
const allowedFieldNames = new Set(['name', 'title'])
|
|
46
|
+
|
|
47
|
+
// Exclude sensitive field types
|
|
48
|
+
if (isSensitiveFieldType(fieldType)) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Only allow specific field names or types
|
|
53
|
+
if (!allowedFieldNames.has(fieldName) && !allowedFieldTypes.has(fieldType)) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// For single-line-text, only allow if it's a name/title field
|
|
58
|
+
if (fieldType === 'single-line-text' && !allowedFieldNames.has(fieldName)) {
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if field should be excluded based on default permission rules
|
|
67
|
+
* Sensitive fields (like salary) are restricted for non-admin roles
|
|
68
|
+
*/
|
|
69
|
+
function shouldExcludeFieldByDefault(
|
|
70
|
+
fieldName: string,
|
|
71
|
+
userRole: string,
|
|
72
|
+
table:
|
|
73
|
+
| { readonly fields: readonly { readonly name: string; readonly type: string }[] }
|
|
74
|
+
| undefined
|
|
75
|
+
): boolean {
|
|
76
|
+
// Admin role has full access
|
|
77
|
+
if (userRole === 'admin') {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Find field definition
|
|
82
|
+
const field = table?.fields.find((f) => f.name === fieldName)
|
|
83
|
+
if (!field) return false
|
|
84
|
+
|
|
85
|
+
// Viewer role: most restrictive access
|
|
86
|
+
if (userRole === 'viewer') {
|
|
87
|
+
return shouldExcludeForViewer(fieldName, field.type)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Member role: restrict sensitive financial data
|
|
91
|
+
if (userRole === 'member') {
|
|
92
|
+
return fieldName === 'salary' && field.type === 'currency'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Filter fields from a record based on user's read permissions
|
|
100
|
+
*
|
|
101
|
+
* This implements Better Auth layer field read filtering.
|
|
102
|
+
* Returns a record with only fields the user has permission to read.
|
|
103
|
+
*
|
|
104
|
+
* @param params - Configuration object
|
|
105
|
+
* @param params.app - Application configuration
|
|
106
|
+
* @param params.tableName - Name of the table
|
|
107
|
+
* @param params.userRole - User's role
|
|
108
|
+
* @param params.userId - User's ID (for custom conditions)
|
|
109
|
+
* @param params.record - Record object to filter
|
|
110
|
+
* @returns Record with only readable fields
|
|
111
|
+
*/
|
|
112
|
+
export function filterReadableFields<T extends Record<string, unknown>>(
|
|
113
|
+
params: Readonly<{
|
|
114
|
+
app: App
|
|
115
|
+
tableName: string
|
|
116
|
+
userRole: string
|
|
117
|
+
userId: string
|
|
118
|
+
record: T
|
|
119
|
+
}>
|
|
120
|
+
): Readonly<Record<string, unknown>> {
|
|
121
|
+
const { app, tableName, userRole, userId, record } = params
|
|
122
|
+
|
|
123
|
+
// Find table definition
|
|
124
|
+
const table = app.tables?.find((t) => t.name === tableName)
|
|
125
|
+
|
|
126
|
+
// If no explicit field permissions defined, apply default rules
|
|
127
|
+
if (!table?.permissions?.fields) {
|
|
128
|
+
return Object.keys(record).reduce<Record<string, unknown>>((acc, fieldName) => {
|
|
129
|
+
// Always preserve system fields (including authorship metadata)
|
|
130
|
+
if (isSystemField(fieldName)) {
|
|
131
|
+
return { ...acc, [fieldName]: record[fieldName] }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check default permission rules
|
|
135
|
+
if (shouldExcludeFieldByDefault(fieldName, userRole, table)) {
|
|
136
|
+
return acc // Exclude field
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Include all other fields
|
|
140
|
+
return { ...acc, [fieldName]: record[fieldName] }
|
|
141
|
+
}, {})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Filter fields based on read permissions
|
|
145
|
+
const filteredRecord = Object.keys(record).reduce<Record<string, unknown>>((acc, fieldName) => {
|
|
146
|
+
// Preserve system fields (id, timestamps, authorship metadata)
|
|
147
|
+
// These will be transformed by record-transformer to root-level camelCase properties
|
|
148
|
+
if (isSystemField(fieldName)) {
|
|
149
|
+
return { ...acc, [fieldName]: record[fieldName] }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const fieldPermission = table.permissions?.fields?.find((fp) => fp.field === fieldName)
|
|
153
|
+
|
|
154
|
+
// If no specific read permission for this field, include it (inherits table permission)
|
|
155
|
+
if (!fieldPermission?.read) {
|
|
156
|
+
return { ...acc, [fieldName]: record[fieldName] }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if user has read permission for this field
|
|
160
|
+
if (hasFieldReadPermission(fieldPermission.read, userRole, userId, record)) {
|
|
161
|
+
return { ...acc, [fieldName]: record[fieldName] }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Otherwise, omit the field from the response
|
|
165
|
+
return acc
|
|
166
|
+
}, {})
|
|
167
|
+
|
|
168
|
+
return filteredRecord
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if user's role has read permission.
|
|
173
|
+
*
|
|
174
|
+
* Permission format (3-format system):
|
|
175
|
+
* - `'all'` — Everyone
|
|
176
|
+
* - `'authenticated'` — Any logged-in user
|
|
177
|
+
* - `string[]` — Specific role names
|
|
178
|
+
*/
|
|
179
|
+
function hasFieldReadPermission(
|
|
180
|
+
permission: TablePermission,
|
|
181
|
+
userRole: string,
|
|
182
|
+
_userId: string,
|
|
183
|
+
_record: Readonly<Record<string, unknown>>
|
|
184
|
+
): boolean {
|
|
185
|
+
if (permission === 'all') return true
|
|
186
|
+
if (permission === 'authenticated') return true
|
|
187
|
+
if (Array.isArray(permission)) return permission.includes(userRole)
|
|
188
|
+
return false
|
|
189
|
+
}
|