ddd-ts-kit 0.13.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 +34 -3
- package/dist/check/catalogue/architecture-rules.d.ts.map +1 -1
- package/dist/check/catalogue/architecture-rules.js +2 -0
- package/dist/check/catalogue/architecture-rules.js.map +1 -1
- package/dist/check/errors/kit-error-codes.d.ts.map +1 -1
- package/dist/check/errors/kit-error-codes.js +1 -0
- package/dist/check/errors/kit-error-codes.js.map +1 -1
- package/dist/check/explain/authz-docs.d.ts.map +1 -1
- package/dist/check/explain/authz-docs.js +12 -0
- package/dist/check/explain/authz-docs.js.map +1 -1
- package/dist/check/explain/errors-docs.js +1 -1
- package/dist/check/explain/errors-docs.js.map +1 -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/authz-fixtures.d.ts.map +1 -1
- package/dist/check/fixtures/authz-fixtures.js +38 -1
- package/dist/check/fixtures/authz-fixtures.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/rules/authz-fields-by-schema.d.ts +3 -0
- package/dist/check/rules/authz-fields-by-schema.d.ts.map +1 -0
- package/dist/check/rules/authz-fields-by-schema.js +33 -0
- package/dist/check/rules/authz-fields-by-schema.js.map +1 -0
- 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 +13 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/parse-stack.d.ts.map +1 -1
- package/dist/config/parse-stack.js +3 -1
- package/dist/config/parse-stack.js.map +1 -1
- package/dist/config/stack.d.ts +14 -0
- package/dist/config/stack.d.ts.map +1 -1
- package/dist/config/stack.js +2 -0
- package/dist/config/stack.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/describe/context-description.d.ts +6 -0
- package/dist/describe/context-description.d.ts.map +1 -1
- package/dist/describe/format-description.js +9 -1
- package/dist/describe/format-description.js.map +1 -1
- package/dist/describe/presentation-of-route.d.ts +4 -0
- package/dist/describe/presentation-of-route.d.ts.map +1 -0
- package/dist/describe/presentation-of-route.js +15 -0
- package/dist/describe/presentation-of-route.js.map +1 -0
- package/dist/describe/read-controllers.d.ts.map +1 -1
- package/dist/describe/read-controllers.js +4 -1
- package/dist/describe/read-controllers.js.map +1 -1
- package/dist/describe/read-routes.d.ts.map +1 -1
- package/dist/describe/read-routes.js +2 -0
- package/dist/describe/read-routes.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/form-kind.d.ts +3 -0
- package/dist/generate/kinds/form-kind.d.ts.map +1 -0
- package/dist/generate/kinds/form-kind.js +38 -0
- package/dist/generate/kinds/form-kind.js.map +1 -0
- 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/presentation/controller-of.d.ts +10 -0
- package/dist/generate/kinds/presentation/controller-of.d.ts.map +1 -0
- package/dist/generate/kinds/presentation/controller-of.js +13 -0
- package/dist/generate/kinds/presentation/controller-of.js.map +1 -0
- package/dist/generate/kinds/presentation/controller-source.d.ts +13 -0
- package/dist/generate/kinds/presentation/controller-source.d.ts.map +1 -0
- package/dist/generate/kinds/presentation/controller-source.js +44 -0
- package/dist/generate/kinds/presentation/controller-source.js.map +1 -0
- package/dist/generate/kinds/representation-kind.d.ts +3 -0
- package/dist/generate/kinds/representation-kind.d.ts.map +1 -0
- package/dist/generate/kinds/representation-kind.js +54 -0
- package/dist/generate/kinds/representation-kind.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/generate/kinds.d.ts.map +1 -1
- package/dist/generate/kinds.js +4 -0
- package/dist/generate/kinds.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 +3 -2
- package/dist/help/usage.js.map +1 -1
- package/dist/init/files/platform-files.js +3 -1
- package/dist/init/files/platform-files.js.map +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/init/stack/resolve-stack.d.ts.map +1 -1
- package/dist/init/stack/resolve-stack.js +15 -1
- package/dist/init/stack/resolve-stack.js.map +1 -1
- package/dist/init/stack/stack-options.d.ts +2 -0
- package/dist/init/stack/stack-options.d.ts.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/stack/stack-vars.js +19 -2
- package/dist/stack/stack-vars.js.map +1 -1
- 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/docs/openapi.json.tpl +15 -0
- package/templates/init/CLAUDE.md.tpl +2 -2
- package/templates/init/platform/auth/http-boundary.ts.tpl +20 -3
- package/templates/init/platform/compose-application.manual.ts.tpl +18 -0
- package/templates/init/platform/compose-application.tsyringe.ts.tpl +23 -0
- package/templates/init/platform/http/presentation-of.ts.tpl +27 -0
- package/templates/init/platform/http/start-http-server.express.ts.tpl +1 -1
- package/templates/init/platform/http/start-http-server.fastify.ts.tpl +1 -1
- package/templates/init/platform/http/start-http-server.hono.ts.tpl +1 -1
- package/templates/init/platform/http/start-http-server.nest.ts.tpl +2 -2
- package/templates/init/platform/http/with-documentation.ts.tpl +13 -3
- package/templates/init/platform/http/with-forms.ts.tpl +7 -0
- package/templates/init/rules/application.md.tpl +1 -1
- package/templates/init/rules/presentation.md.tpl +1 -1
- package/templates/init/scripts/openapi.ts.tpl +27 -6
- package/templates/pipeline/biome.json.tpl +1 -0
- 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,79 @@
|
|
|
1
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
2
|
+
import type { Clock } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import { CommandHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
4
|
+
import type { Member } from '{{alias.context}}/domain/aggregates/member.aggregate{{alias.ext}}';
|
|
5
|
+
import type { Organisation } from '{{alias.context}}/domain/aggregates/organisation.aggregate{{alias.ext}}';
|
|
6
|
+
import { MemberError } from '{{alias.context}}/domain/errors/member.error{{alias.ext}}';
|
|
7
|
+
import { OrganisationError } from '{{alias.context}}/domain/errors/organisation.error{{alias.ext}}';
|
|
8
|
+
import type { DelegationRepository } from '{{alias.context}}/domain/repositories/delegation.repository{{alias.ext}}';
|
|
9
|
+
import type { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
10
|
+
import type { OrganisationRepository } from '{{alias.context}}/domain/repositories/organisation.repository{{alias.ext}}';
|
|
11
|
+
import { RoleRightsService, type HeldRights } from '{{alias.context}}/domain/services/role-rights.service{{alias.ext}}';
|
|
12
|
+
import { MemberRight } from '{{alias.context}}/domain/vocabularies/member-right.vocabulary{{alias.ext}}';
|
|
13
|
+
import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
|
|
14
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
15
|
+
import { RemoveMemberCommand } from '{{alias.context}}/application/commands/remove-member/remove-member.command{{alias.ext}}';
|
|
16
|
+
|
|
17
|
+
@CommandHandler(RemoveMemberCommand)
|
|
18
|
+
export class RemoveMemberHandler implements CommandHandler<RemoveMemberCommand> {
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly organisationRepository: OrganisationRepository,
|
|
21
|
+
private readonly memberRepository: MemberRepository,
|
|
22
|
+
private readonly delegationRepository: DelegationRepository,
|
|
23
|
+
private readonly access: Access,
|
|
24
|
+
private readonly clock: Clock,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Within the role of who decides; the organisation is saved with the removal, unchanged, so that
|
|
29
|
+
* its version orders the changes of its owners. Every delegation the member gave or received in
|
|
30
|
+
* the organisation ends with the membership.
|
|
31
|
+
*/
|
|
32
|
+
async execute({ actor, organisationId, accountId }: RemoveMemberCommand): Promise<string> {
|
|
33
|
+
const now = this.clock.now();
|
|
34
|
+
const held = await this.heldBy(organisationId, actor.id, now);
|
|
35
|
+
const rights = new RoleRightsService();
|
|
36
|
+
|
|
37
|
+
await this.access.authorize(actor, {{context.camel}}Permissions['member.remove'], rights.rightsIn(organisationId, actor.id, held));
|
|
38
|
+
|
|
39
|
+
const { organisation, member } = await this.membershipIn(organisationId, accountId);
|
|
40
|
+
const delegationId = rights.delegationUsedFor(MemberRight.ManageMembers, held);
|
|
41
|
+
const owners = await this.memberRepository.countOwners(organisationId);
|
|
42
|
+
|
|
43
|
+
member.remove({ by: held.member?.role ?? MemberRole.Member, byAccount: actor.id, delegationId, owners }, now);
|
|
44
|
+
await this.organisationRepository.save(organisation);
|
|
45
|
+
await this.memberRepository.save(member);
|
|
46
|
+
await this.endDelegationsInvolving(organisationId, accountId, now);
|
|
47
|
+
|
|
48
|
+
return member.id;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async heldBy(organisationId: string, accountId: string, now: Date): Promise<HeldRights> {
|
|
52
|
+
const member = await this.memberRepository.findOf(organisationId, accountId);
|
|
53
|
+
|
|
54
|
+
return { member, lent: await this.delegationRepository.findUnendedTo(organisationId, accountId), now };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The organisation, to be saved with the change, and the membership the change is about. */
|
|
58
|
+
private async membershipIn(organisationId: string, accountId: string): Promise<{ organisation: Organisation; member: Member }> {
|
|
59
|
+
const organisation = await this.organisationRepository.findById(organisationId);
|
|
60
|
+
const member = await this.memberRepository.findOf(organisationId, accountId);
|
|
61
|
+
|
|
62
|
+
if (organisation === undefined) {
|
|
63
|
+
throw OrganisationError.notFound(organisationId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (member === undefined) {
|
|
67
|
+
throw MemberError.notFound(organisationId, accountId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { organisation, member };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async endDelegationsInvolving(organisationId: string, accountId: string, now: Date): Promise<void> {
|
|
74
|
+
for (const delegation of await this.delegationRepository.findUnendedInvolving(organisationId, accountId)) {
|
|
75
|
+
delegation.end(now);
|
|
76
|
+
await this.delegationRepository.save(delegation);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AuthorizedCommand, type Actor } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { Requires } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
4
|
+
|
|
5
|
+
/** Ends a delegation of an organisation before its period. */
|
|
6
|
+
@Requires({{context.camel}}Permissions['delegation.revoke'], { resource: () => ({ type: 'context', id: '{{context.kebab}}' }) })
|
|
7
|
+
export class RevokeDelegationCommand extends AuthorizedCommand<string> {
|
|
8
|
+
constructor(
|
|
9
|
+
actor: Actor,
|
|
10
|
+
readonly organisationId: string,
|
|
11
|
+
readonly delegationId: string,
|
|
12
|
+
) {
|
|
13
|
+
super(actor);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
2
|
+
import type { Clock } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import { CommandHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
4
|
+
import { DelegationError } from '{{alias.context}}/domain/errors/delegation.error{{alias.ext}}';
|
|
5
|
+
import type { DelegationRepository } from '{{alias.context}}/domain/repositories/delegation.repository{{alias.ext}}';
|
|
6
|
+
import type { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
7
|
+
import { RoleRightsService } from '{{alias.context}}/domain/services/role-rights.service{{alias.ext}}';
|
|
8
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
9
|
+
import { RevokeDelegationCommand } from '{{alias.context}}/application/commands/revoke-delegation/revoke-delegation.command{{alias.ext}}';
|
|
10
|
+
|
|
11
|
+
@CommandHandler(RevokeDelegationCommand)
|
|
12
|
+
export class RevokeDelegationHandler implements CommandHandler<RevokeDelegationCommand> {
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly delegationRepository: DelegationRepository,
|
|
15
|
+
private readonly memberRepository: MemberRepository,
|
|
16
|
+
private readonly access: Access,
|
|
17
|
+
private readonly clock: Clock,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
/** Whoever may grant delegations in the organisation revokes them; one of another organisation is not found here. */
|
|
21
|
+
async execute({ actor, organisationId, delegationId }: RevokeDelegationCommand): Promise<string> {
|
|
22
|
+
const now = this.clock.now();
|
|
23
|
+
const member = await this.memberRepository.findOf(organisationId, actor.id);
|
|
24
|
+
const rights = new RoleRightsService().rightsIn(organisationId, actor.id, { member, lent: [], now });
|
|
25
|
+
|
|
26
|
+
await this.access.authorize(actor, {{context.camel}}Permissions['delegation.revoke'], rights);
|
|
27
|
+
|
|
28
|
+
const delegation = await this.delegationRepository.findById(delegationId);
|
|
29
|
+
|
|
30
|
+
if (delegation?.organisationId !== organisationId) {
|
|
31
|
+
throw DelegationError.notFound(delegationId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
delegation.revoke(now);
|
|
35
|
+
await this.delegationRepository.save(delegation);
|
|
36
|
+
|
|
37
|
+
return delegation.id;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AuthorizedCommand, type Actor } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { Requires } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
4
|
+
|
|
5
|
+
/** Takes back an invitation of an organisation that was not answered. */
|
|
6
|
+
@Requires({{context.camel}}Permissions['invitation.revoke'], { resource: () => ({ type: 'context', id: '{{context.kebab}}' }) })
|
|
7
|
+
export class RevokeInvitationCommand extends AuthorizedCommand<string> {
|
|
8
|
+
constructor(
|
|
9
|
+
actor: Actor,
|
|
10
|
+
readonly organisationId: string,
|
|
11
|
+
readonly invitationId: string,
|
|
12
|
+
) {
|
|
13
|
+
super(actor);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
2
|
+
import type { Clock } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import { CommandHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
4
|
+
import { InvitationError } from '{{alias.context}}/domain/errors/invitation.error{{alias.ext}}';
|
|
5
|
+
import type { DelegationRepository } from '{{alias.context}}/domain/repositories/delegation.repository{{alias.ext}}';
|
|
6
|
+
import type { InvitationRepository } from '{{alias.context}}/domain/repositories/invitation.repository{{alias.ext}}';
|
|
7
|
+
import type { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
8
|
+
import { RoleRightsService } from '{{alias.context}}/domain/services/role-rights.service{{alias.ext}}';
|
|
9
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
10
|
+
import { RevokeInvitationCommand } from '{{alias.context}}/application/commands/revoke-invitation/revoke-invitation.command{{alias.ext}}';
|
|
11
|
+
|
|
12
|
+
@CommandHandler(RevokeInvitationCommand)
|
|
13
|
+
export class RevokeInvitationHandler implements CommandHandler<RevokeInvitationCommand> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly invitationRepository: InvitationRepository,
|
|
16
|
+
private readonly memberRepository: MemberRepository,
|
|
17
|
+
private readonly delegationRepository: DelegationRepository,
|
|
18
|
+
private readonly access: Access,
|
|
19
|
+
private readonly clock: Clock,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/** Whoever may invite in the organisation takes back its invitations: an invitation of another organisation is not found here. */
|
|
23
|
+
async execute({ actor, organisationId, invitationId }: RevokeInvitationCommand): Promise<string> {
|
|
24
|
+
const now = this.clock.now();
|
|
25
|
+
const member = await this.memberRepository.findOf(organisationId, actor.id);
|
|
26
|
+
const lent = await this.delegationRepository.findUnendedTo(organisationId, actor.id);
|
|
27
|
+
const rights = new RoleRightsService().rightsIn(organisationId, actor.id, { member, lent, now });
|
|
28
|
+
|
|
29
|
+
await this.access.authorize(actor, {{context.camel}}Permissions['invitation.revoke'], rights);
|
|
30
|
+
|
|
31
|
+
const invitation = await this.invitationRepository.findById(invitationId);
|
|
32
|
+
|
|
33
|
+
if (invitation?.organisationId !== organisationId) {
|
|
34
|
+
throw InvitationError.notFound();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
invitation.revoke(now);
|
|
38
|
+
await this.invitationRepository.save(invitation);
|
|
39
|
+
|
|
40
|
+
return invitation.id;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
2
|
+
import { accountDeactivatedV1, type AccountDeactivatedV1 } from '{{alias.context}}/published-language/account-deactivated.v1.integration-event{{alias.ext}}';
|
|
3
|
+
import type { IdentityProviderAdmin } from '{{alias.context}}/application/ports/identity-provider-admin.port{{alias.ext}}';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The provider disables the person: they sign in no more. Delivered at least once through the
|
|
7
|
+
* outbox, so a provider down at the time of the deactivation is asked again until it answers;
|
|
8
|
+
* disabling twice changes nothing.
|
|
9
|
+
*/
|
|
10
|
+
@IntegrationEventHandler(accountDeactivatedV1, { consumer: '{{context.kebab}}.provider-disables-account' })
|
|
11
|
+
export class AccountDeactivatedIntegrationHandler {
|
|
12
|
+
static readonly eventType = accountDeactivatedV1.type;
|
|
13
|
+
|
|
14
|
+
constructor(private readonly identityProvider: IdentityProviderAdmin) {}
|
|
15
|
+
|
|
16
|
+
async handle(event: AccountDeactivatedV1): Promise<void> {
|
|
17
|
+
await this.identityProvider.disableAccount(event.payload.accountId);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
2
|
+
import type { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
3
|
+
import { memberJoinedV1, type MemberJoinedV1 } from '{{alias.context}}/published-language/member-joined.v1.integration-event{{alias.ext}}';
|
|
4
|
+
import type { IdentityProviderAdmin } from '{{alias.context}}/application/ports/identity-provider-admin.port{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider follows the membership: delivered at least once and in no guaranteed order, so the
|
|
8
|
+
* handler brings the provider in line with what the context holds now rather than with the fact
|
|
9
|
+
* alone. A membership already ended (removed, or its account deactivated) is taken out again,
|
|
10
|
+
* so that a late retry of this fact never gives a removed person back their organisation.
|
|
11
|
+
*/
|
|
12
|
+
@IntegrationEventHandler(memberJoinedV1, { consumer: '{{context.kebab}}.provider-follows-joined-member' })
|
|
13
|
+
export class MemberJoinedIntegrationHandler {
|
|
14
|
+
static readonly eventType = memberJoinedV1.type;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly memberRepository: MemberRepository,
|
|
18
|
+
private readonly identityProvider: IdentityProviderAdmin,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async handle(event: MemberJoinedV1): Promise<void> {
|
|
22
|
+
const { organisationId, accountId } = event.payload;
|
|
23
|
+
const current = await this.memberRepository.findOf(organisationId, accountId);
|
|
24
|
+
|
|
25
|
+
if (current === undefined) {
|
|
26
|
+
await this.identityProvider.removeMember(organisationId, accountId);
|
|
27
|
+
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await this.identityProvider.addMember(organisationId, accountId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
2
|
+
import type { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
3
|
+
import { memberRemovedV1, type MemberRemovedV1 } from '{{alias.context}}/published-language/member-removed.v1.integration-event{{alias.ext}}';
|
|
4
|
+
import type { IdentityProviderAdmin } from '{{alias.context}}/application/ports/identity-provider-admin.port{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The provider takes the person out of the organisation: their next token no longer names it,
|
|
8
|
+
* and with tenants they reach its data no more. Delivered at least once through the outbox, so a
|
|
9
|
+
* provider down at the time of the removal is asked again until it answers; a person who joined
|
|
10
|
+
* again since is left in, the context holding the membership now.
|
|
11
|
+
*/
|
|
12
|
+
@IntegrationEventHandler(memberRemovedV1, { consumer: '{{context.kebab}}.provider-follows-removed-member' })
|
|
13
|
+
export class MemberRemovedIntegrationHandler {
|
|
14
|
+
static readonly eventType = memberRemovedV1.type;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly memberRepository: MemberRepository,
|
|
18
|
+
private readonly identityProvider: IdentityProviderAdmin,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async handle(event: MemberRemovedV1): Promise<void> {
|
|
22
|
+
const { organisationId, accountId } = event.payload;
|
|
23
|
+
|
|
24
|
+
if ((await this.memberRepository.findOf(organisationId, accountId)) !== undefined) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await this.identityProvider.removeMember(organisationId, accountId);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IntegrationEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
2
|
+
import { organisationOpenedV1, type OrganisationOpenedV1 } from '{{alias.context}}/published-language/organisation-opened.v1.integration-event{{alias.ext}}';
|
|
3
|
+
import type { IdentityProviderAdmin } from '{{alias.context}}/application/ports/identity-provider-admin.port{{alias.ext}}';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The provider learns the organisation under its slug, so that its tokens can name it. Driven by
|
|
7
|
+
* the fact the context published through its outbox, so delivered at least once: a provider that
|
|
8
|
+
* fails is asked again until it answers, and asking twice changes nothing (the adapter is idempotent).
|
|
9
|
+
*/
|
|
10
|
+
@IntegrationEventHandler(organisationOpenedV1, { consumer: '{{context.kebab}}.provider-opens-organisation' })
|
|
11
|
+
export class OrganisationOpenedIntegrationHandler {
|
|
12
|
+
static readonly eventType = organisationOpenedV1.type;
|
|
13
|
+
|
|
14
|
+
constructor(private readonly identityProvider: IdentityProviderAdmin) {}
|
|
15
|
+
|
|
16
|
+
async handle(event: OrganisationOpenedV1): Promise<void> {
|
|
17
|
+
await this.identityProvider.openOrganisation({ id: event.payload.organisationId, name: event.payload.name });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { AccountDeactivated } from '{{alias.context}}/domain/events/account-deactivated.event{{alias.ext}}';
|
|
4
|
+
import { accountDeactivatedV1 } from '{{alias.context}}/published-language/account-deactivated.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(AccountDeactivated, { name: AccountDeactivated.eventName })
|
|
8
|
+
export class AccountDeactivatedPublisher implements DomainEventHandler<AccountDeactivated> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: AccountDeactivated): Promise<void> {
|
|
15
|
+
const published = accountDeactivatedV1.create(
|
|
16
|
+
{
|
|
17
|
+
accountId: event.aggregateId,
|
|
18
|
+
},
|
|
19
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
await this.publisher.publish([published]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { DelegationGranted } from '{{alias.context}}/domain/events/delegation-granted.event{{alias.ext}}';
|
|
4
|
+
import { delegationGrantedV1 } from '{{alias.context}}/published-language/delegation-granted.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(DelegationGranted, { name: DelegationGranted.eventName })
|
|
8
|
+
export class DelegationGrantedPublisher implements DomainEventHandler<DelegationGranted> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: DelegationGranted): Promise<void> {
|
|
15
|
+
const published = delegationGrantedV1.create(
|
|
16
|
+
{
|
|
17
|
+
delegationId: event.aggregateId,
|
|
18
|
+
organisationId: event.facts.organisationId,
|
|
19
|
+
delegatorId: event.facts.delegatorId,
|
|
20
|
+
delegateId: event.facts.delegateId,
|
|
21
|
+
rights: [...event.facts.rights],
|
|
22
|
+
startsAt: event.facts.startsAt.toISOString(),
|
|
23
|
+
endsAt: event.facts.endsAt.toISOString(),
|
|
24
|
+
},
|
|
25
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
{{#if tenancy.any}}
|
|
29
|
+
// The organisation is the tenant of the fact: its consumers run in it, whoever acted.
|
|
30
|
+
await this.publisher.publish([{ ...published, tenantId: event.facts.organisationId }]);
|
|
31
|
+
{{/if}}
|
|
32
|
+
{{#unless tenancy.any}}
|
|
33
|
+
await this.publisher.publish([published]);
|
|
34
|
+
{{/unless}}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { DelegationRevoked } from '{{alias.context}}/domain/events/delegation-revoked.event{{alias.ext}}';
|
|
4
|
+
import { delegationRevokedV1 } from '{{alias.context}}/published-language/delegation-revoked.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(DelegationRevoked, { name: DelegationRevoked.eventName })
|
|
8
|
+
export class DelegationRevokedPublisher implements DomainEventHandler<DelegationRevoked> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: DelegationRevoked): Promise<void> {
|
|
15
|
+
const published = delegationRevokedV1.create(
|
|
16
|
+
{
|
|
17
|
+
delegationId: event.aggregateId,
|
|
18
|
+
organisationId: event.facts.organisationId,
|
|
19
|
+
delegatorId: event.facts.delegatorId,
|
|
20
|
+
delegateId: event.facts.delegateId,
|
|
21
|
+
},
|
|
22
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
{{#if tenancy.any}}
|
|
26
|
+
// The organisation is the tenant of the fact: its consumers run in it, whoever acted.
|
|
27
|
+
await this.publisher.publish([{ ...published, tenantId: event.facts.organisationId }]);
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{#unless tenancy.any}}
|
|
30
|
+
await this.publisher.publish([published]);
|
|
31
|
+
{{/unless}}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { MemberJoined } from '{{alias.context}}/domain/events/member-joined.event{{alias.ext}}';
|
|
4
|
+
import { memberJoinedV1 } from '{{alias.context}}/published-language/member-joined.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(MemberJoined, { name: MemberJoined.eventName })
|
|
8
|
+
export class MemberJoinedPublisher implements DomainEventHandler<MemberJoined> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: MemberJoined): Promise<void> {
|
|
15
|
+
const published = memberJoinedV1.create(
|
|
16
|
+
{
|
|
17
|
+
memberId: event.aggregateId,
|
|
18
|
+
organisationId: event.facts.organisationId,
|
|
19
|
+
accountId: event.facts.accountId,
|
|
20
|
+
role: event.facts.role,
|
|
21
|
+
},
|
|
22
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
{{#if tenancy.any}}
|
|
26
|
+
// The organisation is the tenant of the fact: its consumers run in it, whoever acted.
|
|
27
|
+
await this.publisher.publish([{ ...published, tenantId: event.facts.organisationId }]);
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{#unless tenancy.any}}
|
|
30
|
+
await this.publisher.publish([published]);
|
|
31
|
+
{{/unless}}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { MemberRemoved } from '{{alias.context}}/domain/events/member-removed.event{{alias.ext}}';
|
|
4
|
+
import { memberRemovedV1 } from '{{alias.context}}/published-language/member-removed.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(MemberRemoved, { name: MemberRemoved.eventName })
|
|
8
|
+
export class MemberRemovedPublisher implements DomainEventHandler<MemberRemoved> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: MemberRemoved): Promise<void> {
|
|
15
|
+
const published = memberRemovedV1.create(
|
|
16
|
+
{
|
|
17
|
+
memberId: event.aggregateId,
|
|
18
|
+
organisationId: event.facts.organisationId,
|
|
19
|
+
accountId: event.facts.accountId,
|
|
20
|
+
...(event.facts.delegationId === undefined ? {} : { delegationId: event.facts.delegationId }),
|
|
21
|
+
},
|
|
22
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
{{#if tenancy.any}}
|
|
26
|
+
// The organisation is the tenant of the fact: its consumers run in it, whoever acted.
|
|
27
|
+
await this.publisher.publish([{ ...published, tenantId: event.facts.organisationId }]);
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{#unless tenancy.any}}
|
|
30
|
+
await this.publisher.publish([published]);
|
|
31
|
+
{{/unless}}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { MemberRoleChanged } from '{{alias.context}}/domain/events/member-role-changed.event{{alias.ext}}';
|
|
4
|
+
import { memberRoleChangedV1 } from '{{alias.context}}/published-language/member-role-changed.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(MemberRoleChanged, { name: MemberRoleChanged.eventName })
|
|
8
|
+
export class MemberRoleChangedPublisher implements DomainEventHandler<MemberRoleChanged> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: MemberRoleChanged): Promise<void> {
|
|
15
|
+
const published = memberRoleChangedV1.create(
|
|
16
|
+
{
|
|
17
|
+
memberId: event.aggregateId,
|
|
18
|
+
organisationId: event.facts.organisationId,
|
|
19
|
+
accountId: event.facts.accountId,
|
|
20
|
+
from: event.facts.from,
|
|
21
|
+
to: event.facts.to,
|
|
22
|
+
...(event.facts.delegationId === undefined ? {} : { delegationId: event.facts.delegationId }),
|
|
23
|
+
},
|
|
24
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
{{#if tenancy.any}}
|
|
28
|
+
// The organisation is the tenant of the fact: its consumers run in it, whoever acted.
|
|
29
|
+
await this.publisher.publish([{ ...published, tenantId: event.facts.organisationId }]);
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{#unless tenancy.any}}
|
|
32
|
+
await this.publisher.publish([published]);
|
|
33
|
+
{{/unless}}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { IdGenerator, IntegrationEventPublisher } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { OrganisationOpened } from '{{alias.context}}/domain/events/organisation-opened.event{{alias.ext}}';
|
|
4
|
+
import { organisationOpenedV1 } from '{{alias.context}}/published-language/organisation-opened.v1.integration-event{{alias.ext}}';
|
|
5
|
+
|
|
6
|
+
/** Tells the other contexts, through the outbox, in the transaction of the fact. */
|
|
7
|
+
@DomainEventHandler(OrganisationOpened, { name: OrganisationOpened.eventName })
|
|
8
|
+
export class OrganisationOpenedPublisher implements DomainEventHandler<OrganisationOpened> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly publisher: IntegrationEventPublisher,
|
|
11
|
+
private readonly ids: IdGenerator,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async handle(event: OrganisationOpened): Promise<void> {
|
|
15
|
+
const published = organisationOpenedV1.create(
|
|
16
|
+
{
|
|
17
|
+
organisationId: event.aggregateId,
|
|
18
|
+
name: event.facts.name,
|
|
19
|
+
openedBy: event.facts.openedBy,
|
|
20
|
+
},
|
|
21
|
+
{ id: this.ids.next(), occurredAt: event.occurredAt },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
{{#if tenancy.any}}
|
|
25
|
+
// The organisation is the tenant of the fact: its consumers run in it, whoever acted.
|
|
26
|
+
await this.publisher.publish([{ ...published, tenantId: event.aggregateId }]);
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#unless tenancy.any}}
|
|
29
|
+
await this.publisher.publish([published]);
|
|
30
|
+
{{/unless}}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/templates/presets/identity/context/application/ports/identity-provider-admin.port.ts.tpl
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The administration API of the identity provider: it authenticates, and follows what this
|
|
3
|
+
* context decides, so that its tokens name the organisations of each person and a deactivated
|
|
4
|
+
* account signs in no more. Called by the consumers of the facts the context publishes through its
|
|
5
|
+
* outbox (`application/integration/handlers/`), so at least once and in no guaranteed order: every
|
|
6
|
+
* method is idempotent, and a failure is retried by whoever delivers the fact.
|
|
7
|
+
*/
|
|
8
|
+
export abstract class IdentityProviderAdmin {
|
|
9
|
+
/** The provider knows the organisation under its slug, which its tokens then name. */
|
|
10
|
+
abstract openOrganisation(organisation: { readonly id: string; readonly name: string }): Promise<void>;
|
|
11
|
+
|
|
12
|
+
abstract addMember(organisationId: string, accountId: string): Promise<void>;
|
|
13
|
+
|
|
14
|
+
abstract removeMember(organisationId: string, accountId: string): Promise<void>;
|
|
15
|
+
|
|
16
|
+
abstract disableAccount(accountId: string): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the administration API of the identity provider is, and the client it is called as: the
|
|
3
|
+
* settings of `KeycloakIdentityProviderAdmin`. The default adapter reads them from the
|
|
4
|
+
* environment when a call is made; the platform may bind a value of its validated config instead.
|
|
5
|
+
*/
|
|
6
|
+
export abstract class IdentityProviderConnection {
|
|
7
|
+
/** `baseUrl` of the provider, its realm, and a confidential client allowed to manage users and organisations. */
|
|
8
|
+
abstract settings(): Promise<{
|
|
9
|
+
readonly baseUrl: string;
|
|
10
|
+
readonly realm: string;
|
|
11
|
+
readonly clientId: string;
|
|
12
|
+
readonly clientSecret: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The tokens of the invitations: unguessable, sent to the person, never stored but as a digest. */
|
|
2
|
+
export abstract class InvitationTokens {
|
|
3
|
+
/** A new token, and the digest the invitation keeps of it. */
|
|
4
|
+
abstract issue(): { readonly token: string; readonly digest: string };
|
|
5
|
+
|
|
6
|
+
abstract digestOf(token: string): string;
|
|
7
|
+
}
|
package/templates/presets/identity/context/application/ports/search-members-reader.port.ts.tpl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PageRequest } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type {
|
|
3
|
+
SearchMembersResult,
|
|
4
|
+
SearchMembersSort,
|
|
5
|
+
} from '{{alias.context}}/application/queries/search-members/search-members.result{{alias.ext}}';
|
|
6
|
+
|
|
7
|
+
export abstract class SearchMembersReader {
|
|
8
|
+
/** One page of the current members of an organisation, with the profile of their account, by the instant they joined then by id. */
|
|
9
|
+
abstract search(organisationId: string, page: PageRequest<SearchMembersSort>): Promise<SearchMembersResult>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Access } from '@ddd-ts-kit/auth';
|
|
2
|
+
import type { Clock } from '@ddd-ts-kit/kernel';
|
|
3
|
+
import { QueryHandler } from '@ddd-ts-kit/kernel/declarations';
|
|
4
|
+
import type { DelegationRepository } from '{{alias.context}}/domain/repositories/delegation.repository{{alias.ext}}';
|
|
5
|
+
import type { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
|
|
6
|
+
import { RoleRightsService } from '{{alias.context}}/domain/services/role-rights.service{{alias.ext}}';
|
|
7
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
8
|
+
import type { SearchMembersReader } from '{{alias.context}}/application/ports/search-members-reader.port{{alias.ext}}';
|
|
9
|
+
import { SearchMembersQuery } from '{{alias.context}}/application/queries/search-members/search-members.query{{alias.ext}}';
|
|
10
|
+
import type { SearchMembersResult } from '{{alias.context}}/application/queries/search-members/search-members.result{{alias.ext}}';
|
|
11
|
+
|
|
12
|
+
@QueryHandler(SearchMembersQuery)
|
|
13
|
+
export class SearchMembersHandler implements QueryHandler<SearchMembersQuery> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly reader: SearchMembersReader,
|
|
16
|
+
private readonly memberRepository: MemberRepository,
|
|
17
|
+
private readonly delegationRepository: DelegationRepository,
|
|
18
|
+
private readonly access: Access,
|
|
19
|
+
private readonly clock: Clock,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/** The members of an organisation are read by its members, and by nobody else: the read names the organisation the right was decided on. */
|
|
23
|
+
async execute({ actor, organisationId, page }: SearchMembersQuery): Promise<SearchMembersResult> {
|
|
24
|
+
const now = this.clock.now();
|
|
25
|
+
const member = await this.memberRepository.findOf(organisationId, actor.id);
|
|
26
|
+
const lent = await this.delegationRepository.findUnendedTo(organisationId, actor.id);
|
|
27
|
+
const rights = new RoleRightsService().rightsIn(organisationId, actor.id, { member, lent, now });
|
|
28
|
+
|
|
29
|
+
await this.access.authorize(actor, {{context.camel}}Permissions['member.search'], rights);
|
|
30
|
+
|
|
31
|
+
return this.reader.search(organisationId, page);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AuthorizedQuery, type Actor, type PageRequest } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { Requires } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { {{context.camel}}Permissions } from '{{alias.context}}/application/authorization/{{context.kebab}}.permissions{{alias.ext}}';
|
|
4
|
+
import type {
|
|
5
|
+
SearchMembersResult,
|
|
6
|
+
SearchMembersSort,
|
|
7
|
+
} from '{{alias.context}}/application/queries/search-members/search-members.result{{alias.ext}}';
|
|
8
|
+
|
|
9
|
+
/** One page of the current members of an organisation, for its members. */
|
|
10
|
+
@Requires({{context.camel}}Permissions['member.search'], { resource: () => ({ type: 'context', id: '{{context.kebab}}' }) })
|
|
11
|
+
export class SearchMembersQuery extends AuthorizedQuery<SearchMembersResult> {
|
|
12
|
+
constructor(
|
|
13
|
+
actor: Actor,
|
|
14
|
+
readonly organisationId: string,
|
|
15
|
+
readonly page: PageRequest<SearchMembersSort>,
|
|
16
|
+
) {
|
|
17
|
+
super(actor);
|
|
18
|
+
}
|
|
19
|
+
}
|