ddd-ts-kit 0.14.0 → 0.15.0
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/README.md +28 -1
- package/dist/check/explain/tenancy-docs.d.ts.map +1 -1
- package/dist/check/explain/tenancy-docs.js +3 -1
- package/dist/check/explain/tenancy-docs.js.map +1 -1
- package/dist/check/fixtures/tenancy-fixtures.d.ts.map +1 -1
- package/dist/check/fixtures/tenancy-fixtures.js +28 -0
- package/dist/check/fixtures/tenancy-fixtures.js.map +1 -1
- package/dist/check/tenancy/knex-unscoped.d.ts +6 -1
- package/dist/check/tenancy/knex-unscoped.d.ts.map +1 -1
- package/dist/check/tenancy/knex-unscoped.js +19 -6
- package/dist/check/tenancy/knex-unscoped.js.map +1 -1
- package/dist/check/tenancy/raw-reaches-rows.d.ts +8 -0
- package/dist/check/tenancy/raw-reaches-rows.d.ts.map +1 -0
- package/dist/check/tenancy/raw-reaches-rows.js +91 -0
- package/dist/check/tenancy/raw-reaches-rows.js.map +1 -0
- package/dist/check/tenancy/raw-sql-text.d.ts +9 -0
- package/dist/check/tenancy/raw-sql-text.d.ts.map +1 -0
- package/dist/check/tenancy/raw-sql-text.js +29 -0
- package/dist/check/tenancy/raw-sql-text.js.map +1 -0
- package/dist/commands/generate.js +2 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/declared/injection/laid-out-calls.d.ts +7 -0
- package/dist/declared/injection/laid-out-calls.d.ts.map +1 -0
- package/dist/declared/injection/laid-out-calls.js +14 -0
- package/dist/declared/injection/laid-out-calls.js.map +1 -0
- package/dist/declared/render-barrel.d.ts.map +1 -1
- package/dist/declared/render-barrel.js +2 -1
- package/dist/declared/render-barrel.js.map +1 -1
- package/dist/declared/render-dependencies-file.d.ts.map +1 -1
- package/dist/declared/render-dependencies-file.js +2 -1
- package/dist/declared/render-dependencies-file.js.map +1 -1
- package/dist/generate/kind.d.ts +2 -0
- package/dist/generate/kind.d.ts.map +1 -1
- package/dist/generate/kinds/context-kind.d.ts.map +1 -1
- package/dist/generate/kinds/context-kind.js +13 -6
- package/dist/generate/kinds/context-kind.js.map +1 -1
- package/dist/generate/kinds/next-adr-number.d.ts +4 -0
- package/dist/generate/kinds/next-adr-number.d.ts.map +1 -0
- package/dist/generate/kinds/next-adr-number.js +7 -0
- package/dist/generate/kinds/next-adr-number.js.map +1 -0
- package/dist/generate/kinds/write-adr.js +3 -6
- package/dist/generate/kinds/write-adr.js.map +1 -1
- package/dist/help/usage.d.ts +1 -1
- package/dist/help/usage.d.ts.map +1 -1
- package/dist/help/usage.js +1 -1
- package/dist/init/manifest/versions.js +24 -24
- package/dist/init/run-init.d.ts +2 -0
- package/dist/init/run-init.d.ts.map +1 -1
- package/dist/init/run-init.js +15 -1
- package/dist/init/run-init.js.map +1 -1
- package/dist/presets/apply-preset.d.ts +15 -0
- package/dist/presets/apply-preset.d.ts.map +1 -0
- package/dist/presets/apply-preset.js +94 -0
- package/dist/presets/apply-preset.js.map +1 -0
- package/dist/presets/grant-to-signed-in.d.ts +9 -0
- package/dist/presets/grant-to-signed-in.d.ts.map +1 -0
- package/dist/presets/grant-to-signed-in.js +61 -0
- package/dist/presets/grant-to-signed-in.js.map +1 -0
- package/dist/presets/identity-preset.d.ts +9 -0
- package/dist/presets/identity-preset.d.ts.map +1 -0
- package/dist/presets/identity-preset.js +44 -0
- package/dist/presets/identity-preset.js.map +1 -0
- package/dist/presets/identity-refusal.d.ts +5 -0
- package/dist/presets/identity-refusal.d.ts.map +1 -0
- package/dist/presets/identity-refusal.js +29 -0
- package/dist/presets/identity-refusal.js.map +1 -0
- package/dist/presets/identity-terms.d.ts +4 -0
- package/dist/presets/identity-terms.d.ts.map +1 -0
- package/dist/presets/identity-terms.js +57 -0
- package/dist/presets/identity-terms.js.map +1 -0
- package/dist/presets/no-flags.d.ts +4 -0
- package/dist/presets/no-flags.d.ts.map +1 -0
- package/dist/presets/no-flags.js +18 -0
- package/dist/presets/no-flags.js.map +1 -0
- package/dist/presets/organisations-as-tenants.d.ts +9 -0
- package/dist/presets/organisations-as-tenants.d.ts.map +1 -0
- package/dist/presets/organisations-as-tenants.js +43 -0
- package/dist/presets/organisations-as-tenants.js.map +1 -0
- package/dist/presets/preset-decision.d.ts +8 -0
- package/dist/presets/preset-decision.d.ts.map +1 -0
- package/dist/presets/preset-decision.js +19 -0
- package/dist/presets/preset-decision.js.map +1 -0
- package/dist/presets/preset-file.d.ts +13 -0
- package/dist/presets/preset-file.d.ts.map +1 -0
- package/dist/presets/preset-file.js +2 -0
- package/dist/presets/preset-file.js.map +1 -0
- package/dist/presets/preset-files.d.ts +9 -0
- package/dist/presets/preset-files.d.ts.map +1 -0
- package/dist/presets/preset-files.js +34 -0
- package/dist/presets/preset-files.js.map +1 -0
- package/dist/presets/preset-named.d.ts +3 -0
- package/dist/presets/preset-named.d.ts.map +1 -0
- package/dist/presets/preset-named.js +12 -0
- package/dist/presets/preset-named.js.map +1 -0
- package/dist/presets/preset-refusal.d.ts +6 -0
- package/dist/presets/preset-refusal.d.ts.map +1 -0
- package/dist/presets/preset-refusal.js +2 -0
- package/dist/presets/preset-refusal.js.map +1 -0
- package/dist/presets/preset-step.d.ts +8 -0
- package/dist/presets/preset-step.d.ts.map +1 -0
- package/dist/presets/preset-step.js +2 -0
- package/dist/presets/preset-step.js.map +1 -0
- package/dist/presets/preset.d.ts +36 -0
- package/dist/presets/preset.d.ts.map +1 -0
- package/dist/presets/preset.js +2 -0
- package/dist/presets/preset.js.map +1 -0
- package/dist/presets/with-authorizer-import.d.ts +6 -0
- package/dist/presets/with-authorizer-import.d.ts.map +1 -0
- package/dist/presets/with-authorizer-import.js +27 -0
- package/dist/presets/with-authorizer-import.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace/workspace.d.ts +2 -0
- package/dist/workspace/workspace.d.ts.map +1 -1
- package/dist/workspace/workspace.js +4 -0
- package/dist/workspace/workspace.js.map +1 -1
- package/package.json +2 -2
- package/templates/pipeline/package-scripts.json +1 -1
- package/templates/presets/identity/context/application/authorization/__context__.permissions.ts.tpl +23 -0
- package/templates/presets/identity/context/application/authorization/change-member-roles.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/authorization/grant-delegations.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/authorization/invite-members.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/authorization/read-members.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/authorization/remove-members.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/authorization/revoke-delegations.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/authorization/revoke-invitations.policy.ts.tpl +17 -0
- package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.command.ts.tpl +14 -0
- package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.handler.ts.tpl +56 -0
- package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.command.ts.tpl +17 -0
- package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.handler.ts.tpl +82 -0
- package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.command.ts.tpl +7 -0
- package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.handler.ts.tpl +69 -0
- package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.command.ts.tpl +14 -0
- package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.handler.ts.tpl +36 -0
- package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.command.ts.tpl +21 -0
- package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.handler.ts.tpl +43 -0
- package/templates/presets/identity/context/application/commands/invite-member/invite-member.command.ts.tpl +16 -0
- package/templates/presets/identity/context/application/commands/invite-member/invite-member.handler.ts.tpl +53 -0
- package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.command.ts.tpl +14 -0
- package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.handler.ts.tpl +50 -0
- package/templates/presets/identity/context/application/commands/provision-account/provision-account.command.ts.tpl +14 -0
- package/templates/presets/identity/context/application/commands/provision-account/provision-account.handler.ts.tpl +41 -0
- package/templates/presets/identity/context/application/commands/remove-member/remove-member.command.ts.tpl +15 -0
- package/templates/presets/identity/context/application/commands/remove-member/remove-member.handler.ts.tpl +79 -0
- package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.command.ts.tpl +15 -0
- package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.handler.ts.tpl +39 -0
- package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.command.ts.tpl +15 -0
- package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.handler.ts.tpl +42 -0
- package/templates/presets/identity/context/application/integration/handlers/account-deactivated.integration-handler.ts.tpl +19 -0
- package/templates/presets/identity/context/application/integration/handlers/member-joined.integration-handler.ts.tpl +33 -0
- package/templates/presets/identity/context/application/integration/handlers/member-removed.integration-handler.ts.tpl +30 -0
- package/templates/presets/identity/context/application/integration/handlers/organisation-opened.integration-handler.ts.tpl +19 -0
- package/templates/presets/identity/context/application/integration/publishers/account-deactivated.publisher.ts.tpl +24 -0
- package/templates/presets/identity/context/application/integration/publishers/delegation-granted.publisher.ts.tpl +36 -0
- package/templates/presets/identity/context/application/integration/publishers/delegation-revoked.publisher.ts.tpl +33 -0
- package/templates/presets/identity/context/application/integration/publishers/member-joined.publisher.ts.tpl +33 -0
- package/templates/presets/identity/context/application/integration/publishers/member-removed.publisher.ts.tpl +33 -0
- package/templates/presets/identity/context/application/integration/publishers/member-role-changed.publisher.ts.tpl +35 -0
- package/templates/presets/identity/context/application/integration/publishers/organisation-opened.publisher.ts.tpl +32 -0
- package/templates/presets/identity/context/application/ports/identity-provider-admin.port.ts.tpl +17 -0
- package/templates/presets/identity/context/application/ports/identity-provider-connection.port.ts.tpl +14 -0
- package/templates/presets/identity/context/application/ports/invitation-tokens.port.ts.tpl +7 -0
- package/templates/presets/identity/context/application/ports/search-members-reader.port.ts.tpl +10 -0
- package/templates/presets/identity/context/application/queries/search-members/search-members.handler.ts.tpl +33 -0
- package/templates/presets/identity/context/application/queries/search-members/search-members.query.ts.tpl +19 -0
- package/templates/presets/identity/context/application/queries/search-members/search-members.result.ts.tpl +17 -0
- package/templates/presets/identity/context/domain/aggregates/account.aggregate.ts.tpl +92 -0
- package/templates/presets/identity/context/domain/aggregates/delegation.aggregate.ts.tpl +117 -0
- package/templates/presets/identity/context/domain/aggregates/invitation.aggregate.ts.tpl +128 -0
- package/templates/presets/identity/context/domain/aggregates/member.aggregate.ts.tpl +149 -0
- package/templates/presets/identity/context/domain/aggregates/organisation.aggregate.ts.tpl +52 -0
- package/templates/presets/identity/context/domain/errors/account.error.ts.tpl +40 -0
- package/templates/presets/identity/context/domain/errors/delegation.error.ts.tpl +45 -0
- package/templates/presets/identity/context/domain/errors/invitation.error.ts.tpl +30 -0
- package/templates/presets/identity/context/domain/errors/member.error.ts.tpl +50 -0
- package/templates/presets/identity/context/domain/errors/organisation.error.ts.tpl +32 -0
- package/templates/presets/identity/context/domain/events/account-deactivated.event.ts.tpl +13 -0
- package/templates/presets/identity/context/domain/events/account-provisioned.event.ts.tpl +17 -0
- package/templates/presets/identity/context/domain/events/delegation-granted.event.ts.tpl +22 -0
- package/templates/presets/identity/context/domain/events/delegation-revoked.event.ts.tpl +18 -0
- package/templates/presets/identity/context/domain/events/invitation-accepted.event.ts.tpl +17 -0
- package/templates/presets/identity/context/domain/events/invitation-declined.event.ts.tpl +16 -0
- package/templates/presets/identity/context/domain/events/invitation-issued.event.ts.tpl +22 -0
- package/templates/presets/identity/context/domain/events/invitation-revoked.event.ts.tpl +16 -0
- package/templates/presets/identity/context/domain/events/member-joined.event.ts.tpl +19 -0
- package/templates/presets/identity/context/domain/events/member-removed.event.ts.tpl +19 -0
- package/templates/presets/identity/context/domain/events/member-role-changed.event.ts.tpl +22 -0
- package/templates/presets/identity/context/domain/events/organisation-opened.event.ts.tpl +17 -0
- package/templates/presets/identity/context/domain/repositories/account.repository.ts.tpl +5 -0
- package/templates/presets/identity/context/domain/repositories/delegation.repository.ts.tpl +10 -0
- package/templates/presets/identity/context/domain/repositories/invitation.repository.ts.tpl +7 -0
- package/templates/presets/identity/context/domain/repositories/member.repository.ts.tpl +13 -0
- package/templates/presets/identity/context/domain/repositories/organisation.repository.ts.tpl +5 -0
- package/templates/presets/identity/context/domain/services/role-rights.service.ts.tpl +59 -0
- package/templates/presets/identity/context/domain/value-objects/delegation-period.vo.ts.tpl +42 -0
- package/templates/presets/identity/context/domain/value-objects/display-name.vo.ts.tpl +29 -0
- package/templates/presets/identity/context/domain/value-objects/email-address.vo.ts.tpl +31 -0
- package/templates/presets/identity/context/domain/value-objects/organisation-name.vo.ts.tpl +29 -0
- package/templates/presets/identity/context/domain/value-objects/organisation-rights.vo.ts.tpl +43 -0
- package/templates/presets/identity/context/domain/value-objects/organisation-slug.vo.ts.tpl +30 -0
- package/templates/presets/identity/context/domain/vocabularies/account-status.vocabulary.ts.tpl +7 -0
- package/templates/presets/identity/context/domain/vocabularies/invitation-status.vocabulary.ts.tpl +9 -0
- package/templates/presets/identity/context/domain/vocabularies/member-right.vocabulary.ts.tpl +9 -0
- package/templates/presets/identity/context/domain/vocabularies/member-role.vocabulary.ts.tpl +8 -0
- package/templates/presets/identity/context/infrastructure/external/crypto-invitation-tokens.adapter.ts.tpl +20 -0
- package/templates/presets/identity/context/infrastructure/external/environment-identity-provider-connection.adapter.ts.tpl +37 -0
- package/templates/presets/identity/context/infrastructure/external/keycloak-identity-provider-admin.adapter.ts.tpl +149 -0
- package/templates/presets/identity/context/infrastructure/persistence/__context__.schema.drizzle.ts.tpl +102 -0
- package/templates/presets/identity/context/infrastructure/persistence/account-row.schema.knex.ts.tpl +15 -0
- package/templates/presets/identity/context/infrastructure/persistence/account.mapper.drizzle.ts.tpl +38 -0
- package/templates/presets/identity/context/infrastructure/persistence/account.mapper.knex.ts.tpl +33 -0
- package/templates/presets/identity/context/infrastructure/persistence/create-__context__-tables.schema.knex.ts.tpl +107 -0
- package/templates/presets/identity/context/infrastructure/persistence/delegation-row.schema.knex.ts.tpl +17 -0
- package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.drizzle.ts.tpl +50 -0
- package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.knex.ts.tpl +45 -0
- package/templates/presets/identity/context/infrastructure/persistence/drizzle-account.repository.drizzle.ts.tpl +59 -0
- package/templates/presets/identity/context/infrastructure/persistence/drizzle-delegation.repository.drizzle.ts.tpl +77 -0
- package/templates/presets/identity/context/infrastructure/persistence/drizzle-invitation.repository.drizzle.ts.tpl +65 -0
- package/templates/presets/identity/context/infrastructure/persistence/drizzle-member.repository.drizzle.ts.tpl +84 -0
- package/templates/presets/identity/context/infrastructure/persistence/drizzle-organisation.repository.drizzle.ts.tpl +59 -0
- package/templates/presets/identity/context/infrastructure/persistence/drizzle-search-members-reader.repository.drizzle.ts.tpl +53 -0
- package/templates/presets/identity/context/infrastructure/persistence/invitation-row.schema.knex.ts.tpl +20 -0
- package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.drizzle.ts.tpl +45 -0
- package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.knex.ts.tpl +40 -0
- package/templates/presets/identity/context/infrastructure/persistence/knex-account.repository.knex.ts.tpl +61 -0
- package/templates/presets/identity/context/infrastructure/persistence/knex-delegation.repository.knex.ts.tpl +80 -0
- package/templates/presets/identity/context/infrastructure/persistence/knex-invitation.repository.knex.ts.tpl +67 -0
- package/templates/presets/identity/context/infrastructure/persistence/knex-member.repository.knex.ts.tpl +82 -0
- package/templates/presets/identity/context/infrastructure/persistence/knex-organisation.repository.knex.ts.tpl +61 -0
- package/templates/presets/identity/context/infrastructure/persistence/knex-search-members-reader.repository.knex.ts.tpl +77 -0
- package/templates/presets/identity/context/infrastructure/persistence/member-row.schema.knex.ts.tpl +15 -0
- package/templates/presets/identity/context/infrastructure/persistence/member.mapper.drizzle.ts.tpl +36 -0
- package/templates/presets/identity/context/infrastructure/persistence/member.mapper.knex.ts.tpl +31 -0
- package/templates/presets/identity/context/infrastructure/persistence/organisation-row.schema.knex.ts.tpl +12 -0
- package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.drizzle.ts.tpl +28 -0
- package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.knex.ts.tpl +26 -0
- package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.controller.ts.tpl +71 -0
- package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.body.class-validator.ts.tpl +9 -0
- package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.controller.ts.tpl +85 -0
- package/templates/presets/identity/context/presentation/http/accounts.routes.ts.tpl +5 -0
- package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.body.class-validator.per-route.ts.tpl +8 -0
- package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.controller.ts.tpl +100 -0
- package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.body.class-validator.per-route.ts.tpl +8 -0
- package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.controller.ts.tpl +98 -0
- package/templates/presets/identity/context/presentation/http/invitations.routes.ts.tpl +5 -0
- package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.body.class-validator.ts.tpl +7 -0
- package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.controller.ts.tpl +83 -0
- package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.params.class-validator.ts.tpl +11 -0
- package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.body.class-validator.ts.tpl +18 -0
- package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.controller.ts.tpl +103 -0
- package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.params.class-validator.ts.tpl +7 -0
- package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.accepted.class-validator.ts.tpl +10 -0
- package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.body.class-validator.ts.tpl +10 -0
- package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.controller.ts.tpl +65 -0
- package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.params.class-validator.ts.tpl +7 -0
- package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.body.class-validator.ts.tpl +12 -0
- package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.controller.ts.tpl +79 -0
- package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.controller.ts.tpl +73 -0
- package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.params.class-validator.ts.tpl +11 -0
- package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.controller.ts.tpl +73 -0
- package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.params.class-validator.ts.tpl +11 -0
- package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
- package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.controller.ts.tpl +73 -0
- package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.params.class-validator.ts.tpl +11 -0
- package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.controller.ts.tpl +86 -0
- package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.line.class-validator.ts.tpl +22 -0
- package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.params.class-validator.ts.tpl +7 -0
- package/templates/presets/identity/context/presentation/http/organisations.routes.ts.tpl +5 -0
- package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.class-validator.shared.ts.tpl +9 -0
- package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.zod.shared.ts.tpl +9 -0
- package/templates/presets/identity/context/published-language/account-deactivated.v1.integration-event.ts.tpl +33 -0
- package/templates/presets/identity/context/published-language/account-deactivated.v1.payload.class-validator.ts.tpl +7 -0
- package/templates/presets/identity/context/published-language/delegation-granted.v1.integration-event.ts.tpl +39 -0
- package/templates/presets/identity/context/published-language/delegation-granted.v1.payload.class-validator.ts.tpl +28 -0
- package/templates/presets/identity/context/published-language/delegation-revoked.v1.integration-event.ts.tpl +36 -0
- package/templates/presets/identity/context/published-language/delegation-revoked.v1.payload.class-validator.ts.tpl +16 -0
- package/templates/presets/identity/context/published-language/member-joined.v1.integration-event.ts.tpl +36 -0
- package/templates/presets/identity/context/published-language/member-joined.v1.payload.class-validator.ts.tpl +16 -0
- package/templates/presets/identity/context/published-language/member-removed.v1.integration-event.ts.tpl +36 -0
- package/templates/presets/identity/context/published-language/member-removed.v1.payload.class-validator.ts.tpl +18 -0
- package/templates/presets/identity/context/published-language/member-role-changed.v1.integration-event.ts.tpl +38 -0
- package/templates/presets/identity/context/published-language/member-role-changed.v1.payload.class-validator.ts.tpl +24 -0
- package/templates/presets/identity/context/published-language/organisation-opened.v1.integration-event.ts.tpl +35 -0
- package/templates/presets/identity/context/published-language/organisation-opened.v1.payload.class-validator.ts.tpl +13 -0
- package/templates/presets/identity/context/test/builders/account.builder.ts.tpl +59 -0
- package/templates/presets/identity/context/test/builders/delegation.builder.ts.tpl +67 -0
- package/templates/presets/identity/context/test/builders/invitation.builder.ts.tpl +60 -0
- package/templates/presets/identity/context/test/builders/member.builder.ts.tpl +59 -0
- package/templates/presets/identity/context/test/builders/organisation.builder.ts.tpl +44 -0
- package/templates/presets/identity/context/test/fakes/account.repository.fake.ts.tpl +10 -0
- package/templates/presets/identity/context/test/fakes/delegation.repository.fake.ts.tpl +26 -0
- package/templates/presets/identity/context/test/fakes/identity-provider-admin.fake.ts.tpl +41 -0
- package/templates/presets/identity/context/test/fakes/invitation-tokens.fake.ts.tpl +18 -0
- package/templates/presets/identity/context/test/fakes/invitation.repository.fake.ts.tpl +14 -0
- package/templates/presets/identity/context/test/fakes/member.repository.fake.ts.tpl +29 -0
- package/templates/presets/identity/context/test/fakes/organisation.repository.fake.ts.tpl +10 -0
- package/templates/presets/identity/context/test/fakes/search-members-reader.fake.ts.tpl +25 -0
- package/templates/presets/identity/context/test/integration/account.repository.contract.spec.ts.tpl +48 -0
- package/templates/presets/identity/context/test/integration/delegation.repository.contract.spec.ts.tpl +84 -0
- package/templates/presets/identity/context/test/integration/invitation.repository.contract.spec.ts.tpl +76 -0
- package/templates/presets/identity/context/test/integration/keycloak-identity-provider-admin.adapter.spec.ts.tpl +123 -0
- package/templates/presets/identity/context/test/integration/member.repository.contract.spec.ts.tpl +92 -0
- package/templates/presets/identity/context/test/integration/organisation.repository.contract.spec.ts.tpl +46 -0
- package/templates/presets/identity/context/test/integration/search-members-reader.spec.ts.tpl +86 -0
- package/templates/presets/identity/context/test/unit/accept-invitation.controller.spec.ts.tpl +57 -0
- package/templates/presets/identity/context/test/unit/accept-invitation.handler.spec.ts.tpl +69 -0
- package/templates/presets/identity/context/test/unit/account-deactivated.integration-handler.spec.ts.tpl +24 -0
- package/templates/presets/identity/context/test/unit/account-deactivated.publisher.spec.ts.tpl +25 -0
- package/templates/presets/identity/context/test/unit/account.aggregate.spec.ts.tpl +72 -0
- package/templates/presets/identity/context/test/unit/change-member-role.controller.spec.ts.tpl +57 -0
- package/templates/presets/identity/context/test/unit/change-member-role.handler.spec.ts.tpl +135 -0
- package/templates/presets/identity/context/test/unit/crypto-invitation-tokens.adapter.spec.ts.tpl +15 -0
- package/templates/presets/identity/context/test/unit/deactivate-account.controller.spec.ts.tpl +44 -0
- package/templates/presets/identity/context/test/unit/deactivate-account.handler.spec.ts.tpl +106 -0
- package/templates/presets/identity/context/test/unit/decline-invitation.controller.spec.ts.tpl +57 -0
- package/templates/presets/identity/context/test/unit/decline-invitation.handler.spec.ts.tpl +40 -0
- package/templates/presets/identity/context/test/unit/delegation-granted.publisher.spec.ts.tpl +36 -0
- package/templates/presets/identity/context/test/unit/delegation-period.vo.spec.ts.tpl +32 -0
- package/templates/presets/identity/context/test/unit/delegation-revoked.publisher.spec.ts.tpl +35 -0
- package/templates/presets/identity/context/test/unit/delegation.aggregate.spec.ts.tpl +111 -0
- package/templates/presets/identity/context/test/unit/display-name.vo.spec.ts.tpl +13 -0
- package/templates/presets/identity/context/test/unit/email-address.vo.spec.ts.tpl +24 -0
- package/templates/presets/identity/context/test/unit/environment-identity-provider-connection.adapter.spec.ts.tpl +41 -0
- package/templates/presets/identity/context/test/unit/grant-delegation.controller.spec.ts.tpl +57 -0
- package/templates/presets/identity/context/test/unit/grant-delegation.handler.spec.ts.tpl +66 -0
- package/templates/presets/identity/context/test/unit/identity-application.ts.tpl +79 -0
- package/templates/presets/identity/context/test/unit/invitation.aggregate.spec.ts.tpl +118 -0
- package/templates/presets/identity/context/test/unit/invite-member.controller.spec.ts.tpl +57 -0
- package/templates/presets/identity/context/test/unit/invite-member.handler.spec.ts.tpl +110 -0
- package/templates/presets/identity/context/test/unit/member-joined.integration-handler.spec.ts.tpl +38 -0
- package/templates/presets/identity/context/test/unit/member-joined.publisher.spec.ts.tpl +36 -0
- package/templates/presets/identity/context/test/unit/member-removed.integration-handler.spec.ts.tpl +45 -0
- package/templates/presets/identity/context/test/unit/member-removed.publisher.spec.ts.tpl +35 -0
- package/templates/presets/identity/context/test/unit/member-role-changed.publisher.spec.ts.tpl +36 -0
- package/templates/presets/identity/context/test/unit/member.aggregate.spec.ts.tpl +142 -0
- package/templates/presets/identity/context/test/unit/open-organisation.controller.spec.ts.tpl +57 -0
- package/templates/presets/identity/context/test/unit/open-organisation.handler.spec.ts.tpl +52 -0
- package/templates/presets/identity/context/test/unit/organisation-name.vo.spec.ts.tpl +13 -0
- package/templates/presets/identity/context/test/unit/organisation-opened.integration-handler.spec.ts.tpl +24 -0
- package/templates/presets/identity/context/test/unit/organisation-opened.publisher.spec.ts.tpl +35 -0
- package/templates/presets/identity/context/test/unit/organisation-rights.vo.spec.ts.tpl +23 -0
- package/templates/presets/identity/context/test/unit/organisation-slug.vo.spec.ts.tpl +17 -0
- package/templates/presets/identity/context/test/unit/organisation.aggregate.spec.ts.tpl +33 -0
- package/templates/presets/identity/context/test/unit/provision-account.controller.spec.ts.tpl +73 -0
- package/templates/presets/identity/context/test/unit/provision-account.handler.spec.ts.tpl +65 -0
- package/templates/presets/identity/context/test/unit/remove-member.controller.spec.ts.tpl +44 -0
- package/templates/presets/identity/context/test/unit/remove-member.handler.spec.ts.tpl +125 -0
- package/templates/presets/identity/context/test/unit/revoke-delegation.controller.spec.ts.tpl +44 -0
- package/templates/presets/identity/context/test/unit/revoke-delegation.handler.spec.ts.tpl +51 -0
- package/templates/presets/identity/context/test/unit/revoke-invitation.controller.spec.ts.tpl +44 -0
- package/templates/presets/identity/context/test/unit/revoke-invitation.handler.spec.ts.tpl +44 -0
- package/templates/presets/identity/context/test/unit/role-rights.service.spec.ts.tpl +54 -0
- package/templates/presets/identity/context/test/unit/search-members.controller.spec.ts.tpl +43 -0
- package/templates/presets/identity/context/test/unit/search-members.handler.spec.ts.tpl +38 -0
- package/templates/presets/identity/decision.md.tpl +37 -0
- package/templates/presets/identity/design.md.tpl +71 -0
- package/templates/presets/signed-in-grants.ts.tpl +12 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// ddd-ts-kit:imports
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
4
|
+
import { auditColumns } from '@ddd-ts-kit/knex';
|
|
5
|
+
// /ddd-ts-kit:imports
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The tables of {{context.kebab}}: what the provider authenticates stays with it, no password is stored.
|
|
9
|
+
{{#if tenancy.any}}
|
|
10
|
+
* They hold no tenant column: the organisations are the tenants, and this context decides who
|
|
11
|
+
* belongs to which ({{preset.adr}}).
|
|
12
|
+
{{/if}}
|
|
13
|
+
*/
|
|
14
|
+
export async function create{{context.pascal}}Tables(knex: Knex): Promise<void> {
|
|
15
|
+
await knex.schema.createSchemaIfNotExists({{context.constant}}_SCHEMA);
|
|
16
|
+
// ddd-ts-kit:tables
|
|
17
|
+
await createAccountTable(knex);
|
|
18
|
+
await createOrganisationTable(knex);
|
|
19
|
+
await createMemberTable(knex);
|
|
20
|
+
await createInvitationTable(knex);
|
|
21
|
+
await createDelegationTable(knex);
|
|
22
|
+
// /ddd-ts-kit:tables
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ddd-ts-kit:definitions
|
|
26
|
+
/** Keyed by the subject the identity provider gives the person. */
|
|
27
|
+
function createAccountTable(knex: Knex): Promise<void> {
|
|
28
|
+
return knex.schema.withSchema({{context.constant}}_SCHEMA).createTable('accounts', (table) => {
|
|
29
|
+
table.text('id').primary();
|
|
30
|
+
table.text('email').notNullable();
|
|
31
|
+
table.text('display_name').notNullable();
|
|
32
|
+
table.text('status').notNullable();
|
|
33
|
+
table.timestamp('provisioned_at', { useTz: true }).notNullable();
|
|
34
|
+
table.timestamp('deactivated_at', { useTz: true });
|
|
35
|
+
table.integer('version').notNullable();
|
|
36
|
+
auditColumns(table);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Keyed by the slug, which is also the tenant id when the project has tenants. */
|
|
41
|
+
function createOrganisationTable(knex: Knex): Promise<void> {
|
|
42
|
+
return knex.schema.withSchema({{context.constant}}_SCHEMA).createTable('organisations', (table) => {
|
|
43
|
+
table.text('id').primary();
|
|
44
|
+
table.text('name').notNullable();
|
|
45
|
+
table.text('opened_by').notNullable();
|
|
46
|
+
table.timestamp('opened_at', { useTz: true }).notNullable();
|
|
47
|
+
table.integer('version').notNullable();
|
|
48
|
+
auditColumns(table);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One current membership per account and organisation: the partial unique index is the guard. */
|
|
53
|
+
function createMemberTable(knex: Knex): Promise<void> {
|
|
54
|
+
return knex.schema.withSchema({{context.constant}}_SCHEMA).createTable('members', (table) => {
|
|
55
|
+
table.text('id').primary();
|
|
56
|
+
table.text('organisation_id').notNullable();
|
|
57
|
+
table.text('account_id').notNullable();
|
|
58
|
+
table.text('role').notNullable();
|
|
59
|
+
table.timestamp('joined_at', { useTz: true }).notNullable();
|
|
60
|
+
table.timestamp('removed_at', { useTz: true });
|
|
61
|
+
|
|
62
|
+
table.unique(['organisation_id', 'account_id'], {
|
|
63
|
+
indexName: 'members_current_idx',
|
|
64
|
+
predicate: knex.whereNull('removed_at'),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
table.index(['account_id']);
|
|
68
|
+
table.integer('version').notNullable();
|
|
69
|
+
auditColumns(table);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createInvitationTable(knex: Knex): Promise<void> {
|
|
74
|
+
return knex.schema.withSchema({{context.constant}}_SCHEMA).createTable('invitations', (table) => {
|
|
75
|
+
table.text('id').primary();
|
|
76
|
+
table.text('organisation_id').notNullable();
|
|
77
|
+
table.text('email').notNullable();
|
|
78
|
+
table.text('role').notNullable();
|
|
79
|
+
table.text('token_hash').notNullable().unique();
|
|
80
|
+
table.text('invited_by').notNullable();
|
|
81
|
+
table.timestamp('issued_at', { useTz: true }).notNullable();
|
|
82
|
+
table.timestamp('expires_at', { useTz: true }).notNullable();
|
|
83
|
+
table.text('status').notNullable();
|
|
84
|
+
table.timestamp('answered_at', { useTz: true });
|
|
85
|
+
table.index(['organisation_id', 'status']);
|
|
86
|
+
table.integer('version').notNullable();
|
|
87
|
+
auditColumns(table);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createDelegationTable(knex: Knex): Promise<void> {
|
|
92
|
+
return knex.schema.withSchema({{context.constant}}_SCHEMA).createTable('delegations', (table) => {
|
|
93
|
+
table.text('id').primary();
|
|
94
|
+
table.text('organisation_id').notNullable();
|
|
95
|
+
table.text('delegator_id').notNullable();
|
|
96
|
+
table.text('delegate_id').notNullable();
|
|
97
|
+
table.text('rights').notNullable();
|
|
98
|
+
table.timestamp('starts_at', { useTz: true }).notNullable();
|
|
99
|
+
table.timestamp('ends_at', { useTz: true }).notNullable();
|
|
100
|
+
table.timestamp('ended_at', { useTz: true });
|
|
101
|
+
table.index(['organisation_id', 'delegate_id']);
|
|
102
|
+
table.index(['organisation_id', 'delegator_id']);
|
|
103
|
+
table.integer('version').notNullable();
|
|
104
|
+
auditColumns(table);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// /ddd-ts-kit:definitions
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AuditRow } from '@ddd-ts-kit/knex';
|
|
2
|
+
|
|
3
|
+
export interface DelegationColumns {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly organisation_id: string;
|
|
6
|
+
readonly delegator_id: string;
|
|
7
|
+
readonly delegate_id: string;
|
|
8
|
+
/** The rights lent, separated by commas: a right is a word of `MemberRight`, which holds none. */
|
|
9
|
+
readonly rights: string;
|
|
10
|
+
readonly starts_at: Date;
|
|
11
|
+
readonly ends_at: Date;
|
|
12
|
+
readonly ended_at: Date | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DelegationRow extends DelegationColumns, AuditRow {
|
|
16
|
+
readonly version: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AuditStamp } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { Delegation } from '{{alias.context}}/domain/aggregates/delegation.aggregate{{alias.ext}}';
|
|
3
|
+
import { DelegationPeriod } from '{{alias.context}}/domain/value-objects/delegation-period.vo{{alias.ext}}';
|
|
4
|
+
import { MemberRight } from '{{alias.context}}/domain/vocabularies/member-right.vocabulary{{alias.ext}}';
|
|
5
|
+
import type { delegations } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
6
|
+
|
|
7
|
+
const SEPARATOR = ',';
|
|
8
|
+
|
|
9
|
+
type DelegationRow = typeof delegations.$inferSelect;
|
|
10
|
+
|
|
11
|
+
export type DelegationColumns = Omit<DelegationRow, 'version' | keyof AuditStamp>;
|
|
12
|
+
|
|
13
|
+
export const DelegationMapper = {
|
|
14
|
+
toDomain(row: DelegationRow): Delegation {
|
|
15
|
+
const loaded = Delegation.rehydrate(row.id, {
|
|
16
|
+
organisationId: row.organisationId,
|
|
17
|
+
delegatorId: row.delegatorId,
|
|
18
|
+
delegateId: row.delegateId,
|
|
19
|
+
rights: rightsOf(row.rights),
|
|
20
|
+
period: DelegationPeriod.create(row.startsAt, row.endsAt),
|
|
21
|
+
endedAt: row.endedAt ?? undefined,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
loaded.version = row.version;
|
|
25
|
+
|
|
26
|
+
return loaded;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
toRow(saved: Delegation): DelegationColumns {
|
|
30
|
+
const state = saved.toState();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
id: saved.id,
|
|
34
|
+
organisationId: state.organisationId,
|
|
35
|
+
delegatorId: state.delegatorId,
|
|
36
|
+
delegateId: state.delegateId,
|
|
37
|
+
rights: state.rights.join(SEPARATOR),
|
|
38
|
+
startsAt: state.period.startsAt,
|
|
39
|
+
endsAt: state.period.endsAt,
|
|
40
|
+
endedAt: state.endedAt ?? null,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The rights of a stored delegation, those the vocabulary no longer names left out. */
|
|
46
|
+
function rightsOf(stored: string): MemberRight[] {
|
|
47
|
+
const lent = stored.split(SEPARATOR);
|
|
48
|
+
|
|
49
|
+
return Object.values(MemberRight).filter((right) => lent.includes(right));
|
|
50
|
+
}
|
package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.knex.ts.tpl
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Delegation } from '{{alias.context}}/domain/aggregates/delegation.aggregate{{alias.ext}}';
|
|
2
|
+
import { DelegationPeriod } from '{{alias.context}}/domain/value-objects/delegation-period.vo{{alias.ext}}';
|
|
3
|
+
import { MemberRight } from '{{alias.context}}/domain/vocabularies/member-right.vocabulary{{alias.ext}}';
|
|
4
|
+
import type { DelegationColumns, DelegationRow } from '{{alias.context}}/infrastructure/persistence/delegation-row.schema{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
const SEPARATOR = ',';
|
|
7
|
+
|
|
8
|
+
export const DelegationMapper = {
|
|
9
|
+
toDomain(row: DelegationRow): Delegation {
|
|
10
|
+
const loaded = Delegation.rehydrate(row.id, {
|
|
11
|
+
organisationId: row.organisation_id,
|
|
12
|
+
delegatorId: row.delegator_id,
|
|
13
|
+
delegateId: row.delegate_id,
|
|
14
|
+
rights: rightsOf(row.rights),
|
|
15
|
+
period: DelegationPeriod.create(row.starts_at, row.ends_at),
|
|
16
|
+
endedAt: row.ended_at ?? undefined,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
loaded.version = row.version;
|
|
20
|
+
|
|
21
|
+
return loaded;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
toRow(saved: Delegation): DelegationColumns {
|
|
25
|
+
const state = saved.toState();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
id: saved.id,
|
|
29
|
+
organisation_id: state.organisationId,
|
|
30
|
+
delegator_id: state.delegatorId,
|
|
31
|
+
delegate_id: state.delegateId,
|
|
32
|
+
rights: state.rights.join(SEPARATOR),
|
|
33
|
+
starts_at: state.period.startsAt,
|
|
34
|
+
ends_at: state.period.endsAt,
|
|
35
|
+
ended_at: state.endedAt ?? null,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** The rights of a stored delegation, those the vocabulary no longer names left out. */
|
|
41
|
+
function rightsOf(stored: string): MemberRight[] {
|
|
42
|
+
const lent = stored.split(SEPARATOR);
|
|
43
|
+
|
|
44
|
+
return Object.values(MemberRight).filter((right) => lent.includes(right));
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { optimisticUpdate } from '@ddd-ts-kit/drizzle/postgres';
|
|
3
|
+
import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { Repository } from '@ddd-ts-kit/kernel/declarations';
|
|
5
|
+
import type { Account } from '{{alias.context}}/domain/aggregates/account.aggregate{{alias.ext}}';
|
|
6
|
+
import { AccountRepository } from '{{alias.context}}/domain/repositories/account.repository{{alias.ext}}';
|
|
7
|
+
import type { Database } from '{{alias.platform}}/database/database{{alias.ext}}';
|
|
8
|
+
import { accounts } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
9
|
+
import { AccountMapper, type AccountColumns } from '{{alias.context}}/infrastructure/persistence/account.mapper{{alias.ext}}';
|
|
10
|
+
|
|
11
|
+
type VersionedAccount = AccountColumns & { readonly version: number };
|
|
12
|
+
|
|
13
|
+
@Repository(AccountRepository)
|
|
14
|
+
export class DrizzleAccountRepository implements AccountRepository {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly database: Database,
|
|
17
|
+
private readonly scope: ExecutionScope,
|
|
18
|
+
private readonly clock: Clock,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async findById(id: string): Promise<Account | undefined> {
|
|
22
|
+
const [row] = await executorOf(this.database).select().from(accounts).where(eq(accounts.id, id));
|
|
23
|
+
|
|
24
|
+
return row === undefined ? undefined : AccountMapper.toDomain(row);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async save(saved: Account): Promise<void> {
|
|
28
|
+
await optimisticUpdate(saved, async ({ expected, next }) => {
|
|
29
|
+
const state = { ...AccountMapper.toRow(saved), version: next };
|
|
30
|
+
const written = expected === 0 ? await this.insert(state) : await this.update(state, expected);
|
|
31
|
+
|
|
32
|
+
return written.length;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private insert(state: VersionedAccount) {
|
|
37
|
+
const row = { ...state, ...stampCreation(this.scope, this.clock) };
|
|
38
|
+
|
|
39
|
+
return executorOf(this.database).insert(accounts).values(row).onConflictDoNothing().returning({ id: accounts.id });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private update(state: VersionedAccount, expected: number) {
|
|
43
|
+
const sameVersion = and(eq(accounts.id, state.id), eq(accounts.version, expected));
|
|
44
|
+
|
|
45
|
+
return executorOf(this.database)
|
|
46
|
+
.update(accounts)
|
|
47
|
+
.set({ ...state, ...stampUpdate(this.scope, this.clock) })
|
|
48
|
+
.where(sameVersion)
|
|
49
|
+
.returning({ id: accounts.id });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The executor of the unit of work, which every statement of this file goes through. */
|
|
54
|
+
function executorOf(database: Database): ReturnType<Database['executor']> {
|
|
55
|
+
{{#if tenancy.any}}
|
|
56
|
+
// ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
|
|
57
|
+
{{/if}}
|
|
58
|
+
return database.executor();
|
|
59
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { and, eq, isNull, or } from 'drizzle-orm';
|
|
2
|
+
import { optimisticUpdate } from '@ddd-ts-kit/drizzle/postgres';
|
|
3
|
+
import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { Repository } from '@ddd-ts-kit/kernel/declarations';
|
|
5
|
+
import type { Delegation } from '{{alias.context}}/domain/aggregates/delegation.aggregate{{alias.ext}}';
|
|
6
|
+
import { DelegationRepository } from '{{alias.context}}/domain/repositories/delegation.repository{{alias.ext}}';
|
|
7
|
+
import type { Database } from '{{alias.platform}}/database/database{{alias.ext}}';
|
|
8
|
+
import { delegations } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
9
|
+
import { DelegationMapper, type DelegationColumns } from '{{alias.context}}/infrastructure/persistence/delegation.mapper{{alias.ext}}';
|
|
10
|
+
|
|
11
|
+
type VersionedDelegation = DelegationColumns & { readonly version: number };
|
|
12
|
+
|
|
13
|
+
@Repository(DelegationRepository)
|
|
14
|
+
export class DrizzleDelegationRepository implements DelegationRepository {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly database: Database,
|
|
17
|
+
private readonly scope: ExecutionScope,
|
|
18
|
+
private readonly clock: Clock,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async findById(id: string): Promise<Delegation | undefined> {
|
|
22
|
+
const [row] = await executorOf(this.database).select().from(delegations).where(eq(delegations.id, id));
|
|
23
|
+
|
|
24
|
+
return row === undefined ? undefined : DelegationMapper.toDomain(row);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async findUnendedTo(organisationId: string, delegateId: string): Promise<readonly Delegation[]> {
|
|
28
|
+
const lentTo = and(unended(organisationId), eq(delegations.delegateId, delegateId));
|
|
29
|
+
const rows = await executorOf(this.database).select().from(delegations).where(lentTo).orderBy(delegations.id);
|
|
30
|
+
|
|
31
|
+
return rows.map((row) => DelegationMapper.toDomain(row));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async findUnendedInvolving(organisationId: string, accountId: string): Promise<readonly Delegation[]> {
|
|
35
|
+
const either = or(eq(delegations.delegatorId, accountId), eq(delegations.delegateId, accountId));
|
|
36
|
+
const rows = await executorOf(this.database).select().from(delegations).where(and(unended(organisationId), either)).orderBy(delegations.id);
|
|
37
|
+
|
|
38
|
+
return rows.map((row) => DelegationMapper.toDomain(row));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async save(saved: Delegation): Promise<void> {
|
|
42
|
+
await optimisticUpdate(saved, async ({ expected, next }) => {
|
|
43
|
+
const state = { ...DelegationMapper.toRow(saved), version: next };
|
|
44
|
+
const written = expected === 0 ? await this.insert(state) : await this.update(state, expected);
|
|
45
|
+
|
|
46
|
+
return written.length;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private insert(state: VersionedDelegation) {
|
|
51
|
+
const row = { ...state, ...stampCreation(this.scope, this.clock) };
|
|
52
|
+
|
|
53
|
+
return executorOf(this.database).insert(delegations).values(row).onConflictDoNothing().returning({ id: delegations.id });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private update(state: VersionedDelegation, expected: number) {
|
|
57
|
+
const sameVersion = and(eq(delegations.id, state.id), eq(delegations.version, expected));
|
|
58
|
+
|
|
59
|
+
return executorOf(this.database)
|
|
60
|
+
.update(delegations)
|
|
61
|
+
.set({ ...state, ...stampUpdate(this.scope, this.clock) })
|
|
62
|
+
.where(sameVersion)
|
|
63
|
+
.returning({ id: delegations.id });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function unended(organisationId: string) {
|
|
68
|
+
return and(eq(delegations.organisationId, organisationId), isNull(delegations.endedAt));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The executor of the unit of work, which every statement of this file goes through. */
|
|
72
|
+
function executorOf(database: Database): ReturnType<Database['executor']> {
|
|
73
|
+
{{#if tenancy.any}}
|
|
74
|
+
// ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
|
|
75
|
+
{{/if}}
|
|
76
|
+
return database.executor();
|
|
77
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { optimisticUpdate } from '@ddd-ts-kit/drizzle/postgres';
|
|
3
|
+
import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { Repository } from '@ddd-ts-kit/kernel/declarations';
|
|
5
|
+
import type { Invitation } from '{{alias.context}}/domain/aggregates/invitation.aggregate{{alias.ext}}';
|
|
6
|
+
import { InvitationRepository } from '{{alias.context}}/domain/repositories/invitation.repository{{alias.ext}}';
|
|
7
|
+
import type { Database } from '{{alias.platform}}/database/database{{alias.ext}}';
|
|
8
|
+
import { invitations } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
9
|
+
import { InvitationMapper, type InvitationColumns } from '{{alias.context}}/infrastructure/persistence/invitation.mapper{{alias.ext}}';
|
|
10
|
+
|
|
11
|
+
type VersionedInvitation = InvitationColumns & { readonly version: number };
|
|
12
|
+
|
|
13
|
+
@Repository(InvitationRepository)
|
|
14
|
+
export class DrizzleInvitationRepository implements InvitationRepository {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly database: Database,
|
|
17
|
+
private readonly scope: ExecutionScope,
|
|
18
|
+
private readonly clock: Clock,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async findById(id: string): Promise<Invitation | undefined> {
|
|
22
|
+
const [row] = await executorOf(this.database).select().from(invitations).where(eq(invitations.id, id));
|
|
23
|
+
|
|
24
|
+
return row === undefined ? undefined : InvitationMapper.toDomain(row);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async findByTokenHash(tokenHash: string): Promise<Invitation | undefined> {
|
|
28
|
+
const [row] = await executorOf(this.database).select().from(invitations).where(eq(invitations.tokenHash, tokenHash));
|
|
29
|
+
|
|
30
|
+
return row === undefined ? undefined : InvitationMapper.toDomain(row);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async save(saved: Invitation): Promise<void> {
|
|
34
|
+
await optimisticUpdate(saved, async ({ expected, next }) => {
|
|
35
|
+
const state = { ...InvitationMapper.toRow(saved), version: next };
|
|
36
|
+
const written = expected === 0 ? await this.insert(state) : await this.update(state, expected);
|
|
37
|
+
|
|
38
|
+
return written.length;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private insert(state: VersionedInvitation) {
|
|
43
|
+
const row = { ...state, ...stampCreation(this.scope, this.clock) };
|
|
44
|
+
|
|
45
|
+
return executorOf(this.database).insert(invitations).values(row).onConflictDoNothing().returning({ id: invitations.id });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private update(state: VersionedInvitation, expected: number) {
|
|
49
|
+
const sameVersion = and(eq(invitations.id, state.id), eq(invitations.version, expected));
|
|
50
|
+
|
|
51
|
+
return executorOf(this.database)
|
|
52
|
+
.update(invitations)
|
|
53
|
+
.set({ ...state, ...stampUpdate(this.scope, this.clock) })
|
|
54
|
+
.where(sameVersion)
|
|
55
|
+
.returning({ id: invitations.id });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The executor of the unit of work, which every statement of this file goes through. */
|
|
60
|
+
function executorOf(database: Database): ReturnType<Database['executor']> {
|
|
61
|
+
{{#if tenancy.any}}
|
|
62
|
+
// ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
|
|
63
|
+
{{/if}}
|
|
64
|
+
return database.executor();
|
|
65
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { and, count, eq, isNull } from 'drizzle-orm';
|
|
2
|
+
import { optimisticUpdate } from '@ddd-ts-kit/drizzle/postgres';
|
|
3
|
+
import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { Repository } from '@ddd-ts-kit/kernel/declarations';
|
|
5
|
+
import type { Member } from '{{alias.context}}/domain/aggregates/member.aggregate{{alias.ext}}';
|
|
6
|
+
import { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
7
|
+
import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
|
|
8
|
+
import type { Database } from '{{alias.platform}}/database/database{{alias.ext}}';
|
|
9
|
+
import { members } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
10
|
+
import { MemberMapper, type MemberColumns } from '{{alias.context}}/infrastructure/persistence/member.mapper{{alias.ext}}';
|
|
11
|
+
|
|
12
|
+
type VersionedMember = MemberColumns & { readonly version: number };
|
|
13
|
+
|
|
14
|
+
@Repository(MemberRepository)
|
|
15
|
+
export class DrizzleMemberRepository implements MemberRepository {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly database: Database,
|
|
18
|
+
private readonly scope: ExecutionScope,
|
|
19
|
+
private readonly clock: Clock,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
async findById(id: string): Promise<Member | undefined> {
|
|
23
|
+
const [row] = await executorOf(this.database).select().from(members).where(eq(members.id, id));
|
|
24
|
+
|
|
25
|
+
return row === undefined ? undefined : MemberMapper.toDomain(row);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async findOf(organisationId: string, accountId: string): Promise<Member | undefined> {
|
|
29
|
+
const [row] = await executorOf(this.database).select().from(members).where(current(organisationId, accountId));
|
|
30
|
+
|
|
31
|
+
return row === undefined ? undefined : MemberMapper.toDomain(row);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async searchCurrentOf(accountId: string): Promise<readonly Member[]> {
|
|
35
|
+
const ofAccount = and(eq(members.accountId, accountId), isNull(members.removedAt));
|
|
36
|
+
const rows = await executorOf(this.database).select().from(members).where(ofAccount).orderBy(members.id);
|
|
37
|
+
|
|
38
|
+
return rows.map((row) => MemberMapper.toDomain(row));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async countOwners(organisationId: string): Promise<number> {
|
|
42
|
+
const owners = and(eq(members.organisationId, organisationId), eq(members.role, MemberRole.Owner), isNull(members.removedAt));
|
|
43
|
+
const [counted] = await executorOf(this.database).select({ total: count() }).from(members).where(owners);
|
|
44
|
+
|
|
45
|
+
return counted?.total ?? 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async save(saved: Member): Promise<void> {
|
|
49
|
+
await optimisticUpdate(saved, async ({ expected, next }) => {
|
|
50
|
+
const state = { ...MemberMapper.toRow(saved), version: next };
|
|
51
|
+
const written = expected === 0 ? await this.insert(state) : await this.update(state, expected);
|
|
52
|
+
|
|
53
|
+
return written.length;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private insert(state: VersionedMember) {
|
|
58
|
+
const row = { ...state, ...stampCreation(this.scope, this.clock) };
|
|
59
|
+
|
|
60
|
+
return executorOf(this.database).insert(members).values(row).onConflictDoNothing().returning({ id: members.id });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private update(state: VersionedMember, expected: number) {
|
|
64
|
+
const sameVersion = and(eq(members.id, state.id), eq(members.version, expected));
|
|
65
|
+
|
|
66
|
+
return executorOf(this.database)
|
|
67
|
+
.update(members)
|
|
68
|
+
.set({ ...state, ...stampUpdate(this.scope, this.clock) })
|
|
69
|
+
.where(sameVersion)
|
|
70
|
+
.returning({ id: members.id });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function current(organisationId: string, accountId: string) {
|
|
75
|
+
return and(eq(members.organisationId, organisationId), eq(members.accountId, accountId), isNull(members.removedAt));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The executor of the unit of work, which every statement of this file goes through. */
|
|
79
|
+
function executorOf(database: Database): ReturnType<Database['executor']> {
|
|
80
|
+
{{#if tenancy.any}}
|
|
81
|
+
// ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
|
|
82
|
+
{{/if}}
|
|
83
|
+
return database.executor();
|
|
84
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { optimisticUpdate } from '@ddd-ts-kit/drizzle/postgres';
|
|
3
|
+
import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
|
|
4
|
+
import { Repository } from '@ddd-ts-kit/kernel/declarations';
|
|
5
|
+
import type { Organisation } from '{{alias.context}}/domain/aggregates/organisation.aggregate{{alias.ext}}';
|
|
6
|
+
import { OrganisationRepository } from '{{alias.context}}/domain/repositories/organisation.repository{{alias.ext}}';
|
|
7
|
+
import type { Database } from '{{alias.platform}}/database/database{{alias.ext}}';
|
|
8
|
+
import { organisations } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
9
|
+
import { OrganisationMapper, type OrganisationColumns } from '{{alias.context}}/infrastructure/persistence/organisation.mapper{{alias.ext}}';
|
|
10
|
+
|
|
11
|
+
type VersionedOrganisation = OrganisationColumns & { readonly version: number };
|
|
12
|
+
|
|
13
|
+
@Repository(OrganisationRepository)
|
|
14
|
+
export class DrizzleOrganisationRepository implements OrganisationRepository {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly database: Database,
|
|
17
|
+
private readonly scope: ExecutionScope,
|
|
18
|
+
private readonly clock: Clock,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async findById(id: string): Promise<Organisation | undefined> {
|
|
22
|
+
const [row] = await executorOf(this.database).select().from(organisations).where(eq(organisations.id, id));
|
|
23
|
+
|
|
24
|
+
return row === undefined ? undefined : OrganisationMapper.toDomain(row);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async save(saved: Organisation): Promise<void> {
|
|
28
|
+
await optimisticUpdate(saved, async ({ expected, next }) => {
|
|
29
|
+
const state = { ...OrganisationMapper.toRow(saved), version: next };
|
|
30
|
+
const written = expected === 0 ? await this.insert(state) : await this.update(state, expected);
|
|
31
|
+
|
|
32
|
+
return written.length;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private insert(state: VersionedOrganisation) {
|
|
37
|
+
const row = { ...state, ...stampCreation(this.scope, this.clock) };
|
|
38
|
+
|
|
39
|
+
return executorOf(this.database).insert(organisations).values(row).onConflictDoNothing().returning({ id: organisations.id });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private update(state: VersionedOrganisation, expected: number) {
|
|
43
|
+
const sameVersion = and(eq(organisations.id, state.id), eq(organisations.version, expected));
|
|
44
|
+
|
|
45
|
+
return executorOf(this.database)
|
|
46
|
+
.update(organisations)
|
|
47
|
+
.set({ ...state, ...stampUpdate(this.scope, this.clock) })
|
|
48
|
+
.where(sameVersion)
|
|
49
|
+
.returning({ id: organisations.id });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The executor of the unit of work, which every statement of this file goes through. */
|
|
54
|
+
function executorOf(database: Database): ReturnType<Database['executor']> {
|
|
55
|
+
{{#if tenancy.any}}
|
|
56
|
+
// ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
|
|
57
|
+
{{/if}}
|
|
58
|
+
return database.executor();
|
|
59
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { and, asc, count, desc, eq, isNull } from 'drizzle-orm';
|
|
2
|
+
import { InvalidPageRequestError, pageOf, type PageRequest } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import { Repository } from '@ddd-ts-kit/kernel/declarations';
|
|
4
|
+
import { SearchMembersReader } from '{{alias.context}}/application/ports/search-members-reader.port{{alias.ext}}';
|
|
5
|
+
import type {
|
|
6
|
+
SearchMembersResult,
|
|
7
|
+
SearchMembersSort,
|
|
8
|
+
} from '{{alias.context}}/application/queries/search-members/search-members.result{{alias.ext}}';
|
|
9
|
+
import type { Database } from '{{alias.platform}}/database/database{{alias.ext}}';
|
|
10
|
+
import { accounts, members } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
|
|
11
|
+
|
|
12
|
+
/** The current members of an organisation joined to the profile of their account: one statement, the page applied by the database. */
|
|
13
|
+
@Repository(SearchMembersReader)
|
|
14
|
+
export class DrizzleSearchMembersReader implements SearchMembersReader {
|
|
15
|
+
constructor(private readonly database: Database) {}
|
|
16
|
+
|
|
17
|
+
async search(organisationId: string, page: PageRequest<SearchMembersSort>): Promise<SearchMembersResult> {
|
|
18
|
+
if (page.kind !== 'offset') {
|
|
19
|
+
throw new InvalidPageRequestError('The members of an organisation are read by offset');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const order = page.sort[0]?.direction === 'desc' ? desc : asc;
|
|
23
|
+
const current = and(eq(members.organisationId, organisationId), isNull(members.removedAt));
|
|
24
|
+
|
|
25
|
+
const items = await executorOf(this.database)
|
|
26
|
+
.select({
|
|
27
|
+
memberId: members.id,
|
|
28
|
+
accountId: members.accountId,
|
|
29
|
+
displayName: accounts.displayName,
|
|
30
|
+
email: accounts.email,
|
|
31
|
+
role: members.role,
|
|
32
|
+
joinedAt: members.joinedAt,
|
|
33
|
+
})
|
|
34
|
+
.from(members)
|
|
35
|
+
.innerJoin(accounts, eq(accounts.id, members.accountId))
|
|
36
|
+
.where(current)
|
|
37
|
+
.orderBy(order(members.joinedAt), order(members.id))
|
|
38
|
+
.limit(page.limit)
|
|
39
|
+
.offset(page.offset);
|
|
40
|
+
|
|
41
|
+
const [counted] = await executorOf(this.database).select({ total: count() }).from(members).where(current);
|
|
42
|
+
|
|
43
|
+
return pageOf(items, { total: counted?.total ?? 0 });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The executor of the unit of work, which every statement of this file goes through. */
|
|
48
|
+
function executorOf(database: Database): ReturnType<Database['executor']> {
|
|
49
|
+
{{#if tenancy.any}}
|
|
50
|
+
// ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
|
|
51
|
+
{{/if}}
|
|
52
|
+
return database.executor();
|
|
53
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AuditRow } from '@ddd-ts-kit/knex';
|
|
2
|
+
import type { InvitationStatus } from '{{alias.context}}/domain/vocabularies/invitation-status.vocabulary{{alias.ext}}';
|
|
3
|
+
import type { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
|
|
4
|
+
|
|
5
|
+
export interface InvitationColumns {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly organisation_id: string;
|
|
8
|
+
readonly email: string;
|
|
9
|
+
readonly role: MemberRole;
|
|
10
|
+
readonly token_hash: string;
|
|
11
|
+
readonly invited_by: string;
|
|
12
|
+
readonly issued_at: Date;
|
|
13
|
+
readonly expires_at: Date;
|
|
14
|
+
readonly status: InvitationStatus;
|
|
15
|
+
readonly answered_at: Date | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface InvitationRow extends InvitationColumns, AuditRow {
|
|
19
|
+
readonly version: number;
|
|
20
|
+
}
|