From 3af918c2aebd35aeb7d52373bd5434b96b7c3f29 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Wed, 11 Jun 2025 10:06:20 -0500 Subject: [PATCH 1/2] Increase prettier width 80 -> 100 --- .prettierrc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierrc.yml b/.prettierrc.yml index 3e9d085ced..fd0a379a18 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -1,2 +1,3 @@ singleQuote: true trailingComma: all +printWidth: 100 From 6919ba82473793fe0aec4db6081e1be79dfceeab Mon Sep 17 00:00:00 2001 From: Carson Full Date: Wed, 11 Jun 2025 10:06:35 -0500 Subject: [PATCH 2/2] Apply increased width to codebase --- dbschema/seeds/001.root-user.ts | 5 +- dbschema/seeds/008.projects.ts | 6 +- jest.config.ts | 4 +- src/common/and-call.ts | 6 +- src/common/augmented-metadata.pipe.ts | 4 +- src/common/calculated.decorator.ts | 3 +- src/common/discovery-unique-methods.ts | 4 +- .../exceptions/creation-failed.exception.ts | 10 +- .../date-override-conflict.exception.ts | 34 ++- src/common/exceptions/duplicate.exception.ts | 16 +- src/common/exceptions/input.exception.ts | 19 +- .../exceptions/unauthorized.exception.ts | 4 +- src/common/field-selection.ts | 9 +- src/common/firstLettersOfWords.ts | 16 +- src/common/fiscal-year.test.ts | 4 +- src/common/fiscal-year.ts | 19 +- src/common/generate-id.ts | 9 +- src/common/id-field.test.ts | 3 +- src/common/id-field.ts | 6 +- src/common/id.arg.ts | 3 +- src/common/index.ts | 8 +- src/common/instance-maps.ts | 8 +- src/common/luxon.graphql.ts | 4 +- src/common/markdown.scalar.ts | 4 +- src/common/mask-secrets.ts | 10 +- src/common/optional-field.ts | 9 +- src/common/pagination-list.ts | 3 +- src/common/pagination.input.ts | 7 +- src/common/required-when.ts | 3 +- src/common/resource.dto.ts | 85 ++---- src/common/retry.ts | 18 +- src/common/rich-text.scalar.ts | 14 +- src/common/role.dto.ts | 6 +- src/common/search-camel-case.ts | 16 +- src/common/secured-date.ts | 12 +- src/common/secured-list.ts | 6 +- src/common/secured-property.ts | 109 +++---- src/common/temporal/calendar-date.ts | 44 +-- src/common/temporal/date-interval.spec.ts | 76 ++--- src/common/temporal/date-interval.ts | 13 +- src/common/temporal/interval.ts | 8 +- src/common/trace-layer.ts | 12 +- src/common/transform.decorator.ts | 4 +- src/common/types.ts | 43 +-- src/common/url.field.ts | 11 +- src/common/url.util.ts | 5 +- src/common/validators/email.validator.ts | 3 +- .../iso-3166-1-alpha-3.validator.ts | 3 +- src/common/validators/short-id.validator.ts | 19 +- src/common/validators/validateBy.ts | 5 +- src/common/variant.dto.ts | 21 +- src/common/xlsx.util.ts | 40 +-- src/components/admin/admin.gel.repository.ts | 4 +- src/components/admin/admin.gel.service.ts | 14 +- src/components/admin/admin.repository.ts | 6 +- src/components/admin/admin.service.ts | 12 +- .../authorization/assignable-roles.granter.ts | 4 +- .../authorization/authorization.resolver.ts | 29 +- .../authorization/dto/power.enum.ts | 6 +- src/components/authorization/dto/role.dto.ts | 3 +- .../handler/can-impersonate.handler.ts | 4 +- .../engagements-create-delete.policy.ts | 5 +- ...can-retract-own-change-to-plan-approval.ts | 4 +- .../user-can-manage-own-comments.policy.ts | 4 +- .../user-can-manage-own-prompts.policy.ts | 8 +- .../by-feature/view-progress-report.policy.ts | 8 +- .../by-role/consultant-manager.policy.ts | 10 +- .../policies/by-role/consultant.policy.ts | 9 +- .../by-role/experience-operations.policy.ts | 4 +- .../policies/by-role/field-partner.policy.ts | 20 +- .../by-role/financial-analyst-lead.policy.ts | 11 +- .../by-role/financial-analyst.policy.ts | 132 ++++----- .../policies/by-role/fundraising.policy.ts | 5 +- .../policies/by-role/intern.policy.ts | 11 +- .../by-role/investor-common.policy.ts | 9 +- .../policies/by-role/marketing.policy.ts | 30 +- .../policies/by-role/mentor.policy.ts | 15 +- .../by-role/project-manager.policy.ts | 190 ++++++------- .../by-role/regional-director.policy.ts | 5 +- .../policies/by-role/translator.policy.ts | 44 +-- .../conditions/enum-field.condition.ts | 41 +-- .../policies/conditions/member.condition.ts | 11 +- .../role-and-exp-union.optimizer.ts | 9 +- .../policies/conditions/role.condition.ts | 3 +- .../policies/conditions/self.condition.ts | 4 +- .../conditions/sensitivity.condition.ts | 16 +- .../variant-and-exp-union.optimizer.ts | 9 +- .../policies/conditions/variant.condition.ts | 17 +- .../authorization/policy/actions.ts | 6 +- .../policy/builder/allow-all.helper.ts | 6 +- .../policy/builder/as-normalized.helper.ts | 3 +- .../builder/child-relationship-granter.ts | 17 +- .../policy/builder/granter.decorator.ts | 4 +- .../policy/builder/inherit-resource.helper.ts | 9 +- .../policy/builder/perm-granter.ts | 17 +- .../policy/builder/prop-granter.ts | 10 +- .../policy/builder/resource-granter.ts | 43 +-- .../policy/conditions/aggregate.condition.ts | 41 +-- .../policy/conditions/calculated.condition.ts | 8 +- .../policy/conditions/condition-visitor.ts | 5 +- .../policy/conditions/condition.interface.ts | 28 +- .../policy/conditions/eql.util.ts | 12 +- .../conditions/flatten-aggregate.optimizer.ts | 13 +- .../policy/conditions/optimizer.interface.ts | 7 +- .../policy/executor/all-permissions-view.ts | 38 +-- .../policy/executor/condition-optimizer.ts | 5 +- .../policy/executor/edge-privileges.ts | 26 +- .../policy/executor/policy-dumper.ts | 74 ++--- .../policy/executor/policy-executor.ts | 31 +- .../policy/executor/privileges.ts | 37 +-- .../policy/executor/resource-privileges.ts | 103 ++----- .../policy/gel-access-policy.generator.ts | 20 +- .../authorization/policy/granters.factory.ts | 12 +- src/components/authorization/policy/index.ts | 10 +- .../authorization/policy/policy.factory.ts | 95 ++----- .../budget/budget-record.repository.ts | 11 +- .../budget/budget-record.resolver.ts | 18 +- src/components/budget/budget.repository.ts | 10 +- src/components/budget/budget.resolver.ts | 9 +- src/components/budget/budget.service.ts | 36 +-- src/components/budget/dto/budget.dto.ts | 3 +- src/components/budget/dto/list-budget.dto.ts | 8 +- .../create-project-default-budget.handler.ts | 4 +- ...get-records-to-funding-partners.handler.ts | 44 +-- ...e-project-current-budget-status.handler.ts | 14 +- src/components/ceremony/ceremony.loader.ts | 4 +- .../ceremony/ceremony.repository.ts | 12 +- src/components/ceremony/ceremony.service.ts | 5 +- src/components/ceremony/dto/ceremony.dto.ts | 3 +- .../changeset/changeset-aware.resolver.ts | 13 +- src/components/changeset/changeset.arg.ts | 15 +- .../changeset/changeset.repository.ts | 16 +- .../changeset/changeset.resolver.ts | 12 +- .../changeset/commit-changeset-props.query.ts | 11 +- .../changeset/dto/changeset-diff.dto.ts | 6 +- .../changeset/dto/changeset.args.ts | 3 +- .../enforce-changeset-editable.pipe.ts | 19 +- .../events/changeset-finalizing.event.ts | 4 +- .../changeset/reject-changeset-props.query.ts | 5 +- .../comments/comment-thread.loader.ts | 9 +- .../comments/comment-thread.repository.ts | 11 +- .../comments/comment-thread.resolver.ts | 5 +- src/components/comments/comment.repository.ts | 25 +- src/components/comments/comment.resolver.ts | 12 +- src/components/comments/comment.service.ts | 10 +- .../comments/commentable.resolver.ts | 15 +- .../comments/create-comment.resolver.ts | 12 +- src/components/comments/dto/comment.dto.ts | 3 +- .../comments/dto/commentable.dto.ts | 6 +- .../comments/dto/list-comment-thread.dto.ts | 4 +- ...omment-via-mention-notification.service.ts | 5 +- ...mment-via-mention-notification.strategy.ts | 12 +- .../engagement/dto/create-engagement.dto.ts | 8 +- .../engagement/dto/engagement.dto.ts | 30 +- .../engagement/dto/list-engagements.dto.ts | 60 ++-- src/components/engagement/dto/status.enum.ts | 16 +- .../engagement/engagement-status.resolver.ts | 11 +- .../engagement/engagement.gel.repository.ts | 5 +- .../engagement/engagement.loader.ts | 12 +- .../engagement/engagement.repository.ts | 267 ++++-------------- .../engagement/engagement.resolver.ts | 49 +--- src/components/engagement/engagement.rules.ts | 53 +--- .../engagement/engagement.service.ts | 77 +---- ...nalized-changeset-to-engagement.handler.ts | 27 +- .../handlers/set-initial-end-date.handler.ts | 13 +- .../handlers/set-last-status-date.handler.ts | 4 +- .../update-engagement-status.handler.ts | 23 +- .../internship-engagement.resolver.ts | 4 +- .../internship-position.resolver.ts | 22 +- src/components/ethno-art/dto/ethno-art.dto.ts | 5 +- .../ethno-art/ethno-art.gel.repository.ts | 22 +- src/components/ethno-art/ethno-art.loader.ts | 4 +- .../ethno-art/ethno-art.repository.ts | 46 +-- src/components/ethno-art/ethno-art.service.ts | 12 +- .../dto/create-field-region.dto.ts | 3 +- .../field-region/dto/list-field-region.dto.ts | 4 +- .../field-region/field-region.loader.ts | 4 +- .../field-region/field-region.module.ts | 6 +- .../field-region/field-region.repository.ts | 4 +- .../field-region/field-region.resolver.ts | 17 +- .../field-zone/dto/list-field-zone.dto.ts | 4 +- .../field-zone/field-zone.loader.ts | 4 +- .../field-zone/field-zone.module.ts | 5 +- .../field-zone/field-zone.repository.ts | 15 +- .../field-zone/field-zone.resolver.ts | 13 +- .../file/bucket/composite-bucket.ts | 21 +- src/components/file/bucket/file-bucket.ts | 19 +- .../file/bucket/filesystem-bucket.ts | 11 +- src/components/file/bucket/local-bucket.ts | 16 +- src/components/file/bucket/parse-uri.ts | 4 +- src/components/file/bucket/s3-bucket.ts | 9 +- src/components/file/directory.resolver.ts | 13 +- .../file/dto/file-node-type.enum.ts | 3 +- src/components/file/dto/file.dto.ts | 11 +- src/components/file/dto/list.ts | 27 +- src/components/file/dto/upload.dto.ts | 3 +- src/components/file/file-node.loader.ts | 4 +- src/components/file/file-version.resolver.ts | 5 +- src/components/file/file.repository.ts | 63 +---- src/components/file/file.resolver.ts | 17 +- src/components/file/file.service.ts | 119 +++----- src/components/file/files-bucket.factory.ts | 8 +- .../attach-project-root-directory.handler.ts | 11 +- .../detach-project-root-directory.handler.ts | 9 +- .../file/local-bucket.controller.ts | 25 +- .../media/detect-existing-media.migration.ts | 15 +- .../file/media/events/can-update-event.ts | 4 +- .../media/media-by-file-version.loader.ts | 9 +- .../file/media/media-detector.service.ts | 10 +- src/components/file/media/media.dto.ts | 9 +- src/components/file/media/media.repository.ts | 36 +-- src/components/file/media/media.resolver.ts | 13 +- src/components/file/media/media.service.ts | 4 +- src/components/film/dto/film.dto.ts | 5 +- src/components/film/film.gel.repository.ts | 17 +- src/components/film/film.module.ts | 7 +- src/components/film/film.repository.ts | 51 +--- src/components/film/film.resolver.ts | 13 +- src/components/film/film.service.ts | 12 +- .../finance/department/dto/id-blocks.dto.ts | 4 +- .../finance/department/dto/id-blocks.input.ts | 26 +- .../finance/department/gel.utils.ts | 27 +- .../finance/department/id-block.resolver.ts | 4 +- .../finance/department/neo4j.utils.ts | 20 +- .../dto/list-funding-account.dto.ts | 4 +- .../funding-account.repository.ts | 4 +- .../funding-account.resolver.ts | 4 +- .../funding-account.service.ts | 12 +- .../dto/ai-assisted-translation.enum.ts | 9 +- .../language/dto/create-language.dto.ts | 8 +- src/components/language/dto/language.dto.ts | 7 +- .../language/dto/list-language.dto.ts | 3 +- .../ethnologue-language.repository.ts | 13 +- .../ethnologue-language.service.ts | 10 +- .../language/language.repository.ts | 60 +--- src/components/language/language.resolver.ts | 19 +- src/components/language/language.service.ts | 52 +--- .../location/dto/create-location.dto.ts | 8 +- .../location/dto/update-location.dto.ts | 8 +- .../location/location.gel.repository.ts | 11 +- src/components/location/location.loader.ts | 4 +- .../location/location.repository.ts | 53 +--- src/components/location/location.resolver.ts | 20 +- src/components/location/location.service.ts | 12 +- .../default-marketing-region.migration.ts | 3 +- .../system-notification.resolver.ts | 9 +- .../system-notification.strategy.ts | 3 +- .../dto/notification-list.input.ts | 7 +- .../notification.gel.repository.ts | 29 +- .../notifications/notification.module.ts | 5 +- .../notifications/notification.repository.ts | 33 +-- .../notifications/notification.resolver.ts | 4 +- .../notifications/notification.service.ts | 36 +-- .../organization/dto/list-organization.dto.ts | 8 +- .../dto/organization-reach.dto.ts | 4 +- .../organization/dto/organization-type.dto.ts | 12 +- .../organization/organization.loader.ts | 4 +- .../organization/organization.repository.ts | 10 +- .../organization/organization.resolver.ts | 22 +- .../organization/organization.service.ts | 17 +- .../partner/dto/create-partner.dto.ts | 9 +- .../partner/dto/list-partner.dto.ts | 3 +- .../partner/dto/partner-type.enum.ts | 4 +- src/components/partner/dto/partner.dto.ts | 8 +- src/components/partner/partner.repository.ts | 60 +--- src/components/partner/partner.resolver.ts | 22 +- src/components/partner/partner.service.ts | 39 +-- .../dto/partnership-producing-medium.dto.ts | 4 +- ...partnership-producing-medium.repository.ts | 21 +- .../partnership-producing-medium.resolver.ts | 11 +- .../partnership-producing-medium.service.ts | 21 +- .../dto/financial-reporting-type.enum.ts | 14 +- .../partnership/dto/list-partnership.dto.ts | 4 +- .../dto/partnership-agreement-status.enum.ts | 4 +- .../partnership/dto/partnership.dto.ts | 3 +- .../partnership/dto/update-partnership.dto.ts | 9 +- ...alized-changeset-to-partnership.handler.ts | 15 +- ...ate-overrides-on-project-change.handler.ts | 5 +- ...rtnership-by-project-and-partner.loader.ts | 6 +- .../partnership/partnership.gel.repository.ts | 13 +- .../partnership/partnership.repository.ts | 53 +--- .../partnership/partnership.resolver.ts | 30 +- .../partnership/partnership.service.ts | 67 +---- .../dto/list-periodic-reports.dto.ts | 22 +- .../periodic-report/dto/report-period.enum.ts | 7 +- .../events/periodic-report-uploaded.event.ts | 5 +- .../handlers/abstract-periodic-report-sync.ts | 15 +- ...sync-periodic-report-to-project.handler.ts | 14 +- ...c-progress-report-to-engagement.handler.ts | 27 +- ...odic-report-project-connection.resolver.ts | 47 +-- .../periodic-report/periodic-report.loader.ts | 14 +- .../periodic-report.repository.ts | 96 ++----- .../periodic-report.resolver.ts | 9 +- .../periodic-report.service.ts | 67 ++--- src/components/pin/pin.gel.repository.ts | 5 +- src/components/pin/pin.module.ts | 6 +- src/components/pin/pin.repository.ts | 18 +- src/components/pin/pin.resolver.ts | 18 +- src/components/pnp/extract-scripture.ts | 8 +- .../extraction-result.dto.ts | 33 +-- .../pnp-extraction-result.gel.repository.ts | 9 +- .../pnp-extraction-result.neo4j.repository.ts | 33 +-- .../extraction-result/pnp-problem.resolver.ts | 4 +- src/components/pnp/isGoalRow.ts | 25 +- .../pnp/isGoalStepPlannedInsideProject.ts | 12 +- .../pnp/isProgressCompletedOutsideProject.ts | 16 +- src/components/pnp/planning-sheet.ts | 22 +- src/components/pnp/progress-sheet.ts | 18 +- .../pnp/verifyEngagementDateRangeMatches.ts | 20 +- src/components/post/post.loader.ts | 5 +- src/components/post/post.module.ts | 13 +- src/components/post/post.repository.ts | 17 +- src/components/post/post.resolver.ts | 22 +- src/components/post/post.service.ts | 8 +- .../create-product-connection.resolver.ts | 4 +- .../dto/product-progress.dto.ts | 8 +- .../dto/variant-progress.dto.ts | 23 +- .../handlers/extract-pnp-progress.handler.ts | 23 +- .../product-progress-by-product.loader.ts | 17 +- .../product-progress-by-report.loader.ts | 17 +- .../product-progress.repository.ts | 121 +++----- .../product-progress.resolver.ts | 8 +- .../product-progress.service.ts | 18 +- .../step-not-planned.exception.ts | 6 +- .../step-progress-extractor.service.ts | 41 +-- .../product/dto/completion-description.dto.ts | 3 +- .../product/dto/list-product.dto.ts | 9 +- src/components/product/dto/producible.dto.ts | 12 +- .../product/dto/product-methodology.enum.ts | 14 +- src/components/product/dto/product.dto.ts | 29 +- .../product/dto/progress-measurement.enum.ts | 4 +- .../product/dto/update-product.dto.ts | 19 +- .../extract-products-from-pnp.handler.ts | 14 +- .../backfill-empty-mediums.migration.ts | 8 +- ...x-nan-total-verse-equivalents.migration.ts | 4 +- .../product/pnp-product-sync.service.ts | 87 ++---- src/components/product/product.extractor.ts | 25 +- src/components/product/product.loader.ts | 17 +- src/components/product/product.repository.ts | 98 ++----- src/components/product/product.resolver.ts | 40 +-- src/components/product/product.service.ts | 256 +++++------------ .../community-story-prompts.ts | 6 +- ...rogress-report-community-story.resolver.ts | 8 +- ...progress-report-community-story.service.ts | 9 +- .../dto/community-stories.dto.ts | 7 +- .../progress-report/dto/highlights.dto.ts | 3 +- .../dto/progress-report-list.dto.ts | 17 +- .../dto/progress-report-status.enum.ts | 13 +- .../dto/progress-report.dto.ts | 3 +- .../progress-report/dto/team-news.dto.ts | 3 +- .../progress-report-highlights.resolver.ts | 8 +- .../progress-report-highlights.service.ts | 9 +- .../media/dto/media-list.dto.ts | 15 +- .../progress-report/media/dto/media.dto.ts | 20 +- .../progress-report/media/dto/upload.dto.ts | 10 +- .../handlers/file-is-media-check.handler.ts | 5 +- .../progress-report-featured-media.loader.ts | 6 +- .../media/progress-report-media.loader.ts | 4 +- .../media/progress-report-media.repository.ts | 49 +--- .../media/progress-report-media.service.ts | 28 +- .../media/resolvers/list.resolver.ts | 12 +- .../media/resolvers/media.resolver.ts | 8 +- ...p-internship-progress-reports.migration.ts | 8 +- ...eextract-all-progress-reports.migration.ts | 19 +- ...extra-for-periodic-interface.repository.ts | 117 ++++---- .../progress-report.repository.ts | 83 +++--- ...s-report-engagement-connection.resolver.ts | 45 +-- .../resolvers/progress-report.resolver.ts | 6 +- .../resolvers/reextract-pnp.resolver.ts | 9 +- .../progress-report-team-news.resolver.ts | 8 +- .../progress-report-team-news.service.ts | 9 +- .../migrations/rename.migration.ts | 4 +- .../variance-explanation.loader.ts | 10 +- .../variance-explanation.module.ts | 5 +- .../variance-explanation.repository.ts | 4 +- .../variance-explanation.resolver.ts | 12 +- .../variance-explanation.service.ts | 9 +- ...ss-report-workflow-notification.handler.ts | 41 +-- .../progress-report-workflow.flowchart.ts | 7 +- .../progress-report-workflow.granter.ts | 26 +- .../progress-report-workflow.module.ts | 12 +- .../progress-report-workflow.repository.ts | 34 +-- .../progress-report-workflow.service.ts | 12 +- ...rogress-report-workflow-events.resolver.ts | 4 +- .../progress-report/workflow/transitions.ts | 4 +- .../dto/schedule-status.enum.ts | 6 +- .../extract-pnp-file-on-upload.handler.ts | 24 +- .../progress-summary.extractor.ts | 16 +- .../progress-summary.gel.repository.ts | 59 ++-- .../progress-summary.loader.ts | 6 +- .../progress-summary.repository.ts | 69 ++--- .../progress-summary.resolver.ts | 15 +- .../dto/project-change-request-list.dto.ts | 8 +- .../dto/project-change-request-status.enum.ts | 4 +- .../dto/project-change-request-type.enum.ts | 4 +- .../dto/project-change-request.dto.ts | 3 +- .../dto/update-project-change-request.dto.ts | 8 +- .../project-change-request.loader.ts | 8 +- .../project-change-request.repository.ts | 10 +- .../project-change-request.resolver.ts | 12 +- .../project-change-request.service.ts | 35 +-- .../project/dto/list-projects.dto.ts | 67 ++--- .../project/dto/project-status.enum.ts | 8 +- .../project/dto/project-type.enum.ts | 11 +- src/components/project/dto/project.dto.ts | 16 +- .../financial-approver-neo4j.repository.ts | 13 +- .../financial-approver.resolver.ts | 9 +- ...-finalized-changeset-to-project.handler.ts | 13 +- .../handlers/set-department-id.handler.ts | 51 +--- .../handlers/set-initial-mou-end.handler.ts | 5 +- ...ename-translation-to-momentum.migration.ts | 7 +- .../project/project-engagement-id.resolver.ts | 13 +- .../project/project-filters.query.ts | 20 +- .../available-roles-to-project.resolver.ts | 7 +- .../dto/create-project-member.dto.ts | 8 +- .../dto/list-project-members.dto.ts | 8 +- ...change-apply-to-project-members.handler.ts | 10 +- .../project-member.gel.repository.ts | 26 +- .../project-member/project-member.loader.ts | 4 +- .../project-member.repository.ts | 67 +---- .../project-member/project-member.resolver.ts | 12 +- .../project-member/project-member.service.ts | 25 +- .../project/project.gel.repository.ts | 65 ++--- src/components/project/project.loader.ts | 5 +- src/components/project/project.repository.ts | 23 +- src/components/project/project.resolver.ts | 51 +--- src/components/project/project.service.ts | 97 ++----- .../dto/execute-project-transition.input.ts | 4 +- .../workflow/dto/workflow-transition.dto.ts | 4 +- .../project-workflow-notification.handler.ts | 16 +- ...ep-history-to-workflow-events.migration.ts | 10 +- .../workflow/project-workflow.flowchart.ts | 4 +- .../workflow/project-workflow.granter.ts | 4 +- .../project-workflow.neo4j.repository.ts | 16 +- .../workflow/project-workflow.repository.ts | 10 +- .../workflow/project-workflow.service.ts | 30 +- .../project/workflow/project-workflow.ts | 25 +- .../resolvers/project-transitions.resolver.ts | 3 +- .../project-workflow-events.resolver.ts | 4 +- .../workflow/transitions/conditions.ts | 7 +- .../workflow/transitions/dynamic-step.ts | 4 +- .../project/workflow/transitions/enhancers.ts | 4 +- src/components/prompts/dto/prompt-list.dto.ts | 4 +- .../prompts/dto/prompt-response-list.dto.ts | 6 +- .../prompts/dto/prompt-response.dto.ts | 4 +- .../prompt-variant-response.repository.ts | 33 +-- .../prompt-variant-response.service.ts | 43 +-- src/components/prompts/prompts.module.ts | 6 +- .../scripture/book-difficulty-factor.ts | 3 +- .../scripture/dto/scripture-range.dto.ts | 61 ++-- .../scripture/dto/scripture-reference.dto.ts | 6 +- .../scripture/dto/scripture-reference.test.ts | 18 +- .../dto/scripture-reference.validator.ts | 8 +- .../dto/unspecified-scripture-portion.dto.ts | 16 +- src/components/scripture/gel.utils.ts | 16 +- src/components/scripture/is-equal.ts | 16 +- .../scripture-collection.resolver.ts | 13 +- .../scripture/scripture-range.resolver.ts | 3 +- .../scripture-reference.repository.ts | 13 +- .../scripture/scripture-reference.service.ts | 11 +- src/components/scripture/verse-equivalents.ts | 15 +- .../search/dto/search-results.dto.ts | 11 +- src/components/search/dto/search.dto.ts | 6 +- src/components/search/search.repository.ts | 14 +- src/components/search/search.service.ts | 55 ++-- src/components/story/story.gel.repository.ts | 17 +- src/components/story/story.repository.ts | 46 +-- src/components/story/story.resolver.ts | 8 +- src/components/story/story.service.ts | 12 +- src/components/timezone/timezone.resolver.ts | 4 +- .../user/assignable-roles.resolver.ts | 3 +- src/components/user/dto/user-status.enum.ts | 7 +- .../user/education/dto/education.dto.ts | 9 +- .../user/education/dto/list-education.dto.ts | 4 +- .../user/education/education.loader.ts | 4 +- .../user/education/education.repository.ts | 19 +- src/components/user/fullName.ts | 15 +- .../user/known-language.repository.ts | 17 +- .../user/system-agent.neo4j.repository.ts | 4 +- .../user/system-agent.repository.ts | 5 +- .../dto/list-unavailabilities.dto.ts | 8 +- .../unavailability.repository.ts | 19 +- .../unavailability/unavailability.resolver.ts | 4 +- .../unavailability/unavailability.service.ts | 8 +- src/components/user/user.gel.repository.ts | 26 +- src/components/user/user.repository.ts | 82 +----- src/components/user/user.resolver.ts | 53 +--- src/components/user/user.service.ts | 61 +--- src/components/workflow/define-workflow.ts | 31 +- .../workflow/dto/execute-transition.input.ts | 12 +- .../workflow/dto/serialized-workflow.dto.ts | 19 +- .../workflow/dto/workflow-transition.dto.ts | 12 +- .../workflow/permission.serializer.ts | 16 +- src/components/workflow/transitions/types.ts | 6 +- src/components/workflow/workflow.flowchart.ts | 42 +-- src/components/workflow/workflow.granter.ts | 58 +--- src/components/workflow/workflow.service.ts | 34 +-- .../authentication.gel.repository.ts | 144 ++++------ .../authentication.repository.ts | 53 +--- .../authentication/authentication.service.ts | 18 +- .../resolvers/login.resolver.ts | 8 +- .../resolvers/password.resolver.ts | 8 +- .../resolvers/register.resolver.ts | 8 +- .../resolvers/session.resolver.ts | 27 +- .../authentication/session/session.host.ts | 18 +- .../session/session.initiator.ts | 17 +- .../session/session.interceptor.ts | 18 +- .../authentication/session/session.manager.ts | 47 +-- src/core/cli/command.discovery.ts | 9 +- src/core/config/config.service.ts | 64 ++--- src/core/config/environment.service.ts | 63 ++--- src/core/config/root-user.config.ts | 3 +- src/core/config/version.service.ts | 10 +- src/core/data-loader/data-loader.config.ts | 14 +- .../data-loader/loader-factory.decorator.ts | 3 +- .../object-view-aware-loader.strategy.ts | 10 +- src/core/data-loader/options.type.ts | 10 +- .../single-item-loader.strategy.ts | 9 +- ...act-transactional-mutations.interceptor.ts | 4 +- src/core/database/changes.ts | 35 +-- src/core/database/common.repository.ts | 18 +- src/core/database/cypher.factory.ts | 24 +- src/core/database/database.service.ts | 46 +-- src/core/database/db-type.ts | 4 +- src/core/database/dto.repository.ts | 20 +- src/core/database/errors.ts | 13 +- src/core/database/highlight-cypher.util.ts | 8 +- .../indexer/create-indexes.decorator.ts | 7 +- src/core/database/indexer/indexer.module.ts | 47 ++- .../migration/base-migration.service.ts | 18 +- .../migration/migration-discovery.service.ts | 4 +- .../migration/migration-runner.service.ts | 9 +- .../database/migration/migration.command.ts | 3 +- .../database/migration/migration.module.ts | 6 +- .../database/parameter-transformer.service.ts | 14 +- src/core/database/query-augmentation/apply.ts | 4 +- src/core/database/query-augmentation/call.ts | 21 +- .../database/query-augmentation/comment.ts | 10 +- .../query-augmentation/condition-variables.ts | 14 +- .../database/query-augmentation/foreach.ts | 17 +- .../query-augmentation/interpolate.ts | 10 +- .../query-augmentation/pattern-formatting.ts | 15 +- .../database/query-augmentation/subquery.ts | 8 +- .../database/query-augmentation/summary.ts | 65 ++--- src/core/database/query-augmentation/yield.ts | 8 +- src/core/database/query/create-node.ts | 7 +- .../database/query/create-relationships.ts | 65 ++--- src/core/database/query/cypher-expression.ts | 8 +- src/core/database/query/cypher-functions.ts | 10 +- src/core/database/query/deletes.ts | 8 +- src/core/database/query/filters.ts | 52 +--- src/core/database/query/full-text.ts | 14 +- src/core/database/query/lists.ts | 11 +- .../match-changeset-and-changed-props.ts | 31 +- .../query/match-project-based-props.ts | 31 +- src/core/database/query/matching.ts | 36 +-- .../query/properties/create-property.ts | 19 +- .../query/properties/deactivate-property.ts | 21 +- .../query/properties/update-properties.ts | 44 ++- .../query/properties/update-property.ts | 57 +--- .../query/properties/update-relation-list.ts | 12 +- src/core/database/query/sorting.ts | 20 +- src/core/database/query/where-and-list.ts | 3 +- src/core/database/query/where-path.ts | 4 +- src/core/database/results/types.ts | 4 +- src/core/database/rollback-manager.ts | 5 +- src/core/database/split-db.provider.ts | 8 +- src/core/database/transaction.ts | 11 +- src/core/database/transactional.decorator.ts | 19 +- src/core/database/transformer.ts | 3 +- src/core/email/templates/base.tsx | 48 +--- .../templates/forgot-password.template.tsx | 3 +- .../email/templates/formatted-date-time.tsx | 10 +- ...rogress-report-status-changed.template.tsx | 28 +- .../project-step-changed.template.tsx | 26 +- src/core/events/event-bus.service.ts | 24 +- src/core/exception/exception.filter.ts | 5 +- .../exception/exception.normalizer.test.ts | 10 +- src/core/exception/exception.normalizer.ts | 43 +-- src/core/exception/jest-skip-source-file.ts | 10 +- src/core/gel/alias-id-resolver.ts | 20 +- src/core/gel/codecs/register-codecs.ts | 3 +- src/core/gel/codecs/rich-text.codec.ts | 4 +- src/core/gel/codecs/temporal.codec.ts | 10 +- src/core/gel/common.repository.ts | 10 +- src/core/gel/dto.repository.ts | 86 ++---- src/core/gel/edgeql.ts | 13 +- src/core/gel/errors/attributes.ts | 19 +- .../gel/errors/constraint-violation.error.ts | 9 +- ...gel-transactional-mutations.interceptor.ts | 7 +- src/core/gel/gel.module.ts | 4 +- src/core/gel/gel.service.ts | 10 +- .../gel/generator/find-hydration-shapes.ts | 21 +- src/core/gel/generator/generate-schema.ts | 6 +- src/core/gel/generator/inline-queries.ts | 14 +- src/core/gel/generator/query-builder.ts | 36 +-- src/core/gel/generator/query-files.ts | 7 +- src/core/gel/generator/scalars.ts | 5 +- src/core/gel/generator/util.ts | 3 +- src/core/gel/options.context.ts | 17 +- src/core/gel/query-util/cast-to-enum.ts | 5 +- src/core/gel/query-util/easy-insert.ts | 44 +-- src/core/gel/query-util/map-to-set-block.ts | 9 +- .../gel/schema-ast/access-policy.commands.ts | 7 +- .../gel/schema-ast/access-policy.injector.ts | 12 +- src/core/gel/schema-ast/crude-ast-parser.ts | 16 +- src/core/gel/seeds.run.ts | 33 +-- src/core/gel/transaction.context.ts | 8 +- src/core/graphql/driver.ts | 28 +- src/core/graphql/gql-context.host.ts | 10 +- src/core/graphql/graphql-error-formatter.ts | 13 +- src/core/graphql/graphql-tracing.plugin.ts | 17 +- src/core/graphql/graphql.module.ts | 12 +- src/core/graphql/graphql.options.ts | 12 +- src/core/http/decorators.ts | 3 +- src/core/http/http.adapter.ts | 33 +-- src/core/logger/abstract-logger.ts | 23 +- src/core/logger/formatters.ts | 17 +- src/core/logger/level-matcher.provider.ts | 14 +- src/core/logger/level-matcher.ts | 3 +- src/core/logger/logger.interface.ts | 15 +- src/core/logger/logger.module.ts | 12 +- .../logger/nest-logger-adapter.service.ts | 4 +- src/core/logger/winston-logger.service.ts | 9 +- src/core/resources/loader.registry.ts | 12 +- src/core/resources/resource-name.types.ts | 19 +- .../resources/resource-resolver.service.ts | 26 +- src/core/resources/resource.loader.ts | 35 +-- src/core/resources/resource.module.ts | 14 +- src/core/resources/resources.host.ts | 42 +-- src/core/shutdown.hook.ts | 5 +- src/core/timeout.interceptor.ts | 4 +- src/core/tracing/sampler.ts | 5 +- src/core/tracing/tracing.module.ts | 8 +- src/core/tracing/tracing.service.ts | 8 +- src/core/validation/validation.exception.ts | 4 +- src/core/validation/validation.module.ts | 5 +- src/core/validation/validation.pipe.ts | 10 +- src/graphql.ts | 4 +- src/main.ts | 20 +- src/repl.ts | 4 +- test/education.e2e-spec.ts | 8 +- test/engagement-changeset-aware.e2e-spec.ts | 43 +-- test/engagement-workflow.e2e-spec.ts | 52 +--- test/engagement.e2e-spec.ts | 166 +++-------- ...s-memberships-on-open-projects.e2e-spec.ts | 51 +--- test/file.e2e-spec.ts | 51 +--- test/film.e2e-spec.ts | 8 +- test/funding-account.e2e-spec.ts | 4 +- test/language.e2e-spec.ts | 21 +- test/location.e2e-spec.ts | 4 +- test/organization.e2e-spec.ts | 17 +- test/partner.e2e-spec.ts | 15 +- test/partnership-changeset-aware.e2e-spec.ts | 12 +- test/partnership.e2e-spec.ts | 66 ++--- test/product.e2e-spec.ts | 28 +- test/progress-report-media.e2e-spec.ts | 5 +- test/project-changeset-aware.e2e-spec.ts | 9 +- test/project-member.e2e-spec.ts | 6 +- test/project-workflow.e2e-spec.ts | 37 +-- test/project.e2e-spec.ts | 106 ++----- test/story.e2e-spec.ts | 8 +- test/unavailability.e2e-spec.ts | 10 +- test/user.e2e-spec.ts | 36 +-- test/utility/create-app.ts | 8 +- test/utility/create-directory.ts | 10 +- test/utility/create-engagement.ts | 3 +- test/utility/create-film.ts | 5 +- test/utility/create-funding-account.ts | 3 +- test/utility/create-graphql-client.ts | 22 +- test/utility/create-language.ts | 7 +- test/utility/create-partner.ts | 3 +- test/utility/create-partnership.ts | 9 +- test/utility/create-product-derivative.ts | 4 +- test/utility/create-product-direct.ts | 4 +- test/utility/create-project-member.ts | 12 +- test/utility/create-unavailability.ts | 5 +- test/utility/expect-gql-error.ts | 15 +- test/utility/project-change-request.ts | 9 +- test/utility/register.ts | 5 +- test/utility/transition-engagement.ts | 28 +- test/utility/transition-project.ts | 15 +- test/utility/update-project.ts | 5 +- test/utility/workflow.tester.ts | 26 +- test/zone.e2e-spec.ts | 10 +- 685 files changed, 3247 insertions(+), 9853 deletions(-) diff --git a/dbschema/seeds/001.root-user.ts b/dbschema/seeds/001.root-user.ts index 95ba4adf8b..424514d550 100644 --- a/dbschema/seeds/001.root-user.ts +++ b/dbschema/seeds/001.root-user.ts @@ -1,8 +1,5 @@ import { EnvironmentService } from '~/core/config/environment.service'; -import { - determineRootUser, - RootUserAlias, -} from '~/core/config/root-user.config'; +import { determineRootUser, RootUserAlias } from '~/core/config/root-user.config'; import type { SeedFn } from '~/core/gel/seeds.run'; export default (async function ({ e, db, print }) { diff --git a/dbschema/seeds/008.projects.ts b/dbschema/seeds/008.projects.ts index 04aa8ba728..2d182b9dc3 100644 --- a/dbschema/seeds/008.projects.ts +++ b/dbschema/seeds/008.projects.ts @@ -114,10 +114,8 @@ export default (async function ({ e, db, print }) { for (const { type, step, ...project } of newProjects) { const insertQ = e.insert(e[`${type}Project`], { ...project, - ...mapValues.fromList( - ['mouStart', 'mouEnd', 'estimatedSubmission'], - (key, { SKIP }) => - !project[key] ? SKIP : e.cal.local_date(project[key]!), + ...mapValues.fromList(['mouStart', 'mouEnd', 'estimatedSubmission'], (key, { SKIP }) => + !project[key] ? SKIP : e.cal.local_date(project[key]!), ).asRecord, primaryLocation: project.primaryLocation ? e.assert_exists( diff --git a/jest.config.ts b/jest.config.ts index ebf424712b..9b04a4ae15 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -45,8 +45,6 @@ export default async (): Promise => { testTimeout: Duration.fromObject({ minutes: 1 }).toMillis(), // WebStorm doesn't need this as it adds the cli flag automatically. // I'm guessing VSCode needs it. - ...(debugging - ? { testTimeout: Duration.fromObject({ hours: 2 }).toMillis() } - : {}), + ...(debugging ? { testTimeout: Duration.fromObject({ hours: 2 }).toMillis() } : {}), }; }; diff --git a/src/common/and-call.ts b/src/common/and-call.ts index fd18d4da7f..7fe696e2bd 100644 --- a/src/common/and-call.ts +++ b/src/common/and-call.ts @@ -2,11 +2,7 @@ import { type FnLike } from '@seedcompany/common'; import { isPromise } from 'node:util/types'; import { type ConditionalKeys } from 'type-fest'; -export const andCall = < - T, - K extends ConditionalKeys, - X extends T[K] & FnLike, ->( +export const andCall = , X extends T[K] & FnLike>( thing: T, methodName: K, add: X, diff --git a/src/common/augmented-metadata.pipe.ts b/src/common/augmented-metadata.pipe.ts index cc050e89f2..47db797bea 100644 --- a/src/common/augmented-metadata.pipe.ts +++ b/src/common/augmented-metadata.pipe.ts @@ -2,9 +2,7 @@ import { type ArgumentMetadata, type PipeTransform } from '@nestjs/common'; import { ServerException } from './exceptions'; const key = Symbol('AugmentedMetadata'); -export const createAugmentedMetadataPipe = < - T extends Record, ->() => { +export const createAugmentedMetadataPipe = >() => { const pipe = (data: T | (() => T)): PipeTransform => ({ transform: (value, metadata) => { const actual = typeof data === 'function' ? data() : data; diff --git a/src/common/calculated.decorator.ts b/src/common/calculated.decorator.ts index c170c0d019..f46ceb57d7 100644 --- a/src/common/calculated.decorator.ts +++ b/src/common/calculated.decorator.ts @@ -5,8 +5,7 @@ export const CalculatedSymbol = Symbol('Calculated'); * This means the resource/property is managed by the API, instead of the user. */ export const Calculated = - (): PropertyDecorator & ClassDecorator => - (target: any, key?: string | symbol) => { + (): PropertyDecorator & ClassDecorator => (target: any, key?: string | symbol) => { if (!key) { Reflect.defineMetadata(CalculatedSymbol, true, target); return target; diff --git a/src/common/discovery-unique-methods.ts b/src/common/discovery-unique-methods.ts index 96fe3117a8..14c78fb219 100644 --- a/src/common/discovery-unique-methods.ts +++ b/src/common/discovery-unique-methods.ts @@ -1,8 +1,6 @@ import { type DiscoveredMethodWithMeta } from '@golevelup/nestjs-discovery'; -export const uniqueDiscoveredMethods = ( - methods: Array>, -) => { +export const uniqueDiscoveredMethods = (methods: Array>) => { const seenClasses = new Map>>(); const uniqueMethods = [] as typeof methods; for (const method of methods) { diff --git a/src/common/exceptions/creation-failed.exception.ts b/src/common/exceptions/creation-failed.exception.ts index dbdc2cf07d..dbfc4365d7 100644 --- a/src/common/exceptions/creation-failed.exception.ts +++ b/src/common/exceptions/creation-failed.exception.ts @@ -4,10 +4,7 @@ import { ServerException } from './exception'; export class CreationFailed extends ServerException { readonly resource: EnhancedResource; - constructor( - resource: ResourceLike, - options?: { message?: string; cause?: Error }, - ) { + constructor(resource: ResourceLike, options?: { message?: string; cause?: Error }) { const res = EnhancedResource.resolve(resource); super(options?.message ?? `Failed to create ${res.name}`, options?.cause); this.resource = res; @@ -15,10 +12,7 @@ export class CreationFailed extends ServerException { } export class ReadAfterCreationFailed extends CreationFailed { - constructor( - resource: ResourceLike, - options?: { message?: string; cause?: Error }, - ) { + constructor(resource: ResourceLike, options?: { message?: string; cause?: Error }) { const res = EnhancedResource.resolve(resource); super(res, { message: `Failed to retrieve ${res.name} after creation`, diff --git a/src/common/exceptions/date-override-conflict.exception.ts b/src/common/exceptions/date-override-conflict.exception.ts index 01ef22ea8d..137d8960a4 100644 --- a/src/common/exceptions/date-override-conflict.exception.ts +++ b/src/common/exceptions/date-override-conflict.exception.ts @@ -48,24 +48,22 @@ export class DateOverrideConflictException extends RangeException { end: CalendarDate | null; }>, ): NonEmptyArray | undefined { - const maybeConflicts = items.flatMap( - ({ start, end, ...item }) => [ - canonical.start && start && canonical.start > start - ? { - ...item, - point: 'start' as const, - date: start, - } - : null, - canonical.end && end && canonical.end < end - ? { - ...item, - point: 'end' as const, - date: end, - } - : null, - ], - ); + const maybeConflicts = items.flatMap(({ start, end, ...item }) => [ + canonical.start && start && canonical.start > start + ? { + ...item, + point: 'start' as const, + date: start, + } + : null, + canonical.end && end && canonical.end < end + ? { + ...item, + point: 'end' as const, + date: end, + } + : null, + ]); return asNonEmpty(maybeConflicts.filter(isNotFalsy)); } } diff --git a/src/common/exceptions/duplicate.exception.ts b/src/common/exceptions/duplicate.exception.ts index 7523a9a29d..c12312e7c6 100644 --- a/src/common/exceptions/duplicate.exception.ts +++ b/src/common/exceptions/duplicate.exception.ts @@ -8,25 +8,19 @@ import { InputException } from './input.exception'; */ export class DuplicateException extends InputException { constructor(field: string, message?: string, previous?: Error) { - super( - message ?? `${field} already exists and needs to be unique`, - field, - previous, - ); + super(message ?? `${field} already exists and needs to be unique`, field, previous); } static fromDB(exception: ExclusivityViolationError, context?: ArgumentsHost) { let property = exception.property; - const message = `${upperFirst( - lowerCase(property), - )} already exists and needs to be unique`; + const message = `${upperFirst(lowerCase(property))} already exists and needs to be unique`; // Attempt to add path prefix automatically to the property name, based // on given GQL input. // This kinda assumes the property name will be unique amongst all the input. - const guessedPath = Object.keys( - InputException.getFlattenInput(context), - ).find((path) => property === path || path.endsWith('.' + property)); + const guessedPath = Object.keys(InputException.getFlattenInput(context)).find( + (path) => property === path || path.endsWith('.' + property), + ); property = guessedPath ?? property; return new DuplicateException(property, message, exception); diff --git a/src/common/exceptions/input.exception.ts b/src/common/exceptions/input.exception.ts index f539f2dc6f..885301bcd4 100644 --- a/src/common/exceptions/input.exception.ts +++ b/src/common/exceptions/input.exception.ts @@ -2,10 +2,7 @@ import { type ArgumentsHost } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; import { ClientException } from './exception'; -export type InputExceptionArgs = - | [Error?] - | [string, Error?] - | [string, string?, Error?]; +export type InputExceptionArgs = [Error?] | [string, Error?] | [string, string?, Error?]; /** * Indicate the request cannot be completed because of requester has done @@ -71,10 +68,7 @@ export class InputException extends ClientException { constructor(message: string, field?: string, previous?: Error); constructor(...args: InputExceptionArgs) { - const [message, field, previous] = InputException.parseArgs( - 'Invalid request', - args, - ); + const [message, field, previous] = InputException.parseArgs('Invalid request', args); super(message, previous); this.field = field; } @@ -84,10 +78,7 @@ export class InputException extends ClientException { return this; } - static parseArgs( - defaultMessage: string, - [one, two, three]: InputExceptionArgs, - ) { + static parseArgs(defaultMessage: string, [one, two, three]: InputExceptionArgs) { let message = defaultMessage; let field; let previous; @@ -112,9 +103,7 @@ export class InputException extends ClientException { return {}; } const gqlContext = - context instanceof GqlExecutionContext - ? context - : GqlExecutionContext.create(context as any); + context instanceof GqlExecutionContext ? context : GqlExecutionContext.create(context as any); let gqlArgs = gqlContext.getArgs(); // unwind single `input` argument, based on our own conventions diff --git a/src/common/exceptions/unauthorized.exception.ts b/src/common/exceptions/unauthorized.exception.ts index 048f90b535..1c06be9749 100644 --- a/src/common/exceptions/unauthorized.exception.ts +++ b/src/common/exceptions/unauthorized.exception.ts @@ -87,9 +87,7 @@ export class UnauthorizedException extends InputException { const scope = object ? 'this' : 'any'; if (action === 'create') { const message = edges - ? `${prefix} create ${edges} for ${scope} ${ - object ? resourceName : resources - }` + ? `${prefix} create ${edges} for ${scope} ${object ? resourceName : resources}` : `${prefix} create ${resources}`; return new UnauthorizedException(message); } diff --git a/src/common/field-selection.ts b/src/common/field-selection.ts index 9e55ce35f4..2f9190a2f9 100644 --- a/src/common/field-selection.ts +++ b/src/common/field-selection.ts @@ -71,19 +71,14 @@ export class FieldSelection { * to filter out requested but unrelated fields. */ forType(type: AbstractClassType): FieldInfo; - forType( - type: K, - ): FieldInfo; + forType(type: K): FieldInfo; forType(type: string | AbstractClassType) { const typeName = typeof type === 'string' ? type : type.constructor.name; const typeObj = this.resolveInfo.schema.getType(typeName); if (!typeObj) { return {}; } - const { fields } = simplifyParsedResolveInfoFragmentWithType( - this.tree, - typeObj, - ); + const { fields } = simplifyParsedResolveInfoFragmentWithType(this.tree, typeObj); return fields; } diff --git a/src/common/firstLettersOfWords.ts b/src/common/firstLettersOfWords.ts index ccce6fdbc0..e4ce957a42 100644 --- a/src/common/firstLettersOfWords.ts +++ b/src/common/firstLettersOfWords.ts @@ -24,22 +24,14 @@ const lowercasePattern = RegExp( const matchAndMerge = (pattern: RegExp, str: string, group = 1) => Array.from(str.matchAll(pattern), (m) => m[group]).join(''); -export function firstLettersOfWords( - words: string, - limit: number | null = 3, -): string { +export function firstLettersOfWords(words: string, limit: number | null = 3): string { // If the string has uppercase characters we use the uppercase pattern which // will ignore lowercase characters after word-like boundaries. // If the string doesn't have any uppercase characters we fallback to the lowercase pattern // which is less ideal but gives something instead of an empty string. // See tests for differences in the two patterns. - const pattern = words.match(hasUppercaseLettersPattern) - ? uppercasePattern - : lowercasePattern; - const letters = - matchAndMerge(pattern, words) || matchAndMerge(lowercasePattern, words); + const pattern = words.match(hasUppercaseLettersPattern) ? uppercasePattern : lowercasePattern; + const letters = matchAndMerge(pattern, words) || matchAndMerge(lowercasePattern, words); - return limit === Infinity || limit == null - ? letters - : letters.slice(0, limit); + return limit === Infinity || limit == null ? letters : letters.slice(0, limit); } diff --git a/src/common/fiscal-year.test.ts b/src/common/fiscal-year.test.ts index 0c6a7c651d..18c3ebbc2a 100644 --- a/src/common/fiscal-year.test.ts +++ b/src/common/fiscal-year.test.ts @@ -19,8 +19,6 @@ describe('fullFiscalQuarter', () => { [3, 2020, '2020-04-01/2020-06-30'], [4, 2020, '2020-07-01/2020-09-30'], ])('Q%s FY%s -> %s', (fiscalQuarter, fiscalYear, dateRange) => { - expect(fullFiscalQuarter(fiscalQuarter, fiscalYear).toISO()).toEqual( - dateRange, - ); + expect(fullFiscalQuarter(fiscalQuarter, fiscalYear).toISO()).toEqual(dateRange); }); }); diff --git a/src/common/fiscal-year.ts b/src/common/fiscal-year.ts index 1dda97c3d0..a1b10bf14a 100644 --- a/src/common/fiscal-year.ts +++ b/src/common/fiscal-year.ts @@ -10,8 +10,7 @@ export const fiscalYear = (dt: DateTime) => dt.year + (dt.month >= 10 ? 1 : 0); export const fiscalYears = (start?: DateTime, end?: DateTime) => start && end ? range(fiscalYear(start), fiscalYear(end) + 1) : []; -export const fiscalQuarter = (dt: DateTime) => - dt.quarter === 4 ? 1 : dt.quarter + 1; +export const fiscalQuarter = (dt: DateTime) => (dt.quarter === 4 ? 1 : dt.quarter + 1); export const startOfFiscalYear = (date: CalendarDate) => CalendarDate.local(date.year - (date.month >= 10 ? 0 : 1), 10, 1); @@ -20,10 +19,7 @@ export const endOfFiscalYear = (date: CalendarDate) => CalendarDate.local(date.year + (date.month >= 10 ? 1 : 0), 9, 30); export const expandToFullFiscalYears = (dates: DateInterval) => - DateInterval.fromDateTimes( - startOfFiscalYear(dates.start), - endOfFiscalYear(dates.end), - ); + DateInterval.fromDateTimes(startOfFiscalYear(dates.start), endOfFiscalYear(dates.end)); export const fullFiscalYear = (fiscalYear: number) => DateInterval.fromDateTimes( @@ -32,10 +28,7 @@ export const fullFiscalYear = (fiscalYear: number) => ); /** The date interval of a given fiscal quarter */ -export const fullFiscalQuarter = ( - fiscalQuarter: number, - fiscalYear: number, -) => { +export const fullFiscalQuarter = (fiscalQuarter: number, fiscalYear: number) => { const year = fiscalYear + (fiscalQuarter === 1 ? -1 : 0); const quarter = fiscalQuarter + (fiscalQuarter === 1 ? 2 : -2); const fiscalQuarterStartDate = CalendarDate.local(year, 1, 1).plus({ @@ -48,11 +41,9 @@ export const fullFiscalQuarter = ( ); }; -export const isReasonableYear = (year: unknown) => - isInt(year) && year >= 1970 && year <= 3000; +export const isReasonableYear = (year: unknown) => isInt(year) && year >= 1970 && year <= 3000; -export const isQuarterNumber = (quarter: unknown) => - isInt(quarter) && quarter >= 1 && quarter <= 4; +export const isQuarterNumber = (quarter: unknown) => isInt(quarter) && quarter >= 1 && quarter <= 4; // Not true for falsy case, thus not built-in, but fine for our cases here. const isInt = (x: unknown): x is number => Number.isInteger(x); diff --git a/src/common/generate-id.ts b/src/common/generate-id.ts index 5efe23698c..1ea0470b7d 100644 --- a/src/common/generate-id.ts +++ b/src/common/generate-id.ts @@ -3,12 +3,13 @@ import { type ID } from './id-field'; // 100 IDs / hour = 1k years to have 1% probability of a single collision // https://zelark.github.io/nano-id-cc/ -const alphabet = - '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; +const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; const size = 11; -export const generateId: () => Promise = - customAlphabet(alphabet, size) as any; +export const generateId: () => Promise = customAlphabet( + alphabet, + size, +) as any; export const isValidId = (value: unknown): value is ID => { if (typeof value !== 'string') { diff --git a/src/common/id-field.test.ts b/src/common/id-field.test.ts index dbd638e0de..415e136464 100644 --- a/src/common/id-field.test.ts +++ b/src/common/id-field.test.ts @@ -26,8 +26,7 @@ const UserIncompatibleDifferent: ID<'User'> = '' as ID<'Location'>; // @ts-expect-error this should be blocked const UserIncompatibleDifferent2: ID<'Location'> = '' as ID<'User'>; -const SubclassesAreCompatible: ID<'Engagement'> = - '' as ID<'LanguageEngagement'>; +const SubclassesAreCompatible: ID<'Engagement'> = '' as ID<'LanguageEngagement'>; // @ts-expect-error this should be blocked const InterfaceIsNotDirectlyCompatibleWithConcrete: ID<'LanguageEngagement'> = '' as ID<'Engagement'>; diff --git a/src/common/id-field.ts b/src/common/id-field.ts index a1d9d54d94..f6019e6da2 100644 --- a/src/common/id-field.ts +++ b/src/common/id-field.ts @@ -18,11 +18,9 @@ export const IdField = ({ IsId(validation), ); -export const isIdLike = (value: unknown): value is ID => - typeof value === 'string'; +export const isIdLike = (value: unknown): value is ID => typeof value === 'string'; -export type ID = Tagged & - IDTo>; +export type ID = Tagged & IDTo>; /** @deprecated Use {@link ID} */ export type IdOf = ID; diff --git a/src/common/id.arg.ts b/src/common/id.arg.ts index 07c1b9b73b..b802fce644 100644 --- a/src/common/id.arg.ts +++ b/src/common/id.arg.ts @@ -5,5 +5,4 @@ import { ValidateIdPipe } from './validators/short-id.validator'; export const IdArg = ( opts: Partial = {}, ...pipes: Array | PipeTransform> -) => - Args({ name: 'id', type: () => IdType, ...opts }, ValidateIdPipe, ...pipes); +) => Args({ name: 'id', type: () => IdType, ...opts }, ValidateIdPipe, ...pipes); diff --git a/src/common/index.ts b/src/common/index.ts index a8d8cf4904..dff4c589bf 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -1,10 +1,4 @@ -export { - type Many, - many, - maybeMany, - JsonSet, - type ArrayItem, -} from '@seedcompany/common'; +export { type Many, many, maybeMany, JsonSet, type ArrayItem } from '@seedcompany/common'; export { makeEnum, type MadeEnum, type EnumType } from '@seedcompany/nest'; export * from './and-call'; diff --git a/src/common/instance-maps.ts b/src/common/instance-maps.ts index f40f80fea0..a681724ee4 100644 --- a/src/common/instance-maps.ts +++ b/src/common/instance-maps.ts @@ -7,9 +7,5 @@ export type InstanceMapOf> = Simplify<{ [K in keyof T]: InstanceType; }>; -export const grabInstances = >( - moduleRef: ModuleRef, - typeMap: T, -) => - mapValues(typeMap, (_, cls) => moduleRef.get(cls)) - .asRecord as InstanceMapOf; +export const grabInstances = >(moduleRef: ModuleRef, typeMap: T) => + mapValues(typeMap, (_, cls) => moduleRef.get(cls)).asRecord as InstanceMapOf; diff --git a/src/common/luxon.graphql.ts b/src/common/luxon.graphql.ts index 1df2f1cb2f..55c42670f4 100644 --- a/src/common/luxon.graphql.ts +++ b/src/common/luxon.graphql.ts @@ -70,9 +70,7 @@ export const DateField = (options?: OptionalFieldOptions) => ); @Scalar('DateTime', () => DateTime) -export class DateTimeScalar - implements CustomScalar -{ +export class DateTimeScalar implements CustomScalar { description = 'An ISO-8601 date time string'; parseValue(value: unknown): string { diff --git a/src/common/markdown.scalar.ts b/src/common/markdown.scalar.ts index 8f971d059f..0a16c45d14 100644 --- a/src/common/markdown.scalar.ts +++ b/src/common/markdown.scalar.ts @@ -7,9 +7,7 @@ export class MarkdownScalar implements CustomScalar { parseLiteral(ast: ValueNode): string | null { if (ast.kind !== Kind.STRING) { - throw new GraphQLError( - `Can only validate strings as InlineMarkdown but got a: ${ast.kind}`, - ); + throw new GraphQLError(`Can only validate strings as InlineMarkdown but got a: ${ast.kind}`); } return ast.value; } diff --git a/src/common/mask-secrets.ts b/src/common/mask-secrets.ts index d48cd49520..0108d4f03f 100644 --- a/src/common/mask-secrets.ts +++ b/src/common/mask-secrets.ts @@ -1,9 +1,6 @@ import { isPlainObject, mapValues } from '@seedcompany/common'; -export const maskSecrets = ( - obj: Record, - depth = 3, -): Record => +export const maskSecrets = (obj: Record, depth = 3): Record => mapValues(obj, (key, val) => isSecret(key, val) ? maskSecret(val) @@ -12,10 +9,7 @@ export const maskSecrets = ( : val, ).asRecord; -export const dropSecrets = ( - obj: Record, - depth = 3, -): Record => +export const dropSecrets = (obj: Record, depth = 3): Record => mapValues(obj, (key, val) => isSecret(key, val) ? undefined diff --git a/src/common/optional-field.ts b/src/common/optional-field.ts index 03896f4bb0..aa9efec4c1 100644 --- a/src/common/optional-field.ts +++ b/src/common/optional-field.ts @@ -20,13 +20,8 @@ export type OptionalFieldOptions = FieldOptions & { * A field that is optional/omissible/can be undefined. * Whether it can be explicitly null is based on `nullable`. */ -export function OptionalField( - typeFn: () => any, - options?: OptionalFieldOptions, -): PropertyDecorator; -export function OptionalField( - options?: OptionalFieldOptions, -): PropertyDecorator; +export function OptionalField(typeFn: () => any, options?: OptionalFieldOptions): PropertyDecorator; +export function OptionalField(options?: OptionalFieldOptions): PropertyDecorator; export function OptionalField(...args: any) { const typeFn: (() => any) | undefined = typeof args[0] === 'function' ? (args[0] as () => any) : undefined; diff --git a/src/common/pagination-list.ts b/src/common/pagination-list.ts index 8ecdc49a64..2c4c42c1e2 100644 --- a/src/common/pagination-list.ts +++ b/src/common/pagination-list.ts @@ -28,8 +28,7 @@ export function PaginatedList( { @Field(() => [itemClass], { description: - options.itemsDescription || - PaginatedList.itemDescriptionFor(lowerCase(itemClass.name)), + options.itemsDescription || PaginatedList.itemDescriptionFor(lowerCase(itemClass.name)), }) readonly items: readonly ListItem[]; diff --git a/src/common/pagination.input.ts b/src/common/pagination.input.ts index d39873f193..c10ed5f0c1 100644 --- a/src/common/pagination.input.ts +++ b/src/common/pagination.input.ts @@ -64,15 +64,12 @@ export abstract class CursorPaginationInput extends DataObject { readonly before?: string; } -export interface SortablePaginationInput - extends PaginationInput { +export interface SortablePaginationInput extends PaginationInput { sort: SortKey; order: Order; } -export const isSortablePaginationInput = ( - input: unknown, -): input is SortablePaginationInput => +export const isSortablePaginationInput = (input: unknown): input is SortablePaginationInput => isPaginationInput(input) && 'sort' in input && typeof input.sort === 'string' && diff --git a/src/common/required-when.ts b/src/common/required-when.ts index c57d53816d..8289bcd4c7 100644 --- a/src/common/required-when.ts +++ b/src/common/required-when.ts @@ -51,8 +51,7 @@ RequiredWhen.calc = >( return condition ? { ...condition, field: prop } : []; }); const missing = conditions.flatMap((condition) => { - return condition.isEnabled(obj) && - (condition.isMissing?.(obj) ?? obj[condition.field] == null) + return condition.isEnabled(obj) && (condition.isMissing?.(obj) ?? obj[condition.field] == null) ? { field: condition.field, description: condition.description, diff --git a/src/common/resource.dto.ts b/src/common/resource.dto.ts index 167764c651..3885fda944 100644 --- a/src/common/resource.dto.ts +++ b/src/common/resource.dto.ts @@ -1,21 +1,10 @@ import { CLASS_TYPE_METADATA, Field, InterfaceType } from '@nestjs/graphql'; import { type ClassType as ClassTypeVal } from '@nestjs/graphql/dist/enums/class-type.enum.js'; -import { - cached, - type FnLike, - mapValues, - setInspectOnClass, - setToJson, -} from '@seedcompany/common'; +import { cached, type FnLike, mapValues, setInspectOnClass, setToJson } from '@seedcompany/common'; import { createMetadataDecorator } from '@seedcompany/nest'; import { LazyGetter as Once } from 'lazy-get-decorator'; import { DateTime } from 'luxon'; -import type { - ResourceDBMap, - ResourceLike, - ResourceName, - ResourcesHost, -} from '~/core'; +import type { ResourceDBMap, ResourceLike, ResourceName, ResourcesHost } from '~/core'; import { type $, type e } from '~/core/gel/reexports'; import { type ScopedRole } from '../components/authorization/dto'; import { CalculatedSymbol } from './calculated.decorator'; @@ -39,13 +28,12 @@ const hasTypename = (value: unknown): value is { __typename: string } => '__typename' in value && typeof value.__typename === 'string'; -export const resolveByTypename = - (interfaceName: string) => (value: unknown) => { - if (hasTypename(value)) { - return EnhancedResource.resolve(value.__typename).name; - } - throw new ServerException(`Cannot resolve ${interfaceName} type`); - }; +export const resolveByTypename = (interfaceName: string) => (value: unknown) => { + if (hasTypename(value)) { + return EnhancedResource.resolve(value.__typename).name; + } + throw new ServerException(`Cannot resolve ${interfaceName} type`); +}; @InterfaceType({ resolveType: resolveByTypename(Resource.name), @@ -78,9 +66,7 @@ export type ResourceShape = AbstractClassType & { // An optional list of props that exist on the BaseNode in the DB. // Default should probably be considered the props on Resource class. BaseNodeProps?: string[]; - Relations?: Thunk< - Record | [ResourceShape] | undefined> - >; + Relations?: Thunk | [ResourceShape] | undefined>>; /** * Define this resource as being a child of another. * This means it's _created_ and scoped under this other resource. @@ -103,10 +89,7 @@ export class EnhancedResource> { static resourcesHost?: ResourcesHost; private constructor(readonly type: T) {} - private static readonly refs = new WeakMap< - ResourceShape, - EnhancedResource - >(); + private static readonly refs = new WeakMap, EnhancedResource>(); static resolve(ref: ResourceLike) { if (ref && typeof ref !== 'string') { @@ -118,9 +101,7 @@ export class EnhancedResource> { return EnhancedResource.resourcesHost.enhance(ref); } - static of>( - resource: T | EnhancedResource, - ): EnhancedResource { + static of>(resource: T | EnhancedResource): EnhancedResource { if (resource instanceof EnhancedResource) { return resource; } @@ -143,9 +124,7 @@ export class EnhancedResource> { * If it is, then this is returned otherwise undefined. * This should help to narrow with null coalescing when {@link is} isn't viable. */ - as>( - clsType: S, - ): EnhancedResource | undefined { + as>(clsType: S): EnhancedResource | undefined { return Object.is(this.type, clsType) ? // If check passes then T and S are the same. (this as unknown as EnhancedResource) @@ -218,48 +197,34 @@ export class EnhancedResource> { @Once() get extraPropsFromRelations() { - return this.relNamesIf>( - (rel) => !rel.resource?.hasParent, - ); + return this.relNamesIf>((rel) => !rel.resource?.hasParent); } @Once() get childSingleKeys() { - return this.relNamesIf>( - (rel) => !rel.list && !!rel.resource?.hasParent, - ); + return this.relNamesIf>((rel) => !rel.list && !!rel.resource?.hasParent); } @Once() get childListKeys() { - return this.relNamesIf>( - (rel) => rel.list && !!rel.resource?.hasParent, - ); + return this.relNamesIf>((rel) => rel.list && !!rel.resource?.hasParent); } - private relNamesIf( - predicate: (rel: EnhancedRelation) => boolean, - ): ReadonlySet { + private relNamesIf(predicate: (rel: EnhancedRelation) => boolean): ReadonlySet { return new Set( - [...this.relations.values()].flatMap((rel) => - predicate(rel) ? (rel.name as K) : [], - ), + [...this.relations.values()].flatMap((rel) => (predicate(rel) ? (rel.name as K) : [])), ); } @Once() get relations(): ReadonlyMap, EnhancedRelation> { const rawRels = - typeof this.type.Relations === 'function' - ? this.type.Relations() - : this.type.Relations ?? {}; + typeof this.type.Relations === 'function' ? this.type.Relations() : this.type.Relations ?? {}; return new Map( Object.entries(rawRels).map(([rawName, rawType]) => { const name = rawName as RelKey; const list = Array.isArray(rawType); - const type: ResourceShape | undefined = list - ? rawType[0]! - : rawType; + const type: ResourceShape | undefined = list ? rawType[0]! : rawType; const resource: EnhancedResource | undefined = type?.prototype ? EnhancedResource.of(type) : undefined; @@ -363,11 +328,11 @@ export type DBName = T['__element__']['__name__']; * If the type is abstract, then it is a string union of the concrete type's names. * If the type is concrete, then it is just the name, just as {@link DBName}. */ -export type DBNames = - T['__element__']['__polyTypenames__']; +export type DBNames = T['__element__']['__polyTypenames__']; -export type MaybeUnsecuredInstance> = - MaybeSecured>; +export type MaybeUnsecuredInstance> = MaybeSecured< + InstanceType +>; // Get the secured props of the resource // merged with all of the relations which are assumed to be secure. @@ -382,9 +347,7 @@ export type SecuredResourceKey< IncludeRelations extends boolean | undefined = true, > = keyof SecuredResource & string; -export type SecuredPropsPlusExtraKey< - TResourceStatic extends ResourceShape, -> = +export type SecuredPropsPlusExtraKey> = | (keyof SecuredProps & string) | ExtraPropsFromRelationsKey; diff --git a/src/common/retry.ts b/src/common/retry.ts index db84c13c65..919ec8f0ab 100644 --- a/src/common/retry.ts +++ b/src/common/retry.ts @@ -30,15 +30,9 @@ export type RetryOptions = Merge< const parseOptions = (options: RetryOptions = {}): Options => ({ ...options, - maxRetryTime: options?.maxRetryTime - ? Duration.from(options.maxRetryTime).toMillis() - : undefined, - minTimeout: options?.minTimeout - ? Duration.from(options.minTimeout).toMillis() - : undefined, - maxTimeout: options?.maxTimeout - ? Duration.from(options.maxTimeout).toMillis() - : undefined, + maxRetryTime: options?.maxRetryTime ? Duration.from(options.maxRetryTime).toMillis() : undefined, + minTimeout: options?.minTimeout ? Duration.from(options.minTimeout).toMillis() : undefined, + maxTimeout: options?.maxTimeout ? Duration.from(options.maxTimeout).toMillis() : undefined, }); export const retry = ( @@ -48,11 +42,7 @@ export const retry = ( export const Retry = (options?: RetryOptions) => - ( - target: unknown, - key: string | symbol, - descriptor: TypedPropertyDescriptor, - ) => { + (target: unknown, key: string | symbol, descriptor: TypedPropertyDescriptor) => { const parsed = parseOptions(options); const orig = descriptor.value!; const next: Record = { diff --git a/src/common/rich-text.scalar.ts b/src/common/rich-text.scalar.ts index 1008e1cd4d..554addb648 100644 --- a/src/common/rich-text.scalar.ts +++ b/src/common/rich-text.scalar.ts @@ -48,26 +48,18 @@ export class RichTextDocument { private static readonly serializedPrefix = '\0RichText\0'; static isSerialized(value: any): value is string { - return ( - typeof value === 'string' && - value.startsWith(RichTextDocument.serializedPrefix) - ); + return typeof value === 'string' && value.startsWith(RichTextDocument.serializedPrefix); } static fromSerialized(value: string) { - return RichTextDocument.from( - JSON.parse(value.slice(RichTextDocument.serializedPrefix.length)), - ); + return RichTextDocument.from(JSON.parse(value.slice(RichTextDocument.serializedPrefix.length))); } static serialize(doc: RichTextDocument) { return RichTextDocument.serializedPrefix + JSON.stringify(doc); } - static isEqual( - a: RichTextDocument | null | undefined, - b: RichTextDocument | null | undefined, - ) { + static isEqual(a: RichTextDocument | null | undefined, b: RichTextDocument | null | undefined) { // This is crude but it's better than nothing. const aBlocks = a?.blocks ?? []; const bBlocks = b?.blocks ?? []; diff --git a/src/common/role.dto.ts b/src/common/role.dto.ts index c32fc198f0..f83074652f 100644 --- a/src/common/role.dto.ts +++ b/src/common/role.dto.ts @@ -56,11 +56,7 @@ export const Role = makeEnum({ */ Hierarchies: { Finance: ['FinancialAnalyst', 'LeadFinancialAnalyst', 'Controller'], - Field: [ - 'ProjectManager', - 'RegionalDirector', - 'FieldOperationsDirector', - ], + Field: ['ProjectManager', 'RegionalDirector', 'FieldOperationsDirector'], } satisfies Record, }; }, diff --git a/src/common/search-camel-case.ts b/src/common/search-camel-case.ts index 6c99902b76..0d8f2379e5 100644 --- a/src/common/search-camel-case.ts +++ b/src/common/search-camel-case.ts @@ -17,24 +17,12 @@ import { startCase } from 'lodash'; * BTL -> BibleTranslationLiaison * rcc -> RegionalCommunicationsCoordinator */ -export function searchCamelCase( - items: Iterable, - needle: string, -) { +export function searchCamelCase(items: Iterable, needle: string) { const itemArr = [...items]; const collator = new Intl.Collator('en'); const fuzzy = new Fuzzy({ sort: (info, haystack) => { - const { - idx, - chars, - terms, - interLft2, - interLft1, - start, - intraIns, - interIns, - } = info; + const { idx, chars, terms, interLft2, interLft1, start, intraIns, interIns } = info; return idx .map((v, i) => i) diff --git a/src/common/secured-date.ts b/src/common/secured-date.ts index fa362f09bd..bee2763c0b 100644 --- a/src/common/secured-date.ts +++ b/src/common/secured-date.ts @@ -19,9 +19,7 @@ export abstract class SecuredDateTime implements ISecured, Secured { } @ObjectType({ implements: [ISecured] }) -export abstract class SecuredDateTimeNullable - implements ISecured, Secured -{ +export abstract class SecuredDateTimeNullable implements ISecured, Secured { @DateTimeField({ nullable: true }) readonly value?: DateTime | null; @@ -45,9 +43,7 @@ export abstract class SecuredDate implements ISecured, Secured { } @ObjectType({ implements: [ISecured] }) -export abstract class SecuredDateNullable - implements ISecured, Secured -{ +export abstract class SecuredDateNullable implements ISecured, Secured { @DateField({ nullable: true }) readonly value?: CalendarDate | null; @@ -68,9 +64,7 @@ export abstract class DateRange implements Range { } @ObjectType({ implements: [ISecured] }) -export abstract class SecuredDateRange - implements ISecured, Secured> -{ +export abstract class SecuredDateRange implements ISecured, Secured> { static fromPair( start: Secured, end: Secured, diff --git a/src/common/secured-list.ts b/src/common/secured-list.ts index 6a24b53eb1..656b08c96c 100644 --- a/src/common/secured-list.ts +++ b/src/common/secured-list.ts @@ -2,11 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { type GraphQLScalarType } from 'graphql'; import type { Class } from 'type-fest'; -import { - type ListOptions, - PaginatedList, - type PaginatedListType, -} from './pagination-list'; +import { type ListOptions, PaginatedList, type PaginatedListType } from './pagination-list'; import { ISecured } from './secured.interface'; import { type AbstractClassType } from './types'; diff --git a/src/common/secured-property.ts b/src/common/secured-property.ts index 44c297c0e8..e21f8163ad 100644 --- a/src/common/secured-property.ts +++ b/src/common/secured-property.ts @@ -21,15 +21,9 @@ export interface Secured { readonly canEdit: boolean; } -export type SecuredProps> = ConditionalPick< - Dto, - Secured ->; +export type SecuredProps> = ConditionalPick>; -export type SecuredKeys> = ConditionalKeys< - Dto, - Secured ->; +export type SecuredKeys> = ConditionalKeys>; export type MaybeSecured = Dto | UnsecuredDto; @@ -106,36 +100,24 @@ export function SecuredProperty< valueClass: Class | AbstractClassType | GraphQLScalarType, options: SecuredPropertyOptions = {}, ) { - return InnerSecuredProperty( - valueClass, - options, - ); + return InnerSecuredProperty(valueClass, options); } -export interface SecuredPropertyOptions< - Nullable extends boolean | undefined = false, -> extends Pick { +export interface SecuredPropertyOptions + extends Pick { /** Whether the property can be null (when the requester can read) */ nullable?: Nullable; } -type SecuredValue< - T, - Nullable extends boolean | undefined, -> = Nullable extends true ? T | null : T; +type SecuredValue = Nullable extends true ? T | null : T; function InnerSecuredProperty< GqlType extends GqlTypeReference, TsType = GqlType, Nullable extends boolean | undefined = false, ->( - valueClass: GqlType, - { nullable: _, ...options }: SecuredPropertyOptions = {}, -) { +>(valueClass: GqlType, { nullable: _, ...options }: SecuredPropertyOptions = {}) { @ObjectType({ isAbstract: true, implements: [ISecured] }) - abstract class SecuredPropertyClass - implements ISecured, Secured> - { + abstract class SecuredPropertyClass implements ISecured, Secured> { @Field(() => valueClass as Type, { ...options, nullable: true, @@ -150,18 +132,13 @@ function InnerSecuredProperty< return SecuredPropertyClass; } -SecuredEnum.descriptionFor = SecuredProperty.descriptionFor = ( - value: string, -) => stripIndent` +SecuredEnum.descriptionFor = SecuredProperty.descriptionFor = (value: string) => stripIndent` An object with ${value} \`value\` and additional authorization information. The value is only given if \`canRead\` is \`true\` otherwise it is \`null\`. These \`can*\` authorization properties are specific to the user making the request. `; -type SecuredList = SecuredValue< - readonly T[], - Nullable ->; +type SecuredList = SecuredValue; export function SecuredEnumList< T extends string, @@ -171,16 +148,10 @@ export function SecuredEnumList< valueClass: { [key in T]: EnumValue } | MadeEnum, options: SecuredPropertyOptions = {}, ) { - return SecuredList( - valueClass as any, - options, - ); + return SecuredList(valueClass as any, options); } -export function SecuredPropertyList< - T, - Nullable extends boolean | undefined = false, ->( +export function SecuredPropertyList( valueClass: Class | AbstractClassType | GraphQLScalarType, options: SecuredPropertyOptions = {}, ) { @@ -192,9 +163,7 @@ function SecuredList( options: SecuredPropertyOptions = {}, ) { @ObjectType({ isAbstract: true, implements: [ISecured] }) - abstract class SecuredPropertyListClass - implements ISecured, Secured> - { + abstract class SecuredPropertyListClass implements ISecured, Secured> { @Field(() => [valueClass as Type], { nullable: options.nullable, }) @@ -219,27 +188,22 @@ SecuredEnumList.descriptionFor = SecuredPropertyList.descriptionFor = ( @ObjectType({ description: SecuredProperty.descriptionFor('a string or null'), }) -export abstract class SecuredStringNullable extends SecuredProperty< - string, - string, - true ->(GraphQLString, { - nullable: true, -}) {} +export abstract class SecuredStringNullable extends SecuredProperty( + GraphQLString, + { + nullable: true, + }, +) {} @ObjectType({ description: SecuredProperty.descriptionFor('a string'), }) -export abstract class SecuredString extends SecuredProperty( - GraphQLString, -) {} +export abstract class SecuredString extends SecuredProperty(GraphQLString) {} @ObjectType({ description: SecuredPropertyList.descriptionFor('strings'), }) -export abstract class SecuredStringList extends SecuredPropertyList( - GraphQLString, -) {} +export abstract class SecuredStringList extends SecuredPropertyList(GraphQLString) {} @ObjectType({ description: SecuredProperty.descriptionFor('an integer'), @@ -249,11 +213,7 @@ export abstract class SecuredInt extends SecuredProperty(Int) {} @ObjectType({ description: SecuredProperty.descriptionFor('an integer or null'), }) -export abstract class SecuredIntNullable extends SecuredProperty< - number, - number, - true ->(Int, { +export abstract class SecuredIntNullable extends SecuredProperty(Int, { nullable: true, }) {} @@ -265,26 +225,21 @@ export abstract class SecuredFloat extends SecuredProperty(Float) {} @ObjectType({ description: SecuredProperty.descriptionFor('a float or null'), }) -export abstract class SecuredFloatNullable extends SecuredProperty< - number, - number, - true ->(Float, { nullable: true }) {} +export abstract class SecuredFloatNullable extends SecuredProperty(Float, { + nullable: true, +}) {} @ObjectType({ description: SecuredProperty.descriptionFor('a boolean'), }) -export abstract class SecuredBoolean extends SecuredProperty( - GraphQLBoolean, -) {} +export abstract class SecuredBoolean extends SecuredProperty(GraphQLBoolean) {} @ObjectType({ description: SecuredProperty.descriptionFor('a boolean or null'), }) -export abstract class SecuredBooleanNullable extends SecuredProperty< - boolean, - boolean, - true ->(GraphQLBoolean, { - nullable: true, -}) {} +export abstract class SecuredBooleanNullable extends SecuredProperty( + GraphQLBoolean, + { + nullable: true, + }, +) {} diff --git a/src/common/temporal/calendar-date.ts b/src/common/temporal/calendar-date.ts index f3da5e77a7..3614c2c134 100644 --- a/src/common/temporal/calendar-date.ts +++ b/src/common/temporal/calendar-date.ts @@ -1,8 +1,4 @@ -import { - type NonEmptyArray, - setInspectOnClass, - setToStringTag, -} from '@seedcompany/common'; +import { type NonEmptyArray, setInspectOnClass, setToStringTag } from '@seedcompany/common'; import { type DateObjectUnits, DateTime, @@ -35,9 +31,7 @@ export class CalendarDate return o instanceof CalendarDate; } - static fromDateTime( - dt: DateTime, - ): CalendarDate { + static fromDateTime(dt: DateTime): CalendarDate { return Object.assign( new CalendarDate(), dt instanceof CalendarDate ? dt : dt.startOf('day'), @@ -93,11 +87,7 @@ export class CalendarDate return CalendarDate.fromDateTime(super.fromSQL(text, options)); } - static fromFormat( - text: string, - format: string, - opts?: DateTimeOptions, - ): CalendarDate { + static fromFormat(text: string, format: string, opts?: DateTimeOptions): CalendarDate { return CalendarDate.fromDateTime(super.fromFormat(text, format, opts)); } @@ -143,17 +133,8 @@ export class CalendarDate hour: number, opts?: DateTimeJSOptions, ): CalendarDate; - static local( - year: number, - month: number, - day: number, - opts?: DateTimeJSOptions, - ): CalendarDate; - static local( - year: number, - month: number, - opts?: DateTimeJSOptions, - ): CalendarDate; + static local(year: number, month: number, day: number, opts?: DateTimeJSOptions): CalendarDate; + static local(year: number, month: number, opts?: DateTimeJSOptions): CalendarDate; static local(year: number, opts?: DateTimeJSOptions): CalendarDate; static local(opts?: DateTimeJSOptions): CalendarDate; static local(...args: any) { @@ -213,12 +194,7 @@ export class CalendarDate hour: number, options?: LocaleOptions, ): DateTime; - static utc( - year: number, - month: number, - day: number, - options?: LocaleOptions, - ): DateTime; + static utc(year: number, month: number, day: number, options?: LocaleOptions): DateTime; static utc(year: number, month: number, options?: LocaleOptions): DateTime; static utc(year: number, options?: LocaleOptions): DateTime; static utc(options?: LocaleOptions): DateTime; @@ -247,9 +223,7 @@ export class CalendarDate } reconfigure(properties: LocaleOptions): this { - return CalendarDate.fromDateTime( - super.reconfigure(properties) as DateTime, - ) as this; + return CalendarDate.fromDateTime(super.reconfigure(properties) as DateTime) as this; } set(values: DateObjectUnits): this { @@ -257,9 +231,7 @@ export class CalendarDate } setLocale(locale: string): this { - return CalendarDate.fromDateTime( - super.setLocale(locale) as DateTime, - ) as this; + return CalendarDate.fromDateTime(super.setLocale(locale) as DateTime) as this; } setZone(_zone: string | Zone, _options?: ZoneOptions): CalendarDate { diff --git a/src/common/temporal/date-interval.spec.ts b/src/common/temporal/date-interval.spec.ts index 0415b2ef31..d8576856cb 100644 --- a/src/common/temporal/date-interval.spec.ts +++ b/src/common/temporal/date-interval.spec.ts @@ -23,9 +23,7 @@ expect.extend({ promise: this.promise, })}\n\nExpected:${this.isNot ? ' not' : ''} ${this.utils.EXPECTED_COLOR( expected.toString(), - )}\nReceived:${this.isNot ? ' ' : ''} ${this.utils.RECEIVED_COLOR( - received.toString(), - )}`; + )}\nReceived:${this.isNot ? ' ' : ''} ${this.utils.RECEIVED_COLOR(received.toString())}`; return { pass, message }; }, toBeDateIntervals(received: DateInterval[], expected: DateInterval[]) { @@ -91,9 +89,11 @@ describe('DateInterval', () => { it('toDuration', () => { const interval = DateInterval.fromISO('2020-03-04/2021-05-22'); expect(interval.toDuration().toObject()).toEqual({ days: 445 }); - expect(interval.toDuration(['years', 'months', 'days']).toObject()).toEqual( - { years: 1, months: 2, days: 19 }, - ); + expect(interval.toDuration(['years', 'months', 'days']).toObject()).toEqual({ + years: 1, + months: 2, + days: 19, + }); }); it('fromInterval', () => { const dtInterval = Interval.fromISO('2020-03-04T04/2021-05-22T05'); @@ -207,16 +207,12 @@ describe('DateInterval', () => { describe('xor', () => { it('non-overlapping', () => { const nonOverlapping = [days(5, 6), days(8, 9)]; - expect(DateInterval.xor(nonOverlapping)).toBeDateIntervals( - nonOverlapping, - ); + expect(DateInterval.xor(nonOverlapping)).toBeDateIntervals(nonOverlapping); }); it('empty for fully overlapping', () => { expect(DateInterval.xor([days(5, 8), days(5, 8)])).toBeDateIntervals([]); - expect( - DateInterval.xor([days(5, 8), days(5, 6), days(6, 8)]), - ).toBeDateIntervals([]); + expect(DateInterval.xor([days(5, 8), days(5, 6), days(6, 8)])).toBeDateIntervals([]); }); it('non-overlapping parts', () => { @@ -233,59 +229,45 @@ describe('DateInterval', () => { ]); // adjacent - expect(DateInterval.xor([days(5, 6), days(7, 8)])).toBeDateIntervals([ - days(5, 8), - ]); + expect(DateInterval.xor([days(5, 6), days(7, 8)])).toBeDateIntervals([days(5, 8)]); // three intervals - expect( - DateInterval.xor([days(10, 13), days(8, 10), days(12, 14)]), - ).toBeDateIntervals([days(8, 9), days(11, 11), days(14, 14)]); + expect(DateInterval.xor([days(10, 13), days(8, 10), days(12, 14)])).toBeDateIntervals([ + days(8, 9), + days(11, 11), + days(14, 14), + ]); }); }); describe('difference', () => { it('non-overlapping', () => { - expect(days(7, 8).difference(days(10, 11))).toBeDateIntervals([ - days(7, 8), - ]); + expect(days(7, 8).difference(days(10, 11))).toBeDateIntervals([days(7, 8)]); expect(days(7, 8).difference(days(5, 6))).toBeDateIntervals([days(7, 8)]); }); it('non-overlapping parts', () => { - expect(days(8, 10).difference(days(10, 11))).toBeDateIntervals([ - days(8, 9), - ]); - expect(days(9, 11).difference(days(8, 9))).toBeDateIntervals([ - days(10, 11), - ]); - expect( - days(8, 11).difference(days(7, 8), days(11, 12)), - ).toBeDateIntervals([days(9, 10)]); - expect( - days(9, 11).difference(days(8, 9), days(11, 11)), - ).toBeDateIntervals([days(10, 10)]); + expect(days(8, 10).difference(days(10, 11))).toBeDateIntervals([days(8, 9)]); + expect(days(9, 11).difference(days(8, 9))).toBeDateIntervals([days(10, 11)]); + expect(days(8, 11).difference(days(7, 8), days(11, 12))).toBeDateIntervals([days(9, 10)]); + expect(days(9, 11).difference(days(8, 9), days(11, 11))).toBeDateIntervals([days(10, 10)]); }); it('empty for fully subtracted', () => { expect(days(8, 10).difference(days(7, 11))).toBeDateIntervals([]); - expect( - days(8, 11).difference(days(8, 9), days(10, 11)), - ).toBeDateIntervals([]); - expect( - days(6, 12).difference(days(6, 9), days(8, 11), days(10, 13)), - ).toBeDateIntervals([]); + expect(days(8, 11).difference(days(8, 9), days(10, 11))).toBeDateIntervals([]); + expect(days(6, 12).difference(days(6, 9), days(8, 11), days(10, 13))).toBeDateIntervals([]); }); it('returns outside parts when engulfing another interval', () => { - expect(days(8, 13).difference(days(10, 11))).toBeDateIntervals([ + expect(days(8, 13).difference(days(10, 11))).toBeDateIntervals([days(8, 9), days(12, 13)]); + expect(days(8, 14).difference(days(10, 11), days(11, 12))).toBeDateIntervals([ days(8, 9), - days(12, 13), + days(13, 14), ]); - expect( - days(8, 14).difference(days(10, 11), days(11, 12)), - ).toBeDateIntervals([days(8, 9), days(13, 14)]); }); it('allows holes', () => { - expect( - days(8, 14).difference(days(10, 10), days(12, 12)), - ).toBeDateIntervals([days(8, 9), days(11, 11), days(13, 14)]); + expect(days(8, 14).difference(days(10, 10), days(12, 12))).toBeDateIntervals([ + days(8, 9), + days(11, 11), + days(13, 14), + ]); }); }); it('overlaps', () => { diff --git a/src/common/temporal/date-interval.ts b/src/common/temporal/date-interval.ts index 8936c1576a..f3ee4b7215 100644 --- a/src/common/temporal/date-interval.ts +++ b/src/common/temporal/date-interval.ts @@ -142,10 +142,7 @@ export class DateInterval return int && int.end > int.start ? fromSuper(int) : null; } set(values: Partial>) { - return DateInterval.fromDateTimes( - values.start ?? this.start, - values.end ?? this.end, - ); + return DateInterval.fromDateTimes(values.start ?? this.start, values.end ?? this.end); } splitAt(...dates: CalendarDate[]) { return toSuper(this) @@ -177,17 +174,13 @@ export class DateInterval return `[${this.start.toISO()} – ${this.end.toISO()}]`; } expandToFull(unit: DateTimeUnit): DateInterval { - return DateInterval.fromDateTimes( - this.start.startOf(unit), - this.end.endOf(unit), - ); + return DateInterval.fromDateTimes(this.start.startOf(unit), this.end.endOf(unit)); } } setInspectOnClass(DateInterval, (i) => ({ collapsed }) => { return collapsed(`${format(i.start)} – ${format(i.end)}`, 'Dates'); }); -const format = (date: CalendarDate) => - date.toLocaleString(CalendarDate.DATE_SHORT); +const format = (date: CalendarDate) => date.toLocaleString(CalendarDate.DATE_SHORT); setToStringTag(DateInterval, 'DateInterval'); diff --git a/src/common/temporal/interval.ts b/src/common/temporal/interval.ts index ee1aaaac25..5c0fe1030b 100644 --- a/src/common/temporal/interval.ts +++ b/src/common/temporal/interval.ts @@ -34,8 +34,7 @@ setInspectOnClass(Interval, (i: Interval) => ({ stylize }) => { stylize(`)`, 'special') ); }); -const format = (dt: DateTime) => - dt.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS); +const format = (dt: DateTime) => dt.toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS); setToStringTag(Interval, 'Interval'); markSkipClassTransformation(Interval); @@ -43,10 +42,7 @@ markSkipClassTransformation(Interval); Object.defineProperty(Interval.prototype, 'expandToFull', { configurable: true, value: function expandToFull(this: Interval, unit: DateTimeUnit) { - return Interval.fromDateTimes( - this.start.startOf(unit), - this.end.endOf(unit), - ); + return Interval.fromDateTimes(this.start.startOf(unit), this.end.endOf(unit)); }, }); diff --git a/src/common/trace-layer.ts b/src/common/trace-layer.ts index 7379b3f442..6cef5a29e0 100644 --- a/src/common/trace-layer.ts +++ b/src/common/trace-layer.ts @@ -56,10 +56,10 @@ export class TraceLayer { readonly [layer in string]?: readonly TraceNames[]; }>(); private static readonly instances = new Map(); - private static readonly getNameCacheForInstance = cacheable< - object, - Map - >(new WeakMap(), () => new Map()); + private static readonly getNameCacheForInstance = cacheable>( + new WeakMap(), + () => new Map(), + ); private readonly seenClasses = new WeakSet>(); @@ -116,9 +116,7 @@ export class TraceLayer { const proto = cls.prototype; const descriptors = Object.getOwnPropertyDescriptors(proto); const methods = Object.entries(descriptors).flatMap(([key, descriptor]) => { - return key !== 'constructor' && typeof descriptor.value === 'function' - ? [key] - : []; + return key !== 'constructor' && typeof descriptor.value === 'function' ? [key] : []; }); const layer = this.layer; diff --git a/src/common/transform.decorator.ts b/src/common/transform.decorator.ts index 13f80c81a7..3ef6bdf0fa 100644 --- a/src/common/transform.decorator.ts +++ b/src/common/transform.decorator.ts @@ -1,5 +1,3 @@ import { Transform as T } from 'class-transformer'; -export const Transform = T as ( - ...args: Parameters -) => PropertyDecorator; +export const Transform = T as (...args: Parameters) => PropertyDecorator; diff --git a/src/common/types.ts b/src/common/types.ts index 10519f4ec6..7165cbbd4f 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -47,11 +47,7 @@ export const PartialType = BasePartialType as ( * * @see https://docs.nestjs.com/graphql/mapped-types#pick */ -export const PickType = BasePickType as < - T, - K extends keyof T, - Args extends unknown[], ->( +export const PickType = BasePickType as ( classRef: AbstractClass, keys: readonly K[], decorator?: ClassDecoratorFactory, @@ -65,11 +61,7 @@ export const PickType = BasePickType as < * * @see https://docs.nestjs.com/graphql/mapped-types#omit */ -export const OmitType = BaseOmitType as < - T, - K extends keyof T, - Args extends unknown[], ->( +export const OmitType = BaseOmitType as ( classRef: AbstractClass, keys: readonly K[], decorator?: ClassDecoratorFactory, @@ -127,18 +119,7 @@ export function IntersectTypes( type7: AbstractClass, type8: AbstractClass, ): IntersectedType; -export function IntersectTypes< - A, - B, - C, - D, - E, - F, - G, - H, - I, - Args extends unknown[], ->( +export function IntersectTypes( type1: AbstractClass, type2: AbstractClass, type3: AbstractClass, @@ -149,18 +130,7 @@ export function IntersectTypes< type8: AbstractClass, type9: AbstractClass, ): IntersectedType; -export function IntersectTypes< - A, - B, - C, - D, - E, - F, - G, - H, - I, - Args extends unknown[], ->( +export function IntersectTypes( type1: AbstractClass, type2: AbstractClass, type3: AbstractClass, @@ -176,10 +146,7 @@ export function IntersectTypes( ...types: Array> ): IntersectedType { return Object.assign( - (types as any).reduce( - (a: any, b: any) => (a ? IntersectionType(a, b) : b), - undefined, - ), + (types as any).reduce((a: any, b: any) => (a ? IntersectionType(a, b) : b), undefined), { members: types, }, diff --git a/src/common/url.field.ts b/src/common/url.field.ts index ac22edfad8..974e9a5597 100644 --- a/src/common/url.field.ts +++ b/src/common/url.field.ts @@ -1,10 +1,5 @@ import { applyDecorators } from '@nestjs/common'; -import { - type CustomScalar, - Field, - type FieldOptions, - Scalar, -} from '@nestjs/graphql'; +import { type CustomScalar, Field, type FieldOptions, Scalar } from '@nestjs/graphql'; import { IsUrl } from 'class-validator'; import { GraphQLError, Kind, type ValueNode } from 'graphql'; import { URL } from 'url'; @@ -33,9 +28,7 @@ export class UrlScalar implements CustomScalar { parseLiteral(ast: ValueNode): string | null { if (ast.kind !== Kind.STRING) { - throw new GraphQLError( - `Can only validate strings as URLs but got a: ${ast.kind}`, - ); + throw new GraphQLError(`Can only validate strings as URLs but got a: ${ast.kind}`); } return ast.value; } diff --git a/src/common/url.util.ts b/src/common/url.util.ts index fc39c48582..24b321ffd6 100644 --- a/src/common/url.util.ts +++ b/src/common/url.util.ts @@ -1,10 +1,7 @@ import { URL } from 'node:url'; import { posix } from 'path'; -export const withAddedPath = ( - url: URL | string, - ...pathSegments: string[] -): URL => { +export const withAddedPath = (url: URL | string, ...pathSegments: string[]): URL => { const next = new URL(String(url)); next.pathname = posix.join(next.pathname, ...pathSegments); return next; diff --git a/src/common/validators/email.validator.ts b/src/common/validators/email.validator.ts index 44fa30703a..0399f7fa1a 100644 --- a/src/common/validators/email.validator.ts +++ b/src/common/validators/email.validator.ts @@ -12,8 +12,7 @@ export const IsEmail = ( constraints: [options], validator: { validate: (value, args) => - value == null || - (typeof value === 'string' && isEmail(value, args?.constraints[0])), + value == null || (typeof value === 'string' && isEmail(value, args?.constraints[0])), defaultMessage: () => validationOptions?.each ? 'Each value in $property must be a valid email' diff --git a/src/common/validators/iso-3166-1-alpha-3.validator.ts b/src/common/validators/iso-3166-1-alpha-3.validator.ts index e4ad080a0a..803c78b427 100644 --- a/src/common/validators/iso-3166-1-alpha-3.validator.ts +++ b/src/common/validators/iso-3166-1-alpha-3.validator.ts @@ -7,8 +7,7 @@ export const ISO31661Alpha3 = (validationOptions?: ValidationOptions) => { name: 'ISO-3166-1-Alpha-3', validator: { - validate: (input) => - input != null ? Boolean(whereAlpha3(input)) : true, + validate: (input) => (input != null ? Boolean(whereAlpha3(input)) : true), defaultMessage: () => 'Invalid ISO-3166-1 alpha-3 country code', }, }, diff --git a/src/common/validators/short-id.validator.ts b/src/common/validators/short-id.validator.ts index 85a8a3479b..d7447c1b7d 100644 --- a/src/common/validators/short-id.validator.ts +++ b/src/common/validators/short-id.validator.ts @@ -1,8 +1,4 @@ -import { - type ArgumentMetadata, - Injectable, - type PipeTransform, -} from '@nestjs/common'; +import { type ArgumentMetadata, Injectable, type PipeTransform } from '@nestjs/common'; import { cached } from '@seedcompany/common'; import { type ValidationArguments, @@ -17,9 +13,7 @@ import { ValidateBy } from './validateBy'; export const IsId = (validationOptions?: ValidationOptions) => ValidateBy(ValidIdConstraint, { - message: validationOptions?.each - ? 'Each value in $property must be a valid ID' - : 'Invalid ID', + message: validationOptions?.each ? 'Each value in $property must be a valid ID' : 'Invalid ID', ...validationOptions, }); @@ -35,10 +29,7 @@ export class IdResolver { export class ValidIdConstraint implements ValidatorConstraintInterface { constructor(private readonly resolver: IdResolver) {} - private readonly resolved = new WeakMap< - object, - Map> - >(); + private readonly resolved = new WeakMap>>(); async validate(_value: unknown, args: ValidationArguments) { const value = args.value as unknown; @@ -64,9 +55,7 @@ export class ValidIdConstraint implements ValidatorConstraintInterface { if (!value.every(isValidId)) { return false; } - object[property] = await Promise.all( - value.map((id) => this.resolver.resolve(id)), - ); + object[property] = await Promise.all(value.map((id) => this.resolver.resolve(id))); return true; }); } diff --git a/src/common/validators/validateBy.ts b/src/common/validators/validateBy.ts index f6074e016d..3e2282f698 100644 --- a/src/common/validators/validateBy.ts +++ b/src/common/validators/validateBy.ts @@ -22,10 +22,7 @@ export type ValidateByOptions = | Type; export const ValidateBy = - ( - options: ValidateByOptions, - validationOptions?: ValidationOptions, - ): PropertyDecorator => + (options: ValidateByOptions, validationOptions?: ValidationOptions): PropertyDecorator => (object: Record, propertyName: string | symbol) => { registerDecorator({ target: object.constructor, diff --git a/src/common/variant.dto.ts b/src/common/variant.dto.ts index 6405dc8365..a0662f858f 100644 --- a/src/common/variant.dto.ts +++ b/src/common/variant.dto.ts @@ -42,10 +42,7 @@ export class Variant { value: (key: VKey) => { const found = list.find((v) => v.key === key); if (!found) { - throw new InputException( - `Variant with key "${key}" was not found`, - 'variant', - ); + throw new InputException(`Variant with key "${key}" was not found`, 'variant'); } return found; }, @@ -60,10 +57,11 @@ export type VariantList = ReadonlyArray> & { export type VariantKeyOf = T extends Variant ? K : never; -export type VariantOf> = - TResourceStatic extends { Variants: ReadonlyArray> } - ? VariantKey - : never; +export type VariantOf> = TResourceStatic extends { + Variants: ReadonlyArray>; +} + ? VariantKey + : never; /** * A variant input field. @@ -86,9 +84,7 @@ export const VariantInputField = < // Resolve default to variant object const resolveVariant = (value: Variant> | VariantOf) => - typeof value === 'string' - ? resource.Variants.find((v) => v.key === value) - : value; + typeof value === 'string' ? resource.Variants.find((v) => v.key === value) : value; const defaultVariant = many ? (defaultValue as any[])?.map(resolveVariant) : resolveVariant(defaultValue as VariantOf); @@ -110,8 +106,7 @@ export const VariantInputField = < return resolveVariant(value); }), IsIn(resource.Variants, { - message: ({ value }) => - `Variant with key "${String(value)}" was not found`, + message: ({ value }) => `Variant with key "${String(value)}" was not found`, each: many, }), ); diff --git a/src/common/xlsx.util.ts b/src/common/xlsx.util.ts index 58cd5c0113..092a0baaa2 100644 --- a/src/common/xlsx.util.ts +++ b/src/common/xlsx.util.ts @@ -30,8 +30,7 @@ export class WorkBook { } sheet(name: string): TSheet { - return (this.sheets[name] ?? - (this.sheets[name] = new Sheet(this, name))) as TSheet; + return (this.sheets[name] ?? (this.sheets[name] = new Sheet(this, name))) as TSheet; } registerCustomSheet(sheet: Sheet) { @@ -57,8 +56,7 @@ export class WorkBook { @Once() private get namedRanges(): Record { const rawList = this.book.Workbook?.Names ?? []; return mapEntries(rawList, ({ Ref: ref, Name: name }, { SKIP }) => { - const matched = - /^(?:\[\d+])?'?([^']+)'?!([$\dA-Z]+(?::[$\dA-Z]+)?)$/.exec(ref); + const matched = /^(?:\[\d+])?'?([^']+)'?!([$\dA-Z]+(?::[$\dA-Z]+)?)$/.exec(ref); if (!matched) { return SKIP; } @@ -105,10 +103,7 @@ export class Sheet { } @Once() get hidden() { - return ( - (this.workbook.Workbook?.Sheets?.find((s) => s.name === this.name) - ?.Hidden ?? 0) > 0 - ); + return (this.workbook.Workbook?.Sheets?.find((s) => s.name === this.name)?.Hidden ?? 0) > 0; } @Once() get sheetRange() { @@ -174,17 +169,9 @@ export class Sheet { */ // eslint-disable-next-line @typescript-eslint/unified-signatures cell(address: string | CellAddress): Cell; - cell( - column: string | number | CellAddress | Column | Cell, - row?: number | Row, - ) { + cell(column: string | number | CellAddress | Column | Cell, row?: number | Row) { if (column instanceof Cell) { - return new Cell( - this, - this.sheet, - this.sheet[column.toString()], - column.address, - ); + return new Cell(this, this.sheet, this.sheet[column.toString()], column.address); } if (column instanceof Column) { column = column.a1; @@ -194,9 +181,7 @@ export class Sheet { } if (row == null || typeof column === 'object') { const address = - typeof column === 'string' - ? utils.decode_cell(column) - : (column as CellAddress); + typeof column === 'string' ? utils.decode_cell(column) : (column as CellAddress); column = utils.encode_col(address.c); row = address.r + 1; } else if (typeof column === 'number') { @@ -205,12 +190,7 @@ export class Sheet { } const address = `${column}${row}`; - return new Cell( - this, - this.sheet, - this.sheet[address], - utils.decode_cell(address), - ); + return new Cell(this, this.sheet, this.sheet[address], utils.decode_cell(address)); } } @@ -323,11 +303,7 @@ abstract class Rangable { } export class Range extends Rangable { - constructor( - sheet: TSheet, - readonly start: Cell, - readonly end: Cell, - ) { + constructor(sheet: TSheet, readonly start: Cell, readonly end: Cell) { super(sheet); } } diff --git a/src/components/admin/admin.gel.repository.ts b/src/components/admin/admin.gel.repository.ts index 54918e5be4..19872b59d9 100644 --- a/src/components/admin/admin.gel.repository.ts +++ b/src/components/admin/admin.gel.repository.ts @@ -74,8 +74,6 @@ export class AdminGelRepository { const ghost = await this.agents.getGhost(); const u = e.cast(e.User, e.uuid(id)); const query = e.update(u, () => ({ set: { email } })); - await this.db - .withOptions((o) => o.withGlobals({ currentActorId: ghost.id })) - .run(query); + await this.db.withOptions((o) => o.withGlobals({ currentActorId: ghost.id })).run(query); } } diff --git a/src/components/admin/admin.gel.service.ts b/src/components/admin/admin.gel.service.ts index dbf5bdbcf5..51f7b1a3f7 100644 --- a/src/components/admin/admin.gel.service.ts +++ b/src/components/admin/admin.gel.service.ts @@ -1,8 +1,4 @@ -import { - Inject, - Injectable, - type OnApplicationBootstrap, -} from '@nestjs/common'; +import { Inject, Injectable, type OnApplicationBootstrap } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { LazyGetter as Once } from 'lazy-get-decorator'; import { CryptoService } from '~/core/authentication/crypto.service'; @@ -51,16 +47,12 @@ export class AdminGelService implements OnApplicationBootstrap { } if (root.id !== existing.id) { - this.logger.notice( - 'Stored root user ID differs from config, using stored value', - ); + this.logger.notice('Stored root user ID differs from config, using stored value'); // TODO hack. Change notification handlers to pull from DB, instead of config Object.assign(this.config.rootUser, { id: existing.id }); } - const passwordSame = await this.crypto - .verify(existing.hash, root.password) - .catch(() => false); + const passwordSame = await this.crypto.verify(existing.hash, root.password).catch(() => false); if (existing.email !== root.email || !passwordSame) { this.logger.notice('Updating root user to match app configuration'); await this.repo.updateEmail(root.id, root.email); diff --git a/src/components/admin/admin.repository.ts b/src/components/admin/admin.repository.ts index 20accb366c..06a0e13e05 100644 --- a/src/components/admin/admin.repository.ts +++ b/src/components/admin/admin.repository.ts @@ -114,11 +114,7 @@ export class AdminRepository extends CommonRepository { .first(); } - async createOrgResult( - createdAt: DateTime, - defaultOrgId: string, - defaultOrgName: string, - ) { + async createOrgResult(createdAt: DateTime, defaultOrgId: string, defaultOrgName: string) { return await this.db .query() .match(node('rootuser', 'RootUser')) diff --git a/src/components/admin/admin.service.ts b/src/components/admin/admin.service.ts index e5b6f5d00b..14cda6e19c 100644 --- a/src/components/admin/admin.service.ts +++ b/src/components/admin/admin.service.ts @@ -83,15 +83,11 @@ export class AdminService implements OnApplicationBootstrap { }); await this.repo.setRootUserLabel(tempId, id); } else { - const passwordSame = await this.crypto - .verify(existing.hash, password) - .catch(() => false); + const passwordSame = await this.crypto.verify(existing.hash, password).catch(() => false); // Neo4j can handle ID changes, because it anchors off the RootUser label. if (existing.id !== id || existing.email !== email || !passwordSame) { this.logger.notice('Updating root user to match app configuration'); - const hashedPassword = passwordSame - ? undefined - : await this.crypto.hash(password); + const hashedPassword = passwordSame ? undefined : await this.crypto.hash(password); await this.repo.updateRootUser(id, email, hashedPassword); } } @@ -109,9 +105,7 @@ export class AdminService implements OnApplicationBootstrap { if (doesOrgExist) { // add label to org - const giveOrgDefaultLabel = await this.repo.giveOrgDefaultLabel( - defaultOrgName, - ); + const giveOrgDefaultLabel = await this.repo.giveOrgDefaultLabel(defaultOrgName); if (!giveOrgDefaultLabel) { throw new ServerException('could not create default org'); diff --git a/src/components/authorization/assignable-roles.granter.ts b/src/components/authorization/assignable-roles.granter.ts index 17b5acfdf9..72f3a58e43 100644 --- a/src/components/authorization/assignable-roles.granter.ts +++ b/src/components/authorization/assignable-roles.granter.ts @@ -3,9 +3,7 @@ import { AssignableRoles } from './dto/assignable-roles.dto'; import { Granter, ResourceGranter } from './policy'; @Granter(AssignableRoles) -export class AssignableRolesGranter extends ResourceGranter< - typeof AssignableRoles -> { +export class AssignableRolesGranter extends ResourceGranter { grant(allowed: readonly Role[]) { return this.specifically((p) => p.many(...allowed).edit); } diff --git a/src/components/authorization/authorization.resolver.ts b/src/components/authorization/authorization.resolver.ts index c2101167ee..f3487c71ac 100644 --- a/src/components/authorization/authorization.resolver.ts +++ b/src/components/authorization/authorization.resolver.ts @@ -1,16 +1,7 @@ -import { - Query, - ResolveField, - Resolver, - type ResolverTypeFn, -} from '@nestjs/graphql'; +import { Query, ResolveField, Resolver, type ResolverTypeFn } from '@nestjs/graphql'; import { mapValues } from '@seedcompany/common'; import { EnhancedResource } from '~/common'; -import { - LoginOutput, - RegisterOutput, - SessionOutput, -} from '~/core/authentication/dto'; +import { LoginOutput, RegisterOutput, SessionOutput } from '~/core/authentication/dto'; import { Power } from './dto'; import { BetaFeatures } from './dto/beta-features.dto'; import { Privileges } from './policy'; @@ -39,22 +30,14 @@ function AuthExtraInfoResolver(concreteClass: ResolverTypeFn) { betaFeatures(): BetaFeatures { const privileges = this.privileges.for(BetaFeatures); const { props } = EnhancedResource.of(BetaFeatures); - return mapValues.fromList([...props], (prop) => - privileges.can('edit', prop), - ).asRecord; + return mapValues.fromList([...props], (prop) => privileges.can('edit', prop)).asRecord; } } return ExtraInfoResolver; } -export class SessionExtraInfoResolver extends AuthExtraInfoResolver( - () => SessionOutput, -) {} +export class SessionExtraInfoResolver extends AuthExtraInfoResolver(() => SessionOutput) {} -export class LoginExtraInfoResolver extends AuthExtraInfoResolver( - () => LoginOutput, -) {} +export class LoginExtraInfoResolver extends AuthExtraInfoResolver(() => LoginOutput) {} -export class RegisterExtraInfoResolver extends AuthExtraInfoResolver( - () => RegisterOutput, -) {} +export class RegisterExtraInfoResolver extends AuthExtraInfoResolver(() => RegisterOutput) {} diff --git a/src/components/authorization/dto/power.enum.ts b/src/components/authorization/dto/power.enum.ts index f53187f6c6..529733bd16 100644 --- a/src/components/authorization/dto/power.enum.ts +++ b/src/components/authorization/dto/power.enum.ts @@ -45,8 +45,7 @@ export const Power = makeEnum({ }, { value: 'CreateEthnologueLanguage', - deprecationReason: - 'Just check `CreateLanguage` instead. This is a sub-object ', + deprecationReason: 'Just check `CreateLanguage` instead. This is a sub-object ', }, { value: 'CreateFile', deprecationReason: 'Use something else instead' }, { @@ -100,8 +99,7 @@ export const Power = makeEnum({ }, { value: 'GrantRegionalCommunicationsCoordinatorRole', - deprecationReason: - 'Use `AuthorizedRoles.RegionalCommunicationsCoordinator` instead', + deprecationReason: 'Use `AuthorizedRoles.RegionalCommunicationsCoordinator` instead', }, { value: 'GrantTranslatorRole', diff --git a/src/components/authorization/dto/role.dto.ts b/src/components/authorization/dto/role.dto.ts index 743273e297..fec88bf53b 100644 --- a/src/components/authorization/dto/role.dto.ts +++ b/src/components/authorization/dto/role.dto.ts @@ -12,5 +12,4 @@ export const rolesForScope = export const withoutScope = (role: ScopedRole): Role => splitScope(role)[1]; -export const splitScope = (role: ScopedRole) => - role.split(':') as [AuthScope, Role]; +export const splitScope = (role: ScopedRole) => role.split(':') as [AuthScope, Role]; diff --git a/src/components/authorization/handler/can-impersonate.handler.ts b/src/components/authorization/handler/can-impersonate.handler.ts index 14ebf0a7e0..75ae62f053 100644 --- a/src/components/authorization/handler/can-impersonate.handler.ts +++ b/src/components/authorization/handler/can-impersonate.handler.ts @@ -9,9 +9,7 @@ export class CanImpersonateHandler { handle(event: CanImpersonateEvent) { const p = this.privileges.for(AssignableRoles); - const valid = event.session.roles - .values() - .every((role) => p.can('edit', role)); + const valid = event.session.roles.values().every((role) => p.can('edit', role)); event.allow.vote(valid); } } diff --git a/src/components/authorization/policies/by-feature/engagements-create-delete.policy.ts b/src/components/authorization/policies/by-feature/engagements-create-delete.policy.ts index 9d08466aca..2ee899d6bb 100644 --- a/src/components/authorization/policies/by-feature/engagements-create-delete.policy.ts +++ b/src/components/authorization/policies/by-feature/engagements-create-delete.policy.ts @@ -5,10 +5,7 @@ import { any, field, member, Policy, Role } from '../util'; r.Engagement.read, r.Engagement.whenAll( member, - any( - field('project.status', 'InDevelopment'), - field('project.step', 'DiscussingChangeToPlan'), - ), + any(field('project.status', 'InDevelopment'), field('project.step', 'DiscussingChangeToPlan')), ).create, r.Engagement.whenAll(member, field('status', 'InDevelopment')).delete, ]) diff --git a/src/components/authorization/policies/by-feature/project-managers-can-retract-own-change-to-plan-approval.ts b/src/components/authorization/policies/by-feature/project-managers-can-retract-own-change-to-plan-approval.ts index 09f06a0dc4..f7668e4ed1 100644 --- a/src/components/authorization/policies/by-feature/project-managers-can-retract-own-change-to-plan-approval.ts +++ b/src/components/authorization/policies/by-feature/project-managers-can-retract-own-change-to-plan-approval.ts @@ -3,9 +3,7 @@ import { member, Policy, Role } from '../util'; @Policy(Role.ProjectManager, (r) => [ r.ProjectWorkflowEvent.whenAll( member, - r.ProjectWorkflowEvent.isTransitions( - 'Retract Change To Plan Approval Request', - ), + r.ProjectWorkflowEvent.isTransitions('Retract Change To Plan Approval Request'), ).execute, ]) export class ProjectManagersCanRetractOwnChangeToPlanApproval {} diff --git a/src/components/authorization/policies/by-feature/user-can-manage-own-comments.policy.ts b/src/components/authorization/policies/by-feature/user-can-manage-own-comments.policy.ts index 62fbdc8017..c6e7df438b 100644 --- a/src/components/authorization/policies/by-feature/user-can-manage-own-comments.policy.ts +++ b/src/components/authorization/policies/by-feature/user-can-manage-own-comments.policy.ts @@ -1,8 +1,6 @@ import { creator, Policy } from '../util'; @Policy('all', (r) => - [r.Post, r.CommentThread, r.Comment].flatMap( - (it) => it.when(creator).edit.delete, - ), + [r.Post, r.CommentThread, r.Comment].flatMap((it) => it.when(creator).edit.delete), ) export class UserCanManageOwnCommentsPolicy {} diff --git a/src/components/authorization/policies/by-feature/user-can-manage-own-prompts.policy.ts b/src/components/authorization/policies/by-feature/user-can-manage-own-prompts.policy.ts index 960ff9fd7f..ad7413f25c 100644 --- a/src/components/authorization/policies/by-feature/user-can-manage-own-prompts.policy.ts +++ b/src/components/authorization/policies/by-feature/user-can-manage-own-prompts.policy.ts @@ -1,10 +1,8 @@ import { creator, Policy } from '../util'; @Policy('all', (r) => [ - [ - r.ProgressReportCommunityStory, - r.ProgressReportHighlight, - r.ProgressReportTeamNews, - ].map((it) => it.specifically((p) => p.prompt.when(creator).edit)), + [r.ProgressReportCommunityStory, r.ProgressReportHighlight, r.ProgressReportTeamNews].map((it) => + it.specifically((p) => p.prompt.when(creator).edit), + ), ]) export class UserCanManageOwnPromptsPolicy {} diff --git a/src/components/authorization/policies/by-feature/view-progress-report.policy.ts b/src/components/authorization/policies/by-feature/view-progress-report.policy.ts index 10501e8ebb..a86a0ea17d 100644 --- a/src/components/authorization/policies/by-feature/view-progress-report.policy.ts +++ b/src/components/authorization/policies/by-feature/view-progress-report.policy.ts @@ -12,12 +12,8 @@ import { Policy, Role, variant } from '../util'; Role.LeadFinancialAnalyst, ], (r) => [ - [ - r.ProgressReportCommunityStory, - r.ProgressReportHighlight, - r.ProgressReportTeamNews, - ].map((it) => - it.read.specifically((p) => p.responses.when(variant('published')).read), + [r.ProgressReportCommunityStory, r.ProgressReportHighlight, r.ProgressReportTeamNews].map( + (it) => it.read.specifically((p) => p.responses.when(variant('published')).read), ), r.StepProgress.when(variant('official')).read, ], diff --git a/src/components/authorization/policies/by-role/consultant-manager.policy.ts b/src/components/authorization/policies/by-role/consultant-manager.policy.ts index 3f7e64bc1a..e3856ce4c6 100644 --- a/src/components/authorization/policies/by-role/consultant-manager.policy.ts +++ b/src/components/authorization/policies/by-role/consultant-manager.policy.ts @@ -11,11 +11,7 @@ import * as Consultant from './consultant.policy'; r.Language.read.specifically( (p) => p - .many( - 'registryOfLanguageVarietiesCode', - 'signLanguageCode', - 'locations', - ) + .many('registryOfLanguageVarietiesCode', 'signLanguageCode', 'locations') .whenAny(member, sensMediumOrLower).read, ), r.LanguageEngagement.edit.specifically((p) => [ @@ -25,9 +21,7 @@ import * as Consultant from './consultant.policy'; p.address.none, p.locations.when(sensMediumOrLower).read, ]), - r.Partner.read - .specifically((p) => p.pointOfContact.none) - .children((r) => r.posts.edit), + r.Partner.read.specifically((p) => p.pointOfContact.none).children((r) => r.posts.edit), r.Partnership.read.specifically((p) => [ p.many('partner', 'organization').whenAny(member, sensOnlyLow).read, ]), diff --git a/src/components/authorization/policies/by-role/consultant.policy.ts b/src/components/authorization/policies/by-role/consultant.policy.ts index f58d784696..44804e2569 100644 --- a/src/components/authorization/policies/by-role/consultant.policy.ts +++ b/src/components/authorization/policies/by-role/consultant.policy.ts @@ -2,10 +2,7 @@ import { ProjectWorkflow } from '../../../project/workflow/project-workflow'; import { member, Policy, Role } from '../util'; export const projectTransitions = () => - ProjectWorkflow.pickNames( - 'Consultant Endorses Proposal', - 'Consultant Opposes Proposal', - ); + ProjectWorkflow.pickNames('Consultant Endorses Proposal', 'Consultant Opposes Proposal'); // NOTE: There could be other permissions for this role from other policies @Policy([Role.Consultant, Role.ConsultantManager], (r) => [ @@ -29,9 +26,7 @@ export const projectTransitions = () => r.StepProgress, ].map((it) => it.when(member).read), - r.InternshipEngagement.when(member).edit.specifically((p) => [ - p.ceremony.none, - ]), + r.InternshipEngagement.when(member).edit.specifically((p) => [p.ceremony.none]), r.LanguageEngagement.when(member).read.specifically((p) => [ p.many('pnp', 'paratextRegistryId').edit, ]), diff --git a/src/components/authorization/policies/by-role/experience-operations.policy.ts b/src/components/authorization/policies/by-role/experience-operations.policy.ts index aff7e1b55a..c632d809d5 100644 --- a/src/components/authorization/policies/by-role/experience-operations.policy.ts +++ b/src/components/authorization/policies/by-role/experience-operations.policy.ts @@ -4,9 +4,7 @@ import { member, Policy, Role, sensMediumOrLower } from '../util'; @Policy(Role.ExperienceOperations, (r) => [ r.EthnologueLanguage.read, r.Language.read, - r.Organization.whenAny(sensMediumOrLower).read.specifically((p) => [ - p.address.none, - ]), + r.Organization.whenAny(sensMediumOrLower).read.specifically((p) => [p.address.none]), r.Partner.when(sensMediumOrLower) .read.when(member) .read.specifically((p) => p.many('pmcEntityCode', 'pointOfContact').none) diff --git a/src/components/authorization/policies/by-role/field-partner.policy.ts b/src/components/authorization/policies/by-role/field-partner.policy.ts index fb19859b42..4f179ac07f 100644 --- a/src/components/authorization/policies/by-role/field-partner.policy.ts +++ b/src/components/authorization/policies/by-role/field-partner.policy.ts @@ -13,17 +13,15 @@ import { member, Policy, Role, variant } from '../util'; r.PeriodicReport.read, r.Product.read, r.ProgressReport.when(member).read.specifically((p) => p.reportFile.none), - [ - r.ProgressReportCommunityStory, - r.ProgressReportHighlight, - r.ProgressReportTeamNews, - ].flatMap((it) => [ - it.when(member).create.read, - it.specifically((p) => [ - p.responses.whenAll(member, variant('translated')).read, - p.responses.whenAll(member, variant('draft')).edit, - ]), - ]), + [r.ProgressReportCommunityStory, r.ProgressReportHighlight, r.ProgressReportTeamNews].flatMap( + (it) => [ + it.when(member).create.read, + it.specifically((p) => [ + p.responses.whenAll(member, variant('translated')).read, + p.responses.whenAll(member, variant('draft')).edit, + ]), + ], + ), r.ProgressReportMedia.whenAll(member, variant('draft')).create.edit, r.ProgressReportWorkflowEvent.transitions( 'Start', diff --git a/src/components/authorization/policies/by-role/financial-analyst-lead.policy.ts b/src/components/authorization/policies/by-role/financial-analyst-lead.policy.ts index e4d3c60699..99e25beab0 100644 --- a/src/components/authorization/policies/by-role/financial-analyst-lead.policy.ts +++ b/src/components/authorization/policies/by-role/financial-analyst-lead.policy.ts @@ -7,16 +7,9 @@ import * as FA from './financial-analyst.policy'; r.BudgetRecord.edit, r.Engagement.specifically( (p) => - p.many( - 'disbursementCompleteDate', - 'startDateOverride', - 'endDateOverride', - 'status', - ).edit, + p.many('disbursementCompleteDate', 'startDateOverride', 'endDateOverride', 'status').edit, ), - r.Language.read.specifically((c) => [ - c.locations.whenAny(member, sensMediumOrLower).read, - ]), + r.Language.read.specifically((c) => [c.locations.whenAny(member, sensMediumOrLower).read]), r.Organization.edit, r.Partner.edit, r.Partnership.read.create.delete.specifically((p) => [ diff --git a/src/components/authorization/policies/by-role/financial-analyst.policy.ts b/src/components/authorization/policies/by-role/financial-analyst.policy.ts index bcd42b373a..9e5553bfa5 100644 --- a/src/components/authorization/policies/by-role/financial-analyst.policy.ts +++ b/src/components/authorization/policies/by-role/financial-analyst.policy.ts @@ -1,12 +1,5 @@ import { ProjectWorkflow } from '../../../project/workflow/project-workflow'; -import { - inherit, - member, - Policy, - Role, - sensMediumOrLower, - sensOnlyLow, -} from '../util'; +import { inherit, member, Policy, Role, sensMediumOrLower, sensOnlyLow } from '../util'; export const projectTransitions = () => ProjectWorkflow.pickNames( @@ -17,72 +10,61 @@ export const projectTransitions = () => ); // NOTE: There could be other permissions for this role from other policies -@Policy( - [Role.FinancialAnalyst, Role.LeadFinancialAnalyst, Role.Controller], - (r) => [ - r.Budget.read.when(member).edit, - r.BudgetRecord.read.when(member).edit, - r.Ceremony.read, - r.Directory.read.when(member).edit, - r.Education.read, - inherit( - r.Engagement.when(member).specifically((p) => [ - p.many( - 'disbursementCompleteDate', - 'startDateOverride', - 'endDateOverride', - 'status', - ).edit, - ]), - r.LanguageEngagement.specifically((p) => p.paratextRegistryId.none), - ), - r.FieldRegion.read, - r.FieldZone.read, - r.FinancialReport.edit, - r.FundingAccount.read, - r.Language.read.specifically((p) => [ - p.locations.when(sensOnlyLow).read, - p.many('registryOfLanguageVarietiesCode', 'signLanguageCode').none, +@Policy([Role.FinancialAnalyst, Role.LeadFinancialAnalyst, Role.Controller], (r) => [ + r.Budget.read.when(member).edit, + r.BudgetRecord.read.when(member).edit, + r.Ceremony.read, + r.Directory.read.when(member).edit, + r.Education.read, + inherit( + r.Engagement.when(member).specifically((p) => [ + p.many('disbursementCompleteDate', 'startDateOverride', 'endDateOverride', 'status').edit, ]), - r.Organization.read.create.whenAny(member, sensMediumOrLower).edit, - r.Partner.read.create - .specifically((p) => [ - p.many('pointOfContact').whenAny(member, sensMediumOrLower).read, - ]) - .children((c) => c.posts.edit), - r.Partnership.read - .specifically((p) => [ - p.many('organization', 'partner').whenAny(member, sensMediumOrLower) - .read, - ]) - .when(member).edit.create.delete, - r.Product.read, - r.Project.read - .specifically((p) => [ - p - .many('rootDirectory', 'primaryLocation', 'otherLocations') - .whenAny(member, sensMediumOrLower).read, - p - .many( - 'step', - 'mouStart', - 'mouEnd', - 'rootDirectory', - 'financialReportPeriod', - 'financialReportReceivedAt', - ) - .read.when(member).edit, - ]) - .children((c) => c.posts.edit), - r.ProjectMember.read.when(member).edit.create.delete, - r.ProjectWorkflowEvent.read.whenAll( - member, - r.ProjectWorkflowEvent.isTransitions(projectTransitions), - ).execute, - r.PeriodicReport.read.when(member).edit, - r.StepProgress.read, - r.Unavailability.read, - r.User.read.create, - ], -) + r.LanguageEngagement.specifically((p) => p.paratextRegistryId.none), + ), + r.FieldRegion.read, + r.FieldZone.read, + r.FinancialReport.edit, + r.FundingAccount.read, + r.Language.read.specifically((p) => [ + p.locations.when(sensOnlyLow).read, + p.many('registryOfLanguageVarietiesCode', 'signLanguageCode').none, + ]), + r.Organization.read.create.whenAny(member, sensMediumOrLower).edit, + r.Partner.read.create + .specifically((p) => [p.many('pointOfContact').whenAny(member, sensMediumOrLower).read]) + .children((c) => c.posts.edit), + r.Partnership.read + .specifically((p) => [ + p.many('organization', 'partner').whenAny(member, sensMediumOrLower).read, + ]) + .when(member).edit.create.delete, + r.Product.read, + r.Project.read + .specifically((p) => [ + p + .many('rootDirectory', 'primaryLocation', 'otherLocations') + .whenAny(member, sensMediumOrLower).read, + p + .many( + 'step', + 'mouStart', + 'mouEnd', + 'rootDirectory', + 'financialReportPeriod', + 'financialReportReceivedAt', + ) + .read.when(member).edit, + ]) + .children((c) => c.posts.edit), + r.ProjectMember.read.when(member).edit.create.delete, + r.ProjectWorkflowEvent.read.whenAll( + member, + r.ProjectWorkflowEvent.isTransitions(projectTransitions), + ).execute, + r.PeriodicReport.read.when(member).edit, + r.StepProgress.read, + r.Unavailability.read, + r.User.read.create, +]) export class FinancialAnalystPolicy {} diff --git a/src/components/authorization/policies/by-role/fundraising.policy.ts b/src/components/authorization/policies/by-role/fundraising.policy.ts index ca2521f273..cf1ac2277d 100644 --- a/src/components/authorization/policies/by-role/fundraising.policy.ts +++ b/src/components/authorization/policies/by-role/fundraising.policy.ts @@ -21,9 +21,8 @@ import { member, Policy, Role, sensMediumOrLower } from '../util'; p.many('organization', 'partner').whenAny(member, sensMediumOrLower).read, ]), r.Project.read.specifically((p) => [ - p - .many('rootDirectory', 'primaryLocation', 'otherLocations') - .whenAny(member, sensMediumOrLower).read, + p.many('rootDirectory', 'primaryLocation', 'otherLocations').whenAny(member, sensMediumOrLower) + .read, ]), ]) export class FundraisingPolicy {} diff --git a/src/components/authorization/policies/by-role/intern.policy.ts b/src/components/authorization/policies/by-role/intern.policy.ts index 3042762cbb..0399020b90 100644 --- a/src/components/authorization/policies/by-role/intern.policy.ts +++ b/src/components/authorization/policies/by-role/intern.policy.ts @@ -18,21 +18,14 @@ import { inherit, member, Policy, Role } from '../util'; 'sponsorEstimatedEndDate', ).none, ), - r.Location.when(member).read.specifically( - (p) => p.many('fundingAccount').none, - ), + r.Location.when(member).read.specifically((p) => p.many('fundingAccount').none), r.PeriodicReport.when(member).read, r.Producible.when(member).read, r.Product.when(member).read, r.Project.when(member) .read.specifically((p) => [ p.rootDirectory.edit, - p.many( - 'departmentId', - 'marketingLocation', - 'marketingRegionOverride', - 'fieldRegion', - ).none, + p.many('departmentId', 'marketingLocation', 'marketingRegionOverride', 'fieldRegion').none, ]) .children((c) => c.posts.edit), r.ProjectMember.when(member).read, diff --git a/src/components/authorization/policies/by-role/investor-common.policy.ts b/src/components/authorization/policies/by-role/investor-common.policy.ts index 652922d103..98135fd79c 100644 --- a/src/components/authorization/policies/by-role/investor-common.policy.ts +++ b/src/components/authorization/policies/by-role/investor-common.policy.ts @@ -1,11 +1,4 @@ -import { - inherit, - member, - Policy, - Role, - sensMediumOrLower, - sensOnlyLow, -} from '../util'; +import { inherit, member, Policy, Role, sensMediumOrLower, sensOnlyLow } from '../util'; // NOTE: There could be other permissions for this role from other policies @Policy([Role.Marketing, Role.Fundraising, Role.ExperienceOperations], (r) => [ diff --git a/src/components/authorization/policies/by-role/marketing.policy.ts b/src/components/authorization/policies/by-role/marketing.policy.ts index dc75c35d8e..027e1da9c4 100644 --- a/src/components/authorization/policies/by-role/marketing.policy.ts +++ b/src/components/authorization/policies/by-role/marketing.policy.ts @@ -1,11 +1,4 @@ -import { - member, - Policy, - Role, - sensMediumOrLower, - sensOnlyLow, - variant, -} from '../util'; +import { member, Policy, Role, sensMediumOrLower, sensOnlyLow, variant } from '../util'; // NOTE: There could be other permissions for this role from other policies @Policy([Role.Marketing], (r) => [ @@ -18,24 +11,19 @@ import { .read.specifically((p) => p.many('pmcEntityCode', 'pointOfContact').none) .children((c) => c.posts.edit), r.ProgressReport.specifically((p) => p.status.read), // allows access to workflow - [ - r.ProgressReportCommunityStory, - r.ProgressReportHighlight, - r.ProgressReportTeamNews, - ].flatMap((it) => [ - it.create, - it.read.specifically((p) => [ - p.responses.read.when(variant('published')).edit, - ]), - ]), + [r.ProgressReportCommunityStory, r.ProgressReportHighlight, r.ProgressReportTeamNews].flatMap( + (it) => [ + it.create, + it.read.specifically((p) => [p.responses.read.when(variant('published')).edit]), + ], + ), r.ProgressReportMedia.read.when(variant('published')).create.edit, r.ProgressReportVarianceExplanation.read.specifically((p) => p.comments.none), r.ProgressReportWorkflowEvent.read.transitions('Publish').execute, r.Project.read .specifically((p) => [ - p - .many('rootDirectory', 'primaryLocation', 'otherLocations') - .whenAny(member, sensOnlyLow).read, + p.many('rootDirectory', 'primaryLocation', 'otherLocations').whenAny(member, sensOnlyLow) + .read, p.marketingLocation.edit, p.marketingRegionOverride.edit, ]) diff --git a/src/components/authorization/policies/by-role/mentor.policy.ts b/src/components/authorization/policies/by-role/mentor.policy.ts index e19ff6b721..84b49dd8dc 100644 --- a/src/components/authorization/policies/by-role/mentor.policy.ts +++ b/src/components/authorization/policies/by-role/mentor.policy.ts @@ -18,22 +18,13 @@ import { inherit, member, Policy, Role } from '../util'; 'sponsorEstimatedEndDate', ).none, ), - r.Location.when(member).read.specifically( - (p) => p.many('fundingAccount').none, - ), - r.Partnership.when(member).specifically( - (p) => p.many('organization', 'partner', 'types').read, - ), + r.Location.when(member).read.specifically((p) => p.many('fundingAccount').none), + r.Partnership.when(member).specifically((p) => p.many('organization', 'partner', 'types').read), r.Product.read, r.Project.when(member) .read.specifically((p) => [ p.rootDirectory.edit, - p.many( - 'departmentId', - 'marketingLocation', - 'marketingRegionOverride', - 'fieldRegion', - ).none, + p.many('departmentId', 'marketingLocation', 'marketingRegionOverride', 'fieldRegion').none, ]) .children((c) => c.posts.edit), r.ProjectMember.when(member).read, diff --git a/src/components/authorization/policies/by-role/project-manager.policy.ts b/src/components/authorization/policies/by-role/project-manager.policy.ts index 7f0242035b..2b03d9fcb9 100644 --- a/src/components/authorization/policies/by-role/project-manager.policy.ts +++ b/src/components/authorization/policies/by-role/project-manager.policy.ts @@ -55,58 +55,40 @@ export const projectTransitions = () => ); export const momentumProjectsTransitions = () => - ProjectWorkflow.pickNames( - 'Consultant Endorses Proposal', - 'Consultant Opposes Proposal', - ); + ProjectWorkflow.pickNames('Consultant Endorses Proposal', 'Consultant Opposes Proposal'); // NOTE: There could be other permissions for this role from other policies -@Policy( - [Role.ProjectManager, Role.RegionalDirector, Role.FieldOperationsDirector], - (r) => [ - Role.assignable(r, [ - Role.Intern, - Role.Liaison, - Role.BibleTranslationLiaison, - Role.Mentor, - Role.RegionalCommunicationsCoordinator, - Role.Translator, - ]), +@Policy([Role.ProjectManager, Role.RegionalDirector, Role.FieldOperationsDirector], (r) => [ + Role.assignable(r, [ + Role.Intern, + Role.Liaison, + Role.BibleTranslationLiaison, + Role.Mentor, + Role.RegionalCommunicationsCoordinator, + Role.Translator, + ]), - r.Budget.read.when(member).edit, - r.BudgetRecord.read.whenAll(member, field('status', 'Pending')).edit, - r.Ceremony.read.when(member).edit, - r.Education.read.create, - inherit( - r.Engagement.when(member).edit.specifically((p) => [ - p.disbursementCompleteDate.read, - ]), - ), - r.EthnologueLanguage.read, - r.FieldRegion.read, - r.FieldZone.read, - r.FundingAccount.read, - r.Language.read, - r.Organization.read, - r.Partner.read - .specifically((p) => p.pmcEntityCode.none) - .children((c) => c.posts.read.create), - r.Partnership.whenAny( - member, - sensMediumOrLower, - ).read.create.delete.specifically((p) => [ - p.many('agreement', 'agreementStatus', 'types', 'partner', 'primary') - .edit, - ]), - r.PeriodicReport.read.when(member).edit, - r.Producible.edit.create, - r.Product.read.when(member).edit.create.delete, - r.ProgressReport.when(member).edit, - [ - r.ProgressReportCommunityStory, - r.ProgressReportHighlight, - r.ProgressReportTeamNews, - ].flatMap((it) => [ + r.Budget.read.when(member).edit, + r.BudgetRecord.read.whenAll(member, field('status', 'Pending')).edit, + r.Ceremony.read.when(member).edit, + r.Education.read.create, + inherit(r.Engagement.when(member).edit.specifically((p) => [p.disbursementCompleteDate.read])), + r.EthnologueLanguage.read, + r.FieldRegion.read, + r.FieldZone.read, + r.FundingAccount.read, + r.Language.read, + r.Organization.read, + r.Partner.read.specifically((p) => p.pmcEntityCode.none).children((c) => c.posts.read.create), + r.Partnership.whenAny(member, sensMediumOrLower).read.create.delete.specifically((p) => [ + p.many('agreement', 'agreementStatus', 'types', 'partner', 'primary').edit, + ]), + r.PeriodicReport.read.when(member).edit, + r.Producible.edit.create, + r.Product.read.when(member).edit.create.delete, + r.ProgressReport.when(member).edit, + [r.ProgressReportCommunityStory, r.ProgressReportHighlight, r.ProgressReportTeamNews].flatMap( + (it) => [ it.read, it.when(member).create, it.specifically((p) => [ @@ -114,61 +96,61 @@ export const momentumProjectsTransitions = () => p.responses.when(member).read, p.responses.whenAll(member, variant('draft', 'translated', 'fpm')).edit, ]), - ]), - [r.ProgressReportMedia].flatMap((it) => [ - it.whenAll(sensOnlyLow, variant('fpm', 'published')).read, - it.when(member).read, - it.whenAll(member, variant('draft', 'translated', 'fpm')).create.edit, - ]), - r.ProgressReportVarianceExplanation.edit, - r.ProgressReportWorkflowEvent.read.transitions( - 'Start', - 'In Progress -> In Review', - 'In Progress -> Pending Translation', - 'Translation Done', - 'Translation Reject', - 'Withdraw Review Request', - 'In Review -> Needs Translation', - 'Review Reject', - 'Review Approve', - ).execute, - r.ProjectWorkflowEvent.read.whenAll( - member, - r.ProjectWorkflowEvent.isTransitions(projectTransitions), - ).execute, - // PMs can also endorse for consultant for momentum projects - r.ProjectWorkflowEvent.whenAll( - field('project.type', 'MomentumTranslation', 'Momentum'), - member, - r.ProjectWorkflowEvent.isTransitions(momentumProjectsTransitions), - ).execute, - r.Project.read.create - // eslint-disable-next-line prettier/prettier + ], + ), + [r.ProgressReportMedia].flatMap((it) => [ + it.whenAll(sensOnlyLow, variant('fpm', 'published')).read, + it.when(member).read, + it.whenAll(member, variant('draft', 'translated', 'fpm')).create.edit, + ]), + r.ProgressReportVarianceExplanation.edit, + r.ProgressReportWorkflowEvent.read.transitions( + 'Start', + 'In Progress -> In Review', + 'In Progress -> Pending Translation', + 'Translation Done', + 'Translation Reject', + 'Withdraw Review Request', + 'In Review -> Needs Translation', + 'Review Reject', + 'Review Approve', + ).execute, + r.ProjectWorkflowEvent.read.whenAll( + member, + r.ProjectWorkflowEvent.isTransitions(projectTransitions), + ).execute, + // PMs can also endorse for consultant for momentum projects + r.ProjectWorkflowEvent.whenAll( + field('project.type', 'MomentumTranslation', 'Momentum'), + member, + r.ProjectWorkflowEvent.isTransitions(momentumProjectsTransitions), + ).execute, + r.Project.read.create + // eslint-disable-next-line prettier/prettier .when(member).edit // - .or.specifically((p) => [ - p - .many('rootDirectory', 'otherLocations', 'primaryLocation') - // eslint-disable-next-line prettier/prettier + .or.specifically((p) => [ + p + .many('rootDirectory', 'otherLocations', 'primaryLocation') + // eslint-disable-next-line prettier/prettier .whenAny(member, sensMediumOrLower).read // - .when(member).edit, - p - .many('mouStart', 'mouEnd') - .read // - .whenAll( - member, - field('status', 'InDevelopment'), - // Only allow until financial endorsement - // field('step', stepsUntilFinancialEndorsement), - ).edit, - ]) - .children((c) => c.posts.read.create), - r.ProjectMember.read.when(member).edit.create.delete, - [r.StepProgress].flatMap((it) => [ - it.whenAll(member, variant('partner')).read, - it.whenAll(member, variant('official')).edit, - ]), - r.Unavailability.create.read, - r.User.create.read, - ], -) + .when(member).edit, + p + .many('mouStart', 'mouEnd') + .read // + .whenAll( + member, + field('status', 'InDevelopment'), + // Only allow until financial endorsement + // field('step', stepsUntilFinancialEndorsement), + ).edit, + ]) + .children((c) => c.posts.read.create), + r.ProjectMember.read.when(member).edit.create.delete, + [r.StepProgress].flatMap((it) => [ + it.whenAll(member, variant('partner')).read, + it.whenAll(member, variant('official')).edit, + ]), + r.Unavailability.create.read, + r.User.create.read, +]) export class ProjectManagerPolicy {} diff --git a/src/components/authorization/policies/by-role/regional-director.policy.ts b/src/components/authorization/policies/by-role/regional-director.policy.ts index 0fab033ea4..14d2d1f559 100644 --- a/src/components/authorization/policies/by-role/regional-director.policy.ts +++ b/src/components/authorization/policies/by-role/regional-director.policy.ts @@ -9,10 +9,7 @@ import * as PM from './project-manager.policy'; r.Project.when(member).edit.specifically( (p) => p.rootDirectory.edit.when(sensMediumOrLower).read, ), - r.ProjectWorkflowEvent.transitions( - PM.projectTransitions, - PM.momentumProjectsTransitions, - ).execute, + r.ProjectWorkflowEvent.transitions(PM.projectTransitions, PM.momentumProjectsTransitions).execute, r.ProjectWorkflowEvent.read.transitions( 'Approve Concept', 'Request Concept Changes', diff --git a/src/components/authorization/policies/by-role/translator.policy.ts b/src/components/authorization/policies/by-role/translator.policy.ts index 6c2580f93d..de4161aa7f 100644 --- a/src/components/authorization/policies/by-role/translator.policy.ts +++ b/src/components/authorization/policies/by-role/translator.policy.ts @@ -6,46 +6,30 @@ import { member, Policy, Role, variant } from '../util'; r.Engagement.when(member).read, r.EthnologueLanguage.when(member).read, r.Language.when(member).read.specifically( - (p) => - p.many('leastOfThese', 'leastOfTheseReason', 'sponsorEstimatedEndDate') - .none, - ), - r.Location.when(member).read.specifically( - (p) => p.many('fundingAccount').none, - ), - r.Partnership.when(member).specifically( - (p) => p.many('organization', 'partner', 'types').read, + (p) => p.many('leastOfThese', 'leastOfTheseReason', 'sponsorEstimatedEndDate').none, ), + r.Location.when(member).read.specifically((p) => p.many('fundingAccount').none), + r.Partnership.when(member).specifically((p) => p.many('organization', 'partner', 'types').read), r.Product.read, r.ProgressReport.when(member).read.specifically((p) => p.reportFile.none), - [ - r.ProgressReportCommunityStory, - r.ProgressReportHighlight, - r.ProgressReportTeamNews, - ].flatMap((it) => [ - it.when(member).read, - it.specifically((p) => [ - p.responses.whenAll(member, variant('draft')).read, - p.responses.whenAll(member, variant('translated')).edit, - ]), - ]), + [r.ProgressReportCommunityStory, r.ProgressReportHighlight, r.ProgressReportTeamNews].flatMap( + (it) => [ + it.when(member).read, + it.specifically((p) => [ + p.responses.whenAll(member, variant('draft')).read, + p.responses.whenAll(member, variant('translated')).edit, + ]), + ], + ), [r.ProgressReportMedia].flatMap((it) => [ it.when(member).read, it.whenAll(member, variant('translated')).create.edit, ]), - r.ProgressReportWorkflowEvent.transitions( - 'Translation Done', - 'Translation Reject', - ).execute, + r.ProgressReportWorkflowEvent.transitions('Translation Done', 'Translation Reject').execute, r.Project.when(member) .read.specifically((p) => [ p.rootDirectory.edit, - p.many( - 'departmentId', - 'marketingLocation', - 'marketingRegionOverride', - 'fieldRegion', - ).none, + p.many('departmentId', 'marketingLocation', 'marketingRegionOverride', 'fieldRegion').none, ]) .children((c) => c.posts.edit), r.ProjectMember.when(member).read, diff --git a/src/components/authorization/policies/conditions/enum-field.condition.ts b/src/components/authorization/policies/conditions/enum-field.condition.ts index 8546e5fd13..c2fc74c3d6 100644 --- a/src/components/authorization/policies/conditions/enum-field.condition.ts +++ b/src/components/authorization/policies/conditions/enum-field.condition.ts @@ -3,11 +3,7 @@ import { type Query } from 'cypher-query-builder'; import { get, startCase } from 'lodash'; import type { Get, Paths } from 'type-fest'; import { inspect, type InspectOptionsStylized } from 'util'; -import { - type ResourceShape, - unwrapSecured, - type UnwrapSecured, -} from '~/common'; +import { type ResourceShape, unwrapSecured, type UnwrapSecured } from '~/common'; import { type Condition, eqlInLiteralSet, @@ -30,18 +26,12 @@ export class EnumFieldCondition< // Double check at runtime that object has these, since they are usually // declared from DB, which cannot be verified. if (!object) { - throw new MissingContextException( - `Needed object's ${this.path} but object wasn't given`, - ); + throw new MissingContextException(`Needed object's ${this.path} but object wasn't given`); } - const value = get(object, this.path) as - | Get, Path> - | undefined; + const value = get(object, this.path) as Get, Path> | undefined; const actual = unwrapSecured(value); if (!actual) { - throw new MissingContextException( - `Needed object's ${this.path} but it wasn't found`, - ); + throw new MissingContextException(`Needed object's ${this.path} but it wasn't found`); } return this.allowed.has(actual); @@ -83,9 +73,7 @@ export class EnumFieldCondition< if (this.customId) { return this.customId; } - return `${startCase(this.path)} { ${[...this.allowed] - .map((s) => startCase(s)) - .join(', ')} }`; + return `${startCase(this.path)} { ${[...this.allowed].map((s) => startCase(s)).join(', ')} }`; } } @@ -95,27 +83,18 @@ export class EnumFieldCondition< export function field< TResourceStatic extends ResourceShape, Path extends Paths> & string, ->( - path: Path, - allowed: ManyIn>, - customId?: string, -) { +>(path: Path, allowed: ManyIn>, customId?: string) { const flattened = new Set( // Assume values are strings to normalize cardinality. typeof allowed === 'string' ? [allowed] : [...(allowed as Array>)], ); - return new EnumFieldCondition( - path, - flattened, - customId, - ); + return new EnumFieldCondition(path, flattened, customId); } type ManyIn = T | Iterable; -type ValueOfPath< - TResourceStatic extends ResourceShape, - Path extends string, -> = UnwrapSecured, Path>>; +type ValueOfPath, Path extends string> = UnwrapSecured< + Get, Path> +>; diff --git a/src/components/authorization/policies/conditions/member.condition.ts b/src/components/authorization/policies/conditions/member.condition.ts index 97fd0981f3..6f69687cca 100644 --- a/src/components/authorization/policies/conditions/member.condition.ts +++ b/src/components/authorization/policies/conditions/member.condition.ts @@ -35,9 +35,7 @@ class MemberCondition return 'exists((project)-[:member { active: true }]->(:ProjectMember)-[:user]->(:User { id: $currentUser }))'; } - setupEdgeQLContext({ - resource, - }: AsEdgeQLParams): Record { + setupEdgeQLContext({ resource }: AsEdgeQLParams): Record { return resource.isEmbedded ? { isMember: '(.container[is Project::ContextAware].isMember ?? false)' } : {}; @@ -120,8 +118,7 @@ export const member = new MemberCondition(); * NOTE that the policy roles are filtered before this, so only a subset of the * policy's roles can effectively be used here. */ -export const memberWith = (...roles: Role[]) => - new MemberWithRolesCondition(roles); +export const memberWith = (...roles: Role[]) => new MemberWithRolesCondition(roles); /** * Specify roles that should be used for the membership condition. @@ -142,9 +139,7 @@ export const withScope = (obj: T, roles: ScopedRole[]) => export const getScope = (object?: HasScope): ScopedRole[] => { if (!object) { - throw new MissingContextException( - "Needed object's scoped roles but object wasn't given", - ); + throw new MissingContextException("Needed object's scoped roles but object wasn't given"); } return Reflect.get(object, ScopedRoles) ?? Reflect.get(object, 'scope') ?? []; diff --git a/src/components/authorization/policies/conditions/role-and-exp-union.optimizer.ts b/src/components/authorization/policies/conditions/role-and-exp-union.optimizer.ts index 9c91cb7ba5..a6eadeaee1 100644 --- a/src/components/authorization/policies/conditions/role-and-exp-union.optimizer.ts +++ b/src/components/authorization/policies/conditions/role-and-exp-union.optimizer.ts @@ -52,15 +52,10 @@ export class RoleAndExpUnionOptimizer implements Optimizer { } isRoleAndX(condition: Condition) { - if ( - !(condition instanceof AndConditions) || - condition.conditions.length !== 2 - ) { + if (!(condition instanceof AndConditions) || condition.conditions.length !== 2) { return null; } - const roleIdx = condition.conditions.findIndex( - (cc) => cc instanceof RoleCondition, - ); + const roleIdx = condition.conditions.findIndex((cc) => cc instanceof RoleCondition); if (roleIdx === -1) { return null; } diff --git a/src/components/authorization/policies/conditions/role.condition.ts b/src/components/authorization/policies/conditions/role.condition.ts index b5b47d22df..fa105983ae 100644 --- a/src/components/authorization/policies/conditions/role.condition.ts +++ b/src/components/authorization/policies/conditions/role.condition.ts @@ -21,8 +21,7 @@ export class RoleCondition implements Condition { } asEdgeQLCondition({ namespace }: AsEdgeQLParams) { - const currentRoles = - 'global ' + fqnRelativeTo('default::currentRoles', namespace); + const currentRoles = 'global ' + fqnRelativeTo('default::currentRoles', namespace); const roleType = fqnRelativeTo('default::Role', namespace); return eqlDoesIntersect(currentRoles, this.allowed, roleType); } diff --git a/src/components/authorization/policies/conditions/self.condition.ts b/src/components/authorization/policies/conditions/self.condition.ts index 2596637161..2eb08b7ee3 100644 --- a/src/components/authorization/policies/conditions/self.condition.ts +++ b/src/components/authorization/policies/conditions/self.condition.ts @@ -8,9 +8,7 @@ import { MissingContextException, } from '../../policy/conditions'; -class SelfCondition - implements Condition -{ +class SelfCondition implements Condition { isAllowed({ object, session }: IsAllowedParams) { if (!object) { throw new MissingContextException(); diff --git a/src/components/authorization/policies/conditions/sensitivity.condition.ts b/src/components/authorization/policies/conditions/sensitivity.condition.ts index 8c89649b13..e736b3d626 100644 --- a/src/components/authorization/policies/conditions/sensitivity.condition.ts +++ b/src/components/authorization/policies/conditions/sensitivity.condition.ts @@ -15,9 +15,7 @@ const CQL_VAR = 'sens'; const EffectiveSensitivity = Symbol('EffectiveSensitivity'); -export type HasSensitivity = - | { sensitivity: Sensitivity } - | { [EffectiveSensitivity]: Sensitivity }; +export type HasSensitivity = { sensitivity: Sensitivity } | { [EffectiveSensitivity]: Sensitivity }; export class SensitivityCondition< TResourceStatic extends @@ -36,8 +34,7 @@ export class SensitivityCondition< throw new MissingContextException(); } const actual: Sensitivity | undefined = - Reflect.get(object, EffectiveSensitivity) ?? - Reflect.get(object, 'sensitivity'); + Reflect.get(object, EffectiveSensitivity) ?? Reflect.get(object, 'sensitivity'); if (!actual) { throw new MissingContextException( @@ -55,9 +52,7 @@ export class SensitivityCondition< prevApplied.add('sensitivity'); return query.subQuery('project', (sub) => - sub - .apply(matchProjectSens()) - .return(`${rankSens('sensitivity')} as ${CQL_VAR}`), + sub.apply(matchProjectSens()).return(`${rankSens('sensitivity')} as ${CQL_VAR}`), ); } @@ -128,10 +123,7 @@ export const sensOnlyLow = new SensitivityCondition(Sensitivity.Low); * This is useful when the object doesn't have a `sensitivity` property or * a different/"effective" sensitivity should be used for this logic. */ -export const withEffectiveSensitivity = ( - obj: T, - sensitivity: Sensitivity, -) => +export const withEffectiveSensitivity = (obj: T, sensitivity: Sensitivity) => Object.defineProperty(obj, EffectiveSensitivity, { value: sensitivity, enumerable: false, diff --git a/src/components/authorization/policies/conditions/variant-and-exp-union.optimizer.ts b/src/components/authorization/policies/conditions/variant-and-exp-union.optimizer.ts index 71bdd30ec6..accb9756d0 100644 --- a/src/components/authorization/policies/conditions/variant-and-exp-union.optimizer.ts +++ b/src/components/authorization/policies/conditions/variant-and-exp-union.optimizer.ts @@ -38,15 +38,10 @@ export class VariantAndExpUnionOptimizer implements Optimizer { } isVariantAndX(condition: Condition) { - if ( - !(condition instanceof AndConditions) || - condition.conditions.length !== 2 - ) { + if (!(condition instanceof AndConditions) || condition.conditions.length !== 2) { return null; } - const variantIdx = condition.conditions.findIndex( - (cc) => cc instanceof VariantCondition, - ); + const variantIdx = condition.conditions.findIndex((cc) => cc instanceof VariantCondition); if (variantIdx === -1) { return null; } diff --git a/src/components/authorization/policies/conditions/variant.condition.ts b/src/components/authorization/policies/conditions/variant.condition.ts index f565b64357..49a70c4cab 100644 --- a/src/components/authorization/policies/conditions/variant.condition.ts +++ b/src/components/authorization/policies/conditions/variant.condition.ts @@ -1,11 +1,6 @@ import { type Query } from 'cypher-query-builder'; import { inspect, type InspectOptionsStylized } from 'util'; -import { - type Many, - type ResourceShape, - type Variant, - type VariantOf, -} from '~/common'; +import { type Many, type ResourceShape, type Variant, type VariantOf } from '~/common'; import { type AsCypherParams, type Condition, @@ -30,10 +25,7 @@ export class VariantCondition> throw new MissingContextException(); } - const current = Reflect.get( - object, - VariantForCondition, - ) as VariantOf; + const current = Reflect.get(object, VariantForCondition) as VariantOf; return this.variants.has(current); } @@ -64,10 +56,7 @@ export class VariantCondition> } } -export const withVariant = ( - obj: T, - variant: string | Variant, -) => +export const withVariant = (obj: T, variant: string | Variant) => Object.defineProperty(obj, VariantForCondition, { value: typeof variant === 'string' ? variant : variant.key, enumerable: false, diff --git a/src/components/authorization/policy/actions.ts b/src/components/authorization/policy/actions.ts index cb08977d01..dac97ca77d 100644 --- a/src/components/authorization/policy/actions.ts +++ b/src/components/authorization/policy/actions.ts @@ -35,8 +35,4 @@ export const ChildSingleAction = makeEnum(['read', 'edit']); * @internal */ export type AnyAction = EnumType; -export const AnyAction = makeEnum([ - ...ResourceAction, - ...PropAction, - ...ChildRelationshipAction, -]); +export const AnyAction = makeEnum([...ResourceAction, ...PropAction, ...ChildRelationshipAction]); diff --git a/src/components/authorization/policy/builder/allow-all.helper.ts b/src/components/authorization/policy/builder/allow-all.helper.ts index e8a0f0ce37..928ae3fad9 100644 --- a/src/components/authorization/policy/builder/allow-all.helper.ts +++ b/src/components/authorization/policy/builder/allow-all.helper.ts @@ -15,7 +15,5 @@ export const allowAll = /** * A helper to allow these actions for this resource. */ -export const allowActions = ( - granter: ValueOf, - ...actions: ResourceAction[] -) => asNormalized(granter, (g) => g[action](...actions)); +export const allowActions = (granter: ValueOf, ...actions: ResourceAction[]) => + asNormalized(granter, (g) => g[action](...actions)); diff --git a/src/components/authorization/policy/builder/as-normalized.helper.ts b/src/components/authorization/policy/builder/as-normalized.helper.ts index 18bc7294f5..b8521b8240 100644 --- a/src/components/authorization/policy/builder/as-normalized.helper.ts +++ b/src/components/authorization/policy/builder/as-normalized.helper.ts @@ -11,5 +11,4 @@ import { type ResourceGranter } from './resource-granter'; export const asNormalized = ( granter: ValueOf, fn: (granter: ResourceGranter) => ResourceGranter, -): ValueOf => - fn(granter as ResourceGranter) as ValueOf; +): ValueOf => fn(granter as ResourceGranter) as ValueOf; diff --git a/src/components/authorization/policy/builder/child-relationship-granter.ts b/src/components/authorization/policy/builder/child-relationship-granter.ts index 67081360a2..2e81adb4c4 100644 --- a/src/components/authorization/policy/builder/child-relationship-granter.ts +++ b/src/components/authorization/policy/builder/child-relationship-granter.ts @@ -34,14 +34,10 @@ export abstract class ChildRelationshipGranter< resource: EnhancedResource, stagedCondition: Condition | undefined, ): ChildRelationshipsGranter { - const granter = createLazyRecord< - ChildRelationshipsGranter - >({ + const granter = createLazyRecord>({ getKeys: () => resource.childKeys, calculate: (rel) => { - const cls = resource.childSingleKeys.has(rel) - ? ChildSingleGranter - : ChildListGranter; + const cls = resource.childSingleKeys.has(rel) ? ChildSingleGranter : ChildListGranter; return new cls(resource, [rel as any], stagedCondition) as any; }, }); @@ -107,13 +103,8 @@ export class ChildListGranter< } } -export type ChildRelationshipsGranter< - TResourceStatic extends ResourceShape, -> = Record< +export type ChildRelationshipsGranter> = Record< ChildSinglesKey, Omit, 'extract'> > & - Record< - ChildListsKey, - Omit, 'extract'> - >; + Record, Omit, 'extract'>>; diff --git a/src/components/authorization/policy/builder/granter.decorator.ts b/src/components/authorization/policy/builder/granter.decorator.ts index afbd9d6070..0bd3005984 100644 --- a/src/components/authorization/policy/builder/granter.decorator.ts +++ b/src/components/authorization/policy/builder/granter.decorator.ts @@ -37,9 +37,7 @@ export const Granter = ( }); export const discover = (discovery: DiscoveryService) => - discovery.providersWithMetaAtKey( - GRANTER_FACTORY_METADATA_KEY, - ); + discovery.providersWithMetaAtKey(GRANTER_FACTORY_METADATA_KEY); const GRANTER_FACTORY_METADATA_KEY = Symbol('GranterFactory'); diff --git a/src/components/authorization/policy/builder/inherit-resource.helper.ts b/src/components/authorization/policy/builder/inherit-resource.helper.ts index ceb4bd34cd..ce7868057e 100644 --- a/src/components/authorization/policy/builder/inherit-resource.helper.ts +++ b/src/components/authorization/policy/builder/inherit-resource.helper.ts @@ -26,14 +26,9 @@ type Granter = ValueOf; * r.FooBar.read, * ]) */ -export function inherit( - theInterface: Granter, - ...implementations: Granter[] -): Granter[] { +export function inherit(theInterface: Granter, ...implementations: Granter[]): Granter[] { return [ theInterface, - ...implementations.map((granter) => - granter[withOther](theInterface as any), - ), + ...implementations.map((granter) => granter[withOther](theInterface as any)), ]; } diff --git a/src/components/authorization/policy/builder/perm-granter.ts b/src/components/authorization/policy/builder/perm-granter.ts index d47d3f9a2d..b6e9f9f5ed 100644 --- a/src/components/authorization/policy/builder/perm-granter.ts +++ b/src/components/authorization/policy/builder/perm-granter.ts @@ -21,9 +21,7 @@ export abstract class PermGranter< TResourceStatic extends ResourceShape, TAction extends string, > { - protected constructor( - protected stagedCondition?: Condition, - ) {} + protected constructor(protected stagedCondition?: Condition) {} /** * The requester can do nothing with this prop or object. @@ -62,15 +60,11 @@ export abstract class PermGranter< const cloned = this.clone(); cloned.stagedCondition = condition; if (process.env.NODE_ENV !== 'production') { - cloned.trailingCondition = new Error( - 'Condition applies to nothing. Specify before actions.', - ); + cloned.trailingCondition = new Error('Condition applies to nothing. Specify before actions.'); // Find first frame that is not from a Granter call. let frame = cloned.trailingCondition .stack!.split('\n') - .find( - (line) => line.includes(' at ') && !/\s+at \w+Granter\./.exec(line), - ); + .find((line) => line.includes(' at ') && !/\s+at \w+Granter\./.exec(line)); // If frame is the function call of Policy decorator, which it probably is, // then remove the useless type/function/method name for clarity. if (frame?.startsWith(' at Object.def')) { @@ -126,10 +120,7 @@ export abstract class PermGranter< protected trailingCondition?: Error; protected clone(): this { - const cloned = Object.assign( - Object.create(Object.getPrototypeOf(this)), - this, - ); + const cloned = Object.assign(Object.create(Object.getPrototypeOf(this)), this); cloned.perms = [...this.perms]; return cloned; } diff --git a/src/components/authorization/policy/builder/prop-granter.ts b/src/components/authorization/policy/builder/prop-granter.ts index d4583f174e..3d32606eb4 100644 --- a/src/components/authorization/policy/builder/prop-granter.ts +++ b/src/components/authorization/policy/builder/prop-granter.ts @@ -8,9 +8,10 @@ import { type PropAction } from '../actions'; import { type Condition } from '../conditions'; import { action, extract, PermGranter } from './perm-granter'; -export class PropGranter< - TResourceStatic extends ResourceShape, -> extends PermGranter { +export class PropGranter> extends PermGranter< + TResourceStatic, + PropAction +> { constructor( protected resource: EnhancedResource, protected properties: Array, @@ -47,8 +48,7 @@ export class PropGranter< ): PropsGranter { const granter = createLazyRecord>({ getKeys: () => resource.securedPropsPlusExtra, - calculate: (prop) => - new PropGranter(resource, [prop], stagedCondition) as any, + calculate: (prop) => new PropGranter(resource, [prop], stagedCondition) as any, // @ts-expect-error IDK why this is failing base: { many: (...props) => new PropGranter(resource, props, stagedCondition), diff --git a/src/components/authorization/policy/builder/resource-granter.ts b/src/components/authorization/policy/builder/resource-granter.ts index c71ca88579..aff60da795 100644 --- a/src/components/authorization/policy/builder/resource-granter.ts +++ b/src/components/authorization/policy/builder/resource-granter.ts @@ -1,9 +1,4 @@ -import { - type EnhancedResource, - many, - type Many, - type ResourceShape, -} from '~/common'; +import { type EnhancedResource, many, type Many, type ResourceShape } from '~/common'; import { type ResourceAction } from '../actions'; import { ChildRelationshipGranter, @@ -22,13 +17,12 @@ export type ChildrenGranterFn> = ( granter: ChildRelationshipsGranter, ) => Many>; -export class ResourceGranter< - TResourceStatic extends ResourceShape, -> extends PermGranter { +export class ResourceGranter> extends PermGranter< + TResourceStatic, + ResourceAction +> { protected propGrants: ReadonlyArray> = []; - protected childRelationshipGrants: ReadonlyArray< - ChildRelationshipGranter - > = []; + protected childRelationshipGrants: ReadonlyArray> = []; constructor(protected resource: EnhancedResource) { super(); @@ -44,16 +38,12 @@ export class ResourceGranter< * unless the prop defines its own condition. */ protected specifically(grants: PropsGranterFn): this { - const propsGranter = PropGranter.forResource( - this.resource, - this.stagedCondition, - ); + const propsGranter = PropGranter.forResource(this.resource, this.stagedCondition); const newGrants = many(grants(propsGranter)); const cloned = this.clone(); - cloned.trailingCondition = - newGrants.length > 0 ? undefined : cloned.trailingCondition; + cloned.trailingCondition = newGrants.length > 0 ? undefined : cloned.trailingCondition; cloned.propGrants = [...this.propGrants, ...newGrants]; return cloned; } @@ -76,20 +66,13 @@ export class ResourceGranter< * unless the relation defines its own condition. */ protected children(relationGrants: ChildrenGranterFn): this { - const granter = ChildRelationshipGranter.forResource( - this.resource, - this.stagedCondition, - ); + const granter = ChildRelationshipGranter.forResource(this.resource, this.stagedCondition); const newGrants = many(relationGrants(granter)); const cloned = this.clone(); - cloned.trailingCondition = - newGrants.length > 0 ? undefined : cloned.trailingCondition; - cloned.childRelationshipGrants = [ - ...this.childRelationshipGrants, - ...newGrants, - ]; + cloned.trailingCondition = newGrants.length > 0 ? undefined : cloned.trailingCondition; + cloned.childRelationshipGrants = [...this.childRelationshipGrants, ...newGrants]; return cloned; } @@ -109,9 +92,7 @@ export class ResourceGranter< ...super[extract](), resource: this.resource, props: this.propGrants.map((prop) => prop[extract]()), - childRelationships: this.childRelationshipGrants.map((rel) => - rel[extract](), - ), + childRelationships: this.childRelationshipGrants.map((rel) => rel[extract]()), }; } diff --git a/src/components/authorization/policy/conditions/aggregate.condition.ts b/src/components/authorization/policy/conditions/aggregate.condition.ts index ea5be5954e..1c11ed28c7 100644 --- a/src/components/authorization/policy/conditions/aggregate.condition.ts +++ b/src/components/authorization/policy/conditions/aggregate.condition.ts @@ -16,9 +16,7 @@ export abstract class AggregateConditions< TResourceStatic extends ResourceShape = ResourceShape, > implements Condition { - protected constructor( - readonly conditions: Array>, - ) {} + protected constructor(readonly conditions: Array>) {} attachPolicy(policy: Policy): Condition { const newConditions = this.conditions.map( @@ -34,29 +32,19 @@ export abstract class AggregateConditions< return this.conditions[aggFn]((condition) => condition.isAllowed(params)); } - setupCypherContext( - query: Query, - prevApplied: Set, - other: AsCypherParams, - ) { + setupCypherContext(query: Query, prevApplied: Set, other: AsCypherParams) { for (const condition of this.conditions) { - query = - condition.setupCypherContext?.(query, prevApplied, other) ?? query; + query = condition.setupCypherContext?.(query, prevApplied, other) ?? query; } return query; } - asCypherCondition( - query: Query, - other: AsCypherParams, - ): string { + asCypherCondition(query: Query, other: AsCypherParams): string { if (this.conditions.length === 0) { return 'true'; } const separator = this instanceof AndConditions ? ' AND ' : ' OR '; - const inner = this.conditions - .map((c) => c.asCypherCondition(query, other)) - .join(separator); + const inner = this.conditions.map((c) => c.asCypherCondition(query, other)).join(separator); return `(${inner})`; } @@ -73,9 +61,7 @@ export abstract class AggregateConditions< return 'true'; } const separator = this instanceof AndConditions ? '\nand ' : '\nor '; - const inner = this.conditions - .map((c) => c.asEdgeQLCondition(params)) - .join(separator); + const inner = this.conditions.map((c) => c.asEdgeQLCondition(params)).join(separator); return `(${addIndent('\n' + inner, 2)}\n)`; } @@ -92,9 +78,7 @@ export abstract class AggregateConditions< export class AndConditions< TResourceStatic extends ResourceShape = ResourceShape, > extends AggregateConditions { - static from>( - ...conditionsIn: Array | Nil> - ) { + static from>(...conditionsIn: Array | Nil>) { const conditions = conditionsIn.filter(isNotNil); if (conditions.length === 1) { return conditions[0]; @@ -118,9 +102,7 @@ export class AndConditions< export class OrConditions< TResourceStatic extends ResourceShape = ResourceShape, > extends AggregateConditions { - static from>( - ...conditions: Array | Nil> - ) { + static from>(...conditions: Array | Nil>) { return OrConditions.fromAll(conditions); } @@ -136,9 +118,7 @@ export class OrConditions< throw new Error('OrConditions requires at least one condition'); } - const flattened = conditions.flatMap((c) => - c instanceof OrConditions ? c.conditions : c, - ); + const flattened = conditions.flatMap((c) => (c instanceof OrConditions ? c.conditions : c)); if (!optimize) { return new OrConditions(flattened); @@ -159,5 +139,4 @@ export class OrConditions< export const all = AndConditions.from; export const any = OrConditions.from; -const byType = >(item: InstanceType) => - item.constructor as Constructor; +const byType = >(item: InstanceType) => item.constructor as Constructor; diff --git a/src/components/authorization/policy/conditions/calculated.condition.ts b/src/components/authorization/policy/conditions/calculated.condition.ts index aa1895e946..70927a578a 100644 --- a/src/components/authorization/policy/conditions/calculated.condition.ts +++ b/src/components/authorization/policy/conditions/calculated.condition.ts @@ -8,14 +8,10 @@ export class CalculatedCondition implements Condition { return false; } asCypherCondition(): never { - throw new ServerException( - 'Action is calculated, it should not be going to Cypher', - ); + throw new ServerException('Action is calculated, it should not be going to Cypher'); } asEdgeQLCondition(): never { - throw new ServerException( - 'Action is calculated, it should not be going to EdgeQL', - ); + throw new ServerException('Action is calculated, it should not be going to EdgeQL'); } [inspect.custom]() { return 'Calculated'; diff --git a/src/components/authorization/policy/conditions/condition-visitor.ts b/src/components/authorization/policy/conditions/condition-visitor.ts index 6fc8410c65..1725510879 100644 --- a/src/components/authorization/policy/conditions/condition-visitor.ts +++ b/src/components/authorization/policy/conditions/condition-visitor.ts @@ -1,10 +1,7 @@ import { AggregateConditions } from './aggregate.condition'; import { type Condition } from './condition.interface'; -export const visitCondition = ( - condition: Condition, - iteratee: (node: Condition) => void, -): void => { +export const visitCondition = (condition: Condition, iteratee: (node: Condition) => void): void => { iteratee(condition); if (condition instanceof AggregateConditions) { for (const c of condition.conditions) { diff --git a/src/components/authorization/policy/conditions/condition.interface.ts b/src/components/authorization/policy/conditions/condition.interface.ts index 1634d479b0..8c6e0613ab 100644 --- a/src/components/authorization/policy/conditions/condition.interface.ts +++ b/src/components/authorization/policy/conditions/condition.interface.ts @@ -30,9 +30,7 @@ export type AsEdgeQLParams> = Pick< 'resource' > & { namespace: string }; -export abstract class Condition< - TResourceStatic extends ResourceShape = ResourceShape, -> { +export abstract class Condition = ResourceShape> { static id(permission: Condition | boolean) { if (typeof permission === 'boolean') { return String(permission); @@ -67,17 +65,12 @@ export abstract class Condition< /** * DB query where clause fragment that represents the condition. */ - abstract asCypherCondition( - query: Query, - other: AsCypherParams, - ): string; + abstract asCypherCondition(query: Query, other: AsCypherParams): string; /** * Add with statement aliases. */ - setupEdgeQLContext?( - params: AsEdgeQLParams, - ): Record; + setupEdgeQLContext?(params: AsEdgeQLParams): Record; abstract asEdgeQLCondition(params: AsEdgeQLParams): string; @@ -86,28 +79,19 @@ export abstract class Condition< * This should not logically change anything, but rather just simplify unnecessary conditions. * Note: The current context, this, should not be used. */ - union?( - this: void, - conditions: readonly this[], - ): Many>; + union?(this: void, conditions: readonly this[]): Many>; /** * Intersect multiple conditions of this type together to a single one. * This should not logically change anything, but rather just simplify unnecessary conditions. * Note: The current context, this, should not be used. */ - intersect?( - this: void, - conditions: readonly this[], - ): Many>; + intersect?(this: void, conditions: readonly this[]): Many>; /** * Stringify the condition. * This is used to uniquely identify the condition. * And is what is displayed in dumper/debugger. */ - abstract [inspect.custom]( - depth: number, - options: InspectOptionsStylized, - ): string; + abstract [inspect.custom](depth: number, options: InspectOptionsStylized): string; } diff --git a/src/components/authorization/policy/conditions/eql.util.ts b/src/components/authorization/policy/conditions/eql.util.ts index d392beb4d4..ada24361ba 100644 --- a/src/components/authorization/policy/conditions/eql.util.ts +++ b/src/components/authorization/policy/conditions/eql.util.ts @@ -1,8 +1,4 @@ -export function eqlDoesIntersect( - actual: string, - expected: Iterable, - castName?: string, -) { +export function eqlDoesIntersect(actual: string, expected: Iterable, castName?: string) { const list = [...expected]; if (list.length === 1) { const expectedStr = castName ? `${castName}.${list[0]}` : `'${list[0]}'`; @@ -11,11 +7,7 @@ export function eqlDoesIntersect( return `exists (${eqlLiteralSet(list, castName)} intersect ${actual})`; } -export const eqlInLiteralSet = ( - actual: string, - items: Iterable, - castName?: string, -) => { +export const eqlInLiteralSet = (actual: string, items: Iterable, castName?: string) => { const list = [...items]; if (list.length === 1) { const isEnum = castName && castName !== castName.toLowerCase(); diff --git a/src/components/authorization/policy/conditions/flatten-aggregate.optimizer.ts b/src/components/authorization/policy/conditions/flatten-aggregate.optimizer.ts index eee021e03c..b3a35bfb81 100644 --- a/src/components/authorization/policy/conditions/flatten-aggregate.optimizer.ts +++ b/src/components/authorization/policy/conditions/flatten-aggregate.optimizer.ts @@ -10,19 +10,12 @@ export class FlattenAggregateOptimizer implements Optimizer { input.conditions.some((c) => c instanceof AndConditions) ) { return AndConditions.from( - ...input.conditions.flatMap((c) => - c instanceof AndConditions ? c.conditions : c, - ), + ...input.conditions.flatMap((c) => (c instanceof AndConditions ? c.conditions : c)), ); } - if ( - input instanceof OrConditions && - input.conditions.some((c) => c instanceof OrConditions) - ) { + if (input instanceof OrConditions && input.conditions.some((c) => c instanceof OrConditions)) { return OrConditions.from( - ...input.conditions.flatMap((c) => - c instanceof OrConditions ? c.conditions : c, - ), + ...input.conditions.flatMap((c) => (c instanceof OrConditions ? c.conditions : c)), ); } return input; diff --git a/src/components/authorization/policy/conditions/optimizer.interface.ts b/src/components/authorization/policy/conditions/optimizer.interface.ts index 3fba9a8120..c853f6a826 100644 --- a/src/components/authorization/policy/conditions/optimizer.interface.ts +++ b/src/components/authorization/policy/conditions/optimizer.interface.ts @@ -1,9 +1,4 @@ -import { - applyDecorators, - Injectable, - SetMetadata, - type Type, -} from '@nestjs/common'; +import { applyDecorators, Injectable, SetMetadata, type Type } from '@nestjs/common'; import { type Condition } from './condition.interface'; export abstract class Optimizer { diff --git a/src/components/authorization/policy/executor/all-permissions-view.ts b/src/components/authorization/policy/executor/all-permissions-view.ts index 0a2b27b136..204f6e8b95 100644 --- a/src/components/authorization/policy/executor/all-permissions-view.ts +++ b/src/components/authorization/policy/executor/all-permissions-view.ts @@ -19,20 +19,14 @@ import { import { type EdgePrivileges } from './edge-privileges'; import { type ResourcePrivileges } from './resource-privileges'; -export type AllPermissionsView> = - Record< - SecuredPropsPlusExtraKey, - Record - > & - Record< - ChildSinglesKey, - Record - > & - Record, Record>; +export type AllPermissionsView> = Record< + SecuredPropsPlusExtraKey, + Record +> & + Record, Record> & + Record, Record>; -export const createAllPermissionsView = < - TResourceStatic extends ResourceShape, ->( +export const createAllPermissionsView = >( resource: EnhancedResource, privileges: ResourcePrivileges, ) => @@ -43,8 +37,7 @@ export const createAllPermissionsView = < getKeys: () => CompatAction.values, calculate: (actionInput, propPerms) => { const action = - actionInput === 'canEdit' && - resource.childListKeys.has(propName as any) + actionInput === 'canEdit' && resource.childListKeys.has(propName as any) ? 'create' // Handled deprecated checks to list.canEdit === list.create : compatMap.forward[actionInput]; // @ts-expect-error dynamic usage here is struggling @@ -56,10 +49,7 @@ export const createAllPermissionsView = < }) as any, }); -export type AllPermissionsOfEdgeView = Record< - TAction, - boolean ->; +export type AllPermissionsOfEdgeView = Record; export const createAllPermissionsOfEdgeView = < TResourceStatic extends ResourceShape, @@ -78,19 +68,13 @@ const asLegacyAction = (action: AnyAction) => `can${startCase(action)}` as `can${PascalCase}`; type CompatAction = EnumType; -const CompatAction = makeEnum([ - ...AnyAction, - ...[...AnyAction].map(asLegacyAction), -]); +const CompatAction = makeEnum([...AnyAction, ...[...AnyAction].map(asLegacyAction)]); const compatMap = { forward: { ...mapValues.fromList( CompatAction, - (action) => - (action.startsWith('can') - ? action.slice(3).toLowerCase() - : action) as AnyAction, + (action) => (action.startsWith('can') ? action.slice(3).toLowerCase() : action) as AnyAction, ).asRecord, }, backward: { diff --git a/src/components/authorization/policy/executor/condition-optimizer.ts b/src/components/authorization/policy/executor/condition-optimizer.ts index 42b1055597..d7cd458b30 100644 --- a/src/components/authorization/policy/executor/condition-optimizer.ts +++ b/src/components/authorization/policy/executor/condition-optimizer.ts @@ -51,9 +51,6 @@ export class ConditionOptimizer implements OnModuleInit { } } - return this.optimizers.reduce( - (current, optimizer) => optimizer.optimize(current), - condition, - ); + return this.optimizers.reduce((current, optimizer) => optimizer.optimize(current), condition); } } diff --git a/src/components/authorization/policy/executor/edge-privileges.ts b/src/components/authorization/policy/executor/edge-privileges.ts index bf1f66d7b0..db4f7fe0e7 100644 --- a/src/components/authorization/policy/executor/edge-privileges.ts +++ b/src/components/authorization/policy/executor/edge-privileges.ts @@ -1,19 +1,11 @@ import { LazyGetter as Once } from 'lazy-get-decorator'; -import { - EnhancedResource, - type ResourceShape, - UnauthorizedException, -} from '~/common'; +import { EnhancedResource, type ResourceShape, UnauthorizedException } from '~/common'; import { type ResourceObjectContext } from '../object.type'; import { type AllPermissionsOfEdgeView, createAllPermissionsOfEdgeView, } from './all-permissions-view'; -import { - type FilterOptions, - type PolicyExecutor, - type ResolveParams, -} from './policy-executor'; +import { type FilterOptions, type PolicyExecutor, type ResolveParams } from './policy-executor'; export class EdgePrivileges< TResourceStatic extends ResourceShape, @@ -38,12 +30,7 @@ export class EdgePrivileges< if (object === this.object) { return this; } - return new EdgePrivileges( - this.resource, - this.key, - object, - this.policyExecutor, - ); + return new EdgePrivileges(this.resource, this.key, object, this.policyExecutor); } can(action: TAction) { @@ -65,12 +52,7 @@ export class EdgePrivileges< if (this.can(action)) { return; } - throw UnauthorizedException.fromPrivileges( - action, - this.object, - this.resource, - this.key, - ); + throw UnauthorizedException.fromPrivileges(action, this.object, this.resource, this.key); } /** diff --git a/src/components/authorization/policy/executor/policy-dumper.ts b/src/components/authorization/policy/executor/policy-dumper.ts index 5a7896cc4d..25054ded21 100644 --- a/src/components/authorization/policy/executor/policy-dumper.ts +++ b/src/components/authorization/policy/executor/policy-dumper.ts @@ -24,12 +24,7 @@ import { searchCamelCase } from '~/common/search-camel-case'; import { Identity } from '~/core/authentication'; import { InjectableCommand } from '~/core/cli'; import { type ResourceLike, ResourcesHost } from '~/core/resources'; -import { - ChildListAction, - ChildSingleAction, - PropAction, - ResourceAction, -} from '../actions'; +import { ChildListAction, ChildSingleAction, PropAction, ResourceAction } from '../actions'; import { type Permission } from '../builder/perm-granter'; import { CalculatedCondition } from '../conditions'; import { PolicyExecutor } from './policy-executor'; @@ -50,9 +45,7 @@ export class PolicyDumper { const chalk = new Chalk({ level: 0 }); const resources = this.selectResources(); for (const role of Role) { - const dumped = resources.flatMap((res) => - this.dumpRes(role, res, { props: true }), - ); + const dumped = resources.flatMap((res) => this.dumpRes(role, res, { props: true })); const data = dumped.map((row) => ({ Resource: startCase(row.resource.name), 'Property/Relationship': startCase(row.edge), @@ -69,18 +62,13 @@ export class PolicyDumper { await fs.writeFile(filename ?? 'permissions.xlsx', buffer); } - async dump( - rolesIn: Many>, - resourcesIn: Many, - ) { + async dump(rolesIn: Many>, resourcesIn: Many) { const roles = search(rolesIn, [...Role], 'role'); const map = this.resources.getEnhancedMap(); const resources = searchResources(resourcesIn, map); const data = roles.flatMap((role) => - resources.flatMap((r) => - this.dumpRes(role, r.resource, { props: r.props }), - ), + resources.flatMap((r) => this.dumpRes(role, r.resource, { props: r.props })), ); const table = new Table({ @@ -89,8 +77,7 @@ export class PolicyDumper { const chalk = new Chalk(); const showRoleCol = roles.length > 1; - const showResCol = - !showRoleCol || resources.length > 1 || resources.some((r) => r.props); + const showResCol = !showRoleCol || resources.length > 1 || resources.some((r) => r.props); // Table title table.push([ @@ -99,8 +86,7 @@ export class PolicyDumper { cleanJoin(' ', [ 'Permissions', roles.length === 1 && `for ${chalk.italic(startCase(roles[0]))}`, - resources.length === 1 && - `for ${chalk.italic(startCase(resources[0].resource.name))}`, + resources.length === 1 && `for ${chalk.italic(startCase(resources[0].resource.name))}`, ]), ), colSpan: 4 + (showRoleCol ? 1 : 0) + (showResCol ? 1 : 0), @@ -111,12 +97,8 @@ export class PolicyDumper { // Table header row table.push([ ...(showRoleCol ? [chalk.magentaBright('Role')] : []), - ...(showResCol - ? [resources.length === 1 ? undefined : chalk.magentaBright('Resource')] - : []), - ...['Read', 'Edit', 'Create', 'Delete'].map((k) => - chalk.magentaBright(k), - ), + ...(showResCol ? [resources.length === 1 ? undefined : chalk.magentaBright('Resource')] : []), + ...['Read', 'Edit', 'Create', 'Delete'].map((k) => chalk.magentaBright(k)), ]); // Table data rows @@ -124,11 +106,7 @@ export class PolicyDumper { ...data.map((row) => [ ...(showRoleCol ? [chalk.cyan(startCase(row.role))] : []), ...(showResCol - ? [ - chalk.cyan( - row.edge ? ' .' + row.edge : startCase(row.resource.name), - ), - ] + ? [chalk.cyan(row.edge ? ' .' + row.edge : startCase(row.resource.name))] : []), ...[row.read, row.edit, row.create, row.delete].map((perm) => this.humanizePerm(perm, chalk), @@ -182,8 +160,7 @@ export class PolicyDumper { role, resource, edge: undefined, - ...mapValues.fromList(ResourceAction, (action) => resolve(action)) - .asRecord, + ...mapValues.fromList(ResourceAction, (action) => resolve(action)).asRecord, }, ...(options.props !== false ? ([ @@ -194,15 +171,12 @@ export class PolicyDumper { : [] ).flatMap(([set, actions]) => [...set] - .filter( - (p) => typeof options.props === 'boolean' || options.props.has(p), - ) + .filter((p) => typeof options.props === 'boolean' || options.props.has(p)) .map((prop) => ({ role, resource, edge: prop, - ...mapValues.fromList(actions, (action) => resolve(action, prop)) - .asRecord, + ...mapValues.fromList(actions, (action) => resolve(action, prop)).asRecord, })), ), ]; @@ -234,11 +208,7 @@ interface DumpedRow { delete?: Permission; } -const search = ( - input: Many, - bank: T[], - thing: string, -) => { +const search = (input: Many, bank: T[], thing: string) => { const values = many(input); if (values.some(isWildcard)) { return bank; @@ -246,18 +216,12 @@ const search = ( const results = values .flatMap(csv) .map((r) => - firstOr( - searchCamelCase(bank, r), - () => new Error(`Could not find ${thing} from "${r}"`), - ), + firstOr(searchCamelCase(bank, r), () => new Error(`Could not find ${thing} from "${r}"`)), ); return [...new Set(results)].sort((a, b) => a.localeCompare(b)); }; -const searchResources = ( - input: Many, - map: Record, -) => { +const searchResources = (input: Many, map: Record) => { const resNames = Object.keys(map); const selections = many(input) // Split by comma, but not inside curly braces @@ -298,9 +262,7 @@ const searchResources = ( return { resource, props: availableProps }; } - const found = csv(propsIn).flatMap((p) => - searchCamelCase(availableProps, p), - ); + const found = csv(propsIn).flatMap((p) => searchCamelCase(availableProps, p)); return { resource, props: setOf(found) }; }); @@ -309,9 +271,7 @@ const searchResources = ( .map((rows) => { const resource = rows[0]!.resource; const propList = [ - ...new Set( - ...rows.flatMap((r) => (typeof r.props === 'boolean' ? [] : r.props)), - ), + ...new Set(...rows.flatMap((r) => (typeof r.props === 'boolean' ? [] : r.props))), ].sort((a, b) => a.localeCompare(b)); const props = propList.length === 0 ? false : setOf(propList); return { resource, props }; diff --git a/src/components/authorization/policy/executor/policy-executor.ts b/src/components/authorization/policy/executor/policy-executor.ts index 9d5fef6b06..990a8a380e 100644 --- a/src/components/authorization/policy/executor/policy-executor.ts +++ b/src/components/authorization/policy/executor/policy-executor.ts @@ -111,10 +111,7 @@ export class PolicyExecutor { return condition; } - forGel({ - action, - resource, - }: Pick): Permission { + forGel({ action, resource }: Pick): Permission { const isDerivedInDB = [...resource.interfaces].some((e) => e.hasDB); if (action !== 'read' && resource.isCalculated) { @@ -147,17 +144,13 @@ export class PolicyExecutor { } const roleCondition = - policy.roles && policy.roles.size > 0 - ? new RoleCondition(policy.roles) - : undefined; + policy.roles && policy.roles.size > 0 ? new RoleCondition(policy.roles) : undefined; if (!roleCondition && condition === true) { // globally allowed return true; } - conditions.push( - all(roleCondition, condition !== true ? condition : null), - ); + conditions.push(all(roleCondition, condition !== true ? condition : null)); } if (conditions.length === 0) { return false; @@ -190,11 +183,7 @@ export class PolicyExecutor { }; return query .comment("Loading policy condition's context") - .apply( - wrapContext( - (q1) => perm.setupCypherContext?.(q1, new Set(), other) ?? q1, - ), - ) + .apply(wrapContext((q1) => perm.setupCypherContext?.(q1, new Set(), other) ?? q1)) .comment('Filtering by policy conditions') .with('*') .raw(`WHERE ${perm.asCypherCondition(query, other)}`); @@ -240,9 +229,7 @@ export class PolicyExecutor { if (children.some((perm) => perm === false)) { return false; } - const remainingConditions = children.filter( - (perm): perm is Condition => perm !== true, - ); + const remainingConditions = children.filter((perm): perm is Condition => perm !== true); // true && true && true = true if (remainingConditions.length === 0) { // no children were false, no children were conditions @@ -257,9 +244,7 @@ export class PolicyExecutor { if (children.some((perm) => perm === true)) { return true; } - const remainingConditions = children.filter( - (perm): perm is Condition => perm !== false, - ); + const remainingConditions = children.filter((perm): perm is Condition => perm !== false); // false || false || false = false if (remainingConditions.length === 0) { // no children were true, no children were conditions @@ -279,9 +264,7 @@ export class PolicyExecutor { if (!policy.roles) { return true; // policy doesn't limit roles } - const rolesSpecifiedByPolicyThatUserHas = policy.roles.intersection( - session.roles, - ); + const rolesSpecifiedByPolicyThatUserHas = policy.roles.intersection(session.roles); return rolesSpecifiedByPolicyThatUserHas.size > 0; }); return policies; diff --git a/src/components/authorization/policy/executor/privileges.ts b/src/components/authorization/policy/executor/privileges.ts index 7128d823db..5d7409c144 100644 --- a/src/components/authorization/policy/executor/privileges.ts +++ b/src/components/authorization/policy/executor/privileges.ts @@ -9,11 +9,7 @@ import { import { Identity } from '~/core/authentication'; import type { Power } from '../../dto'; import { MissingPowerException } from '../../missing-power.exception'; -import { - type ChildListAction, - type ChildSingleAction, - type PropAction, -} from '../actions'; +import { type ChildListAction, type ChildSingleAction, type PropAction } from '../actions'; import { type ResourceObjectContext } from '../object.type'; import { EdgePrivileges } from './edge-privileges'; import { PolicyExecutor } from './policy-executor'; @@ -39,37 +35,20 @@ export class Privileges { forEdge>( resource: TResourceStatic | EnhancedResource, key: SecuredPropsPlusExtraKey, - ): EdgePrivileges< - TResourceStatic, - SecuredPropsPlusExtraKey, - PropAction - >; + ): EdgePrivileges, PropAction>; forEdge>( resource: TResourceStatic | EnhancedResource, key: ChildSinglesKey, - ): EdgePrivileges< - TResourceStatic, - ChildSinglesKey, - ChildSingleAction - >; + ): EdgePrivileges, ChildSingleAction>; forEdge>( resource: TResourceStatic | EnhancedResource, key: ChildListsKey, - ): EdgePrivileges< - TResourceStatic, - ChildListsKey, - ChildListAction - >; + ): EdgePrivileges, ChildListAction>; forEdge>( resource: TResourceStatic | EnhancedResource, key: string, ) { - return new EdgePrivileges( - EnhancedResource.of(resource), - key, - undefined, - this.policyExecutor, - ); + return new EdgePrivileges(EnhancedResource.of(resource), key, undefined, this.policyExecutor); } /** @@ -79,11 +58,7 @@ export class Privileges { resource: TResourceStatic | EnhancedResource, object?: NoInfer>, ) { - return new ResourcePrivileges( - resource, - object, - this.policyExecutor, - ); + return new ResourcePrivileges(resource, object, this.policyExecutor); } /** diff --git a/src/components/authorization/policy/executor/resource-privileges.ts b/src/components/authorization/policy/executor/resource-privileges.ts index 7c0fbdde0d..2e06b7bf98 100644 --- a/src/components/authorization/policy/executor/resource-privileges.ts +++ b/src/components/authorization/policy/executor/resource-privileges.ts @@ -21,16 +21,9 @@ import { type ResourceAction, } from '../actions'; import { type ResourceObjectContext } from '../object.type'; -import { - type AllPermissionsView, - createAllPermissionsView, -} from './all-permissions-view'; +import { type AllPermissionsView, createAllPermissionsView } from './all-permissions-view'; import { EdgePrivileges } from './edge-privileges'; -import { - type FilterOptions, - type PolicyExecutor, - type ResolveParams, -} from './policy-executor'; +import { type FilterOptions, type PolicyExecutor, type ResolveParams } from './policy-executor'; export class ResourcePrivileges> { readonly resource: EnhancedResource; @@ -56,49 +49,23 @@ export class ResourcePrivileges> { forEdge( key: SecuredPropsPlusExtraKey, object?: ResourceObjectContext, - ): EdgePrivileges< - TResourceStatic, - SecuredPropsPlusExtraKey, - PropAction - >; + ): EdgePrivileges, PropAction>; forEdge( key: ChildSinglesKey, object?: ResourceObjectContext, - ): EdgePrivileges< - TResourceStatic, - ChildSinglesKey, - ChildSingleAction - >; + ): EdgePrivileges, ChildSingleAction>; forEdge( key: ChildListsKey, object?: ResourceObjectContext, - ): EdgePrivileges< - TResourceStatic, - ChildListsKey, - ChildListAction - >; + ): EdgePrivileges, ChildListAction>; forEdge(key: string, object?: any) { - return new EdgePrivileges( - this.resource, - key, - object ?? this.object, - this.policyExecutor, - ); + return new EdgePrivileges(this.resource, key, object ?? this.object, this.policyExecutor); } can(action: ResourceAction): boolean; - can( - action: PropAction, - prop: SecuredPropsPlusExtraKey, - ): boolean; - can( - action: ChildSingleAction, - relation: ChildSinglesKey, - ): boolean; - can( - action: ChildListAction, - relation: ChildListsKey, - ): boolean; + can(action: PropAction, prop: SecuredPropsPlusExtraKey): boolean; + can(action: ChildSingleAction, relation: ChildSinglesKey): boolean; + can(action: ChildListAction, relation: ChildListsKey): boolean; can(action: AnyAction, prop?: SecuredResourceKey) { const perm = this.resolve({ action, prop }); return perm === true || perm === false @@ -118,29 +85,15 @@ export class ResourcePrivileges> { } verifyCan(action: ResourceAction): void; - verifyCan( - action: PropAction, - prop: SecuredPropsPlusExtraKey, - ): void; - verifyCan( - action: ChildSingleAction, - relation: ChildSinglesKey, - ): void; - verifyCan( - action: ChildListAction, - relation: ChildListsKey, - ): void; + verifyCan(action: PropAction, prop: SecuredPropsPlusExtraKey): void; + verifyCan(action: ChildSingleAction, relation: ChildSinglesKey): void; + verifyCan(action: ChildListAction, relation: ChildListsKey): void; verifyCan(action: AnyAction, prop?: string) { // @ts-expect-error yeah IDK why but this is literally the signature. if (this.can(action, prop)) { return; } - throw UnauthorizedException.fromPrivileges( - action, - this.object, - this.resource, - prop, - ); + throw UnauthorizedException.fromPrivileges(action, this.object, this.resource, prop); } /** @@ -170,16 +123,11 @@ export class ResourcePrivileges> { ) { if (pathPrefix === undefined) { // Guess the input field path based on name convention - pathPrefix = startCase(this.resource.name) - .split(' ') - .at(-1)! - .toLowerCase(); + pathPrefix = startCase(this.resource.name).split(' ').at(-1)!.toLowerCase(); } for (const prop of Object.keys(changes)) { - const dtoPropName: any = isRelation(this.resource, prop) - ? prop.slice(0, -2) - : prop; + const dtoPropName: any = isRelation(this.resource, prop) ? prop.slice(0, -2) : prop; if (!this.resource.securedProps.has(dtoPropName)) { continue; } @@ -198,23 +146,18 @@ export class ResourcePrivileges> { * Takes the given unsecured dto which has unsecured props and returns the props that * are supposed to be secured (unsecured props are omitted) as secured. */ - secure( - dto: UnsecuredDto, - ): TResourceStatic['prototype'] { + secure(dto: UnsecuredDto): TResourceStatic['prototype'] { // Be helpful and allow object param to be skipped upstream. // But it still can be used if given possible for use with condition wrapper functions. const perms = this.object ? this : this.forContext(dto); - const securedProps = mapValues.fromList( - this.resource.securedProps, - (key) => { - const canRead = perms.can('read', key); - const canEdit = perms.can('edit', key); - let value = (dto as any)[key]; - value = canRead ? value : Array.isArray(value) ? [] : undefined; - return { value, canRead, canEdit }; - }, - ).asRecord as SecuredResource; + const securedProps = mapValues.fromList(this.resource.securedProps, (key) => { + const canRead = perms.can('read', key); + const canEdit = perms.can('edit', key); + let value = (dto as any)[key]; + value = canRead ? value : Array.isArray(value) ? [] : undefined; + return { value, canRead, canEdit }; + }).asRecord as SecuredResource; return { ...dto, diff --git a/src/components/authorization/policy/gel-access-policy.generator.ts b/src/components/authorization/policy/gel-access-policy.generator.ts index 8b80d9d6eb..51c200da20 100644 --- a/src/components/authorization/policy/gel-access-policy.generator.ts +++ b/src/components/authorization/policy/gel-access-policy.generator.ts @@ -35,9 +35,7 @@ export class GelAccessPolicyGenerator { this.executor.forGel({ resource, action }), ]); - const policies = groupBy(actionPerms, ([_, perm]) => - Condition.id(perm), - ).map((group) => { + const policies = groupBy(actionPerms, ([_, perm]) => Condition.id(perm)).map((group) => { const actions = group.map(([action]) => action); const perm = group[0][1]; return this.makeSdlForAction(params, actions, perm); @@ -46,11 +44,7 @@ export class GelAccessPolicyGenerator { return cleanJoin('\n\n', policies); } - makeSdlForAction( - params: AsEdgeQLParams, - stmtTypes: string[], - perm: Permission, - ) { + makeSdlForAction(params: AsEdgeQLParams, stmtTypes: string[], perm: Permission) { if (perm === false) { // App policies haven't declared any perms for this specific type. return null; @@ -60,14 +54,12 @@ export class GelAccessPolicyGenerator { .map((action) => startCase(action).replaceAll(/\s+/g, '')) .join('')}GeneratedFromAppPoliciesFor${params.resource.name}`; - const withAliases = - typeof perm === 'boolean' ? {} : perm.setupEdgeQLContext?.(params) ?? {}; + const withAliases = typeof perm === 'boolean' ? {} : perm.setupEdgeQLContext?.(params) ?? {}; const withAliasesEql = Object.entries(withAliases) .map(([key, value]) => `${key} := ${value}`) .join(',\n'); - const conditionEql = - typeof perm === 'boolean' ? String(perm) : perm.asEdgeQLCondition(params); + const conditionEql = typeof perm === 'boolean' ? String(perm) : perm.asEdgeQLCondition(params); const usingBodyEql = withAliasesEql ? stripIndent` @@ -80,9 +72,7 @@ ${addIndent(conditionEql, 6, { indent: ' ' })} : conditionEql; const usingEql = - perm === true - ? '' - : ` using (\n${addIndent(usingBodyEql, 1, { indent: ' ' })}\n)`; + perm === true ? '' : ` using (\n${addIndent(usingBodyEql, 1, { indent: ' ' })}\n)`; const actions = stmtTypes.join(', '); const sdl = `access policy ${name}\nallow ${actions}${usingEql};`; return sdl; diff --git a/src/components/authorization/policy/granters.factory.ts b/src/components/authorization/policy/granters.factory.ts index 5cc1a638ae..5892fff5b4 100644 --- a/src/components/authorization/policy/granters.factory.ts +++ b/src/components/authorization/policy/granters.factory.ts @@ -5,10 +5,7 @@ import { mapValues } from 'lodash'; import { EnhancedResource } from '~/common'; import { ResourcesHost } from '~/core/resources'; import { discover } from './builder/granter.decorator'; -import { - DefaultResourceGranter, - ResourceGranter, -} from './builder/resource-granter'; +import { DefaultResourceGranter, ResourceGranter } from './builder/resource-granter'; import { type ResourcesGranter } from './granters'; @Injectable() @@ -27,12 +24,9 @@ export class GrantersFactory { ({ meta: { resources, factory }, discoveredClass }) => mapEntries(many(resources), (raw) => { const res = EnhancedResource.of(raw); - const granter = - factory?.(res) ?? new discoveredClass.dependencyType(res); + const granter = factory?.(res) ?? new discoveredClass.dependencyType(res); if (!(granter instanceof ResourceGranter)) { - throw new Error( - `Granter for ${res.name} must extend ResourceGranter class`, - ); + throw new Error(`Granter for ${res.name} must extend ResourceGranter class`); } return [res.name, granter]; }).asRecord, diff --git a/src/components/authorization/policy/index.ts b/src/components/authorization/policy/index.ts index 786c3d6e3f..e5fab9eb2c 100644 --- a/src/components/authorization/policy/index.ts +++ b/src/components/authorization/policy/index.ts @@ -1,17 +1,11 @@ export { Policy } from './builder/policy.decorator'; export type { ResourcesGranter } from './granters'; export { Granter } from './builder/granter.decorator'; -export { - ResourceGranter, - DefaultResourceGranter, -} from './builder/resource-granter'; +export { ResourceGranter, DefaultResourceGranter } from './builder/resource-granter'; export * from './builder/allow-all.helper'; export * from './builder/as-normalized.helper'; export * from './builder/inherit-resource.helper'; export * from './executor/privileges'; export * from './executor/resource-privileges'; export * from './executor/edge-privileges'; -export type { - AllPermissionsView, - AllPermissionsOfEdgeView, -} from './executor/all-permissions-view'; +export type { AllPermissionsView, AllPermissionsOfEdgeView } from './executor/all-permissions-view'; diff --git a/src/components/authorization/policy/policy.factory.ts b/src/components/authorization/policy/policy.factory.ts index 5d54f24db8..b642413efb 100644 --- a/src/components/authorization/policy/policy.factory.ts +++ b/src/components/authorization/policy/policy.factory.ts @@ -1,7 +1,4 @@ -import { - type DiscoveredClassWithMeta, - DiscoveryService, -} from '@golevelup/nestjs-discovery'; +import { type DiscoveredClassWithMeta, DiscoveryService } from '@golevelup/nestjs-discovery'; import { Injectable, type OnModuleInit } from '@nestjs/common'; import { entries, mapEntries, mapValues, setOf } from '@seedcompany/common'; import { pick, startCase } from 'lodash'; @@ -10,15 +7,8 @@ import { type EnhancedResource, many, type Role } from '~/common'; import { ResourcesHost } from '~/core/resources'; import { Power } from '../dto'; import { ChildListAction, ChildSingleAction } from './actions'; -import { - extract, - type Permission, - type Permissions, -} from './builder/perm-granter'; -import { - POLICY_METADATA_KEY, - type PolicyMetadata, -} from './builder/policy.decorator'; +import { extract, type Permission, type Permissions } from './builder/perm-granter'; +import { POLICY_METADATA_KEY, type PolicyMetadata } from './builder/policy.decorator'; import { all, any, Condition } from './conditions'; import { type ResourcesGranter } from './granters'; import { GrantersFactory } from './granters.factory'; @@ -72,10 +62,9 @@ export class PolicyFactory implements OnModuleInit { } async onModuleInit() { - const discoveredPolicies = - await this.discovery.providersWithMetaAtKey( - POLICY_METADATA_KEY, - ); + const discoveredPolicies = await this.discovery.providersWithMetaAtKey( + POLICY_METADATA_KEY, + ); const resGranter = await this.grantersFactory.makeGranters(); @@ -108,8 +97,7 @@ export class PolicyFactory implements OnModuleInit { const grants: WritableGrants = new Map(); const resultList = many(meta.def(resGranter)).flat(); for (const resourceGrant of resultList) { - const { resource, perms, props, childRelationships } = - resourceGrant[extract](); + const { resource, perms, props, childRelationships } = resourceGrant[extract](); if (!grants.has(resource)) { grants.set(resource, { objectLevel: {}, @@ -122,17 +110,13 @@ export class PolicyFactory implements OnModuleInit { for (const prop of props) { for (const propName of prop.properties) { const propPerms = (propLevel[propName] ??= {}); - prop.perms.forEach((perms) => - this.mergePermissions(propPerms, perms), - ); + prop.perms.forEach((perms) => this.mergePermissions(propPerms, perms)); } } for (const childRelation of childRelationships) { for (const relationName of childRelation.relationNames) { const childPerms = (childRelations[relationName] ??= {}); - childRelation.perms.forEach((perms) => - this.mergePermissions(childPerms, perms), - ); + childRelation.perms.forEach((perms) => this.mergePermissions(childPerms, perms)); } } } @@ -155,13 +139,9 @@ export class PolicyFactory implements OnModuleInit { * Declare permissions of missing interfaces based on the intersection * its implementations */ - private defaultInterfacesFromAllImplementationsIntersection( - grantMap: WritableGrants, - ) { + private defaultInterfacesFromAllImplementationsIntersection(grantMap: WritableGrants) { const interfaceCandidates = new Set( - [...grantMap.keys()] - .map((res) => this.resourcesHost.getInterfaces(res)) - .flat(), + [...grantMap.keys()].map((res) => this.resourcesHost.getInterfaces(res)).flat(), ); const allKeysOf = (list: Array) => @@ -197,16 +177,10 @@ export class PolicyFactory implements OnModuleInit { const interfaceGrants: ResourceGrants = { objectLevel: intersectPermissions(objectLevelPermissions), propLevel: mapValues.fromList(allKeysOf(propLevelPermissions), (prop) => - intersectPermissions( - propLevelPermissions.map((propLevel) => propLevel[prop]), - ), + intersectPermissions(propLevelPermissions.map((propLevel) => propLevel[prop])), ).asRecord, - childRelations: mapValues.fromList( - allKeysOf(childRelationPermissions), - (prop) => - intersectPermissions( - childRelationPermissions.map((propLevel) => propLevel[prop]), - ), + childRelations: mapValues.fromList(allKeysOf(childRelationPermissions), (prop) => + intersectPermissions(childRelationPermissions.map((propLevel) => propLevel[prop])), ).asRecord, }; @@ -216,9 +190,7 @@ export class PolicyFactory implements OnModuleInit { private stripImplementationsMatchingInterfaces(grantMap: WritableGrants) { const interfaceCandidates = new Set( - [...grantMap.keys()] - .map((res) => this.resourcesHost.getInterfaces(res)) - .flat(), + [...grantMap.keys()].map((res) => this.resourcesHost.getInterfaces(res)).flat(), ); for (const interfaceRes of interfaceCandidates) { @@ -236,19 +208,13 @@ export class PolicyFactory implements OnModuleInit { continue; } // Only bother checking object level read/create/delete as that is all our DB AP's use - const isSame = entries(implGrants.objectLevel).every( - ([action, perm]) => { - if (action === 'edit') { - return true; - } - const ifacePerm = interfaceGrants?.objectLevel[action]; - return ( - ifacePerm && - perm && - Condition.id(ifacePerm) === Condition.id(perm) - ); - }, - ); + const isSame = entries(implGrants.objectLevel).every(([action, perm]) => { + if (action === 'edit') { + return true; + } + const ifacePerm = interfaceGrants?.objectLevel[action]; + return ifacePerm && perm && Condition.id(ifacePerm) === Condition.id(perm); + }); if (isSame) { grantMap.delete(impl); } @@ -290,9 +256,7 @@ export class PolicyFactory implements OnModuleInit { } // If policy doesn't specify this implementation then use most specific // interface given. - const interfaceToApply = this.resourcesHost - .getInterfaces(impl) - .find((i) => grants.has(i)); + const interfaceToApply = this.resourcesHost.getInterfaces(impl).find((i) => grants.has(i)); const interfacePerms = interfaceToApply && grants.get(interfaceToApply); if (!interfacePerms) { // Safety check, but this shouldn't ever happen, since we only got @@ -331,9 +295,7 @@ export class PolicyFactory implements OnModuleInit { existing: Writable>, toMerge: Permissions, ) { - for (const [action, perm] of Object.entries(toMerge) as Array< - [TAction, Permission] - >) { + for (const [action, perm] of Object.entries(toMerge) as Array<[TAction, Permission]>) { existing[action] = this.mergePermission([perm, existing[action]], any); } return existing; @@ -396,12 +358,9 @@ const cloneGrants = (grants: Grants): WritableGrants => new Map([ ...mapValues(grants, (_, grant) => ({ objectLevel: clonePermissions(grant.objectLevel), - propLevel: mapValues(grant.propLevel, (_, perms) => - clonePermissions(perms), - ).asRecord, - childRelations: mapValues(grant.childRelations, (_, perms) => - clonePermissions(perms), - ).asRecord, + propLevel: mapValues(grant.propLevel, (_, perms) => clonePermissions(perms)).asRecord, + childRelations: mapValues(grant.childRelations, (_, perms) => clonePermissions(perms)) + .asRecord, })), ]); diff --git a/src/components/budget/budget-record.repository.ts b/src/components/budget/budget-record.repository.ts index 8681b6084d..46a077f943 100644 --- a/src/components/budget/budget-record.repository.ts +++ b/src/components/budget/budget-record.repository.ts @@ -116,10 +116,7 @@ export class BudgetRecordRepository extends DtoRepository< .return<{ dto: UnsecuredDto }>('dto'); const result = await query.first(); if (!result) { - throw new NotFoundException( - 'Could not find BudgetRecord', - 'budgetRecord.budgetId', - ); + throw new NotFoundException('Could not find BudgetRecord', 'budgetRecord.budgetId'); } return result.dto; @@ -129,11 +126,7 @@ export class BudgetRecordRepository extends DtoRepository< const { budgetId } = input.filter ?? {}; const result = await this.db .query() - .matchNode( - 'budget', - labelForView('Budget', view), - pickBy({ id: budgetId }), - ) + .matchNode('budget', labelForView('Budget', view), pickBy({ id: budgetId })) .apply(this.recordsOfBudget({ view })) .apply(sorting(BudgetRecord, input)) .apply(paginate(input)) diff --git a/src/components/budget/budget-record.resolver.ts b/src/components/budget/budget-record.resolver.ts index ab222ce42d..5df3d9f0a4 100644 --- a/src/components/budget/budget-record.resolver.ts +++ b/src/components/budget/budget-record.resolver.ts @@ -1,20 +1,10 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { mapSecuredValue } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { OrganizationLoader } from '../organization'; import { SecuredOrganization } from '../organization/dto'; import { BudgetService } from './budget.service'; -import { - BudgetRecord, - UpdateBudgetRecordInput, - UpdateBudgetRecordOutput, -} from './dto'; +import { BudgetRecord, UpdateBudgetRecordInput, UpdateBudgetRecordOutput } from './dto'; @Resolver(BudgetRecord) export class BudgetRecordResolver { @@ -25,9 +15,7 @@ export class BudgetRecordResolver { @Parent() record: BudgetRecord, @Loader(OrganizationLoader) organizations: LoaderOf, ): Promise { - return await mapSecuredValue(record.organization, (id) => - organizations.load(id), - ); + return await mapSecuredValue(record.organization, (id) => organizations.load(id)); } @Mutation(() => UpdateBudgetRecordOutput, { diff --git a/src/components/budget/budget.repository.ts b/src/components/budget/budget.repository.ts index 09b4b0afb4..2115bf9b61 100644 --- a/src/components/budget/budget.repository.ts +++ b/src/components/budget/budget.repository.ts @@ -35,10 +35,7 @@ import { } from './dto'; @Injectable() -export class BudgetRepository extends DtoRepository< - typeof Budget, - [view?: ObjectView] ->(Budget) { +export class BudgetRepository extends DtoRepository(Budget) { constructor(private readonly records: BudgetRecordRepository) { super(); } @@ -70,10 +67,7 @@ export class BudgetRepository extends DtoRepository< async update( existing: Budget, - simpleChanges: Omit< - ChangesOf, - 'universalTemplateFile' - >, + simpleChanges: Omit, 'universalTemplateFile'>, ) { return await this.updateProperties(existing, simpleChanges); } diff --git a/src/components/budget/budget.resolver.ts b/src/components/budget/budget.resolver.ts index aada3c14b0..5a20d13dee 100644 --- a/src/components/budget/budget.resolver.ts +++ b/src/components/budget/budget.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Float, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Float, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { sumBy } from 'lodash'; import { Loader, type LoaderOf } from '~/core'; import { BudgetService } from '../budget'; diff --git a/src/components/budget/budget.service.ts b/src/components/budget/budget.service.ts index 70b273e645..e37fd1aea7 100644 --- a/src/components/budget/budget.service.ts +++ b/src/components/budget/budget.service.ts @@ -66,16 +66,11 @@ export class BudgetService { } } - async createRecord( - input: CreateBudgetRecord, - changeset?: ID, - ): Promise { + async createRecord(input: CreateBudgetRecord, changeset?: ID): Promise { const { organizationId, fiscalYear } = input; if (!fiscalYear || !organizationId) { - throw new InputException( - !fiscalYear ? 'budget.fiscalYear' : 'budget.organizationId', - ); + throw new InputException(!fiscalYear ? 'budget.fiscalYear' : 'budget.organizationId'); } await this.verifyRecordUniqueness(input); @@ -83,10 +78,7 @@ export class BudgetService { try { const recordId = await this.budgetRecordsRepo.create(input, changeset); - const budgetRecord = await this.readOneRecord( - recordId, - viewOfChangeset(changeset), - ); + const budgetRecord = await this.readOneRecord(recordId, viewOfChangeset(changeset)); return budgetRecord; } catch (exception) { @@ -139,9 +131,7 @@ export class BudgetService { async readMany(ids: readonly ID[], view?: ObjectView) { const budgets = await this.budgetRepo.readMany(ids, view); - return await Promise.all( - budgets.map(async (dto) => await this.readOne(dto.id, view)), - ); + return await Promise.all(budgets.map(async (dto) => await this.readOne(dto.id, view))); } @HandleIdLookup(BudgetRecord) @@ -165,10 +155,7 @@ export class BudgetService { return await this.budgetRepo.update(budget, simpleChanges); } - async updateRecord( - { id, ...input }: UpdateBudgetRecord, - changeset?: ID, - ): Promise { + async updateRecord({ id, ...input }: UpdateBudgetRecord, changeset?: ID): Promise { const br = await this.readOneRecord(id, viewOfChangeset(changeset)); const changes = this.budgetRecordsRepo.getActualChanges(br, input); this.privileges.for(BudgetRecord, br).verifyChanges(changes); @@ -198,15 +185,10 @@ export class BudgetService { } } - async list( - partialInput: Partial, - changeset?: ID, - ): Promise { + async list(partialInput: Partial, changeset?: ID): Promise { const input = BudgetListInput.defaultValue(BudgetListInput, partialInput); const results = await this.budgetRepo.list(input); - return await mapListResults(results, (id) => - this.readOne(id, viewOfChangeset(changeset)), - ); + return await mapListResults(results, (id) => this.readOne(id, viewOfChangeset(changeset))); } async listUnsecure( @@ -215,9 +197,7 @@ export class BudgetService { ): Promise { const input = BudgetListInput.defaultValue(BudgetListInput, partialInput); const results = await this.budgetRepo.listUnsecure(input); - return await mapListResults(results, (id) => - this.readOne(id, viewOfChangeset(changeset)), - ); + return await mapListResults(results, (id) => this.readOne(id, viewOfChangeset(changeset))); } async listRecords( diff --git a/src/components/budget/dto/budget.dto.ts b/src/components/budget/dto/budget.dto.ts index 4a6dc7a756..ab320d86c5 100644 --- a/src/components/budget/dto/budget.dto.ts +++ b/src/components/budget/dto/budget.dto.ts @@ -29,8 +29,7 @@ export class Budget extends Interfaces { static readonly Relations = (() => ({ records: [BudgetRecord], })) satisfies ResourceRelationsShape; - static readonly Parent = () => - import('../../project/dto').then((m) => m.IProject); + static readonly Parent = () => import('../../project/dto').then((m) => m.IProject); @Field(() => IProject) declare readonly parent: BaseNode; diff --git a/src/components/budget/dto/list-budget.dto.ts b/src/components/budget/dto/list-budget.dto.ts index 61903eea8d..93990cad51 100644 --- a/src/components/budget/dto/list-budget.dto.ts +++ b/src/components/budget/dto/list-budget.dto.ts @@ -38,9 +38,7 @@ export abstract class BudgetRecordFilters { } @InputType() -export class BudgetRecordListInput extends SortablePaginationInput< - keyof BudgetRecord ->({ +export class BudgetRecordListInput extends SortablePaginationInput({ defaultSort: 'fiscalYear', }) { @Type(() => BudgetRecordFilters) @@ -54,6 +52,4 @@ export class BudgetRecordListOutput extends PaginatedList(BudgetRecord) {} @ObjectType({ description: SecuredList.descriptionFor('budget records'), }) -export abstract class SecuredBudgetRecordList extends SecuredList( - BudgetRecord, -) {} +export abstract class SecuredBudgetRecordList extends SecuredList(BudgetRecord) {} diff --git a/src/components/budget/handlers/create-project-default-budget.handler.ts b/src/components/budget/handlers/create-project-default-budget.handler.ts index 9de59763d3..375bbeb856 100644 --- a/src/components/budget/handlers/create-project-default-budget.handler.ts +++ b/src/components/budget/handlers/create-project-default-budget.handler.ts @@ -3,9 +3,7 @@ import { ProjectCreatedEvent } from '../../project/events'; import { BudgetService } from '../budget.service'; @EventsHandler(ProjectCreatedEvent) -export class CreateProjectDefaultBudgetHandler - implements IEventHandler -{ +export class CreateProjectDefaultBudgetHandler implements IEventHandler { constructor(private readonly budgets: BudgetService) {} async handle({ project }: ProjectCreatedEvent) { diff --git a/src/components/budget/handlers/sync-budget-records-to-funding-partners.handler.ts b/src/components/budget/handlers/sync-budget-records-to-funding-partners.handler.ts index ea333ce496..c7c5a6ac50 100644 --- a/src/components/budget/handlers/sync-budget-records-to-funding-partners.handler.ts +++ b/src/components/budget/handlers/sync-budget-records-to-funding-partners.handler.ts @@ -38,9 +38,7 @@ type SubscribedEvent = PartnershipUpdatedEvent, PartnershipWillDeleteEvent, ) -export class SyncBudgetRecordsToFundingPartners - implements IEventHandler -{ +export class SyncBudgetRecordsToFundingPartners implements IEventHandler { constructor( private readonly budgets: BudgetService, private readonly budgetRepo: BudgetRepository, @@ -55,17 +53,11 @@ export class SyncBudgetRecordsToFundingPartners }); // Get some easy conditions out of the way that don't require DB queries - if ( - event instanceof PartnershipCreatedEvent && - !isFunding(event.partnership) - ) { + if (event instanceof PartnershipCreatedEvent && !isFunding(event.partnership)) { // Partnership is not and never was funding, so do nothing. return; } - if ( - event instanceof PartnershipWillDeleteEvent && - !isFunding(event.partnership) - ) { + if (event instanceof PartnershipWillDeleteEvent && !isFunding(event.partnership)) { // Partnership was not funding, so do nothing. return; } @@ -82,10 +74,7 @@ export class SyncBudgetRecordsToFundingPartners const changeset = this.determineChangeset(event); // Fetch budget & only continue if it is pending - const budget = await this.budgetRepo.listRecordsForSync( - projectId, - changeset, - ); + const budget = await this.budgetRepo.listRecordsForSync(projectId, changeset); const partnerships = await this.determinePartnerships(event, changeset); @@ -153,9 +142,7 @@ export class SyncBudgetRecordsToFundingPartners .filter((record) => record.organization === organizationId) .map((record) => record.fiscalYear); const updated = - event instanceof PartnershipWillDeleteEvent - ? [] - : partnershipFiscalYears(partnership); + event instanceof PartnershipWillDeleteEvent ? [] : partnershipFiscalYears(partnership); const removals = difference(previous, updated); const additions = difference(updated, previous); @@ -200,37 +187,26 @@ export class SyncBudgetRecordsToFundingPartners changeset?: ID, ) { const recordsToDelete = budget.records.filter( - (record) => - record.organization === organizationId && - removals.includes(record.fiscalYear), + (record) => record.organization === organizationId && removals.includes(record.fiscalYear), ); await Promise.all( - recordsToDelete.map((record) => - this.budgets.deleteRecord(record.id, changeset), - ), + recordsToDelete.map((record) => this.budgets.deleteRecord(record.id, changeset)), ); } } const isFunding = (partnership: Partnership) => - readSecured(partnership.types, `partnership's types`).includes( - PartnerType.Funding, - ); + readSecured(partnership.types, `partnership's types`).includes(PartnerType.Funding); type FiscalYear = number; -const partnershipFiscalYears = ( - partnership: Partnership, -): readonly FiscalYear[] => { +const partnershipFiscalYears = (partnership: Partnership): readonly FiscalYear[] => { if (!isFunding(partnership)) { return []; } - const start = readSecured( - partnership.mouStart, - `partnership's mouStart date`, - ); + const start = readSecured(partnership.mouStart, `partnership's mouStart date`); const end = readSecured(partnership.mouEnd, `partnership's mouEnd date`); return start && end ? fiscalYears(start, end) : []; }; diff --git a/src/components/budget/handlers/update-project-current-budget-status.handler.ts b/src/components/budget/handlers/update-project-current-budget-status.handler.ts index 9143cabee7..704f14177c 100644 --- a/src/components/budget/handlers/update-project-current-budget-status.handler.ts +++ b/src/components/budget/handlers/update-project-current-budget-status.handler.ts @@ -5,9 +5,7 @@ import { BudgetService } from '../budget.service'; import { BudgetStatus } from '../dto'; @EventsHandler(ProjectTransitionedEvent) -export class UpdateProjectBudgetStatusHandler - implements IEventHandler -{ +export class UpdateProjectBudgetStatusHandler implements IEventHandler { constructor(private readonly budgets: BudgetService) {} async handle(event: ProjectTransitionedEvent) { @@ -17,15 +15,9 @@ export class UpdateProjectBudgetStatusHandler const nextStatus = stepToStatus(event.workflowEvent.to); let change: [from: BudgetStatus, to: BudgetStatus] | undefined; - if ( - prevStatus === ProjectStatus.InDevelopment && - nextStatus === ProjectStatus.Active - ) { + if (prevStatus === ProjectStatus.InDevelopment && nextStatus === ProjectStatus.Active) { change = [BudgetStatus.Pending, BudgetStatus.Current]; - } else if ( - prevStatus === ProjectStatus.Active && - nextStatus === ProjectStatus.InDevelopment - ) { + } else if (prevStatus === ProjectStatus.Active && nextStatus === ProjectStatus.InDevelopment) { change = [BudgetStatus.Current, BudgetStatus.Pending]; } if (!change) { diff --git a/src/components/ceremony/ceremony.loader.ts b/src/components/ceremony/ceremony.loader.ts index a7447e239d..dee9da3e7f 100644 --- a/src/components/ceremony/ceremony.loader.ts +++ b/src/components/ceremony/ceremony.loader.ts @@ -4,9 +4,7 @@ import { CeremonyService } from './ceremony.service'; import { Ceremony } from './dto'; @LoaderFactory(() => Ceremony) -export class CeremonyLoader - implements DataLoaderStrategy> -{ +export class CeremonyLoader implements DataLoaderStrategy> { constructor(private readonly ceremonies: CeremonyService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/ceremony/ceremony.repository.ts b/src/components/ceremony/ceremony.repository.ts index ab3a03cb2b..c9dcd31426 100644 --- a/src/components/ceremony/ceremony.repository.ts +++ b/src/components/ceremony/ceremony.repository.ts @@ -10,12 +10,7 @@ import { paginate, sorting, } from '~/core/database/query'; -import { - Ceremony, - type CeremonyListInput, - type CreateCeremony, - type UpdateCeremony, -} from './dto'; +import { Ceremony, type CeremonyListInput, type CreateCeremony, type UpdateCeremony } from './dto'; @Injectable() export class CeremonyRepository extends DtoRepository(Ceremony) { @@ -69,10 +64,7 @@ export class CeremonyRepository extends DtoRepository(Ceremony) { relation('in', '', 'engagement', ACTIVE), node('project', 'Project'), ...(filter?.type - ? [ - relation('out', '', 'type', ACTIVE), - node('name', 'Property', { value: filter.type }), - ] + ? [relation('out', '', 'type', ACTIVE), node('name', 'Property', { value: filter.type })] : []), ]) .apply( diff --git a/src/components/ceremony/ceremony.service.ts b/src/components/ceremony/ceremony.service.ts index 96f8a2bf6a..ed26e7471c 100644 --- a/src/components/ceremony/ceremony.service.ts +++ b/src/components/ceremony/ceremony.service.ts @@ -13,10 +13,7 @@ import { Ceremony, type CreateCeremony, type UpdateCeremony } from './dto'; @Injectable() export class CeremonyService { - constructor( - private readonly privileges: Privileges, - private readonly repo: CeremonyRepository, - ) {} + constructor(private readonly privileges: Privileges, private readonly repo: CeremonyRepository) {} async create(input: CreateCeremony): Promise { const { id } = await this.repo.create(input); diff --git a/src/components/ceremony/dto/ceremony.dto.ts b/src/components/ceremony/dto/ceremony.dto.ts index 1c4b92403b..7bf3addfee 100644 --- a/src/components/ceremony/dto/ceremony.dto.ts +++ b/src/components/ceremony/dto/ceremony.dto.ts @@ -18,8 +18,7 @@ import { CeremonyType } from './ceremony-type.enum'; implements: [Resource], }) export class Ceremony extends Resource { - static readonly Parent = () => - import('../../engagement/dto').then((m) => m.IEngagement); + static readonly Parent = () => import('../../engagement/dto').then((m) => m.IEngagement); @Field(() => CeremonyType) readonly type: CeremonyType; diff --git a/src/components/changeset/changeset-aware.resolver.ts b/src/components/changeset/changeset-aware.resolver.ts index c07f996fd0..5cb10621cc 100644 --- a/src/components/changeset/changeset-aware.resolver.ts +++ b/src/components/changeset/changeset-aware.resolver.ts @@ -17,19 +17,14 @@ export class ChangesetAwareResolver { @ResolveField() async changeset(@Parent() object: ChangesetAware): Promise { - return object.changeset - ? await this.resources.load(Changeset, object.changeset) - : null; + return object.changeset ? await this.resources.load(Changeset, object.changeset) : null; } @ResolveField(() => Resource, { description: 'The parent resource of this resource', nullable: true, }) - async parent( - @Parent() object: ChangesetAware, - @Info(Fields, IsOnlyId) isOnlyId: boolean, - ) { + async parent(@Parent() object: ChangesetAware, @Info(Fields, IsOnlyId) isOnlyId: boolean) { if (!object.parent) { return null; } @@ -49,9 +44,7 @@ export class ChangesetAwareResolver { The changes made within this changeset limited to this resource's sub-tree `, }) - async changesetDiff( - @Parent() object: ChangesetAware, - ): Promise { + async changesetDiff(@Parent() object: ChangesetAware): Promise { // TODO move to auth policy if (this.identity.isAnonymous) { return null; diff --git a/src/components/changeset/changeset.arg.ts b/src/components/changeset/changeset.arg.ts index 86e96854c3..3152ce8930 100644 --- a/src/components/changeset/changeset.arg.ts +++ b/src/components/changeset/changeset.arg.ts @@ -1,9 +1,4 @@ -import { - type ArgumentMetadata, - Injectable, - type PipeTransform, - Scope, -} from '@nestjs/common'; +import { type ArgumentMetadata, Injectable, type PipeTransform, Scope } from '@nestjs/common'; import { Args, type ArgsOptions, ID as IDType } from '@nestjs/graphql'; import { Resolver } from '@nestjs/graphql/dist/enums/resolver.enum.js'; import { RESOLVER_TYPE_METADATA as TypeKey } from '@nestjs/graphql/dist/graphql.constants.js'; @@ -17,9 +12,7 @@ const pipeMetadata = createAugmentedMetadataPipe<{ fieldName: string; }>(); -export const ChangesetArg = ( - options?: Omit, -): ParameterDecorator => { +export const ChangesetArg = (options?: Omit): ParameterDecorator => { return (target, methodName, argIndex) => { let type: Resolver; const resolved: ArgsOptions = { @@ -43,9 +36,7 @@ export const ChangesetArg = ( process.nextTick(() => { type = Reflect.getMetadata(TypeKey, (target as any)[methodName!]); if (!type) { - throw new ServerException( - 'Something went wrong trying to determine operation type', - ); + throw new ServerException('Something went wrong trying to determine operation type'); } if (resolved.description) { return; diff --git a/src/components/changeset/changeset.repository.ts b/src/components/changeset/changeset.repository.ts index 51b04850c6..e830586a5f 100644 --- a/src/components/changeset/changeset.repository.ts +++ b/src/components/changeset/changeset.repository.ts @@ -1,13 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - equals, - hasLabel, - isNull, - node, - not, - or, - relation, -} from 'cypher-query-builder'; +import { equals, hasLabel, isNull, node, not, or, relation } from 'cypher-query-builder'; import { type ID, NotFoundException } from '~/common'; import { DtoRepository } from '~/core/database'; import { ACTIVE, path, variable } from '~/core/database/query'; @@ -78,11 +70,7 @@ export class ChangesetRepository extends DtoRepository(Changeset) { }) .return('collect(distinct node) as added'), ) - .return>([ - 'changed', - 'removed', - 'added', - ]) + .return>(['changed', 'removed', 'added']) .first(); if (!result) { throw new NotFoundException('Could not find changeset'); diff --git a/src/components/changeset/changeset.resolver.ts b/src/components/changeset/changeset.resolver.ts index 8b5ff4a1bd..af1210d76f 100644 --- a/src/components/changeset/changeset.resolver.ts +++ b/src/components/changeset/changeset.resolver.ts @@ -27,8 +27,7 @@ export class ChangesetResolver { @IdArg({ name: 'resource', nullable: true, - description: - 'Optionally limit to only changes of this resource and its (grand)children', + description: 'Optionally limit to only changes of this resource and its (grand)children', }) parent?: ID, ): Promise { @@ -44,16 +43,11 @@ export class ChangesetResolver { Promise.all(diff.added.map((node) => load(node))), // If the changeset is approved, we read deleted node otherwise read node in changeset Promise.all( - diff.removed.map((node) => - load(node, changeset.applied ? { deleted: true } : undefined), - ), + diff.removed.map((node) => load(node, changeset.applied ? { deleted: true } : undefined)), ), Promise.all( diff.changed.map(async (node): Promise => { - const [previous, updated] = await Promise.all([ - load(node, { active: true }), - load(node), - ]); + const [previous, updated] = await Promise.all([load(node, { active: true }), load(node)]); return { previous, updated }; }), ), diff --git a/src/components/changeset/commit-changeset-props.query.ts b/src/components/changeset/commit-changeset-props.query.ts index 48dc95cc35..b8737ac30a 100644 --- a/src/components/changeset/commit-changeset-props.query.ts +++ b/src/components/changeset/commit-changeset-props.query.ts @@ -1,10 +1,6 @@ import { node, type Query, relation } from 'cypher-query-builder'; import { DateTime } from 'luxon'; -import { - ACTIVE, - INACTIVE, - prefixNodeLabelsWithDeleted, -} from '~/core/database/query'; +import { ACTIVE, INACTIVE, prefixNodeLabelsWithDeleted } from '~/core/database/query'; export interface CommitChangesetPropsOptions { nodeVar?: string; @@ -12,10 +8,7 @@ export interface CommitChangesetPropsOptions { } export const commitChangesetProps = - ({ - nodeVar = 'node', - changesetVar = 'changeset', - }: CommitChangesetPropsOptions = {}) => + ({ nodeVar = 'node', changesetVar = 'changeset' }: CommitChangesetPropsOptions = {}) => (query: Query) => { query.subQuery([nodeVar, changesetVar], (body) => body diff --git a/src/components/changeset/dto/changeset-diff.dto.ts b/src/components/changeset/dto/changeset-diff.dto.ts index cac5c35893..415da05a4c 100644 --- a/src/components/changeset/dto/changeset-diff.dto.ts +++ b/src/components/changeset/dto/changeset-diff.dto.ts @@ -7,8 +7,7 @@ import { type ResourceMap } from '~/core'; type SomeResource = ValueOf['prototype']; @ObjectType({ - description: - 'The resources that have been added/removed/changed in a given changeset', + description: 'The resources that have been added/removed/changed in a given changeset', }) export class ChangesetDiff { @Field(() => [Resource], { @@ -17,8 +16,7 @@ export class ChangesetDiff { readonly added: readonly SomeResource[]; @Field(() => [Resource], { - description: - 'The list of resources that have been removed in this changeset', + description: 'The list of resources that have been removed in this changeset', }) readonly removed: readonly SomeResource[]; diff --git a/src/components/changeset/dto/changeset.args.ts b/src/components/changeset/dto/changeset.args.ts index c60a138c6e..83f5987a5b 100644 --- a/src/components/changeset/dto/changeset.args.ts +++ b/src/components/changeset/dto/changeset.args.ts @@ -17,8 +17,7 @@ export class ChangesetIds { export type IdsAndView = ChangesetIds & { view: ObjectView }; -export const IdsAndViewArg = () => - Args({ type: () => ChangesetIds }, ObjectViewPipe); +export const IdsAndViewArg = () => Args({ type: () => ChangesetIds }, ObjectViewPipe); @Injectable() export class ObjectViewPipe implements PipeTransform { diff --git a/src/components/changeset/enforce-changeset-editable.pipe.ts b/src/components/changeset/enforce-changeset-editable.pipe.ts index a071513cc6..afb7ee07ec 100644 --- a/src/components/changeset/enforce-changeset-editable.pipe.ts +++ b/src/components/changeset/enforce-changeset-editable.pipe.ts @@ -1,20 +1,7 @@ -import { - type ArgumentMetadata, - Injectable, - type PipeTransform, - type Type, -} from '@nestjs/common'; +import { type ArgumentMetadata, Injectable, type PipeTransform, type Type } from '@nestjs/common'; import { hasCtor, isRegularObject } from '@seedcompany/common'; -import { - DataLoaderContext, - type DataLoaderStrategy, -} from '@seedcompany/data-loader'; -import { - type ID, - InputException, - isIdLike, - loadManyIgnoreMissingThrowAny, -} from '~/common'; +import { DataLoaderContext, type DataLoaderStrategy } from '@seedcompany/data-loader'; +import { type ID, InputException, isIdLike, loadManyIgnoreMissingThrowAny } from '~/common'; import { GqlContextHost, ifGqlContext } from '~/core/graphql'; import { ResourceLoaderRegistry } from '~/core/resources/loader.registry'; import { type Changeset } from './dto'; diff --git a/src/components/changeset/events/changeset-finalizing.event.ts b/src/components/changeset/events/changeset-finalizing.event.ts index 34b8b54524..d951e7a18e 100644 --- a/src/components/changeset/events/changeset-finalizing.event.ts +++ b/src/components/changeset/events/changeset-finalizing.event.ts @@ -5,8 +5,6 @@ import { type Changeset } from '../dto'; * This changeset is in the process of becoming finalized. * Please attach to this event to determine how your objects should change. */ -export class ChangesetFinalizingEvent< - TChangeset extends Changeset = Changeset, -> { +export class ChangesetFinalizingEvent { constructor(readonly changeset: UnsecuredDto) {} } diff --git a/src/components/changeset/reject-changeset-props.query.ts b/src/components/changeset/reject-changeset-props.query.ts index a88963cbca..9efc563087 100644 --- a/src/components/changeset/reject-changeset-props.query.ts +++ b/src/components/changeset/reject-changeset-props.query.ts @@ -8,10 +8,7 @@ export interface RejectChangesetPropsOptions { } export const rejectChangesetProps = - ({ - nodeVar = 'node', - changesetVar = 'changeset', - }: RejectChangesetPropsOptions = {}) => + ({ nodeVar = 'node', changesetVar = 'changeset' }: RejectChangesetPropsOptions = {}) => (query: Query) => { query.subQuery([nodeVar, changesetVar], (body) => body diff --git a/src/components/comments/comment-thread.loader.ts b/src/components/comments/comment-thread.loader.ts index 2785cc867a..48f5a176aa 100644 --- a/src/components/comments/comment-thread.loader.ts +++ b/src/components/comments/comment-thread.loader.ts @@ -5,13 +5,8 @@ import { CommentService } from './comment.service'; import { CommentThread } from './dto'; @LoaderFactory(() => CommentThread) -export class CommentThreadLoader - implements DataLoaderStrategy> -{ - constructor( - private readonly service: CommentService, - private readonly repo: CommentRepository, - ) {} +export class CommentThreadLoader implements DataLoaderStrategy> { + constructor(private readonly service: CommentService, private readonly repo: CommentRepository) {} async loadMany(ids: ReadonlyArray>) { const threads = await this.repo.threads.readMany(ids); diff --git a/src/components/comments/comment-thread.repository.ts b/src/components/comments/comment-thread.repository.ts index 5292002e08..1cd7e3ae9e 100644 --- a/src/components/comments/comment-thread.repository.ts +++ b/src/components/comments/comment-thread.repository.ts @@ -45,11 +45,7 @@ export class CommentThreadRepository extends DtoRepository(CommentThread) { relation('in', undefined, 'commentThread'), node('parent', 'BaseNode'), ]) - .match([ - node('node'), - relation('out', '', 'creator'), - node('creator', 'User'), - ]) + .match([node('node'), relation('out', '', 'creator'), node('creator', 'User')]) .subQuery('node', (sub) => sub .with('node as thread') @@ -82,10 +78,7 @@ export class CommentThreadRepository extends DtoRepository(CommentThread) { .match([ node('node', 'CommentThread'), ...(parent - ? [ - relation('in', '', 'commentThread', ACTIVE), - node('', 'BaseNode', { id: parent }), - ] + ? [relation('in', '', 'commentThread', ACTIVE), node('', 'BaseNode', { id: parent })] : []), ]) .apply(sorting(CommentThread, input)) diff --git a/src/components/comments/comment-thread.resolver.ts b/src/components/comments/comment-thread.resolver.ts index 5a1a9d43e9..30396c11af 100644 --- a/src/components/comments/comment-thread.resolver.ts +++ b/src/components/comments/comment-thread.resolver.ts @@ -96,10 +96,7 @@ export class CommentThreadResolver { } @ResolveField(() => User) - async creator( - @Parent() thread: CommentThread, - @Loader(UserLoader) users: LoaderOf, - ) { + async creator(@Parent() thread: CommentThread, @Loader(UserLoader) users: LoaderOf) { return await users.load(thread.creator); } } diff --git a/src/components/comments/comment.repository.ts b/src/components/comments/comment.repository.ts index ce037e3342..2d20d2de6b 100644 --- a/src/components/comments/comment.repository.ts +++ b/src/components/comments/comment.repository.ts @@ -41,10 +41,7 @@ export class CommentRepository extends DtoRepository(Comment) { .query() .subQuery( input.threadId - ? (q) => - q - .matchNode('thread', 'CommentThread', { id: input.threadId }) - .return('thread') + ? (q) => q.matchNode('thread', 'CommentThread', { id: input.threadId }).return('thread') : await this.threads.create(input.resourceId), ) .apply(await createNode(Comment, { initialProps })) @@ -54,17 +51,11 @@ export class CommentRepository extends DtoRepository(Comment) { out: { creator: currentUser }, }), ) - .return<{ id: ID; threadId: ID }>([ - 'node.id as id', - 'thread.id as threadId', - ]) + .return<{ id: ID; threadId: ID }>(['node.id as id', 'thread.id as threadId']) .first(); } - async update( - existing: UnsecuredDto, - changes: ChangesOf, - ) { + async update(existing: UnsecuredDto, changes: ChangesOf) { await this.updateProperties(existing, changes); } @@ -77,15 +68,9 @@ export class CommentRepository extends DtoRepository(Comment) { relation('in', '', 'comment', ACTIVE), node('thread', 'CommentThread'), ]) - .match([ - node('node'), - relation('out', '', 'creator'), - node('creator', 'User'), - ]) + .match([node('node'), relation('out', '', 'creator'), node('creator', 'User')]) .return<{ dto: UnsecuredDto }>( - merge('props', { thread: 'thread.id', creator: 'creator.id' }).as( - 'dto', - ), + merge('props', { thread: 'thread.id', creator: 'creator.id' }).as('dto'), ); } diff --git a/src/components/comments/comment.resolver.ts b/src/components/comments/comment.resolver.ts index c4aec03b7d..31ea1c3b71 100644 --- a/src/components/comments/comment.resolver.ts +++ b/src/components/comments/comment.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { UserLoader } from '../user'; @@ -27,9 +21,7 @@ export class CommentResolver { @Mutation(() => UpdateCommentOutput, { description: 'Update an existing comment', }) - async updateComment( - @Args('input') input: UpdateCommentInput, - ): Promise { + async updateComment(@Args('input') input: UpdateCommentInput): Promise { const comment = await this.service.update(input); return { comment }; } diff --git a/src/components/comments/comment.service.ts b/src/components/comments/comment.service.ts index ef5a1e7487..263458971b 100644 --- a/src/components/comments/comment.service.ts +++ b/src/components/comments/comment.service.ts @@ -54,14 +54,8 @@ export class CommentService { } dto = await this.repo.readOne(result.id); } catch (exception) { - if ( - input.threadId && - !(await this.repo.threads.getBaseNode(input.threadId)) - ) { - throw new NotFoundException( - 'Comment thread does not exist', - 'threadId', - ); + if (input.threadId && !(await this.repo.threads.getBaseNode(input.threadId))) { + throw new NotFoundException('Comment thread does not exist', 'threadId'); } throw new CreationFailed(Comment, { cause: exception }); diff --git a/src/components/comments/commentable.resolver.ts b/src/components/comments/commentable.resolver.ts index 9c42dff448..d9aabd056e 100644 --- a/src/components/comments/commentable.resolver.ts +++ b/src/components/comments/commentable.resolver.ts @@ -1,13 +1,5 @@ import { Info, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; -import { - Fields, - type ID, - IdArg, - IsOnly, - ListArg, - type Resource, - SecuredList, -} from '~/common'; +import { Fields, type ID, IdArg, IsOnly, ListArg, type Resource, SecuredList } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { Identity } from '~/core/authentication'; import { CommentThreadLoader } from './comment-thread.loader'; @@ -16,10 +8,7 @@ import { Commentable, CommentThreadList, CommentThreadListInput } from './dto'; @Resolver(Commentable) export class CommentableResolver { - constructor( - private readonly service: CommentService, - private readonly identity: Identity, - ) {} + constructor(private readonly service: CommentService, private readonly identity: Identity) {} @Query(() => Commentable, { description: 'Load a commentable resource by ID', diff --git a/src/components/comments/create-comment.resolver.ts b/src/components/comments/create-comment.resolver.ts index 28fc816c06..dd72c090c6 100644 --- a/src/components/comments/create-comment.resolver.ts +++ b/src/components/comments/create-comment.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { Loader, type LoaderOf } from '~/core'; import { CommentThreadLoader } from './comment-thread.loader'; import { CommentService } from './comment.service'; @@ -17,9 +11,7 @@ export class CreateCommentResolver { @Mutation(() => CreateCommentOutput, { description: 'Create a comment', }) - async createComment( - @Args('input') input: CreateCommentInput, - ): Promise { + async createComment(@Args('input') input: CreateCommentInput): Promise { const comment = await this.service.create(input); return { comment }; } diff --git a/src/components/comments/dto/comment.dto.ts b/src/components/comments/dto/comment.dto.ts index 89e640e611..de77e9dcd6 100644 --- a/src/components/comments/dto/comment.dto.ts +++ b/src/components/comments/dto/comment.dto.ts @@ -9,8 +9,7 @@ import { RegisterResource } from '~/core/resources'; implements: [Resource], }) export class Comment extends Resource { - static readonly Parent = () => - import('./comment-thread.dto').then((m) => m.CommentThread); + static readonly Parent = () => import('./comment-thread.dto').then((m) => m.CommentThread); readonly thread: ID; diff --git a/src/components/comments/dto/commentable.dto.ts b/src/components/comments/dto/commentable.dto.ts index 2054edbb91..b8bfa2429d 100644 --- a/src/components/comments/dto/commentable.dto.ts +++ b/src/components/comments/dto/commentable.dto.ts @@ -1,9 +1,5 @@ import { InterfaceType } from '@nestjs/graphql'; -import { - resolveByTypename, - Resource, - type ResourceRelationsShape, -} from '~/common'; +import { resolveByTypename, Resource, type ResourceRelationsShape } from '~/common'; import { e } from '~/core/gel'; import { RegisterResource } from '~/core/resources'; import { CommentThread } from './comment-thread.dto'; diff --git a/src/components/comments/dto/list-comment-thread.dto.ts b/src/components/comments/dto/list-comment-thread.dto.ts index d37c0f6f4a..ae7d72ce42 100644 --- a/src/components/comments/dto/list-comment-thread.dto.ts +++ b/src/components/comments/dto/list-comment-thread.dto.ts @@ -4,9 +4,7 @@ import { CommentThread } from './comment-thread.dto'; import { Commentable } from './commentable.dto'; @InputType() -export class CommentThreadListInput extends SortablePaginationInput< - keyof CommentThread ->({ +export class CommentThreadListInput extends SortablePaginationInput({ defaultSort: 'createdAt', defaultOrder: Order.DESC, }) {} diff --git a/src/components/comments/mention-notification/comment-via-mention-notification.service.ts b/src/components/comments/mention-notification/comment-via-mention-notification.service.ts index 933b12107e..8916ac6344 100644 --- a/src/components/comments/mention-notification/comment-via-mention-notification.service.ts +++ b/src/components/comments/mention-notification/comment-via-mention-notification.service.ts @@ -12,10 +12,7 @@ export class CommentViaMentionNotificationService { return []; // TODO } - async notify( - mentionees: ReadonlyArray>, - comment: UnsecuredDto, - ) { + async notify(mentionees: ReadonlyArray>, comment: UnsecuredDto) { await this.notifications.create(CommentViaMentionNotification, mentionees, { comment: comment.id, }); diff --git a/src/components/comments/mention-notification/comment-via-mention-notification.strategy.ts b/src/components/comments/mention-notification/comment-via-mention-notification.strategy.ts index 6808303873..7a1b6687d3 100644 --- a/src/components/comments/mention-notification/comment-via-mention-notification.strategy.ts +++ b/src/components/comments/mention-notification/comment-via-mention-notification.strategy.ts @@ -1,10 +1,6 @@ import { node, type Query, relation } from 'cypher-query-builder'; import { createRelationships, exp } from '~/core/database/query'; -import { - INotificationStrategy, - type InputOf, - NotificationStrategy, -} from '../../notifications'; +import { INotificationStrategy, type InputOf, NotificationStrategy } from '../../notifications'; import { CommentViaMentionNotification } from './comment-via-mention-notification.dto'; @NotificationStrategy(CommentViaMentionNotification) @@ -21,11 +17,7 @@ export class CommentViaMentionNotificationStrategy extends INotificationStrategy hydrateExtraForNeo4j(outVar: string) { return (query: Query) => query - .match([ - node('node'), - relation('out', '', 'comment'), - node('comment', 'Comment'), - ]) + .match([node('node'), relation('out', '', 'comment'), node('comment', 'Comment')]) .return( exp({ comment: 'comment { .id }', diff --git a/src/components/engagement/dto/create-engagement.dto.ts b/src/components/engagement/dto/create-engagement.dto.ts index d93d85b805..1cd8b49662 100644 --- a/src/components/engagement/dto/create-engagement.dto.ts +++ b/src/components/engagement/dto/create-engagement.dto.ts @@ -3,13 +3,7 @@ import { entries } from '@seedcompany/common'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; import { stripIndent } from 'common-tags'; -import { - CalendarDate, - DataObject, - DateField, - type ID, - IdField, -} from '~/common'; +import { CalendarDate, DataObject, DateField, type ID, IdField } from '~/common'; import { ChangesetIdField } from '../../changeset'; import { CreateDefinedFileVersionInput } from '../../file/dto'; import { LanguageMilestone } from '../../language/dto'; diff --git a/src/components/engagement/dto/engagement.dto.ts b/src/components/engagement/dto/engagement.dto.ts index c6cdbaa05f..bc889f13a1 100644 --- a/src/components/engagement/dto/engagement.dto.ts +++ b/src/components/engagement/dto/engagement.dto.ts @@ -30,11 +30,7 @@ import { Commentable } from '../../comments/dto'; import { SecuredLanguageMilestone } from '../../language/dto'; import { SecuredAIAssistedTranslation } from '../../language/dto/ai-assisted-translation.enum'; import { Product, SecuredMethodologies } from '../../product/dto'; -import { - InternshipProject, - IProject, - TranslationProject, -} from '../../project/dto'; +import { InternshipProject, IProject, TranslationProject } from '../../project/dto'; import { SecuredInternPosition } from './intern-position.enum'; import { SecuredEngagementStatus } from './status.enum'; @@ -42,22 +38,16 @@ import { SecuredEngagementStatus } from './status.enum'; * This should be used for TypeScript types as we'll always be passing around * concrete engagements. */ -export type AnyEngagement = MergeExclusive< - LanguageEngagement, - InternshipEngagement ->; +export type AnyEngagement = MergeExclusive; const Interfaces = IntersectTypes(Resource, ChangesetAware, Commentable); export const resolveEngagementType = (val: Pick) => - val.__typename === 'default::LanguageEngagement' - ? LanguageEngagement - : InternshipEngagement; + val.__typename === 'default::LanguageEngagement' ? LanguageEngagement : InternshipEngagement; const RequiredWhenNotInDev = RequiredWhen(() => Engagement)({ description: 'the engagement is not in development', - isEnabled: ({ status }) => - status !== 'InDevelopment' && status !== 'DidNotDevelop', + isEnabled: ({ status }) => status !== 'InDevelopment' && status !== 'DidNotDevelop', }); @RegisterResource({ db: e.Engagement }) @@ -72,14 +62,12 @@ class Engagement extends Interfaces { static readonly Relations = { ...Commentable.Relations, } satisfies ResourceRelationsShape; - static readonly Parent = () => - import('../../project/dto').then((m) => m.IProject); + static readonly Parent = () => import('../../project/dto').then((m) => m.IProject); static readonly resolve = resolveEngagementType; declare readonly __typename: DBNames; - readonly project: LinkTo<'Project'> & - Pick, 'status' | 'step' | 'type'>; + readonly project: LinkTo<'Project'> & Pick, 'status' | 'step' | 'type'>; @Field(() => IProject) declare readonly parent: BaseNode; @@ -168,8 +156,7 @@ export class LanguageEngagement extends Engagement { // why is this singular? product: [Product], } satisfies ResourceRelationsShape; - static readonly Parent = () => - import('../../project/dto').then((m) => m.TranslationProject); + static readonly Parent = () => import('../../project/dto').then((m) => m.TranslationProject); declare readonly __typename: DBNames; @@ -212,8 +199,7 @@ export class LanguageEngagement extends Engagement { implements: [Engagement], }) export class InternshipEngagement extends Engagement { - static readonly Parent = () => - import('../../project/dto').then((m) => m.InternshipProject); + static readonly Parent = () => import('../../project/dto').then((m) => m.InternshipProject); declare readonly __typename: DBNames; diff --git a/src/components/engagement/dto/list-engagements.dto.ts b/src/components/engagement/dto/list-engagements.dto.ts index b2edac5fb7..c011e1d743 100644 --- a/src/components/engagement/dto/list-engagements.dto.ts +++ b/src/components/engagement/dto/list-engagements.dto.ts @@ -11,11 +11,7 @@ import { SecuredList, SortablePaginationInput, } from '~/common'; -import { - AIAssistedTranslation, - LanguageFilters, - LanguageMilestone, -} from '../../language/dto'; +import { AIAssistedTranslation, LanguageFilters, LanguageMilestone } from '../../language/dto'; import { ProjectFilters } from '../../project/dto'; import { UserFilters } from '../../user/dto'; import { @@ -34,8 +30,7 @@ export abstract class EngagementFilters { readonly type?: 'language' | 'internship'; @OptionalField({ - description: - 'Only engagements whose project or engaged entity (language / user) name match', + description: 'Only engagements whose project or engaged entity (language / user) name match', }) readonly name?: string; @@ -49,8 +44,7 @@ export abstract class EngagementFilters { readonly project?: ProjectFilters & {}; @OptionalField({ - description: - 'Only engagements whose engaged entity (language / user) name match', + description: 'Only engagements whose engaged entity (language / user) name match', }) readonly engagedName?: string; @@ -87,9 +81,7 @@ export abstract class EngagementFilters { } @InputType() -export class EngagementListInput extends SortablePaginationInput< - keyof Engagement ->({ +export class EngagementListInput extends SortablePaginationInput({ defaultSort: 'createdAt', }) { @FilterField(() => EngagementFilters) @@ -97,49 +89,35 @@ export class EngagementListInput extends SortablePaginationInput< } @ObjectType() -export class EngagementListOutput extends PaginatedList< - IEngagement, - Engagement ->(IEngagement, { +export class EngagementListOutput extends PaginatedList(IEngagement, { itemsDescription: PaginatedList.itemDescriptionFor('engagements'), }) {} @ObjectType() -export class LanguageEngagementListOutput extends PaginatedList( - LanguageEngagement, - { - itemsDescription: PaginatedList.itemDescriptionFor('language engagements'), - }, -) {} +export class LanguageEngagementListOutput extends PaginatedList(LanguageEngagement, { + itemsDescription: PaginatedList.itemDescriptionFor('language engagements'), +}) {} @ObjectType({ description: SecuredList.descriptionFor('engagements'), }) -export abstract class SecuredEngagementList extends SecuredList< +export abstract class SecuredEngagementList extends SecuredList( IEngagement, - Engagement ->(IEngagement, { - itemsDescription: PaginatedList.itemDescriptionFor('engagements'), -}) {} + { + itemsDescription: PaginatedList.itemDescriptionFor('engagements'), + }, +) {} @ObjectType({ description: SecuredList.descriptionFor('language engagements'), }) -export abstract class SecuredLanguageEngagementList extends SecuredList( - LanguageEngagement, - { - itemsDescription: PaginatedList.itemDescriptionFor('language engagements'), - }, -) {} +export abstract class SecuredLanguageEngagementList extends SecuredList(LanguageEngagement, { + itemsDescription: PaginatedList.itemDescriptionFor('language engagements'), +}) {} @ObjectType({ description: SecuredList.descriptionFor('internship engagements'), }) -export abstract class SecuredInternshipEngagementList extends SecuredList( - InternshipEngagement, - { - itemsDescription: PaginatedList.itemDescriptionFor( - 'internship engagements', - ), - }, -) {} +export abstract class SecuredInternshipEngagementList extends SecuredList(InternshipEngagement, { + itemsDescription: PaginatedList.itemDescriptionFor('internship engagements'), +}) {} diff --git a/src/components/engagement/dto/status.enum.ts b/src/components/engagement/dto/status.enum.ts index 37f5bc9af0..a797dd1bcb 100644 --- a/src/components/engagement/dto/status.enum.ts +++ b/src/components/engagement/dto/status.enum.ts @@ -23,13 +23,11 @@ export const EngagementStatus = makeEnum({ { value: 'Terminated', terminal: true }, { value: 'Completed', terminal: true }, - ...(['Converted', 'Unapproved', 'Transferred', 'NotRenewed'] as const).map( - (value) => ({ - value, - terminal: true, - deprecationReason: 'Legacy. Only used in historic data.', - }), - ), + ...(['Converted', 'Unapproved', 'Transferred', 'NotRenewed'] as const).map((value) => ({ + value, + terminal: true, + deprecationReason: 'Legacy. Only used in historic data.', + })), ], exposeOrder: true, extra: ({ entries }) => ({ @@ -43,9 +41,7 @@ export const EngagementStatus = makeEnum({ }) export class SecuredEngagementStatus extends SecuredEnum(EngagementStatus) {} -export type EngagementTransitionType = EnumType< - typeof EngagementTransitionType ->; +export type EngagementTransitionType = EnumType; export const EngagementTransitionType = makeEnum({ name: 'EngagementTransitionType', values: ['Neutral', 'Approve', 'Reject'], diff --git a/src/components/engagement/engagement-status.resolver.ts b/src/components/engagement/engagement-status.resolver.ts index 23248270e8..daaf588974 100644 --- a/src/components/engagement/engagement-status.resolver.ts +++ b/src/components/engagement/engagement-status.resolver.ts @@ -1,11 +1,7 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { Grandparent } from '~/common'; -import { - type Engagement, - EngagementStatusTransition, - SecuredEngagementStatus, -} from './dto'; +import { type Engagement, EngagementStatusTransition, SecuredEngagementStatus } from './dto'; import { EngagementRules } from './engagement.rules'; @Resolver(SecuredEngagementStatus) @@ -22,10 +18,7 @@ export class EngagementStatusResolver { if (!status.canRead || !status.canEdit || !status.value) { return []; } - return await this.engagementRules.getAvailableTransitions( - eng.id, - eng.changeset, - ); + return await this.engagementRules.getAvailableTransitions(eng.id, eng.changeset); } @ResolveField(() => Boolean, { diff --git a/src/components/engagement/engagement.gel.repository.ts b/src/components/engagement/engagement.gel.repository.ts index a2e220644e..fa5a0e3b71 100644 --- a/src/components/engagement/engagement.gel.repository.ts +++ b/src/components/engagement/engagement.gel.repository.ts @@ -99,10 +99,7 @@ const hydrate = e.shape(e.Engagement, (engagement) => { project: engagement.project.name, language: langEng.language.name, intern: e.array_join_maybe( - e.array([ - internEng.intern.displayFirstName, - internEng.intern.displayLastName, - ]), + e.array([internEng.intern.displayFirstName, internEng.intern.displayLastName]), ' ', ), }), diff --git a/src/components/engagement/engagement.loader.ts b/src/components/engagement/engagement.loader.ts index feb63de77d..4ecc730fad 100644 --- a/src/components/engagement/engagement.loader.ts +++ b/src/components/engagement/engagement.loader.ts @@ -1,18 +1,10 @@ import { type ID, type ObjectView } from '~/common'; import { LoaderFactory, ObjectViewAwareLoader } from '~/core/data-loader'; -import { - type Engagement, - IEngagement, - InternshipEngagement, - LanguageEngagement, -} from './dto'; +import { type Engagement, IEngagement, InternshipEngagement, LanguageEngagement } from './dto'; import { EngagementService } from './engagement.service'; @LoaderFactory(() => [IEngagement, LanguageEngagement, InternshipEngagement]) -export class EngagementLoader extends ObjectViewAwareLoader< - Engagement, - IEngagement -> { +export class EngagementLoader extends ObjectViewAwareLoader { constructor(private readonly engagements: EngagementService) { super(); } diff --git a/src/components/engagement/engagement.repository.ts b/src/components/engagement/engagement.repository.ts index 28b2a2edd8..063d951158 100644 --- a/src/components/engagement/engagement.repository.ts +++ b/src/components/engagement/engagement.repository.ts @@ -1,13 +1,6 @@ import { Injectable } from '@nestjs/common'; import { mapValues, simpleSwitch } from '@seedcompany/common'; -import { - hasLabel, - inArray, - node, - type Node, - type Query, - relation, -} from 'cypher-query-builder'; +import { hasLabel, inArray, node, type Node, type Query, relation } from 'cypher-query-builder'; import { difference, pickBy, upperFirst } from 'lodash'; import { DateTime } from 'luxon'; import { type MergeExclusive } from 'type-fest'; @@ -55,10 +48,7 @@ import { FileService } from '../file'; import { type FileId } from '../file/dto'; import { LanguageMilestone } from '../language/dto'; import { AIAssistedTranslation } from '../language/dto/ai-assisted-translation.enum'; -import { - languageFilters, - languageSorters, -} from '../language/language.repository'; +import { languageFilters, languageSorters } from '../language/language.repository'; import { Location } from '../location/dto'; import { matchCurrentDue, @@ -83,17 +73,11 @@ import { type UpdateLanguageEngagement, } from './dto'; -export type LanguageOrEngagementId = MergeExclusive< - { engagementId: ID }, - { languageId: ID } ->; +export type LanguageOrEngagementId = MergeExclusive<{ engagementId: ID }, { languageId: ID }>; @Injectable() export class EngagementRepository extends CommonRepository { - constructor( - private readonly privileges: Privileges, - private readonly files: FileService, - ) { + constructor(private readonly privileges: Privileges, private readonly files: FileService) { super(); } @@ -132,58 +116,27 @@ export class EngagementRepository extends CommonRepository { ]) .apply(matchPropsAndProjectSensAndScopedRoles({ view })) .apply(matchChangesetAndChangedProps(view?.changeset)) - .optionalMatch([ - node('node'), - relation('out', '', 'ceremony', ACTIVE), - node('ceremony'), - ]) - .optionalMatch([ - node('node'), - relation('out', '', 'language', ACTIVE), - node('language'), - ]) - .optionalMatch([ - node('node'), - relation('out', '', 'intern', ACTIVE), - node('intern'), - ]) + .optionalMatch([node('node'), relation('out', '', 'ceremony', ACTIVE), node('ceremony')]) + .optionalMatch([node('node'), relation('out', '', 'language', ACTIVE), node('language')]) + .optionalMatch([node('node'), relation('out', '', 'intern', ACTIVE), node('intern')]) .optionalMatch([ node('node'), relation('out', '', 'countryOfOrigin', ACTIVE), node('countryOfOrigin'), ]) - .optionalMatch([ - node('node'), - relation('out', '', 'mentor', ACTIVE), - node('mentor'), - ]) - .optionalMatch([ - node('project'), - relation('out', '', 'mouStart', ACTIVE), - node('mouStart'), - ]) - .optionalMatch([ - node('project'), - relation('out', '', 'mouEnd', ACTIVE), - node('mouEnd'), - ]) + .optionalMatch([node('node'), relation('out', '', 'mentor', ACTIVE), node('mentor')]) + .optionalMatch([node('project'), relation('out', '', 'mouStart', ACTIVE), node('mouStart')]) + .optionalMatch([node('project'), relation('out', '', 'mouEnd', ACTIVE), node('mouEnd')]) .apply(matchNames) .match([ - [ - node('project'), - relation('out', '', 'status', ACTIVE), - node('status'), - ], + [node('project'), relation('out', '', 'status', ACTIVE), node('status')], [node('project'), relation('out', '', 'step', ACTIVE), node('step')], ]) .return<{ dto: UnsecuredDto }>( merge('props', 'changedProps', { __typename: listConcat( '"default::"', - typenameForView( - ['LanguageEngagement', 'InternshipEngagement'], - view, - ), + typenameForView(['LanguageEngagement', 'InternshipEngagement'], view), ), parent: 'project', project: { @@ -219,10 +172,7 @@ export class EngagementRepository extends CommonRepository { ); } - async createLanguageEngagement( - input: CreateLanguageEngagement, - changeset?: ID, - ) { + async createLanguageEngagement(input: CreateLanguageEngagement, changeset?: ID) { const pnpId = await generateId(); const { @@ -231,8 +181,7 @@ export class EngagementRepository extends CommonRepository { methodology: _, ...initialProps } = { - ...mapValues.fromList(CreateLanguageEngagement.Props, () => undefined) - .asRecord, + ...mapValues.fromList(CreateLanguageEngagement.Props, () => undefined).asRecord, ...input, status: input.status || EngagementStatus.InDevelopment, pnp: pnpId, @@ -241,18 +190,12 @@ export class EngagementRepository extends CommonRepository { lastSuspendedAt: undefined, lastReactivatedAt: undefined, milestoneReached: input.milestoneReached || LanguageMilestone.Unknown, - usingAIAssistedTranslation: - input.usingAIAssistedTranslation || AIAssistedTranslation.Unknown, + usingAIAssistedTranslation: input.usingAIAssistedTranslation || AIAssistedTranslation.Unknown, modifiedAt: DateTime.local(), canDelete: true, }; - await this.verifyRelationshipEligibility( - projectId, - languageId, - false, - changeset, - ); + await this.verifyRelationshipEligibility(projectId, languageId, false, changeset); if (input.firstScripture) { await this.verifyFirstScripture({ languageId }); @@ -277,39 +220,18 @@ export class EngagementRepository extends CommonRepository { throw new CreationFailed(LanguageEngagement); } - await this.files.createDefinedFile( - pnpId, - `PNP`, - result.id, - 'pnp', - input.pnp, - 'engagement.pnp', - ); + await this.files.createDefinedFile(pnpId, `PNP`, result.id, 'pnp', input.pnp, 'engagement.pnp'); - return (await this.readOne(result.id, viewOfChangeset(changeset)).catch( - (e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(LanguageEngagement) - : e; - }, - )) as UnsecuredDto; + return (await this.readOne(result.id, viewOfChangeset(changeset)).catch((e) => { + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(LanguageEngagement) : e; + })) as UnsecuredDto; } - async createInternshipEngagement( - input: CreateInternshipEngagement, - changeset?: ID, - ) { + async createInternshipEngagement(input: CreateInternshipEngagement, changeset?: ID) { const growthPlanId = await generateId(); - const { - projectId, - internId, - mentorId, - countryOfOriginId, - ...initialProps - } = { - ...mapValues.fromList(CreateInternshipEngagement.Props, () => undefined) - .asRecord, + const { projectId, internId, mentorId, countryOfOriginId, ...initialProps } = { + ...mapValues.fromList(CreateInternshipEngagement.Props, () => undefined).asRecord, ...input, methodologies: input.methodologies || [], status: input.status || EngagementStatus.InDevelopment, @@ -322,12 +244,7 @@ export class EngagementRepository extends CommonRepository { canDelete: true, }; - await this.verifyRelationshipEligibility( - projectId, - internId, - true, - changeset, - ); + await this.verifyRelationshipEligibility(projectId, internId, true, changeset); const query = this.db .query() @@ -349,16 +266,10 @@ export class EngagementRepository extends CommonRepository { const result = await query.first(); if (!result) { if (mentorId && !(await this.getBaseNode(mentorId, User))) { - throw new NotFoundException( - 'Could not find mentor', - 'engagement.mentorId', - ); + throw new NotFoundException('Could not find mentor', 'engagement.mentorId'); } - if ( - countryOfOriginId && - !(await this.getBaseNode(countryOfOriginId, Location)) - ) { + if (countryOfOriginId && !(await this.getBaseNode(countryOfOriginId, Location))) { throw new NotFoundException( 'Could not find country of origin', 'engagement.countryOfOriginId', @@ -377,13 +288,9 @@ export class EngagementRepository extends CommonRepository { 'engagement.growthPlan', ); - return (await this.readOne(result.id, viewOfChangeset(changeset)).catch( - (e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(InternshipEngagement) - : e; - }, - )) as UnsecuredDto; + return (await this.readOne(result.id, viewOfChangeset(changeset)).catch((e) => { + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(InternshipEngagement) : e; + })) as UnsecuredDto; } getActualLanguageChanges = getChanges(LanguageEngagement); @@ -395,9 +302,7 @@ export class EngagementRepository extends CommonRepository { const engagement = await this.readOne(id); if (!engagement.pnp) { - throw new ServerException( - 'Expected PnP file to be created with the engagement', - ); + throw new ServerException('Expected PnP file to be created with the engagement'); } await this.files.createFileVersion({ @@ -433,22 +338,13 @@ export class EngagementRepository extends CommonRepository { getActualInternshipChanges = getChanges(InternshipEngagement); async updateInternship(changes: UpdateInternshipEngagement, changeset?: ID) { - const { - id, - mentorId, - countryOfOriginId, - growthPlan, - status, - ...simpleChanges - } = changes; + const { id, mentorId, countryOfOriginId, growthPlan, status, ...simpleChanges } = changes; if (growthPlan) { const engagement = await this.readOne(id); if (!engagement.growthPlan) { - throw new ServerException( - 'Expected Growth Plan file to be created with the engagement', - ); + throw new ServerException('Expected Growth Plan file to be created with the engagement'); } await this.files.createFileVersion({ @@ -462,12 +358,7 @@ export class EngagementRepository extends CommonRepository { } if (countryOfOriginId !== undefined) { - await this.updateRelation( - 'countryOfOrigin', - 'Location', - id, - countryOfOriginId, - ); + await this.updateRelation('countryOfOrigin', 'Location', id, countryOfOriginId); } await this.db.updateProperties({ @@ -496,11 +387,7 @@ export class EngagementRepository extends CommonRepository { .subQuery((sub) => sub .match([ - node( - 'project', - 'Project', - pickBy({ id: input.filter?.project?.id }), - ), + node('project', 'Project', pickBy({ id: input.filter?.project?.id })), relation('out', '', 'engagement', ACTIVE), node('node', 'Engagement'), ]) @@ -615,19 +502,13 @@ export class EngagementRepository extends CommonRepository { .first(); if (!result?.project) { - throw new NotFoundException( - 'Could not find project', - 'engagement.projectId', - ); + throw new NotFoundException('Could not find project', 'engagement.projectId'); } - const isActuallyInternship = - result.project.properties.type === ProjectType.Internship; + const isActuallyInternship = result.project.properties.type === ProjectType.Internship; if (isActuallyInternship !== isInternship) { throw new InputException( - `Only ${ - isInternship ? 'Internship' : 'Language' - } Engagements can be created on ${ + `Only ${isInternship ? 'Internship' : 'Language'} Engagements can be created on ${ isInternship ? 'Internship' : 'Translation' } Projects`, `engagement.${property}Id`, @@ -636,10 +517,7 @@ export class EngagementRepository extends CommonRepository { const label = isInternship ? 'person' : 'language'; if (!result?.other) { - throw new NotFoundException( - `Could not find ${label}`, - `engagement.${property}Id`, - ); + throw new NotFoundException(`Could not find ${label}`, `engagement.${property}Id`); } if (result.engagement) { @@ -652,9 +530,7 @@ export class EngagementRepository extends CommonRepository { return result; } - private async doesLanguageHaveExternalFirstScripture( - id: LanguageOrEngagementId, - ) { + private async doesLanguageHaveExternalFirstScripture(id: LanguageOrEngagementId) { const result = await this.db .query() .apply(this.matchLanguageOrEngagement(id)) @@ -668,9 +544,7 @@ export class EngagementRepository extends CommonRepository { return !!result; } - private async doOtherEngagementsHaveFirstScripture( - id: LanguageOrEngagementId, - ) { + private async doOtherEngagementsHaveFirstScripture(id: LanguageOrEngagementId) { const result = await this.db .query() .apply(this.matchLanguageOrEngagement(id)) @@ -686,10 +560,7 @@ export class EngagementRepository extends CommonRepository { return !!result; } - private matchLanguageOrEngagement({ - engagementId, - languageId, - }: LanguageOrEngagementId) { + private matchLanguageOrEngagement({ engagementId, languageId }: LanguageOrEngagementId) { return (query: Query) => engagementId ? query.match([ @@ -761,11 +632,7 @@ export const engagementFilters = filter.define(() => EngagementFilters, { ]), startDate: filter.dateTime(({ query }) => { query.match([ - [ - node('node'), - relation('out', '', 'startDateOverride', ACTIVE), - node('startDateOverride'), - ], + [node('node'), relation('out', '', 'startDateOverride', ACTIVE), node('startDateOverride')], [ node('node'), relation('in', '', 'engagement'), @@ -778,11 +645,7 @@ export const engagementFilters = filter.define(() => EngagementFilters, { }), endDate: filter.dateTime(({ query }) => { query.match([ - [ - node('node'), - relation('out', '', 'endDateOverride', ACTIVE), - node('endDateOverride'), - ], + [node('node'), relation('out', '', 'endDateOverride', ACTIVE), node('endDateOverride')], [ node('node'), relation('in', '', 'engagement'), @@ -829,25 +692,13 @@ export const engagementFilters = filter.define(() => EngagementFilters, { minScore: 0.9, }), project: filter.sub(() => projectFilters)((sub) => - sub.match([ - node('outer'), - relation('in', '', 'engagement'), - node('node', 'Project'), - ]), + sub.match([node('outer'), relation('in', '', 'engagement'), node('node', 'Project')]), ), language: filter.sub(() => languageFilters)((sub) => - sub.match([ - node('outer'), - relation('out', '', 'language'), - node('node', 'Language'), - ]), + sub.match([node('outer'), relation('out', '', 'language'), node('node', 'Language')]), ), intern: filter.sub(() => userFilters)((sub) => - sub.match([ - node('outer'), - relation('out', '', 'intern'), - node('node', 'User'), - ]), + sub.match([node('outer'), relation('out', '', 'intern'), node('node', 'User')]), ), milestoneReached: filter.stringListProp(), usingAIAssistedTranslation: filter.stringListProp(), @@ -857,15 +708,11 @@ export const engagementSorters = defineSorters(IEngagement, { nameProjectFirst: (query) => query .apply(matchNames) - .return( - multiPropsAsSortString('projectName', 'languageName', 'dfn', 'dln'), - ), + .return(multiPropsAsSortString('projectName', 'languageName', 'dfn', 'dln')), nameProjectLast: (query) => query .apply(matchNames) - .return( - multiPropsAsSortString('languageName', 'dfn', 'dln', 'projectName'), - ), + .return(multiPropsAsSortString('languageName', 'dfn', 'dln', 'projectName')), sensitivity: (query) => query .match([node('project'), relation('out', '', 'engagement'), node('node')]) @@ -935,11 +782,7 @@ export const engagementSorters = defineSorters(IEngagement, { const matchNames = (query: Query) => query - .match([ - node('project'), - relation('out', '', 'name', ACTIVE), - node('projectName'), - ]) + .match([node('project'), relation('out', '', 'name', ACTIVE), node('projectName')]) .optionalMatch([ node('node'), relation('out', '', 'language'), @@ -949,16 +792,8 @@ const matchNames = (query: Query) => ]) .optionalMatch([ [node('node'), relation('out', '', 'intern'), node('intern', 'User')], - [ - node('intern'), - relation('out', '', 'displayFirstName', ACTIVE), - node('dfn'), - ], - [ - node('intern'), - relation('out', '', 'displayLastName', ACTIVE), - node('dln'), - ], + [node('intern'), relation('out', '', 'displayFirstName', ACTIVE), node('dfn')], + [node('intern'), relation('out', '', 'displayLastName', ACTIVE), node('dln')], ]); const NameIndex = FullTextIndex({ diff --git a/src/components/engagement/engagement.resolver.ts b/src/components/engagement/engagement.resolver.ts index 239f5bf2be..d405246618 100644 --- a/src/components/engagement/engagement.resolver.ts +++ b/src/components/engagement/engagement.resolver.ts @@ -1,17 +1,5 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; -import { - InvalidIdForTypeException, - ListArg, - mapSecuredValue, - SecuredDateRange, -} from '~/common'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { InvalidIdForTypeException, ListArg, mapSecuredValue, SecuredDateRange } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { CeremonyLoader } from '../ceremony'; import { SecuredCeremony } from '../ceremony/dto'; @@ -96,9 +84,7 @@ export class EngagementResolver { @Parent() engagement: Engagement, @Loader(CeremonyLoader) ceremonies: LoaderOf, ): Promise { - return await mapSecuredValue(engagement.ceremony, ({ id }) => - ceremonies.load(id), - ); + return await mapSecuredValue(engagement.ceremony, ({ id }) => ceremonies.load(id)); } @ResolveField() @@ -108,10 +94,7 @@ export class EngagementResolver { @ResolveField() dateRangeOverride(@Parent() engagement: Engagement): SecuredDateRange { - return SecuredDateRange.fromPair( - engagement.startDateOverride, - engagement.endDateOverride, - ); + return SecuredDateRange.fromPair(engagement.startDateOverride, engagement.endDateOverride); } @Mutation(() => CreateLanguageEngagementOutput, { @@ -121,10 +104,7 @@ export class EngagementResolver { @Args('input') { engagement: input, changeset }: CreateLanguageEngagementInput, ): Promise { - const engagement = await this.service.createLanguageEngagement( - input, - changeset, - ); + const engagement = await this.service.createLanguageEngagement(input, changeset); return { engagement }; } @@ -135,10 +115,7 @@ export class EngagementResolver { @Args('input') { engagement: input, changeset }: CreateInternshipEngagementInput, ): Promise { - const engagement = await this.service.createInternshipEngagement( - input, - changeset, - ); + const engagement = await this.service.createInternshipEngagement(input, changeset); return { engagement }; } @@ -149,10 +126,7 @@ export class EngagementResolver { @Args('input') { engagement: input, changeset }: UpdateLanguageEngagementInput, ): Promise { - const engagement = await this.service.updateLanguageEngagement( - input, - changeset, - ); + const engagement = await this.service.updateLanguageEngagement(input, changeset); return { engagement }; } @@ -163,19 +137,14 @@ export class EngagementResolver { @Args('input') { engagement: input, changeset }: UpdateInternshipEngagementInput, ): Promise { - const engagement = await this.service.updateInternshipEngagement( - input, - changeset, - ); + const engagement = await this.service.updateInternshipEngagement(input, changeset); return { engagement }; } @Mutation(() => DeleteEngagementOutput, { description: 'Delete an engagement', }) - async deleteEngagement( - @Args() { id, changeset }: ChangesetIds, - ): Promise { + async deleteEngagement(@Args() { id, changeset }: ChangesetIds): Promise { await this.service.delete(id, changeset); return { success: true }; } diff --git a/src/components/engagement/engagement.rules.ts b/src/components/engagement/engagement.rules.ts index 082835cdb1..071d3a7e58 100644 --- a/src/components/engagement/engagement.rules.ts +++ b/src/components/engagement/engagement.rules.ts @@ -3,22 +3,13 @@ import { Injectable } from '@nestjs/common'; import { setOf } from '@seedcompany/common'; import { node, relation } from 'cypher-query-builder'; import { first, intersection } from 'lodash'; -import { - type ID, - Role, - ServerException, - UnauthorizedException, -} from '~/common'; +import { type ID, Role, ServerException, UnauthorizedException } from '~/common'; import { ILogger, Logger } from '~/core'; import { Identity } from '~/core/authentication'; import { DatabaseService } from '~/core/database'; import { ACTIVE, INACTIVE } from '~/core/database/query'; import { ProjectStep } from '../project/dto'; -import { - EngagementStatus, - type EngagementStatusTransition, - EngagementTransitionType, -} from './dto'; +import { EngagementStatus, type EngagementStatusTransition, EngagementTransitionType } from './dto'; interface Transition extends EngagementStatusTransition { projectStepRequirements?: ProjectStep[]; @@ -40,10 +31,7 @@ export class EngagementRules { @Logger('engagement:rules') private readonly logger: ILogger, ) {} - private async getStatusRule( - status: EngagementStatus, - id: ID, - ): Promise { + private async getStatusRule(status: EngagementStatus, id: ID): Promise { const mostRecentPreviousStatus = (steps: EngagementStatus[]) => this.getMostRecentPreviousStatus(id, steps); @@ -323,10 +311,7 @@ export class EngagementRules { const currentStatus = await this.getCurrentStatus(engagementId, changeset); // get roles that can approve the current status - const { approvers, transitions } = await this.getStatusRule( - currentStatus, - engagementId, - ); + const { approvers, transitions } = await this.getStatusRule(currentStatus, engagementId); // If current user is not an approver (based on roles) then don't allow any transitions if (session.roles.intersection(setOf(approvers)).size === 0) { @@ -334,18 +319,11 @@ export class EngagementRules { } // If transitions don't need project's step then dont fetch or filter it. - if ( - !transitions.some( - (transition) => transition.projectStepRequirements?.length, - ) - ) { + if (!transitions.some((transition) => transition.projectStepRequirements?.length)) { return transitions; } - const currentStep = await this.getCurrentProjectStep( - engagementId, - changeset, - ); + const currentStep = await this.getCurrentProjectStep(engagementId, changeset); const availableTransitionsAccordingToProject = transitions.filter( (transition) => !transition.projectStepRequirements?.length || @@ -359,23 +337,14 @@ export class EngagementRules { return roles.intersection(rolesThatCanBypassWorkflow).size > 0; } - async verifyStatusChange( - engagementId: ID, - nextStatus: EngagementStatus, - changeset?: ID, - ) { + async verifyStatusChange(engagementId: ID, nextStatus: EngagementStatus, changeset?: ID) { if (this.canBypassWorkflow()) { return; } - const transitions = await this.getAvailableTransitions( - engagementId, - changeset, - ); + const transitions = await this.getAvailableTransitions(engagementId, changeset); - const validNextStatus = transitions.some( - (transition) => transition.to === nextStatus, - ); + const validNextStatus = transitions.some((transition) => transition.to === nextStatus); if (!validNextStatus) { throw new UnauthorizedException( `One or more engagements cannot be changed to ${ @@ -503,9 +472,7 @@ export class EngagementRules { .asResult<{ status: EngagementStatus[] }>() .first(); if (!result) { - throw new ServerException( - "Failed to determine engagement's previous status", - ); + throw new ServerException("Failed to determine engagement's previous status"); } return result.status; } diff --git a/src/components/engagement/engagement.service.ts b/src/components/engagement/engagement.service.ts index 606b966e8f..1dca61eb0b 100644 --- a/src/components/engagement/engagement.service.ts +++ b/src/components/engagement/engagement.service.ts @@ -12,14 +12,7 @@ import { type UnsecuredDto, viewOfChangeset, } from '~/common'; -import { - ConfigService, - HandleIdLookup, - IEventBus, - ILogger, - Logger, - ResourceLoader, -} from '~/core'; +import { ConfigService, HandleIdLookup, IEventBus, ILogger, Logger, ResourceLoader } from '~/core'; import { type AnyChangesOf } from '~/core/database/changes'; import { Privileges } from '../authorization'; import { CeremonyService } from '../ceremony'; @@ -73,10 +66,7 @@ export class EngagementService { this.verifyCreationStatus(input.status); EngagementDateRangeException.throwIfInvalid(input); - const engagement = await this.repo.createLanguageEngagement( - input, - changeset, - ); + const engagement = await this.repo.createLanguageEngagement(input, changeset); RequiredWhen.verify(LanguageEngagement, engagement); @@ -94,10 +84,7 @@ export class EngagementService { this.verifyCreationStatus(input.status); EngagementDateRangeException.throwIfInvalid(input); - const engagement = await this.repo.createInternshipEngagement( - input, - changeset, - ); + const engagement = await this.repo.createInternshipEngagement(input, changeset); RequiredWhen.verify(InternshipEngagement, engagement); @@ -131,10 +118,7 @@ export class EngagementService { } @HandleIdLookup([LanguageEngagement, InternshipEngagement]) - async readOne( - id: ID, - view?: ObjectView, - ): Promise { + async readOne(id: ID, view?: ObjectView): Promise { const dto = await this.repo.readOne(id, view); return this.secure(dto); } @@ -155,20 +139,13 @@ export class EngagementService { ): Promise { const view: ObjectView = viewOfChangeset(changeset); - const previous = (await this.repo.readOne( - input.id, - view, - )) as UnsecuredDto; + const previous = (await this.repo.readOne(input.id, view)) as UnsecuredDto; const object = this.secure(previous); const { methodology, ...maybeChanges } = input; const changes = this.repo.getActualLanguageChanges(object, maybeChanges); if (changes.status) { - await this.engagementRules.verifyStatusChange( - input.id, - changes.status, - changeset, - ); + await this.engagementRules.verifyStatusChange(input.id, changes.status, changeset); } this.privileges.for(LanguageEngagement, object).verifyChanges(changes); EngagementDateRangeException.throwIfInvalid(previous, changes); @@ -183,10 +160,7 @@ export class EngagementService { const prevMissing = RequiredWhen.calc(LanguageEngagement, previous); const nowMissing = RequiredWhen.calc(LanguageEngagement, updated); - if ( - nowMissing && - (!prevMissing || nowMissing.missing.length >= prevMissing.missing.length) - ) { + if (nowMissing && (!prevMissing || nowMissing.missing.length >= prevMissing.missing.length)) { throw nowMissing; } @@ -216,28 +190,18 @@ export class EngagementService { const changes = this.repo.getActualInternshipChanges(object, input); if (changes.status) { - await this.engagementRules.verifyStatusChange( - input.id, - changes.status, - changeset, - ); + await this.engagementRules.verifyStatusChange(input.id, changes.status, changeset); } this.privileges .for(InternshipEngagement, object) .verifyChanges(changes, { pathPrefix: 'engagement' }); EngagementDateRangeException.throwIfInvalid(previous, changes); - const updated = await this.repo.updateInternship( - { id: object.id, ...changes }, - changeset, - ); + const updated = await this.repo.updateInternship({ id: object.id, ...changes }, changeset); const prevMissing = RequiredWhen.calc(InternshipEngagement, previous); const nowMissing = RequiredWhen.calc(InternshipEngagement, updated); - if ( - nowMissing && - (!prevMissing || nowMissing.missing.length >= prevMissing.missing.length) - ) { + if (nowMissing && (!prevMissing || nowMissing.missing.length >= prevMissing.missing.length)) { throw nowMissing; } @@ -261,18 +225,13 @@ export class EngagementService { async delete(id: ID, changeset?: ID): Promise { const object = await this.readOne(id); - this.privileges - .for(resolveEngagementType(object), object) - .verifyCan('delete'); + this.privileges.for(resolveEngagementType(object), object).verifyCan('delete'); await this.eventBus.publish(new EngagementWillDeleteEvent(object)); await this.repo.deleteNode(object, { changeset }); } - async list( - input: EngagementListInput, - view?: ObjectView, - ): Promise { + async list(input: EngagementListInput, view?: ObjectView): Promise { // -- don't have to check if canList because all roles can see at least on prop of it // if that ever changes, create a limitedScope and add to the list function. const results = await this.repo.list(input, view?.changeset); @@ -291,9 +250,7 @@ export class EngagementService { engagement: LanguageEngagement, input: ProductListInput, ): Promise { - const privs = this.privileges - .for(LanguageEngagement, engagement) - .forEdge('product'); + const privs = this.privileges.for(LanguageEngagement, engagement).forEdge('product'); if (!privs.can('read')) { return SecuredList.Redacted; @@ -322,9 +279,7 @@ export class EngagementService { class EngagementDateRangeException extends RangeException { static throwIfInvalid( - current: Partial< - Pick, 'startDateOverride' | 'endDateOverride'> - >, + current: Partial, 'startDateOverride' | 'endDateOverride'>>, changes: AnyChangesOf = {}, ) { const start = @@ -332,9 +287,7 @@ class EngagementDateRangeException extends RangeException { ? changes.startDateOverride : current.startDateOverride; const end = - changes.endDateOverride !== undefined - ? changes.endDateOverride - : current.endDateOverride; + changes.endDateOverride !== undefined ? changes.endDateOverride : current.endDateOverride; if (start && end && start > end) { const field = changes.endDateOverride !== undefined diff --git a/src/components/engagement/handlers/apply-finalized-changeset-to-engagement.handler.ts b/src/components/engagement/handlers/apply-finalized-changeset-to-engagement.handler.ts index f8794205f4..b34ed32ee8 100644 --- a/src/components/engagement/handlers/apply-finalized-changeset-to-engagement.handler.ts +++ b/src/components/engagement/handlers/apply-finalized-changeset-to-engagement.handler.ts @@ -14,9 +14,7 @@ import { EngagementService } from '../engagement.service'; type SubscribedEvent = ChangesetFinalizingEvent; @EventsHandler(ChangesetFinalizingEvent) -export class ApplyFinalizedChangesetToEngagement - implements IEventHandler -{ +export class ApplyFinalizedChangesetToEngagement implements IEventHandler { constructor( private readonly db: DatabaseService, private readonly engagementService: EngagementService, @@ -44,16 +42,10 @@ export class ApplyFinalizedChangesetToEngagement relation('out', 'engagementRel', 'engagement', ACTIVE), node('node', 'Engagement'), ]) - .apply( - changeset.applied - ? commitChangesetProps() - : rejectChangesetProps(), - ) + .apply(changeset.applied ? commitChangesetProps() : rejectChangesetProps()) .return('node.id as engagementId'), ) - .return<{ engagementIds: ID[] }>( - 'collect(engagementId) as engagementIds', - ) + .return<{ engagementIds: ID[] }>('collect(engagementId) as engagementIds') .first(); const newResult = await this.db @@ -78,9 +70,7 @@ export class ApplyFinalizedChangesetToEngagement }) .return('node.id as engagementId'), ) - .return<{ engagementIds: ID[] }>( - 'collect(engagementId) as engagementIds', - ) + .return<{ engagementIds: ID[] }>('collect(engagementId) as engagementIds') .first(); /** @@ -105,9 +95,7 @@ export class ApplyFinalizedChangesetToEngagement relation('out', '', 'language', ACTIVE), node('node', 'Language'), ]) - .apply( - changeset.id ? commitChangesetProps() : rejectChangesetProps(), - ) + .apply(changeset.id ? commitChangesetProps() : rejectChangesetProps()) .return('1 as one'), ) .return('project') @@ -124,10 +112,7 @@ export class ApplyFinalizedChangesetToEngagement ]); await this.triggerUpdateEvent(allEngagementIds); } catch (exception) { - throw new ServerException( - 'Failed to apply changeset to project', - exception, - ); + throw new ServerException('Failed to apply changeset to project', exception); } } diff --git a/src/components/engagement/handlers/set-initial-end-date.handler.ts b/src/components/engagement/handlers/set-initial-end-date.handler.ts index c4a29ef9b7..49cb296b45 100644 --- a/src/components/engagement/handlers/set-initial-end-date.handler.ts +++ b/src/components/engagement/handlers/set-initial-end-date.handler.ts @@ -26,9 +26,7 @@ export class SetInitialEndDate implements IEventHandler { ) { return; } - if ( - engagement.initialEndDate?.toMillis() === engagement.endDate?.toMillis() - ) { + if (engagement.initialEndDate?.toMillis() === engagement.endDate?.toMillis()) { return; } @@ -36,9 +34,7 @@ export class SetInitialEndDate implements IEventHandler { const initialEndDate = engagement.endDate; const type = - LanguageEngagement.resolve(engagement) === LanguageEngagement - ? 'Language' - : 'Internship'; + LanguageEngagement.resolve(engagement) === LanguageEngagement ? 'Language' : 'Internship'; await this.engagementRepo[`update${type}`]( { id: engagement.id, @@ -58,10 +54,7 @@ export class SetInitialEndDate implements IEventHandler { event.engagement = updatedEngagement; } } catch (exception) { - throw new ServerException( - 'Could not set initial end date on engagement', - exception, - ); + throw new ServerException('Could not set initial end date on engagement', exception); } } } diff --git a/src/components/engagement/handlers/set-last-status-date.handler.ts b/src/components/engagement/handlers/set-last-status-date.handler.ts index eb2e1f5056..af6c83e48b 100644 --- a/src/components/engagement/handlers/set-last-status-date.handler.ts +++ b/src/components/engagement/handlers/set-last-status-date.handler.ts @@ -5,9 +5,7 @@ import { EngagementStatus, IEngagement } from '../dto'; import { EngagementUpdatedEvent } from '../events'; @EventsHandler(EngagementUpdatedEvent) -export class SetLastStatusDate - implements IEventHandler -{ +export class SetLastStatusDate implements IEventHandler { constructor(private readonly db: DatabaseService) {} async handle(event: EngagementUpdatedEvent) { diff --git a/src/components/engagement/handlers/update-engagement-status.handler.ts b/src/components/engagement/handlers/update-engagement-status.handler.ts index 21a6b1c12d..6c997e54de 100644 --- a/src/components/engagement/handlers/update-engagement-status.handler.ts +++ b/src/components/engagement/handlers/update-engagement-status.handler.ts @@ -72,10 +72,7 @@ const changes: Change[] = [ type Change = RequireAtLeastOne<{ from: Condition; to: Condition }> & { newStatus: EngagementStatus; }; -type Condition = MergeExclusive< - { status: ProjectStatus }, - { step: ProjectStep } ->; +type Condition = MergeExclusive<{ status: ProjectStatus }, { step: ProjectStep }>; type ProjectState = Pick, 'status' | 'step'>; const changeMatcher = (previousStep: ProjectStep, updatedStep: ProjectStep) => { @@ -89,9 +86,7 @@ const changeMatcher = (previousStep: ProjectStep, updatedStep: ProjectStep) => { }; return ({ from, to }: Change) => { const toMatches = to ? matches(to, updated) : !matches(from!, updated); - const fromMatches = from - ? matches(from, previous) - : !matches(to!, previous); + const fromMatches = from ? matches(from, previous) : !matches(to!, previous); return toMatches && fromMatches; }; }; @@ -99,22 +94,14 @@ const matches = (cond: Condition, p: ProjectState) => cond.step ? cond.step === p.step : cond.status === p.status; @EventsHandler(ProjectTransitionedEvent) -export class UpdateEngagementStatusHandler - implements IEventHandler -{ +export class UpdateEngagementStatusHandler implements IEventHandler { constructor( private readonly repo: EngagementRepository, private readonly engagementService: EngagementService, ) {} - async handle({ - project, - previousStep, - workflowEvent, - }: ProjectTransitionedEvent) { - const engagementStatus = changes.find( - changeMatcher(previousStep, workflowEvent.to), - )?.newStatus; + async handle({ project, previousStep, workflowEvent }: ProjectTransitionedEvent) { + const engagementStatus = changes.find(changeMatcher(previousStep, workflowEvent.to))?.newStatus; if (!engagementStatus) return; const engagementIds = await this.repo.getOngoingEngagementIds(project.id, [ diff --git a/src/components/engagement/internship-engagement.resolver.ts b/src/components/engagement/internship-engagement.resolver.ts index 882dedf00d..d4997c931a 100644 --- a/src/components/engagement/internship-engagement.resolver.ts +++ b/src/components/engagement/internship-engagement.resolver.ts @@ -40,8 +40,6 @@ export class InternshipEngagementResolver { @Parent() engagement: InternshipEngagement, @Loader(LocationLoader) locations: LoaderOf, ): Promise { - return await mapSecuredValue(engagement.countryOfOrigin, ({ id }) => - locations.load(id), - ); + return await mapSecuredValue(engagement.countryOfOrigin, ({ id }) => locations.load(id)); } } diff --git a/src/components/engagement/internship-position.resolver.ts b/src/components/engagement/internship-position.resolver.ts index e4e894ad43..d9ba1be00a 100644 --- a/src/components/engagement/internship-position.resolver.ts +++ b/src/components/engagement/internship-position.resolver.ts @@ -1,10 +1,4 @@ -import { - Field, - ObjectType, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Field, ObjectType, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { InternshipDomain, InternshipPosition, @@ -29,8 +23,7 @@ class InternshipPositionOptions { @Resolver(SecuredInternPosition) export class InternshipPositionResolver { @ResolveField(() => [InternshipPositionOptions], { - description: - 'The available position options for the internship engagement.', + description: 'The available position options for the internship engagement.', }) options(): InternshipPositionOptions[] { return InternshipPosition.entries @@ -46,9 +39,7 @@ export class InternshipPositionResolver { nullable: true, description: 'The InternshipDomain based on the currently selected `value`', }) - domain( - @Parent() { value: position }: SecuredInternPosition, - ): InternshipDomain | null { + domain(@Parent() { value: position }: SecuredInternPosition): InternshipDomain | null { if (!position) return null; const { domain } = InternshipPosition.entry(position); return domain ?? null; @@ -56,12 +47,9 @@ export class InternshipPositionResolver { @ResolveField(() => InternshipProgram, { nullable: true, - description: - 'The InternshipProgram based on the currently selected `value`', + description: 'The InternshipProgram based on the currently selected `value`', }) - program( - @Parent() { value: position }: SecuredInternPosition, - ): InternshipProgram | null { + program(@Parent() { value: position }: SecuredInternPosition): InternshipProgram | null { if (!position) return null; const { program } = InternshipPosition.entry(position); return program ?? null; diff --git a/src/components/ethno-art/dto/ethno-art.dto.ts b/src/components/ethno-art/dto/ethno-art.dto.ts index ec15d743ce..6365bd4f29 100644 --- a/src/components/ethno-art/dto/ethno-art.dto.ts +++ b/src/components/ethno-art/dto/ethno-art.dto.ts @@ -2,10 +2,7 @@ import { ObjectType } from '@nestjs/graphql'; import { DbUnique, NameField, Resource, SecuredString } from '~/common'; import { e } from '~/core/gel'; import { RegisterResource } from '~/core/resources'; -import { - Producible, - ProducibleTypeEntries, -} from '../../product/dto/producible.dto'; +import { Producible, ProducibleTypeEntries } from '../../product/dto/producible.dto'; ProducibleTypeEntries.add('EthnoArt'); declare module '../../product/dto/producible.dto' { diff --git a/src/components/ethno-art/ethno-art.gel.repository.ts b/src/components/ethno-art/ethno-art.gel.repository.ts index 5d42f196bb..8f5e263764 100644 --- a/src/components/ethno-art/ethno-art.gel.repository.ts +++ b/src/components/ethno-art/ethno-art.gel.repository.ts @@ -17,26 +17,20 @@ export class EthnoArtGelRepository implements PublicOf { async create(input: CreateEthnoArt): Promise> { - const query = e.params( - { name: e.str, scripture: e.optional(scripture.type) }, - ($) => { - const created = e.insert(this.resource.db, { - name: $.name, - scripture: scripture.insert($.scripture), - }); - return e.select(created, this.hydrate); - }, - ); + const query = e.params({ name: e.str, scripture: e.optional(scripture.type) }, ($) => { + const created = e.insert(this.resource.db, { + name: $.name, + scripture: scripture.insert($.scripture), + }); + return e.select(created, this.hydrate); + }); return await this.db.run(query, { name: input.name, scripture: scripture.valueOptional(input.scriptureReferences), }); } - async update({ - id, - ...changes - }: UpdateEthnoArt): Promise> { + async update({ id, ...changes }: UpdateEthnoArt): Promise> { const query = e.params({ scripture: e.optional(scripture.type) }, ($) => { const ethnoArt = e.cast(e.EthnoArt, e.uuid(id)); const updated = e.update(ethnoArt, () => ({ diff --git a/src/components/ethno-art/ethno-art.loader.ts b/src/components/ethno-art/ethno-art.loader.ts index a67861379e..9254b12bc4 100644 --- a/src/components/ethno-art/ethno-art.loader.ts +++ b/src/components/ethno-art/ethno-art.loader.ts @@ -4,9 +4,7 @@ import { EthnoArt } from './dto'; import { EthnoArtService } from './ethno-art.service'; @LoaderFactory(() => EthnoArt) -export class EthnoArtLoader - implements DataLoaderStrategy> -{ +export class EthnoArtLoader implements DataLoaderStrategy> { constructor(private readonly ethnoArt: EthnoArtService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/ethno-art/ethno-art.repository.ts b/src/components/ethno-art/ethno-art.repository.ts index 44a386fac3..1dcf4cc34c 100644 --- a/src/components/ethno-art/ethno-art.repository.ts +++ b/src/components/ethno-art/ethno-art.repository.ts @@ -10,23 +10,9 @@ import { type UnsecuredDto, } from '~/common'; import { type DbTypeOf, DtoRepository } from '~/core/database'; -import { - createNode, - matchProps, - merge, - paginate, - sorting, -} from '~/core/database/query'; -import { - ScriptureReferenceRepository, - ScriptureReferenceService, -} from '../scripture'; -import { - type CreateEthnoArt, - EthnoArt, - type EthnoArtListInput, - type UpdateEthnoArt, -} from './dto'; +import { createNode, matchProps, merge, paginate, sorting } from '~/core/database/query'; +import { ScriptureReferenceRepository, ScriptureReferenceService } from '../scripture'; +import { type CreateEthnoArt, EthnoArt, type EthnoArtListInput, type UpdateEthnoArt } from './dto'; @Injectable() export class EthnoArtRepository extends DtoRepository(EthnoArt) { @@ -39,10 +25,7 @@ export class EthnoArtRepository extends DtoRepository(EthnoArt) { async create(input: CreateEthnoArt) { if (!(await this.isUnique(input.name))) { - throw new DuplicateException( - 'ethnoArt.name', - 'Ethno art with this name already exists', - ); + throw new DuplicateException('ethnoArt.name', 'Ethno art with this name already exists'); } const initialProps = { @@ -59,15 +42,10 @@ export class EthnoArtRepository extends DtoRepository(EthnoArt) { throw new CreationFailed(EthnoArt); } - await this.scriptureRefsService.create( - result.id, - input.scriptureReferences, - ); + await this.scriptureRefsService.create(result.id, input.scriptureReferences); return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(EthnoArt) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(EthnoArt) : e; }); } @@ -84,15 +62,11 @@ export class EthnoArtRepository extends DtoRepository(EthnoArt) { return (await super.readOne(id)) as UnsecuredDto; } - async readMany( - ids: readonly ID[], - ): Promise>> { + async readMany(ids: readonly ID[]): Promise>> { const items = await super.readMany(ids); return items.map((r) => ({ ...r, - scriptureReferences: this.scriptureRefsService.parseList( - r.scriptureReferences, - ), + scriptureReferences: this.scriptureRefsService.parseList(r.scriptureReferences), })); } @@ -110,9 +84,7 @@ export class EthnoArtRepository extends DtoRepository(EthnoArt) { ...result!, items: result!.items.map((r) => ({ ...r, - scriptureReferences: this.scriptureRefsService.parseList( - r.scriptureReferences, - ), + scriptureReferences: this.scriptureRefsService.parseList(r.scriptureReferences), })), }; } diff --git a/src/components/ethno-art/ethno-art.service.ts b/src/components/ethno-art/ethno-art.service.ts index e2e9329700..3558de174a 100644 --- a/src/components/ethno-art/ethno-art.service.ts +++ b/src/components/ethno-art/ethno-art.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - type ID, - type ObjectView, - ServerException, - type UnsecuredDto, -} from '~/common'; +import { type ID, type ObjectView, ServerException, type UnsecuredDto } from '~/common'; import { HandleIdLookup } from '~/core'; import { ifDiff } from '~/core/database/changes'; import { Privileges } from '../authorization'; @@ -20,10 +15,7 @@ import { EthnoArtRepository } from './ethno-art.repository'; @Injectable() export class EthnoArtService { - constructor( - private readonly privileges: Privileges, - private readonly repo: EthnoArtRepository, - ) {} + constructor(private readonly privileges: Privileges, private readonly repo: EthnoArtRepository) {} async create(input: CreateEthnoArt): Promise { const dto = await this.repo.create(input); diff --git a/src/components/field-region/dto/create-field-region.dto.ts b/src/components/field-region/dto/create-field-region.dto.ts index 924908934f..8f65487903 100644 --- a/src/components/field-region/dto/create-field-region.dto.ts +++ b/src/components/field-region/dto/create-field-region.dto.ts @@ -10,8 +10,7 @@ export abstract class CreateFieldRegion { readonly name: string; @IdField({ - description: - 'The field zone ID that the field region will be associated with', + description: 'The field zone ID that the field region will be associated with', }) readonly fieldZoneId: ID; diff --git a/src/components/field-region/dto/list-field-region.dto.ts b/src/components/field-region/dto/list-field-region.dto.ts index f531132077..2cdf9a119d 100644 --- a/src/components/field-region/dto/list-field-region.dto.ts +++ b/src/components/field-region/dto/list-field-region.dto.ts @@ -14,9 +14,7 @@ export abstract class FieldRegionFilters { } @InputType() -export class FieldRegionListInput extends SortablePaginationInput< - keyof FieldRegion ->({ +export class FieldRegionListInput extends SortablePaginationInput({ defaultSort: 'name', }) { @FilterField(() => FieldRegionFilters, { internal: true }) diff --git a/src/components/field-region/field-region.loader.ts b/src/components/field-region/field-region.loader.ts index 30d93c8356..460a3f2ad6 100644 --- a/src/components/field-region/field-region.loader.ts +++ b/src/components/field-region/field-region.loader.ts @@ -4,9 +4,7 @@ import { FieldRegion } from './dto'; import { FieldRegionService } from './field-region.service'; @LoaderFactory(() => FieldRegion) -export class FieldRegionLoader - implements DataLoaderStrategy> -{ +export class FieldRegionLoader implements DataLoaderStrategy> { constructor(private readonly fieldRegions: FieldRegionService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/field-region/field-region.module.ts b/src/components/field-region/field-region.module.ts index 412a739f33..41478d8f63 100644 --- a/src/components/field-region/field-region.module.ts +++ b/src/components/field-region/field-region.module.ts @@ -11,11 +11,7 @@ import { FieldRegionService } from './field-region.service'; import { RestrictRegionDirectorRemovalHandler } from './handlers/restrict-region-director-removal.handler'; @Module({ - imports: [ - forwardRef(() => AuthorizationModule), - FieldZoneModule, - forwardRef(() => UserModule), - ], + imports: [forwardRef(() => AuthorizationModule), FieldZoneModule, forwardRef(() => UserModule)], providers: [ FieldRegionResolver, FieldRegionService, diff --git a/src/components/field-region/field-region.repository.ts b/src/components/field-region/field-region.repository.ts index dbc1285764..28b0e386a6 100644 --- a/src/components/field-region/field-region.repository.ts +++ b/src/components/field-region/field-region.repository.ts @@ -59,9 +59,7 @@ export class FieldRegionRepository extends DtoRepository(FieldRegion) { } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(FieldRegion) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(FieldRegion) : e; }); } diff --git a/src/components/field-region/field-region.resolver.ts b/src/components/field-region/field-region.resolver.ts index 2f846d469d..d55342de68 100644 --- a/src/components/field-region/field-region.resolver.ts +++ b/src/components/field-region/field-region.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg, ListArg, mapSecuredValue } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { FieldZoneLoader } from '../field-zone'; @@ -56,9 +49,7 @@ export class FieldRegionResolver { @Parent() fieldRegion: FieldRegion, @Loader(UserLoader) users: LoaderOf, ): Promise { - return await mapSecuredValue(fieldRegion.director, ({ id }) => - users.load(id), - ); + return await mapSecuredValue(fieldRegion.director, ({ id }) => users.load(id)); } @ResolveField(() => SecuredFieldZone) @@ -66,9 +57,7 @@ export class FieldRegionResolver { @Parent() fieldRegion: FieldRegion, @Loader(FieldZoneLoader) fieldZones: LoaderOf, ): Promise { - return await mapSecuredValue(fieldRegion.fieldZone, ({ id }) => - fieldZones.load(id), - ); + return await mapSecuredValue(fieldRegion.fieldZone, ({ id }) => fieldZones.load(id)); } @Mutation(() => CreateFieldRegionOutput, { diff --git a/src/components/field-zone/dto/list-field-zone.dto.ts b/src/components/field-zone/dto/list-field-zone.dto.ts index 5201ef0588..5442a0cd9f 100644 --- a/src/components/field-zone/dto/list-field-zone.dto.ts +++ b/src/components/field-zone/dto/list-field-zone.dto.ts @@ -14,9 +14,7 @@ export abstract class FieldZoneFilters { } @InputType() -export class FieldZoneListInput extends SortablePaginationInput< - keyof FieldZone ->({ +export class FieldZoneListInput extends SortablePaginationInput({ defaultSort: 'name', }) { @FilterField(() => FieldZoneFilters, { internal: true }) diff --git a/src/components/field-zone/field-zone.loader.ts b/src/components/field-zone/field-zone.loader.ts index f8eec1c1a9..fd9681df81 100644 --- a/src/components/field-zone/field-zone.loader.ts +++ b/src/components/field-zone/field-zone.loader.ts @@ -4,9 +4,7 @@ import { FieldZone } from './dto'; import { FieldZoneService } from './field-zone.service'; @LoaderFactory(() => FieldZone) -export class FieldZoneLoader - implements DataLoaderStrategy> -{ +export class FieldZoneLoader implements DataLoaderStrategy> { constructor(private readonly fieldZones: FieldZoneService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/field-zone/field-zone.module.ts b/src/components/field-zone/field-zone.module.ts index cc000c89f8..553618f7ff 100644 --- a/src/components/field-zone/field-zone.module.ts +++ b/src/components/field-zone/field-zone.module.ts @@ -10,10 +10,7 @@ import { FieldZoneService } from './field-zone.service'; import { RestrictZoneDirectorRemovalHandler } from './handlers/restrict-zone-director-removal.handler'; @Module({ - imports: [ - forwardRef(() => AuthorizationModule), - forwardRef(() => UserModule), - ], + imports: [forwardRef(() => AuthorizationModule), forwardRef(() => UserModule)], providers: [ FieldZoneResolver, FieldZoneService, diff --git a/src/components/field-zone/field-zone.repository.ts b/src/components/field-zone/field-zone.repository.ts index ca5ad476ba..712cd6e311 100644 --- a/src/components/field-zone/field-zone.repository.ts +++ b/src/components/field-zone/field-zone.repository.ts @@ -31,10 +31,7 @@ import { export class FieldZoneRepository extends DtoRepository(FieldZone) { async create(input: CreateFieldZone) { if (!(await this.isUnique(input.name))) { - throw new DuplicateException( - 'fieldZone.name', - 'FieldZone with this name already exists.', - ); + throw new DuplicateException('fieldZone.name', 'FieldZone with this name already exists.'); } const initialProps = { @@ -59,9 +56,7 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) { } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(FieldZone) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(FieldZone) : e; }); } @@ -101,11 +96,7 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) { .with('fieldZone') .limit(1) .match([node('director', 'User', { id: directorId })]) - .optionalMatch([ - node('fieldZone'), - relation('out', 'oldRel', 'director', ACTIVE), - node(''), - ]) + .optionalMatch([node('fieldZone'), relation('out', 'oldRel', 'director', ACTIVE), node('')]) .setValues({ 'oldRel.active': false }) .with('fieldZone, director') .limit(1) diff --git a/src/components/field-zone/field-zone.resolver.ts b/src/components/field-zone/field-zone.resolver.ts index 13ad6a64ba..2832eadade 100644 --- a/src/components/field-zone/field-zone.resolver.ts +++ b/src/components/field-zone/field-zone.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg, ListArg, mapSecuredValue } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { UserLoader } from '../user'; @@ -54,9 +47,7 @@ export class FieldZoneResolver { @Parent() fieldZone: FieldZone, @Loader(UserLoader) users: LoaderOf, ): Promise { - return await mapSecuredValue(fieldZone.director, ({ id }) => - users.load(id), - ); + return await mapSecuredValue(fieldZone.director, ({ id }) => users.load(id)); } @Mutation(() => CreateFieldZoneOutput, { diff --git a/src/components/file/bucket/composite-bucket.ts b/src/components/file/bucket/composite-bucket.ts index bba67b7617..6e5c5046ee 100644 --- a/src/components/file/bucket/composite-bucket.ts +++ b/src/components/file/bucket/composite-bucket.ts @@ -65,23 +65,17 @@ export class CompositeBucket extends FileBucket { } async putObject(input: PutObjectInput): Promise { - await this.doAndThrowAllErrors( - this.writableSources.map((bucket) => bucket.putObject(input)), - ); + await this.doAndThrowAllErrors(this.writableSources.map((bucket) => bucket.putObject(input))); } async copyObject(oldKey: string, newKey: string): Promise { const [existing] = await this.selectSources(oldKey, this.writableSources); - await this.doAndThrowAllErrors( - existing.map(([bucket]) => bucket.copyObject(oldKey, newKey)), - ); + await this.doAndThrowAllErrors(existing.map(([bucket]) => bucket.copyObject(oldKey, newKey))); } async deleteObject(key: string): Promise { const [existing] = await this.selectSources(key, this.writableSources); - await this.doAndThrowAllErrors( - existing.map(([bucket]) => bucket.deleteObject(key)), - ); + await this.doAndThrowAllErrors(existing.map(([bucket]) => bucket.deleteObject(key))); } private get writableSources() { @@ -106,10 +100,7 @@ export class CompositeBucket extends FileBucket { const [success] = await this.selectSources(key); return success[0]; } catch (e) { - if ( - e instanceof AggregateError && - e.errors.every((e) => e instanceof NotFoundException) - ) { + if (e instanceof AggregateError && e.errors.every((e) => e instanceof NotFoundException)) { throw e.errors[0]; } throw e; @@ -125,9 +116,7 @@ export class CompositeBucket extends FileBucket { const success = results.flatMap((result) => result.status === 'fulfilled' ? [result.value] : [], ); - const errors = results.flatMap((result) => - result.status === 'rejected' ? result.reason : [], - ); + const errors = results.flatMap((result) => (result.status === 'rejected' ? result.reason : [])); if (success.length === 0) { throw new AggregateError(errors, 'Key does not exist in any source'); } diff --git a/src/components/file/bucket/file-bucket.ts b/src/components/file/bucket/file-bucket.ts index 4b20ee7447..86e659dc2d 100644 --- a/src/components/file/bucket/file-bucket.ts +++ b/src/components/file/bucket/file-bucket.ts @@ -9,27 +9,14 @@ import { type MaybeAsync } from '@seedcompany/common'; import { type Command } from '@smithy/smithy-client'; import { type NodeJsRuntimeStreamingBlobPayloadInputTypes } from '@smithy/types/dist-types/streaming-payload/streaming-blob-payload-input-types'; import { type Readable } from 'stream'; -import type { - Except, - LiteralUnion, - Merge, - SetNonNullable, - SetRequired, -} from 'type-fest'; -import { - type DurationIn, - InputException, - type InputExceptionArgs, -} from '~/common'; +import type { Except, LiteralUnion, Merge, SetNonNullable, SetRequired } from 'type-fest'; +import { type DurationIn, InputException, type InputExceptionArgs } from '~/common'; // Limit body to only `Readable` which is always the case for Nodejs execution. export type GetObjectOutput = Merge; export type PutObjectInput = Merge< - SetNonNullable< - SetRequired, 'ContentType'>, - 'Key' - >, + SetNonNullable, 'ContentType'>, 'Key'>, { Body: NodeJsRuntimeStreamingBlobPayloadInputTypes; } diff --git a/src/components/file/bucket/filesystem-bucket.ts b/src/components/file/bucket/filesystem-bucket.ts index f5b7220baa..41803d4da2 100644 --- a/src/components/file/bucket/filesystem-bucket.ts +++ b/src/components/file/bucket/filesystem-bucket.ts @@ -1,11 +1,7 @@ import { promises as fs } from 'fs'; import { dirname, join } from 'path'; import { NotFoundException } from '~/common'; -import { - type FakeAwsFile, - LocalBucket, - type LocalBucketOptions, -} from './local-bucket'; +import { type FakeAwsFile, LocalBucket, type LocalBucketOptions } from './local-bucket'; export interface FilesystemBucketOptions extends LocalBucketOptions { rootDirectory: string; @@ -54,10 +50,7 @@ export class FilesystemBucket extends LocalBucket { async copyObject(oldKey: string, newKey: string) { await fs.copyFile(this.getPath(oldKey), this.getPath(newKey)); - await fs.copyFile( - this.getPath(oldKey) + '.info', - this.getPath(newKey) + '.info', - ); + await fs.copyFile(this.getPath(oldKey) + '.info', this.getPath(newKey) + '.info'); } async deleteObject(key: string) { diff --git a/src/components/file/bucket/local-bucket.ts b/src/components/file/bucket/local-bucket.ts index dc18e9ea96..68ec8d30c7 100644 --- a/src/components/file/bucket/local-bucket.ts +++ b/src/components/file/bucket/local-bucket.ts @@ -1,7 +1,4 @@ -import { - GetObjectCommand as GetObject, - PutObjectCommand as PutObject, -} from '@aws-sdk/client-s3'; +import { GetObjectCommand as GetObject, PutObjectCommand as PutObject } from '@aws-sdk/client-s3'; import { type Type } from '@nestjs/common'; import { bufferFromStream } from '@seedcompany/common'; import { type Command } from '@smithy/smithy-client'; @@ -72,9 +69,7 @@ export abstract class LocalBucket< async putObject(input: PutObjectInput) { const buffer = - input.Body instanceof Readable - ? await bufferFromStream(input.Body) - : Buffer.from(input.Body); + input.Body instanceof Readable ? await bufferFromStream(input.Body) : Buffer.from(input.Body); await this.saveFile(input.Key, { LastModified: new Date(), ...input, @@ -92,9 +87,7 @@ export abstract class LocalBucket< ...input, signing: { ...input.signing, - expiresIn: DateTime.local() - .plus(Duration.from(input.signing.expiresIn)) - .toMillis(), + expiresIn: DateTime.local().plus(Duration.from(input.signing.expiresIn)).toMillis(), }, }); const baseUrl = await firstValueFrom(this.options.baseUrl); @@ -122,8 +115,7 @@ export abstract class LocalBucket< operation: string; }; assert( - parsed.operation === operation.name || - `${parsed.operation}Command` === operation.name, + parsed.operation === operation.name || `${parsed.operation}Command` === operation.name, ); return parsed; } catch (e) { diff --git a/src/components/file/bucket/parse-uri.ts b/src/components/file/bucket/parse-uri.ts index 90fdaec0f6..53ba077b4c 100644 --- a/src/components/file/bucket/parse-uri.ts +++ b/src/components/file/bucket/parse-uri.ts @@ -17,8 +17,6 @@ export const parseUri = (uri: string): ParsedBucketUri => { const [, type, remainingSrc] = typeMatch; const roMatch = /(:ro|:readonly)$/i.exec(remainingSrc); const readonly = !!roMatch; - const path = roMatch - ? remainingSrc.slice(0, -roMatch[0].length) - : remainingSrc; + const path = roMatch ? remainingSrc.slice(0, -roMatch[0].length) : remainingSrc; return { type: type?.toLowerCase() ?? '', path, readonly }; }; diff --git a/src/components/file/bucket/s3-bucket.ts b/src/components/file/bucket/s3-bucket.ts index cbb1bb65d9..80b048a0d5 100644 --- a/src/components/file/bucket/s3-bucket.ts +++ b/src/components/file/bucket/s3-bucket.ts @@ -44,10 +44,7 @@ export class S3Bucket extends FileBucket { } async parseSignedUrl(url: URL) { - if ( - !url.hostname.startsWith(this.bucket + '.') || - !url.hostname.endsWith('.amazonaws.com') - ) { + if (!url.hostname.startsWith(this.bucket + '.') || !url.hostname.endsWith('.amazonaws.com')) { throw new InvalidSignedUrlException(); } @@ -89,9 +86,7 @@ export class S3Bucket extends FileBucket { // Since we streams don't have that, and we don't know from file, we need to // buffer it. This way we can know the length to send to S3. const fixedLengthBody = - input.Body instanceof Readable - ? await bufferFromStream(input.Body) - : input.Body; + input.Body instanceof Readable ? await bufferFromStream(input.Body) : input.Body; await this.s3.putObject({ ...input, Key: this.fullKey(input.Key), diff --git a/src/components/file/directory.resolver.ts b/src/components/file/directory.resolver.ts index eed5803a25..138f2054ed 100644 --- a/src/components/file/directory.resolver.ts +++ b/src/components/file/directory.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { type ID, IdArg, ListArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; @@ -68,9 +61,7 @@ export class DirectoryResolver { @Parent() node: Directory, @Loader(FileNodeLoader) files: LoaderOf, ): Promise { - return node.firstFileCreated - ? await files.load(node.firstFileCreated) - : null; + return node.firstFileCreated ? await files.load(node.firstFileCreated) : null; } @Mutation(() => Directory) diff --git a/src/components/file/dto/file-node-type.enum.ts b/src/components/file/dto/file-node-type.enum.ts index 43073f58dd..a37d9df606 100644 --- a/src/components/file/dto/file-node-type.enum.ts +++ b/src/components/file/dto/file-node-type.enum.ts @@ -3,7 +3,6 @@ import { type EnumType, makeEnum } from '~/common'; export type FileNodeType = EnumType; export const FileNodeType = makeEnum({ name: 'FileNodeType', - description: - 'The type of node in the file tree. A file, directory or file version.', + description: 'The type of node in the file tree. A file, directory or file version.', values: ['Directory', 'File', 'FileVersion'], }); diff --git a/src/components/file/dto/file.dto.ts b/src/components/file/dto/file.dto.ts index 654db87936..565e156260 100644 --- a/src/components/file/dto/file.dto.ts +++ b/src/components/file/dto/file.dto.ts @@ -25,10 +25,7 @@ import { FileNodeType } from './file-node-type.enum'; * This should be used for TypeScript types as we'll always be passing around * concrete nodes. */ -export type AnyFileNode = MergeExclusive< - MergeExclusive, - FileVersion ->; +export type AnyFileNode = MergeExclusive, FileVersion>; export const resolveFileNode = (val: AnyFileNode) => { const type = simpleSwitch(val.type, { @@ -133,8 +130,7 @@ export class Directory extends FileNode { readonly size: number; @Field(() => Int, { - description: - 'The total number of files under this directory and all its subdirectories', + description: 'The total number of files under this directory and all its subdirectories', }) readonly totalFiles: number; @@ -176,8 +172,7 @@ export const asDirectory = (node: AnyFileNode) => { return node; }; -export const isFile = (node: AnyFileNode): node is File => - node.type === FileNodeType.File; +export const isFile = (node: AnyFileNode): node is File => node.type === FileNodeType.File; export const asFile = (node: AnyFileNode) => { if (!isFile(node)) { diff --git a/src/components/file/dto/list.ts b/src/components/file/dto/list.ts index b578865994..75ddd3bd5f 100644 --- a/src/components/file/dto/list.ts +++ b/src/components/file/dto/list.ts @@ -1,17 +1,7 @@ import { InputType, ObjectType } from '@nestjs/graphql'; -import { - FilterField, - OptionalField, - PaginatedList, - SortablePaginationInput, -} from '~/common'; +import { FilterField, OptionalField, PaginatedList, SortablePaginationInput } from '~/common'; import { FileNodeType } from './file-node-type.enum'; -import { - type Directory, - type File, - type FileNode, - IFileNode, -} from './file.dto'; +import { type Directory, type File, type FileNode, IFileNode } from './file.dto'; @InputType() export abstract class FileFilters { @@ -27,9 +17,7 @@ export abstract class FileFilters { } @InputType() -export class FileListInput extends SortablePaginationInput< - keyof File | keyof Directory ->({ +export class FileListInput extends SortablePaginationInput({ defaultSort: 'name', }) { @FilterField(() => FileFilters) @@ -37,9 +25,6 @@ export class FileListInput extends SortablePaginationInput< } @ObjectType() -export class FileListOutput extends PaginatedList( - IFileNode, - { - itemsDescription: PaginatedList.itemDescriptionFor('file nodes'), - }, -) {} +export class FileListOutput extends PaginatedList(IFileNode, { + itemsDescription: PaginatedList.itemDescriptionFor('file nodes'), +}) {} diff --git a/src/components/file/dto/upload.dto.ts b/src/components/file/dto/upload.dto.ts index cda1c1be1a..69227e5553 100644 --- a/src/components/file/dto/upload.dto.ts +++ b/src/components/file/dto/upload.dto.ts @@ -66,8 +66,7 @@ export abstract class CreateDefinedFileVersionInput { @InputType() export abstract class CreateFileVersionInput extends CreateDefinedFileVersionInput { @IdField({ - description: - 'The directory ID if creating a new file or the file ID if creating a new version', + description: 'The directory ID if creating a new file or the file ID if creating a new version', }) readonly parentId: ID; } diff --git a/src/components/file/file-node.loader.ts b/src/components/file/file-node.loader.ts index 850df94093..77328291e6 100644 --- a/src/components/file/file-node.loader.ts +++ b/src/components/file/file-node.loader.ts @@ -4,9 +4,7 @@ import { Directory, File, type FileNode, FileVersion } from './dto'; import { FileService } from './file.service'; @LoaderFactory(() => [Directory, File, FileVersion]) -export class FileNodeLoader - implements DataLoaderStrategy> -{ +export class FileNodeLoader implements DataLoaderStrategy> { constructor(private readonly files: FileService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/file/file-version.resolver.ts b/src/components/file/file-version.resolver.ts index e2594f81f1..24327afcc1 100644 --- a/src/components/file/file-version.resolver.ts +++ b/src/components/file/file-version.resolver.ts @@ -8,10 +8,7 @@ export class FileVersionResolver { constructor(protected readonly service: FileService) {} @FileUrl.Resolver() - async url( - @Parent() node: FileVersion, - @FileUrl.DownloadArg() download: boolean, - ) { + async url(@Parent() node: FileVersion, @FileUrl.DownloadArg() download: boolean) { return await this.service.getUrl(node, download); } } diff --git a/src/components/file/file.repository.ts b/src/components/file/file.repository.ts index f15f6e4b9e..25de91da5e 100644 --- a/src/components/file/file.repository.ts +++ b/src/components/file/file.repository.ts @@ -13,12 +13,7 @@ import { import { type Direction } from 'cypher-query-builder/dist/typings/clauses/order-by'; import { type AnyConditions } from 'cypher-query-builder/dist/typings/clauses/where-utils'; import { DateTime } from 'luxon'; -import { - CreationFailed, - type ID, - NotFoundException, - ServerException, -} from '~/common'; +import { CreationFailed, type ID, NotFoundException, ServerException } from '~/common'; import { ILogger, type LinkTo, Logger } from '~/core'; import { CommonRepository, OnIndex } from '~/core/database'; import { @@ -120,11 +115,7 @@ export class FileRepository extends CommonRepository { .apply((q) => { const conditions: AnyConditions = {}; if (input?.filter?.name) { - q.match([ - node('node'), - relation('out', '', 'name', ACTIVE), - node('name', 'Property'), - ]); + q.match([node('node'), relation('out', '', 'name', ACTIVE), node('name', 'Property')]); conditions['name.value'] = contains(input.filter.name); } if (input?.filter?.type) { @@ -171,16 +162,11 @@ export class FileRepository extends CommonRepository { // Need to filter out FileNodes which are children of this dir // (the schema was mistakenly pointing these relationships in the wrong direction) // Also filter to ACTIVE, if applicable. - .raw( - 'WHERE NOT resource:FileNode AND coalesce(rel.active, true) <> false', - ) + .raw('WHERE NOT resource:FileNode AND coalesce(rel.active, true) <> false') .return('[resource, type(rel)] as rootAttachedTo'), ) .return<{ dto: FileNode }>( - merge( - 'dto', - mapKeys.fromList(['root', 'rootAttachedTo'], (k) => k).asRecord, - ).as('dto'), + merge('dto', mapKeys.fromList(['root', 'rootAttachedTo'], (k) => k).asRecord).as('dto'), ); } @@ -190,16 +176,8 @@ export class FileRepository extends CommonRepository { .apply(this.matchLatestVersion()) .apply(matchProps()) .apply(matchProps({ nodeName: 'version', outputVar: 'versionProps' })) - .match([ - node('node'), - relation('out', '', 'createdBy', ACTIVE), - node('createdBy'), - ]) - .match([ - node('version'), - relation('out', '', 'createdBy', ACTIVE), - node('modifiedBy'), - ]) + .match([node('node'), relation('out', '', 'createdBy', ACTIVE), node('createdBy')]) + .match([node('version'), relation('out', '', 'createdBy', ACTIVE), node('modifiedBy')]) .return<{ dto: File }>( merge({ public: false }, 'versionProps', 'props', { type: `"${FileNodeType.File}"`, @@ -216,11 +194,7 @@ export class FileRepository extends CommonRepository { return (query: Query) => query .apply(matchProps()) - .match([ - node('node'), - relation('out', '', 'createdBy', ACTIVE), - node('createdBy'), - ]) + .match([node('node'), relation('out', '', 'createdBy', ACTIVE), node('createdBy')]) // Fetch directory info determined by children .subQuery('node', (sub) => sub @@ -244,10 +218,7 @@ export class FileRepository extends CommonRepository { .with('version') .orderBy('version.createdAt') .with('collect(version) as versions') - .return([ - 'versions[0] as firstVersion', - 'versions[-1] as latestVersion', - ]), + .return(['versions[0] as firstVersion', 'versions[-1] as latestVersion']), ) // endregion // region For each latest file version grab its size @@ -281,13 +252,7 @@ export class FileRepository extends CommonRepository { relation('out', '', 'parent', ACTIVE), node('firstFile', 'File'), ]) - .return([ - 'totalFiles', - 'size', - 'firstFile', - 'latestVersion', - 'modifiedBy', - ]), + .return(['totalFiles', 'size', 'firstFile', 'latestVersion', 'modifiedBy']), ) .return<{ dto: Directory }>( merge({ public: false }, 'props', { @@ -324,11 +289,7 @@ export class FileRepository extends CommonRepository { return (query: Query) => query .apply(matchProps()) - .match([ - node('node'), - relation('out', '', 'createdBy', ACTIVE), - node('createdBy'), - ]) + .match([node('node'), relation('out', '', 'createdBy', ACTIVE), node('createdBy')]) .return<{ dto: FileVersion }>( merge({ public: false }, 'props', { type: `"${FileNodeType.FileVersion}"`, @@ -447,9 +408,7 @@ export class FileRepository extends CommonRepository { const createFile = this.db .query() - .apply( - await createNode(File, { initialProps, baseNodeProps: { id: fileId } }), - ) + .apply(await createNode(File, { initialProps, baseNodeProps: { id: fileId } })) .apply( createRelationships(File, { out: { diff --git a/src/components/file/file.resolver.ts b/src/components/file/file.resolver.ts index a901cf88a4..5f3d654cc6 100644 --- a/src/components/file/file.resolver.ts +++ b/src/components/file/file.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { type ID, IdArg, ListArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; @@ -97,18 +90,14 @@ export class FileResolver { the existing file with the same name or create a new file if not found. `, }) - createFileVersion( - @Args('input') input: CreateFileVersionInput, - ): Promise { + createFileVersion(@Args('input') input: CreateFileVersionInput): Promise { return this.service.createFileVersion(input); } @Mutation(() => IFileNode, { description: 'Rename a file or directory', }) - async renameFileNode( - @Args('input') input: RenameFileInput, - ): Promise { + async renameFileNode(@Args('input') input: RenameFileInput): Promise { await this.service.rename(input); return await this.service.getFileNode(input.id); } diff --git a/src/components/file/file.service.ts b/src/components/file/file.service.ts index b747e99f68..6649c1fc64 100644 --- a/src/components/file/file.service.ts +++ b/src/components/file/file.service.ts @@ -1,7 +1,4 @@ -import { - GetObjectCommand as GetObject, - PutObjectCommand as PutObject, -} from '@aws-sdk/client-s3'; +import { GetObjectCommand as GetObject, PutObjectCommand as PutObject } from '@aws-sdk/client-s3'; import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { bufferFromStream, cleanJoin, type Nil } from '@seedcompany/common'; import { fileTypeFromBuffer } from 'file-type'; @@ -92,16 +89,12 @@ export class FileService { asDownloadable(obj: T, fileVersionId: ID): Downloadable; asDownloadable(fileVersion: FileVersion): Downloadable; - asDownloadable( - obj: T, - fileVersionId?: ID, - ): Downloadable { + asDownloadable(obj: T, fileVersionId?: ID): Downloadable { const id = fileVersionId ?? (obj as unknown as FileVersion).id; let downloading: Promise | undefined; return Object.assign(obj, { - download: () => - (downloading ??= this.downloadFileVersion(id).then(bufferFromStream)), + download: () => (downloading ??= this.downloadFileVersion(id).then(bufferFromStream)), stream: async () => { if (downloading) { // If already buffering file, just use that instead of going to source. @@ -162,15 +155,13 @@ export class FileService { await this.bucket.headObject(id); return await this.bucket.getSignedUrl(GetObject, { Key: id, - ResponseContentDisposition: `${disposition}; filename="${encodeURIComponent( - node.name, - )}"`, + ResponseContentDisposition: `${disposition}; filename="${encodeURIComponent(node.name)}"`, ResponseContentType: node.mimeType, ResponseCacheControl: this.determineCacheHeader(node), signing: { - expiresIn: this.config.files.cacheTtl.version[ - node.public ? 'public' : 'private' - ].plus({ seconds: 10 }), // buffer to ensure validity while cached is fresh + expiresIn: this.config.files.cacheTtl.version[node.public ? 'public' : 'private'].plus({ + seconds: 10, + }), // buffer to ensure validity while cached is fresh }, }); } catch (e) { @@ -180,8 +171,7 @@ export class FileService { } determineCacheHeader(node: FileNode) { - const duration = (name: string, d: DurationIn) => - `${name}=${Duration.from(d).as('seconds')}`; + const duration = (name: string, d: DurationIn) => `${name}=${Duration.from(d).as('seconds')}`; const { cacheTtl } = this.config.files; const publicStr = node.public ? 'public' : 'private'; @@ -197,17 +187,11 @@ export class FileService { return await this.repo.getParentsById(nodeId); } - async listChildren( - parent: FileNode, - input: FileListInput | undefined, - ): Promise { + async listChildren(parent: FileNode, input: FileListInput | undefined): Promise { return await this.repo.getChildrenById(parent, input); } - async createDirectory( - parentId: ID | undefined, - name: string, - ): Promise { + async createDirectory(parentId: ID | undefined, name: string): Promise { if (parentId) { await this.validateParentNode( parentId, @@ -232,9 +216,7 @@ export class FileService { return await this.getDirectory(id); } - async createRootDirectory( - ...args: Parameters - ) { + async createRootDirectory(...args: Parameters) { return await this.repo.createRootDirectory(...args); } @@ -256,9 +238,7 @@ export class FileService { * If the given parent is a directory, this will attach the new version to * the existing file with the same name or create a new file if not found. */ - async createFileVersion( - input: CreateFileVersionInput, - ): Promise { + async createFileVersion(input: CreateFileVersionInput): Promise { const { parentId, file: uploadingFile, @@ -290,9 +270,7 @@ export class FileService { }) : false; if (prevExists) { - throw new InputException( - 'A file with this ID already exists. Request an new upload ID.', - ); + throw new InputException('A file with this ID already exists. Request an new upload ID.'); } const body = await uploadingFile.arrayBuffer(); @@ -315,29 +293,17 @@ export class FileService { this.bucket.headObject(uploadId), ]); - if ( - tempUpload.status === 'rejected' && - existingUpload.status === 'rejected' - ) { + if (tempUpload.status === 'rejected' && existingUpload.status === 'rejected') { if (tempUpload.reason instanceof NotFoundException) { throw new NotFoundException('Could not find upload', 'uploadId'); } throw new CreationFailed(FileVersion); - } else if ( - tempUpload.status === 'fulfilled' && - existingUpload.status === 'fulfilled' - ) { + } else if (tempUpload.status === 'fulfilled' && existingUpload.status === 'fulfilled') { if (tempUpload.value && existingUpload.value) { - throw new InputException( - 'Upload request has already been used', - 'uploadId', - ); + throw new InputException('Upload request has already been used', 'uploadId'); } throw new CreationFailed(FileVersion); - } else if ( - tempUpload.status === 'rejected' && - existingUpload.status === 'fulfilled' - ) { + } else if (tempUpload.status === 'rejected' && existingUpload.status === 'fulfilled') { try { await this.getFileNode(uploadId); throw new InputException('Already uploaded', 'uploadId'); @@ -365,8 +331,7 @@ export class FileService { ? existingUpload.value : undefined; - const mimeType = - mimeTypeOverride ?? upload?.ContentType ?? 'application/octet-stream'; + const mimeType = mimeTypeOverride ?? upload?.ContentType ?? 'application/octet-stream'; const fv = await this.repo.createFileVersion(fileId, { id: uploadId, @@ -381,14 +346,12 @@ export class FileService { // Undo the above operation by moving it back to temp folder. this.rollbacks.add(async () => { - await this.bucket - .moveObject(uploadId, `temp/${uploadId}`) - .catch((e) => { - this.logger.error('Failed to move file back to temp holding', { - uploadId, - exception: e, - }); + await this.bucket.moveObject(uploadId, `temp/${uploadId}`).catch((e) => { + this.logger.error('Failed to move file back to temp holding', { + uploadId, + exception: e, }); + }); }); } @@ -413,20 +376,14 @@ export class FileService { if (!node) { throw new NotFoundException('Could not find parent', 'parentId'); } - const type = intersection( - node.labels, - Object.keys(FileNodeType), - )[0] as FileNodeType; + const type = intersection(node.labels, Object.keys(FileNodeType))[0] as FileNodeType; if (!isType(type)) { throw new InputException(typeMismatchError, 'parentId'); } return type; } - private async resolveName( - name?: string, - input?: CreateDefinedFileVersionInput, - ) { + private async resolveName(name?: string, input?: CreateDefinedFileVersionInput) { if (name) { return sanitizeFilename(name); } @@ -460,14 +417,11 @@ export class FileService { const fileId = await generateId(); await this.repo.createFile({ fileId, name, parentId }); - this.logger.debug( - 'File matching given name not found, creating a new one', - { - parentId, - fileName: name, - fileId: fileId, - }, - ); + this.logger.debug('File matching given name not found, creating a new one', { + parentId, + fileName: name, + fileId: fileId, + }); return fileId; } @@ -504,24 +458,17 @@ export class FileService { } } - async updateDefinedFile< - Input extends CreateDefinedFileVersionInput | undefined, - >( + async updateDefinedFile( file: Secured | null>, field: string, input: Input, - ): Promise< - FileWithNewVersion | (Input extends NonNullable ? never : undefined) - > { + ): Promise ? never : undefined)> { if (input == null) { // @ts-expect-error idk why TS doesn't like this, but the signature is right. return undefined; } if (!file.canRead || !file.canEdit || !file.value) { - throw new UnauthorizedException( - 'You do not have permission to update this file', - field, - ); + throw new UnauthorizedException('You do not have permission to update this file', field); } const fileId = isIdLike(file.value) ? file.value : file.value.id; try { diff --git a/src/components/file/files-bucket.factory.ts b/src/components/file/files-bucket.factory.ts index f923f0c141..1d31016981 100644 --- a/src/components/file/files-bucket.factory.ts +++ b/src/components/file/files-bucket.factory.ts @@ -4,13 +4,7 @@ import { resolve } from 'path'; import { map } from 'rxjs/operators'; import { withAddedPath } from '~/common/url.util'; import { ConfigService } from '~/core'; -import { - CompositeBucket, - FileBucket, - FilesystemBucket, - MemoryBucket, - S3Bucket, -} from './bucket'; +import { CompositeBucket, FileBucket, FilesystemBucket, MemoryBucket, S3Bucket } from './bucket'; import { type ParsedBucketUri } from './bucket/parse-uri'; import { ReadonlyBucket } from './bucket/readonly-bucket'; import { LocalBucketController } from './local-bucket.controller'; diff --git a/src/components/file/handlers/attach-project-root-directory.handler.ts b/src/components/file/handlers/attach-project-root-directory.handler.ts index 5a9645ecb7..025307a397 100644 --- a/src/components/file/handlers/attach-project-root-directory.handler.ts +++ b/src/components/file/handlers/attach-project-root-directory.handler.ts @@ -3,9 +3,7 @@ import { ProjectCreatedEvent } from '../../project/events'; import { FileService } from '../file.service'; @EventsHandler(ProjectCreatedEvent) -export class AttachProjectRootDirectoryHandler - implements IEventHandler -{ +export class AttachProjectRootDirectoryHandler implements IEventHandler { constructor(private readonly files: FileService) {} async handle(event: ProjectCreatedEvent) { @@ -22,12 +20,7 @@ export class AttachProjectRootDirectoryHandler rootDirectory: { id: rootDirId }, }; - const folders = [ - 'Approval Documents', - 'Consultant Reports', - 'Field Correspondence', - 'Photos', - ]; + const folders = ['Approval Documents', 'Consultant Reports', 'Field Correspondence', 'Photos']; for (const folder of folders) { await this.files.createDirectory(rootDirId, folder); } diff --git a/src/components/file/handlers/detach-project-root-directory.handler.ts b/src/components/file/handlers/detach-project-root-directory.handler.ts index b6c73ed0aa..010917a468 100644 --- a/src/components/file/handlers/detach-project-root-directory.handler.ts +++ b/src/components/file/handlers/detach-project-root-directory.handler.ts @@ -1,13 +1,8 @@ import { EventsHandler, type IEventHandler } from '~/core'; -import { - type ProjectCreatedEvent, - ProjectDeletedEvent, -} from '../../project/events'; +import { type ProjectCreatedEvent, ProjectDeletedEvent } from '../../project/events'; @EventsHandler(ProjectDeletedEvent) -export class DetachProjectRootDirectoryHandler - implements IEventHandler -{ +export class DetachProjectRootDirectoryHandler implements IEventHandler { async handle(_event: ProjectDeletedEvent) { // TODO Update DB is some fashion } diff --git a/src/components/file/local-bucket.controller.ts b/src/components/file/local-bucket.controller.ts index 708ca258c5..19080357e1 100644 --- a/src/components/file/local-bucket.controller.ts +++ b/src/components/file/local-bucket.controller.ts @@ -12,12 +12,7 @@ import { DateTime } from 'luxon'; import { URL } from 'node:url'; import { InputException } from '~/common'; import { AuthLevel } from '~/core/authentication'; -import { - HttpAdapter, - type IRequest, - type IResponse, - RawBody, -} from '~/core/http'; +import { HttpAdapter, type IRequest, type IResponse, RawBody } from '~/core/http'; import { FileBucket, InvalidSignedUrlException } from './bucket'; /** @@ -28,10 +23,7 @@ import { FileBucket, InvalidSignedUrlException } from './bucket'; export class LocalBucketController { static path = '/local-bucket'; - constructor( - private readonly bucket: FileBucket, - private readonly http: HttpAdapter, - ) {} + constructor(private readonly bucket: FileBucket, private readonly http: HttpAdapter) {} @Put() @RawBody({ passthrough: true }) @@ -59,10 +51,7 @@ export class LocalBucketController { } @Get() - async download( - @Request() req: IRequest, - @Response({ passthrough: true }) res: IResponse, - ) { + async download(@Request() req: IRequest, @Response({ passthrough: true }) res: IResponse) { const url = new URL(`https://localhost${req.url}`); const { Key, operation, ...rest } = await this.bucket.parseSignedUrl(url); if (operation !== 'GetObject') { @@ -83,13 +72,9 @@ export class LocalBucketController { 'Content-Language': out.ContentLanguage, 'Content-Length': String(out.ContentLength), 'Content-Type': out.ContentType, - Expires: out.Expires - ? DateTime.fromJSDate(out.Expires).toHTTP() - : undefined, + Expires: out.Expires ? DateTime.fromJSDate(out.Expires).toHTTP() : undefined, ETag: out.ETag, - LastModified: out.LastModified - ? DateTime.fromJSDate(out.LastModified).toHTTP() - : undefined, + LastModified: out.LastModified ? DateTime.fromJSDate(out.LastModified).toHTTP() : undefined, }; for (const [header, val] of Object.entries(headers)) { if (val != null) { diff --git a/src/components/file/media/detect-existing-media.migration.ts b/src/components/file/media/detect-existing-media.migration.ts index d5950aa38f..26d92b5f94 100644 --- a/src/components/file/media/detect-existing-media.migration.ts +++ b/src/components/file/media/detect-existing-media.migration.ts @@ -8,10 +8,7 @@ import { MediaService } from './media.service'; @Migration('2023-09-06T13:00:00') export class DetectExistingMediaMigration extends BaseMigration { - constructor( - private readonly mediaService: MediaService, - private readonly moduleRef: ModuleRef, - ) { + constructor(private readonly mediaService: MediaService, private readonly moduleRef: ModuleRef) { super(); } @@ -57,15 +54,9 @@ export class DetectExistingMediaMigration extends BaseMigration { relation('out', '', 'mimeType', ACTIVE), node('mt', 'Property'), ]) - .optionalMatch([ - node('fv'), - relation('out', '', 'media'), - node('media', 'Media'), - ]) + .optionalMatch([node('fv'), relation('out', '', 'media'), node('media', 'Media')]) .with('fv, mt, media') - .raw( - `where mt.value starts with '${type}/' and (media is null or media.duration = 0)`, - ) + .raw(`where mt.value starts with '${type}/' and (media is null or media.duration = 0)`) .return('fv') .orderBy('fv.createdAt') .skip(page * size) diff --git a/src/components/file/media/events/can-update-event.ts b/src/components/file/media/events/can-update-event.ts index 00e1b13ea5..aba6c88642 100644 --- a/src/components/file/media/events/can-update-event.ts +++ b/src/components/file/media/events/can-update-event.ts @@ -20,9 +20,7 @@ export class CanUpdateMediaUserMetadataEvent { ) {} @Once() getAttachedResource() { - const attachedResName = this.resourceResolver.resolveTypeByBaseNode( - this.media.attachedTo[0], - ); + const attachedResName = this.resourceResolver.resolveTypeByBaseNode(this.media.attachedTo[0]); const attachedResource = this.resourceHost.getByName(attachedResName); return attachedResource; } diff --git a/src/components/file/media/media-by-file-version.loader.ts b/src/components/file/media/media-by-file-version.loader.ts index 95b91b9e49..738d8a438b 100644 --- a/src/components/file/media/media-by-file-version.loader.ts +++ b/src/components/file/media/media-by-file-version.loader.ts @@ -1,16 +1,11 @@ -import { - type DataLoaderOptions, - type DataLoaderStrategy, -} from '@seedcompany/data-loader'; +import { type DataLoaderOptions, type DataLoaderStrategy } from '@seedcompany/data-loader'; import { type ID } from '~/common'; import { LoaderFactory } from '~/core/data-loader'; import { type AnyMedia } from './media.dto'; import { MediaRepository } from './media.repository'; @LoaderFactory() -export class MediaByFileVersionLoader - implements DataLoaderStrategy -{ +export class MediaByFileVersionLoader implements DataLoaderStrategy { constructor(private readonly repo: MediaRepository) {} getOptions(): DataLoaderOptions { diff --git a/src/components/file/media/media-detector.service.ts b/src/components/file/media/media-detector.service.ts index 9835a7cb5e..2906ca996c 100644 --- a/src/components/file/media/media-detector.service.ts +++ b/src/components/file/media/media-detector.service.ts @@ -75,15 +75,7 @@ export class MediaDetector { async () => { const probe = await execa( binaryPath, - [ - '-v', - 'error', - '-print_format', - 'json', - '-show_format', - '-show_streams', - url, - ], + ['-v', 'error', '-print_format', 'json', '-show_format', '-show_streams', url], { timeout: 10_000, }, diff --git a/src/components/file/media/media.dto.ts b/src/components/file/media/media.dto.ts index 8e4a549269..ef0aefb9e0 100644 --- a/src/components/file/media/media.dto.ts +++ b/src/components/file/media/media.dto.ts @@ -1,11 +1,4 @@ -import { - Field, - Float, - InputType, - Int, - InterfaceType, - ObjectType, -} from '@nestjs/graphql'; +import { Field, Float, InputType, Int, InterfaceType, ObjectType } from '@nestjs/graphql'; import { simpleSwitch } from '@seedcompany/common'; import { stripIndent } from 'common-tags'; import { diff --git a/src/components/file/media/media.repository.ts b/src/components/file/media/media.repository.ts index afc7d1b43f..4236e2fbde 100644 --- a/src/components/file/media/media.repository.ts +++ b/src/components/file/media/media.repository.ts @@ -24,16 +24,10 @@ export class MediaRepository extends CommonRepository { return media; } - async readMany( - input: RequireAtLeastOne>, - ) { + async readMany(input: RequireAtLeastOne>) { return await this.db .query() - .match([ - node('fv', 'FileVersion'), - relation('out', '', 'media'), - node('node', 'Media'), - ]) + .match([node('fv', 'FileVersion'), relation('out', '', 'media'), node('node', 'Media')]) .where( or([ ...(input.fvIds ? [{ 'fv.id': inArray(input.fvIds) }] : []), @@ -81,9 +75,7 @@ export class MediaRepository extends CommonRepository { input: RequireAtLeastOne> & Partial>, ) { - const res = input.__typename - ? EnhancedResource.of(resolveMedia(input as AnyMedia)) - : undefined; + const res = input.__typename ? EnhancedResource.of(resolveMedia(input as AnyMedia)) : undefined; const metadata = EnhancedResource.of(MediaUserMetadata); const tempId = await generateId(); @@ -105,22 +97,14 @@ export class MediaRepository extends CommonRepository { values: { 'node.id': tempId }, variables: { 'node.createdAt': 'datetime()' }, }) - : q.match([ - node('fv', 'FileVersion'), - relation('out', '', 'media'), - node('node'), - ]), + : q.match([node('fv', 'FileVersion'), relation('out', '', 'media'), node('node')]), ) .setValues({ node: toDbShape(input) }, true) .with('node, fv') // Update the labels if typename is given, and maybe changed. .apply((q) => res - ? q.call( - apoc.create - .setLabels('node', res.dbLabels) - .yield({ node: 'labelsAdded' }), - ) + ? q.call(apoc.create.setLabels('node', res.dbLabels).yield({ node: 'labelsAdded' })) : q, ) // Grab the previous media node or null @@ -133,11 +117,7 @@ export class MediaRepository extends CommonRepository { relation('in', '', 'parent', ACTIVE), node('fvs', 'FileVersion'), ]) - .optionalMatch([ - node('fvs'), - relation('out', '', 'media'), - node('prevMedia', 'Media'), - ]) + .optionalMatch([node('fvs'), relation('out', '', 'media'), node('prevMedia', 'Media')]) .return('prevMedia') .orderBy('fvs.createdAt', 'DESC') .raw('LIMIT 1'), @@ -167,9 +147,7 @@ export class MediaRepository extends CommonRepository { if (input.file) { const exists = await this.getBaseNode(input.file, 'FileVersion'); if (!exists) { - throw new NotFoundException( - 'Media could not be saved to nonexistent file', - ); + throw new NotFoundException('Media could not be saved to nonexistent file'); } } if (input.id) { diff --git a/src/components/file/media/media.resolver.ts b/src/components/file/media/media.resolver.ts index 77f650d4ef..efc1ca65e8 100644 --- a/src/components/file/media/media.resolver.ts +++ b/src/components/file/media/media.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { Loader, type LoaderOf } from '@seedcompany/data-loader'; import { type ID, IdArg } from '~/common'; import { FileVersion } from '../dto'; @@ -17,10 +11,7 @@ export class MediaResolver { constructor(private readonly service: MediaService) {} @ResolveField(() => FileVersion) - async file( - @Parent() media: Media, - @Loader(FileNodeLoader) files: LoaderOf, - ) { + async file(@Parent() media: Media, @Loader(FileNodeLoader) files: LoaderOf) { return await files.load(media.file); } diff --git a/src/components/file/media/media.service.ts b/src/components/file/media/media.service.ts index 7f5d5b654c..d0e7a046f8 100644 --- a/src/components/file/media/media.service.ts +++ b/src/components/file/media/media.service.ts @@ -52,9 +52,7 @@ export class MediaService { ); await this.eventBus.publish(event); if (!(poll.plurality && !poll.vetoed)) { - throw new UnauthorizedException( - 'You do not have permission to update this media metadata', - ); + throw new UnauthorizedException('You do not have permission to update this media metadata'); } try { diff --git a/src/components/film/dto/film.dto.ts b/src/components/film/dto/film.dto.ts index 97fcb73161..9ab6ec08bf 100644 --- a/src/components/film/dto/film.dto.ts +++ b/src/components/film/dto/film.dto.ts @@ -2,10 +2,7 @@ import { ObjectType } from '@nestjs/graphql'; import { DbUnique, NameField, Resource, SecuredString } from '~/common'; import { e } from '~/core/gel'; import { RegisterResource } from '~/core/resources'; -import { - Producible, - ProducibleTypeEntries, -} from '../../product/dto/producible.dto'; +import { Producible, ProducibleTypeEntries } from '../../product/dto/producible.dto'; ProducibleTypeEntries.add('Film'); declare module '../../product/dto/producible.dto' { diff --git a/src/components/film/film.gel.repository.ts b/src/components/film/film.gel.repository.ts index 8f1350310b..b054596002 100644 --- a/src/components/film/film.gel.repository.ts +++ b/src/components/film/film.gel.repository.ts @@ -17,16 +17,13 @@ export class FilmGelRepository implements PublicOf { async create(input: CreateFilm): Promise> { - const query = e.params( - { name: e.str, scripture: e.optional(scripture.type) }, - ($) => { - const created = e.insert(this.resource.db, { - name: $.name, - scripture: scripture.insert($.scripture), - }); - return e.select(created, this.hydrate); - }, - ); + const query = e.params({ name: e.str, scripture: e.optional(scripture.type) }, ($) => { + const created = e.insert(this.resource.db, { + name: $.name, + scripture: scripture.insert($.scripture), + }); + return e.select(created, this.hydrate); + }); return await this.db.run(query, { name: input.name, scripture: scripture.valueOptional(input.scriptureReferences), diff --git a/src/components/film/film.module.ts b/src/components/film/film.module.ts index 7606668874..7bd02c2609 100644 --- a/src/components/film/film.module.ts +++ b/src/components/film/film.module.ts @@ -10,12 +10,7 @@ import { FilmService } from './film.service'; @Module({ imports: [forwardRef(() => AuthorizationModule), ScriptureModule], - providers: [ - FilmResolver, - FilmService, - splitDb(FilmRepository, FilmGelRepository), - FilmLoader, - ], + providers: [FilmResolver, FilmService, splitDb(FilmRepository, FilmGelRepository), FilmLoader], exports: [FilmService], }) export class FilmModule {} diff --git a/src/components/film/film.repository.ts b/src/components/film/film.repository.ts index 48eb61fc7d..fe45318e32 100644 --- a/src/components/film/film.repository.ts +++ b/src/components/film/film.repository.ts @@ -10,23 +10,9 @@ import { type UnsecuredDto, } from '~/common'; import { type DbTypeOf, DtoRepository } from '~/core/database'; -import { - createNode, - matchProps, - merge, - paginate, - sorting, -} from '~/core/database/query'; -import { - ScriptureReferenceRepository, - ScriptureReferenceService, -} from '../scripture'; -import { - type CreateFilm, - Film, - type FilmListInput, - type UpdateFilm, -} from './dto'; +import { createNode, matchProps, merge, paginate, sorting } from '~/core/database/query'; +import { ScriptureReferenceRepository, ScriptureReferenceService } from '../scripture'; +import { type CreateFilm, Film, type FilmListInput, type UpdateFilm } from './dto'; @Injectable() export class FilmRepository extends DtoRepository(Film) { @@ -39,10 +25,7 @@ export class FilmRepository extends DtoRepository(Film) { async create(input: CreateFilm) { if (!(await this.isUnique(input.name))) { - throw new DuplicateException( - 'film.name', - 'Film with this name already exists', - ); + throw new DuplicateException('film.name', 'Film with this name already exists'); } const initialProps = { @@ -59,15 +42,10 @@ export class FilmRepository extends DtoRepository(Film) { throw new CreationFailed(Film); } - await this.scriptureRefsService.create( - result.id, - input.scriptureReferences, - ); + await this.scriptureRefsService.create(result.id, input.scriptureReferences); return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Film) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Film) : e; }); } @@ -84,22 +62,15 @@ export class FilmRepository extends DtoRepository(Film) { return (await super.readOne(id)) as UnsecuredDto; } - async readMany( - ids: readonly ID[], - ): Promise>> { + async readMany(ids: readonly ID[]): Promise>> { const items = await super.readMany(ids); return items.map((r) => ({ ...r, - scriptureReferences: this.scriptureRefsService.parseList( - r.scriptureReferences, - ), + scriptureReferences: this.scriptureRefsService.parseList(r.scriptureReferences), })); } - async list({ - filter, - ...input - }: FilmListInput): Promise>> { + async list({ filter, ...input }: FilmListInput): Promise>> { const result = await this.db .query() .matchNode('node', 'Film') @@ -110,9 +81,7 @@ export class FilmRepository extends DtoRepository(Film) { ...result!, items: result!.items.map((r) => ({ ...r, - scriptureReferences: this.scriptureRefsService.parseList( - r.scriptureReferences, - ), + scriptureReferences: this.scriptureRefsService.parseList(r.scriptureReferences), })), }; } diff --git a/src/components/film/film.resolver.ts b/src/components/film/film.resolver.ts index a0d9861838..dae9f3ef52 100644 --- a/src/components/film/film.resolver.ts +++ b/src/components/film/film.resolver.ts @@ -21,10 +21,7 @@ export class FilmResolver { @Query(() => Film, { description: 'Look up a film by its ID', }) - async film( - @Loader(FilmLoader) films: LoaderOf, - @IdArg() id: ID, - ): Promise { + async film(@Loader(FilmLoader) films: LoaderOf, @IdArg() id: ID): Promise { return await films.load(id); } @@ -43,9 +40,7 @@ export class FilmResolver { @Mutation(() => CreateFilmOutput, { description: 'Create a film', }) - async createFilm( - @Args('input') { film: input }: CreateFilmInput, - ): Promise { + async createFilm(@Args('input') { film: input }: CreateFilmInput): Promise { const film = await this.filmService.create(input); return { film }; } @@ -53,9 +48,7 @@ export class FilmResolver { @Mutation(() => UpdateFilmOutput, { description: 'Update a film', }) - async updateFilm( - @Args('input') { film: input }: UpdateFilmInput, - ): Promise { + async updateFilm(@Args('input') { film: input }: UpdateFilmInput): Promise { const film = await this.filmService.update(input); return { film }; } diff --git a/src/components/film/film.service.ts b/src/components/film/film.service.ts index 5fd7ce02e9..36a47f6703 100644 --- a/src/components/film/film.service.ts +++ b/src/components/film/film.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - type ID, - type ObjectView, - ServerException, - type UnsecuredDto, -} from '~/common'; +import { type ID, type ObjectView, ServerException, type UnsecuredDto } from '~/common'; import { HandleIdLookup } from '~/core'; import { ifDiff } from '~/core/database/changes'; import { Privileges } from '../authorization'; @@ -20,10 +15,7 @@ import { FilmRepository } from './film.repository'; @Injectable() export class FilmService { - constructor( - private readonly privileges: Privileges, - private readonly repo: FilmRepository, - ) {} + constructor(private readonly privileges: Privileges, private readonly repo: FilmRepository) {} async create(input: CreateFilm): Promise { const dto = await this.repo.create(input); diff --git a/src/components/finance/department/dto/id-blocks.dto.ts b/src/components/finance/department/dto/id-blocks.dto.ts index 2733c42899..46200cc745 100644 --- a/src/components/finance/department/dto/id-blocks.dto.ts +++ b/src/components/finance/department/dto/id-blocks.dto.ts @@ -13,9 +13,7 @@ export class FinanceDepartmentIdBlock extends FinanceDepartmentIdBlockInput { } @ObjectType() -export class SecuredFinanceDepartmentIdBlock extends SecuredProperty( - FinanceDepartmentIdBlock, -) {} +export class SecuredFinanceDepartmentIdBlock extends SecuredProperty(FinanceDepartmentIdBlock) {} @ObjectType() export class SecuredFinanceDepartmentIdBlockNullable extends SecuredProperty( FinanceDepartmentIdBlock, diff --git a/src/components/finance/department/dto/id-blocks.input.ts b/src/components/finance/department/dto/id-blocks.input.ts index f61c1158ef..938448145a 100644 --- a/src/components/finance/department/dto/id-blocks.input.ts +++ b/src/components/finance/department/dto/id-blocks.input.ts @@ -44,22 +44,20 @@ export class FinanceDepartmentIdBlockInput { } const parse = (multiRangeInts: string): Blocks => { - const [first, ...rest] = csv(multiRangeInts).map( - (rangeInts): Range => { - const [start, end = start] = rangeInts.split(/[-–]/); - const range = mapRange({ start, end }, (str) => { - const point = Number(str); - if (!Number.isSafeInteger(point) || point < 1) { - throw new Error(`Invalid range: ${rangeInts}`); - } - return point; - }); - if (range.start > range.end) { + const [first, ...rest] = csv(multiRangeInts).map((rangeInts): Range => { + const [start, end = start] = rangeInts.split(/[-–]/); + const range = mapRange({ start, end }, (str) => { + const point = Number(str); + if (!Number.isSafeInteger(point) || point < 1) { throw new Error(`Invalid range: ${rangeInts}`); } - return range; - }, - ); + return point; + }); + if (range.start > range.end) { + throw new Error(`Invalid range: ${rangeInts}`); + } + return range; + }); if (!first) { throw new Error('Ranges cannot be empty'); } diff --git a/src/components/finance/department/gel.utils.ts b/src/components/finance/department/gel.utils.ts index 5533cf6468..7a99d8347f 100644 --- a/src/components/finance/department/gel.utils.ts +++ b/src/components/finance/department/gel.utils.ts @@ -4,25 +4,21 @@ import { type FinanceDepartmentIdBlockInput as Input } from './dto/id-blocks.inp export const hydrate = e.shape(e.Finance.Department.IdBlock, (block) => ({ id: true, - blocks: e.for( - e.assert_exists(e.op('distinct', e.multirange_unpack(block.range))), - (range) => - e.select({ - start: e.assert_exists(e.range_get_lower(range)), - end: e.op(e.assert_exists(e.range_get_upper(range)), '-', 1), - }), + blocks: e.for(e.assert_exists(e.op('distinct', e.multirange_unpack(block.range))), (range) => + e.select({ + start: e.assert_exists(e.range_get_lower(range)), + end: e.op(e.assert_exists(e.range_get_upper(range)), '-', 1), + }), ), programs: true, })); -export const insertMaybe = (input: Input | Nil) => - !input ? undefined : insert(input); +export const insertMaybe = (input: Input | Nil) => (!input ? undefined : insert(input)); export const setMaybe = (ref: IdBlockOptionalRef, input: Input | Nil) => input === undefined ? undefined : set(ref, input); -export const insert = (input: Input) => - e.insert(e.Finance.Department.IdBlock, inputForGel(input)); +export const insert = (input: Input) => e.insert(e.Finance.Department.IdBlock, inputForGel(input)); export const set = (ref: IdBlockOptionalRef, input: Input | null) => input ? upsert(ref, input) : e.delete(ref); @@ -37,9 +33,7 @@ export const upsert = (ref: IdBlockOptionalRef, input: Input) => ); const inputForGel = (input: Input) => { - const ranges = input.blocks.map((range) => - e.range(range.start, range.end + 1), - ); + const ranges = input.blocks.map((range) => e.range(range.start, range.end + 1)); return { range: e.multirange(e.array(asNonEmpty(ranges))), programs: input.programs, @@ -54,8 +48,5 @@ const asNonEmpty = (list: readonly T[]) => { }; type IdBlockOptionalRef = $.$expr_PathNode< - $.TypeSet< - typeof e.Finance.Department.IdBlock.__element__, - $.Cardinality.AtMostOne - > + $.TypeSet >; diff --git a/src/components/finance/department/id-block.resolver.ts b/src/components/finance/department/id-block.resolver.ts index f81ab21dec..3158dc7772 100644 --- a/src/components/finance/department/id-block.resolver.ts +++ b/src/components/finance/department/id-block.resolver.ts @@ -5,8 +5,6 @@ import { FinanceDepartmentIdBlock } from './dto/id-blocks.dto'; export class IdBlockResolver { @ResolveField(() => String) blocks(@Parent() idBlock: FinanceDepartmentIdBlock) { - return idBlock.blocks - .map((range) => `${range.start}-${range.end}`) - .join(', '); + return idBlock.blocks.map((range) => `${range.start}-${range.end}`).join(', '); } } diff --git a/src/components/finance/department/neo4j.utils.ts b/src/components/finance/department/neo4j.utils.ts index afc87b87e1..d4a4c105fc 100644 --- a/src/components/finance/department/neo4j.utils.ts +++ b/src/components/finance/department/neo4j.utils.ts @@ -3,10 +3,7 @@ import { node, type Pattern, type Query, relation } from 'cypher-query-builder'; import { ACTIVE, apoc, collect, merge, variable } from '~/core/database/query'; import { type FinanceDepartmentIdBlockInput as Input } from './dto/id-blocks.input'; -const defaultRel = [ - node('node'), - relation('out', '', 'departmentIdBlock', ACTIVE), -]; +const defaultRel = [node('node'), relation('out', '', 'departmentIdBlock', ACTIVE)]; interface Options { input?: Pattern[]; output?: string; @@ -28,9 +25,8 @@ export const hydrate = .return(`departmentIdBlocks[0] as ${output ?? 'departmentIdBlock'}`), ); -export const createMaybe = - (input: Input | Nil, options?: Options) => (query: Query) => - !input ? query : query.apply(create(input, options)); +export const createMaybe = (input: Input | Nil, options?: Options) => (query: Query) => + !input ? query : query.apply(create(input, options)); export const create = (input: Input, { input: inputRel = defaultRel, output }: Options = {}) => @@ -48,15 +44,11 @@ export const create = .return(`block as ${output ?? 'block'}`), ); -export const setMaybe = - (input: Input | Nil, options?: Options) => (query: Query) => - input === undefined ? query : query.apply(set(input, options)); +export const setMaybe = (input: Input | Nil, options?: Options) => (query: Query) => + input === undefined ? query : query.apply(set(input, options)); export const set = - ( - input: Input | null, - { input: inputRel = defaultRel, output }: Options = {}, - ) => + (input: Input | null, { input: inputRel = defaultRel, output }: Options = {}) => (query: Query) => input ? query.apply(upsert(input, { input: inputRel, output })) diff --git a/src/components/funding-account/dto/list-funding-account.dto.ts b/src/components/funding-account/dto/list-funding-account.dto.ts index a1581eeff7..b7e9f8a924 100644 --- a/src/components/funding-account/dto/list-funding-account.dto.ts +++ b/src/components/funding-account/dto/list-funding-account.dto.ts @@ -3,9 +3,7 @@ import { PaginatedList, SortablePaginationInput } from '~/common'; import { FundingAccount } from './funding-account.dto'; @InputType() -export class FundingAccountListInput extends SortablePaginationInput< - keyof FundingAccount ->({ +export class FundingAccountListInput extends SortablePaginationInput({ defaultSort: 'name', }) {} diff --git a/src/components/funding-account/funding-account.repository.ts b/src/components/funding-account/funding-account.repository.ts index 68d40b5bda..af0b37f3eb 100644 --- a/src/components/funding-account/funding-account.repository.ts +++ b/src/components/funding-account/funding-account.repository.ts @@ -43,9 +43,7 @@ export class FundingAccountRepository extends DtoRepository(FundingAccount) { relation('out', '', 'departmentIdBlock', ACTIVE), node('', 'DepartmentIdBlock', { id: variable(apoc.create.uuid()), - blocks: variable( - apoc.convert.toJson(blockOfAccount(input.accountNumber)), - ), + blocks: variable(apoc.convert.toJson(blockOfAccount(input.accountNumber))), programs: [Program.MomentumTranslation, Program.Internship], }), ]) diff --git a/src/components/funding-account/funding-account.resolver.ts b/src/components/funding-account/funding-account.resolver.ts index e7bb2ad3fd..503bdce361 100644 --- a/src/components/funding-account/funding-account.resolver.ts +++ b/src/components/funding-account/funding-account.resolver.ts @@ -65,9 +65,7 @@ export class FundingAccountResolver { @Mutation(() => DeleteFundingAccountOutput, { description: 'Delete a funding account', }) - async deleteFundingAccount( - @IdArg() id: ID, - ): Promise { + async deleteFundingAccount(@IdArg() id: ID): Promise { await this.fundingAccountService.delete(id); return { success: true }; } diff --git a/src/components/funding-account/funding-account.service.ts b/src/components/funding-account/funding-account.service.ts index 3811401572..26674bfcdb 100644 --- a/src/components/funding-account/funding-account.service.ts +++ b/src/components/funding-account/funding-account.service.ts @@ -46,9 +46,7 @@ export class FundingAccountService { } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(FundingAccount) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(FundingAccount) : e; }); } catch (err) { throw new CreationFailed(FundingAccount, { cause: err }); @@ -70,9 +68,7 @@ export class FundingAccountService { return await Promise.all(fundingAccounts.map((dto) => this.secure(dto))); } - private async secure( - dto: UnsecuredDto, - ): Promise { + private async secure(dto: UnsecuredDto): Promise { return this.privileges.for(FundingAccount).secure(dto); } @@ -97,9 +93,7 @@ export class FundingAccountService { } } - async list( - input: FundingAccountListInput, - ): Promise { + async list(input: FundingAccountListInput): Promise { if (this.privileges.for(FundingAccount).can('read')) { const results = await this.repo.list(input); return await mapListResults(results, (dto) => this.secure(dto)); diff --git a/src/components/language/dto/ai-assisted-translation.enum.ts b/src/components/language/dto/ai-assisted-translation.enum.ts index 4fd9c7556e..6c268d4e21 100644 --- a/src/components/language/dto/ai-assisted-translation.enum.ts +++ b/src/components/language/dto/ai-assisted-translation.enum.ts @@ -11,9 +11,6 @@ export const AIAssistedTranslation = makeEnum({ @ObjectType({ description: SecuredEnum.descriptionFor('using AI assisted translation'), }) -export class SecuredAIAssistedTranslation extends SecuredEnum( - AIAssistedTranslation, - { - nullable: true, - }, -) {} +export class SecuredAIAssistedTranslation extends SecuredEnum(AIAssistedTranslation, { + nullable: true, +}) {} diff --git a/src/components/language/dto/create-language.dto.ts b/src/components/language/dto/create-language.dto.ts index c8eb7e05dc..1a5174e84c 100644 --- a/src/components/language/dto/create-language.dto.ts +++ b/src/components/language/dto/create-language.dto.ts @@ -9,13 +9,7 @@ import { ValidateNested, } from 'class-validator'; import { uniq } from 'lodash'; -import { - type CalendarDate, - DateField, - NameField, - Sensitivity, - SensitivityField, -} from '~/common'; +import { type CalendarDate, DateField, NameField, Sensitivity, SensitivityField } from '~/common'; import { ExactLength } from '~/common/validators/exactLength'; import { Language } from './language.dto'; diff --git a/src/components/language/dto/language.dto.ts b/src/components/language/dto/language.dto.ts index a468d2c58b..cdc4813116 100644 --- a/src/components/language/dto/language.dto.ts +++ b/src/components/language/dto/language.dto.ts @@ -34,9 +34,7 @@ import { type UpdateEthnologueLanguage } from './update-language.dto'; @ObjectType({ description: SecuredPropertyList.descriptionFor('tags'), }) -export abstract class SecuredTags extends SecuredPropertyList( - GraphQLString, -) {} +export abstract class SecuredTags extends SecuredPropertyList(GraphQLString) {} @RegisterResource({ db: e.Ethnologue.Language }) @ObjectType() @@ -118,8 +116,7 @@ export class Language extends Interfaces { @Field(() => EthnologueLanguage) readonly ethnologue: EthnologueLanguage & SetUnsecuredType< - UnsecuredDto & - SetChangeType<'ethnologue', UpdateEthnologueLanguage> + UnsecuredDto & SetChangeType<'ethnologue', UpdateEthnologueLanguage> >; @Field({ diff --git a/src/components/language/dto/list-language.dto.ts b/src/components/language/dto/list-language.dto.ts index 602f9f2983..7886b78fcd 100644 --- a/src/components/language/dto/list-language.dto.ts +++ b/src/components/language/dto/list-language.dto.ts @@ -48,8 +48,7 @@ export abstract class LanguageFilters { readonly presetInventory?: boolean; @OptionalField({ - description: - 'Only languages that are pinned/unpinned by the requesting user', + description: 'Only languages that are pinned/unpinned by the requesting user', }) readonly pinned?: boolean; diff --git a/src/components/language/ethnologue-language/ethnologue-language.repository.ts b/src/components/language/ethnologue-language/ethnologue-language.repository.ts index 16a4995637..f69bfde68d 100644 --- a/src/components/language/ethnologue-language/ethnologue-language.repository.ts +++ b/src/components/language/ethnologue-language/ethnologue-language.repository.ts @@ -17,9 +17,7 @@ import { } from '../dto'; @Injectable() -export class EthnologueLanguageRepository extends DtoRepository( - EthnologueLanguage, -) { +export class EthnologueLanguageRepository extends DtoRepository(EthnologueLanguage) { async create(input: CreateEthnologueLanguage & { languageId: ID }) { const initialProps = { code: input.code, @@ -59,9 +57,7 @@ export class EthnologueLanguageRepository extends DtoRepository( } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(EthnologueLanguage) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(EthnologueLanguage) : e; }); } @@ -71,10 +67,7 @@ export class EthnologueLanguageRepository extends DtoRepository( try { await this.updateProperties({ id }, simpleChanges); } catch (exception) { - throw new ServerException( - 'Failed to update ethnologue language', - exception, - ); + throw new ServerException('Failed to update ethnologue language', exception); } return undefined as unknown; diff --git a/src/components/language/ethnologue-language/ethnologue-language.service.ts b/src/components/language/ethnologue-language/ethnologue-language.service.ts index 3dbebffbeb..b28e7e4c75 100644 --- a/src/components/language/ethnologue-language/ethnologue-language.service.ts +++ b/src/components/language/ethnologue-language/ethnologue-language.service.ts @@ -29,18 +29,12 @@ export class EthnologueLanguageService { secure(dto: UnsecuredDto, sensitivity: Sensitivity) { return { - ...this.privileges - .for(EthnologueLanguage) - .secure(withEffectiveSensitivity(dto, sensitivity)), + ...this.privileges.for(EthnologueLanguage).secure(withEffectiveSensitivity(dto, sensitivity)), sensitivity, }; } - async update( - id: ID, - input: UpdateEthnologueLanguage, - sensitivity: Sensitivity, - ) { + async update(id: ID, input: UpdateEthnologueLanguage, sensitivity: Sensitivity) { if (!input) return; const ethnologueLanguage = await this.repo.readOne(id); diff --git a/src/components/language/language.repository.ts b/src/components/language/language.repository.ts index 45d9723f74..ced6e3f910 100644 --- a/src/components/language/language.repository.ts +++ b/src/components/language/language.repository.ts @@ -1,13 +1,6 @@ import { Injectable } from '@nestjs/common'; import { simpleSwitch } from '@seedcompany/common'; -import { - equals, - inArray, - node, - not, - type Query, - relation, -} from 'cypher-query-builder'; +import { equals, inArray, node, not, type Query, relation } from 'cypher-query-builder'; import { CreationFailed, DuplicateException, @@ -56,13 +49,10 @@ import { import { EthnologueLanguageService } from './ethnologue-language'; @Injectable() -export class LanguageRepository extends DtoRepository< - typeof Language, - [view?: ObjectView] ->(Language) { - constructor( - private readonly ethnologueLanguageService: EthnologueLanguageService, - ) { +export class LanguageRepository extends DtoRepository( + Language, +) { + constructor(private readonly ethnologueLanguageService: EthnologueLanguageService) { super(); } @@ -86,9 +76,7 @@ export class LanguageRepository extends DtoRepository< canDelete: true, }; - const ethnologueId = await this.ethnologueLanguageService.create( - input?.ethnologue, - ); + const ethnologueId = await this.ethnologueLanguageService.create(input?.ethnologue); const createLanguage = this.db .query() @@ -126,9 +114,7 @@ export class LanguageRepository extends DtoRepository< } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Language) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Language) : e; }); } @@ -201,11 +187,7 @@ export class LanguageRepository extends DtoRepository< .raw('WHERE size(projList) = 0') .return(`props.sensitivity as effectiveSensitivity`), ) - .match([ - node('node'), - relation('out', '', 'ethnologue'), - node('eth', 'EthnologueLanguage'), - ]) + .match([node('node'), relation('out', '', 'ethnologue'), node('eth', 'EthnologueLanguage')]) .apply(matchProps({ nodeName: 'eth', outputVar: 'ethProps' })) .apply(isPresetInventory) .optionalMatch([ @@ -301,9 +283,7 @@ export const languageFilters = filter.define(() => LanguageFilters, { leastOfThese: filter.propVal(), isSignLanguage: filter.propVal(), isDialect: filter.propVal(), - registryOfDialectsCode: filter.propPartialVal( - 'registryOfLanguageVarietiesCode', - ), + registryOfDialectsCode: filter.propPartialVal('registryOfLanguageVarietiesCode'), registryOfLanguageVarietiesCode: filter.propPartialVal(), partnerId: filter.pathExists((id) => [ node('node'), @@ -319,11 +299,7 @@ export const languageFilters = filter.define(() => LanguageFilters, { name: filter.fullText({ index: () => NameIndex, matchToNode: (q) => - q.match([ - node('node', 'Language'), - relation('out', '', undefined, ACTIVE), - node('match'), - ]), + q.match([node('node', 'Language'), relation('out', '', undefined, ACTIVE), node('match')]), }), ethnologue: filter.sub(() => ethnologueFilters)((sub) => sub.match([ @@ -377,22 +353,12 @@ export const languageSorters = defineSorters(Language, { 'ethnologue.*': (query, input) => query .with('node as lang') - .match([ - node('lang'), - relation('out', '', 'ethnologue'), - node('node', 'EthnologueLanguage'), - ]) + .match([node('lang'), relation('out', '', 'ethnologue'), node('node', 'EthnologueLanguage')]) .apply(sortWith(ethnologueSorters, input)), - ['registryOfDialectsCode' as any]: propSorter( - 'registryOfLanguageVarietiesCode', - ), + ['registryOfDialectsCode' as any]: propSorter('registryOfLanguageVarietiesCode'), population: (query) => query - .match([ - node('node'), - relation('out', '', 'populationOverride', ACTIVE), - node('override'), - ]) + .match([node('node'), relation('out', '', 'populationOverride', ACTIVE), node('override')]) .match([ node('node'), relation('out', '', 'ethnologue'), diff --git a/src/components/language/language.resolver.ts b/src/components/language/language.resolver.ts index b8fe0f022c..d93e1ac293 100644 --- a/src/components/language/language.resolver.ts +++ b/src/components/language/language.resolver.ts @@ -1,12 +1,4 @@ -import { - Args, - ArgsType, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, ArgsType, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { firstLettersOfWords, @@ -91,9 +83,7 @@ export class LanguageResolver { return { canEdit: false, // this is a computed field canRead, - value: canRead - ? value ?? language.ethnologue.population.value - : undefined, + value: canRead ? value ?? language.ethnologue.population.value : undefined, }; } @@ -192,10 +182,7 @@ export class LanguageResolver { async updateLanguage( @Args('input') { language: input, changeset }: UpdateLanguageInput, ): Promise { - const language = await this.langService.update( - input, - viewOfChangeset(changeset), - ); + const language = await this.langService.update(input, viewOfChangeset(changeset)); return { language }; } diff --git a/src/components/language/language.service.ts b/src/components/language/language.service.ts index 9ab58cefe5..a1c5993dea 100644 --- a/src/components/language/language.service.ts +++ b/src/components/language/language.service.ts @@ -14,16 +14,9 @@ import { Privileges } from '../authorization'; import { EngagementService } from '../engagement'; import { type EngagementListInput, EngagementStatus } from '../engagement/dto'; import { LocationService } from '../location'; -import { - type LocationListInput, - type SecuredLocationList, -} from '../location/dto'; +import { type LocationListInput, type SecuredLocationList } from '../location/dto'; import { ProjectService } from '../project'; -import { - IProject, - type ProjectListInput, - type SecuredProjectList, -} from '../project/dto'; +import { IProject, type ProjectListInput, type SecuredProjectList } from '../project/dto'; import { type CreateLanguage, Language, @@ -77,10 +70,7 @@ export class LanguageService { } private secure(dto: UnsecuredDto) { - const ethnologue = this.ethnologueLanguageService.secure( - dto.ethnologue, - dto.sensitivity, - ); + const ethnologue = this.ethnologueLanguageService.secure(dto.ethnologue, dto.sensitivity); return { ...this.privileges.for(Language).secure(dto), @@ -112,10 +102,7 @@ export class LanguageService { ); } - const updated = await this.repo.update( - { id: language.id, ...simpleChanges }, - view?.changeset, - ); + const updated = await this.repo.update({ id: language.id, ...simpleChanges }, view?.changeset); return this.secure(updated); } @@ -142,10 +129,7 @@ export class LanguageService { }; } - async listLocations( - dto: Language, - input: LocationListInput, - ): Promise { + async listLocations(dto: Language, input: LocationListInput): Promise { return await this.locationService.listLocationForResource( this.privileges.for(Language, dto).forEdge('locations'), dto, @@ -153,10 +137,7 @@ export class LanguageService { ); } - async listProjects( - language: Language, - input: ProjectListInput, - ): Promise { + async listProjects(language: Language, input: ProjectListInput): Promise { const projectListOutput = await this.projectService.list({ ...input, filter: { ...input.filter, languageId: language.id }, @@ -186,9 +167,7 @@ export class LanguageService { try { const engagements = await Promise.all( - engagementIds.map((engagementId) => - this.engagementService.readOne(engagementId), - ), + engagementIds.map((engagementId) => this.engagementService.readOne(engagementId)), ); const statusesToIgnore = setOf([ EngagementStatus.InDevelopment, @@ -199,18 +178,14 @@ export class LanguageService { const dates = engagements .filter( (engagement) => - engagement.status.value && - !setHas(statusesToIgnore, engagement.status.value), + engagement.status.value && !setHas(statusesToIgnore, engagement.status.value), ) .map((engagement) => engagement.startDate.value) .filter(isNotFalsy); - const canRead = engagements.every( - (engagement) => engagement.startDate.canRead, - ); + const canRead = engagements.every((engagement) => engagement.startDate.canRead); - const value = - dates.length && canRead ? CalendarDate.min(...dates) : undefined; + const value = dates.length && canRead ? CalendarDate.min(...dates) : undefined; return { canRead, @@ -229,12 +204,7 @@ export class LanguageService { async addLocation(languageId: ID, locationId: ID): Promise { try { - await this.locationService.addLocationToNode( - 'Language', - languageId, - 'locations', - locationId, - ); + await this.locationService.addLocationToNode('Language', languageId, 'locations', locationId); } catch (e) { throw new ServerException('Could not add location to language', e); } diff --git a/src/components/location/dto/create-location.dto.ts b/src/components/location/dto/create-location.dto.ts index 62ca5c58f2..6a0967596f 100644 --- a/src/components/location/dto/create-location.dto.ts +++ b/src/components/location/dto/create-location.dto.ts @@ -1,13 +1,7 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; -import { - type ID, - IdField, - type IdOf, - ISO31661Alpha3, - NameField, -} from '~/common'; +import { type ID, IdField, type IdOf, ISO31661Alpha3, NameField } from '~/common'; import { Transform } from '~/common/transform.decorator'; import { CreateDefinedFileVersionInput } from '../../file/dto'; import { LocationType } from './location-type.enum'; diff --git a/src/components/location/dto/update-location.dto.ts b/src/components/location/dto/update-location.dto.ts index d00f1afe39..c9ea6dc4ee 100644 --- a/src/components/location/dto/update-location.dto.ts +++ b/src/components/location/dto/update-location.dto.ts @@ -1,13 +1,7 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; -import { - type ID, - IdField, - ISO31661Alpha3, - NameField, - OptionalField, -} from '~/common'; +import { type ID, IdField, ISO31661Alpha3, NameField, OptionalField } from '~/common'; import { Transform } from '~/common/transform.decorator'; import { CreateDefinedFileVersionInput } from '../../file/dto'; import { LocationType } from './location-type.enum'; diff --git a/src/components/location/location.gel.repository.ts b/src/components/location/location.gel.repository.ts index 9a36ac4f0f..6146ee60ac 100644 --- a/src/components/location/location.gel.repository.ts +++ b/src/components/location/location.gel.repository.ts @@ -29,12 +29,7 @@ export class LocationGelRepository await this.db.run(query); } - async removeLocationFromNode( - label: string, - id: ID, - rel: string, - locationId: ID, - ) { + async removeLocationFromNode(label: string, id: ID, rel: string, locationId: ID) { const res = this.resources.getByGel(label); const node = e.cast(res.db, e.cast(e.uuid, id)); const location = e.cast(e.Location, e.cast(e.uuid, locationId)); @@ -54,9 +49,7 @@ export class LocationGelRepository ) { const res = this.resources.getByGel(label); const node = e.cast(res.db, e.cast(e.uuid, id)); - const locations = e.select(node)[ - rel as keyof typeof node - ] as typeof e.Location; + const locations = e.select(node)[rel as keyof typeof node] as typeof e.Location; if (!locations) { throw new ServerException(`${label} does not have a "${rel}" link`); } diff --git a/src/components/location/location.loader.ts b/src/components/location/location.loader.ts index d8a29fff76..ee93d42fad 100644 --- a/src/components/location/location.loader.ts +++ b/src/components/location/location.loader.ts @@ -4,9 +4,7 @@ import { Location } from './dto'; import { LocationService } from './location.service'; @LoaderFactory(() => Location) -export class LocationLoader - implements DataLoaderStrategy> -{ +export class LocationLoader implements DataLoaderStrategy> { constructor(private readonly locations: LocationService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/location/location.repository.ts b/src/components/location/location.repository.ts index 90d92f7e56..ce70cc9d3a 100644 --- a/src/components/location/location.repository.ts +++ b/src/components/location/location.repository.ts @@ -42,10 +42,7 @@ export class LocationRepository extends DtoRepository(Location) { async create(input: CreateLocation) { const checkName = await this.doesNameExist(input.name); if (checkName) { - throw new DuplicateException( - 'location.name', - 'Location with this name already exists.', - ); + throw new DuplicateException('location.name', 'Location with this name already exists.'); } const mapImageId = await generateId(); @@ -76,9 +73,7 @@ export class LocationRepository extends DtoRepository(Location) { } const dto = await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Location) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Location) : e; }); await this.files.createDefinedFile( @@ -107,21 +102,14 @@ export class LocationRepository extends DtoRepository(Location) { await this.updateProperties({ id }, simpleChanges); if (fundingAccountId !== undefined) { - await this.updateRelation( - 'fundingAccount', - 'FundingAccount', - id, - fundingAccountId, - ); + await this.updateRelation('fundingAccount', 'FundingAccount', id, fundingAccountId); } if (mapImage !== undefined) { const location = await this.readOne(id); if (!location.mapImage) { - throw new ServerException( - 'Expected map image file to be updated with the location', - ); + throw new ServerException('Expected map image file to be updated with the location'); } await this.files.createFileVersion({ @@ -131,21 +119,11 @@ export class LocationRepository extends DtoRepository(Location) { } if (defaultFieldRegionId !== undefined) { - await this.updateRelation( - 'defaultFieldRegion', - 'FieldRegion', - id, - defaultFieldRegionId, - ); + await this.updateRelation('defaultFieldRegion', 'FieldRegion', id, defaultFieldRegionId); } if (defaultMarketingRegionId !== undefined) { - await this.updateRelation( - 'defaultMarketingRegion', - 'Location', - id, - defaultMarketingRegionId, - ); + await this.updateRelation('defaultMarketingRegion', 'Location', id, defaultMarketingRegionId); } return await this.readOne(id); @@ -207,21 +185,12 @@ export class LocationRepository extends DtoRepository(Location) { .run(); } - async removeLocationFromNode( - label: string, - id: ID, - rel: string, - locationId: ID, - ) { + async removeLocationFromNode(label: string, id: ID, rel: string, locationId: ID) { await this.db .query() .matchNode('node', label, { id }) .matchNode('location', 'Location', { id: locationId }) - .match([ - node('node'), - relation('out', 'rel', rel, ACTIVE), - node('location'), - ]) + .match([node('node'), relation('out', 'rel', rel, ACTIVE), node('location')]) .setValues({ 'rel.active': false, }) @@ -273,11 +242,7 @@ export const locationFilters = filter.define(() => LocationFilters, { name: filter.fullText({ index: () => NameIndex, matchToNode: (q) => - q.match([ - node('node', 'Location'), - relation('out', '', undefined, ACTIVE), - node('match'), - ]), + q.match([node('node', 'Location'), relation('out', '', undefined, ACTIVE), node('match')]), }), }); diff --git a/src/components/location/location.resolver.ts b/src/components/location/location.resolver.ts index b4f02e1e66..bc3000a146 100644 --- a/src/components/location/location.resolver.ts +++ b/src/components/location/location.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { all as countries, whereAlpha3 } from 'iso-3166-1'; import { type ID, IdArg, ListArg, mapSecuredValue } from '~/common'; import { Loader, type LoaderOf } from '~/core'; @@ -62,9 +55,7 @@ export class LocationResolver { @Loader(FundingAccountLoader) fundingAccounts: LoaderOf, ): Promise { - return await mapSecuredValue(location.fundingAccount, ({ id }) => - fundingAccounts.load(id), - ); + return await mapSecuredValue(location.fundingAccount, ({ id }) => fundingAccounts.load(id)); } @ResolveField(() => SecuredFieldRegion) @@ -72,9 +63,7 @@ export class LocationResolver { @Parent() location: Location, @Loader(FieldRegionLoader) fieldRegions: LoaderOf, ): Promise { - return await mapSecuredValue(location.defaultFieldRegion, ({ id }) => - fieldRegions.load(id), - ); + return await mapSecuredValue(location.defaultFieldRegion, ({ id }) => fieldRegions.load(id)); } @ResolveField(() => SecuredLocation) @@ -97,8 +86,7 @@ export class LocationResolver { @ResolveField(() => IsoCountry, { nullable: true, - description: - "An ISO 3166-1 country, looked up by the `Location`'s `isoAlpha3` code", + description: "An ISO 3166-1 country, looked up by the `Location`'s `isoAlpha3` code", }) async isoCountry(@Parent() location: Location): Promise { const { value, canRead } = location.isoAlpha3; diff --git a/src/components/location/location.service.ts b/src/components/location/location.service.ts index 6532473dc3..1734423446 100644 --- a/src/components/location/location.service.ts +++ b/src/components/location/location.service.ts @@ -22,10 +22,7 @@ import { LocationRepository } from './location.repository'; @Injectable() export class LocationService { - constructor( - private readonly privileges: Privileges, - private readonly repo: LocationRepository, - ) {} + constructor(private readonly privileges: Privileges, private readonly repo: LocationRepository) {} async create(input: CreateLocation): Promise { this.privileges.for(Location).verifyCan('create'); @@ -92,12 +89,7 @@ export class LocationService { } } - async removeLocationFromNode( - label: string, - id: ID, - rel: string, - locationId: ID, - ) { + async removeLocationFromNode(label: string, id: ID, rel: string, locationId: ID) { try { await this.repo.removeLocationFromNode(label, id, rel, locationId); } catch (e) { diff --git a/src/components/location/migrations/default-marketing-region.migration.ts b/src/components/location/migrations/default-marketing-region.migration.ts index 8a17db8baa..0914c5b7d4 100644 --- a/src/components/location/migrations/default-marketing-region.migration.ts +++ b/src/components/location/migrations/default-marketing-region.migration.ts @@ -70,8 +70,7 @@ export class DefaultMarketingRegionMigration extends BaseMigration { .run(); for (const country of countries) { - const marketingRegionName = - fieldRegionNameToMarketingRegionName[country.fieldRegionName]; + const marketingRegionName = fieldRegionNameToMarketingRegionName[country.fieldRegionName]; const marketingRegionId = marketingRegionNameToId[marketingRegionName]; if (marketingRegionId === undefined) { continue; diff --git a/src/components/notification-system/system-notification.resolver.ts b/src/components/notification-system/system-notification.resolver.ts index a5c81d800d..dee53e03dd 100644 --- a/src/components/notification-system/system-notification.resolver.ts +++ b/src/components/notification-system/system-notification.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Field, - Int, - Mutation, - ObjectType, - Resolver, -} from '@nestjs/graphql'; +import { Args, Field, Int, Mutation, ObjectType, Resolver } from '@nestjs/graphql'; import { MarkdownScalar } from '~/common/markdown.scalar'; import { Privileges } from '../authorization'; import { NotificationService } from '../notifications'; diff --git a/src/components/notification-system/system-notification.strategy.ts b/src/components/notification-system/system-notification.strategy.ts index bfcea7b222..eb1e6a8642 100644 --- a/src/components/notification-system/system-notification.strategy.ts +++ b/src/components/notification-system/system-notification.strategy.ts @@ -6,8 +6,7 @@ import { SystemNotification } from './system-notification.dto'; @NotificationStrategy(SystemNotification) export class SystemNotificationStrategy extends INotificationStrategy { recipientsForNeo4j() { - return (query: Query) => - query.match(node('recipient', 'User')).return('recipient'); + return (query: Query) => query.match(node('recipient', 'User')).return('recipient'); } recipientsForGel() { diff --git a/src/components/notifications/dto/notification-list.input.ts b/src/components/notifications/dto/notification-list.input.ts index 5a3d006deb..a851deab92 100644 --- a/src/components/notifications/dto/notification-list.input.ts +++ b/src/components/notifications/dto/notification-list.input.ts @@ -1,10 +1,5 @@ import { Field, InputType, Int, ObjectType } from '@nestjs/graphql'; -import { - FilterField, - OptionalField, - PaginatedList, - PaginationInput, -} from '~/common'; +import { FilterField, OptionalField, PaginatedList, PaginationInput } from '~/common'; import { Notification } from './notification.dto'; @InputType() diff --git a/src/components/notifications/notification.gel.repository.ts b/src/components/notifications/notification.gel.repository.ts index 70d7ad8140..e905c36798 100644 --- a/src/components/notifications/notification.gel.repository.ts +++ b/src/components/notifications/notification.gel.repository.ts @@ -1,18 +1,9 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { mapValues, type Nil, setOf } from '@seedcompany/common'; -import { - EnhancedResource, - type ID, - type PublicOf, - type ResourceShape, -} from '~/common'; +import { EnhancedResource, type ID, type PublicOf, type ResourceShape } from '~/common'; import { e, RepoFor, type ScopeOf } from '~/core/gel'; import { mapToSetBlock } from '~/core/gel/query-util/map-to-set-block'; -import { - type MarkNotificationReadArgs, - Notification, - type NotificationListInput, -} from './dto'; +import { type MarkNotificationReadArgs, Notification, type NotificationListInput } from './dto'; import { type NotificationRepository as Neo4jRepository } from './notification.repository'; import { NotificationServiceImpl } from './notification.service'; @@ -37,9 +28,7 @@ export class NotificationRepository async onModuleInit() { await this.service.ready.wait(); - const basePointers = setOf( - Object.keys(this.resource.db.__element__.__pointers__), - ); + const basePointers = setOf(Object.keys(this.resource.db.__element__.__pointers__)); const hydrateConcretes = Object.assign( {}, ...[...this.service.strategyMap].flatMap(([type, strategy]) => { @@ -50,10 +39,7 @@ export class NotificationRepository const ownPointers = Object.keys(dbType.__element__.__pointers__).filter( (p) => !p.startsWith('<') && !basePointers.has(p), ); - return e.is( - dbType, - mapValues.fromList(ownPointers, () => true).asRecord, - ); + return e.is(dbType, mapValues.fromList(ownPointers, () => true).asRecord); }), ); (this as any).hydrate = e.shape(e.Notification, (notification) => ({ @@ -72,8 +58,7 @@ export class NotificationRepository const dbType = EnhancedResource.of(type as typeof Notification).db; const created = - strategy.insertForGel?.(input) ?? - e.insert(dbType, mapToSetBlock(dbType, input, false)); + strategy.insertForGel?.(input) ?? e.insert(dbType, mapToSetBlock(dbType, input, false)); const recipientsQuery = recipients ? e.cast(e.User, e.cast(e.uuid, e.set(...recipients))) @@ -130,8 +115,6 @@ export class NotificationRepository notification: ScopeOf, { filter }: NotificationListInput, ) { - return [ - filter?.unread != null && e.op(notification.unread, '=', filter.unread), - ]; + return [filter?.unread != null && e.op(notification.unread, '=', filter.unread)]; } } diff --git a/src/components/notifications/notification.module.ts b/src/components/notifications/notification.module.ts index f3a28e5555..f1935c5915 100644 --- a/src/components/notifications/notification.module.ts +++ b/src/components/notifications/notification.module.ts @@ -3,10 +3,7 @@ import { splitDb } from '~/core'; import { NotificationRepository as GelRepository } from './notification.gel.repository'; import { NotificationRepository as Neo4jRepository } from './notification.repository'; import { NotificationResolver } from './notification.resolver'; -import { - NotificationService, - NotificationServiceImpl, -} from './notification.service'; +import { NotificationService, NotificationServiceImpl } from './notification.service'; @Module({ providers: [ diff --git a/src/components/notifications/notification.repository.ts b/src/components/notifications/notification.repository.ts index bad4f808e9..50ef01636d 100644 --- a/src/components/notifications/notification.repository.ts +++ b/src/components/notifications/notification.repository.ts @@ -1,13 +1,6 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { type Nil } from '@seedcompany/common'; -import { - inArray, - isNull, - node, - not, - type Query, - relation, -} from 'cypher-query-builder'; +import { inArray, isNull, node, not, type Query, relation } from 'cypher-query-builder'; import { omit } from 'lodash'; import { DateTime } from 'luxon'; import { @@ -71,21 +64,11 @@ export class NotificationRepository extends CommonRepository { sub .apply((q) => recipients == null - ? q.subQuery( - this.service.getStrategy(type).recipientsForNeo4j(input), - ) - : q - .match(node('recipient', 'User')) - .where({ 'recipient.id': inArray(recipients) }), + ? q.subQuery(this.service.getStrategy(type).recipientsForNeo4j(input)) + : q.match(node('recipient', 'User')).where({ 'recipient.id': inArray(recipients) }), ) - .create([ - node('node'), - relation('out', '', 'recipient'), - node('recipient'), - ]) - .return<{ totalRecipients: number }>( - 'count(recipient) as totalRecipients', - ), + .create([node('node'), relation('out', '', 'recipient'), node('recipient')]) + .return<{ totalRecipients: number }>('count(recipient) as totalRecipients'), ) .subQuery('node', this.hydrate()) .return('dto, totalRecipients') @@ -164,11 +147,7 @@ export class NotificationRepository extends CommonRepository { undefined, )!; }) - .optionalMatch([ - node('node'), - relation('out', 'recipient', 'recipient'), - currentUser, - ]) + .optionalMatch([node('node'), relation('out', 'recipient', 'recipient'), currentUser]) .return<{ dto: UnsecuredDto }>( merge('node', 'extra', { __typename: 'node.type + "Notification"', diff --git a/src/components/notifications/notification.resolver.ts b/src/components/notifications/notification.resolver.ts index 9e4570b33c..bc9e3213d8 100644 --- a/src/components/notifications/notification.resolver.ts +++ b/src/components/notifications/notification.resolver.ts @@ -28,9 +28,7 @@ export class NotificationResolver { } @Mutation(() => Notification) - async readNotification( - @Args() input: MarkNotificationReadArgs, - ): Promise { + async readNotification(@Args() input: MarkNotificationReadArgs): Promise { return await this.service.markRead(input); } } diff --git a/src/components/notifications/notification.service.ts b/src/components/notifications/notification.service.ts index 62883bfde2..df86305f1e 100644 --- a/src/components/notifications/notification.service.ts +++ b/src/components/notifications/notification.service.ts @@ -1,18 +1,8 @@ import { DiscoveryService } from '@golevelup/nestjs-discovery'; -import { - forwardRef, - Inject, - Injectable, - type OnModuleInit, -} from '@nestjs/common'; +import { forwardRef, Inject, Injectable, type OnModuleInit } from '@nestjs/common'; import { mapEntries, type Nil } from '@seedcompany/common'; import Event from 'gel/dist/primitives/event.js'; -import { - type ID, - type ResourceShape, - ServerException, - type UnsecuredDto, -} from '~/common'; +import { type ID, type ResourceShape, ServerException, type UnsecuredDto } from '~/common'; import { type MarkNotificationReadArgs, type Notification, @@ -20,11 +10,7 @@ import { type NotificationListInput, } from './dto'; import { NotificationRepository } from './notification.repository'; -import { - INotificationStrategy, - type InputOf, - NotificationStrategy, -} from './notification.strategy'; +import { INotificationStrategy, type InputOf, NotificationStrategy } from './notification.strategy'; @Injectable() export abstract class NotificationService { @@ -52,14 +38,8 @@ export abstract class NotificationService { } @Injectable() -export class NotificationServiceImpl - extends NotificationService - implements OnModuleInit -{ - strategyMap: ReadonlyMap< - ResourceShape, - INotificationStrategy - >; +export class NotificationServiceImpl extends NotificationService implements OnModuleInit { + strategyMap: ReadonlyMap, INotificationStrategy>; readonly ready = new ((Event as any).default as typeof Event)(); constructor(private readonly discovery: DiscoveryService) { @@ -88,9 +68,9 @@ export class NotificationServiceImpl } async onModuleInit() { - const discovered = await this.discovery.providersWithMetaAtKey< - ResourceShape - >(NotificationStrategy.KEY); + const discovered = await this.discovery.providersWithMetaAtKey>( + NotificationStrategy.KEY, + ); this.strategyMap = mapEntries(discovered, ({ meta, discoveredClass }) => { const { instance } = discoveredClass; if (!(instance instanceof INotificationStrategy)) { diff --git a/src/components/organization/dto/list-organization.dto.ts b/src/components/organization/dto/list-organization.dto.ts index 15b5063bab..a69c6b07fe 100644 --- a/src/components/organization/dto/list-organization.dto.ts +++ b/src/components/organization/dto/list-organization.dto.ts @@ -18,9 +18,7 @@ export abstract class OrganizationFilters { } @InputType() -export class OrganizationListInput extends SortablePaginationInput< - keyof Organization ->({ +export class OrganizationListInput extends SortablePaginationInput({ defaultSort: 'name', }) { @FilterField(() => OrganizationFilters, { internal: true }) @@ -33,6 +31,4 @@ export class OrganizationListOutput extends PaginatedList(Organization) {} @ObjectType({ description: SecuredList.descriptionFor('organizations'), }) -export abstract class SecuredOrganizationList extends SecuredList( - Organization, -) {} +export abstract class SecuredOrganizationList extends SecuredList(Organization) {} diff --git a/src/components/organization/dto/organization-reach.dto.ts b/src/components/organization/dto/organization-reach.dto.ts index 9a05aa0602..27a699833c 100644 --- a/src/components/organization/dto/organization-reach.dto.ts +++ b/src/components/organization/dto/organization-reach.dto.ts @@ -14,6 +14,4 @@ export const OrganizationReach = makeEnum({ @ObjectType({ description: SecuredEnumList.descriptionFor('organization reach'), }) -export class SecuredOrganizationReach extends SecuredEnumList( - OrganizationReach, -) {} +export class SecuredOrganizationReach extends SecuredEnumList(OrganizationReach) {} diff --git a/src/components/organization/dto/organization-type.dto.ts b/src/components/organization/dto/organization-type.dto.ts index f7b0058328..10d45d72d9 100644 --- a/src/components/organization/dto/organization-type.dto.ts +++ b/src/components/organization/dto/organization-type.dto.ts @@ -8,18 +8,10 @@ export type OrganizationType = EnumType; export const OrganizationType = makeEnum({ name: 'OrganizationType', description: 'The type of organization', - values: [ - 'Church', - 'Parachurch', - 'Mission', - 'TranslationOrganization', - 'Alliance', - ], + values: ['Church', 'Parachurch', 'Mission', 'TranslationOrganization', 'Alliance'], }); @ObjectType({ description: SecuredEnumList.descriptionFor('organization types'), }) -export class SecuredOrganizationTypes extends SecuredEnumList( - OrganizationType, -) {} +export class SecuredOrganizationTypes extends SecuredEnumList(OrganizationType) {} diff --git a/src/components/organization/organization.loader.ts b/src/components/organization/organization.loader.ts index fa9f28e18f..fad74f86d5 100644 --- a/src/components/organization/organization.loader.ts +++ b/src/components/organization/organization.loader.ts @@ -4,9 +4,7 @@ import { Organization } from './dto'; import { OrganizationService } from './organization.service'; @LoaderFactory(() => Organization) -export class OrganizationLoader - implements DataLoaderStrategy> -{ +export class OrganizationLoader implements DataLoaderStrategy> { constructor(private readonly organizations: OrganizationService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/organization/organization.repository.ts b/src/components/organization/organization.repository.ts index 3fef1ba856..287f29e7f2 100644 --- a/src/components/organization/organization.repository.ts +++ b/src/components/organization/organization.repository.ts @@ -62,9 +62,7 @@ export class OrganizationRepository extends DtoRepository(Organization) { } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Organization) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Organization) : e; }); } @@ -157,11 +155,7 @@ export const organizationFilters = filter.define(() => OrganizationFilters, { name: filter.fullText({ index: () => OrgNameIndex, matchToNode: (q) => - q.match([ - node('node', 'Organization'), - relation('out', '', 'name', ACTIVE), - node('match'), - ]), + q.match([node('node', 'Organization'), relation('out', '', 'name', ACTIVE), node('match')]), minScore: 0.8, }), }); diff --git a/src/components/organization/organization.resolver.ts b/src/components/organization/organization.resolver.ts index e52a87ef30..0e7f64965b 100644 --- a/src/components/organization/organization.resolver.ts +++ b/src/components/organization/organization.resolver.ts @@ -1,19 +1,5 @@ -import { - Args, - ArgsType, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; -import { - firstLettersOfWords, - type ID, - IdArg, - IdField, - ListArg, -} from '~/common'; +import { Args, ArgsType, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { firstLettersOfWords, type ID, IdArg, IdField, ListArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { LocationLoader } from '../location'; import { LocationListInput, SecuredLocationList } from '../location/dto'; @@ -64,9 +50,7 @@ export class OrganizationResolver { @ResolveField(() => String, { nullable: true }) avatarLetters(@Parent() org: Organization): string | undefined { - return org.name.canRead && org.name.value - ? firstLettersOfWords(org.name.value) - : undefined; + return org.name.canRead && org.name.value ? firstLettersOfWords(org.name.value) : undefined; } @Query(() => OrganizationListOutput, { diff --git a/src/components/organization/organization.service.ts b/src/components/organization/organization.service.ts index 9eff85808b..ae31af60a8 100644 --- a/src/components/organization/organization.service.ts +++ b/src/components/organization/organization.service.ts @@ -1,17 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { - type ID, - type ObjectView, - ServerException, - type UnsecuredDto, -} from '~/common'; +import { type ID, type ObjectView, ServerException, type UnsecuredDto } from '~/common'; import { HandleIdLookup } from '~/core'; import { Privileges } from '../authorization'; import { LocationService } from '../location'; -import { - type LocationListInput, - type SecuredLocationList, -} from '../location/dto'; +import { type LocationListInput, type SecuredLocationList } from '../location/dto'; import { type CreateOrganization, Organization, @@ -106,10 +98,7 @@ export class OrganizationService { locationId, ); } catch (e) { - throw new ServerException( - 'Could not remove location from organization', - e, - ); + throw new ServerException('Could not remove location from organization', e); } } diff --git a/src/components/partner/dto/create-partner.dto.ts b/src/components/partner/dto/create-partner.dto.ts index cfaf81bbec..6bf70ed6e7 100644 --- a/src/components/partner/dto/create-partner.dto.ts +++ b/src/components/partner/dto/create-partner.dto.ts @@ -2,14 +2,7 @@ import { Field, ID as IDType, InputType, ObjectType } from '@nestjs/graphql'; import { Transform, Type } from 'class-transformer'; import { Matches, ValidateNested } from 'class-validator'; import { uniq } from 'lodash'; -import { - CalendarDate, - DateField, - type ID, - IdField, - IsId, - NameField, -} from '~/common'; +import { CalendarDate, DateField, type ID, IdField, IsId, NameField } from '~/common'; import { FinanceDepartmentIdBlockInput } from '../../finance/department/dto/id-blocks.input'; import { FinancialReportingType } from '../../partnership/dto/financial-reporting-type.enum'; import { ProjectType } from '../../project/dto/project-type.enum'; diff --git a/src/components/partner/dto/list-partner.dto.ts b/src/components/partner/dto/list-partner.dto.ts index c7395f49c0..f90daa568f 100644 --- a/src/components/partner/dto/list-partner.dto.ts +++ b/src/components/partner/dto/list-partner.dto.ts @@ -22,8 +22,7 @@ export abstract class PartnerFilters { readonly userId?: ID; @OptionalField({ - description: - 'Only partners that are pinned/unpinned by the requesting user', + description: 'Only partners that are pinned/unpinned by the requesting user', }) readonly pinned?: boolean; diff --git a/src/components/partner/dto/partner-type.enum.ts b/src/components/partner/dto/partner-type.enum.ts index 81537d8886..c636f51255 100644 --- a/src/components/partner/dto/partner-type.enum.ts +++ b/src/components/partner/dto/partner-type.enum.ts @@ -10,6 +10,4 @@ export const PartnerType = makeEnum({ @ObjectType({ description: SecuredEnumList.descriptionFor('partner types'), }) -export abstract class SecuredPartnerTypes extends SecuredEnumList( - PartnerType, -) {} +export abstract class SecuredPartnerTypes extends SecuredEnumList(PartnerType) {} diff --git a/src/components/partner/dto/partner.dto.ts b/src/components/partner/dto/partner.dto.ts index b410a35a2b..b60388d1d1 100644 --- a/src/components/partner/dto/partner.dto.ts +++ b/src/components/partner/dto/partner.dto.ts @@ -61,15 +61,11 @@ export class Partner extends Interfaces { readonly languageOfWiderCommunication: Secured | null>; - readonly fieldRegions: Required< - Secured>> - >; + readonly fieldRegions: Required>>>; readonly countries: Required>>>; - readonly languagesOfConsulting: Required< - Secured>> - >; + readonly languagesOfConsulting: Required>>>; @Field() readonly startDate: SecuredDateNullable; diff --git a/src/components/partner/partner.repository.ts b/src/components/partner/partner.repository.ts index 77657c4efa..b69a78bdb5 100644 --- a/src/components/partner/partner.repository.ts +++ b/src/components/partner/partner.repository.ts @@ -31,10 +31,7 @@ import { sortWith, } from '~/core/database/query'; import * as departmentIdBlockUtils from '../finance/department/neo4j.utils'; -import { - organizationFilters, - organizationSorters, -} from '../organization/organization.repository'; +import { organizationFilters, organizationSorters } from '../organization/organization.repository'; import { type CreatePartner, Partner, @@ -86,10 +83,7 @@ export class PartnerRepository extends DtoRepository(Partner) { createRelationships(Partner, 'out', { organization: ['Organization', input.organizationId], pointOfContact: ['User', input.pointOfContactId], - languageOfWiderCommunication: [ - 'Language', - input.languageOfWiderCommunicationId, - ], + languageOfWiderCommunication: ['Language', input.languageOfWiderCommunicationId], fieldRegions: ['FieldRegion', input.fieldRegions], countries: ['Location', input.countries], languagesOfConsulting: ['Language', input.languagesOfConsulting], @@ -103,9 +97,7 @@ export class PartnerRepository extends DtoRepository(Partner) { } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Partner) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Partner) : e; }); } @@ -124,12 +116,7 @@ export class PartnerRepository extends DtoRepository(Partner) { await this.updateProperties({ id }, simpleChanges); if (pointOfContactId !== undefined) { - await this.updateRelation( - 'pointOfContact', - 'User', - changes.id, - pointOfContactId, - ); + await this.updateRelation('pointOfContact', 'User', changes.id, pointOfContactId); } if (languageOfWiderCommunicationId) { @@ -149,9 +136,7 @@ export class PartnerRepository extends DtoRepository(Partner) { newList: countries, }); } catch (e) { - throw e instanceof InputException - ? e.withField('partner.countries') - : e; + throw e instanceof InputException ? e.withField('partner.countries') : e; } } @@ -163,9 +148,7 @@ export class PartnerRepository extends DtoRepository(Partner) { newList: fieldRegions, }); } catch (e) { - throw e instanceof InputException - ? e.withField('partner.fieldRegions') - : e; + throw e instanceof InputException ? e.withField('partner.fieldRegions') : e; } } @@ -177,9 +160,7 @@ export class PartnerRepository extends DtoRepository(Partner) { newList: languagesOfConsulting, }); } catch (e) { - throw e instanceof InputException - ? e.withField('partner.languagesOfConsulting') - : e; + throw e instanceof InputException ? e.withField('partner.languagesOfConsulting') : e; } } @@ -237,11 +218,7 @@ export class PartnerRepository extends DtoRepository(Partner) { ) .subQuery('node', (sub) => sub - .match([ - node('node'), - relation('out', '', 'countries'), - node('countries', 'Location'), - ]) + .match([node('node'), relation('out', '', 'countries'), node('countries', 'Location')]) .return(collect('countries { .id }').as('countries')), ) .subQuery('node', (sub) => @@ -251,11 +228,7 @@ export class PartnerRepository extends DtoRepository(Partner) { relation('out', '', 'languagesOfConsulting', ACTIVE), node('languagesOfConsulting', 'Language'), ]) - .return( - collect('languagesOfConsulting { .id }').as( - 'languagesOfConsulting', - ), - ), + .return(collect('languagesOfConsulting { .id }').as('languagesOfConsulting')), ) .apply(matchProps()) .optionalMatch([ @@ -280,8 +253,7 @@ export class PartnerRepository extends DtoRepository(Partner) { sensitivity: 'sensitivity', organization: 'organization { .id }', pointOfContact: 'pointOfContact { .id }', - languageOfWiderCommunication: - 'languageOfWiderCommunication { .id }', + languageOfWiderCommunication: 'languageOfWiderCommunication { .id }', fieldRegions: 'fieldRegions', countries: 'countries', languagesOfConsulting: 'languagesOfConsulting', @@ -328,11 +300,7 @@ export const partnerFilters = filters.define(() => PartnerFilters, { node('', 'User', { id }), ]), organization: filter.sub(() => organizationFilters)((sub) => - sub.match([ - node('outer'), - relation('out', '', 'organization'), - node('node', 'Organization'), - ]), + sub.match([node('outer'), relation('out', '', 'organization'), node('node', 'Organization')]), ), types: filter.intersectsProp(), financialReportingTypes: filter.intersectsProp(), @@ -356,10 +324,6 @@ export const partnerSorters = defineSorters(Partner, { 'organization.*': (query, input) => query .with('node as partner') - .match([ - node('partner'), - relation('out', '', 'organization'), - node('node', 'Organization'), - ]) + .match([node('partner'), relation('out', '', 'organization'), node('node', 'Organization')]) .apply(sortWith(organizationSorters, input)), }); diff --git a/src/components/partner/partner.resolver.ts b/src/components/partner/partner.resolver.ts index b285523d24..fce9df2485 100644 --- a/src/components/partner/partner.resolver.ts +++ b/src/components/partner/partner.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg, @@ -77,9 +70,7 @@ export class PartnerResolver { @Parent() partner: Partner, @Loader(OrganizationLoader) organizations: LoaderOf, ): Promise { - return await mapSecuredValue(partner.organization, ({ id }) => - organizations.load(id), - ); + return await mapSecuredValue(partner.organization, ({ id }) => organizations.load(id)); } @ResolveField(() => SecuredUser) @@ -87,9 +78,7 @@ export class PartnerResolver { @Parent() partner: Partner, @Loader(UserLoader) users: LoaderOf, ): Promise { - return await mapSecuredValue(partner.pointOfContact, ({ id }) => - users.load(id), - ); + return await mapSecuredValue(partner.pointOfContact, ({ id }) => users.load(id)); } @ResolveField(() => SecuredLanguageNullable) @@ -97,9 +86,8 @@ export class PartnerResolver { @Parent() partner: Partner, @Loader(LanguageLoader) languages: LoaderOf, ): Promise { - return await mapSecuredValue( - partner.languageOfWiderCommunication, - ({ id }) => languages.load({ id, view: { active: true } }), + return await mapSecuredValue(partner.languageOfWiderCommunication, ({ id }) => + languages.load({ id, view: { active: true } }), ); } diff --git a/src/components/partner/partner.service.ts b/src/components/partner/partner.service.ts index 3ae475a65c..f1f258ed7b 100644 --- a/src/components/partner/partner.service.ts +++ b/src/components/partner/partner.service.ts @@ -13,19 +13,12 @@ import { Privileges } from '../authorization'; import { EngagementService } from '../engagement'; import { type EngagementListInput } from '../engagement/dto'; import { LanguageService } from '../language'; -import { - type LanguageListInput, - type SecuredLanguageList, -} from '../language/dto'; +import { type LanguageListInput, type SecuredLanguageList } from '../language/dto'; import { LocationLoader } from '../location'; import { type Location, LocationType } from '../location/dto'; import { type FinancialReportingType } from '../partnership/dto'; import { ProjectService } from '../project'; -import { - IProject, - type ProjectListInput, - type SecuredProjectList, -} from '../project/dto'; +import { IProject, type ProjectListInput, type SecuredProjectList } from '../project/dto'; import { type CreatePartner, Partner, @@ -51,10 +44,7 @@ export class PartnerService { ) {} async create(input: CreatePartner): Promise { - this.verifyFinancialReportingType( - input.financialReportingTypes, - input.types, - ); + this.verifyFinancialReportingType(input.financialReportingTypes, input.types); if (input.countries) { await this.verifyCountries(input.countries); @@ -69,8 +59,7 @@ export class PartnerService { async readOnePartnerByOrgId(id: ID): Promise { const partnerId = await this.repo.partnerIdByOrg(id); - if (!partnerId) - throw new NotFoundException('No Partner Exists for this Org Id'); + if (!partnerId) throw new NotFoundException('No Partner Exists for this Org Id'); return await this.readOne(partnerId); } @@ -156,10 +145,7 @@ export class PartnerService { }; } - async listProjects( - partner: Partner, - input: ProjectListInput, - ): Promise { + async listProjects(partner: Partner, input: ProjectListInput): Promise { const projectListOutput = await this.projectService.list({ ...input, filter: { ...input.filter, partnerId: partner.id }, @@ -172,10 +158,7 @@ export class PartnerService { }; } - async listLanguages( - partner: Partner, - input: LanguageListInput, - ): Promise { + async listLanguages(partner: Partner, input: LanguageListInput): Promise { const languageListOutput = await this.languageService.list({ ...input, filter: { ...input.filter, partnerId: partner.id }, @@ -210,10 +193,7 @@ export class PartnerService { financialReportingTypes: readonly FinancialReportingType[] | undefined, types: readonly PartnerType[] | undefined, ) { - return financialReportingTypes?.length && - !types?.includes(PartnerType.Managing) - ? false - : true; + return financialReportingTypes?.length && !types?.includes(PartnerType.Managing) ? false : true; } private async verifyCountries(ids: ReadonlyArray>) { @@ -231,10 +211,7 @@ export class PartnerService { } class LocationTypeException extends InputException { - constructor( - readonly allowedTypes: readonly LocationType[], - readonly invalidIds: ID[], - ) { + constructor(readonly allowedTypes: readonly LocationType[], readonly invalidIds: ID[]) { super('Given locations do not match the expected type'); } } diff --git a/src/components/partnership-producing-medium/dto/partnership-producing-medium.dto.ts b/src/components/partnership-producing-medium/dto/partnership-producing-medium.dto.ts index 6210210da1..c15b0be6ea 100644 --- a/src/components/partnership-producing-medium/dto/partnership-producing-medium.dto.ts +++ b/src/components/partnership-producing-medium/dto/partnership-producing-medium.dto.ts @@ -11,9 +11,7 @@ export class PartnershipProducingMedium { } @ObjectType() -export class SecuredPartnershipsProducingMediums extends SecuredList( - PartnershipProducingMedium, -) {} +export class SecuredPartnershipsProducingMediums extends SecuredList(PartnershipProducingMedium) {} @InputType() export class PartnershipProducingMediumInput { diff --git a/src/components/partnership-producing-medium/partnership-producing-medium.repository.ts b/src/components/partnership-producing-medium/partnership-producing-medium.repository.ts index 6eb28b81c2..a8f85738ce 100644 --- a/src/components/partnership-producing-medium/partnership-producing-medium.repository.ts +++ b/src/components/partnership-producing-medium/partnership-producing-medium.repository.ts @@ -29,9 +29,7 @@ export class PartnershipProducingMediumRepository extends CommonRepository { 'keys(apoc.coll.frequenciesAsMap(apoc.coll.flatten(collect(mediumsNode.value)))) as mediums', ) .return( - merge( - '[medium in mediums | apoc.map.fromValues([medium, null])]', - ).as('allAvailable'), + merge('[medium in mediums | apoc.map.fromValues([medium, null])]').as('allAvailable'), ), ) .comment('Grab all the defined producing partnership pairs') @@ -43,14 +41,10 @@ export class PartnershipProducingMediumRepository extends CommonRepository { node('partnership', 'Partnership'), ]) .return( - merge( - collect(apoc.map.fromValues(['ppm.medium', 'partnership.id'])), - ).as('defined'), + merge(collect(apoc.map.fromValues(['ppm.medium', 'partnership.id']))).as('defined'), ), ) - .return<{ out: Record }>( - merge('allAvailable', 'defined').as('out'), - ) + .return<{ out: Record }>(merge('allAvailable', 'defined').as('out')) .first(); if (!res) { throw new NotFoundException('Engagement not found'); @@ -58,17 +52,12 @@ export class PartnershipProducingMediumRepository extends CommonRepository { return res.out; } - async update( - engagementId: ID, - input: readonly PartnershipProducingMediumInput[], - ) { + async update(engagementId: ID, input: readonly PartnershipProducingMediumInput[]) { const results = await this.db .query() .matchNode('eng', 'LanguageEngagement', { id: engagementId }) .unwind(input.slice(), 'input') - .comment( - "Deactivate all existing PPMs that don't match the current input", - ) + .comment("Deactivate all existing PPMs that don't match the current input") .subQuery(['eng', 'input'], (sub) => sub .optionalMatch([ diff --git a/src/components/partnership-producing-medium/partnership-producing-medium.resolver.ts b/src/components/partnership-producing-medium/partnership-producing-medium.resolver.ts index cffd2c58b3..d3dfcd7607 100644 --- a/src/components/partnership-producing-medium/partnership-producing-medium.resolver.ts +++ b/src/components/partnership-producing-medium/partnership-producing-medium.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { PartnershipLoader } from '../partnership'; @@ -42,8 +36,7 @@ export class PartnershipProducingMediumResolver { @Args({ name: 'input', type: () => [PartnershipProducingMediumInput], - description: - 'A partial list of changes to the partners producing which mediums', + description: 'A partial list of changes to the partners producing which mediums', }) input: readonly PartnershipProducingMediumInput[], ): Promise { diff --git a/src/components/partnership-producing-medium/partnership-producing-medium.service.ts b/src/components/partnership-producing-medium/partnership-producing-medium.service.ts index 15af06d97e..461f850b92 100644 --- a/src/components/partnership-producing-medium/partnership-producing-medium.service.ts +++ b/src/components/partnership-producing-medium/partnership-producing-medium.service.ts @@ -1,12 +1,7 @@ import { Injectable } from '@nestjs/common'; import { entries } from '@seedcompany/common'; import { uniqBy } from 'lodash'; -import { - type ID, - InputException, - SecuredList, - UnauthorizedException, -} from '~/common'; +import { type ID, InputException, SecuredList, UnauthorizedException } from '~/common'; import { ResourceResolver } from '~/core'; import { Privileges } from '../authorization'; import { LanguageEngagement } from '../engagement/dto'; @@ -25,9 +20,7 @@ export class PartnershipProducingMediumService { private readonly repo: PartnershipProducingMediumRepository, ) {} - async list( - engagement: LanguageEngagement, - ): Promise { + async list(engagement: LanguageEngagement): Promise { const perms = this.privileges.for(IProject, engagement as any); if (!perms.can('read', 'partnership')) { @@ -49,18 +42,12 @@ export class PartnershipProducingMediumService { }; } - async update( - engagementId: ID, - input: readonly PartnershipProducingMediumInput[], - ) { + async update(engagementId: ID, input: readonly PartnershipProducingMediumInput[]) { if (uniqBy(input, (pair) => pair.medium).length !== input.length) { throw new InputException('A medium can only be mentioned once'); } - const engagement = await this.resources.lookup( - LanguageEngagement, - engagementId, - ); + const engagement = await this.resources.lookup(LanguageEngagement, engagementId); const perms = this.privileges.for(IProject, engagement as any); diff --git a/src/components/partnership/dto/financial-reporting-type.enum.ts b/src/components/partnership/dto/financial-reporting-type.enum.ts index 85315f1cfe..d7354c4dd2 100644 --- a/src/components/partnership/dto/financial-reporting-type.enum.ts +++ b/src/components/partnership/dto/financial-reporting-type.enum.ts @@ -1,10 +1,5 @@ import { ObjectType } from '@nestjs/graphql'; -import { - type EnumType, - makeEnum, - SecuredEnum, - SecuredEnumList, -} from '~/common'; +import { type EnumType, makeEnum, SecuredEnum, SecuredEnumList } from '~/common'; export type FinancialReportingType = EnumType; export const FinancialReportingType = makeEnum({ @@ -22,7 +17,6 @@ export abstract class SecuredFinancialReportingTypes extends SecuredEnumList( @ObjectType({ description: SecuredEnum.descriptionFor('financial reporting type'), }) -export abstract class SecuredFinancialReportingType extends SecuredEnum( - FinancialReportingType, - { nullable: true }, -) {} +export abstract class SecuredFinancialReportingType extends SecuredEnum(FinancialReportingType, { + nullable: true, +}) {} diff --git a/src/components/partnership/dto/list-partnership.dto.ts b/src/components/partnership/dto/list-partnership.dto.ts index 8fb1cfb8bb..a85fd5c99d 100644 --- a/src/components/partnership/dto/list-partnership.dto.ts +++ b/src/components/partnership/dto/list-partnership.dto.ts @@ -28,9 +28,7 @@ export abstract class PartnershipFilters { } @InputType() -export class PartnershipListInput extends SortablePaginationInput< - keyof Partnership ->({ +export class PartnershipListInput extends SortablePaginationInput({ defaultSort: 'createdAt', }) { @FilterField(() => PartnershipFilters, { internal: true }) diff --git a/src/components/partnership/dto/partnership-agreement-status.enum.ts b/src/components/partnership/dto/partnership-agreement-status.enum.ts index ededd0aabf..b6f228f0f3 100644 --- a/src/components/partnership/dto/partnership-agreement-status.enum.ts +++ b/src/components/partnership/dto/partnership-agreement-status.enum.ts @@ -1,9 +1,7 @@ import { ObjectType } from '@nestjs/graphql'; import { type EnumType, makeEnum, SecuredEnum } from '~/common'; -export type PartnershipAgreementStatus = EnumType< - typeof PartnershipAgreementStatus ->; +export type PartnershipAgreementStatus = EnumType; export const PartnershipAgreementStatus = makeEnum({ name: 'PartnershipAgreementStatus', values: ['NotAttached', 'AwaitingSignature', 'Signed'], diff --git a/src/components/partnership/dto/partnership.dto.ts b/src/components/partnership/dto/partnership.dto.ts index bec5749aba..fe6cf55fbf 100644 --- a/src/components/partnership/dto/partnership.dto.ts +++ b/src/components/partnership/dto/partnership.dto.ts @@ -32,8 +32,7 @@ export class Partnership extends Interfaces { // why is this here? We have a relation to partner, not org... organization: Organization, } satisfies ResourceRelationsShape; - static readonly Parent = () => - import('../../project/dto').then((m) => m.IProject); + static readonly Parent = () => import('../../project/dto').then((m) => m.IProject); readonly project: LinkTo<'Project'>; diff --git a/src/components/partnership/dto/update-partnership.dto.ts b/src/components/partnership/dto/update-partnership.dto.ts index 4f5ca881f8..cde1063cd8 100644 --- a/src/components/partnership/dto/update-partnership.dto.ts +++ b/src/components/partnership/dto/update-partnership.dto.ts @@ -1,14 +1,7 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; -import { - type CalendarDate, - DateField, - type ID, - IdField, - ListField, - OptionalField, -} from '~/common'; +import { type CalendarDate, DateField, type ID, IdField, ListField, OptionalField } from '~/common'; import { ChangesetIdField } from '../../changeset'; import { CreateDefinedFileVersionInput } from '../../file/dto'; import { PartnerType } from '../../partner/dto'; diff --git a/src/components/partnership/handlers/apply-finalized-changeset-to-partnership.handler.ts b/src/components/partnership/handlers/apply-finalized-changeset-to-partnership.handler.ts index 1f6b9ea6a9..999077c04e 100644 --- a/src/components/partnership/handlers/apply-finalized-changeset-to-partnership.handler.ts +++ b/src/components/partnership/handlers/apply-finalized-changeset-to-partnership.handler.ts @@ -12,9 +12,7 @@ import { type SubscribedEvent = ChangesetFinalizingEvent; @EventsHandler(ChangesetFinalizingEvent) -export class ApplyFinalizedChangesetToPartnership - implements IEventHandler -{ +export class ApplyFinalizedChangesetToPartnership implements IEventHandler { constructor( private readonly db: DatabaseService, @Logger('partnership:change-request:finalized') @@ -41,11 +39,7 @@ export class ApplyFinalizedChangesetToPartnership relation('out', 'partnershipRel', 'partnership', ACTIVE), node('node', 'Partnership'), ]) - .apply( - changeset.applied - ? commitChangesetProps() - : rejectChangesetProps(), - ) + .apply(changeset.applied ? commitChangesetProps() : rejectChangesetProps()) .return('1 as one'), ) .return('project') @@ -79,10 +73,7 @@ export class ApplyFinalizedChangesetToPartnership // Remove deleting partnerships await this.removeDeletingPartnerships(changeset.id); } catch (exception) { - throw new ServerException( - 'Failed to apply changeset to partnership', - exception, - ); + throw new ServerException('Failed to apply changeset to partnership', exception); } } diff --git a/src/components/partnership/handlers/validate-partnership-date-overrides-on-project-change.handler.ts b/src/components/partnership/handlers/validate-partnership-date-overrides-on-project-change.handler.ts index 8c7ac922bc..a56392923b 100644 --- a/src/components/partnership/handlers/validate-partnership-date-overrides-on-project-change.handler.ts +++ b/src/components/partnership/handlers/validate-partnership-date-overrides-on-project-change.handler.ts @@ -35,10 +35,7 @@ export class ValidatePartnershipDateOverridesOnProjectChangeHandler ); if (!conflicts) return; const orgLoader = await this.resources.getLoader(OrganizationLoader); - const partnershipToOrg = mapEntries(partnerships, (p) => [ - p.id, - p.organization.id, - ]).asRecord; + const partnershipToOrg = mapEntries(partnerships, (p) => [p.id, p.organization.id]).asRecord; const orgs = await orgLoader.loadMany( conflicts.map((conflict) => partnershipToOrg[conflict.id]), ); diff --git a/src/components/partnership/partnership-by-project-and-partner.loader.ts b/src/components/partnership/partnership-by-project-and-partner.loader.ts index a017015c4a..73eaaa86a5 100644 --- a/src/components/partnership/partnership-by-project-and-partner.loader.ts +++ b/src/components/partnership/partnership-by-project-and-partner.loader.ts @@ -1,9 +1,5 @@ import { type ID } from '~/common'; -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; import { type Partnership } from './dto'; import { PartnershipService } from './partnership.service'; diff --git a/src/components/partnership/partnership.gel.repository.ts b/src/components/partnership/partnership.gel.repository.ts index a84f62adc1..d097fa55c6 100644 --- a/src/components/partnership/partnership.gel.repository.ts +++ b/src/components/partnership/partnership.gel.repository.ts @@ -17,9 +17,7 @@ export class PartnershipGelRepository agreement: true, parent: e.select({ identity: partnership.project.id, - labels: e.array_agg( - e.set(partnership.project.__type__.name.slice(9, null)), - ), + labels: e.array_agg(e.set(partnership.project.__type__.name.slice(9, null))), properties: e.select({ id: partnership.project.id, createdAt: partnership.project.createdAt, @@ -29,9 +27,7 @@ export class PartnershipGelRepository }) implements PublicOf { - async readManyByProjectAndPartner( - input: readonly PartnershipByProjectAndPartnerInput[], - ) { + async readManyByProjectAndPartner(input: readonly PartnershipByProjectAndPartnerInput[]) { return await this.db.run(this.readManyByProjectAndPartnerQuery, { input }); } private readonly readManyByProjectAndPartnerQuery = e.params( @@ -53,9 +49,8 @@ export class PartnershipGelRepository async listAllByProjectId(project: ID) { return await this.db.run(this.listAllByProjectIdQuery, { project }); } - private readonly listAllByProjectIdQuery = e.params( - { project: e.uuid }, - ($) => e.select(e.cast(e.Project, $.project).partnerships, this.hydrate), + private readonly listAllByProjectIdQuery = e.params({ project: e.uuid }, ($) => + e.select(e.cast(e.Project, $.project).partnerships, this.hydrate), ); async isFirstPartnership(projectId: ID) { diff --git a/src/components/partnership/partnership.repository.ts b/src/components/partnership/partnership.repository.ts index cf789c7753..c221f50216 100644 --- a/src/components/partnership/partnership.repository.ts +++ b/src/components/partnership/partnership.repository.ts @@ -46,10 +46,9 @@ import { import type { PartnershipByProjectAndPartnerInput } from './partnership-by-project-and-partner.loader'; @Injectable() -export class PartnershipRepository extends DtoRepository< - typeof Partnership, - [view?: ObjectView] ->(Partnership) { +export class PartnershipRepository extends DtoRepository( + Partnership, +) { constructor(private readonly files: FileService) { super(); } @@ -61,8 +60,7 @@ export class PartnershipRepository extends DtoRepository< const agreementId = await generateId(); const initialProps = { - agreementStatus: - input.agreementStatus || PartnershipAgreementStatus.NotAttached, + agreementStatus: input.agreementStatus || PartnershipAgreementStatus.NotAttached, agreement: agreementId, mou: mouId, mouStatus: input.mouStatus || PartnershipAgreementStatus.NotAttached, @@ -115,10 +113,7 @@ export class PartnershipRepository extends DtoRepository< return result; } - async update( - changes: Omit, - changeset?: ID, - ) { + async update(changes: Omit, changeset?: ID) { const { id, ...simpleChanges } = changes; await this.updateProperties({ id }, simpleChanges, changeset); @@ -132,11 +127,7 @@ export class PartnershipRepository extends DtoRepository< .query() .subQuery((sub) => sub - .match([ - node('project'), - relation('out', '', 'partnership', ACTIVE), - node('node', label), - ]) + .match([node('project'), relation('out', '', 'partnership', ACTIVE), node('node', label)]) .raw('WHERE node.id in $ids', { ids }) .return('project, node') .apply((q) => @@ -160,9 +151,7 @@ export class PartnershipRepository extends DtoRepository< .run(); } - async readManyByProjectAndPartner( - input: readonly PartnershipByProjectAndPartnerInput[], - ) { + async readManyByProjectAndPartner(input: readonly PartnershipByProjectAndPartnerInput[]) { return await this.db .query() .unwind([...input], 'input') @@ -278,11 +267,7 @@ export class PartnershipRepository extends DtoRepository< return result!; // result from paginate() will always have 1 row. } - private async verifyRelationshipEligibility( - projectId: ID, - partnerId: ID, - changeset?: ID, - ) { + private async verifyRelationshipEligibility(projectId: ID, partnerId: ID, changeset?: ID) { const result = (await this.db .query() @@ -326,17 +311,11 @@ export class PartnershipRepository extends DtoRepository< .first()) ?? {}; if (!result.project) { - throw new NotFoundException( - 'Could not find project', - 'partnership.projectId', - ); + throw new NotFoundException('Could not find project', 'partnership.projectId'); } if (!result.partner) { - throw new NotFoundException( - 'Could not find partner', - 'partnership.partnerId', - ); + throw new NotFoundException('Could not find partner', 'partnership.partnerId'); } if (result.partnership) { @@ -437,11 +416,7 @@ export const partnershipFilters = filter.define(() => PartnershipFilters, { projectId: filter.skip, types: filter.intersectsProp(), partner: filter.sub(() => partnerFilters)((sub) => - sub.match([ - node('outer'), - relation('out', '', 'partner'), - node('node', 'Partner'), - ]), + sub.match([node('outer'), relation('out', '', 'partner'), node('node', 'Partner')]), ), }); @@ -450,10 +425,6 @@ export const partnershipSorters = defineSorters(Partnership, { 'partner.*': (query, input) => query .with('node as partnership') - .match([ - node('partnership'), - relation('out', '', 'partner'), - node('node', 'Partner'), - ]) + .match([node('partnership'), relation('out', '', 'partner'), node('node', 'Partner')]) .apply(sortWith(partnerSorters, input)), }); diff --git a/src/components/partnership/partnership.resolver.ts b/src/components/partnership/partnership.resolver.ts index 7b4262f716..bf745dc327 100644 --- a/src/components/partnership/partnership.resolver.ts +++ b/src/components/partnership/partnership.resolver.ts @@ -1,17 +1,5 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; -import { - ListArg, - mapSecuredValue, - SecuredDateRange, - viewOfChangeset, -} from '~/common'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { ListArg, mapSecuredValue, SecuredDateRange, viewOfChangeset } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { ChangesetIds, type IdsAndView, IdsAndViewArg } from '../changeset/dto'; import { FileNodeLoader, resolveDefinedFile } from '../file'; @@ -79,9 +67,7 @@ export class PartnershipResolver { @Parent() partnership: Partnership, @Loader(PartnerLoader) partners: LoaderOf, ): Promise { - return await mapSecuredValue(partnership.partner, ({ id }) => - partners.load(id), - ); + return await mapSecuredValue(partnership.partner, ({ id }) => partners.load(id)); } @ResolveField() @@ -91,10 +77,7 @@ export class PartnershipResolver { @ResolveField() mouRangeOverride(@Parent() partnership: Partnership): SecuredDateRange { - return SecuredDateRange.fromPair( - partnership.mouStartOverride, - partnership.mouEndOverride, - ); + return SecuredDateRange.fromPair(partnership.mouStartOverride, partnership.mouEndOverride); } @Query(() => PartnershipListOutput, { @@ -116,10 +99,7 @@ export class PartnershipResolver { async updatePartnership( @Args('input') { partnership: input, changeset }: UpdatePartnershipInput, ): Promise { - const partnership = await this.service.update( - input, - viewOfChangeset(changeset), - ); + const partnership = await this.service.update(input, viewOfChangeset(changeset)); return { partnership }; } diff --git a/src/components/partnership/partnership.service.ts b/src/components/partnership/partnership.service.ts index f60ef8dcb7..94b95ba3a7 100644 --- a/src/components/partnership/partnership.service.ts +++ b/src/components/partnership/partnership.service.ts @@ -13,13 +13,7 @@ import { type UnsecuredDto, viewOfChangeset, } from '~/common'; -import { - HandleIdLookup, - IEventBus, - ILogger, - Logger, - ResourceLoader, -} from '~/core'; +import { HandleIdLookup, IEventBus, ILogger, Logger, ResourceLoader } from '~/core'; import { type AnyChangesOf } from '~/core/database/changes'; import { Privileges } from '../authorization'; import { FileService } from '../file'; @@ -62,18 +56,11 @@ export class PartnershipService { PartnershipDateRangeException.throwIfInvalid(input); - const isFirstPartnership = await this.repo.isFirstPartnership( - projectId, - changeset, - ); + const isFirstPartnership = await this.repo.isFirstPartnership(projectId, changeset); const primary = isFirstPartnership ? true : input.primary; const partner = await this.partnerService.readOne(partnerId); - this.verifyFinancialReportingType( - input.financialReportingType, - input.types ?? [], - partner, - ); + this.verifyFinancialReportingType(input.financialReportingType, input.types ?? [], partner); try { const result = await this.repo.create( @@ -88,13 +75,8 @@ export class PartnershipService { await this.repo.removePrimaryFromOtherPartnerships(result.id); } - const partnership = await this.readOne( - result.id, - viewOfChangeset(changeset), - ).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Partnership) - : e; + const partnership = await this.readOne(result.id, viewOfChangeset(changeset)).catch((e) => { + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Partnership) : e; }); this.privileges.for(Partnership, partnership).verifyCan('create'); @@ -121,9 +103,7 @@ export class PartnershipService { return partnerships.map((dto) => this.secure(dto)); } - async readManyByProjectAndPartner( - input: readonly PartnershipByProjectAndPartnerInput[], - ) { + async readManyByProjectAndPartner(input: readonly PartnershipByProjectAndPartnerInput[]) { const partnerships = await this.repo.readManyByProjectAndPartner(input); return partnerships.map((dto) => ({ id: { project: dto.project.id, partner: dto.partner.id }, @@ -180,10 +160,7 @@ export class PartnershipService { await this.repo.removePrimaryFromOtherPartnerships(input.id); } - await this.repo.update( - { id: object.id, ...simpleChanges }, - view?.changeset, - ); + await this.repo.update({ id: object.id, ...simpleChanges }, view?.changeset); // TODO: remove negation. Temporary fix until file handling is refactored if (!object.mou) { @@ -191,11 +168,7 @@ export class PartnershipService { } // TODO: remove negation. Temporary fix until file handling is refactored if (!object.agreement) { - await this.files.updateDefinedFile( - object.agreement, - 'partnership.agreement', - agreement, - ); + await this.files.updateDefinedFile(object.agreement, 'partnership.agreement', agreement); } const partnership = await this.readOne(input.id, view); @@ -234,10 +207,7 @@ export class PartnershipService { partialInput: Partial, changeset?: ID, ): Promise { - const input = PartnershipListInput.defaultValue( - PartnershipListInput, - partialInput, - ); + const input = PartnershipListInput.defaultValue(PartnershipListInput, partialInput); const results = await this.repo.list(input, changeset); return { ...results, @@ -253,9 +223,7 @@ export class PartnershipService { if (!financialReportingType) { return; } - if ( - !partner.financialReportingTypes.value?.includes(financialReportingType) - ) { + if (!partner.financialReportingTypes.value?.includes(financialReportingType)) { throw new InputException( `Partner does not have this financial reporting type available`, 'partnership.financialReportingType', @@ -272,19 +240,13 @@ export class PartnershipService { class PartnershipDateRangeException extends RangeException { static throwIfInvalid( - current: Partial< - Pick, 'mouStartOverride' | 'mouEndOverride'> - >, + current: Partial, 'mouStartOverride' | 'mouEndOverride'>>, changes: AnyChangesOf = {}, ) { const start = - changes.mouStartOverride !== undefined - ? changes.mouStartOverride - : current.mouStartOverride; + changes.mouStartOverride !== undefined ? changes.mouStartOverride : current.mouStartOverride; const end = - changes.mouEndOverride !== undefined - ? changes.mouEndOverride - : current.mouEndOverride; + changes.mouEndOverride !== undefined ? changes.mouEndOverride : current.mouEndOverride; if (start && end && start > end) { const field = changes.mouEndOverride !== undefined @@ -295,8 +257,7 @@ class PartnershipDateRangeException extends RangeException { } constructor(readonly value: Range, readonly field: string) { - const message = - "Partnership's MOU start date must be before the MOU end date"; + const message = "Partnership's MOU start date must be before the MOU end date"; super({ message, field }); } } diff --git a/src/components/periodic-report/dto/list-periodic-reports.dto.ts b/src/components/periodic-report/dto/list-periodic-reports.dto.ts index 1ff85f12f1..ff12bb72ea 100644 --- a/src/components/periodic-report/dto/list-periodic-reports.dto.ts +++ b/src/components/periodic-report/dto/list-periodic-reports.dto.ts @@ -2,21 +2,13 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; import { stripIndent } from 'common-tags'; -import { - DateFilter, - type ID, - PaginatedList, - SecuredList, - SortablePaginationInput, -} from '~/common'; +import { DateFilter, type ID, PaginatedList, SecuredList, SortablePaginationInput } from '~/common'; import { type PeriodicReport } from './merge-periodic-reports.dto'; import { IPeriodicReport } from './periodic-report.dto'; import { ReportType } from './report-type.enum'; @InputType() -export class PeriodicReportListInput extends SortablePaginationInput< - keyof PeriodicReport ->({ +export class PeriodicReportListInput extends SortablePaginationInput({ defaultSort: 'start', }) { @Field(() => ReportType, { @@ -48,12 +40,12 @@ export class PeriodicReportListInput extends SortablePaginationInput< } @ObjectType() -export class PeriodicReportListOutput extends PaginatedList< +export class PeriodicReportListOutput extends PaginatedList( IPeriodicReport, - PeriodicReport ->(IPeriodicReport, { - itemsDescription: PaginatedList.itemDescriptionFor('periodic reports'), -}) {} + { + itemsDescription: PaginatedList.itemDescriptionFor('periodic reports'), + }, +) {} @ObjectType({ description: SecuredList.descriptionFor('periodic reports'), diff --git a/src/components/periodic-report/dto/report-period.enum.ts b/src/components/periodic-report/dto/report-period.enum.ts index a0102a42c9..2232420646 100644 --- a/src/components/periodic-report/dto/report-period.enum.ts +++ b/src/components/periodic-report/dto/report-period.enum.ts @@ -1,10 +1,5 @@ import { ObjectType } from '@nestjs/graphql'; -import { - type EnumType, - makeEnum, - SecuredEnum, - SecuredProperty, -} from '~/common'; +import { type EnumType, makeEnum, SecuredEnum, SecuredProperty } from '~/common'; export type ReportPeriod = EnumType; export const ReportPeriod = makeEnum({ diff --git a/src/components/periodic-report/events/periodic-report-uploaded.event.ts b/src/components/periodic-report/events/periodic-report-uploaded.event.ts index b65b250671..e06b9e8f98 100644 --- a/src/components/periodic-report/events/periodic-report-uploaded.event.ts +++ b/src/components/periodic-report/events/periodic-report-uploaded.event.ts @@ -7,10 +7,7 @@ import { type PeriodicReport } from '../dto'; * Dispatched when a new file is uploaded for a periodic report */ export class PeriodicReportUploadedEvent { - constructor( - readonly report: PeriodicReport, - readonly file: Downloadable, - ) {} + constructor(readonly report: PeriodicReport, readonly file: Downloadable) {} pnpResultUsed = false; @Once() get pnpResult() { diff --git a/src/components/periodic-report/handlers/abstract-periodic-report-sync.ts b/src/components/periodic-report/handlers/abstract-periodic-report-sync.ts index f7be2291dc..60bc0f584c 100644 --- a/src/components/periodic-report/handlers/abstract-periodic-report-sync.ts +++ b/src/components/periodic-report/handlers/abstract-periodic-report-sync.ts @@ -3,10 +3,7 @@ import { type CalendarDate, DateInterval, type ID, type Range } from '~/common'; import { type ReportType } from '../dto'; import { type PeriodicReportService } from '../periodic-report.service'; -export type Intervals = [ - updated: DateInterval | null, - previous: DateInterval | null, -]; +export type Intervals = [updated: DateInterval | null, previous: DateInterval | null]; export abstract class AbstractPeriodicReportSync { constructor(protected readonly periodicReports: PeriodicReportService) {} @@ -46,17 +43,11 @@ export abstract class AbstractPeriodicReportSync { unit: DateTimeUnit, ) { const fullUpdated = updated?.expandToFull(unit); - const diff = DateInterval.compare( - previous?.expandToFull(unit), - fullUpdated, - ); + const diff = DateInterval.compare(previous?.expandToFull(unit), fullUpdated); const splitByUnit = (range: DateInterval) => range.splitBy({ [unit]: 1 }); return { additions: diff.additions.flatMap(splitByUnit), - removals: [ - ...diff.removals.flatMap(splitByUnit), - ...this.invertedRange(fullUpdated), - ], + removals: [...diff.removals.flatMap(splitByUnit), ...this.invertedRange(fullUpdated)], }; } diff --git a/src/components/periodic-report/handlers/sync-periodic-report-to-project.handler.ts b/src/components/periodic-report/handlers/sync-periodic-report-to-project.handler.ts index 0915bceb4e..fc5a693ad7 100644 --- a/src/components/periodic-report/handlers/sync-periodic-report-to-project.handler.ts +++ b/src/components/periodic-report/handlers/sync-periodic-report-to-project.handler.ts @@ -5,10 +5,7 @@ import { projectRange } from '../../project/dto'; import { ProjectUpdatedEvent } from '../../project/events'; import { ReportPeriod, ReportType } from '../dto'; import { PeriodicReportService } from '../periodic-report.service'; -import { - AbstractPeriodicReportSync, - type Intervals, -} from './abstract-periodic-report-sync'; +import { AbstractPeriodicReportSync, type Intervals } from './abstract-periodic-report-sync'; type SubscribedEvent = ProjectUpdatedEvent; @@ -31,10 +28,7 @@ export class SyncPeriodicReportsToProjectDateRange }); const project = event.updated; - const intervals: Intervals = [ - projectRange(project), - projectRange(event.previous), - ]; + const intervals: Intervals = [projectRange(project), projectRange(event.previous)]; const narrativeDiff = this.diffBy(...intervals, 'quarter'); await this.sync( @@ -60,9 +54,7 @@ export class SyncPeriodicReportsToProjectDateRange const { updated, previous } = event; const newInterval: DateTimeUnit = - updated.financialReportPeriod === ReportPeriod.Monthly - ? 'month' - : 'quarter'; + updated.financialReportPeriod === ReportPeriod.Monthly ? 'month' : 'quarter'; if (updated.financialReportPeriod === previous.financialReportPeriod) { const diff = this.diffBy(...intervals, newInterval); diff --git a/src/components/periodic-report/handlers/sync-progress-report-to-engagement.handler.ts b/src/components/periodic-report/handlers/sync-progress-report-to-engagement.handler.ts index 2ddf09617c..b599fa9fb7 100644 --- a/src/components/periodic-report/handlers/sync-progress-report-to-engagement.handler.ts +++ b/src/components/periodic-report/handlers/sync-progress-report-to-engagement.handler.ts @@ -2,28 +2,15 @@ import { DateInterval, type UnsecuredDto } from '~/common'; import { EventsHandler, type IEventHandler, ILogger, Logger } from '~/core'; import { EngagementService } from '../../engagement'; import { type Engagement, engagementRange } from '../../engagement/dto'; -import { - EngagementCreatedEvent, - EngagementUpdatedEvent, -} from '../../engagement/events'; +import { EngagementCreatedEvent, EngagementUpdatedEvent } from '../../engagement/events'; import { ProjectUpdatedEvent } from '../../project/events'; import { ReportType } from '../dto'; import { PeriodicReportService } from '../periodic-report.service'; -import { - AbstractPeriodicReportSync, - type Intervals, -} from './abstract-periodic-report-sync'; +import { AbstractPeriodicReportSync, type Intervals } from './abstract-periodic-report-sync'; -type SubscribedEvent = - | EngagementCreatedEvent - | EngagementUpdatedEvent - | ProjectUpdatedEvent; +type SubscribedEvent = EngagementCreatedEvent | EngagementUpdatedEvent | ProjectUpdatedEvent; -@EventsHandler( - EngagementCreatedEvent, - EngagementUpdatedEvent, - ProjectUpdatedEvent, -) +@EventsHandler(EngagementCreatedEvent, EngagementUpdatedEvent, ProjectUpdatedEvent) export class SyncProgressReportToEngagementDateRange extends AbstractPeriodicReportSync implements IEventHandler @@ -40,11 +27,9 @@ export class SyncProgressReportToEngagementDateRange // Only LanguageEngagements if ( !( - ((event instanceof EngagementCreatedEvent || - event instanceof EngagementUpdatedEvent) && + ((event instanceof EngagementCreatedEvent || event instanceof EngagementUpdatedEvent) && event.isLanguageEngagement()) || - (event instanceof ProjectUpdatedEvent && - event.updated.type.includes('Translation')) + (event instanceof ProjectUpdatedEvent && event.updated.type.includes('Translation')) ) ) { return; diff --git a/src/components/periodic-report/periodic-report-project-connection.resolver.ts b/src/components/periodic-report/periodic-report-project-connection.resolver.ts index 852de02ece..8e32ee3720 100644 --- a/src/components/periodic-report/periodic-report-project-connection.resolver.ts +++ b/src/components/periodic-report/periodic-report-project-connection.resolver.ts @@ -1,10 +1,7 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { ListArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; -import { - PeriodicReportLoader, - PeriodicReportService, -} from '../periodic-report'; +import { PeriodicReportLoader, PeriodicReportService } from '../periodic-report'; import { IProject, type Project } from '../project/dto'; import { PeriodicReportListInput, @@ -54,13 +51,8 @@ export class PeriodicReportProjectConnectionResolver { description: 'The financial report currently due. This is the period that most recently completed.', }) - async currentFinancialReportDue( - @Parent() project: Project, - ): Promise { - const value = await this.service.getCurrentReportDue( - project.id, - ReportType.Financial, - ); + async currentFinancialReportDue(@Parent() project: Project): Promise { + const value = await this.service.getCurrentReportDue(project.id, ReportType.Financial); return { canRead: true, canEdit: false, @@ -72,13 +64,8 @@ export class PeriodicReportProjectConnectionResolver { description: 'The narrative report currently due. This is the period that most recently completed.', }) - async currentNarrativeReportDue( - @Parent() project: Project, - ): Promise { - const value = await this.service.getCurrentReportDue( - project.id, - ReportType.Narrative, - ); + async currentNarrativeReportDue(@Parent() project: Project): Promise { + const value = await this.service.getCurrentReportDue(project.id, ReportType.Narrative); return { canRead: true, canEdit: false, @@ -87,16 +74,10 @@ export class PeriodicReportProjectConnectionResolver { } @ResolveField(() => SecuredPeriodicReport, { - description: - 'The financial report due next. This is the period currently in progress.', + description: 'The financial report due next. This is the period currently in progress.', }) - async nextFinancialReportDue( - @Parent() project: Project, - ): Promise { - const value = await this.service.getNextReportDue( - project.id, - ReportType.Financial, - ); + async nextFinancialReportDue(@Parent() project: Project): Promise { + const value = await this.service.getNextReportDue(project.id, ReportType.Financial); return { canRead: true, canEdit: false, @@ -105,16 +86,10 @@ export class PeriodicReportProjectConnectionResolver { } @ResolveField(() => SecuredPeriodicReport, { - description: - 'The narrative report due next. This is the period currently in progress.', + description: 'The narrative report due next. This is the period currently in progress.', }) - async nextNarrativeReportDue( - @Parent() project: Project, - ): Promise { - const value = await this.service.getNextReportDue( - project.id, - ReportType.Narrative, - ); + async nextNarrativeReportDue(@Parent() project: Project): Promise { + const value = await this.service.getNextReportDue(project.id, ReportType.Narrative); return { canRead: true, canEdit: false, diff --git a/src/components/periodic-report/periodic-report.loader.ts b/src/components/periodic-report/periodic-report.loader.ts index d329084989..3f516b114f 100644 --- a/src/components/periodic-report/periodic-report.loader.ts +++ b/src/components/periodic-report/periodic-report.loader.ts @@ -1,20 +1,10 @@ import { type ID } from '~/common'; import { type DataLoaderStrategy, LoaderFactory } from '~/core/data-loader'; import { ProgressReport } from '../progress-report/dto'; -import { - FinancialReport, - IPeriodicReport, - NarrativeReport, - type PeriodicReport, -} from './dto'; +import { FinancialReport, IPeriodicReport, NarrativeReport, type PeriodicReport } from './dto'; import { PeriodicReportService } from './periodic-report.service'; -@LoaderFactory(() => [ - IPeriodicReport, - FinancialReport, - NarrativeReport, - ProgressReport, -]) +@LoaderFactory(() => [IPeriodicReport, FinancialReport, NarrativeReport, ProgressReport]) export class PeriodicReportLoader implements DataLoaderStrategy> { diff --git a/src/components/periodic-report/periodic-report.repository.ts b/src/components/periodic-report/periodic-report.repository.ts index b390bc779f..0ffe53b6a4 100644 --- a/src/components/periodic-report/periodic-report.repository.ts +++ b/src/components/periodic-report/periodic-report.repository.ts @@ -9,13 +9,7 @@ import { type Query, relation, } from 'cypher-query-builder'; -import { - type CalendarDate, - generateId, - type ID, - type Range, - type UnsecuredDto, -} from '~/common'; +import { type CalendarDate, generateId, type ID, type Range, type UnsecuredDto } from '~/common'; import { DtoRepository } from '~/core/database'; import { type ChangesOf } from '~/core/database/changes'; import { @@ -34,10 +28,7 @@ import { type Variable, } from '~/core/database/query'; import { File } from '../file/dto'; -import { - ProgressReport, - ProgressReportStatus as ProgressStatus, -} from '../progress-report/dto'; +import { ProgressReport, ProgressReportStatus as ProgressStatus } from '../progress-report/dto'; import { ProgressReportExtraForPeriodicInterfaceRepository, progressReportExtrasSorters, @@ -58,9 +49,7 @@ export class PeriodicReportRepository extends DtoRepository< [], PeriodicReport >(IPeriodicReport) { - constructor( - private readonly progressRepo: ProgressReportExtraForPeriodicInterfaceRepository, - ) { + constructor(private readonly progressRepo: ProgressReportExtraForPeriodicInterfaceRepository) { super(); } @@ -79,9 +68,7 @@ export class PeriodicReportRepository extends DtoRepository< ); const isProgress = input.type === ReportType.Progress; - const extraCreateOptions = isProgress - ? this.progressRepo.getCreateOptions(input) - : {}; + const extraCreateOptions = isProgress ? this.progressRepo.getCreateOptions(input) : {}; const query = this.db .query() @@ -164,27 +151,20 @@ export class PeriodicReportRepository extends DtoRepository< out: { createdBy: currentUser }, }), ) - .return<{ id: ID; interval: Range }>( - 'report.id as id, interval', - ); + .return<{ id: ID; interval: Range }>('report.id as id, interval'); return await query.run(); } async update>( existing: T, - simpleChanges: Omit< - ChangesOf, - 'reportFile' - > & + simpleChanges: Omit, 'reportFile'> & Partial>, ) { return await this.updateProperties(existing, simpleChanges); } async list(input: PeriodicReportListInput) { - const resource = input.type - ? resolveReportType({ type: input.type }) - : IPeriodicReport; + const resource = input.type ? resolveReportType({ type: input.type }) : IPeriodicReport; const { type, parent, start, end } = input; const filters = { type, parent, start, end }; const result = await this.db @@ -210,16 +190,8 @@ export class PeriodicReportRepository extends DtoRepository< relation('out', '', 'report', ACTIVE), node('node', `${reportType}Report`), ], - [ - node('node'), - relation('out', '', 'start', ACTIVE), - node('start', 'Property'), - ], - [ - node('node'), - relation('out', '', 'end', ACTIVE), - node('end', 'Property'), - ], + [node('node'), relation('out', '', 'start', ACTIVE), node('start', 'Property')], + [node('node'), relation('out', '', 'end', ACTIVE), node('end', 'Property')], ]) .where( and({ @@ -287,16 +259,8 @@ export class PeriodicReportRepository extends DtoRepository< relation('out', '', 'report', ACTIVE), node('node', `${type}Report`), ]) - .match([ - node('node'), - relation('out', '', 'start', ACTIVE), - node('start', 'Property'), - ]) - .match([ - node('node'), - relation('out', '', 'end', ACTIVE), - node('end', 'Property'), - ]) + .match([node('node'), relation('out', '', 'start', ACTIVE), node('start', 'Property')]) + .match([node('node'), relation('out', '', 'end', ACTIVE), node('end', 'Property')]) .raw(`where start.value = end.value`) .apply(this.hydrate()) .first(); @@ -326,16 +290,8 @@ export class PeriodicReportRepository extends DtoRepository< relation('out', '', 'report', ACTIVE), node('report', `${type}Report`), ], - [ - node('report'), - relation('out', '', 'start', ACTIVE), - node('start', 'Property'), - ], - [ - node('report'), - relation('out', '', 'end', ACTIVE), - node('end', 'Property'), - ], + [node('report'), relation('out', '', 'start', ACTIVE), node('start', 'Property')], + [node('report'), relation('out', '', 'end', ACTIVE), node('end', 'Property')], ]) .raw( ` @@ -359,10 +315,7 @@ export class PeriodicReportRepository extends DtoRepository< `, ) .subQuery('report', (sub) => - sub - .apply(deleteBaseNode('report')) - .return('node as somethingDeleted') - .raw('LIMIT 1'), + sub.apply(deleteBaseNode('report')).return('node as somethingDeleted').raw('LIMIT 1'), ) .return<{ count: number }>('count(report) as count') .first(); @@ -385,11 +338,7 @@ export class PeriodicReportRepository extends DtoRepository< ) .subQuery('node', (sub) => sub - .match([ - node('node'), - relation('in', '', 'report', ACTIVE), - node('project', 'Project'), - ]) + .match([node('node'), relation('in', '', 'report', ACTIVE), node('project', 'Project')]) .return('project') .union() .with('node') @@ -402,11 +351,7 @@ export class PeriodicReportRepository extends DtoRepository< ]) .return('project'), ) - .match([ - node('parent', 'BaseNode'), - relation('out', '', 'report', ACTIVE), - node('node'), - ]) + .match([node('parent', 'BaseNode'), relation('out', '', 'report', ACTIVE), node('node')]) .apply(matchPropsAndProjectSensAndScopedRoles()) .return<{ dto: UnsecuredDto }>( merge('props', { parent: 'parent' }, 'extra').as('dto'), @@ -415,8 +360,7 @@ export class PeriodicReportRepository extends DtoRepository< } export const matchCurrentDue = - (parentId: ID | Variable | undefined, reportType: ReportType) => - (query: Query) => + (parentId: ID | Variable | undefined, reportType: ReportType) => (query: Query) => query.comment`matchCurrentDue()` .match([ [ @@ -426,11 +370,7 @@ export const matchCurrentDue = relation('out', '', 'end', ACTIVE), node('end', 'Property'), ], - [ - node('node'), - relation('out', '', 'start', ACTIVE), - node('start', 'Property'), - ], + [node('node'), relation('out', '', 'start', ACTIVE), node('start', 'Property')], ]) .raw(`WHERE end.value < date()`) .with('node, start') diff --git a/src/components/periodic-report/periodic-report.resolver.ts b/src/components/periodic-report/periodic-report.resolver.ts index a554e65bce..f5e3171145 100644 --- a/src/components/periodic-report/periodic-report.resolver.ts +++ b/src/components/periodic-report/periodic-report.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { CalendarDate, ListArg, UnauthorizedException } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { Identity } from '~/core/authentication'; diff --git a/src/components/periodic-report/periodic-report.service.ts b/src/components/periodic-report/periodic-report.service.ts index 73f3e28f79..4324196337 100644 --- a/src/components/periodic-report/periodic-report.service.ts +++ b/src/components/periodic-report/periodic-report.service.ts @@ -51,9 +51,7 @@ export class PeriodicReportService { existing: input.intervals.length - result.length, new: result.length, parent: input.parent, - newIntervals: result.map(({ interval }) => - DateInterval.fromObject(interval).toISO(), - ), + newIntervals: result.map(({ interval }) => DateInterval.fromObject(interval).toISO()), }); } catch (exception) { const Report = resolveReportType({ type: input.type }); @@ -65,25 +63,16 @@ export class PeriodicReportService { const currentRaw = await this.repo.readOne(input.id); const current = this.secure(currentRaw); const changes = this.repo.getActualChanges(current, input); - this.privileges - .for(resolveReportType(current), currentRaw) - .verifyChanges(changes); + this.privileges.for(resolveReportType(current), currentRaw).verifyChanges(changes); const { reportFile, ...simpleChanges } = changes; const updated = await this.repo.update(current, simpleChanges); if (reportFile) { - const file = await this.files.updateDefinedFile( - current.reportFile, - 'file', - reportFile, - ); + const file = await this.files.updateDefinedFile(current.reportFile, 'file', reportFile); await this.eventBus.publish( - new PeriodicReportUploadedEvent( - updated, - this.files.asDownloadable(file.newVersion), - ), + new PeriodicReportUploadedEvent(updated, this.files.asDownloadable(file.newVersion)), ); } @@ -93,10 +82,7 @@ export class PeriodicReportService { @HandleIdLookup([FinancialReport, NarrativeReport, ProgressReport]) async readOne(id: ID, _view?: ObjectView): Promise { if (!id) { - throw new NotFoundException( - 'No periodic report id to search for', - 'periodicReport.id', - ); + throw new NotFoundException('No periodic report id to search for', 'periodicReport.id'); } const result = await this.repo.readOne(id); @@ -112,9 +98,7 @@ export class PeriodicReportService { return this.privileges.for(resolveReportType(dto)).secure(dto); } - async list( - input: PeriodicReportListInput, - ): Promise { + async list(input: PeriodicReportListInput): Promise { const results = await this.repo.list(input); return { @@ -131,20 +115,18 @@ export class PeriodicReportService { reportType: Type & ReportType, ): Promise { const report = await this.repo.getByDate(parentId, date, reportType); - return report - ? (this.secure(report) as PeriodicReportTypeMap[Type]) - : undefined; + return report ? (this.secure(report) as PeriodicReportTypeMap[Type]) : undefined; } async getCurrentReportDue( parentId: ID, reportType: Type & ReportType, ): Promise { - const report: UnsecuredDto | undefined = - await this.repo.getCurrentDue(parentId, reportType); - return report - ? (this.secure(report) as PeriodicReportTypeMap[Type]) - : undefined; + const report: UnsecuredDto | undefined = await this.repo.getCurrentDue( + parentId, + reportType, + ); + return report ? (this.secure(report) as PeriodicReportTypeMap[Type]) : undefined; } matchCurrentDue(parentId: ID | Variable, reportType: ReportType) { @@ -156,9 +138,7 @@ export class PeriodicReportService { reportType: Type & ReportType, ): Promise { const report = await this.repo.getNextDue(parentId, reportType); - return report - ? (this.secure(report) as PeriodicReportTypeMap[Type]) - : undefined; + return report ? (this.secure(report) as PeriodicReportTypeMap[Type]) : undefined; } async getLatestReportSubmitted( @@ -166,16 +146,10 @@ export class PeriodicReportService { type: Type & ReportType, ): Promise { const report = await this.repo.getLatestReportSubmitted(parentId, type); - return report - ? (this.secure(report) as PeriodicReportTypeMap[Type]) - : undefined; + return report ? (this.secure(report) as PeriodicReportTypeMap[Type]) : undefined; } - async delete( - parent: ID, - type: ReportType, - intervals: ReadonlyArray>, - ) { + async delete(parent: ID, type: ReportType, intervals: ReadonlyArray>) { intervals = intervals.filter((i) => i.start || i.end); if (intervals.length === 0) { return; @@ -185,19 +159,12 @@ export class PeriodicReportService { this.logger.info('Deleted reports', { parent, type, ...result }); } - async getFinalReport( - parentId: ID, - type: ReportType, - ): Promise { + async getFinalReport(parentId: ID, type: ReportType): Promise { const report = await this.repo.getFinalReport(parentId, type); return report ? this.secure(report) : undefined; } - async mergeFinalReport( - parentId: ID, - type: ReportType, - at: CalendarDate, - ): Promise { + async mergeFinalReport(parentId: ID, type: ReportType, at: CalendarDate): Promise { const report = await this.repo.getFinalReport(parentId, type); if (report) { diff --git a/src/components/pin/pin.gel.repository.ts b/src/components/pin/pin.gel.repository.ts index 2340375c62..f94c4df8f2 100644 --- a/src/components/pin/pin.gel.repository.ts +++ b/src/components/pin/pin.gel.repository.ts @@ -4,10 +4,7 @@ import { CommonRepository, e } from '~/core/gel'; import { type PinRepository } from './pin.repository'; @Injectable() -export class PinGelRepository - extends CommonRepository - implements PublicOf -{ +export class PinGelRepository extends CommonRepository implements PublicOf { async isPinned(id: ID) { const resource = e.cast(e.Mixin.Pinnable, e.uuid(id)); const query = e.op(resource, 'in', e.global.currentUser.pins); diff --git a/src/components/pin/pin.module.ts b/src/components/pin/pin.module.ts index accdfa3cda..fd5f69c70b 100644 --- a/src/components/pin/pin.module.ts +++ b/src/components/pin/pin.module.ts @@ -6,11 +6,7 @@ import { PinResolver } from './pin.resolver'; import { PinService } from './pin.service'; @Module({ - providers: [ - PinResolver, - PinService, - splitDb(PinRepository, PinGelRepository), - ], + providers: [PinResolver, PinService, splitDb(PinRepository, PinGelRepository)], exports: [PinService], }) export class PinModule {} diff --git a/src/components/pin/pin.repository.ts b/src/components/pin/pin.repository.ts index 81147b2c06..4a1c4607ba 100644 --- a/src/components/pin/pin.repository.ts +++ b/src/components/pin/pin.repository.ts @@ -13,11 +13,7 @@ export class PinRepository { async isPinned(id: ID): Promise { const result = await this.db .query() - .match([ - currentUser, - relation('out', '', 'pinned'), - node('node', 'BaseNode', { id }), - ]) + .match([currentUser, relation('out', '', 'pinned'), node('node', 'BaseNode', { id })]) .return('node') .first(); return !!result; @@ -29,11 +25,7 @@ export class PinRepository { .query() .match(node('node', 'BaseNode', { id })) .match(currentUser.as('currentUser')) - .merge([ - node('currentUser'), - relation('out', 'rel', 'pinned'), - node('node'), - ]) + .merge([node('currentUser'), relation('out', 'rel', 'pinned'), node('node')]) .onCreate.setValues({ 'rel.createdAt': createdAt, }) @@ -43,11 +35,7 @@ export class PinRepository { async remove(id: ID): Promise { await this.db .query() - .match([ - currentUser, - relation('out', 'rel', 'pinned'), - node('node', 'BaseNode', { id }), - ]) + .match([currentUser, relation('out', 'rel', 'pinned'), node('node', 'BaseNode', { id })]) .delete('rel') .run(); } diff --git a/src/components/pin/pin.resolver.ts b/src/components/pin/pin.resolver.ts index 695f6833d6..88026a15be 100644 --- a/src/components/pin/pin.resolver.ts +++ b/src/components/pin/pin.resolver.ts @@ -6,14 +6,10 @@ import { PinService } from './pin.service'; @Resolver() export class PinResolver { - constructor( - private readonly pins: PinService, - private readonly identity: Identity, - ) {} + constructor(private readonly pins: PinService, private readonly identity: Identity) {} @Query(() => Boolean, { - description: - 'Returns whether or not the requesting user has pinned the resource ID', + description: 'Returns whether or not the requesting user has pinned the resource ID', }) async isPinned( @IdArg({ @@ -29,8 +25,7 @@ export class PinResolver { } @Mutation(() => Boolean, { - description: - 'Toggles the pinned state for the resource ID for the requesting user', + description: 'Toggles the pinned state for the resource ID for the requesting user', }) async togglePinned( @IdArg({ @@ -39,8 +34,7 @@ export class PinResolver { id: ID, @Args('pinned', { nullable: true, - description: - 'Whether the item should be pinned or not. Omit to toggle the current state.', + description: 'Whether the item should be pinned or not. Omit to toggle the current state.', }) pinned?: boolean, ): Promise { @@ -51,9 +45,7 @@ export class PinResolver { // name: 'pins', // description: "A list of the requesting user's pinned items", // }) - async list( - @ListArg(PinnedListInput) _input: PinnedListInput, - ): Promise { + async list(@ListArg(PinnedListInput) _input: PinnedListInput): Promise { throw new NotImplementedException(); } } diff --git a/src/components/pnp/extract-scripture.ts b/src/components/pnp/extract-scripture.ts index 2aa6e69f08..6699b81776 100644 --- a/src/components/pnp/extract-scripture.ts +++ b/src/components/pnp/extract-scripture.ts @@ -25,9 +25,7 @@ export const extractScripture = ( let mismatchError = false; // If scripture from book column matches total count, use it. - const totalVersesInBookCol = ScriptureRange.totalVerses( - ...scriptureFromBookCol, - ); + const totalVersesInBookCol = ScriptureRange.totalVerses(...scriptureFromBookCol); if (totalVersesInBookCol === totalVerses) { return { ...common, @@ -52,9 +50,7 @@ export const extractScripture = ( const noteCell = sheet.myNote(row); const scriptureFromNoteCol = tryParseScripture(noteCell.asString); if (scriptureFromNoteCol) { - const totalVersesFromNoteCol = ScriptureRange.totalVerses( - ...scriptureFromNoteCol, - ); + const totalVersesFromNoteCol = ScriptureRange.totalVerses(...scriptureFromNoteCol); if (totalVersesFromNoteCol === totalVerses) { return { ...common, diff --git a/src/components/pnp/extraction-result/extraction-result.dto.ts b/src/components/pnp/extraction-result/extraction-result.dto.ts index db14767c4e..473ef4ac7c 100644 --- a/src/components/pnp/extraction-result/extraction-result.dto.ts +++ b/src/components/pnp/extraction-result/extraction-result.dto.ts @@ -4,13 +4,7 @@ import { stripIndent } from 'common-tags'; import { type UUID } from 'node:crypto'; import { type Merge } from 'type-fest'; import * as uuid from 'uuid'; -import { - type EnumType, - type ID, - IdField, - makeEnum, - OptionalField, -} from '~/common'; +import { type EnumType, type ID, IdField, makeEnum, OptionalField } from '~/common'; import { InlineMarkdownScalar } from '~/common/markdown.scalar'; import { type Cell } from '~/common/xlsx.util'; @@ -29,13 +23,8 @@ export class PnpProblemType { severity, render, wiki, - }: Merge< - PnpProblemType, - { id?: UUID | string } - >): PnpProblemType { - const id = ( - idIn && uuid.validate(idIn) ? idIn : uuid.v5(name, ID_NS) - ) as UUID; + }: Merge, { id?: UUID | string }>): PnpProblemType { + const id = (idIn && uuid.validate(idIn) ? idIn : uuid.v5(name, ID_NS)) as UUID; const type = Object.assign(new PnpProblemType(), { id, @@ -57,10 +46,7 @@ export class PnpProblemType { render: ( context: Context, - ) => (baseCtx: { - source: string; - sheet: string; - }) => Pick; + ) => (baseCtx: { source: string; sheet: string }) => Pick; } @ObjectType() @@ -138,15 +124,8 @@ export abstract class PnpExtractionResult { readonly problems = new Map(); - addProblem( - type: PnpProblemType, - source: Cell, - context: Omit, - ) { - const id = uuid.v5( - [this.fileVersionId, type.id, source.fqn].join('\0'), - ID_NS, - ) as ID & UUID; + addProblem(type: PnpProblemType, source: Cell, context: Omit) { + const id = uuid.v5([this.fileVersionId, type.id, source.fqn].join('\0'), ID_NS) as ID & UUID; this.problems.set(id, { id, type: type.id, diff --git a/src/components/pnp/extraction-result/pnp-extraction-result.gel.repository.ts b/src/components/pnp/extraction-result/pnp-extraction-result.gel.repository.ts index 05db1c769b..0c867a6bf8 100644 --- a/src/components/pnp/extraction-result/pnp-extraction-result.gel.repository.ts +++ b/src/components/pnp/extraction-result/pnp-extraction-result.gel.repository.ts @@ -6,16 +6,11 @@ import { type PnpExtractionResultLoadResult } from './pnp-extraction-result.load @Injectable() export class PnpExtractionResultRepository extends CommonRepository { - async read( - files: ReadonlyArray>, - ): Promise { + async read(files: ReadonlyArray>): Promise { throw new NotImplementedException().with(files); } - async save( - file: ID<'FileVersion'>, - result: PnpExtractionResult, - ): Promise { + async save(file: ID<'FileVersion'>, result: PnpExtractionResult): Promise { throw new NotImplementedException().with(file, result); } } diff --git a/src/components/pnp/extraction-result/pnp-extraction-result.neo4j.repository.ts b/src/components/pnp/extraction-result/pnp-extraction-result.neo4j.repository.ts index 501e4343df..1df3df6801 100644 --- a/src/components/pnp/extraction-result/pnp-extraction-result.neo4j.repository.ts +++ b/src/components/pnp/extraction-result/pnp-extraction-result.neo4j.repository.ts @@ -43,11 +43,7 @@ export class PnpExtractionResultNeo4jRepository .where({ 'file.id': inArray(files) }) .subQuery('result', (sub) => sub - .match([ - node('result'), - relation('out', 'problemRel', 'problem'), - node('type'), - ]) + .match([node('result'), relation('out', 'problemRel', 'problem'), node('type')]) .with( merge('problemRel', { type: 'type.id', @@ -55,9 +51,7 @@ export class PnpExtractionResultNeo4jRepository }).as('problem'), ) .orderBy(String(sortingForEnumIndex(Severity)('problem.severity'))) - .return<{ problems: StoredProblem }>( - collect('problem').as('problems'), - ), + .return<{ problems: StoredProblem }>(collect('problem').as('problems')), ) .return>([ 'file.id as id', @@ -94,11 +88,7 @@ export class PnpExtractionResultNeo4jRepository .with('result') .subQuery('result', (sub) => sub - .match([ - node('result'), - relation('out', 'problem', 'problem'), - node(), - ]) + .match([node('result'), relation('out', 'problem', 'problem'), node()]) .delete('problem') .return('count(problem) as count'), ) @@ -132,16 +122,13 @@ export class PnpExtractionResultNeo4jRepository } } -export const pnpExtractionResultFilters = filter.define( - () => PnpExtractionResultFilters, - { - hasError: filter.pathExists([ - node('node'), - relation('out', '', 'problem'), - node('', { severity: Severity.Error }), - ]), - }, -); +export const pnpExtractionResultFilters = filter.define(() => PnpExtractionResultFilters, { + hasError: filter.pathExists([ + node('node'), + relation('out', '', 'problem'), + node('', { severity: Severity.Error }), + ]), +}); export const pnpExtractionResultSorters = defineSorters(PnpExtractionResult, { totalErrors: (query) => diff --git a/src/components/pnp/extraction-result/pnp-problem.resolver.ts b/src/components/pnp/extraction-result/pnp-problem.resolver.ts index cc2a93b12f..6396c9a00b 100644 --- a/src/components/pnp/extraction-result/pnp-problem.resolver.ts +++ b/src/components/pnp/extraction-result/pnp-problem.resolver.ts @@ -5,8 +5,6 @@ import { PnpExtractionResult, PnpProblem } from './extraction-result.dto'; export class PnpProblemResolver { @ResolveField(() => [PnpProblem]) problems(@Parent() result: PnpExtractionResult) { - return [...result.problems.values()].map((problem) => - PnpProblem.render(problem), - ); + return [...result.problems.values()].map((problem) => PnpProblem.render(problem)); } } diff --git a/src/components/pnp/isGoalRow.ts b/src/components/pnp/isGoalRow.ts index 193c3fc77c..76b17892f8 100644 --- a/src/components/pnp/isGoalRow.ts +++ b/src/components/pnp/isGoalRow.ts @@ -1,9 +1,4 @@ -import { - Book, - parseScripture, - type Range, - type Verse, -} from '@seedcompany/scripture'; +import { Book, parseScripture, type Range, type Verse } from '@seedcompany/scripture'; import { type Cell } from '~/common/xlsx.util'; import { ScriptureRange } from '../scripture/dto'; import { type PnpExtractionResult, PnpProblemType } from './extraction-result'; @@ -86,21 +81,19 @@ export const isGoalRow = ( const NoBook = PnpProblemType.register({ name: 'NoBook', severity: 'Error', - render: - (ctx: { bookRef: string; verseVal: number; verseRef: string }) => () => ({ - groups: 'No book name given', - message: `Ignoring row with no book name \`${ctx.bookRef}\` even though there are **${ctx.verseVal}** verses to translate \`${ctx.verseRef}\``, - }), + render: (ctx: { bookRef: string; verseVal: number; verseRef: string }) => () => ({ + groups: 'No book name given', + message: `Ignoring row with no book name \`${ctx.bookRef}\` even though there are **${ctx.verseVal}** verses to translate \`${ctx.verseRef}\``, + }), }); const InvalidVerseCount = PnpProblemType.register({ name: 'InvalidVerseCount', severity: 'Error', - render: - (ctx: { bookVal: string; verseVal: number; verseRef: string }) => () => ({ - groups: 'The verses to translate exceeds total verses in book', - message: `Ignoring _${ctx.bookVal}_ because **${ctx.verseVal}** \`${ctx.verseRef}\` verses to translate exceeds the total number of verses in the book`, - }), + render: (ctx: { bookVal: string; verseVal: number; verseRef: string }) => () => ({ + groups: 'The verses to translate exceeds total verses in book', + message: `Ignoring _${ctx.bookVal}_ because **${ctx.verseVal}** \`${ctx.verseRef}\` verses to translate exceeds the total number of verses in the book`, + }), wiki: 'https://github.com/SeedCompany/cord-docs/wiki/PnP-Extraction-Validation:-Errors-and-Troubleshooting-Steps#4-the-verses-to-translate-exceeds-total-verses-in-book', }); diff --git a/src/components/pnp/isGoalStepPlannedInsideProject.ts b/src/components/pnp/isGoalStepPlannedInsideProject.ts index 3bef580379..233c360813 100644 --- a/src/components/pnp/isGoalStepPlannedInsideProject.ts +++ b/src/components/pnp/isGoalStepPlannedInsideProject.ts @@ -16,8 +16,7 @@ export const isGoalStepPlannedInsideProject = ( result: PnpExtractionResult, ) => { const fullFY = stepPlanCompleteDate(cell); - const isPlanned = - !!fullFY && pnp.planning.projectFiscalYears.contains(fullFY); + const isPlanned = !!fullFY && pnp.planning.projectFiscalYears.contains(fullFY); if (isPlanned) { return true; } @@ -39,8 +38,7 @@ export const isGoalStepPlannedInsideProject = ( }; export const stepPlanCompleteDate = (cell: Cell) => { - const fiscalYear = - cell.asNumber ?? (Number(trimStart(cell.asString ?? '', `'`)) || undefined); + const fiscalYear = cell.asNumber ?? (Number(trimStart(cell.asString ?? '', `'`)) || undefined); const fullFY = fiscalYear ? fullFiscalYear(fiscalYear) : undefined; return fullFY?.end; }; @@ -80,11 +78,7 @@ const GoalPlannedCompleteAfterProject = PnpProblemType.register({ wiki: 'https://github.com/SeedCompany/cord-docs/wiki/PnP-Extraction-Validation:-Errors-and-Troubleshooting-Steps#5-steps-of-goals-are-planned-to-be-complete-after-this-projects-fiscal-years', }); -const renderCtx = (ctx: { - goal: string; - step: ProductStep; - fiscalYear: number; -}) => { +const renderCtx = (ctx: { goal: string; step: ProductStep; fiscalYear: number }) => { const step = ProductStep.entry(ctx.step).label; return { ...ctx, step, fiscalYear: `FY${ctx.fiscalYear}` }; }; diff --git a/src/components/pnp/isProgressCompletedOutsideProject.ts b/src/components/pnp/isProgressCompletedOutsideProject.ts index 7f2ef8a70c..2c48c37b7d 100644 --- a/src/components/pnp/isProgressCompletedOutsideProject.ts +++ b/src/components/pnp/isProgressCompletedOutsideProject.ts @@ -1,9 +1,4 @@ -import { - CalendarDate, - fiscalQuarter, - fiscalYear, - fullFiscalQuarter, -} from '~/common'; +import { CalendarDate, fiscalQuarter, fiscalYear, fullFiscalQuarter } from '~/common'; import { type Cell } from '~/common/xlsx.util'; import { ProductStep } from '../product/dto'; import { type PnpExtractionResult, PnpProblemType } from './extraction-result'; @@ -21,8 +16,7 @@ export const isProgressCompletedOutsideProject = ( return false; } // Steps completion dates smallest unit is quarters, so expand project range to that. - const projectTimeframe = - pnp.planning.projectDateRange.expandToFull('quarter'); + const projectTimeframe = pnp.planning.projectDateRange.expandToFull('quarter'); if (projectTimeframe.contains(completeDate)) { return false; } @@ -88,11 +82,7 @@ const GoalProgressedAfterProject = PnpProblemType.register({ wiki: 'https://github.com/SeedCompany/cord-docs/wiki/PnP-Extraction-Validation:-Errors-and-Troubleshooting-Steps#6-steps-of-goals-are-marked-complete-after-this-projects-date-range', }); -const renderCtx = (ctx: { - goal: string; - step: ProductStep; - completed: string; -}) => { +const renderCtx = (ctx: { goal: string; step: ProductStep; completed: string }) => { const step = ProductStep.entry(ctx.step).label; const date = CalendarDate.fromISO(ctx.completed); const completed = `Q${fiscalQuarter(date)} FY${fiscalYear(date)}`; diff --git a/src/components/pnp/planning-sheet.ts b/src/components/pnp/planning-sheet.ts index e1fce3272f..ba08a150db 100644 --- a/src/components/pnp/planning-sheet.ts +++ b/src/components/pnp/planning-sheet.ts @@ -1,13 +1,6 @@ import { LazyGetter as Once } from 'lazy-get-decorator'; import { CalendarDate, DateInterval, expandToFullFiscalYears } from '~/common'; -import { - type Cell, - type Column, - Range, - type Row, - Sheet, - type WorkBook, -} from '~/common/xlsx.util'; +import { type Cell, type Column, Range, type Row, Sheet, type WorkBook } from '~/common/xlsx.util'; export abstract class PlanningSheet extends Sheet { static register(book: WorkBook) { @@ -27,9 +20,7 @@ export abstract class PlanningSheet extends Sheet { } @Once() get revision() { - return ( - this.revisionCell.asDate ?? CalendarDate.fromMillis(0).plus({ day: 1 }) - ); + return this.revisionCell.asDate ?? CalendarDate.fromMillis(0).plus({ day: 1 }); } protected abstract revisionCell: Cell; @@ -103,9 +94,7 @@ export class WrittenScripturePlanningSheet extends PlanningSheet { protected myNotesFallbackCell = this.cell('AI16'); @Once() protected get goalColumn() { - return this.revision > CalendarDate.local(2025, 2, 24) - ? this.column('P') - : this.column('Q'); + return this.revision > CalendarDate.local(2025, 2, 24) ? this.column('P') : this.column('Q'); } bookName(goalRow: Row) { @@ -115,10 +104,7 @@ export class WrittenScripturePlanningSheet extends PlanningSheet { @Once() get goalsEnd() { const lastRow = super.goalsEnd.row; let row = this.goalsStart.row; - while ( - row < lastRow && - this.bookName(row).asString !== 'Other Goals and Milestones' - ) { + while (row < lastRow && this.bookName(row).asString !== 'Other Goals and Milestones') { row = row.move(1); } return super.goalsEnd.column.cell(row); diff --git a/src/components/pnp/progress-sheet.ts b/src/components/pnp/progress-sheet.ts index 41dc6f5acb..989a0a40a2 100644 --- a/src/components/pnp/progress-sheet.ts +++ b/src/components/pnp/progress-sheet.ts @@ -1,12 +1,6 @@ import { LazyGetter as Once } from 'lazy-get-decorator'; import { fullFiscalQuarter, isQuarterNumber, isReasonableYear } from '~/common'; -import { - type Column, - type Range, - type Row, - Sheet, - type WorkBook, -} from '~/common/xlsx.util'; +import { type Column, type Range, type Row, Sheet, type WorkBook } from '~/common/xlsx.util'; export abstract class ProgressSheet extends Sheet { static register(book: WorkBook) { @@ -34,10 +28,7 @@ export abstract class ProgressSheet extends Sheet { } protected abstract goalStartColumn: Column; @Once() protected get goalsStart() { - return this.cell( - this.goalStartColumn, - this.book.namedRange('ProgDraft').start.row, - ); + return this.cell(this.goalStartColumn, this.book.namedRange('ProgDraft').start.row); } @Once() protected get goalsEnd() { return this.sheetRange.end; @@ -89,10 +80,7 @@ export class WrittenScriptureProgressSheet extends ProgressSheet { @Once() get goalsEnd() { const lastRow = super.goalsEnd.row; let row = this.goalsStart.row; - while ( - row < lastRow && - this.bookName(row).asString !== 'Other Goals and Milestones' - ) { + while (row < lastRow && this.bookName(row).asString !== 'Other Goals and Milestones') { row = row.move(1); } return super.goalsEnd.column.cell(row); diff --git a/src/components/pnp/verifyEngagementDateRangeMatches.ts b/src/components/pnp/verifyEngagementDateRangeMatches.ts index aeac9787b9..e16111c5ef 100644 --- a/src/components/pnp/verifyEngagementDateRangeMatches.ts +++ b/src/components/pnp/verifyEngagementDateRangeMatches.ts @@ -1,10 +1,7 @@ import { stripIndent } from 'common-tags'; import { DateTime } from 'luxon'; import { DateInterval } from '~/common'; -import { - type PnpPlanningExtractionResult, - PnpProblemType, -} from './extraction-result'; +import { type PnpPlanningExtractionResult, PnpProblemType } from './extraction-result'; import { type PlanningSheet } from './planning-sheet'; export function verifyEngagementDateRangeMatches( @@ -19,21 +16,16 @@ export function verifyEngagementDateRangeMatches( // fall } - const matches = - engagementRange && pnpRange && engagementRange.equals(pnpRange); + const matches = engagementRange && pnpRange && engagementRange.equals(pnpRange); if (matches) { return true; } - result.addProblem( - MismatchedEngagementDateRange, - sheet.projectDateCells.start, - { - eng: engagementRange?.toISO(), - pnp: pnpRange?.toISO(), - }, - ); + result.addProblem(MismatchedEngagementDateRange, sheet.projectDateCells.start, { + eng: engagementRange?.toISO(), + pnp: pnpRange?.toISO(), + }); return false; } diff --git a/src/components/post/post.loader.ts b/src/components/post/post.loader.ts index 7a4fa667cd..7c672cc31d 100644 --- a/src/components/post/post.loader.ts +++ b/src/components/post/post.loader.ts @@ -6,10 +6,7 @@ import { PostService } from './post.service'; @LoaderFactory() export class PostLoader implements DataLoaderStrategy> { - constructor( - private readonly service: PostService, - private readonly repo: PostRepository, - ) {} + constructor(private readonly service: PostService, private readonly repo: PostRepository) {} async loadMany(ids: ReadonlyArray>) { const posts = await this.repo.readMany(ids); diff --git a/src/components/post/post.module.ts b/src/components/post/post.module.ts index 061c5179ee..2584a19c72 100644 --- a/src/components/post/post.module.ts +++ b/src/components/post/post.module.ts @@ -8,17 +8,8 @@ import { PostService } from './post.service'; import { PostableResolver } from './postable.resolver'; @Module({ - imports: [ - forwardRef(() => UserModule), - forwardRef(() => AuthorizationModule), - ], - providers: [ - PostResolver, - PostService, - PostRepository, - PostableResolver, - PostLoader, - ], + imports: [forwardRef(() => UserModule), forwardRef(() => AuthorizationModule)], + providers: [PostResolver, PostService, PostRepository, PostableResolver, PostLoader], exports: [PostService], }) export class PostModule {} diff --git a/src/components/post/post.repository.ts b/src/components/post/post.repository.ts index ef9f06be66..b2a9045b99 100644 --- a/src/components/post/post.repository.ts +++ b/src/components/post/post.repository.ts @@ -44,10 +44,7 @@ export class PostRepository extends DtoRepository(Post) { .first(); } - async update( - existing: UnsecuredDto, - changes: ChangesOf, - ) { + async update(existing: UnsecuredDto, changes: ChangesOf) { return await this.updateProperties(existing, changes); } @@ -110,16 +107,8 @@ export class PostRepository extends DtoRepository(Post) { protected hydrate() { return (query: Query) => query - .match([ - node('node'), - relation('in', '', 'post', ACTIVE), - node('parent', 'BaseNode'), - ]) - .match([ - node('node'), - relation('out', '', 'creator', ACTIVE), - node('creator', 'User'), - ]) + .match([node('node'), relation('in', '', 'post', ACTIVE), node('parent', 'BaseNode')]) + .match([node('node'), relation('out', '', 'creator', ACTIVE), node('creator', 'User')]) .apply(matchProps()) .return<{ dto: DbTypeOf }>( merge('props', { diff --git a/src/components/post/post.resolver.ts b/src/components/post/post.resolver.ts index 0499d85819..4b57eef441 100644 --- a/src/components/post/post.resolver.ts +++ b/src/components/post/post.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg, mapSecuredValue } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { PostLoader, PostService } from '../post'; @@ -27,9 +20,7 @@ export class PostResolver { @Mutation(() => CreatePostOutput, { description: 'Create a discussion post', }) - async createPost( - @Args('input') { post: input }: CreatePostInput, - ): Promise { + async createPost(@Args('input') { post: input }: CreatePostInput): Promise { const post = await this.service.create(input); return { post }; } @@ -37,10 +28,7 @@ export class PostResolver { @Query(() => Post, { description: 'Look up a post by ID', }) - async post( - @IdArg() id: ID, - @Loader(PostLoader) posts: LoaderOf, - ): Promise { + async post(@IdArg() id: ID, @Loader(PostLoader) posts: LoaderOf): Promise { return await posts.load(id); } @@ -55,9 +43,7 @@ export class PostResolver { @Mutation(() => UpdatePostOutput, { description: 'Update an existing Post', }) - async updatePost( - @Args('input') { post: input }: UpdatePostInput, - ): Promise { + async updatePost(@Args('input') { post: input }: UpdatePostInput): Promise { const post = await this.service.update(input); return { post }; } diff --git a/src/components/post/post.service.ts b/src/components/post/post.service.ts index a7f4694d0f..dcc740e5fb 100644 --- a/src/components/post/post.service.ts +++ b/src/components/post/post.service.ts @@ -35,9 +35,7 @@ export class PostService { async create(input: CreatePost): Promise { if (!input.parentId) { - throw new ServerException( - 'A post must be associated with a parent node.', - ); + throw new ServerException('A post must be associated with a parent node.'); } const perms = await this.getPermissionsFromPostable(input.parentId); perms.verifyCan('create'); @@ -119,9 +117,7 @@ export class PostService { async getPermissionsFromPostable(resource: PostableRef) { const parent = await this.loadPostable(resource); - const parentType = this.resourcesHost.getByName( - parent.__typename as 'Postable', - ); + const parentType = this.resourcesHost.getByName(parent.__typename as 'Postable'); return this.privileges.for(parentType, parent).forEdge('posts'); } diff --git a/src/components/product-progress/create-product-connection.resolver.ts b/src/components/product-progress/create-product-connection.resolver.ts index 9b5655b17e..9100deabbf 100644 --- a/src/components/product-progress/create-product-connection.resolver.ts +++ b/src/components/product-progress/create-product-connection.resolver.ts @@ -14,9 +14,7 @@ export class ProgressReportCreateProductConnectionResolver { @ResolveField(() => [Variant], { description: 'All available progress variants for this product', }) - async availableVariants( - @Parent() { product }: CreateProductOutput, - ): Promise { + async availableVariants(@Parent() { product }: CreateProductOutput): Promise { // TODO move to auth policy if (this.identity.isAnonymous) { return []; diff --git a/src/components/product-progress/dto/product-progress.dto.ts b/src/components/product-progress/dto/product-progress.dto.ts index 2ddbc9bc5e..b954595a97 100644 --- a/src/components/product-progress/dto/product-progress.dto.ts +++ b/src/components/product-progress/dto/product-progress.dto.ts @@ -13,10 +13,7 @@ import { e } from '~/core/gel'; import { RegisterResource } from '~/core/resources'; import { type Product, ProductStep } from '../../product/dto'; import { type ProgressReport } from '../../progress-report/dto'; -import { - ProgressReportVariantProgress, - type ProgressVariant, -} from './variant-progress.dto'; +import { ProgressReportVariantProgress, type ProgressVariant } from './variant-progress.dto'; export interface ProgressVariantByProductInput { product: Product; @@ -53,8 +50,7 @@ export class ProductProgress { readonly reportId: ID; @Field(() => Variant) - readonly variant: Variant & - SetUnsecuredType; + readonly variant: Variant & SetUnsecuredType; @Field(() => [StepProgress], { description: stripIndent` diff --git a/src/components/product-progress/dto/variant-progress.dto.ts b/src/components/product-progress/dto/variant-progress.dto.ts index a898317b8e..3cc29b92b9 100644 --- a/src/components/product-progress/dto/variant-progress.dto.ts +++ b/src/components/product-progress/dto/variant-progress.dto.ts @@ -1,16 +1,7 @@ import { ArgsType, Field, InputType, ObjectType } from '@nestjs/graphql'; -import { - Role, - type SetUnsecuredType, - Variant, - VariantInputField, - type VariantOf, -} from '~/common'; +import { Role, type SetUnsecuredType, Variant, VariantInputField, type VariantOf } from '~/common'; import { RegisterResource } from '~/core/resources'; -import { - type ProductProgress, - type UnsecuredProductProgress, -} from './product-progress.dto'; +import { type ProductProgress, type UnsecuredProductProgress } from './product-progress.dto'; export type ProgressVariant = VariantOf; @@ -18,9 +9,7 @@ export type ProgressVariant = VariantOf; @ObjectType() export class ProgressReportVariantProgress { static readonly Parent = () => - import('../../progress-report/dto/progress-report.dto').then( - (m) => m.ProgressReport, - ); + import('../../progress-report/dto/progress-report.dto').then((m) => m.ProgressReport); static readonly Variants = Variant.createList({ partner: { @@ -32,12 +21,10 @@ export class ProgressReportVariantProgress { responsibleRole: Role.ProjectManager, }, }); - static readonly FallbackVariant = - ProgressReportVariantProgress.Variants.byKey('official'); + static readonly FallbackVariant = ProgressReportVariantProgress.Variants.byKey('official'); @Field(() => Variant) - readonly variant: Variant & - SetUnsecuredType; + readonly variant: Variant & SetUnsecuredType; readonly details: readonly ProductProgress[] & SetUnsecuredType; diff --git a/src/components/product-progress/handlers/extract-pnp-progress.handler.ts b/src/components/product-progress/handlers/extract-pnp-progress.handler.ts index ee4b181133..673d5206a0 100644 --- a/src/components/product-progress/handlers/extract-pnp-progress.handler.ts +++ b/src/components/product-progress/handlers/extract-pnp-progress.handler.ts @@ -46,16 +46,10 @@ export class ExtractPnpProgressHandler { // Fetch products for report mapped to a book/story name const engagementId = event.report.parent.properties.id; const storyProducts = progressRows[0].story - ? await this.products.loadProductIdsWithProducibleNames( - engagementId, - ProducibleType.Story, - ) + ? await this.products.loadProductIdsWithProducibleNames(engagementId, ProducibleType.Story) : mapOf({}); const scriptureProducts = progressRows[0].bookName - ? await this.products.loadProductIdsForBookAndVerse( - engagementId, - this.logger, - ) + ? await this.products.loadProductIdsForBookAndVerse(engagementId, this.logger) : []; // Convert row to product ID @@ -71,16 +65,14 @@ export class ExtractPnpProgressHandler { if (row.bookName) { const exactScriptureMatch = scriptureProducts.find( (ref) => - ref.scriptureRanges.length > 0 && - isScriptureEqual(ref.scriptureRanges, row.scripture), + ref.scriptureRanges.length > 0 && isScriptureEqual(ref.scriptureRanges, row.scripture), ); if (exactScriptureMatch) { return { extracted: row, productId: exactScriptureMatch.id, steps }; } const unspecifiedScriptureMatch = scriptureProducts.find( - (ref) => - ref.book === row.bookName && ref.totalVerses === row.totalVerses, + (ref) => ref.book === row.bookName && ref.totalVerses === row.totalVerses, ); if (unspecifiedScriptureMatch) { return { @@ -110,12 +102,7 @@ export class ExtractPnpProgressHandler { variant: Progress.FallbackVariant, }); } catch (e) { - if ( - !( - e instanceof AggregateError && - e.message === 'Invalid Progress Input' - ) - ) { + if (!(e instanceof AggregateError && e.message === 'Invalid Progress Input')) { throw e; } for (const error of e.errors) { diff --git a/src/components/product-progress/product-progress-by-product.loader.ts b/src/components/product-progress/product-progress-by-product.loader.ts index 87380e0a44..8178a3eb91 100644 --- a/src/components/product-progress/product-progress-by-product.loader.ts +++ b/src/components/product-progress/product-progress-by-product.loader.ts @@ -1,22 +1,11 @@ -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; -import { - type ProgressVariantByProductInput, - type ProgressVariantByProductOutput, -} from './dto'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; +import { type ProgressVariantByProductInput, type ProgressVariantByProductOutput } from './dto'; import { ProductProgressService } from './product-progress.service'; @LoaderFactory() export class ProductProgressByProductLoader implements - DataLoaderStrategy< - ProgressVariantByProductOutput, - ProgressVariantByProductInput, - string - > + DataLoaderStrategy { constructor(private readonly service: ProductProgressService) {} diff --git a/src/components/product-progress/product-progress-by-report.loader.ts b/src/components/product-progress/product-progress-by-report.loader.ts index e8137ebe66..9ca6baa22d 100644 --- a/src/components/product-progress/product-progress-by-report.loader.ts +++ b/src/components/product-progress/product-progress-by-report.loader.ts @@ -1,22 +1,11 @@ -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; -import { - type ProgressVariantByReportInput, - type ProgressVariantByReportOutput, -} from './dto'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; +import { type ProgressVariantByReportInput, type ProgressVariantByReportOutput } from './dto'; import { ProductProgressService } from './product-progress.service'; @LoaderFactory() export class ProductProgressByReportLoader implements - DataLoaderStrategy< - ProgressVariantByReportOutput, - ProgressVariantByReportInput, - string - > + DataLoaderStrategy { constructor(private readonly service: ProductProgressService) {} diff --git a/src/components/product-progress/product-progress.repository.ts b/src/components/product-progress/product-progress.repository.ts index 0af26811fa..d87075bf81 100644 --- a/src/components/product-progress/product-progress.repository.ts +++ b/src/components/product-progress/product-progress.repository.ts @@ -2,13 +2,7 @@ import { Injectable } from '@nestjs/common'; import { stripIndent } from 'common-tags'; import { node, type Query, relation } from 'cypher-query-builder'; import { DateTime } from 'luxon'; -import { - EnhancedResource, - generateId, - type ID, - NotFoundException, - type Variant, -} from '~/common'; +import { EnhancedResource, generateId, type ID, NotFoundException, type Variant } from '~/common'; import { DatabaseService } from '~/core/database'; import { ACTIVE, @@ -54,9 +48,7 @@ export class ProductProgressRepository { .apply(this.hydrateAll()) .first(); if (!result) { - throw new NotFoundException( - 'Could not find progress for product and report period', - ); + throw new NotFoundException('Could not find progress for product and report period'); } return result; } @@ -68,17 +60,8 @@ export class ProductProgressRepository { .apply(this.withVariant(input.variant)) .subQuery('product', (sub) => sub - .match([ - node('product'), - relation('in', '', 'product'), - node('baseNode', 'Engagement'), - ]) - .apply( - this.reports.matchCurrentDue( - variable('baseNode.id'), - ReportType.Progress, - ), - ) + .match([node('product'), relation('in', '', 'product'), node('baseNode', 'Engagement')]) + .apply(this.reports.matchCurrentDue(variable('baseNode.id'), ReportType.Progress)) .return('node as report'), ) .apply(this.hydrateAll()) @@ -86,9 +69,7 @@ export class ProductProgressRepository { return result; } - async readAllProgressReportsForManyProducts( - products: readonly ProgressVariantByProductInput[], - ) { + async readAllProgressReportsForManyProducts(products: readonly ProgressVariantByProductInput[]) { const result = await this.db .query() .unwind( @@ -125,9 +106,7 @@ export class ProductProgressRepository { return result; } - async readAllProgressReportsForManyReports( - reports: readonly ProgressVariantByReportInput[], - ) { + async readAllProgressReportsForManyReports(reports: readonly ProgressVariantByReportInput[]) { const result = await this.db .query() .unwind( @@ -147,11 +126,7 @@ export class ProductProgressRepository { .with('eng, report, input.variant as variant') .subQuery(['eng', 'report', 'variant'], (sub) => sub - .match([ - node('eng'), - relation('out', '', 'product', ACTIVE), - node('product', 'Product'), - ]) + .match([node('eng'), relation('out', '', 'product', ACTIVE), node('product', 'Product')]) .subQuery(['report', 'product', 'variant'], this.hydrateAll()) .return(collect('dto').as('progressList')), ) @@ -184,47 +159,45 @@ export class ProductProgressRepository { private hydrateOne() { return (query: Query) => - query.comment`hydrateOne()`.subQuery( - ['product', 'report', 'progress', 'variant'], - (sub1) => - sub1 - .subQuery(['product', 'report', 'progress'], (sub2) => - sub2 - .match([ - node('progress'), - relation('out', '', 'step', ACTIVE), - node('stepNode', StepProgress.dbLabel), - ]) - .apply(matchProps({ nodeName: 'stepNode', outputVar: 'step' })) - .return(collect('step').as('steps')), - ) - .match([ - node('product'), - relation('out', '', 'steps', ACTIVE), - node('declaredSteps', 'Property'), - ]) - .with([ - '*', - // Convert StepProgress list to a map keyed by step - 'apoc.map.fromPairs([sp in steps | [sp.step, sp]]) as progressStepMap', - ]) - .return<{ dto: UnsecuredProductProgress }>( - // FYI `progress` is nullable, so this could include its props or not. - merge('progress', { - productId: 'product.id', - reportId: 'report.id', - variant: 'variant', - // Convert the products step strings into actual StepProgress - // or fallback to a placeholder. This ensures that the list is - // in the correct order and indicates which steps still need - // progress reported. - steps: stripIndent` + query.comment`hydrateOne()`.subQuery(['product', 'report', 'progress', 'variant'], (sub1) => + sub1 + .subQuery(['product', 'report', 'progress'], (sub2) => + sub2 + .match([ + node('progress'), + relation('out', '', 'step', ACTIVE), + node('stepNode', StepProgress.dbLabel), + ]) + .apply(matchProps({ nodeName: 'stepNode', outputVar: 'step' })) + .return(collect('step').as('steps')), + ) + .match([ + node('product'), + relation('out', '', 'steps', ACTIVE), + node('declaredSteps', 'Property'), + ]) + .with([ + '*', + // Convert StepProgress list to a map keyed by step + 'apoc.map.fromPairs([sp in steps | [sp.step, sp]]) as progressStepMap', + ]) + .return<{ dto: UnsecuredProductProgress }>( + // FYI `progress` is nullable, so this could include its props or not. + merge('progress', { + productId: 'product.id', + reportId: 'report.id', + variant: 'variant', + // Convert the products step strings into actual StepProgress + // or fallback to a placeholder. This ensures that the list is + // in the correct order and indicates which steps still need + // progress reported. + steps: stripIndent` [step in declaredSteps.value | apoc.map.get(progressStepMap, step, { step: step, completed: null }) ] `, - }).as('dto'), - ), + }).as('dto'), + ), ); } @@ -325,17 +298,11 @@ export class ProductProgressRepository { .comment('Now read back progress node') .apply(this.hydrateOne()) - .return([ - 'dto', - 'numProgressPercentCreated', - 'numProgressPercentDeactivated', - ]); + .return(['dto', 'numProgressPercentCreated', 'numProgressPercentDeactivated']); const result = await query.first(); if (!result) { - throw new NotFoundException( - 'Could not find product or report to add progress to', - ); + throw new NotFoundException('Could not find product or report to add progress to'); } return result.dto; } diff --git a/src/components/product-progress/product-progress.resolver.ts b/src/components/product-progress/product-progress.resolver.ts index 32dccfdddc..784b01b1f7 100644 --- a/src/components/product-progress/product-progress.resolver.ts +++ b/src/components/product-progress/product-progress.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { Loader, type LoaderOf } from '~/core'; import { PeriodicReportLoader } from '../periodic-report'; import { ProductLoader } from '../product'; diff --git a/src/components/product-progress/product-progress.service.ts b/src/components/product-progress/product-progress.service.ts index 80c09aab13..84b0a7120b 100644 --- a/src/components/product-progress/product-progress.service.ts +++ b/src/components/product-progress/product-progress.service.ts @@ -54,10 +54,7 @@ export class ProductProgressService { if (reports.length === 0) { return []; } - const reportMap = mapEntries(reports, ({ report }) => [ - report.id, - report, - ]).asRecord; + const reportMap = mapEntries(reports, ({ report }) => [report.id, report]).asRecord; const rows = await this.repo.readAllProgressReportsForManyReports(reports); return rows.map((row): ProgressVariantByReportOutput => { const report = reportMap[row.reportId]; @@ -78,13 +75,8 @@ export class ProductProgressService { if (products.length === 0) { return []; } - const productMap = mapEntries(products, ({ product }) => [ - product.id, - product, - ]).asRecord; - const rows = await this.repo.readAllProgressReportsForManyProducts( - products, - ); + const productMap = mapEntries(products, ({ product }) => [product.id, product]).asRecord; + const rows = await this.repo.readAllProgressReportsForManyProducts(products); return rows.map((row): ProgressVariantByProductOutput => { const product = productMap[row.productId]; return { @@ -162,9 +154,7 @@ export class ProductProgressService { progress: UnsecuredProductProgress, privileges: UserResourcePrivileges, ): ProductProgress | undefined { - const vp = privileges.forContext( - withVariant(privileges.context!, progress.variant), - ); + const vp = privileges.forContext(withVariant(privileges.context!, progress.variant)); if (!vp.can('read')) { return undefined; } diff --git a/src/components/product-progress/step-not-planned.exception.ts b/src/components/product-progress/step-not-planned.exception.ts index cfa82c6cf2..254aebca96 100644 --- a/src/components/product-progress/step-not-planned.exception.ts +++ b/src/components/product-progress/step-not-planned.exception.ts @@ -2,11 +2,7 @@ import { type ID, InputException } from '~/common'; import { type ProductStep } from '../product/dto'; export class StepNotPlannedException extends InputException { - constructor( - readonly productId: ID, - readonly step: ProductStep, - readonly index: number, - ) { + constructor(readonly productId: ID, readonly step: ProductStep, readonly index: number) { super('Step is not planned', `steps.${index}.step`); } } diff --git a/src/components/product-progress/step-progress-extractor.service.ts b/src/components/product-progress/step-progress-extractor.service.ts index b07e1aed50..abde5f8889 100644 --- a/src/components/product-progress/step-progress-extractor.service.ts +++ b/src/components/product-progress/step-progress-extractor.service.ts @@ -44,10 +44,7 @@ type ExtractedRow = MergeExclusive< @Injectable() export class StepProgressExtractor { - async extract( - file: Downloadable, - result: PnpProgressExtractionResult, - ) { + async extract(file: Downloadable, result: PnpProgressExtractionResult) { const pnp = await Pnp.fromDownloadable(file); const sheet = pnp.progress; @@ -74,28 +71,21 @@ const parseProgressRow = const sheet = cell.sheet; const row = cell.row; const rowIndex = row.a1 - sheet.goals.start.row.a1; - const planningRow = pnp.planning.row( - pnp.planning.goals.start.row.a1 + rowIndex, - ); + const planningRow = pnp.planning.row(pnp.planning.goals.start.row.a1 + rowIndex); - const steps = entries(stepColumns).flatMap( - ([step, column]) => { - const fiscalYear = pnp.planning.cell( - planningStepColumns[step], - planningRow, - ); + const steps = entries(stepColumns).flatMap(([step, column]) => { + const fiscalYear = pnp.planning.cell(planningStepColumns[step], planningRow); - const cell = sheet.cell(column, row); - if ( - !isGoalStepPlannedInsideProject(pnp, fiscalYear, step, result) || - isProgressCompletedOutsideProject(pnp, cell, step, result) - ) { - return []; - } + const cell = sheet.cell(column, row); + if ( + !isGoalStepPlannedInsideProject(pnp, fiscalYear, step, result) || + isProgressCompletedOutsideProject(pnp, cell, step, result) + ) { + return []; + } - return { step, completed: progress(cell) }; - }, - ); + return { step, completed: progress(cell) }; + }); const common = { rowIndex: rowIndex + 1, @@ -112,10 +102,7 @@ const parseProgressRow = assert(sheet.isWritten()); return { ...common, - ...extractScripture( - planningRow as Row, - result, - ), + ...extractScripture(planningRow as Row, result), }; }; diff --git a/src/components/product/dto/completion-description.dto.ts b/src/components/product/dto/completion-description.dto.ts index ec084c7ee3..03901bfbc8 100644 --- a/src/components/product/dto/completion-description.dto.ts +++ b/src/components/product/dto/completion-description.dto.ts @@ -12,8 +12,7 @@ export class ProductCompletionDescriptionSuggestionsInput extends PaginationInpu @Field(() => ProductMethodology, { nullable: true, - description: - 'Optionally limit suggestions to only ones for this methodology', + description: 'Optionally limit suggestions to only ones for this methodology', }) methodology?: ProductMethodology; } diff --git a/src/components/product/dto/list-product.dto.ts b/src/components/product/dto/list-product.dto.ts index ca9eb338cc..6b70cf80c6 100644 --- a/src/components/product/dto/list-product.dto.ts +++ b/src/components/product/dto/list-product.dto.ts @@ -44,14 +44,9 @@ export class ProductListInput extends SortablePaginationInput({ } @ObjectType() -export class ProductListOutput extends PaginatedList( - Product, -) {} +export class ProductListOutput extends PaginatedList(Product) {} @ObjectType({ description: SecuredList.descriptionFor('products'), }) -export abstract class SecuredProductList extends SecuredList< - Product, - AnyProduct ->(Product) {} +export abstract class SecuredProductList extends SecuredList(Product) {} diff --git a/src/components/product/dto/producible.dto.ts b/src/components/product/dto/producible.dto.ts index de42deede3..8a716c60f7 100644 --- a/src/components/product/dto/producible.dto.ts +++ b/src/components/product/dto/producible.dto.ts @@ -1,9 +1,4 @@ -import { - Field, - InterfaceType, - ObjectType, - registerEnumType, -} from '@nestjs/graphql'; +import { Field, InterfaceType, ObjectType, registerEnumType } from '@nestjs/graphql'; import { type MadeEnum } from '@seedcompany/nest'; import { stripIndent } from 'common-tags'; import { @@ -19,10 +14,7 @@ import { type SetChangeType } from '~/core/database/changes'; import { e } from '~/core/gel'; import { RegisterResource } from '~/core/resources'; import { type DbScriptureReferences } from '../../scripture'; -import { - type ScriptureRangeInput, - SecuredScriptureRanges, -} from '../../scripture/dto'; +import { type ScriptureRangeInput, SecuredScriptureRanges } from '../../scripture/dto'; @RegisterResource({ db: e.Producible }) @InterfaceType({ diff --git a/src/components/product/dto/product-methodology.enum.ts b/src/components/product/dto/product-methodology.enum.ts index d9102bf72c..9ad959fb41 100644 --- a/src/components/product/dto/product-methodology.enum.ts +++ b/src/components/product/dto/product-methodology.enum.ts @@ -1,11 +1,6 @@ import { ObjectType } from '@nestjs/graphql'; import { invertBy } from 'lodash'; -import { - type EnumType, - makeEnum, - SecuredEnum, - SecuredEnumList, -} from '~/common'; +import { type EnumType, makeEnum, SecuredEnum, SecuredEnumList } from '~/common'; import { ProductApproach as Approach } from './product-approach.enum'; /** @@ -71,6 +66,7 @@ export const MethodologyToApproach: Record = { [ProductMethodology.OtherVisual]: Approach.Visual, }; -export const ApproachToMethodologies = invertBy( - MethodologyToApproach, -) as Record; +export const ApproachToMethodologies = invertBy(MethodologyToApproach) as Record< + Approach, + ProductMethodology[] +>; diff --git a/src/components/product/dto/product.dto.ts b/src/components/product/dto/product.dto.ts index cdf0b12be9..14dfe0f2c4 100644 --- a/src/components/product/dto/product.dto.ts +++ b/src/components/product/dto/product.dto.ts @@ -37,9 +37,7 @@ import { SecuredProductPurposes } from './product-purpose.enum'; import { SecuredProductSteps } from './product-step.enum'; import { SecuredProgressMeasurement } from './progress-measurement.enum'; -export const resolveProductType = ( - product: AnyProduct | UnsecuredDto, -) => +export const resolveProductType = (product: AnyProduct | UnsecuredDto) => product.produces ? DerivativeScriptureProduct : product.title @@ -52,8 +50,7 @@ export const resolveProductType = ( implements: [Producible], }) export class Product extends Producible { - static readonly Parent = () => - import('../../engagement/dto').then((m) => m.LanguageEngagement); + static readonly Parent = () => import('../../engagement/dto').then((m) => m.LanguageEngagement); readonly engagement: ID; readonly project: ID; @@ -138,8 +135,7 @@ export class DirectScriptureProduct extends Product { unspecifiedScripture: SecuredUnspecifiedScripturePortion; @Field(() => Int, { - description: - 'The total number of verses of the selected scripture in this product', + description: 'The total number of verses of the selected scripture in this product', }) totalVerses: number; @@ -183,10 +179,7 @@ export class DerivativeScriptureProduct extends Product { }) readonly scriptureReferencesOverride: SecuredScriptureRangesOverride & SetDbType & - SetChangeType< - 'scriptureReferencesOverride', - readonly ScriptureRangeInput[] | null - >; + SetChangeType<'scriptureReferencesOverride', readonly ScriptureRangeInput[] | null>; @Field({ description: stripIndent` @@ -196,8 +189,7 @@ export class DerivativeScriptureProduct extends Product { readonly composite: SecuredBoolean; @Field(() => Int, { - description: - 'The total number of verses of the selected scripture in this product', + description: 'The total number of verses of the selected scripture in this product', }) totalVerses: number; @@ -213,8 +205,7 @@ export class DerivativeScriptureProduct extends Product { @RegisterResource({ db: e.Product }) @ObjectType({ implements: [Product], - description: - 'A product which does not fit into the other two types of products', + description: 'A product which does not fit into the other two types of products', }) export class OtherProduct extends Product { static readonly Parent = Product.Parent; @@ -238,14 +229,10 @@ export type AnyProduct = MergeExclusive< * we just want to narrow the type in a safe way. */ export const asProductType = - >( - expected: Concrete, - ) => + >(expected: Concrete) => >( product: Given, - ): Given extends AnyProduct - ? Concrete['prototype'] - : UnsecuredDto => { + ): Given extends AnyProduct ? Concrete['prototype'] : UnsecuredDto => { if (resolveProductType(product) !== expected) { const type = startCase(expected.name.replace(/Product$/, '')); throw new ServerException(`Product was not the ${type} type`); diff --git a/src/components/product/dto/progress-measurement.enum.ts b/src/components/product/dto/progress-measurement.enum.ts index 0e981e13e7..d3d3c15755 100644 --- a/src/components/product/dto/progress-measurement.enum.ts +++ b/src/components/product/dto/progress-measurement.enum.ts @@ -11,6 +11,4 @@ export const ProgressMeasurement = makeEnum({ @ObjectType({ description: SecuredEnum.descriptionFor('progress measurement'), }) -export class SecuredProgressMeasurement extends SecuredEnum( - ProgressMeasurement, -) {} +export class SecuredProgressMeasurement extends SecuredEnum(ProgressMeasurement) {} diff --git a/src/components/product/dto/update-product.dto.ts b/src/components/product/dto/update-product.dto.ts index c5fb1d68b4..177886af5a 100644 --- a/src/components/product/dto/update-product.dto.ts +++ b/src/components/product/dto/update-product.dto.ts @@ -1,13 +1,6 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; -import { - type ID, - IdField, - IntersectTypes, - NameField, - OmitType, - PickType, -} from '~/common'; +import { type ID, IdField, IntersectTypes, NameField, OmitType, PickType } from '~/common'; import { CreateBaseProduct, CreateDerivativeScriptureProduct, @@ -28,10 +21,7 @@ export abstract class UpdateBaseProduct extends OmitType(CreateBaseProduct, [ @InputType() export abstract class UpdateDirectScriptureProduct extends IntersectTypes( UpdateBaseProduct, - PickType(CreateDirectScriptureProduct, [ - 'scriptureReferences', - 'unspecifiedScripture', - ]), + PickType(CreateDirectScriptureProduct, ['scriptureReferences', 'unspecifiedScripture']), ) { totalVerses?: number; totalVerseEquivalents?: number; @@ -40,10 +30,7 @@ export abstract class UpdateDirectScriptureProduct extends IntersectTypes( @InputType() export abstract class UpdateDerivativeScriptureProduct extends IntersectTypes( UpdateBaseProduct, - PickType(CreateDerivativeScriptureProduct, [ - 'scriptureReferencesOverride', - 'composite', - ]), + PickType(CreateDerivativeScriptureProduct, ['scriptureReferencesOverride', 'composite']), ) { @IdField({ optional: true, diff --git a/src/components/product/handlers/extract-products-from-pnp.handler.ts b/src/components/product/handlers/extract-products-from-pnp.handler.ts index 19837f88d0..4698731819 100644 --- a/src/components/product/handlers/extract-products-from-pnp.handler.ts +++ b/src/components/product/handlers/extract-products-from-pnp.handler.ts @@ -1,8 +1,5 @@ import { EventsHandler, type IEventHandler } from '~/core'; -import { - EngagementCreatedEvent, - EngagementUpdatedEvent, -} from '../../engagement/events'; +import { EngagementCreatedEvent, EngagementUpdatedEvent } from '../../engagement/events'; import { FileService } from '../../file'; import { PnpPlanningExtractionResult } from '../../pnp/extraction-result'; import { PlanningExtractionResultSaver } from '../../pnp/extraction-result/planning-extraction-result-saver'; @@ -12,9 +9,7 @@ import { PnpProductSyncService } from '../pnp-product-sync.service'; type SubscribedEvent = EngagementCreatedEvent | EngagementUpdatedEvent; @EventsHandler(EngagementCreatedEvent, EngagementUpdatedEvent) -export class ExtractProductsFromPnpHandler - implements IEventHandler -{ +export class ExtractProductsFromPnpHandler implements IEventHandler { constructor( private readonly syncer: PnpProductSyncService, private readonly files: FileService, @@ -25,10 +20,7 @@ export class ExtractProductsFromPnpHandler if (!event.isLanguageEngagement()) { return; } - const engagement = - event instanceof EngagementCreatedEvent - ? event.engagement - : event.updated; + const engagement = event instanceof EngagementCreatedEvent ? event.engagement : event.updated; const { pnp: hasPnpInput, methodology } = event.input; if (!engagement.pnp || !hasPnpInput || !methodology) { return; diff --git a/src/components/product/migrations/backfill-empty-mediums.migration.ts b/src/components/product/migrations/backfill-empty-mediums.migration.ts index 459d970bfb..1a7afbe420 100644 --- a/src/components/product/migrations/backfill-empty-mediums.migration.ts +++ b/src/components/product/migrations/backfill-empty-mediums.migration.ts @@ -24,9 +24,7 @@ export class BackfillEmptyMediumsMigration extends BaseMigration { where any(product in products where size(product.mediums) > 0) return products `.run(); - this.logger.notice( - `Found ${engagements.length} engagements with some empty mediums`, - ); + this.logger.notice(`Found ${engagements.length} engagements with some empty mediums`); const updates = engagements.flatMap(({ products }) => { const grouped = groupBy(products, (p) => @@ -43,9 +41,7 @@ export class BackfillEmptyMediumsMigration extends BaseMigration { const mediums = nonEmpties[0].mediums; return empties.map((p) => ({ id: p.id, mediums })); }); - this.logger.notice( - `Resolves to ${updates.length} products to assign mediums to`, - ); + this.logger.notice(`Resolves to ${updates.length} products to assign mediums to`); await this.db .query() diff --git a/src/components/product/migrations/fix-nan-total-verse-equivalents.migration.ts b/src/components/product/migrations/fix-nan-total-verse-equivalents.migration.ts index 53c3086952..177f4802e6 100644 --- a/src/components/product/migrations/fix-nan-total-verse-equivalents.migration.ts +++ b/src/components/product/migrations/fix-nan-total-verse-equivalents.migration.ts @@ -27,9 +27,7 @@ export class FixNaNTotalVerseEquivalentsMigration extends BaseMigration { const products = await this.productService.readManyUnsecured(ids); for (const p of products) { - const correctTotalVerseEquivalent = getTotalVerseEquivalents( - ...p.scriptureReferences, - ); + const correctTotalVerseEquivalent = getTotalVerseEquivalents(...p.scriptureReferences); if (p.__typename === 'DirectScriptureProduct') { await this.productService.updateDirect({ diff --git a/src/components/product/pnp-product-sync.service.ts b/src/components/product/pnp-product-sync.service.ts index 554f288113..cdfcf679e1 100644 --- a/src/components/product/pnp-product-sync.service.ts +++ b/src/components/product/pnp-product-sync.service.ts @@ -7,10 +7,7 @@ import { DateTime } from 'luxon'; import { DateInterval, type ID } from '~/common'; import { ILogger, Logger, ResourceLoader } from '~/core'; import { type Downloadable, type FileVersion } from '../file/dto'; -import { - type PnpExtractionResult, - PnpProblemType, -} from '../pnp/extraction-result'; +import { type PnpExtractionResult, PnpProblemType } from '../pnp/extraction-result'; import { StoryService } from '../story'; import { type CreateDerivativeScriptureProduct, @@ -49,10 +46,7 @@ export class PnpProductSyncService { pnp: Downloadable; result: PnpExtractionResult; }) { - const engagement = await this.resources.load( - 'LanguageEngagement', - engagementId, - ); + const engagement = await this.resources.load('LanguageEngagement', engagementId); const engagementRange = DateInterval.tryFrom( engagement.startDate.value, engagement.endDate.value, @@ -60,12 +54,7 @@ export class PnpProductSyncService { let productRows; try { - productRows = await this.extractor.extract( - pnp, - engagementRange, - availableSteps, - result, - ); + productRows = await this.extractor.extract(pnp, engagementRange, availableSteps, result); } catch (e) { this.logger.error(e.message, { id: pnp.id, @@ -77,11 +66,7 @@ export class PnpProductSyncService { return []; } - return await this.matchRowsToProductChanges( - engagementId, - productRows, - result, - ); + return await this.matchRowsToProductChanges(engagementId, productRows, result); } /** @@ -94,17 +79,11 @@ export class PnpProductSyncService { result: PnpExtractionResult, ) { const scriptureProducts = rows[0].bookName - ? await this.products.loadProductIdsForBookAndVerse( - engagementId, - this.logger, - ) + ? await this.products.loadProductIdsForBookAndVerse(engagementId, this.logger) : []; const storyProducts = rows[0].story - ? await this.products.loadProductIdsByPnpIndex( - engagementId, - DerivativeScriptureProduct.name, - ) + ? await this.products.loadProductIdsByPnpIndex(engagementId, DerivativeScriptureProduct.name) : mapOf({}); if (rows[0].story) { @@ -119,12 +98,9 @@ export class PnpProductSyncService { return row.scripture[0]?.start.book ?? row.unspecifiedScripture?.book; }).flatMap((rowsOfBook) => { const bookName = - rowsOfBook[0].scripture[0]?.start.book ?? - rowsOfBook[0].unspecifiedScripture?.book; + rowsOfBook[0].scripture[0]?.start.book ?? rowsOfBook[0].unspecifiedScripture?.book; if (!bookName) return []; - let existingProductsForBook = scriptureProducts.filter( - (ref) => ref.book === bookName, - ); + let existingProductsForBook = scriptureProducts.filter((ref) => ref.book === bookName); const matches: Array = []; let nonExactMatches: ExtractedRow[] = []; @@ -135,30 +111,24 @@ export class PnpProductSyncService { const withMatches = existingProductsForBook.filter((existingRef) => { if ( existingRef.scriptureRanges.length > 0 && - rowScriptureLabel === - labelOfVerseRanges(existingRef.scriptureRanges) + rowScriptureLabel === labelOfVerseRanges(existingRef.scriptureRanges) ) { return true; } if ( existingRef.unspecifiedScripture && row.unspecifiedScripture && - existingRef.unspecifiedScripture.book === - row.unspecifiedScripture.book && - existingRef.unspecifiedScripture.totalVerses === - row.unspecifiedScripture.totalVerses + existingRef.unspecifiedScripture.book === row.unspecifiedScripture.book && + existingRef.unspecifiedScripture.totalVerses === row.unspecifiedScripture.totalVerses ) { return true; } return false; }); - const existingId = - withMatches.length === 1 ? withMatches[0].id : undefined; + const existingId = withMatches.length === 1 ? withMatches[0].id : undefined; if (existingId) { matches.push({ ...row, existingId }); - existingProductsForBook = existingProductsForBook.filter( - (ref) => ref.id !== existingId, - ); + existingProductsForBook = existingProductsForBook.filter((ref) => ref.id !== existingId); } else { nonExactMatches.push(row); } @@ -166,10 +136,7 @@ export class PnpProductSyncService { // If there's only one product left for this book that hasn't been matched // And there's only one row left that can't be matched to a book & verse count - if ( - existingProductsForBook.length === 1 && - nonExactMatches.length === 1 - ) { + if (existingProductsForBook.length === 1 && nonExactMatches.length === 1) { // Assume that ID belongs to this row. // Use case: A single row changes total verse count while other rows // for this book remain the same or are new. @@ -220,23 +187,14 @@ export class PnpProductSyncService { }: { engagementId: ID<'LanguageEngagement'>; methodology: ProductMethodology; - actionableProductRows: ReadonlyArray< - ExtractedRow & { existingId: ID<'Product'> | undefined } - >; + actionableProductRows: ReadonlyArray | undefined }>; }) { const createdAt = DateTime.now(); const storyIds = await this.getOrCreateStoriesByName(actionableProductRows); // Create/update products 5 at a time. await asyncPool(5, actionableProductRows, async (row) => { - const { - scripture, - unspecifiedScripture, - existingId, - steps, - note, - rowIndex: index, - } = row; + const { scripture, unspecifiedScripture, existingId, steps, note, rowIndex: index } = row; if (row.bookName) { // Populate one of the two product props based on whether it's a known verse range or not. @@ -269,9 +227,7 @@ export class PnpProductSyncService { } else if (row.story) { const props = { produces: storyIds[row.placeholder ? 'Unknown' : row.story]!, - placeholderDescription: row.placeholder - ? `#${row.order} ${row.story}` - : null, + placeholderDescription: row.placeholder ? `#${row.order} ${row.story}` : null, methodology, steps: steps.map((s) => s.step), scriptureReferencesOverride: row.scripture, @@ -300,17 +256,12 @@ export class PnpProductSyncService { private async getOrCreateStoriesByName(rows: readonly ExtractedRow[]) { const names = uniq( - rows.flatMap((row) => - !row.story ? [] : row.placeholder ? 'Unknown' : row.story, - ), + rows.flatMap((row) => (!row.story ? [] : row.placeholder ? 'Unknown' : row.story)), ); if (names.length === 0) { return {}; } - const existingList = await this.repo.getProducibleIdsByNames( - names, - ProducibleType.Story, - ); + const existingList = await this.repo.getProducibleIdsByNames(names, ProducibleType.Story); const existing = mapEntries(existingList, (r) => [r.name, r.id]).asRecord; const byName = { ...existing }; const newNames = difference(names, Object.keys(existing)); diff --git a/src/components/product/product.extractor.ts b/src/components/product/product.extractor.ts index 06b2813bc8..4f1bddcf8b 100644 --- a/src/components/product/product.extractor.ts +++ b/src/components/product/product.extractor.ts @@ -17,15 +17,9 @@ import { stepPlanCompleteDate, type WrittenScripturePlanningSheet, } from '../pnp'; -import { - type PnpPlanningExtractionResult, - PnpProblemType, -} from '../pnp/extraction-result'; +import { type PnpPlanningExtractionResult, PnpProblemType } from '../pnp/extraction-result'; import { verifyEngagementDateRangeMatches } from '../pnp/verifyEngagementDateRangeMatches'; -import { - ScriptureRange, - type UnspecifiedScripturePortion, -} from '../scripture/dto'; +import { ScriptureRange, type UnspecifiedScripturePortion } from '../scripture/dto'; import { type ProductStep, type ProductStep as Step } from './dto'; @Injectable() @@ -40,11 +34,7 @@ export class ProductExtractor { const sheet = pnp.planning; const stepColumns = findStepColumns(sheet, result, availableSteps); - const progressStepColumns = findStepColumns( - pnp.progress, - undefined, - availableSteps, - ); + const progressStepColumns = findStepColumns(pnp.progress, undefined, availableSteps); if (!verifyEngagementDateRangeMatches(sheet, result, engagementRange)) { return []; @@ -66,9 +56,7 @@ export class ProductExtractor { ? sheet.sustainabilityGoals .walkDown() .map((cell) => ({ - title: `Train ${ - sheet.sustainabilityRole(cell.row)?.replace(/:$/, '') ?? '' - }`, + title: `Train ${sheet.sustainabilityRole(cell.row)?.replace(/:$/, '') ?? ''}`, count: sheet.sustainabilityRoleCount(cell.row) ?? 0, })) .filter((row) => row.count > 0) @@ -93,10 +81,7 @@ const parseProductRow = const steps = entries(stepColumns).flatMap(([step, column]) => { const plannedCell = sheet.cell(column, row); - const progressCell = pnp.progress.cell( - progressStepColumns[step], - progressRow, - ); + const progressCell = pnp.progress.cell(progressStepColumns[step], progressRow); if ( !isGoalStepPlannedInsideProject(pnp, plannedCell, step, result) || diff --git a/src/components/product/product.loader.ts b/src/components/product/product.loader.ts index dfff693bf6..c8ac943397 100644 --- a/src/components/product/product.loader.ts +++ b/src/components/product/product.loader.ts @@ -1,9 +1,5 @@ import { type ID } from '~/common'; -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; import { type AnyProduct, DerivativeScriptureProduct, @@ -13,15 +9,8 @@ import { } from './dto'; import { ProductService } from './product.service'; -@LoaderFactory(() => [ - Product, - DirectScriptureProduct, - DerivativeScriptureProduct, - OtherProduct, -]) -export class ProductLoader - implements DataLoaderStrategy> -{ +@LoaderFactory(() => [Product, DirectScriptureProduct, DerivativeScriptureProduct, OtherProduct]) +export class ProductLoader implements DataLoaderStrategy> { constructor(private readonly products: ProductService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/product/product.repository.ts b/src/components/product/product.repository.ts index 1581e087a8..5614fc1d8b 100644 --- a/src/components/product/product.repository.ts +++ b/src/components/product/product.repository.ts @@ -1,21 +1,9 @@ import { Injectable } from '@nestjs/common'; import { oneLine } from 'common-tags'; -import { - inArray, - isNull, - node, - not, - type Query, - relation, -} from 'cypher-query-builder'; +import { inArray, isNull, node, not, type Query, relation } from 'cypher-query-builder'; import { DateTime } from 'luxon'; import { type Except, type Merge } from 'type-fest'; -import { - CreationFailed, - EnhancedResource, - type ID, - type Range, -} from '~/common'; +import { CreationFailed, EnhancedResource, type ID, type Range } from '~/common'; import { CommonRepository, type DbTypeOf, OnIndex } from '~/core/database'; import { type DbChanges, getChanges } from '~/core/database/changes'; import { @@ -63,9 +51,7 @@ const ScriptureRange = EnhancedResource.of(RawScriptureRange); export type HydratedProductRow = Merge< Omit< - DbTypeOf< - DirectScriptureProduct & DerivativeScriptureProduct & OtherProduct - >, + DbTypeOf, 'scriptureReferencesOverride' >, { @@ -112,9 +98,7 @@ export class ProductRepository extends CommonRepository { relation('out', '', 'scriptureReferences', ACTIVE), node('scriptureRanges', ScriptureRange.dbLabel), ]) - .return( - collect('scriptureRanges { .start, .end }').as('scriptureRanges'), - ), + .return(collect('scriptureRanges { .start, .end }').as('scriptureRanges')), ) .return<{ id: ID; @@ -139,10 +123,7 @@ export class ProductRepository extends CommonRepository { node('node', ['Product', ...(type ? [type] : [])]), ]) .where({ 'node.pnpIndex': not(isNull()) }) - .return<{ id: ID; pnpIndex: number }>([ - 'node.id as id', - 'node.pnpIndex as pnpIndex', - ]) + .return<{ id: ID; pnpIndex: number }>(['node.id as id', 'node.pnpIndex as pnpIndex']) .run(); } @@ -162,10 +143,7 @@ export class ProductRepository extends CommonRepository { .run(); } - async getProducibleIdsByNames( - names: readonly string[], - type?: ProducibleType, - ) { + async getProducibleIdsByNames(names: readonly string[], type?: ProducibleType) { const res = await this.db .query() .match([ @@ -174,10 +152,7 @@ export class ProductRepository extends CommonRepository { node('prop', 'Property'), ]) .where({ 'prop.value': inArray(names) }) - .return<{ id: ID; name: string }>([ - 'producible.id as id', - 'prop.value as name', - ]) + .return<{ id: ID; name: string }>(['producible.id as id', 'prop.value as name']) .run(); return res; } @@ -236,8 +211,7 @@ export class ProductRepository extends CommonRepository { engagement: 'engagement.id', project: 'project.id', produces: 'produces', - unspecifiedScripture: - 'unspecifiedScripture { .book, .totalVerses }', + unspecifiedScripture: 'unspecifiedScripture { .book, .totalVerses }', scriptureReferences: 'scriptureReferences', }).as('dto'), ); @@ -278,9 +252,7 @@ export class ProductRepository extends CommonRepository { }, ) { const isDerivative = 'produces' in input; - const Product = isDerivative - ? DerivativeScriptureProduct - : DirectScriptureProduct; + const Product = isDerivative ? DerivativeScriptureProduct : DirectScriptureProduct; const initialProps = { mediums: input.mediums ?? [], purposes: input.purposes ?? [], @@ -290,8 +262,7 @@ export class ProductRepository extends CommonRepository { placeholderDescription: input.placeholderDescription, canDelete: true, progressTarget: input.progressTarget, - progressStepMeasurement: - input.progressStepMeasurement ?? ProgressMeasurement.Percent, + progressStepMeasurement: input.progressStepMeasurement ?? ProgressMeasurement.Percent, ...(isDerivative ? { isOverriding: !!input.scriptureReferencesOverride, @@ -328,16 +299,15 @@ export class ProductRepository extends CommonRepository { // Connect scripture ranges .apply((q) => { const createdAt = DateTime.local(); - const connectScriptureRange = - (label: string) => (range: ScriptureRangeInput) => - [ - node('node'), - relation('out', '', label, ACTIVE), - node('', ScriptureRange.dbLabels, { - ...ScriptureRange.type.fromReferences(range), - createdAt, - }), - ]; + const connectScriptureRange = (label: string) => (range: ScriptureRangeInput) => + [ + node('node'), + relation('out', '', label, ACTIVE), + node('', ScriptureRange.dbLabels, { + ...ScriptureRange.type.fromReferences(range), + createdAt, + }), + ]; return q.create([ ...(!isDerivative ? input.scriptureReferences ?? [] : []).map( connectScriptureRange('scriptureReferences'), @@ -376,8 +346,7 @@ export class ProductRepository extends CommonRepository { description: input.description, canDelete: true, progressTarget: input.progressTarget, - progressStepMeasurement: - input.progressStepMeasurement ?? ProgressMeasurement.Percent, + progressStepMeasurement: input.progressStepMeasurement ?? ProgressMeasurement.Percent, placeholderDescription: input.placeholderDescription, }; @@ -438,10 +407,7 @@ export class ProductRepository extends CommonRepository { .first(); } - async updateUnspecifiedScripture( - productId: ID, - input: UnspecifiedScripturePortionInput | null, - ) { + async updateUnspecifiedScripture(productId: ID, input: UnspecifiedScripturePortionInput | null) { await this.db .query() .matchNode('node', 'Product', { id: productId }) @@ -518,10 +484,7 @@ export class ProductRepository extends CommonRepository { return result!; // result from paginate() will always have 1 row. } - async mergeCompletionDescription( - description: string, - methodology: Methodology, - ) { + async mergeCompletionDescription(description: string, methodology: Methodology) { await this.db .query() .merge( @@ -550,17 +513,11 @@ export class ProductRepository extends CommonRepository { .query() .apply((q) => query - ? q.call( - ProductCompletionDescriptionIndex.search(query).yield('node'), - ) + ? q.call(ProductCompletionDescriptionIndex.search(query).yield('node')) : q.matchNode('node', 'ProductCompletionDescription'), ) - .apply((q) => - methodology ? q.with('node').where({ node: { methodology } }) : q, - ) - .apply((q) => - query ? q : q.with('node').orderBy('node.lastUsedAt', 'DESC'), - ) + .apply((q) => (methodology ? q.with('node').where({ node: { methodology } }) : q)) + .apply((q) => (query ? q : q.with('node').orderBy('node.lastUsedAt', 'DESC'))) .with('node.value as node') .apply(paginate(input, (q) => q.return<{ dto: string }>('node as dto'))) .first(); @@ -569,10 +526,7 @@ export class ProductRepository extends CommonRepository { @OnIndex('schema') private async createCompletionDescriptionIndex() { - await this.db - .query() - .apply(ProductCompletionDescriptionIndex.create()) - .run(); + await this.db.query().apply(ProductCompletionDescriptionIndex.create()).run(); } @OnIndex() diff --git a/src/components/product/product.resolver.ts b/src/components/product/product.resolver.ts index 3dba73433c..d913600ff2 100644 --- a/src/components/product/product.resolver.ts +++ b/src/components/product/product.resolver.ts @@ -1,13 +1,4 @@ -import { - Args, - Info, - Int, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Info, Int, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { Book, labelOfVerseRanges } from '@seedcompany/scripture'; import { stripIndent } from 'common-tags'; import { startCase } from 'lodash'; @@ -81,9 +72,7 @@ export class ProductResolver { @ResolveField(() => ProductApproach, { nullable: true }) approach(@Parent() product: AnyProduct): ProductApproach | null { - return product.methodology.value - ? MethodologyToApproach[product.methodology.value] - : null; + return product.methodology.value ? MethodologyToApproach[product.methodology.value] : null; } @ResolveField(() => String, { @@ -127,22 +116,15 @@ export class ProductResolver { return product.title.value ?? null; } if (!product.produces) { - if ( - !product.scriptureReferences.canRead || - !product.unspecifiedScripture.canRead - ) { + if (!product.scriptureReferences.canRead || !product.unspecifiedScripture.canRead) { return null; } if (product.unspecifiedScripture.value) { - const { book, totalVerses: verses } = - product.unspecifiedScripture.value; + const { book, totalVerses: verses } = product.unspecifiedScripture.value; const totalVerses = Book.named(book).totalVerses; return `${book} (${verses} / ${totalVerses} verses)`; } - return labelOfVerseRanges( - product.scriptureReferences.value, - collapseAfter, - ); + return labelOfVerseRanges(product.scriptureReferences.value, collapseAfter); } if (!product.produces.value) { return null; @@ -175,9 +157,7 @@ export class ProductResolver { Returns a list of available steps for the given constraints. `, }) - availableProductSteps( - @Args() options: AvailableStepsOptions, - ): readonly Step[] { + availableProductSteps(@Args() options: AvailableStepsOptions): readonly Step[] { return getAvailableSteps(options); } @@ -228,9 +208,7 @@ export class ProductResolver { @Mutation(() => CreateProductOutput, { description: 'Create an other product entry', }) - async createOtherProduct( - @Args('input') input: CreateOtherProduct, - ): Promise { + async createOtherProduct(@Args('input') input: CreateOtherProduct): Promise { const product = await this.productService.create(input); return { product }; } @@ -258,9 +236,7 @@ export class ProductResolver { @Mutation(() => UpdateProductOutput, { description: 'Update an other product entry', }) - async updateOtherProduct( - @Args('input') input: UpdateOtherProduct, - ): Promise { + async updateOtherProduct(@Args('input') input: UpdateOtherProduct): Promise { const product = await this.productService.updateOther(input); return { product }; } diff --git a/src/components/product/product.service.ts b/src/components/product/product.service.ts index 64d8ca2e4a..179c7760ee 100644 --- a/src/components/product/product.service.ts +++ b/src/components/product/product.service.ts @@ -45,10 +45,7 @@ import { type UpdateOtherProduct, type UpdateBaseProduct as UpdateProduct, } from './dto'; -import { - type HydratedProductRow, - ProductRepository, -} from './product.repository'; +import { type HydratedProductRow, ProductRepository } from './product.repository'; @Injectable() export class ProductService { @@ -61,23 +58,14 @@ export class ProductService { ) {} async create( - input: - | CreateDirectScriptureProduct - | CreateDerivativeScriptureProduct - | CreateOtherProduct, + input: CreateDirectScriptureProduct | CreateDerivativeScriptureProduct | CreateOtherProduct, ): Promise { - const engagement = await this.repo.getBaseNode( - input.engagementId, - 'Engagement', - ); + const engagement = await this.repo.getBaseNode(input.engagementId, 'Engagement'); if (!engagement) { this.logger.warning(`Could not find engagement`, { id: input.engagementId, }); - throw new NotFoundException( - 'Could not find engagement', - 'product.engagementId', - ); + throw new NotFoundException('Could not find engagement', 'product.engagementId'); } const otherInput: CreateOtherProduct | undefined = @@ -92,26 +80,16 @@ export class ProductService { let producibleType: ProducibleType | undefined = undefined; if (derivativeInput) { - const producible = await this.repo.getBaseNode( - derivativeInput.produces, - 'Producible', - ); + const producible = await this.repo.getBaseNode(derivativeInput.produces, 'Producible'); if (!producible) { this.logger.warning(`Could not find producible node`, { id: derivativeInput.produces, }); - throw new NotFoundException( - 'Could not find producible node', - 'product.produces', - ); + throw new NotFoundException('Could not find producible node', 'product.produces'); } - producibleType = this.resources.resolveTypeByBaseNode( - producible, - ) as ProducibleType; + producibleType = this.resources.resolveTypeByBaseNode(producible) as ProducibleType; - totalVerses = getTotalVerses( - ...(derivativeInput.scriptureReferencesOverride ?? []), - ); + totalVerses = getTotalVerses(...(derivativeInput.scriptureReferencesOverride ?? [])); totalVerseEquivalents = getTotalVerseEquivalents( ...(derivativeInput.scriptureReferencesOverride ?? []), ); @@ -120,12 +98,8 @@ export class ProductService { ? scriptureInput.unspecifiedScripture.totalVerses : getTotalVerses(...(scriptureInput.scriptureReferences ?? [])); totalVerseEquivalents = scriptureInput.unspecifiedScripture - ? getVerseEquivalentsFromUnspecified( - scriptureInput.unspecifiedScripture, - ) - : getTotalVerseEquivalents( - ...(scriptureInput.scriptureReferences ?? []), - ); + ? getVerseEquivalentsFromUnspecified(scriptureInput.unspecifiedScripture) + : getTotalVerseEquivalents(...(scriptureInput.scriptureReferences ?? [])); } const type = @@ -157,23 +131,15 @@ export class ProductService { this.logger.debug(`product created`, { id }); const created = await this.readOne(id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Product) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Product) : e; }); - this.privileges - .for(resolveProductType(created), created) - .verifyCan('create'); + this.privileges.for(resolveProductType(created), created).verifyCan('create'); return created; } - @HandleIdLookup([ - DirectScriptureProduct, - DerivativeScriptureProduct, - OtherProduct, - ]) + @HandleIdLookup([DirectScriptureProduct, DerivativeScriptureProduct, OtherProduct]) async readOne(id: ID, _view?: ObjectView): Promise { const dto = await this.readOneUnsecured(id); return this.secure(dto); @@ -193,29 +159,19 @@ export class ProductService { return rows.map((row) => this.secure(row)); } - async readManyUnsecured( - ids: readonly ID[], - ): Promise>> { + async readManyUnsecured(ids: readonly ID[]): Promise>> { const rows = await this.repo.readMany(ids); return rows.map((row) => this.mapDbRowToDto(row)); } private mapDbRowToDto(row: HydratedProductRow): UnsecuredDto { - const { - isOverriding, - produces: rawProducible, - title, - description, - ...rawProps - } = row; + const { isOverriding, produces: rawProducible, title, description, ...rawProps } = row; const props = { ...rawProps, mediums: rawProps.mediums ?? [], purposes: rawProps.purposes ?? [], steps: rawProps.steps ?? [], - scriptureReferences: this.scriptureRefs.parseList( - rawProps.scriptureReferences, - ), + scriptureReferences: this.scriptureRefs.parseList(rawProps.scriptureReferences), }; if (title) { @@ -240,14 +196,10 @@ export class ProductService { const producible = { ...rawProducible, - scriptureReferences: this.scriptureRefs.parseList( - rawProducible.scriptureReferences, - ), + scriptureReferences: this.scriptureRefs.parseList(rawProducible.scriptureReferences), }; - const producibleType = this.resources.resolveType( - producible.__typename, - ) as ProducibleType; + const producibleType = this.resources.resolveType(producible.__typename) as ProducibleType; const dto: UnsecuredDto = { ...props, @@ -255,9 +207,7 @@ export class ProductService { scriptureReferences: !isOverriding ? producible.scriptureReferences : props.scriptureReferences, - scriptureReferencesOverride: !isOverriding - ? null - : props.scriptureReferences, + scriptureReferencesOverride: !isOverriding ? null : props.scriptureReferences, __typename: 'DerivativeScriptureProduct', }; return dto; @@ -271,37 +221,28 @@ export class ProductService { input: UpdateDirectScriptureProduct, currentProduct?: UnsecuredDto, ): Promise { - currentProduct ??= asProductType(DirectScriptureProduct)( - await this.readOneUnsecured(input.id), - ); + currentProduct ??= asProductType(DirectScriptureProduct)(await this.readOneUnsecured(input.id)); const changes = this.getDirectProductChanges(input, currentProduct); this.privileges .for(DirectScriptureProduct, currentProduct) .verifyChanges(changes, { pathPrefix: 'product' }); - const { scriptureReferences, unspecifiedScripture, ...simpleChanges } = - changes; + const { scriptureReferences, unspecifiedScripture, ...simpleChanges } = changes; await this.scriptureRefs.update(input.id, scriptureReferences); // update unspecifiedScripture if it's defined if (unspecifiedScripture !== undefined) { - await this.repo.updateUnspecifiedScripture( - input.id, - unspecifiedScripture, - ); + await this.repo.updateUnspecifiedScripture(input.id, unspecifiedScripture); } await this.mergeCompletionDescription(changes, currentProduct); - const productUpdatedScriptureReferences = asProductType( - DirectScriptureProduct, - )(await this.readOne(input.id)); - - return await this.repo.updateProperties( - productUpdatedScriptureReferences, - simpleChanges, + const productUpdatedScriptureReferences = asProductType(DirectScriptureProduct)( + await this.readOne(input.id), ); + + return await this.repo.updateProperties(productUpdatedScriptureReferences, simpleChanges); } private getDirectProductChanges( @@ -316,23 +257,17 @@ export class ProductService { let changes = { ...partialChanges, steps: this.restrictStepsChange(current, partialChanges), - progressTarget: this.restrictProgressTargetChange( - current, - input, - partialChanges, + progressTarget: this.restrictProgressTargetChange(current, input, partialChanges), + unspecifiedScripture: ifDiff(compareNullable(UnspecifiedScripturePortion.isEqual))( + input.unspecifiedScripture, + current.unspecifiedScripture, ), - unspecifiedScripture: ifDiff( - compareNullable(UnspecifiedScripturePortion.isEqual), - )(input.unspecifiedScripture, current.unspecifiedScripture), scriptureReferences: ifDiff(isScriptureEqual)( input.scriptureReferences, current.scriptureReferences, ), }; - if ( - changes.unspecifiedScripture !== undefined || - changes.scriptureReferences !== undefined - ) { + if (changes.unspecifiedScripture !== undefined || changes.scriptureReferences !== undefined) { changes = { ...changes, totalVerses: changes.unspecifiedScripture @@ -368,10 +303,7 @@ export class ProductService { this.logger.warning(`Could not find producible node`, { id: produces, }); - throw new NotFoundException( - 'Could not find producible node', - 'product.produces', - ); + throw new NotFoundException('Could not find producible node', 'product.produces'); } await this.repo.updateProducible(input, produces); } @@ -383,9 +315,9 @@ export class ProductService { isOverriding: true, }); - const productUpdatedScriptureReferences = asProductType( - DerivativeScriptureProduct, - )(await this.readOne(input.id)); + const productUpdatedScriptureReferences = asProductType(DerivativeScriptureProduct)( + await this.readOne(input.id), + ); return await this.repo.updateDerivativeProperties( productUpdatedScriptureReferences, @@ -408,28 +340,19 @@ export class ProductService { steps: this.restrictStepsChange(current, partialChanges), // This needs to be manually checked for changes as the existing value // is the object not the ID. - produces: - current.produces.id !== input.produces ? input.produces : undefined, - progressTarget: this.restrictProgressTargetChange( - current, - input, - partialChanges, - ), + produces: current.produces.id !== input.produces ? input.produces : undefined, + progressTarget: this.restrictProgressTargetChange(current, input, partialChanges), scriptureReferencesOverride: input.scriptureReferencesOverride !== undefined && !compareNullable(isScriptureEqual)( input.scriptureReferencesOverride, - current.scriptureReferencesOverride - ? current.scriptureReferencesOverride - : null, + current.scriptureReferencesOverride ? current.scriptureReferencesOverride : null, ) ? input.scriptureReferencesOverride : undefined, }; if (changes.scriptureReferencesOverride !== undefined) { - const scripture = - changes.scriptureReferencesOverride ?? - current.produces.scriptureReferences; + const scripture = changes.scriptureReferencesOverride ?? current.produces.scriptureReferences; changes = { ...changes, totalVerses: getTotalVerses(...scripture), @@ -448,11 +371,7 @@ export class ProductService { let changes = this.repo.getActualOtherChanges(currentProduct, input); changes = { ...changes, - progressTarget: this.restrictProgressTargetChange( - currentProduct, - input, - changes, - ), + progressTarget: this.restrictProgressTargetChange(currentProduct, input, changes), }; this.privileges @@ -461,9 +380,7 @@ export class ProductService { await this.mergeCompletionDescription(changes, currentProduct); - const currentSecured = asProductType(OtherProduct)( - this.secure(currentProduct), - ); + const currentSecured = asProductType(OtherProduct)(this.secure(currentProduct)); return await this.repo.updateOther(currentSecured, changes); } @@ -475,47 +392,32 @@ export class ProductService { input: Pick, changes: Pick, ) { - return simpleSwitch( - input.progressStepMeasurement ?? currentProduct.progressStepMeasurement, - { - Number: changes.progressStepMeasurement - ? // If measurement is being changed to number, - // accept new target value or default to 1 (as done in create). - changes.progressTarget ?? 1 - : // If measurement was already number, - // accept new target value if given or accept no change. - changes.progressTarget, - // If measurement is changing to percent or boolean, change target - // to its enforced value, otherwise don't allow any changes. - Percent: changes.progressStepMeasurement ? 100 : undefined, - Boolean: changes.progressStepMeasurement ? 1 : undefined, - }, - ); + return simpleSwitch(input.progressStepMeasurement ?? currentProduct.progressStepMeasurement, { + Number: changes.progressStepMeasurement + ? // If measurement is being changed to number, + // accept new target value or default to 1 (as done in create). + changes.progressTarget ?? 1 + : // If measurement was already number, + // accept new target value if given or accept no change. + changes.progressTarget, + // If measurement is changing to percent or boolean, change target + // to its enforced value, otherwise don't allow any changes. + Percent: changes.progressStepMeasurement ? 100 : undefined, + Boolean: changes.progressStepMeasurement ? 1 : undefined, + }); } private restrictStepsChange( - current: Pick< - UnsecuredDto, - 'methodology' | 'steps' | 'produces' - >, - changes: Partial< - Pick - >, + current: Pick, 'methodology' | 'steps' | 'produces'>, + changes: Partial>, ) { const methodology = - changes.methodology !== undefined - ? changes.methodology - : current.methodology; + changes.methodology !== undefined ? changes.methodology : current.methodology; const availableSteps = getAvailableSteps({ - type: current.produces - ? current.produces.__typename - : ProducibleType.DirectScriptureProduct, + type: current.produces ? current.produces.__typename : ProducibleType.DirectScriptureProduct, methodology, }); - const steps = intersection( - availableSteps, - changes.steps ? changes.steps : current.steps ?? [], - ); + const steps = intersection(availableSteps, changes.steps ? changes.steps : current.steps ?? []); // Check again to see if new steps value is different than current. // and return updated value or "no change". return !isSame(steps, current.steps) ? steps : undefined; @@ -525,10 +427,7 @@ export class ProductService { changes: Partial>, currentProduct: UnsecuredDto, ) { - if ( - changes.describeCompletion === undefined && - changes.methodology === undefined - ) { + if (changes.describeCompletion === undefined && changes.methodology === undefined) { // no changes, do nothing return; } @@ -537,9 +436,7 @@ export class ProductService { ? changes.describeCompletion : currentProduct.describeCompletion; const methodology = - changes.methodology !== undefined - ? changes.methodology - : currentProduct.methodology; + changes.methodology !== undefined ? changes.methodology : currentProduct.methodology; if (!describeCompletion || !methodology) { // If either are still missing or have been set to null skip persisting. return; @@ -569,20 +466,13 @@ export class ProductService { }; } - async loadProductIdsForBookAndVerse( - engagementId: ID, - logger: ILogger = this.logger, - ) { + async loadProductIdsForBookAndVerse(engagementId: ID, logger: ILogger = this.logger) { const productRefs = await this.repo.listIdsAndScriptureRefs(engagementId); return productRefs.flatMap((productRef) => { - const refs = productRef.scriptureRanges.map((raw) => - ScriptureRange.fromIds(raw), - ); + const refs = productRef.scriptureRanges.map((raw) => ScriptureRange.fromIds(raw)); const books = uniq([ ...refs.flatMap((ref) => [ref.start.book, ref.end.book]), - ...(productRef.unspecifiedScripture - ? [productRef.unspecifiedScripture.book] - : []), + ...(productRef.unspecifiedScripture ? [productRef.unspecifiedScripture.book] : []), ]); const totalVerses = productRef.unspecifiedScripture?.totalVerses ?? @@ -615,32 +505,22 @@ export class ProductService { } async loadProductIdsByPnpIndex(engagementId: ID, typeFilter?: string) { - const productRefs = await this.repo.listIdsWithPnpIndexes( - engagementId, - typeFilter, - ); + const productRefs = await this.repo.listIdsWithPnpIndexes(engagementId, typeFilter); return mapEntries(productRefs, ({ id, pnpIndex }) => [pnpIndex, id]).asMap; } - async loadProductIdsWithProducibleNames( - engagementId: ID, - type?: ProducibleType, - ) { + async loadProductIdsWithProducibleNames(engagementId: ID, type?: ProducibleType) { const refs = await this.repo.listIdsWithProducibleNames(engagementId, type); return mapEntries(refs, ({ id, name }) => [name, id]).asMap; } - protected getMethodologiesByApproach( - approach: ProductApproach, - ): ProductMethodology[] { + protected getMethodologiesByApproach(approach: ProductApproach): ProductMethodology[] { return Object.keys(MethodologyToApproach).filter( (key) => MethodologyToApproach[key as ProductMethodology] === approach, ) as ProductMethodology[]; } - async suggestCompletionDescriptions( - input: ProductCompletionDescriptionSuggestionsInput, - ) { + async suggestCompletionDescriptions(input: ProductCompletionDescriptionSuggestionsInput) { return await this.repo.suggestCompletionDescriptions(input); } } diff --git a/src/components/progress-report/community-stories/community-story-prompts.ts b/src/components/progress-report/community-stories/community-story-prompts.ts index 831e4777ed..d68d94e767 100644 --- a/src/components/progress-report/community-stories/community-story-prompts.ts +++ b/src/components/progress-report/community-stories/community-story-prompts.ts @@ -14,13 +14,11 @@ export const prompts = [ Prompt.create({ id: 'MsdfnoZkYda', text: 'Share a specific example of how translated Scripture changed or challenged the way one person viewed themselves, others, and/or God. (Ex: in marriage, parenting, family life, personal growth, friendships, relationship with God, etc.)', - shortLabel: - 'Share a specific example of how translated Scripture changed a person?', + shortLabel: 'Share a specific example of how translated Scripture changed a person?', }), Prompt.create({ id: 'P4FO95xsZ7A', text: 'Tell how someone responded to the Scripture in a checking session. Was there a passage that was meaningful to this specific person? If yes, what did they discover?', - shortLabel: - 'Tell how someone responded to the Scripture in a checking session.', + shortLabel: 'Tell how someone responded to the Scripture in a checking session.', }), ]; diff --git a/src/components/progress-report/community-stories/progress-report-community-story.resolver.ts b/src/components/progress-report/community-stories/progress-report-community-story.resolver.ts index f14739d8b2..3e5efeba3b 100644 --- a/src/components/progress-report/community-stories/progress-report-community-story.resolver.ts +++ b/src/components/progress-report/community-stories/progress-report-community-story.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { IdArg, type IdOf } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { PeriodicReportLoader } from '../../periodic-report'; diff --git a/src/components/progress-report/community-stories/progress-report-community-story.service.ts b/src/components/progress-report/community-stories/progress-report-community-story.service.ts index 2357c1ccde..e400804acb 100644 --- a/src/components/progress-report/community-stories/progress-report-community-story.service.ts +++ b/src/components/progress-report/community-stories/progress-report-community-story.service.ts @@ -17,12 +17,7 @@ export class ProgressReportCommunityStoryService extends PromptVariantResponseLi } protected async getPrivilegeContext(dto: UnsecuredDto) { - const report = (await this.resources.loadByBaseNode( - dto.parent, - )) as ProgressReport; - return withEffectiveSensitivity( - withScope({}, report.scope), - report.sensitivity, - ); + const report = (await this.resources.loadByBaseNode(dto.parent)) as ProgressReport; + return withEffectiveSensitivity(withScope({}, report.scope), report.sensitivity); } } diff --git a/src/components/progress-report/dto/community-stories.dto.ts b/src/components/progress-report/dto/community-stories.dto.ts index fbae90b40e..97f315706f 100644 --- a/src/components/progress-report/dto/community-stories.dto.ts +++ b/src/components/progress-report/dto/community-stories.dto.ts @@ -6,15 +6,12 @@ import { ProgressReportHighlight } from './highlights.dto'; @RegisterResource({ db: e.ProgressReport.CommunityStory }) export class ProgressReportCommunityStory extends PromptVariantResponse { - static readonly Parent = () => - import('./progress-report.dto').then((m) => m.ProgressReport); + static readonly Parent = () => import('./progress-report.dto').then((m) => m.ProgressReport); static Variants = ProgressReportHighlight.Variants; static readonly ConfirmThisClassPassesSensitivityToPolicies = true; } -export type CommunityStoryVariant = VariantOf< - typeof ProgressReportCommunityStory ->; +export type CommunityStoryVariant = VariantOf; declare module '~/core/resources/map' { interface ResourceMap { diff --git a/src/components/progress-report/dto/highlights.dto.ts b/src/components/progress-report/dto/highlights.dto.ts index 3994ceb211..485da6160b 100644 --- a/src/components/progress-report/dto/highlights.dto.ts +++ b/src/components/progress-report/dto/highlights.dto.ts @@ -24,8 +24,7 @@ const variants = Variant.createList({ @RegisterResource({ db: e.ProgressReport.Highlight }) export class ProgressReportHighlight extends PromptVariantResponse { - static readonly Parent = () => - import('./progress-report.dto').then((m) => m.ProgressReport); + static readonly Parent = () => import('./progress-report.dto').then((m) => m.ProgressReport); static Variants = variants; static readonly ConfirmThisClassPassesSensitivityToPolicies = true; } diff --git a/src/components/progress-report/dto/progress-report-list.dto.ts b/src/components/progress-report/dto/progress-report-list.dto.ts index 128f2bc838..7c3f070a5b 100644 --- a/src/components/progress-report/dto/progress-report-list.dto.ts +++ b/src/components/progress-report/dto/progress-report-list.dto.ts @@ -1,11 +1,5 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; -import { - FilterField, - OmitType, - PaginatedList, - PickType, - SecuredList, -} from '~/common'; +import { FilterField, OmitType, PaginatedList, PickType, SecuredList } from '~/common'; import { EngagementFilters } from '../../engagement/dto'; import { PeriodicReportListInput } from '../../periodic-report/dto'; import { PnpExtractionResultFilters } from '../../pnp/extraction-result'; @@ -14,10 +8,11 @@ import { ProgressReportStatus } from './progress-report-status.enum'; import { ProgressReport } from './progress-report.dto'; @InputType() -export abstract class ProgressReportFilters extends PickType( - PeriodicReportListInput, - ['start', 'end', 'parent'], -) { +export abstract class ProgressReportFilters extends PickType(PeriodicReportListInput, [ + 'start', + 'end', + 'parent', +]) { @Field(() => [ProgressReportStatus], { nullable: true, }) diff --git a/src/components/progress-report/dto/progress-report-status.enum.ts b/src/components/progress-report/dto/progress-report-status.enum.ts index 399b862f71..2431b867f6 100644 --- a/src/components/progress-report/dto/progress-report-status.enum.ts +++ b/src/components/progress-report/dto/progress-report-status.enum.ts @@ -4,18 +4,9 @@ import { type EnumType, makeEnum, SecuredEnum } from '~/common'; export type ProgressReportStatus = EnumType; export const ProgressReportStatus = makeEnum({ name: 'ProgressReportStatus', - values: [ - 'NotStarted', - 'InProgress', - 'PendingTranslation', - 'InReview', - 'Approved', - 'Published', - ], + values: ['NotStarted', 'InProgress', 'PendingTranslation', 'InReview', 'Approved', 'Published'], exposeOrder: true, }); @ObjectType() -export class SecuredProgressReportStatus extends SecuredEnum( - ProgressReportStatus, -) {} +export class SecuredProgressReportStatus extends SecuredEnum(ProgressReportStatus) {} diff --git a/src/components/progress-report/dto/progress-report.dto.ts b/src/components/progress-report/dto/progress-report.dto.ts index 567643e734..66b18367c2 100644 --- a/src/components/progress-report/dto/progress-report.dto.ts +++ b/src/components/progress-report/dto/progress-report.dto.ts @@ -30,8 +30,7 @@ const Interfaces = IntersectTypes(IPeriodicReport, Resource, Commentable); implements: Interfaces.members, }) export class ProgressReport extends Interfaces { - static readonly Parent = () => - import('../../engagement/dto').then((m) => m.IEngagement); + static readonly Parent = () => import('../../engagement/dto').then((m) => m.IEngagement); static readonly Relations = { highlights: [ProgressReportHighlight], teamNews: [ProgressReportTeamNews], diff --git a/src/components/progress-report/dto/team-news.dto.ts b/src/components/progress-report/dto/team-news.dto.ts index 5fbda73ce7..1d69355964 100644 --- a/src/components/progress-report/dto/team-news.dto.ts +++ b/src/components/progress-report/dto/team-news.dto.ts @@ -6,8 +6,7 @@ import { ProgressReportHighlight } from './highlights.dto'; @RegisterResource({ db: e.ProgressReport.TeamNews }) export class ProgressReportTeamNews extends PromptVariantResponse { - static readonly Parent = () => - import('./progress-report.dto').then((m) => m.ProgressReport); + static readonly Parent = () => import('./progress-report.dto').then((m) => m.ProgressReport); static Variants = ProgressReportHighlight.Variants; static readonly ConfirmThisClassPassesSensitivityToPolicies = true; } diff --git a/src/components/progress-report/highlights/progress-report-highlights.resolver.ts b/src/components/progress-report/highlights/progress-report-highlights.resolver.ts index 2d1fa17061..e6a2d86e48 100644 --- a/src/components/progress-report/highlights/progress-report-highlights.resolver.ts +++ b/src/components/progress-report/highlights/progress-report-highlights.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { IdArg, type IdOf } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { PeriodicReportLoader } from '../../periodic-report'; diff --git a/src/components/progress-report/highlights/progress-report-highlights.service.ts b/src/components/progress-report/highlights/progress-report-highlights.service.ts index 84dce2bcdf..614584f30c 100644 --- a/src/components/progress-report/highlights/progress-report-highlights.service.ts +++ b/src/components/progress-report/highlights/progress-report-highlights.service.ts @@ -16,12 +16,7 @@ export class ProgressReportHighlightsService extends PromptVariantResponseListSe } protected async getPrivilegeContext(dto: UnsecuredDto) { - const report = (await this.resources.loadByBaseNode( - dto.parent, - )) as ProgressReport; - return withEffectiveSensitivity( - withScope({}, report.scope), - report.sensitivity, - ); + const report = (await this.resources.loadByBaseNode(dto.parent)) as ProgressReport; + return withEffectiveSensitivity(withScope({}, report.scope), report.sensitivity); } } diff --git a/src/components/progress-report/media/dto/media-list.dto.ts b/src/components/progress-report/media/dto/media-list.dto.ts index d0ff80ed3c..99a99b034a 100644 --- a/src/components/progress-report/media/dto/media-list.dto.ts +++ b/src/components/progress-report/media/dto/media-list.dto.ts @@ -1,17 +1,10 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; -import { - PaginatedList, - SortablePaginationInput, - Variant, - VariantInputField, -} from '~/common'; +import { PaginatedList, SortablePaginationInput, Variant, VariantInputField } from '~/common'; import { type ProgressReport } from '../../dto'; import { type MediaVariant, ProgressReportMedia } from './media.dto'; @InputType() -export class ProgressReportMediaListInput extends SortablePaginationInput< - 'createdAt' | 'variant' ->({ +export class ProgressReportMediaListInput extends SortablePaginationInput<'createdAt' | 'variant'>({ defaultSort: 'createdAt', }) { @VariantInputField(ProgressReportMedia, { @@ -23,9 +16,7 @@ export class ProgressReportMediaListInput extends SortablePaginationInput< } @ObjectType() -export class ProgressReportMediaList extends PaginatedList( - ProgressReportMedia, -) { +export class ProgressReportMediaList extends PaginatedList(ProgressReportMedia) { readonly report: ProgressReport; } diff --git a/src/components/progress-report/media/dto/media.dto.ts b/src/components/progress-report/media/dto/media.dto.ts index e1fe4e0b4c..4f41b26e92 100644 --- a/src/components/progress-report/media/dto/media.dto.ts +++ b/src/components/progress-report/media/dto/media.dto.ts @@ -1,13 +1,6 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { setOf } from '@seedcompany/common'; -import { - EnhancedResource, - IdField, - type IdOf, - Resource, - Variant, - type VariantOf, -} from '~/common'; +import { EnhancedResource, IdField, type IdOf, Resource, Variant, type VariantOf } from '~/common'; import { type LinkTo } from '~/core'; import { type SetDbType } from '~/core/database'; import { e } from '~/core/gel'; @@ -24,12 +17,7 @@ export type VariantGroup = IdOf<'ProgressReportMediaVariantGroup'>; @InputType({ isAbstract: true }) @ObjectType() export class ProgressReportMedia extends Resource { - static BaseNodeProps = [ - ...EnhancedResource.of(Resource).props, - 'category', - 'creator', - 'variant', - ]; + static BaseNodeProps = [...EnhancedResource.of(Resource).props, 'category', 'creator', 'variant']; static readonly Parent = () => import('../../dto/progress-report.dto').then((m) => m.ProgressReport); static readonly ConfirmThisClassPassesSensitivityToPolicies = true; @@ -37,9 +25,7 @@ export class ProgressReportMedia extends Resource { static Variants = ProgressReportHighlight.Variants; // Only the last variant is publicly visible (accessible by anyone anonymously) // Saved in DB, so adjust with caution - static PublicVariants = setOf( - ProgressReportHighlight.Variants.slice(-1).map((v) => v.key), - ); + static PublicVariants = setOf(ProgressReportHighlight.Variants.slice(-1).map((v) => v.key)); readonly report: IdOf; diff --git a/src/components/progress-report/media/dto/upload.dto.ts b/src/components/progress-report/media/dto/upload.dto.ts index 8216185f07..9993696f3c 100644 --- a/src/components/progress-report/media/dto/upload.dto.ts +++ b/src/components/progress-report/media/dto/upload.dto.ts @@ -11,16 +11,10 @@ import { import { CreateDefinedFileVersionInput } from '../../../file/dto'; import { MediaUserMetadata } from '../../../file/media/media.dto'; import { type ProgressReport } from '../../dto'; -import { - type MediaVariant, - ProgressReportMedia, - type VariantGroup, -} from './media.dto'; +import { type MediaVariant, ProgressReportMedia, type VariantGroup } from './media.dto'; @InputType() -export class UploadProgressReportMedia extends PickType(ProgressReportMedia, [ - 'category', -]) { +export class UploadProgressReportMedia extends PickType(ProgressReportMedia, ['category']) { @IdField() readonly reportId: IdOf; diff --git a/src/components/progress-report/media/handlers/file-is-media-check.handler.ts b/src/components/progress-report/media/handlers/file-is-media-check.handler.ts index ec411f2200..d6610cc27c 100644 --- a/src/components/progress-report/media/handlers/file-is-media-check.handler.ts +++ b/src/components/progress-report/media/handlers/file-is-media-check.handler.ts @@ -21,10 +21,7 @@ export class ProgressReportMediaFileIsMediaCheckHandler { try { await mediaByFv.load(file.latestVersionId); } catch (e) { - throw new InputException( - 'File does not appear to be a media file', - 'file', - ); + throw new InputException('File does not appear to be a media file', 'file'); } } } diff --git a/src/components/progress-report/media/progress-report-featured-media.loader.ts b/src/components/progress-report/media/progress-report-featured-media.loader.ts index 26c25cceda..6337b704e7 100644 --- a/src/components/progress-report/media/progress-report-featured-media.loader.ts +++ b/src/components/progress-report/media/progress-report-featured-media.loader.ts @@ -1,9 +1,5 @@ import { type ID } from '~/common'; -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; import { type ProgressReport } from '../dto'; import { type ProgressReportMedia as ReportMedia } from './dto'; import { ProgressReportMediaService } from './progress-report-media.service'; diff --git a/src/components/progress-report/media/progress-report-media.loader.ts b/src/components/progress-report/media/progress-report-media.loader.ts index 90de7a5725..e05c813b40 100644 --- a/src/components/progress-report/media/progress-report-media.loader.ts +++ b/src/components/progress-report/media/progress-report-media.loader.ts @@ -5,9 +5,7 @@ import { ProgressReportMedia as ReportMedia } from './dto'; import { ProgressReportMediaService } from './progress-report-media.service'; @LoaderFactory(() => ReportMedia) -export class ProgressReportMediaLoader - implements DataLoaderStrategy> -{ +export class ProgressReportMediaLoader implements DataLoaderStrategy> { constructor( @Inject(forwardRef(() => ProgressReportMediaService)) private readonly service: ProgressReportMediaService & {}, diff --git a/src/components/progress-report/media/progress-report-media.repository.ts b/src/components/progress-report/media/progress-report-media.repository.ts index fb26f94718..c50f31813c 100644 --- a/src/components/progress-report/media/progress-report-media.repository.ts +++ b/src/components/progress-report/media/progress-report-media.repository.ts @@ -181,10 +181,7 @@ export class ProgressReportMediaRepository extends DtoRepository(ReportMedia) { if (input.variantGroup) { const vg = await this.getBaseNode(input.variantGroup, 'VariantGroup'); if (!vg) { - throw new NotFoundException( - 'Variant group does not exist', - 'variantGroup', - ); + throw new NotFoundException('Variant group does not exist', 'variantGroup'); } } @@ -198,10 +195,7 @@ export class ProgressReportMediaRepository extends DtoRepository(ReportMedia) { .return('vg') .first(); if (variantAlreadyExists) { - throw new InputException( - 'Variant group already has this variant', - 'variant', - ); + throw new InputException('Variant group already has this variant', 'variant'); } throw new CreationFailed(ReportMedia); @@ -232,26 +226,10 @@ export class ProgressReportMediaRepository extends DtoRepository(ReportMedia) { .apply(matchProjectSens()) .apply(matchProjectScopedRoles({ outputVar: 'scope' })) .match([ - [ - node('node'), - relation('in', '', 'child', ACTIVE), - node('report', 'ProgressReport'), - ], - [ - node('node'), - relation('in', '', 'child', ACTIVE), - node('variantGroup', 'VariantGroup'), - ], - [ - node('node'), - relation('out', '', 'fileNode', ACTIVE), - node('file', 'File'), - ], - [ - node('node'), - relation('out', '', 'creator', ACTIVE), - node('creator', 'User'), - ], + [node('node'), relation('in', '', 'child', ACTIVE), node('report', 'ProgressReport')], + [node('node'), relation('in', '', 'child', ACTIVE), node('variantGroup', 'VariantGroup')], + [node('node'), relation('out', '', 'fileNode', ACTIVE), node('file', 'File')], + [node('node'), relation('out', '', 'creator', ACTIVE), node('creator', 'User')], ]) .subQuery('file', (sub) => sub @@ -280,10 +258,11 @@ export class ProgressReportMediaRepository extends DtoRepository(ReportMedia) { } } -export const progressReportMediaFilters = filter.define< - Pick ->(() => ListArgs, { - variants: ({ value }) => ({ - 'node.variant': inArray(value.map((v) => v.key)), - }), -}); +export const progressReportMediaFilters = filter.define>( + () => ListArgs, + { + variants: ({ value }) => ({ + 'node.variant': inArray(value.map((v) => v.key)), + }), + }, +); diff --git a/src/components/progress-report/media/progress-report-media.service.ts b/src/components/progress-report/media/progress-report-media.service.ts index d81416f6d1..6fb9becb89 100644 --- a/src/components/progress-report/media/progress-report-media.service.ts +++ b/src/components/progress-report/media/progress-report-media.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - generateId, - type IdOf, - NotImplementedException, - type UnsecuredDto, -} from '~/common'; +import { generateId, type IdOf, NotImplementedException, type UnsecuredDto } from '~/common'; import { ResourceLoader } from '~/core'; import { type DbTypeOf } from '~/core/database'; import { Privileges, withVariant } from '../../authorization'; @@ -31,10 +26,7 @@ export class ProgressReportMediaService { private readonly repo: ProgressReportMediaRepository, ) {} - async listForReport( - report: Report, - args: ListArgs, - ): Promise { + async listForReport(report: Report, args: ListArgs): Promise { const privileges = this.privileges.for(ReportMedia); const rows = await this.repo.listForReport(report, args); return { @@ -51,25 +43,19 @@ export class ProgressReportMediaService { async readMany(ids: ReadonlyArray>) { const row = await this.repo.readMany(ids); - return row.map((row) => - this.privileges.for(ReportMedia).secure(this.dbRowToDto(row)), - ); + return row.map((row) => this.privileges.for(ReportMedia).secure(this.dbRowToDto(row))); } async readFeaturedOfReport(ids: ReadonlyArray>) { const rows = await this.repo.readFeaturedOfReport(ids); - return rows.map((row) => - this.privileges.for(ReportMedia).secure(this.dbRowToDto(row)), - ); + return rows.map((row) => this.privileges.for(ReportMedia).secure(this.dbRowToDto(row))); } async upload(input: UploadMedia) { const report = await this.resources.load(Report, input.reportId); const context = report as any; // the report is fine for condition context - this.privileges - .for(ReportMedia, withVariant(context, input.variant)) - .verifyCan('create'); + this.privileges.for(ReportMedia, withVariant(context, input.variant)).verifyCan('create'); const initialDto = await this.repo.create(input); @@ -112,9 +98,7 @@ export class ProgressReportMediaService { async delete(id: IdOf) { const media = await this.repo.readOne(id); - this.privileges - .for(ReportMedia, this.dbRowToDto(media)) - .verifyCan('delete'); + this.privileges.for(ReportMedia, this.dbRowToDto(media)).verifyCan('delete'); await this.repo.deleteNode(id); await this.repo.deleteVariantGroupIfEmpty(media.variantGroup); diff --git a/src/components/progress-report/media/resolvers/list.resolver.ts b/src/components/progress-report/media/resolvers/list.resolver.ts index 5e86b173bc..9f2c2e5d2e 100644 --- a/src/components/progress-report/media/resolvers/list.resolver.ts +++ b/src/components/progress-report/media/resolvers/list.resolver.ts @@ -15,9 +15,7 @@ export class ProgressReportMediaListResolver { description: 'The variants the requester has access to upload', deprecationReason: 'Use `availableVariants` instead', }) - uploadableVariants( - @Parent() { report }: ReportMediaList, - ): ReadonlyArray { + uploadableVariants(@Parent() { report }: ReportMediaList): ReadonlyArray { const context = report as any; // the report is fine for condition context const privileges = this.privileges.for(ReportMedia); return ReportMedia.Variants.filter((variant) => @@ -28,9 +26,7 @@ export class ProgressReportMediaListResolver { @ResolveField(() => [AvailableVariant], { description: 'The variants available to the requester', }) - availableVariants( - @Parent() { report }: ReportMediaList, - ): readonly AvailableVariant[] { + availableVariants(@Parent() { report }: ReportMediaList): readonly AvailableVariant[] { const context = report as any; // the report is fine for condition context const privileges = this.privileges.for(ReportMedia); return ReportMedia.Variants.filter((variant) => @@ -38,9 +34,7 @@ export class ProgressReportMediaListResolver { ).map( (variant): AvailableVariant => ({ variant, - canCreate: privileges - .forContext(withVariant(context, variant)) - .can('create'), + canCreate: privileges.forContext(withVariant(context, variant)).can('create'), }), ); } diff --git a/src/components/progress-report/media/resolvers/media.resolver.ts b/src/components/progress-report/media/resolvers/media.resolver.ts index b462f0f984..f6f25f2e24 100644 --- a/src/components/progress-report/media/resolvers/media.resolver.ts +++ b/src/components/progress-report/media/resolvers/media.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { IdArg, type IdOf } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { Privileges } from '../../../authorization'; diff --git a/src/components/progress-report/migrations/drop-internship-progress-reports.migration.ts b/src/components/progress-report/migrations/drop-internship-progress-reports.migration.ts index 724400a2ce..1a713e934b 100644 --- a/src/components/progress-report/migrations/drop-internship-progress-reports.migration.ts +++ b/src/components/progress-report/migrations/drop-internship-progress-reports.migration.ts @@ -8,13 +8,7 @@ export class DropInternshipProgressReportsMigration extends BaseMigration { await this.db .query() .match(node('report', 'ProgressReport')) - .where( - path([ - node('report'), - relation('either'), - node('', 'InternshipEngagement'), - ]), - ) + .where(path([node('report'), relation('either'), node('', 'InternshipEngagement')])) .detachDelete('report') .executeAndLogStats(); } diff --git a/src/components/progress-report/migrations/reextract-all-progress-reports.migration.ts b/src/components/progress-report/migrations/reextract-all-progress-reports.migration.ts index 81849f57ad..b97514ddb2 100644 --- a/src/components/progress-report/migrations/reextract-all-progress-reports.migration.ts +++ b/src/components/progress-report/migrations/reextract-all-progress-reports.migration.ts @@ -10,17 +10,12 @@ import { type ProgressReport } from '../dto'; @Migration('2025-01-06T09:00:00') export class ReextractPnpProgressReportsMigration extends BaseMigration { - constructor( - private readonly eventBus: IEventBus, - private readonly files: FileService, - ) { + constructor(private readonly eventBus: IEventBus, private readonly files: FileService) { super(); } async up() { - const pnps = createPaginator((page) => - this.grabSomePnpsToReextract(page, 100), - ); + const pnps = createPaginator((page) => this.grabSomePnpsToReextract(page, 100)); await asyncPool(2, pnps, async ({ dto: report, fv }) => { try { const pnp = this.files.asDownloadable(fv); @@ -66,11 +61,7 @@ export class ReextractPnpProgressReportsMigration extends BaseMigration { .skip(page * size) .limit(size), ) - .match([ - node('parent', 'BaseNode'), - relation('out', '', 'report', ACTIVE), - node('report'), - ]) + .match([node('parent', 'BaseNode'), relation('out', '', 'report', ACTIVE), node('report')]) .apply(matchProps({ nodeName: 'report' })) .return<{ // These are close enough lol @@ -82,9 +73,7 @@ export class ReextractPnpProgressReportsMigration extends BaseMigration { } } -async function* createPaginator( - getPage: (page: number) => Promise, -) { +async function* createPaginator(getPage: (page: number) => Promise) { let page = 0; do { const currentPage = await getPage(page); diff --git a/src/components/progress-report/progress-report-extra-for-periodic-interface.repository.ts b/src/components/progress-report/progress-report-extra-for-periodic-interface.repository.ts index 2ca8632d66..9a9df15fdb 100644 --- a/src/components/progress-report/progress-report-extra-for-periodic-interface.repository.ts +++ b/src/components/progress-report/progress-report-extra-for-periodic-interface.repository.ts @@ -23,9 +23,7 @@ import { ProgressReport, ProgressReportStatus as Status } from './dto'; @Injectable() export class ProgressReportExtraForPeriodicInterfaceRepository { - getCreateOptions( - _input: MergePeriodicReports, - ): CreateNodeOptions { + getCreateOptions(_input: MergePeriodicReports): CreateNodeOptions { return { initialProps: { status: Status.NotStarted, @@ -47,62 +45,57 @@ export class ProgressReportExtraForPeriodicInterfaceRepository { } } -export const progressReportExtrasSorters: DefinedSorters< - SortFieldOf -> = defineSorters(ProgressReport, { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'pnpExtractionResult.*': (query, input) => - query - .with('node as report') - .match([ - node('report'), - relation('out', '', 'reportFileNode'), - node('file', 'File'), - relation('out', '', 'pnpExtractionResult'), - node('node', 'PnpExtractionResult'), - ]) - .apply(sortWith(pnpExtractionResultSorters, input)), - // eslint-disable-next-line @typescript-eslint/naming-convention - 'engagement.*': (query, input) => - query - .with('node as report') - .match([ - node('report'), - relation('in', '', 'report'), - node('node', 'LanguageEngagement'), - ]) - .apply(sortWith(engagementSorters, input)), - ...mapEntries( - [ - { field: 'cumulativeSummary', period: SummaryPeriod.Cumulative }, - { field: 'fiscalYearSummary', period: SummaryPeriod.FiscalYearSoFar }, - { field: 'periodSummary', period: SummaryPeriod.ReportPeriod }, - ], - ({ field, period }) => { - const periodVar = { period: variable(`"${period}"`) }; - const matcher: SortMatcher = (query, input) => - query - .with('node as report') - .match([ - node('report'), - relation('out', '', 'summary'), - node('node', 'ProgressSummary', periodVar), - ]) - .apply(sortWith(progressSummarySorters, input)) - .union() - .with('node') - .with('node as report') - .where( - not( - path([ - node('report'), - relation('out', '', 'summary'), - node('', 'ProgressSummary', periodVar), - ]), - ), - ) - .return('null as sortValue'); - return [`${field}.*`, matcher]; - }, - ).asRecord, -}); +export const progressReportExtrasSorters: DefinedSorters> = + defineSorters(ProgressReport, { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'pnpExtractionResult.*': (query, input) => + query + .with('node as report') + .match([ + node('report'), + relation('out', '', 'reportFileNode'), + node('file', 'File'), + relation('out', '', 'pnpExtractionResult'), + node('node', 'PnpExtractionResult'), + ]) + .apply(sortWith(pnpExtractionResultSorters, input)), + // eslint-disable-next-line @typescript-eslint/naming-convention + 'engagement.*': (query, input) => + query + .with('node as report') + .match([node('report'), relation('in', '', 'report'), node('node', 'LanguageEngagement')]) + .apply(sortWith(engagementSorters, input)), + ...mapEntries( + [ + { field: 'cumulativeSummary', period: SummaryPeriod.Cumulative }, + { field: 'fiscalYearSummary', period: SummaryPeriod.FiscalYearSoFar }, + { field: 'periodSummary', period: SummaryPeriod.ReportPeriod }, + ], + ({ field, period }) => { + const periodVar = { period: variable(`"${period}"`) }; + const matcher: SortMatcher = (query, input) => + query + .with('node as report') + .match([ + node('report'), + relation('out', '', 'summary'), + node('node', 'ProgressSummary', periodVar), + ]) + .apply(sortWith(progressSummarySorters, input)) + .union() + .with('node') + .with('node as report') + .where( + not( + path([ + node('report'), + relation('out', '', 'summary'), + node('', 'ProgressSummary', periodVar), + ]), + ), + ) + .return('null as sortValue'); + return [`${field}.*`, matcher]; + }, + ).asRecord, + }); diff --git a/src/components/progress-report/progress-report.repository.ts b/src/components/progress-report/progress-report.repository.ts index 30fd1811b8..a65813900e 100644 --- a/src/components/progress-report/progress-report.repository.ts +++ b/src/components/progress-report/progress-report.repository.ts @@ -17,18 +17,12 @@ import { progressReportSorters } from '../periodic-report/periodic-report.reposi import { pnpExtractionResultFilters } from '../pnp/extraction-result/pnp-extraction-result.neo4j.repository'; import { SummaryPeriod } from '../progress-summary/dto'; import { progressSummaryFilters } from '../progress-summary/progress-summary.repository'; -import { - ProgressReport, - ProgressReportFilters, - type ProgressReportListInput, -} from './dto'; +import { ProgressReport, ProgressReportFilters, type ProgressReportListInput } from './dto'; import { ProgressReportExtraForPeriodicInterfaceRepository } from './progress-report-extra-for-periodic-interface.repository'; @Injectable() export class ProgressReportRepository extends DtoRepository(ProgressReport) { - constructor( - private readonly extraRepo: ProgressReportExtraForPeriodicInterfaceRepository, - ) { + constructor(private readonly extraRepo: ProgressReportExtraForPeriodicInterfaceRepository) { super(); } @@ -72,44 +66,37 @@ export class ProgressReportRepository extends DtoRepository(ProgressReport) { } } -export const progressReportFilters = filter.define( - () => ProgressReportFilters, - { - parent: filter.pathExists((id) => [ - node('', 'BaseNode', { id }), - relation('out', '', 'report', ACTIVE), - node('node'), - ]), - start: filter.dateTimeProp(), - end: filter.dateTimeProp(), - status: filter.stringListProp(), - cumulativeSummary: filter.sub(() => progressSummaryFilters)((sub) => - sub - .optionalMatch([ - node('outer'), - relation('out', '', 'summary', ACTIVE), - node('node', 'ProgressSummary', { - period: variable(`"${SummaryPeriod.Cumulative}"`), - }), - ]) - // needed in conjunction with `optionalMatch` - .with('outer, node'), - ), - engagement: filter.sub(() => engagementFilters)((sub) => - sub.match([ - node('outer'), - relation('in', '', 'report'), - node('node', 'Engagement'), - ]), - ), - pnpExtractionResult: filter.sub(() => pnpExtractionResultFilters)((sub) => - sub.match([ +export const progressReportFilters = filter.define(() => ProgressReportFilters, { + parent: filter.pathExists((id) => [ + node('', 'BaseNode', { id }), + relation('out', '', 'report', ACTIVE), + node('node'), + ]), + start: filter.dateTimeProp(), + end: filter.dateTimeProp(), + status: filter.stringListProp(), + cumulativeSummary: filter.sub(() => progressSummaryFilters)((sub) => + sub + .optionalMatch([ node('outer'), - relation('out', '', 'reportFileNode'), - node('file', 'File'), - relation('out', '', 'pnpExtractionResult'), - node('node', 'PnpExtractionResult'), - ]), - ), - }, -); + relation('out', '', 'summary', ACTIVE), + node('node', 'ProgressSummary', { + period: variable(`"${SummaryPeriod.Cumulative}"`), + }), + ]) + // needed in conjunction with `optionalMatch` + .with('outer, node'), + ), + engagement: filter.sub(() => engagementFilters)((sub) => + sub.match([node('outer'), relation('in', '', 'report'), node('node', 'Engagement')]), + ), + pnpExtractionResult: filter.sub(() => pnpExtractionResultFilters)((sub) => + sub.match([ + node('outer'), + relation('out', '', 'reportFileNode'), + node('file', 'File'), + relation('out', '', 'pnpExtractionResult'), + node('node', 'PnpExtractionResult'), + ]), + ), +}); diff --git a/src/components/progress-report/resolvers/progress-report-engagement-connection.resolver.ts b/src/components/progress-report/resolvers/progress-report-engagement-connection.resolver.ts index 8bdbcd9f6f..bd14236116 100644 --- a/src/components/progress-report/resolvers/progress-report-engagement-connection.resolver.ts +++ b/src/components/progress-report/resolvers/progress-report-engagement-connection.resolver.ts @@ -1,17 +1,8 @@ -import { - Args, - ArgsType, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, ArgsType, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { CalendarDate, DateField, ListArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { type Engagement, LanguageEngagement } from '../../engagement/dto'; -import { - PeriodicReportLoader, - PeriodicReportService, -} from '../../periodic-report'; +import { PeriodicReportLoader, PeriodicReportService } from '../../periodic-report'; import { PeriodicReportListInput, ReportType, @@ -50,11 +41,7 @@ export class ProgressReportEngagementConnectionResolver { @Parent() engagement: Engagement, @Args() { date }: PeriodicReportArgs, ): Promise { - const value = await this.service.getReportByDate( - engagement.id, - date, - ReportType.Progress, - ); + const value = await this.service.getReportByDate(engagement.id, date, ReportType.Progress); return { canEdit: false, canRead: true, value }; } @@ -62,13 +49,8 @@ export class ProgressReportEngagementConnectionResolver { description: 'The progress report currently due. This is the period that most recently completed.', }) - async currentProgressReportDue( - @Parent() engagement: Engagement, - ): Promise { - const value = await this.service.getCurrentReportDue( - engagement.id, - ReportType.Progress, - ); + async currentProgressReportDue(@Parent() engagement: Engagement): Promise { + const value = await this.service.getCurrentReportDue(engagement.id, ReportType.Progress); return { canEdit: false, canRead: true, @@ -82,10 +64,7 @@ export class ProgressReportEngagementConnectionResolver { async latestProgressReportSubmitted( @Parent() engagement: Engagement, ): Promise { - const value = await this.service.getLatestReportSubmitted( - engagement.id, - ReportType.Progress, - ); + const value = await this.service.getLatestReportSubmitted(engagement.id, ReportType.Progress); return { canEdit: false, canRead: true, @@ -94,16 +73,10 @@ export class ProgressReportEngagementConnectionResolver { } @ResolveField(() => SecuredProgressReport, { - description: - 'The progress report due next. This is the period currently in progress.', + description: 'The progress report due next. This is the period currently in progress.', }) - async nextProgressReportDue( - @Parent() engagement: Engagement, - ): Promise { - const value = await this.service.getNextReportDue( - engagement.id, - ReportType.Progress, - ); + async nextProgressReportDue(@Parent() engagement: Engagement): Promise { + const value = await this.service.getNextReportDue(engagement.id, ReportType.Progress); return { canEdit: false, canRead: true, diff --git a/src/components/progress-report/resolvers/progress-report.resolver.ts b/src/components/progress-report/resolvers/progress-report.resolver.ts index 3ef6b90118..461d075c10 100644 --- a/src/components/progress-report/resolvers/progress-report.resolver.ts +++ b/src/components/progress-report/resolvers/progress-report.resolver.ts @@ -2,11 +2,7 @@ import { Query, Resolver } from '@nestjs/graphql'; import { ListArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { PeriodicReportLoader as ReportLoader } from '../../periodic-report'; -import { - ProgressReport, - ProgressReportList, - ProgressReportListInput, -} from '../dto'; +import { ProgressReport, ProgressReportList, ProgressReportListInput } from '../dto'; import { ProgressReportService } from '../progress-report.service'; @Resolver(ProgressReport) diff --git a/src/components/progress-report/resolvers/reextract-pnp.resolver.ts b/src/components/progress-report/resolvers/reextract-pnp.resolver.ts index a39bb96e53..1b821ea970 100644 --- a/src/components/progress-report/resolvers/reextract-pnp.resolver.ts +++ b/src/components/progress-report/resolvers/reextract-pnp.resolver.ts @@ -8,10 +8,7 @@ import { PnpProgressExtractionResult } from '../../pnp/extraction-result'; @Resolver() export class ReextractPnpResolver { - constructor( - private readonly files: FileService, - private readonly eventBus: IEventBus, - ) {} + constructor(private readonly files: FileService, private readonly eventBus: IEventBus) {} @Mutation(() => PnpProgressExtractionResult) async reextractPnpProgress( @@ -25,9 +22,7 @@ export class ReextractPnpResolver { ): Promise { const report = await reportLoader.load(reportId); if (report.type !== 'Progress') { - throw new InputException( - "Only ProgressReports can have PnP's re-extracted", - ); + throw new InputException("Only ProgressReports can have PnP's re-extracted"); } const file = await resolveDefinedFile(fileLoader, report.reportFile); if (!file.value) { diff --git a/src/components/progress-report/team-news/progress-report-team-news.resolver.ts b/src/components/progress-report/team-news/progress-report-team-news.resolver.ts index 939001297c..256ad982a4 100644 --- a/src/components/progress-report/team-news/progress-report-team-news.resolver.ts +++ b/src/components/progress-report/team-news/progress-report-team-news.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { IdArg, type IdOf } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { PeriodicReportLoader } from '../../periodic-report'; diff --git a/src/components/progress-report/team-news/progress-report-team-news.service.ts b/src/components/progress-report/team-news/progress-report-team-news.service.ts index fd8279337a..b427bc514e 100644 --- a/src/components/progress-report/team-news/progress-report-team-news.service.ts +++ b/src/components/progress-report/team-news/progress-report-team-news.service.ts @@ -17,12 +17,7 @@ export class ProgressReportTeamNewsService extends PromptVariantResponseListServ } protected async getPrivilegeContext(dto: UnsecuredDto) { - const report = (await this.resources.loadByBaseNode( - dto.parent, - )) as ProgressReport; - return withEffectiveSensitivity( - withScope({}, report.scope), - report.sensitivity, - ); + const report = (await this.resources.loadByBaseNode(dto.parent)) as ProgressReport; + return withEffectiveSensitivity(withScope({}, report.scope), report.sensitivity); } } diff --git a/src/components/progress-report/variance-explanation/migrations/rename.migration.ts b/src/components/progress-report/variance-explanation/migrations/rename.migration.ts index eb4f5b910f..0d2dd90807 100644 --- a/src/components/progress-report/variance-explanation/migrations/rename.migration.ts +++ b/src/components/progress-report/variance-explanation/migrations/rename.migration.ts @@ -16,9 +16,7 @@ export class RenameReasonOptionMigration extends BaseMigration { old: 'Partner organization issues currently being addressed.', }) .setValues({ - 'reason.value': [ - 'Partner organization issues currently being addressed', - ], + 'reason.value': ['Partner organization issues currently being addressed'], }) .return('count(reason) as count') .executeAndLogStats(); diff --git a/src/components/progress-report/variance-explanation/variance-explanation.loader.ts b/src/components/progress-report/variance-explanation/variance-explanation.loader.ts index 03aced1d1a..d23116ee5d 100644 --- a/src/components/progress-report/variance-explanation/variance-explanation.loader.ts +++ b/src/components/progress-report/variance-explanation/variance-explanation.loader.ts @@ -1,9 +1,5 @@ import { type ID } from '~/common'; -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; import { type ProgressReport } from '../dto'; import { type ProgressReportVarianceExplanation as VarianceExplanation } from './variance-explanation.dto'; import { ProgressReportVarianceExplanationService } from './variance-explanation.service'; @@ -12,9 +8,7 @@ import { ProgressReportVarianceExplanationService } from './variance-explanation export class ProgressReportVarianceExplanationLoader implements DataLoaderStrategy { - constructor( - private readonly service: ProgressReportVarianceExplanationService, - ) {} + constructor(private readonly service: ProgressReportVarianceExplanationService) {} getOptions() { return { diff --git a/src/components/progress-report/variance-explanation/variance-explanation.module.ts b/src/components/progress-report/variance-explanation/variance-explanation.module.ts index db5b8ab79f..b8cf3812a6 100644 --- a/src/components/progress-report/variance-explanation/variance-explanation.module.ts +++ b/src/components/progress-report/variance-explanation/variance-explanation.module.ts @@ -16,10 +16,7 @@ import { ProgressReportVarianceExplanationService } from './variance-explanation ProgressReportVarianceExplanationReasonOptionsResolver, ProgressReportVarianceExplanationLoader, ProgressReportVarianceExplanationService, - splitDb( - ProgressReportVarianceExplanationRepository, - VarianceExplanationGelRepository, - ), + splitDb(ProgressReportVarianceExplanationRepository, VarianceExplanationGelRepository), RenameReasonOptionMigration, ], }) diff --git a/src/components/progress-report/variance-explanation/variance-explanation.repository.ts b/src/components/progress-report/variance-explanation/variance-explanation.repository.ts index d683e18dc3..fdeb075314 100644 --- a/src/components/progress-report/variance-explanation/variance-explanation.repository.ts +++ b/src/components/progress-report/variance-explanation/variance-explanation.repository.ts @@ -46,9 +46,7 @@ export class ProgressReportVarianceExplanationRepository extends DtoRepository( sub .with(ctx) .apply(matchProps({ optional: true, excludeBaseProps: true })) - .return<{ dto: UnsecuredDto }>( - merge(defaults, 'props').as('dto'), - ), + .return<{ dto: UnsecuredDto }>(merge(defaults, 'props').as('dto')), ) .return('dto'); } diff --git a/src/components/progress-report/variance-explanation/variance-explanation.resolver.ts b/src/components/progress-report/variance-explanation/variance-explanation.resolver.ts index a4561cfc0a..6bb95b4e2e 100644 --- a/src/components/progress-report/variance-explanation/variance-explanation.resolver.ts +++ b/src/components/progress-report/variance-explanation/variance-explanation.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { clamp } from 'lodash'; import { Loader, type LoaderOf } from '~/core'; import { ScheduleStatus } from '../../progress-summary/dto'; @@ -21,9 +15,7 @@ import { ProgressReportVarianceExplanationService } from './variance-explanation @Resolver(ProgressReport) export class ProgressReportVarianceExplanationResolver { - constructor( - private readonly service: ProgressReportVarianceExplanationService, - ) {} + constructor(private readonly service: ProgressReportVarianceExplanationService) {} @ResolveField(() => VarianceExplanation) async varianceExplanation( diff --git a/src/components/progress-report/variance-explanation/variance-explanation.service.ts b/src/components/progress-report/variance-explanation/variance-explanation.service.ts index c5bfd1c74d..037c2efd7e 100644 --- a/src/components/progress-report/variance-explanation/variance-explanation.service.ts +++ b/src/components/progress-report/variance-explanation/variance-explanation.service.ts @@ -52,13 +52,8 @@ export class ProgressReportVarianceExplanationService { // Don't allow changing to a deprecated reason. // Here to be nice and allow updating the comments even if the current // reason is deprecated. - if ( - changes.reasons?.some((r) => ReasonOptions.instance.deprecated.has(r)) - ) { - throw new InputException( - 'Reason is deprecated and cannot be used', - 'reasons', - ); + if (changes.reasons?.some((r) => ReasonOptions.instance.deprecated.has(r))) { + throw new InputException('Reason is deprecated and cannot be used', 'reasons'); } this.privilegesFor(report).verifyChanges(changes); diff --git a/src/components/progress-report/workflow/handlers/progress-report-workflow-notification.handler.ts b/src/components/progress-report/workflow/handlers/progress-report-workflow-notification.handler.ts index 24a0a0ce3e..9c4b8d9d40 100644 --- a/src/components/progress-report/workflow/handlers/progress-report-workflow-notification.handler.ts +++ b/src/components/progress-report/workflow/handlers/progress-report-workflow-notification.handler.ts @@ -2,13 +2,7 @@ import { entries, mapEntries } from '@seedcompany/common'; import { EmailService } from '@seedcompany/nestjs-email'; import { type RequireExactlyOne } from 'type-fest'; import { type ID, Role, type UnsecuredDto } from '~/common'; -import { - ConfigService, - EventsHandler, - type IEventHandler, - ILogger, - Logger, -} from '~/core'; +import { ConfigService, EventsHandler, type IEventHandler, ILogger, Logger } from '~/core'; import { Identity } from '~/core/authentication'; import { type ProgressReportStatusChangedProps as EmailReportStatusNotification, @@ -49,25 +43,15 @@ export class ProgressReportWorkflowNotificationHandler private readonly logger: ILogger, ) {} - async handle({ - reportId, - previousStatus, - next, - workflowEvent, - }: WorkflowUpdatedEvent) { + async handle({ reportId, previousStatus, next, workflowEvent }: WorkflowUpdatedEvent) { const { enabled } = this.configService.progressReportStatusChange; if (!enabled) { return; } - const { projectId, languageId } = await this.repo.getProjectInfoByReportId( - reportId, - ); + const { projectId, languageId } = await this.repo.getProjectInfoByReportId(reportId); const userIdByEmail = mapEntries( - [ - ...(await this.getEnvNotifyees(next)), - ...(await this.getProjectNotifyees(reportId, next)), - ], + [...(await this.getEnvNotifyees(next)), ...(await this.getProjectNotifyees(reportId, next))], ({ id, email }) => [email, id], ).asMap; @@ -137,9 +121,7 @@ export class ProgressReportWorkflowNotificationHandler }); } - private fakeUserFromEmailAddress( - receiver: string, - ): EmailReportStatusNotification['recipient'] { + private fakeUserFromEmailAddress(receiver: string): EmailReportStatusNotification['recipient'] { return { email: { value: receiver, canRead: true, canEdit: false }, displayFirstName: { @@ -160,27 +142,20 @@ export class ProgressReportWorkflowNotificationHandler const { forTransitions, forBypasses } = this.configService.progressReportStatusChange.notifyExtraEmails; const envEmailList = - typeof next !== 'string' - ? forTransitions.get(next.name) - : forBypasses.get(next); + typeof next !== 'string' ? forTransitions.get(next.name) : forBypasses.get(next); return [ ...(envEmailList?.map((email) => ({ id: undefined, email })) ?? []), ...(envEmailList ? await this.repo.getUserIdByEmails(envEmailList) : []), ]; } - private async getProjectNotifyees( - reportId: ID, - next: Status | InternalTransition, - ) { + private async getProjectNotifyees(reportId: ID, next: Status | InternalTransition) { const roles = [ ...rolesToAlwaysNotify, ...(typeof next !== 'string' ? next.notify?.membersWithRoles ?? [] : []), ]; const members = await this.repo.getProjectMemberInfoByReportId(reportId); - return members.filter((mbr) => - mbr.roles.some((role) => roles.includes(role)), - ); + return members.filter((mbr) => mbr.roles.some((role) => roles.includes(role))); } } diff --git a/src/components/progress-report/workflow/progress-report-workflow.flowchart.ts b/src/components/progress-report/workflow/progress-report-workflow.flowchart.ts index b2bbd3fbc7..63d982f39d 100644 --- a/src/components/progress-report/workflow/progress-report-workflow.flowchart.ts +++ b/src/components/progress-report/workflow/progress-report-workflow.flowchart.ts @@ -8,8 +8,7 @@ import { Transitions } from './transitions'; export class ProgressReportWorkflowFlowchart { /** Generate a flowchart in mermaid markup. */ generate() { - const rgbHexAddAlpha = (rgb: string, alpha: number) => - rgb + alpha.toString(16).slice(2, 4); + const rgbHexAddAlpha = (rgb: string, alpha: number) => rgb + alpha.toString(16).slice(2, 4); const colorStyle = (color: string) => ({ fill: color, stroke: color.slice(0, 7), @@ -36,9 +35,7 @@ export class ProgressReportWorkflowFlowchart { return str ? `classDef ${type} ${str}` : []; }), '', - ...Status.entries.map( - (status) => `${status.value}(${status.label}):::State`, - ), + ...Status.entries.map((status) => `${status.value}(${status.label}):::State`), '', ...Object.values(Transitions).flatMap((t) => { return [ diff --git a/src/components/progress-report/workflow/progress-report-workflow.granter.ts b/src/components/progress-report/workflow/progress-report-workflow.granter.ts index 5590e2d981..986ba1df00 100644 --- a/src/components/progress-report/workflow/progress-report-workflow.granter.ts +++ b/src/components/progress-report/workflow/progress-report-workflow.granter.ts @@ -18,9 +18,7 @@ import { type TransitionName, Transitions } from './transitions'; type Status = `${ProgressReportStatus}`; @Granter(Event) -export class ProgressReportWorkflowEventGranter extends ResourceGranter< - typeof Event -> { +export class ProgressReportWorkflowEventGranter extends ResourceGranter { get read() { return this[action]('read'); } @@ -115,26 +113,18 @@ class TransitionCondition implements Condition { if (!transitionId) { return false; } - return this.allowedTransitionIds.has( - isIdLike(transitionId) ? transitionId : transitionId.id, - ); + return this.allowedTransitionIds.has(isIdLike(transitionId) ? transitionId : transitionId.id); } asCypherCondition(query: Query) { // TODO bypasses to statuses won't work with this. How should these be filtered? - const required = query.params.addParam( - this.allowedTransitionIds, - 'allowedTransitions', - ); + const required = query.params.addParam(this.allowedTransitionIds, 'allowedTransitions'); return `node.transition IN ${String(required)}`; } asEdgeQLCondition() { // TODO bypasses to statuses won't work with this. How should these be filtered? - const transitionAllowed = eqlInLiteralSet( - '.transitionId', - this.allowedTransitionIds, - ); + const transitionAllowed = eqlInLiteralSet('.transitionId', this.allowedTransitionIds); // If no transition then false return `((${transitionAllowed}) ?? false)`; } @@ -145,9 +135,7 @@ class TransitionCondition implements Condition { conditions .flatMap((condition) => condition.checks) .map((check) => { - const key = check.name - ? `name:${check.name}` - : `status:${check.endStatus!}`; + const key = check.name ? `name:${check.name}` : `status:${check.endStatus!}`; return [key, check]; }), ).values(), @@ -159,9 +147,7 @@ class TransitionCondition implements Condition { const checks = [...conditions[0].checks].filter((check1) => conditions.every((cond) => cond.checks.some( - (check2) => - check1.name === check2.name || - check1.endStatus === check2.endStatus, + (check2) => check1.name === check2.name || check1.endStatus === check2.endStatus, ), ), ); diff --git a/src/components/progress-report/workflow/progress-report-workflow.module.ts b/src/components/progress-report/workflow/progress-report-workflow.module.ts index aaf4255cd4..ca6aa10a24 100644 --- a/src/components/progress-report/workflow/progress-report-workflow.module.ts +++ b/src/components/progress-report/workflow/progress-report-workflow.module.ts @@ -17,12 +17,7 @@ import { ProgressReportWorkflowEventResolver } from './resolvers/progress-report import { ProgressReportWorkflowEventsResolver } from './resolvers/progress-report-workflow-events.resolver'; @Module({ - imports: [ - UserModule, - ProjectModule, - LanguageModule, - forwardRef(() => PeriodicReportModule), - ], + imports: [UserModule, ProjectModule, LanguageModule, forwardRef(() => PeriodicReportModule)], providers: [ ProgressReportTransitionsResolver, ProgressReportExecuteTransitionResolver, @@ -31,10 +26,7 @@ import { ProgressReportWorkflowEventsResolver } from './resolvers/progress-repor ProgressReportWorkflowEventLoader, ProgressReportWorkflowService, ProgressReportWorkflowEventGranter, - splitDb( - ProgressReportWorkflowRepository, - ProgressReportWorkflowGelRepository, - ), + splitDb(ProgressReportWorkflowRepository, ProgressReportWorkflowGelRepository), ProgressReportWorkflowFlowchart, ...Object.values(handlers), ], diff --git a/src/components/progress-report/workflow/progress-report-workflow.repository.ts b/src/components/progress-report/workflow/progress-report-workflow.repository.ts index 2fb2e7e3e7..4f477c49c9 100644 --- a/src/components/progress-report/workflow/progress-report-workflow.repository.ts +++ b/src/components/progress-report/workflow/progress-report-workflow.repository.ts @@ -23,9 +23,7 @@ import { type ExecuteProgressReportTransitionInput } from './dto/execute-progres import { ProgressReportWorkflowEvent as WorkflowEvent } from './dto/workflow-event.dto'; @Injectable() -export class ProgressReportWorkflowRepository extends DtoRepository( - WorkflowEvent, -) { +export class ProgressReportWorkflowRepository extends DtoRepository(WorkflowEvent) { async readMany(ids: readonly ID[]) { return await this.db .query() @@ -66,11 +64,7 @@ export class ProgressReportWorkflowRepository extends DtoRepository( protected hydrate() { return (query: Query) => query - .match([ - node('node'), - relation('out', undefined, 'who'), - node('who', 'User'), - ]) + .match([node('node'), relation('out', undefined, 'who'), node('who', 'User')]) .return<{ dto: UnsecuredDto }>( merge('node', { at: 'node.createdAt', @@ -143,25 +137,13 @@ export class ProgressReportWorkflowRepository extends DtoRepository( relation('out', '', 'user', ACTIVE), node('user', 'User'), ]) - .match([ - node('user'), - relation('out', '', 'email', ACTIVE), - node('email', 'EmailAddress'), - ]) - .match([ - node('member'), - relation('out', '', 'roles', ACTIVE), - node('role', 'Property'), - ]) + .match([node('user'), relation('out', '', 'email', ACTIVE), node('email', 'EmailAddress')]) + .match([node('member'), relation('out', '', 'roles', ACTIVE), node('role', 'Property')]) .return<{ id: ID; email: string; roles: readonly Role[]; - }>([ - 'user.id as id', - 'email.value as email', - 'coalesce(role.value, []) as roles', - ]); + }>(['user.id as id', 'email.value as email', 'coalesce(role.value, []) as roles']); return await query.run(); } @@ -191,11 +173,7 @@ export class ProgressReportWorkflowRepository extends DtoRepository( relation('in', '', 'engagement', ACTIVE), node('project', 'Project'), ]) - .match([ - node('engagement'), - relation('out', '', ACTIVE), - node('language', 'Language'), - ]) + .match([node('engagement'), relation('out', '', ACTIVE), node('language', 'Language')]) .return<{ projectId: ID; languageId: ID; diff --git a/src/components/progress-report/workflow/progress-report-workflow.service.ts b/src/components/progress-report/workflow/progress-report-workflow.service.ts index e1f214dc20..eb1fd37466 100644 --- a/src/components/progress-report/workflow/progress-report-workflow.service.ts +++ b/src/components/progress-report/workflow/progress-report-workflow.service.ts @@ -8,10 +8,7 @@ import { } from '~/common'; import { IEventBus } from '~/core'; import { Privileges } from '../../authorization'; -import { - type ProgressReport, - type ProgressReportStatus as Status, -} from '../dto'; +import { type ProgressReport, type ProgressReportStatus as Status } from '../dto'; import { type ExecuteProgressReportTransitionInput } from './dto/execute-progress-report-transition.input'; import { ProgressReportWorkflowEvent as WorkflowEvent } from './dto/workflow-event.dto'; import { WorkflowUpdatedEvent } from './events/workflow-updated.event'; @@ -76,12 +73,7 @@ export class ProgressReportWorkflowService { this.repo.changeStatus(reportId, isTransition ? next.to : next), ]); - const event = new WorkflowUpdatedEvent( - reportId, - currentStatus, - next, - unsecuredEvent, - ); + const event = new WorkflowUpdatedEvent(reportId, currentStatus, next, unsecuredEvent); await this.eventBus.publish(event); } diff --git a/src/components/progress-report/workflow/resolvers/progress-report-workflow-events.resolver.ts b/src/components/progress-report/workflow/resolvers/progress-report-workflow-events.resolver.ts index f03f9abb42..93b6d99583 100644 --- a/src/components/progress-report/workflow/resolvers/progress-report-workflow-events.resolver.ts +++ b/src/components/progress-report/workflow/resolvers/progress-report-workflow-events.resolver.ts @@ -8,9 +8,7 @@ export class ProgressReportWorkflowEventsResolver { constructor(private readonly service: ProgressReportWorkflowService) {} @ResolveField(() => [WorkflowEvent]) - async workflowEvents( - @Parent() report: ProgressReport, - ): Promise { + async workflowEvents(@Parent() report: ProgressReport): Promise { return await this.service.list(report); } } diff --git a/src/components/progress-report/workflow/transitions.ts b/src/components/progress-report/workflow/transitions.ts index dc9c113a49..e649be60b5 100644 --- a/src/components/progress-report/workflow/transitions.ts +++ b/src/components/progress-report/workflow/transitions.ts @@ -106,9 +106,7 @@ export interface InternalTransition extends PublicTransition { }; } -function defineTransitions( - obj: Record, -) { +function defineTransitions(obj: Record) { return mapValues( obj, (name, transition): InternalTransition => ({ diff --git a/src/components/progress-summary/dto/schedule-status.enum.ts b/src/components/progress-summary/dto/schedule-status.enum.ts index 41bcd610f3..afe19cd283 100644 --- a/src/components/progress-summary/dto/schedule-status.enum.ts +++ b/src/components/progress-summary/dto/schedule-status.enum.ts @@ -10,11 +10,7 @@ export const ScheduleStatus = makeEnum({ if (variance > 1 || variance < -1) { throw new Error('Variance should be a decimal between [-1, 1]'); } - return variance > 0.1 - ? status.Ahead - : variance < -0.1 - ? status.Behind - : status.OnTime; + return variance > 0.1 ? status.Ahead : variance < -0.1 ? status.Behind : status.OnTime; }, }), }); diff --git a/src/components/progress-summary/handlers/extract-pnp-file-on-upload.handler.ts b/src/components/progress-summary/handlers/extract-pnp-file-on-upload.handler.ts index 48d9bc6819..33b33b6426 100644 --- a/src/components/progress-summary/handlers/extract-pnp-file-on-upload.handler.ts +++ b/src/components/progress-summary/handlers/extract-pnp-file-on-upload.handler.ts @@ -27,11 +27,7 @@ export class ExtractPnpFileOnUploadHandler { let extracted; try { - extracted = await this.extractor.extract( - event.file, - event.report.start, - result, - ); + extracted = await this.extractor.extract(event.file, event.report.start, result); } catch (e) { this.logger.warning(e.message, { name: event.file.name, @@ -47,25 +43,13 @@ export class ExtractPnpFileOnUploadHandler { }); if (extracted?.cumulative) { - await this.repo.save( - event.report, - SummaryPeriod.Cumulative, - extracted.cumulative, - ); + await this.repo.save(event.report, SummaryPeriod.Cumulative, extracted.cumulative); } if (extracted?.reportPeriod) { - await this.repo.save( - event.report, - SummaryPeriod.ReportPeriod, - extracted.reportPeriod, - ); + await this.repo.save(event.report, SummaryPeriod.ReportPeriod, extracted.reportPeriod); } if (extracted?.fiscalYear) { - await this.repo.save( - event.report, - SummaryPeriod.FiscalYearSoFar, - extracted.fiscalYear, - ); + await this.repo.save(event.report, SummaryPeriod.FiscalYearSoFar, extracted.fiscalYear); } } } diff --git a/src/components/progress-summary/progress-summary.extractor.ts b/src/components/progress-summary/progress-summary.extractor.ts index 8f3602b381..5aec371c7d 100644 --- a/src/components/progress-summary/progress-summary.extractor.ts +++ b/src/components/progress-summary/progress-summary.extractor.ts @@ -1,19 +1,11 @@ import { Injectable } from '@nestjs/common'; import { oneLine } from 'common-tags'; import { clamp, round } from 'lodash'; -import { - CalendarDate, - fiscalQuarter, - fiscalQuarterLabel, - fiscalYear, -} from '~/common'; +import { CalendarDate, fiscalQuarter, fiscalQuarterLabel, fiscalYear } from '~/common'; import { type Column, type Row } from '~/common/xlsx.util'; import { type Downloadable } from '../file/dto'; import { Pnp, type ProgressSheet } from '../pnp'; -import { - PnpProblemType, - type PnpProgressExtractionResult, -} from '../pnp/extraction-result'; +import { PnpProblemType, type PnpProgressExtractionResult } from '../pnp/extraction-result'; import { type ProgressSummary as Progress } from './dto'; @Injectable() @@ -100,9 +92,7 @@ const MismatchedReportingQuarter = PnpProblemType.register({ message: oneLine` The PnP's Reporting Quarter (_${ - ctx.pnpDate - ? fiscalQuarterLabel(CalendarDate.fromISO(ctx.pnpDate)) - : 'undetermined' + ctx.pnpDate ? fiscalQuarterLabel(CalendarDate.fromISO(ctx.pnpDate)) : 'undetermined' }_ \`${source}\`/\`${ctx.yearRef}\`) needs to match the quarter of this CORD report (_${fiscalQuarterLabel(CalendarDate.fromISO(ctx.reportDate))}_). diff --git a/src/components/progress-summary/progress-summary.gel.repository.ts b/src/components/progress-summary/progress-summary.gel.repository.ts index 86f54fe094..5c6f4ba021 100644 --- a/src/components/progress-summary/progress-summary.gel.repository.ts +++ b/src/components/progress-summary/progress-summary.gel.repository.ts @@ -17,40 +17,33 @@ export class ProgressSummaryGelRepository return await this.db.run(this.readManyQuery, { ids: reportIds }); } - private readonly readManyQuery = e.params( - { ids: e.array(e.uuid) }, - ({ ids }) => { - const reports = e.cast(e.ProgressReport, e.array_unpack(ids)); - - return e.select(reports, (report) => { - const scriptureProducts = e.op( - report.engagement[' [ - period, - e.select(Summary, (summary) => ({ - ...summary['*'], - filter_single: { report, period: Period[period] }, - })), - ]).asRecord, - - totalVerses: e.sum(scriptureProducts.totalVerses), - totalVerseEquivalents: e.sum(scriptureProducts.totalVerseEquivalents), - }; - }); - }, - ); + private readonly readManyQuery = e.params({ ids: e.array(e.uuid) }, ({ ids }) => { + const reports = e.cast(e.ProgressReport, e.array_unpack(ids)); + + return e.select(reports, (report) => { + const scriptureProducts = e.op( + report.engagement[' [ + period, + e.select(Summary, (summary) => ({ + ...summary['*'], + filter_single: { report, period: Period[period] }, + })), + ]).asRecord, + + totalVerses: e.sum(scriptureProducts.totalVerses), + totalVerseEquivalents: e.sum(scriptureProducts.totalVerseEquivalents), + }; + }); + }); - async save( - report: ProgressReport, - period: SummaryPeriod, - data: ProgressSummary, - ) { + async save(report: ProgressReport, period: SummaryPeriod, data: ProgressSummary) { const reportEntity = e.cast(e.ProgressReport, e.uuid(report.id)); const preExists = e.select(Summary, () => ({ diff --git a/src/components/progress-summary/progress-summary.loader.ts b/src/components/progress-summary/progress-summary.loader.ts index ecc1af5037..8e4c8585da 100644 --- a/src/components/progress-summary/progress-summary.loader.ts +++ b/src/components/progress-summary/progress-summary.loader.ts @@ -1,9 +1,5 @@ import { type ID } from '~/common'; -import { - type DataLoaderStrategy, - LoaderFactory, - type LoaderOptionsOf, -} from '~/core/data-loader'; +import { type DataLoaderStrategy, LoaderFactory, type LoaderOptionsOf } from '~/core/data-loader'; import { type FetchedSummaries } from './dto'; import { ProgressSummaryRepository } from './progress-summary.repository'; diff --git a/src/components/progress-summary/progress-summary.repository.ts b/src/components/progress-summary/progress-summary.repository.ts index 1b4e1b3c5a..cdb136c9c2 100644 --- a/src/components/progress-summary/progress-summary.repository.ts +++ b/src/components/progress-summary/progress-summary.repository.ts @@ -37,21 +37,14 @@ export class ProgressSummaryRepository extends CommonRepository { relation('out', '', 'product', ACTIVE), node('product', 'Product'), ], - [ - node('product'), - relation('out', '', 'totalVerses', ACTIVE), - node('tv', 'Property'), - ], + [node('product'), relation('out', '', 'totalVerses', ACTIVE), node('tv', 'Property')], [ node('product'), relation('out', '', 'totalVerseEquivalents', ACTIVE), node('tve', 'Property'), ], ]) - .return([ - 'sum(tv.value) as totalVerses', - 'sum(tve.value) as totalVerseEquivalents', - ]), + .return(['sum(tv.value) as totalVerses', 'sum(tve.value) as totalVerseEquivalents']), ) .optionalMatch([ node('report'), @@ -77,11 +70,7 @@ export class ProgressSummaryRepository extends CommonRepository { return await query.run(); } - async save( - report: ProgressReport, - period: SummaryPeriod, - data: ProgressSummary, - ) { + async save(report: ProgressReport, period: SummaryPeriod, data: ProgressSummary) { await this.db .query() .matchNode('pr', 'ProgressReport', { id: report.id }) @@ -98,36 +87,32 @@ export class ProgressSummaryRepository extends CommonRepository { export const progressSummarySorters = defineSorters(ProgressSummary, { ...mapValues.fromList( ['variance', 'scheduleStatus'], - () => (query: Query) => - query.return('(node.actual - node.planned) as sortValue'), + () => (query: Query) => query.return('(node.actual - node.planned) as sortValue'), ).asRecord, }); -export const progressSummaryFilters = filter.define( - () => ProgressSummaryFilters, - { - scheduleStatus: ({ value, query }) => { - const status = setOf(value); - if (status.size === 0) { - return undefined; - } - if (status.size === 1 && status.has(null)) { - return query.where(new WhereExp('node IS NULL')); - } +export const progressSummaryFilters = filter.define(() => ProgressSummaryFilters, { + scheduleStatus: ({ value, query }) => { + const status = setOf(value); + if (status.size === 0) { + return undefined; + } + if (status.size === 1 && status.has(null)) { + return query.where(new WhereExp('node IS NULL')); + } - const conditions = cleanJoin(' OR ', [ - status.has(null) && `node IS NULL`, - status.has('Ahead') && `node.actual - node.planned > 0.1`, - status.has('Behind') && `node.actual - node.planned < -0.1`, - status.has('OnTime') && - `node.actual - node.planned <= 0.1 and node.actual - node.planned >= -0.1`, - ]); - const required = status.has(null) ? undefined : `node IS NOT NULL`; - const str = [required, conditions] - .filter(isNotFalsy) - .map((s) => `(${s})`) - .join(' AND '); - return str ? query.where(new WhereExp(str)) : query; - }, + const conditions = cleanJoin(' OR ', [ + status.has(null) && `node IS NULL`, + status.has('Ahead') && `node.actual - node.planned > 0.1`, + status.has('Behind') && `node.actual - node.planned < -0.1`, + status.has('OnTime') && + `node.actual - node.planned <= 0.1 and node.actual - node.planned >= -0.1`, + ]); + const required = status.has(null) ? undefined : `node IS NOT NULL`; + const str = [required, conditions] + .filter(isNotFalsy) + .map((s) => `(${s})`) + .join(' AND '); + return str ? query.where(new WhereExp(str)) : query; }, -); +}); diff --git a/src/components/progress-summary/progress-summary.resolver.ts b/src/components/progress-summary/progress-summary.resolver.ts index de86bdc5c8..4867df6d97 100644 --- a/src/components/progress-summary/progress-summary.resolver.ts +++ b/src/components/progress-summary/progress-summary.resolver.ts @@ -20,19 +20,13 @@ const formatArg: ArgsOptions = { @Resolver(ProgressSummary) export class ProgressSummaryResolver { @ResolveField() - planned( - @Parent() summary: ProgressSummary, - @Args(formatArg) format: ProgressFormat, - ): number { + planned(@Parent() summary: ProgressSummary, @Args(formatArg) format: ProgressFormat): number { const planned = clamp(summary.planned, 0, 1); return planned * this.formatFactor(summary, format); } @ResolveField() - actual( - @Parent() summary: ProgressSummary, - @Args(formatArg) format: ProgressFormat, - ): number { + actual(@Parent() summary: ProgressSummary, @Args(formatArg) format: ProgressFormat): number { const actual = clamp(summary.actual, 0, 1); return actual * this.formatFactor(summary, format); } @@ -51,10 +45,7 @@ export class ProgressSummaryResolver { @ResolveField(() => Float, { description: 'The difference between the actual and planned values', }) - variance( - @Parent() summary: ProgressSummary, - @Args(formatArg) format: ProgressFormat, - ): number { + variance(@Parent() summary: ProgressSummary, @Args(formatArg) format: ProgressFormat): number { return this.actual(summary, format) - this.planned(summary, format); } diff --git a/src/components/project-change-request/dto/project-change-request-list.dto.ts b/src/components/project-change-request/dto/project-change-request-list.dto.ts index 82bdb7602c..d047d57049 100644 --- a/src/components/project-change-request/dto/project-change-request-list.dto.ts +++ b/src/components/project-change-request/dto/project-change-request-list.dto.ts @@ -24,13 +24,9 @@ export class ProjectChangeRequestListInput extends SortablePaginationInput< } @ObjectType() -export class ProjectChangeRequestListOutput extends PaginatedList( - ProjectChangeRequest, -) {} +export class ProjectChangeRequestListOutput extends PaginatedList(ProjectChangeRequest) {} @ObjectType({ description: SecuredList.descriptionFor('project change requests'), }) -export abstract class SecuredProjectChangeRequestList extends SecuredList( - ProjectChangeRequest, -) {} +export abstract class SecuredProjectChangeRequestList extends SecuredList(ProjectChangeRequest) {} diff --git a/src/components/project-change-request/dto/project-change-request-status.enum.ts b/src/components/project-change-request/dto/project-change-request-status.enum.ts index 0c06ca26d6..f3daaa1033 100644 --- a/src/components/project-change-request/dto/project-change-request-status.enum.ts +++ b/src/components/project-change-request/dto/project-change-request-status.enum.ts @@ -1,9 +1,7 @@ import { ObjectType } from '@nestjs/graphql'; import { type EnumType, makeEnum, SecuredEnum } from '~/common'; -export type ProjectChangeRequestStatus = EnumType< - typeof ProjectChangeRequestStatus ->; +export type ProjectChangeRequestStatus = EnumType; export const ProjectChangeRequestStatus = makeEnum({ name: 'ProjectChangeRequestStatus', values: ['Pending', 'Approved', 'Rejected'], diff --git a/src/components/project-change-request/dto/project-change-request-type.enum.ts b/src/components/project-change-request/dto/project-change-request-type.enum.ts index 0589551d66..4e38513266 100644 --- a/src/components/project-change-request/dto/project-change-request-type.enum.ts +++ b/src/components/project-change-request/dto/project-change-request-type.enum.ts @@ -1,9 +1,7 @@ import { ObjectType } from '@nestjs/graphql'; import { type EnumType, makeEnum, SecuredEnumList } from '~/common'; -export type ProjectChangeRequestType = EnumType< - typeof ProjectChangeRequestType ->; +export type ProjectChangeRequestType = EnumType; export const ProjectChangeRequestType = makeEnum({ name: 'ProjectChangeRequestType', values: ['Time', 'Budget', 'Goal', 'Engagement', 'Other'], diff --git a/src/components/project-change-request/dto/project-change-request.dto.ts b/src/components/project-change-request/dto/project-change-request.dto.ts index 88917cb2b6..9e6f47e152 100644 --- a/src/components/project-change-request/dto/project-change-request.dto.ts +++ b/src/components/project-change-request/dto/project-change-request.dto.ts @@ -10,8 +10,7 @@ import { SecuredProjectChangeRequestTypes } from './project-change-request-type. implements: [Changeset], }) export abstract class ProjectChangeRequest extends Changeset { - static readonly Parent = () => - import('../../project/dto').then((m) => m.IProject); + static readonly Parent = () => import('../../project/dto').then((m) => m.IProject); declare __typename: 'ProjectChangeRequest'; diff --git a/src/components/project-change-request/dto/update-project-change-request.dto.ts b/src/components/project-change-request/dto/update-project-change-request.dto.ts index 8104ee2c40..f4deec7cbe 100644 --- a/src/components/project-change-request/dto/update-project-change-request.dto.ts +++ b/src/components/project-change-request/dto/update-project-change-request.dto.ts @@ -2,13 +2,7 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { type NonEmptyArray } from '@seedcompany/common'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; -import { - type ID, - IdField, - ListField, - NameField, - OptionalField, -} from '~/common'; +import { type ID, IdField, ListField, NameField, OptionalField } from '~/common'; import { ProjectChangeRequestStatus } from './project-change-request-status.enum'; import { ProjectChangeRequestType } from './project-change-request-type.enum'; import { ProjectChangeRequest } from './project-change-request.dto'; diff --git a/src/components/project-change-request/project-change-request.loader.ts b/src/components/project-change-request/project-change-request.loader.ts index a58f77b0be..ef98710b46 100644 --- a/src/components/project-change-request/project-change-request.loader.ts +++ b/src/components/project-change-request/project-change-request.loader.ts @@ -9,12 +9,8 @@ import { ProjectChangeRequestService } from './project-change-request.service'; // Cheat for now and assume concrete type since it is the only one. Changeset, ]) -export class ProjectChangeRequestLoader - implements DataLoaderStrategy -{ - constructor( - private readonly projectChangeRequests: ProjectChangeRequestService, - ) {} +export class ProjectChangeRequestLoader implements DataLoaderStrategy { + constructor(private readonly projectChangeRequests: ProjectChangeRequestService) {} async loadMany(ids: readonly ID[]) { return await this.projectChangeRequests.readMany(ids); diff --git a/src/components/project-change-request/project-change-request.repository.ts b/src/components/project-change-request/project-change-request.repository.ts index 8e2d45c85f..29000db688 100644 --- a/src/components/project-change-request/project-change-request.repository.ts +++ b/src/components/project-change-request/project-change-request.repository.ts @@ -19,9 +19,7 @@ import { } from './dto'; @Injectable() -export class ProjectChangeRequestRepository extends DtoRepository( - ProjectChangeRequest, -) { +export class ProjectChangeRequestRepository extends DtoRepository(ProjectChangeRequest) { async create(input: CreateProjectChangeRequest) { const result = await this.db .query() @@ -52,11 +50,7 @@ export class ProjectChangeRequestRepository extends DtoRepository( protected hydrate() { return (query: Query) => query - .match([ - node('node'), - relation('in', '', 'changeset', ACTIVE), - node('project', 'Project'), - ]) + .match([node('node'), relation('in', '', 'changeset', ACTIVE), node('project', 'Project')]) .apply(matchPropsAndProjectSensAndScopedRoles()) .return<{ dto: UnsecuredDto }>( merge('props', { diff --git a/src/components/project-change-request/project-change-request.resolver.ts b/src/components/project-change-request/project-change-request.resolver.ts index 23d4ae19c7..e4f524d2ff 100644 --- a/src/components/project-change-request/project-change-request.resolver.ts +++ b/src/components/project-change-request/project-change-request.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { ProjectLoader } from '../project'; @@ -59,9 +53,7 @@ export class ProjectChangeRequestResolver { @Mutation(() => DeleteProjectChangeRequestOutput, { description: 'Delete a project change request', }) - async deleteProjectChangeRequest( - @IdArg() id: ID, - ): Promise { + async deleteProjectChangeRequest(@IdArg() id: ID): Promise { await this.service.delete(id); return { success: true }; } diff --git a/src/components/project-change-request/project-change-request.service.ts b/src/components/project-change-request/project-change-request.service.ts index 9b1e1966cd..9b808f81b0 100644 --- a/src/components/project-change-request/project-change-request.service.ts +++ b/src/components/project-change-request/project-change-request.service.ts @@ -38,24 +38,18 @@ export class ProjectChangeRequestService { private readonly repo: ProjectChangeRequestRepository, ) {} - async create( - input: CreateProjectChangeRequest, - ): Promise { + async create(input: CreateProjectChangeRequest): Promise { this.privileges.for(ProjectChangeRequest).verifyCan('create'); const project = await this.projects.readOne(input.projectId); if (project.status !== ProjectStatus.Active) { - throw new InputException( - 'Only active projects can create change requests', - ); + throw new InputException('Only active projects can create change requests'); } const id = await this.repo.create(input); return await this.readOne(id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(ProjectChangeRequest) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(ProjectChangeRequest) : e; }); } @@ -67,33 +61,26 @@ export class ProjectChangeRequestService { async readMany(ids: readonly ID[]) { const projectChangeRequests = await this.repo.readMany(ids); - return await Promise.all( - projectChangeRequests.map((dto) => this.secure(dto)), - ); + return await Promise.all(projectChangeRequests.map((dto) => this.secure(dto))); } async readOneUnsecured(id: ID): Promise> { return await this.repo.readOne(id); } - async secure( - dto: UnsecuredDto, - ): Promise { + async secure(dto: UnsecuredDto): Promise { return { ...this.privileges.for(ProjectChangeRequest).secure(dto), __typename: 'ProjectChangeRequest', }; } - async update( - input: UpdateProjectChangeRequest, - ): Promise { + async update(input: UpdateProjectChangeRequest): Promise { const object = await this.readOneUnsecured(input.id); const changes = this.repo.getActualChanges(object, input); const isStatusChanged = object.status === Status.Pending && - (changes.status === Status.Approved || - changes.status === Status.Rejected); + (changes.status === Status.Approved || changes.status === Status.Rejected); await this.db.updateProperties({ type: ProjectChangeRequest, @@ -110,9 +97,7 @@ export class ProjectChangeRequestService { if (isStatusChanged) { await this.eventBus.publish(new ChangesetFinalizingEvent(updated)); if (changes.status === Status.Approved) { - await this.eventBus.publish( - new ProjectChangeRequestApprovedEvent(updated), - ); + await this.eventBus.publish(new ProjectChangeRequestApprovedEvent(updated)); } } @@ -134,9 +119,7 @@ export class ProjectChangeRequestService { } } - async list( - input: ProjectChangeRequestListInput, - ): Promise { + async list(input: ProjectChangeRequestListInput): Promise { // no need to check if canList for now, all roles allow for listing. const results = await this.repo.list(input); return await mapListResults(results, (dto) => this.secure(dto)); diff --git a/src/components/project/dto/list-projects.dto.ts b/src/components/project/dto/list-projects.dto.ts index c8f9bd9354..659d07250d 100644 --- a/src/components/project/dto/list-projects.dto.ts +++ b/src/components/project/dto/list-projects.dto.ts @@ -20,12 +20,7 @@ import { ProjectMemberFilters } from '../project-member/dto'; import { ProjectStatus } from './project-status.enum'; import { ProjectStep } from './project-step.enum'; import { ProjectType } from './project-type.enum'; -import { - InternshipProject, - IProject, - type Project, - TranslationProject, -} from './project.dto'; +import { InternshipProject, IProject, type Project, TranslationProject } from './project.dto'; @InputType() export abstract class ProjectFilters { @@ -59,8 +54,7 @@ export abstract class ProjectFilters { readonly step?: ProjectStep[]; @OptionalField({ - description: - 'Only projects that are pinned/unpinned by the requesting user', + description: 'Only projects that are pinned/unpinned by the requesting user', }) readonly pinned?: boolean; @@ -100,8 +94,7 @@ export abstract class ProjectFilters { readonly isMember?: boolean; @FilterField(() => ProjectMemberFilters, { - description: - "Only projects with the requesting user's membership that matches these filters", + description: "Only projects with the requesting user's membership that matches these filters", }) readonly membership?: ProjectMemberFilters & {}; @@ -147,55 +140,37 @@ export class ProjectListInput extends SortablePaginationInput({ } @ObjectType() -export class ProjectListOutput extends PaginatedList( - IProject, - { - itemsDescription: PaginatedList.itemDescriptionFor('projects'), - }, -) {} +export class ProjectListOutput extends PaginatedList(IProject, { + itemsDescription: PaginatedList.itemDescriptionFor('projects'), +}) {} @ObjectType() -export class TranslationProjectListOutput extends PaginatedList( - TranslationProject, - { - itemsDescription: PaginatedList.itemDescriptionFor('translation projects'), - }, -) {} +export class TranslationProjectListOutput extends PaginatedList(TranslationProject, { + itemsDescription: PaginatedList.itemDescriptionFor('translation projects'), +}) {} @ObjectType() -export class InternshipProjectListOutput extends PaginatedList( - InternshipProject, - { - itemsDescription: PaginatedList.itemDescriptionFor('internship projects'), - }, -) {} +export class InternshipProjectListOutput extends PaginatedList(InternshipProject, { + itemsDescription: PaginatedList.itemDescriptionFor('internship projects'), +}) {} @ObjectType({ description: SecuredList.descriptionFor('projects'), }) -export abstract class SecuredProjectList extends SecuredList( - IProject, - { - itemsDescription: PaginatedList.itemDescriptionFor('projects'), - }, -) {} +export abstract class SecuredProjectList extends SecuredList(IProject, { + itemsDescription: PaginatedList.itemDescriptionFor('projects'), +}) {} @ObjectType({ description: SecuredList.descriptionFor('translation projects'), }) -export abstract class SecuredTranslationProjectList extends SecuredList( - TranslationProject, - { - itemsDescription: PaginatedList.itemDescriptionFor('translation projects'), - }, -) {} +export abstract class SecuredTranslationProjectList extends SecuredList(TranslationProject, { + itemsDescription: PaginatedList.itemDescriptionFor('translation projects'), +}) {} @ObjectType({ description: SecuredList.descriptionFor('internship projects'), }) -export abstract class SecuredInternshipProjectList extends SecuredList( - InternshipProject, - { - itemsDescription: PaginatedList.itemDescriptionFor('internship projects'), - }, -) {} +export abstract class SecuredInternshipProjectList extends SecuredList(InternshipProject, { + itemsDescription: PaginatedList.itemDescriptionFor('internship projects'), +}) {} diff --git a/src/components/project/dto/project-status.enum.ts b/src/components/project/dto/project-status.enum.ts index cac35062d9..b413f97e27 100644 --- a/src/components/project/dto/project-status.enum.ts +++ b/src/components/project/dto/project-status.enum.ts @@ -6,13 +6,7 @@ export type ProjectStatus = EnumType; export const ProjectStatus = makeEnum({ name: 'ProjectStatus', description: 'A alias for a group of project steps', - values: [ - 'InDevelopment', - 'Active', - 'Terminated', - 'Completed', - 'DidNotDevelop', - ], + values: ['InDevelopment', 'Active', 'Terminated', 'Completed', 'DidNotDevelop'], exposeOrder: true, }); diff --git a/src/components/project/dto/project-type.enum.ts b/src/components/project/dto/project-type.enum.ts index 33cec3584d..e03c9b5cd4 100644 --- a/src/components/project/dto/project-type.enum.ts +++ b/src/components/project/dto/project-type.enum.ts @@ -1,10 +1,5 @@ import { ObjectType } from '@nestjs/graphql'; -import { - type EnumType, - makeEnum, - SecuredEnum, - SecuredEnumList, -} from '~/common'; +import { type EnumType, makeEnum, SecuredEnum, SecuredEnumList } from '~/common'; export type ProjectType = EnumType; export const ProjectType = makeEnum({ @@ -20,6 +15,4 @@ export const ProjectType = makeEnum({ @ObjectType({ description: SecuredEnum.descriptionFor('project types'), }) -export abstract class SecuredProjectTypes extends SecuredEnumList( - ProjectType, -) {} +export abstract class SecuredProjectTypes extends SecuredEnumList(ProjectType) {} diff --git a/src/components/project/dto/project.dto.ts b/src/components/project/dto/project.dto.ts index 94aaa3eec0..eb2bc98a15 100644 --- a/src/components/project/dto/project.dto.ts +++ b/src/components/project/dto/project.dto.ts @@ -55,13 +55,7 @@ type AnyProject = MergeExclusive< MergeExclusive >; -const Interfaces = IntersectTypes( - Resource, - ChangesetAware, - Pinnable, - Postable, - Commentable, -); +const Interfaces = IntersectTypes(Resource, ChangesetAware, Pinnable, Postable, Commentable); export const resolveProjectType = (val: Pick) => { const type = simpleSwitch(val.type, ProjectConcretes); @@ -73,8 +67,7 @@ export const resolveProjectType = (val: Pick) => { const RequiredWhenNotInDev = RequiredWhen(() => Project)({ description: 'the project is not in development', - isEnabled: ({ status }) => - status !== 'InDevelopment' && status !== 'DidNotDevelop', + isEnabled: ({ status }) => status !== 'InDevelopment' && status !== 'DidNotDevelop', }); @RegisterResource({ db: e.Project }) @@ -83,10 +76,7 @@ const RequiredWhenNotInDev = RequiredWhen(() => Project)({ implements: Interfaces.members, }) class Project extends Interfaces { - static readonly BaseNodeProps = [ - ...EnhancedResource.of(Resource).props, - 'type', - ]; + static readonly BaseNodeProps = [...EnhancedResource.of(Resource).props, 'type']; static readonly Relations = () => ({ rootDirectory: Directory, diff --git a/src/components/project/financial-approver/financial-approver-neo4j.repository.ts b/src/components/project/financial-approver/financial-approver-neo4j.repository.ts index 20f1053d8c..333d023802 100644 --- a/src/components/project/financial-approver/financial-approver-neo4j.repository.ts +++ b/src/components/project/financial-approver/financial-approver-neo4j.repository.ts @@ -23,10 +23,9 @@ export class FinancialApproverNeo4jRepository ]) .apply((q) => types - ? q.raw( - `WHERE size(apoc.coll.intersection(node.projectTypes, $types)) > 0`, - { types: many(types) }, - ) + ? q.raw(`WHERE size(apoc.coll.intersection(node.projectTypes, $types)) > 0`, { + types: many(types), + }) : q, ) .apply(this.hydrate()); @@ -72,11 +71,7 @@ export class FinancialApproverNeo4jRepository return (query: Query) => query .with('node, user') - .optionalMatch([ - node('user'), - relation('out', '', 'email', ACTIVE), - node('email'), - ]) + .optionalMatch([node('user'), relation('out', '', 'email', ACTIVE), node('email')]) .return<{ dto: FinancialApprover }>( merge('node', { user: merge('user { .id }', { email: 'email.value' }), diff --git a/src/components/project/financial-approver/financial-approver.resolver.ts b/src/components/project/financial-approver/financial-approver.resolver.ts index 70d4dd44b7..aed7216087 100644 --- a/src/components/project/financial-approver/financial-approver.resolver.ts +++ b/src/components/project/financial-approver/financial-approver.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { Loader, type LoaderOf } from '~/core'; import { Identity } from '~/core/authentication'; import { Privileges } from '../../authorization'; diff --git a/src/components/project/handlers/apply-finalized-changeset-to-project.handler.ts b/src/components/project/handlers/apply-finalized-changeset-to-project.handler.ts index 68856432e4..76d8a9e00c 100644 --- a/src/components/project/handlers/apply-finalized-changeset-to-project.handler.ts +++ b/src/components/project/handlers/apply-finalized-changeset-to-project.handler.ts @@ -12,9 +12,7 @@ import { type SubscribedEvent = ChangesetFinalizingEvent; @EventsHandler(ChangesetFinalizingEvent) -export class ApplyFinalizedChangesetToProject - implements IEventHandler -{ +export class ApplyFinalizedChangesetToProject implements IEventHandler { constructor( private readonly db: DatabaseService, @Logger('project:change-request:finalized') @@ -32,9 +30,7 @@ export class ApplyFinalizedChangesetToProject relation('out', '', 'changeset', ACTIVE), node('changeset', 'Changeset', { id: changeset.id }), ]) - .apply( - changeset.applied ? commitChangesetProps() : rejectChangesetProps(), - ) + .apply(changeset.applied ? commitChangesetProps() : rejectChangesetProps()) // Apply pending budget records .subQuery((sub) => sub @@ -68,10 +64,7 @@ export class ApplyFinalizedChangesetToProject await query.run(); // TODO handle relations (locations, etc.) } catch (exception) { - throw new ServerException( - 'Failed to apply changeset to project', - exception, - ); + throw new ServerException('Failed to apply changeset to project', exception); } } } diff --git a/src/components/project/handlers/set-department-id.handler.ts b/src/components/project/handlers/set-department-id.handler.ts index b11664f077..2f178c9437 100644 --- a/src/components/project/handlers/set-department-id.handler.ts +++ b/src/components/project/handlers/set-department-id.handler.ts @@ -1,23 +1,8 @@ import { isNull, node, not, relation } from 'cypher-query-builder'; -import { - ClientException, - type ID, - ServerException, - type UnsecuredDto, -} from '~/common'; +import { ClientException, type ID, ServerException, type UnsecuredDto } from '~/common'; import { ConfigService, EventsHandler, type IEventHandler } from '~/core'; -import { - DatabaseService, - TransactionRetryInformer, - UniquenessError, -} from '~/core/database'; -import { - ACTIVE, - apoc, - collect, - updateProperty, - variable, -} from '~/core/database/query'; +import { DatabaseService, TransactionRetryInformer, UniquenessError } from '~/core/database'; +import { ACTIVE, apoc, collect, updateProperty, variable } from '~/core/database/query'; import { type Project, resolveProjectType, @@ -55,20 +40,14 @@ export class SetDepartmentId implements IEventHandler { const block = await this.getDepartmentIdBlockId(event.project); - const departmentId = await this.assignDepartmentIdForProject( - event.project, - block, - ); + const departmentId = await this.assignDepartmentIdForProject(event.project, block); event.project = { ...event.project, departmentId, }; } - private async assignDepartmentIdForProject( - project: UnsecuredDto, - block: { id: ID }, - ) { + private async assignDepartmentIdForProject(project: UnsecuredDto, block: { id: ID }) { const query = this.db .query() // Enumerate IDs from the department ID block @@ -77,11 +56,7 @@ export class SetDepartmentId implements IEventHandler { .match(node('block', 'DepartmentIdBlock', { id: block.id })) .with(apoc.convert.fromJsonList('block.blocks').as('blocks')) // enumerate all ranges - .with( - apoc.coll - .flatten(['block in blocks | range(block.start, block.end)']) - .as('ids'), - ) + .with(apoc.coll.flatten(['block in blocks | range(block.start, block.end)']).as('ids')) // convert numbers to strings and pad to 5 digits with leading zeros .with( `[id in ids | @@ -138,14 +113,10 @@ export class SetDepartmentId implements IEventHandler { const isMultiplication = project.type === 'MultiplicationTranslation'; if (isMultiplication) { if (!project.primaryPartnership) { - throw new ClientException( - 'Project must have a partnership to continue', - ); + throw new ClientException('Project must have a partnership to continue'); } } else if (!project.primaryLocation) { - throw new ClientException( - 'Project must have a primary location to continue', - ); + throw new ClientException('Project must have a primary location to continue'); } const block = await this.db @@ -171,11 +142,7 @@ export class SetDepartmentId implements IEventHandler { node('holder', 'FundingAccount'), ], ) - .match([ - node('holder'), - relation('out'), - node('block', 'DepartmentIdBlock'), - ]) + .match([node('holder'), relation('out'), node('block', 'DepartmentIdBlock')]) .return<{ id: ID }>('block.id as id') .first(); if (block) { diff --git a/src/components/project/handlers/set-initial-mou-end.handler.ts b/src/components/project/handlers/set-initial-mou-end.handler.ts index 9fdba47c99..cab6ac4660 100644 --- a/src/components/project/handlers/set-initial-mou-end.handler.ts +++ b/src/components/project/handlers/set-initial-mou-end.handler.ts @@ -39,10 +39,7 @@ export class SetInitialMouEnd implements IEventHandler { event.project = updatedProject; } } catch (exception) { - throw new ServerException( - 'Could not set initial mou end on project', - exception, - ); + throw new ServerException('Could not set initial mou end on project', exception); } } } diff --git a/src/components/project/migrations/rename-translation-to-momentum.migration.ts b/src/components/project/migrations/rename-translation-to-momentum.migration.ts index f5f050d084..e99bee35fc 100644 --- a/src/components/project/migrations/rename-translation-to-momentum.migration.ts +++ b/src/components/project/migrations/rename-translation-to-momentum.migration.ts @@ -8,12 +8,7 @@ export class RenameTranslationToMomentumMigration extends BaseMigration { .matchNode('node', 'TranslationProject') .setValues({ 'node.type': 'MomentumTranslation' }, true) .setLabels({ - node: [ - 'MomentumTranslationProject', - 'TranslationProject', - 'Project', - 'BaseNode', - ], + node: ['MomentumTranslationProject', 'TranslationProject', 'Project', 'BaseNode'], }) .logIt() .executeAndLogStats(); diff --git a/src/components/project/project-engagement-id.resolver.ts b/src/components/project/project-engagement-id.resolver.ts index 67960ba710..b58ea1fd0a 100644 --- a/src/components/project/project-engagement-id.resolver.ts +++ b/src/components/project/project-engagement-id.resolver.ts @@ -1,18 +1,9 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; -import { - type AbstractClassType, - type ID, - IdArg, - NotFoundException, -} from '~/common'; +import { type AbstractClassType, type ID, IdArg, NotFoundException } from '~/common'; import { Loader, type LoaderOf } from '~/core'; import { Privileges } from '../authorization'; import { EngagementLoader } from '../engagement'; -import { - IEngagement, - InternshipEngagement, - LanguageEngagement, -} from '../engagement/dto'; +import { IEngagement, InternshipEngagement, LanguageEngagement } from '../engagement/dto'; import { InternshipProject, IProject, diff --git a/src/components/project/project-filters.query.ts b/src/components/project/project-filters.query.ts index d975e8dece..1704ede8b3 100644 --- a/src/components/project/project-filters.query.ts +++ b/src/components/project/project-filters.query.ts @@ -1,10 +1,4 @@ -import { - equals, - greaterThan, - inArray, - node, - relation, -} from 'cypher-query-builder'; +import { equals, greaterThan, inArray, node, relation } from 'cypher-query-builder'; import { ACTIVE, currentUser, @@ -68,11 +62,7 @@ export const projectFilters = filter.define(() => ProjectFilters, { ]), ), members: filter.sub(() => projectMemberFilters)((sub) => - sub.match([ - node('node', 'ProjectMember'), - relation('in', '', 'member', ACTIVE), - node('outer'), - ]), + sub.match([node('node', 'ProjectMember'), relation('in', '', 'member', ACTIVE), node('outer')]), ), userId: ({ value }) => ({ userId: [ @@ -107,11 +97,7 @@ export const projectFilters = filter.define(() => ProjectFilters, { name: filter.fullText({ index: () => ProjectNameIndex, matchToNode: (q) => - q.match([ - node('node', 'Project'), - relation('out', '', 'name', ACTIVE), - node('match'), - ]), + q.match([node('node', 'Project'), relation('out', '', 'name', ACTIVE), node('match')]), minScore: 0.8, }), primaryLocation: filter.sub(() => locationFilters)((sub) => diff --git a/src/components/project/project-member/available-roles-to-project.resolver.ts b/src/components/project/project-member/available-roles-to-project.resolver.ts index ad4a4a2a89..1a77a05748 100644 --- a/src/components/project/project-member/available-roles-to-project.resolver.ts +++ b/src/components/project/project-member/available-roles-to-project.resolver.ts @@ -8,12 +8,9 @@ export class AvailableRolesToProjectResolver { constructor(private readonly service: ProjectMemberService) {} @ResolveField(() => [Role], { - description: - 'All of the roles this user could serve in project memberships', + description: 'All of the roles this user could serve in project memberships', }) - async availableForProjects( - @Grandparent() user: User, - ): Promise { + async availableForProjects(@Grandparent() user: User): Promise { const roles = this.service.getAvailableRoles(user); return [...roles]; } diff --git a/src/components/project/project-member/dto/create-project-member.dto.ts b/src/components/project/project-member/dto/create-project-member.dto.ts index a3c23d6737..0256b43f94 100644 --- a/src/components/project/project-member/dto/create-project-member.dto.ts +++ b/src/components/project/project-member/dto/create-project-member.dto.ts @@ -2,13 +2,7 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; import type { DateTime } from 'luxon'; -import { - DateTimeField, - type ID, - IdField, - Role, - type UnsecuredDto, -} from '~/common'; +import { DateTimeField, type ID, IdField, Role, type UnsecuredDto } from '~/common'; import { type Project } from '../../dto'; import { ProjectMember } from './project-member.dto'; diff --git a/src/components/project/project-member/dto/list-project-members.dto.ts b/src/components/project/project-member/dto/list-project-members.dto.ts index 5b73a074c3..bf19c39ce6 100644 --- a/src/components/project/project-member/dto/list-project-members.dto.ts +++ b/src/components/project/project-member/dto/list-project-members.dto.ts @@ -32,9 +32,7 @@ export abstract class ProjectMemberFilters { } @InputType() -export class ProjectMemberListInput extends SortablePaginationInput< - keyof ProjectMember ->({ +export class ProjectMemberListInput extends SortablePaginationInput({ defaultSort: 'createdAt', }) { @FilterField(() => ProjectMemberFilters) @@ -47,6 +45,4 @@ export class ProjectMemberListOutput extends PaginatedList(ProjectMember) {} @ObjectType({ description: SecuredList.descriptionFor('project members'), }) -export abstract class SecuredProjectMemberList extends SecuredList( - ProjectMember, -) {} +export abstract class SecuredProjectMemberList extends SecuredList(ProjectMember) {} diff --git a/src/components/project/project-member/handlers/director-change-apply-to-project-members.handler.ts b/src/components/project/project-member/handlers/director-change-apply-to-project-members.handler.ts index d00a6cc0cc..c3547f7ba7 100644 --- a/src/components/project/project-member/handlers/director-change-apply-to-project-members.handler.ts +++ b/src/components/project/project-member/handlers/director-change-apply-to-project-members.handler.ts @@ -19,15 +19,9 @@ export class DirectorChangeApplyToProjectMembersHandler { return; } const role: Role = - event instanceof FieldZoneUpdatedEvent - ? 'FieldOperationsDirector' - : 'RegionalDirector'; + event instanceof FieldZoneUpdatedEvent ? 'FieldOperationsDirector' : 'RegionalDirector'; - const stats = await this.repo.replaceMembershipsOnOpenProjects( - oldDirector, - newDirector, - role, - ); + const stats = await this.repo.replaceMembershipsOnOpenProjects(oldDirector, newDirector, role); this.logger.notice('Replaced director memberships on open projects', { location: event.updated.id, diff --git a/src/components/project/project-member/project-member.gel.repository.ts b/src/components/project/project-member/project-member.gel.repository.ts index 96f17bd596..12acae3adf 100644 --- a/src/components/project/project-member/project-member.gel.repository.ts +++ b/src/components/project/project-member/project-member.gel.repository.ts @@ -2,11 +2,7 @@ import { Injectable } from '@nestjs/common'; import { type ID, isIdLike, type PublicOf, type Role } from '~/common'; import { e, RepoFor, type ScopeOf } from '~/core/gel'; import { hydrateUser } from '../../user/user.gel.repository'; -import { - type CreateProjectMember, - ProjectMember, - type ProjectMemberListInput, -} from './dto'; +import { type CreateProjectMember, ProjectMember, type ProjectMemberListInput } from './dto'; import { type ProjectMemberRepository as Neo4jRepository } from './project-member.repository'; @Injectable() @@ -20,11 +16,7 @@ export class ProjectMemberGelRepository }) implements PublicOf { - async create({ - projectId: projectOrId, - userId, - ...rest - }: CreateProjectMember) { + async create({ projectId: projectOrId, userId, ...rest }: CreateProjectMember) { const projectId = isIdLike(projectOrId) ? projectOrId : projectOrId.id; const project = e.cast(e.Project, e.uuid(projectId)); @@ -42,10 +34,7 @@ export class ProjectMemberGelRepository const project = e.cast(e.Project, e.uuid(projectId)); const members = e.select(project.members, (member) => ({ filter: roles - ? e.op( - 'exists', - e.op(member.roles, 'intersect', e.cast(e.Role, e.set(...roles))), - ) + ? e.op('exists', e.op(member.roles, 'intersect', e.cast(e.Role, e.set(...roles)))) : undefined, })); const query = e.select(members.user, () => ({ @@ -62,14 +51,7 @@ export class ProjectMemberGelRepository if (!input) return []; return [ (input.roles?.length ?? 0) > 0 && - e.op( - 'exists', - e.op( - member.roles, - 'intersect', - e.cast(e.Role, e.set(...input.roles!)), - ), - ), + e.op('exists', e.op(member.roles, 'intersect', e.cast(e.Role, e.set(...input.roles!)))), ]; } diff --git a/src/components/project/project-member/project-member.loader.ts b/src/components/project/project-member/project-member.loader.ts index 040db80eca..a6b8e552a2 100644 --- a/src/components/project/project-member/project-member.loader.ts +++ b/src/components/project/project-member/project-member.loader.ts @@ -4,9 +4,7 @@ import { ProjectMember } from './dto'; import { ProjectMemberService } from './project-member.service'; @LoaderFactory(() => ProjectMember) -export class ProjectMemberLoader - implements DataLoaderStrategy> -{ +export class ProjectMemberLoader implements DataLoaderStrategy> { constructor(private readonly projectMembers: ProjectMemberService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/project/project-member/project-member.repository.ts b/src/components/project/project-member/project-member.repository.ts index fb2ec16e69..ac6a245296 100644 --- a/src/components/project/project-member/project-member.repository.ts +++ b/src/components/project/project-member/project-member.repository.ts @@ -1,11 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - type Node, - node, - not, - type Query, - relation, -} from 'cypher-query-builder'; +import { type Node, node, not, type Query, relation } from 'cypher-query-builder'; import { DateTime } from 'luxon'; import { CreationFailed, @@ -63,24 +57,14 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) { relation('out', '', 'user', ACTIVE), node('user'), ]) - .return<{ user?: Node; project?: Node; member?: Node }>([ - 'user', - 'project', - 'member', - ]) + .return<{ user?: Node; project?: Node; member?: Node }>(['user', 'project', 'member']) .first(); if (!result?.project) { - throw new NotFoundException( - 'Could not find project', - 'projectMember.projectId', - ); + throw new NotFoundException('Could not find project', 'projectMember.projectId'); } if (!result?.user) { - throw new NotFoundException( - 'Could not find person', - 'projectMember.userId', - ); + throw new NotFoundException('Could not find person', 'projectMember.userId'); } if (result.member) { throw new DuplicateException( @@ -90,11 +74,7 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) { } } - async create({ - userId, - projectId: projectOrId, - ...input - }: CreateProjectMember) { + async create({ userId, projectId: projectOrId, ...input }: CreateProjectMember) { const projectId = isIdLike(projectOrId) ? projectOrId : projectOrId.id; await this.verifyRelationshipEligibility(projectId, userId); @@ -133,23 +113,11 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) { protected hydrate() { return (query: Query) => query - .match([ - node('project', 'Project'), - relation('out', '', 'member', ACTIVE), - node('node'), - ]) + .match([node('project', 'Project'), relation('out', '', 'member', ACTIVE), node('node')]) .apply(matchPropsAndProjectSensAndScopedRoles()) - .match([ - node('node'), - relation('out', '', 'user'), - node('user', 'User'), - ]) - .subQuery('user', (sub) => - sub.with('user as node').apply(this.users.hydrateAsNeo4j()), - ) - .return<{ dto: UnsecuredDto }>( - merge('props', { user: 'dto' }).as('dto'), - ); + .match([node('node'), relation('out', '', 'user'), node('user', 'User')]) + .subQuery('user', (sub) => sub.with('user as node').apply(this.users.hydrateAsNeo4j())) + .return<{ dto: UnsecuredDto }>(merge('props', { user: 'dto' }).as('dto')); } async list({ filter, ...input }: ProjectMemberListInput) { @@ -188,10 +156,7 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) { relation('out', '', 'email', ACTIVE), node('email', 'EmailAddress'), ]) - .return<{ id: ID; email: string | null }>([ - 'user.id as id', - 'email.value as email', - ]) + .return<{ id: ID; email: string | null }>(['user.id as id', 'email.value as email']) .run(); } @@ -319,18 +284,10 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) { export const projectMemberFilters = filter.define(() => ProjectMemberFilters, { project: filter.sub((): FilterFn => projectFilters)((sub) => - sub.match([ - node('node', 'Project'), - relation('out', '', 'member', ACTIVE), - node('outer'), - ]), + sub.match([node('node', 'Project'), relation('out', '', 'member', ACTIVE), node('outer')]), ), user: filter.sub(() => userFilters)((sub) => - sub.match([ - node('outer'), - relation('out', '', 'user'), - node('node', 'User'), - ]), + sub.match([node('outer'), relation('out', '', 'user'), node('node', 'User')]), ), roles: filter.intersectsProp(), active: filter.isPropNotNull('inactiveAt'), diff --git a/src/components/project/project-member/project-member.resolver.ts b/src/components/project/project-member/project-member.resolver.ts index 7199a1919e..fbd53981a2 100644 --- a/src/components/project/project-member/project-member.resolver.ts +++ b/src/components/project/project-member/project-member.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { type ID, IdArg } from '~/common'; import { ProjectMemberService } from '../project-member'; import { @@ -50,9 +44,7 @@ export class ProjectMemberResolver { @Mutation(() => DeleteProjectMemberOutput, { description: 'Delete a project member', }) - async deleteProjectMember( - @IdArg() id: ID, - ): Promise { + async deleteProjectMember(@IdArg() id: ID): Promise { await this.service.delete(id); return { success: true }; } diff --git a/src/components/project/project-member/project-member.service.ts b/src/components/project/project-member/project-member.service.ts index 5a1668e587..539e27541b 100644 --- a/src/components/project/project-member/project-member.service.ts +++ b/src/components/project/project-member/project-member.service.ts @@ -33,14 +33,9 @@ export class ProjectMemberService { private readonly repo: ProjectMemberRepository, ) {} - async create( - input: CreateProjectMember, - enforcePerms = true, - ): Promise { + async create(input: CreateProjectMember, enforcePerms = true): Promise { enforcePerms && - (await this.assertValidRoles(input.roles, () => - this.resources.load('User', input.userId), - )); + (await this.assertValidRoles(input.roles, () => this.resources.load('User', input.userId))); const created = await this.repo.create(input); @@ -51,8 +46,7 @@ export class ProjectMemberService { ); } - enforcePerms && - this.privileges.for(ProjectMember, created).verifyCan('create'); + enforcePerms && this.privileges.for(ProjectMember, created).verifyCan('create'); return this.secure(created); } @@ -60,10 +54,7 @@ export class ProjectMemberService { @HandleIdLookup(ProjectMember) async readOne(id: ID, _view?: ObjectView): Promise { if (!id) { - throw new NotFoundException( - 'No project member id to search for', - 'projectMember.id', - ); + throw new NotFoundException('No project member id to search for', 'projectMember.id'); } const dto = await this.repo.readOne(id); @@ -83,9 +74,7 @@ export class ProjectMemberService { ...user, value: user.value && user.canRead - ? this.userService.secure( - user.value as unknown as UnsecuredDto, - ) + ? this.userService.secure(user.value as unknown as UnsecuredDto) : undefined, }, }; @@ -97,9 +86,7 @@ export class ProjectMemberService { await this.assertValidRoles(input.roles, () => { const user = object.user.value; if (!user) { - throw new UnauthorizedException( - 'Cannot read user to verify roles available', - ); + throw new UnauthorizedException('Cannot read user to verify roles available'); } return user; }); diff --git a/src/components/project/project.gel.repository.ts b/src/components/project/project.gel.repository.ts index b559ba5207..a76be8fe6e 100644 --- a/src/components/project/project.gel.repository.ts +++ b/src/components/project/project.gel.repository.ts @@ -1,12 +1,7 @@ import { Injectable, type Type } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { LazyGetter } from 'lazy-get-decorator'; -import { - type ID, - type PublicOf, - type SortablePaginationInput, - type UnsecuredDto, -} from '~/common'; +import { type ID, type PublicOf, type SortablePaginationInput, type UnsecuredDto } from '~/common'; import { grabInstances } from '~/common/instance-maps'; import { type ChangesOf } from '~/core/database/changes'; import { @@ -63,10 +58,9 @@ export const ConcreteRepos = { { hydrate }, ) {}, - Internship: class InternshipProjectRepository extends RepoFor( - ConcreteTypes.Internship, - { hydrate }, - ) {}, + Internship: class InternshipProjectRepository extends RepoFor(ConcreteTypes.Internship, { + hydrate, + }) {}, } satisfies Record; @Injectable() @@ -86,8 +80,7 @@ export class ProjectGelRepository } async create(input: CreateProject) { - const { type, sensitivity, otherLocationIds, presetInventory, ...props } = - input; + const { type, sensitivity, otherLocationIds, presetInventory, ...props } = input; return await this.concretes[input.type].create({ ...props, ownSensitivity: sensitivity, @@ -95,10 +88,7 @@ export class ProjectGelRepository }); } - async update( - existing: UnsecuredDto, - changes: ChangesOf, - ) { + async update(existing: UnsecuredDto, changes: ChangesOf) { try { return await this.defaults.update({ id: existing.id, @@ -127,10 +117,7 @@ export class ProjectGelRepository return await this.db.run(query); } - protected listFilters( - project: ScopeOf, - { filter: input }: ProjectListInput, - ) { + protected listFilters(project: ScopeOf, { filter: input }: ProjectListInput) { if (!input) return []; return [ (input.type?.length ?? 0) > 0 && @@ -140,34 +127,26 @@ export class ProjectGelRepository e.set(...input.type!.map((type) => `default::${type}Project`)), ), (input.status?.length ?? 0) > 0 && - e.op( - project.status, - 'in', - e.cast(e.Project.Status, e.set(...input.status!)), - ), + e.op(project.status, 'in', e.cast(e.Project.Status, e.set(...input.status!))), (input.step?.length ?? 0) > 0 && e.op(project.step, 'in', e.cast(e.Project.Step, e.set(...input.step!))), input.onlyMultipleEngagements && e.op(project.engagementTotal, '>', 1), ...(input.createdAt ? [ - input.createdAt.after && - e.op(project.createdAt, '>', input.createdAt.after), + input.createdAt.after && e.op(project.createdAt, '>', input.createdAt.after), input.createdAt.afterInclusive && e.op(project.createdAt, '>=', input.createdAt.afterInclusive), - input.createdAt.before && - e.op(project.createdAt, '<', input.createdAt.before), + input.createdAt.before && e.op(project.createdAt, '<', input.createdAt.before), input.createdAt.beforeInclusive && e.op(project.createdAt, '<=', input.createdAt.beforeInclusive), ] : []), ...(input.modifiedAt ? [ - input.modifiedAt.after && - e.op(project.modifiedAt, '>', input.modifiedAt.after), + input.modifiedAt.after && e.op(project.modifiedAt, '>', input.modifiedAt.after), input.modifiedAt.afterInclusive && e.op(project.modifiedAt, '>=', input.modifiedAt.afterInclusive), - input.modifiedAt.before && - e.op(project.modifiedAt, '<', input.modifiedAt.before), + input.modifiedAt.before && e.op(project.modifiedAt, '<', input.modifiedAt.before), input.modifiedAt.beforeInclusive && e.op(project.modifiedAt, '<=', input.modifiedAt.beforeInclusive), ] @@ -175,13 +154,8 @@ export class ProjectGelRepository input.isMember != null && e.op(project.isMember, '=', input.isMember), input.pinned != null && e.op(project.pinned, '=', input.pinned), input.languageId && - e.op( - e.uuid(input.languageId), - 'in', - project.is(e.TranslationProject).languages.id, - ), - input.partnerId && - e.op(e.uuid(input.partnerId), 'in', project.partnerships.partner.id), + e.op(e.uuid(input.languageId), 'in', project.is(e.TranslationProject).languages.id), + input.partnerId && e.op(e.uuid(input.partnerId), 'in', project.partnerships.partner.id), input.userId && e.op( e.uuid(input.userId), @@ -195,18 +169,11 @@ export class ProjectGelRepository ), ), (input.sensitivity?.length ?? 0) > 0 && - e.op( - project.sensitivity, - 'in', - e.cast(e.Sensitivity, e.set(...input.sensitivity!)), - ), + e.op(project.sensitivity, 'in', e.cast(e.Sensitivity, e.set(...input.sensitivity!))), ]; } - protected orderBy( - scope: ScopeOf, - input: SortablePaginationInput, - ) { + protected orderBy(scope: ScopeOf, input: SortablePaginationInput) { if (input.sort === 'type') { return { expression: scope.__type__, diff --git a/src/components/project/project.loader.ts b/src/components/project/project.loader.ts index d72ecd057f..fbb29902d9 100644 --- a/src/components/project/project.loader.ts +++ b/src/components/project/project.loader.ts @@ -22,10 +22,7 @@ export class ProjectLoader extends ObjectViewAwareLoader { super(); } - async loadManyByView( - ids: readonly ID[], - view: ObjectView, - ): Promise { + async loadManyByView(ids: readonly ID[], view: ObjectView): Promise { return await this.projects.readMany(ids, view); } } diff --git a/src/components/project/project.repository.ts b/src/components/project/project.repository.ts index 6a2f3579d9..14c7ec6712 100644 --- a/src/components/project/project.repository.ts +++ b/src/components/project/project.repository.ts @@ -47,10 +47,7 @@ import { projectFilters } from './project-filters.query'; @Injectable() export class ProjectRepository extends CommonRepository { - constructor( - private readonly config: ConfigService, - private readonly privileges: Privileges, - ) { + constructor(private readonly config: ConfigService, private readonly privileges: Privileges) { super(); } @@ -148,10 +145,7 @@ export class ProjectRepository extends CommonRepository { ); } - getActualChanges( - currentProject: UnsecuredDto, - input: UpdateProject, - ) { + getActualChanges(currentProject: UnsecuredDto, input: UpdateProject) { return getChanges(IProject)(currentProject, input); } @@ -209,10 +203,7 @@ export class ProjectRepository extends CommonRepository { } catch (e) { if (e instanceof UniquenessError && e.label === 'ProjectName') { throw Object.assign( - new DuplicateException( - 'project.name', - 'Project with this name already exists', - ), + new DuplicateException('project.name', 'Project with this name already exists'), { value: e.value }, ); } @@ -304,9 +295,7 @@ export class ProjectRepository extends CommonRepository { ); result = { ...result, - marketingLocation: marketingLocationId - ? { id: marketingLocationId } - : null, + marketingLocation: marketingLocationId ? { id: marketingLocationId } : null, }; } @@ -387,9 +376,7 @@ export const ProjectNameIndex = FullTextIndex({ export const projectSorters = defineSorters(IProject, { sensitivity: (query) => - query - .apply(matchProjectSens('node', 'sortValue')) - .return('sortValue'), + query.apply(matchProjectSens('node', 'sortValue')).return('sortValue'), ...mapValues.fromList( [ 'engagements', // probably "deprecated" diff --git a/src/components/project/project.resolver.ts b/src/components/project/project.resolver.ts index 78d08842bc..ea016b692e 100644 --- a/src/components/project/project.resolver.ts +++ b/src/components/project/project.resolver.ts @@ -30,17 +30,10 @@ import { SecuredFieldRegion } from '../field-region/dto'; import { FileNodeLoader } from '../file'; import { asDirectory, SecuredDirectory } from '../file/dto'; import { LocationLoader } from '../location'; -import { - LocationListInput, - SecuredLocation, - SecuredLocationList, -} from '../location/dto'; +import { LocationListInput, SecuredLocation, SecuredLocationList } from '../location/dto'; import { OrganizationLoader } from '../organization'; import { SecuredOrganization } from '../organization/dto'; -import { - PartnershipByProjectAndPartnerLoader, - PartnershipLoader, -} from '../partnership'; +import { PartnershipByProjectAndPartnerLoader, PartnershipLoader } from '../partnership'; import { Partnership, PartnershipListInput, @@ -67,10 +60,7 @@ import { UpdateProjectOutput, } from './dto'; import { ProjectMemberLoader } from './project-member'; -import { - ProjectMemberListInput, - SecuredProjectMemberList, -} from './project-member/dto'; +import { ProjectMemberListInput, SecuredProjectMemberList } from './project-member/dto'; import { ProjectLoader } from './project.loader'; import { ProjectService } from './project.service'; @@ -120,10 +110,7 @@ export class ProjectResolver { ...input, filter: { ...input.filter, - type: [ - ProjectType.MomentumTranslation, - ProjectType.MultiplicationTranslation, - ], + type: [ProjectType.MomentumTranslation, ProjectType.MultiplicationTranslation], }, }); projects.primeAll(list.items); @@ -216,11 +203,7 @@ export class ProjectResolver { @ListArg(PartnershipListInput) input: PartnershipListInput, @Loader(PartnershipLoader) partnerships: LoaderOf, ): Promise { - const list = await this.projectService.listPartnerships( - project, - input, - project.changeset, - ); + const list = await this.projectService.listPartnerships(project, input, project.changeset); partnerships.primeAll(list.items); return list; } @@ -254,9 +237,7 @@ export class ProjectResolver { }; } if (!project.rootDirectory.value?.id) { - throw new NotFoundException( - 'Could not find root directory associated to this project', - ); + throw new NotFoundException('Could not find root directory associated to this project'); } const dir = asDirectory(await files.load(project.rootDirectory.value.id)); @@ -282,9 +263,7 @@ export class ProjectResolver { @Parent() project: Project, @Loader(LocationLoader) locations: LoaderOf, ): Promise { - return await mapSecuredValue(project.primaryLocation, ({ id }) => - locations.load(id), - ); + return await mapSecuredValue(project.primaryLocation, ({ id }) => locations.load(id)); } @ResolveField(() => SecuredLocationList) @@ -303,9 +282,7 @@ export class ProjectResolver { @Parent() project: Project, @Loader(LocationLoader) locations: LoaderOf, ): Promise { - return await mapSecuredValue(project.marketingLocation, ({ id }) => - locations.load(id), - ); + return await mapSecuredValue(project.marketingLocation, ({ id }) => locations.load(id)); } @ResolveField(() => SecuredLocation) @@ -313,9 +290,7 @@ export class ProjectResolver { @Parent() project: Project, @Loader(LocationLoader) locations: LoaderOf, ): Promise { - return await mapSecuredValue(project.marketingRegionOverride, ({ id }) => - locations.load(id), - ); + return await mapSecuredValue(project.marketingRegionOverride, ({ id }) => locations.load(id)); } @ResolveField(() => SecuredFieldRegion) @@ -323,9 +298,7 @@ export class ProjectResolver { @Parent() project: Project, @Loader(FieldRegionLoader) fieldRegions: LoaderOf, ): Promise { - return await mapSecuredValue(project.fieldRegion, ({ id }) => - fieldRegions.load(id), - ); + return await mapSecuredValue(project.fieldRegion, ({ id }) => fieldRegions.load(id)); } @ResolveField(() => SecuredOrganization) @@ -333,9 +306,7 @@ export class ProjectResolver { @Parent() project: Project, @Loader(OrganizationLoader) organizations: LoaderOf, ): Promise { - return await mapSecuredValue(project.owningOrganization, ({ id }) => - organizations.load(id), - ); + return await mapSecuredValue(project.owningOrganization, ({ id }) => organizations.load(id)); } @ResolveField() diff --git a/src/components/project/project.service.ts b/src/components/project/project.service.ts index 418bddb328..dd10c91ce6 100644 --- a/src/components/project/project.service.ts +++ b/src/components/project/project.service.ts @@ -28,20 +28,11 @@ import { Privileges } from '../authorization'; import { BudgetService } from '../budget'; import { BudgetStatus, type SecuredBudget } from '../budget/dto'; import { EngagementService } from '../engagement'; -import { - type EngagementListInput, - type SecuredEngagementList, -} from '../engagement/dto'; +import { type EngagementListInput, type SecuredEngagementList } from '../engagement/dto'; import { LocationService } from '../location'; -import { - type LocationListInput, - type SecuredLocationList, -} from '../location/dto'; +import { type LocationListInput, type SecuredLocationList } from '../location/dto'; import { PartnershipService } from '../partnership'; -import { - type PartnershipListInput, - type SecuredPartnershipList, -} from '../partnership/dto'; +import { type PartnershipListInput, type SecuredPartnershipList } from '../partnership/dto'; import { ProjectChangeRequestService } from '../project-change-request'; import { type ProjectChangeRequestListInput, @@ -63,16 +54,9 @@ import { TranslationProject, UpdateProject, } from './dto'; -import { - ProjectCreatedEvent, - ProjectDeletedEvent, - ProjectUpdatedEvent, -} from './events'; +import { ProjectCreatedEvent, ProjectDeletedEvent, ProjectUpdatedEvent } from './events'; import { ProjectMemberService } from './project-member'; -import { - type ProjectMemberListInput, - type SecuredProjectMemberList, -} from './project-member/dto'; +import { type ProjectMemberListInput, type SecuredProjectMemberList } from './project-member/dto'; import { ProjectRepository } from './project.repository'; @Injectable() @@ -147,9 +131,7 @@ export class ProjectService { try { const { id } = await this.repo.create(input); const project = await this.readOneUnsecured(id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(IProject) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(IProject) : e; }); RequiredWhen.verify(IProject, project); @@ -191,10 +173,7 @@ export class ProjectService { MomentumTranslationProject, MultiplicationTranslationProject, ]) - async readOneTranslation( - id: ID, - view?: ObjectView, - ): Promise { + async readOneTranslation(id: ID, view?: ObjectView): Promise { const project = await this.readOne(id, view?.changeset); if (project.type === ProjectType.Internship) { throw new Error('Project is not a translation project'); @@ -203,10 +182,7 @@ export class ProjectService { } @HandleIdLookup(InternshipProject) - async readOneInternship( - id: ID, - view?: ObjectView, - ): Promise { + async readOneInternship(id: ID, view?: ObjectView): Promise { const project = await this.readOne(id, view?.changeset); if (project.type !== ProjectType.Internship) { throw new Error('Project is not an internship project'); @@ -214,17 +190,11 @@ export class ProjectService { return project as InternshipProject; } - async readOneUnsecured( - id: ID, - changeset?: ID, - ): Promise> { + async readOneUnsecured(id: ID, changeset?: ID): Promise> { return await this.repo.readOne(id, changeset); } - async readMany( - ids: readonly ID[], - view: ObjectView, - ): Promise { + async readMany(ids: readonly ID[], view: ObjectView): Promise { const projects = await this.repo.readMany(ids, view?.changeset); return await Promise.all(projects.map((dto) => this.secure(dto))); } @@ -239,10 +209,7 @@ export class ProjectService { } @Transactional() - async update( - input: UpdateProject, - changeset?: ID, - ): Promise> { + async update(input: UpdateProject, changeset?: ID): Promise> { const currentProject = await this.readOneUnsecured(input.id, changeset); if (input.sensitivity && currentProject.type !== ProjectType.Internship) throw new InputException( @@ -251,10 +218,7 @@ export class ProjectService { ); // Only allow admins to specify department IDs - if ( - input.departmentId !== undefined && - !this.identity.isImpersonatorAdmin - ) { + if (input.departmentId !== undefined && !this.identity.isImpersonatorAdmin) { throw UnauthorizedException.fromPrivileges( 'edit', undefined, @@ -275,9 +239,7 @@ export class ProjectService { if (changes.primaryLocationId) { try { - const location = await this.locationService.readOne( - changes.primaryLocationId, - ); + const location = await this.locationService.readOne(changes.primaryLocationId); if (!location.fundingAccount.value) { throw new InputException( 'Cannot connect location without a funding account', @@ -286,11 +248,7 @@ export class ProjectService { } } catch (e) { if (e instanceof NotFoundException) { - throw new NotFoundException( - 'Primary location not found', - 'project.primaryLocationId', - e, - ); + throw new NotFoundException('Primary location not found', 'project.primaryLocationId', e); } throw e; } @@ -307,10 +265,7 @@ export class ProjectService { const prevMissing = RequiredWhen.calc(IProject, currentProject); const nowMissing = RequiredWhen.calc(IProject, updated); - if ( - nowMissing && - (!prevMissing || nowMissing.missing.length >= prevMissing.missing.length) - ) { + if (nowMissing && (!prevMissing || nowMissing.missing.length >= prevMissing.missing.length)) { throw nowMissing; } @@ -440,10 +395,7 @@ export class ProjectService { }; } - async listProjectsByUserId( - userId: ID, - input: ProjectListInput, - ): Promise { + async listProjectsByUserId(userId: ID, input: ProjectListInput): Promise { // Instead of trying to handle which subset of projects should be included, // based on doing the work of seeing which project teams they can view, // we'll use this course all/nothing check. This, assuming role permissions @@ -492,10 +444,7 @@ export class ProjectService { locationId, ); } catch (e) { - throw new ServerException( - 'Could not remove other location from project', - e, - ); + throw new ServerException('Could not remove other location from project', e); } } @@ -510,10 +459,7 @@ export class ProjectService { ); } - async currentBudget( - project: IProject, - changeset?: ID, - ): Promise { + async currentBudget(project: IProject, changeset?: ID): Promise { let budgetToReturn; const perms = this.privileges.for(IProject, project).forEdge('budget'); @@ -527,9 +473,7 @@ export class ProjectService { changeset, ); - const current = budgets.items.find( - (b) => b.status === BudgetStatus.Current, - ); + const current = budgets.items.find((b) => b.status === BudgetStatus.Current); // #574 - if no current budget, then fallback to the first pending budget budgetToReturn = current ?? budgets.items[0]; @@ -572,8 +516,7 @@ class ProjectDateRangeException extends RangeException { current: Partial, 'mouStart' | 'mouEnd'>>, changes: AnyChangesOf = {}, ) { - const start = - changes.mouStart !== undefined ? changes.mouStart : current.mouStart; + const start = changes.mouStart !== undefined ? changes.mouStart : current.mouStart; const end = changes.mouEnd !== undefined ? changes.mouEnd : current.mouEnd; if (start && end && start > end) { const field = changes.mouEnd ? 'project.mouEnd' : 'project.mouStart'; diff --git a/src/components/project/workflow/dto/execute-project-transition.input.ts b/src/components/project/workflow/dto/execute-project-transition.input.ts index 706f5fe11c..eea2b79d0e 100644 --- a/src/components/project/workflow/dto/execute-project-transition.input.ts +++ b/src/components/project/workflow/dto/execute-project-transition.input.ts @@ -4,9 +4,7 @@ import { ExecuteTransitionInput } from '../../../workflow/dto'; import { ProjectStep } from '../../dto'; @InputType() -export abstract class ExecuteProjectTransitionInput extends ExecuteTransitionInput( - ProjectStep, -) { +export abstract class ExecuteProjectTransitionInput extends ExecuteTransitionInput(ProjectStep) { @IdField({ description: 'The project ID to transition', }) diff --git a/src/components/project/workflow/dto/workflow-transition.dto.ts b/src/components/project/workflow/dto/workflow-transition.dto.ts index 5e19a0b272..9540ac3dd9 100644 --- a/src/components/project/workflow/dto/workflow-transition.dto.ts +++ b/src/components/project/workflow/dto/workflow-transition.dto.ts @@ -5,6 +5,4 @@ import { ProjectStep } from '../../dto'; @ObjectType({ description: WorkflowTransition.descriptionFor('project'), }) -export abstract class ProjectWorkflowTransition extends WorkflowTransition( - ProjectStep, -) {} +export abstract class ProjectWorkflowTransition extends WorkflowTransition(ProjectStep) {} diff --git a/src/components/project/workflow/handlers/project-workflow-notification.handler.ts b/src/components/project/workflow/handlers/project-workflow-notification.handler.ts index 3553cef8f2..c81d065774 100644 --- a/src/components/project/workflow/handlers/project-workflow-notification.handler.ts +++ b/src/components/project/workflow/handlers/project-workflow-notification.handler.ts @@ -2,13 +2,7 @@ import { ModuleRef } from '@nestjs/core'; import { asyncPool } from '@seedcompany/common'; import { EmailService } from '@seedcompany/nestjs-email'; import { type UnsecuredDto } from '~/common'; -import { - ConfigService, - EventsHandler, - type IEventHandler, - ILogger, - Logger, -} from '~/core'; +import { ConfigService, EventsHandler, type IEventHandler, ILogger, Logger } from '~/core'; import { Identity } from '~/core/authentication'; import { ProjectStepChanged, @@ -22,9 +16,7 @@ import { type Project, type ProjectStep } from '../../dto'; import { ProjectTransitionedEvent } from '../events/project-transitioned.event'; @EventsHandler(ProjectTransitionedEvent) -export class ProjectWorkflowNotificationHandler - implements IEventHandler -{ +export class ProjectWorkflowNotificationHandler implements IEventHandler { constructor( private readonly identity: Identity, private readonly config: ConfigService, @@ -50,9 +42,7 @@ export class ProjectWorkflowNotificationHandler previousStep, moduleRef: this.moduleRef, }; - const notifyees = ( - await Promise.all(notifiers.map((notifier) => notifier.resolve(params))) - ) + const notifyees = (await Promise.all(notifiers.map((notifier) => notifier.resolve(params)))) .flat() .filter( (n) => diff --git a/src/components/project/workflow/migrations/step-history-to-workflow-events.migration.ts b/src/components/project/workflow/migrations/step-history-to-workflow-events.migration.ts index 3daaefb1bd..a93ecca00a 100644 --- a/src/components/project/workflow/migrations/step-history-to-workflow-events.migration.ts +++ b/src/components/project/workflow/migrations/step-history-to-workflow-events.migration.ts @@ -28,11 +28,7 @@ export class StepHistoryToWorkflowEventsMigration extends BaseMigration { .match(node('ghost', 'Actor', { id: ghost.id })) .subQuery('project', (sub) => sub - .match([ - node('project', 'Project'), - relation('out', '', 'step'), - node('step'), - ]) + .match([node('project', 'Project'), relation('out', '', 'step'), node('step')]) .with('step') .orderBy('step.createdAt', 'asc') .return('collect(apoc.convert.toMap(step)) as steps'), @@ -44,9 +40,7 @@ export class StepHistoryToWorkflowEventsMigration extends BaseMigration { steps: ReadonlyArray<{ value: ProjectStep; createdAt: DateTime }>; }>('apoc.convert.toMap(project) as project, steps') .run(); - this.logger.notice( - `Found ${projects.length} projects to add event history to.`, - ); + this.logger.notice(`Found ${projects.length} projects to add event history to.`); const events: Array< Parameters[0] & { at: DateTime } diff --git a/src/components/project/workflow/project-workflow.flowchart.ts b/src/components/project/workflow/project-workflow.flowchart.ts index ee0a204b68..249be5fc73 100644 --- a/src/components/project/workflow/project-workflow.flowchart.ts +++ b/src/components/project/workflow/project-workflow.flowchart.ts @@ -3,6 +3,4 @@ import { WorkflowFlowchart } from '../../workflow/workflow.flowchart'; import { ProjectWorkflow } from './project-workflow'; @Injectable() -export class ProjectWorkflowFlowchart extends WorkflowFlowchart( - () => ProjectWorkflow, -) {} +export class ProjectWorkflowFlowchart extends WorkflowFlowchart(() => ProjectWorkflow) {} diff --git a/src/components/project/workflow/project-workflow.granter.ts b/src/components/project/workflow/project-workflow.granter.ts index fa01011f2d..9ce868b99c 100644 --- a/src/components/project/workflow/project-workflow.granter.ts +++ b/src/components/project/workflow/project-workflow.granter.ts @@ -4,9 +4,7 @@ import { ProjectWorkflowEvent as Event } from './dto'; import { ProjectWorkflow } from './project-workflow'; @Granter(Event) -export class ProjectWorkflowEventGranter extends WorkflowEventGranter( - () => ProjectWorkflow, -) {} +export class ProjectWorkflowEventGranter extends WorkflowEventGranter(() => ProjectWorkflow) {} declare module '../../authorization/policy/granters' { interface GrantersOverride { diff --git a/src/components/project/workflow/project-workflow.neo4j.repository.ts b/src/components/project/workflow/project-workflow.neo4j.repository.ts index ba7308483b..ae0135c129 100644 --- a/src/components/project/workflow/project-workflow.neo4j.repository.ts +++ b/src/components/project/workflow/project-workflow.neo4j.repository.ts @@ -12,10 +12,7 @@ import { sorting, } from '~/core/database/query'; import { IProject, type ProjectStep, stepToStatus } from '../dto'; -import { - type ExecuteProjectTransitionInput, - ProjectWorkflowEvent as WorkflowEvent, -} from './dto'; +import { type ExecuteProjectTransitionInput, ProjectWorkflowEvent as WorkflowEvent } from './dto'; import { type ProjectWorkflowRepository } from './project-workflow.repository'; @Injectable() @@ -66,11 +63,7 @@ export class ProjectWorkflowNeo4jRepository relation('out', undefined, 'who'), node('who', 'Actor'), ]) - .match([ - node('project'), - relation('out', '', 'step', ACTIVE), - node('step', 'Property'), - ]) + .match([node('project'), relation('out', '', 'step', ACTIVE), node('step', 'Property')]) .return<{ dto: UnsecuredDto & { project: { previousStep: ProjectStep }; @@ -125,10 +118,7 @@ export class ProjectWorkflowNeo4jRepository return event; } - async mostRecentStep( - projectId: ID<'Project'>, - steps: readonly ProjectStep[], - ) { + async mostRecentStep(projectId: ID<'Project'>, steps: readonly ProjectStep[]) { const result = await this.db .query() .match([ diff --git a/src/components/project/workflow/project-workflow.repository.ts b/src/components/project/workflow/project-workflow.repository.ts index f346e092d2..003bffc7f3 100644 --- a/src/components/project/workflow/project-workflow.repository.ts +++ b/src/components/project/workflow/project-workflow.repository.ts @@ -3,10 +3,7 @@ import { type ID } from '~/common'; import { e, edgeql, RepoFor } from '~/core/gel'; import { type ProjectStep } from '../dto'; import { projectRefShape } from '../project.gel.repository'; -import { - type ExecuteProjectTransitionInput, - ProjectWorkflowEvent, -} from './dto'; +import { type ExecuteProjectTransitionInput, ProjectWorkflowEvent } from './dto'; @Injectable() export class ProjectWorkflowRepository extends RepoFor(ProjectWorkflowEvent, { @@ -48,10 +45,7 @@ export class ProjectWorkflowRepository extends RepoFor(ProjectWorkflowEvent, { return await this.db.run(query); } - async mostRecentStep( - projectId: ID<'Project'>, - steps: readonly ProjectStep[], - ) { + async mostRecentStep(projectId: ID<'Project'>, steps: readonly ProjectStep[]) { const query = edgeql(` with project := $projectId, diff --git a/src/components/project/workflow/project-workflow.service.ts b/src/components/project/workflow/project-workflow.service.ts index 12067807bd..0344c14002 100644 --- a/src/components/project/workflow/project-workflow.service.ts +++ b/src/components/project/workflow/project-workflow.service.ts @@ -8,24 +8,16 @@ import { unwrapSecured, } from '~/common'; import { IEventBus } from '~/core'; -import { - findTransition, - WorkflowService, -} from '../../workflow/workflow.service'; +import { findTransition, WorkflowService } from '../../workflow/workflow.service'; import { IProject, type Project } from '../dto'; import { ProjectService } from '../project.service'; -import { - type ExecuteProjectTransitionInput, - ProjectWorkflowEvent as WorkflowEvent, -} from './dto'; +import { type ExecuteProjectTransitionInput, ProjectWorkflowEvent as WorkflowEvent } from './dto'; import { ProjectTransitionedEvent } from './events/project-transitioned.event'; import { ProjectWorkflow } from './project-workflow'; import { ProjectWorkflowRepository } from './project-workflow.repository'; @Injectable() -export class ProjectWorkflowService extends WorkflowService( - () => ProjectWorkflow, -) { +export class ProjectWorkflowService extends WorkflowService(() => ProjectWorkflow) { constructor( @Inject(forwardRef(() => ProjectService)) private readonly projects: ProjectService & {}, @@ -68,16 +60,11 @@ export class ProjectWorkflowService extends WorkflowService( const next = this.getBypassIfValid(input) ?? - findTransition( - await this.getAvailableTransitions(previous), - input.transition, - ); + findTransition(await this.getAvailableTransitions(previous), input.transition); const unsecuredEvent = await this.repo.recordEvent({ project: projectId, - ...(typeof next !== 'string' - ? { transition: next.key, to: next.to } - : { to: next }), + ...(typeof next !== 'string' ? { transition: next.key, to: next.to } : { to: next }), notes, }); @@ -85,12 +72,7 @@ export class ProjectWorkflowService extends WorkflowService( RequiredWhen.verify(IProject, updated); - const event = new ProjectTransitionedEvent( - updated, - previous.step, - next, - unsecuredEvent, - ); + const event = new ProjectTransitionedEvent(updated, previous.step, next, unsecuredEvent); await this.eventBus.publish(event); return this.projects.secure(event.project); diff --git a/src/components/project/workflow/project-workflow.ts b/src/components/project/workflow/project-workflow.ts index 956ce85e3d..7dd82d0d00 100644 --- a/src/components/project/workflow/project-workflow.ts +++ b/src/components/project/workflow/project-workflow.ts @@ -8,11 +8,7 @@ import { IsMultiplication, RequireOngoingEngagementsToBeFinalizingCompletion, } from './transitions/conditions'; -import { - BackTo, - BackToActive, - type ResolveParams, -} from './transitions/dynamic-step'; +import { BackTo, BackToActive, type ResolveParams } from './transitions/dynamic-step'; import { ApprovalFromEarlyConversationsRequiresEngagements, ImplicitlyNotifyTeamMembers, @@ -330,10 +326,7 @@ export const ProjectWorkflow = defineWorkflow({ }, 'Retract Change To Plan Approval Request': { - from: [ - Step.PendingChangeToPlanApproval, - Step.PendingChangeToPlanConfirmation, - ], + from: [Step.PendingChangeToPlanApproval, Step.PendingChangeToPlanConfirmation], to: Step.DiscussingChangeToPlan, label: 'Retract Approval Request', type: Type.Neutral, @@ -490,12 +483,7 @@ export const ProjectWorkflow = defineWorkflow({ }, 'End Termination Discussion': { from: Step.DiscussingTermination, - to: BackTo( - Step.Active, - Step.ActiveChangedPlan, - Step.DiscussingReactivation, - Step.Suspended, - ), + to: BackTo(Step.Active, Step.ActiveChangedPlan, Step.DiscussingReactivation, Step.Suspended), label: 'Will Not Terminate', type: Type.Neutral, notifiers: Distros.Termination, @@ -517,12 +505,7 @@ export const ProjectWorkflow = defineWorkflow({ }, 'End Termination Discussion By Approver': { from: Step.PendingTerminationApproval, - to: BackTo( - Step.Active, - Step.ActiveChangedPlan, - Step.DiscussingReactivation, - Step.Suspended, - ), + to: BackTo(Step.Active, Step.ActiveChangedPlan, Step.DiscussingReactivation, Step.Suspended), label: 'Will Not Terminate', type: Type.Neutral, notifiers: Distros.Termination, diff --git a/src/components/project/workflow/resolvers/project-transitions.resolver.ts b/src/components/project/workflow/resolvers/project-transitions.resolver.ts index 8c6ccf3300..c245c82883 100644 --- a/src/components/project/workflow/resolvers/project-transitions.resolver.ts +++ b/src/components/project/workflow/resolvers/project-transitions.resolver.ts @@ -16,8 +16,7 @@ export class ProjectTransitionsResolver { } @ResolveField(() => [ProjectWorkflowTransition], { - description: - 'The transitions currently available to execute for this project', + description: 'The transitions currently available to execute for this project', }) async transitions( @Grandparent() project: Project, diff --git a/src/components/project/workflow/resolvers/project-workflow-events.resolver.ts b/src/components/project/workflow/resolvers/project-workflow-events.resolver.ts index 8477864cc7..03917fd8f1 100644 --- a/src/components/project/workflow/resolvers/project-workflow-events.resolver.ts +++ b/src/components/project/workflow/resolvers/project-workflow-events.resolver.ts @@ -8,9 +8,7 @@ export class ProjectWorkflowEventsResolver { constructor(private readonly service: ProjectWorkflowService) {} @ResolveField(() => [WorkflowEvent]) - async workflowEvents( - @Parent() report: Project, - ): Promise { + async workflowEvents(@Parent() report: Project): Promise { return await this.service.list(report); } } diff --git a/src/components/project/workflow/transitions/conditions.ts b/src/components/project/workflow/transitions/conditions.ts index 67ca0f0733..09917be197 100644 --- a/src/components/project/workflow/transitions/conditions.ts +++ b/src/components/project/workflow/transitions/conditions.ts @@ -34,13 +34,10 @@ export const HasEngagement: Condition = { }; export const RequireOngoingEngagementsToBeFinalizingCompletion: Condition = { - description: - 'All engagements must be Finalizing Completion or in a terminal status', + description: 'All engagements must be Finalizing Completion or in a terminal status', async resolve({ project, moduleRef }) { const repo = moduleRef.get(EngagementService, { strict: false }); - const hasOngoing = await repo.hasOngoing(project.id, [ - EngagementStatus.FinalizingCompletion, - ]); + const hasOngoing = await repo.hasOngoing(project.id, [EngagementStatus.FinalizingCompletion]); return { status: hasOngoing ? 'DISABLED' : 'ENABLED', disabledReason: `The project cannot be completed since some ongoing engagements are not "Finalizing Completion"`, diff --git a/src/components/project/workflow/transitions/dynamic-step.ts b/src/components/project/workflow/transitions/dynamic-step.ts index 86a1cb6034..a23e0a09a8 100644 --- a/src/components/project/workflow/transitions/dynamic-step.ts +++ b/src/components/project/workflow/transitions/dynamic-step.ts @@ -10,9 +10,7 @@ export interface ResolveParams { moduleRef: ModuleRef; } -export const BackTo = ( - ...steps: ProjectStep[] -): DynamicState => ({ +export const BackTo = (...steps: ProjectStep[]): DynamicState => ({ description: 'Back', relatedStates: steps, async resolve({ project, moduleRef }) { diff --git a/src/components/project/workflow/transitions/enhancers.ts b/src/components/project/workflow/transitions/enhancers.ts index 3526b8825b..be5e758d83 100644 --- a/src/components/project/workflow/transitions/enhancers.ts +++ b/src/components/project/workflow/transitions/enhancers.ts @@ -11,9 +11,7 @@ export const ImplicitlyNotifyTeamMembers: Enhancer = (transition) => ({ notifiers: transition.notifiers.concat(TeamMembers), }); -export const ApprovalFromEarlyConversationsRequiresEngagements: Enhancer = ( - transition, -) => +export const ApprovalFromEarlyConversationsRequiresEngagements: Enhancer = (transition) => transition.type === 'Approve' && transition.from?.has('EarlyConversations') ? { ...transition, diff --git a/src/components/prompts/dto/prompt-list.dto.ts b/src/components/prompts/dto/prompt-list.dto.ts index 9c4a4f61ca..14e139c099 100644 --- a/src/components/prompts/dto/prompt-list.dto.ts +++ b/src/components/prompts/dto/prompt-list.dto.ts @@ -9,9 +9,7 @@ export abstract class PromptList { } @ObjectType() -export abstract class VariantPromptList< - VariantKey extends string = string, -> extends PromptList { +export abstract class VariantPromptList extends PromptList { @Field(() => [Variant]) readonly variants: ReadonlyArray>; } diff --git a/src/components/prompts/dto/prompt-response-list.dto.ts b/src/components/prompts/dto/prompt-response-list.dto.ts index aa6ed8ec4f..20e2c1c2ba 100644 --- a/src/components/prompts/dto/prompt-response-list.dto.ts +++ b/src/components/prompts/dto/prompt-response-list.dto.ts @@ -10,9 +10,9 @@ export class PromptResponseList extends SecuredList(PromptResponse) { } @ObjectType() -export class PromptVariantResponseList< - VariantKey extends string = string, -> extends SecuredList(PromptVariantResponse) { +export class PromptVariantResponseList extends SecuredList( + PromptVariantResponse, +) { @Field() readonly available: VariantPromptList; } diff --git a/src/components/prompts/dto/prompt-response.dto.ts b/src/components/prompts/dto/prompt-response.dto.ts index 047597ae2e..8d32aeb67f 100644 --- a/src/components/prompts/dto/prompt-response.dto.ts +++ b/src/components/prompts/dto/prompt-response.dto.ts @@ -47,9 +47,7 @@ export abstract class VariantResponse { } @ObjectType() -export class PromptVariantResponse< - Key extends string = string, -> extends Resource { +export class PromptVariantResponse extends Resource { static readonly Parent = 'dynamic' as 'dynamic' | (() => Promise); static Relations = { diff --git a/src/components/prompts/prompt-variant-response.repository.ts b/src/components/prompts/prompt-variant-response.repository.ts index 4ff567e104..0a346c4289 100644 --- a/src/components/prompts/prompt-variant-response.repository.ts +++ b/src/components/prompts/prompt-variant-response.repository.ts @@ -70,9 +70,7 @@ export const PromptVariantResponseRepository = < async list( parentId: ID, - ): Promise< - PaginatedListType>> - > { + ): Promise>>> { const result = await this.db .query() .match([ @@ -80,9 +78,7 @@ export const PromptVariantResponseRepository = < relation('out', undefined, 'child', ACTIVE), node('node', this.resource.dbLabel), ]) - .apply( - sorting(this.resource.type, { sort: 'createdAt', order: Order.ASC }), - ) + .apply(sorting(this.resource.type, { sort: 'createdAt', order: Order.ASC })) .apply(paginate({ count: 25, page: 1 }, this.hydrate())) .first(); return result!; // the result from paginate() will always have 1 row. @@ -92,21 +88,9 @@ export const PromptVariantResponseRepository = < return (query: Query) => query .apply(this.filterToReadable()) - .match([ - node('parent', 'BaseNode'), - relation('out', undefined, 'child'), - node('node'), - ]) - .match([ - node('node'), - relation('out', undefined, 'creator'), - node('creator', 'User'), - ]) - .match([ - node('node'), - relation('out', undefined, 'prompt'), - node('prompt', 'Property'), - ]) + .match([node('parent', 'BaseNode'), relation('out', undefined, 'child'), node('node')]) + .match([node('node'), relation('out', undefined, 'creator'), node('creator', 'User')]) + .match([node('node'), relation('out', undefined, 'prompt'), node('prompt', 'Property')]) .subQuery('node', (sub) => sub .match([ @@ -116,8 +100,7 @@ export const PromptVariantResponseRepository = < ]) .with( merge('response', { - modifiedAt: - 'coalesce(response.modifiedAt, response.createdAt)', + modifiedAt: 'coalesce(response.modifiedAt, response.createdAt)', }).as('response'), ) .return('collect(response) as responses'), @@ -134,9 +117,7 @@ export const PromptVariantResponseRepository = < protected abstract filterToReadable(): QueryFragment; - async create( - input: ChoosePrompt, - ): Promise>> { + async create(input: ChoosePrompt): Promise>> { // @ts-expect-error uhhhh yolo ¯\_(ツ)_/¯ const resource: typeof PromptVariantResponse = this.resource.type; diff --git a/src/components/prompts/prompt-variant-response.service.ts b/src/components/prompts/prompt-variant-response.service.ts index 4598669bc8..062190138c 100644 --- a/src/components/prompts/prompt-variant-response.service.ts +++ b/src/components/prompts/prompt-variant-response.service.ts @@ -18,11 +18,7 @@ import { import { ResourceLoader } from '~/core'; import { Identity } from '~/core/authentication'; import { mapListResults } from '~/core/database/results'; -import { - Privileges, - type UserResourcePrivileges, - withVariant, -} from '../authorization'; +import { Privileges, type UserResourcePrivileges, withVariant } from '../authorization'; import { type ChangePrompt, type ChoosePrompt, @@ -42,11 +38,7 @@ export const PromptVariantResponseListService = < TVariant extends string = VariantOf, >( repo: ReturnType< - typeof PromptVariantResponseRepository< - TParentResourceStatic, - TResourceStatic, - TVariant - > + typeof PromptVariantResponseRepository >, ) => { @Injectable() @@ -64,14 +56,12 @@ export const PromptVariantResponseListService = < } protected get resourcePrivileges() { - return this.privileges.forResource< - typeof PromptVariantResponse - >(this.resource as any); + return this.privileges.forResource>( + this.resource as any, + ); } - protected abstract getPrivilegeContext( - dto: UnsecuredDto, - ): Promise; + protected abstract getPrivilegeContext(dto: UnsecuredDto): Promise; protected abstract getPrompts(): Promise; @@ -112,9 +102,7 @@ export const PromptVariantResponseListService = < } protected async getAvailableVariants( - privileges: UserResourcePrivileges< - typeof PromptVariantResponse - >, + privileges: UserResourcePrivileges>, ) { const variants = this.resource.Variants.filter((variant) => privileges @@ -133,9 +121,7 @@ export const PromptVariantResponseListService = < const secured = privileges.secure(dto); return { ...secured, - prompt: await mapSecuredValue(secured.prompt, (id) => - this.getPromptById(id), - ), + prompt: await mapSecuredValue(secured.prompt, (id) => this.getPromptById(id)), responses: this.resource.Variants.flatMap((variant) => { const variantPrivileges = privileges.forContext( withVariant(privileges.context!, variant.key), @@ -162,9 +148,7 @@ export const PromptVariantResponseListService = < }; } - async create( - input: ChoosePrompt, - ): Promise> { + async create(input: ChoosePrompt): Promise> { const edge = this.repo.edge; const parent = await this.resources.load( // @ts-expect-error yeah we are assuming it's registered @@ -184,9 +168,7 @@ export const PromptVariantResponseListService = < return await this.secure(dto); } - async changePrompt( - input: ChangePrompt, - ): Promise> { + async changePrompt(input: ChangePrompt): Promise> { const response = await this.repo.readOne(input.id); const context = await this.getPrivilegeContext(response); const privileges = this.resourcePrivileges.forContext(context); @@ -231,10 +213,7 @@ export const PromptVariantResponseListService = < } const session = this.identity.current; - const responses = mapKeys.fromList( - response.responses, - (response) => response.variant, - ).asMap; + const responses = mapKeys.fromList(response.responses, (response) => response.variant).asMap; const updated: UnsecuredDto> = { ...response, responses: this.resource.Variants.map(({ key }) => ({ diff --git a/src/components/prompts/prompts.module.ts b/src/components/prompts/prompts.module.ts index b325e10c34..737561f985 100644 --- a/src/components/prompts/prompts.module.ts +++ b/src/components/prompts/prompts.module.ts @@ -6,10 +6,6 @@ import { } from './prompt-variant-response.resolver'; @Module({ - providers: [ - PromptResponseResolver, - PromptVariantResponseResolver, - VariantResponseResolver, - ], + providers: [PromptResponseResolver, PromptVariantResponseResolver, VariantResponseResolver], }) export class PromptsModule {} diff --git a/src/components/scripture/book-difficulty-factor.ts b/src/components/scripture/book-difficulty-factor.ts index 21ff3cff6a..6c3ddb8bb3 100644 --- a/src/components/scripture/book-difficulty-factor.ts +++ b/src/components/scripture/book-difficulty-factor.ts @@ -4,8 +4,7 @@ import { type BookDifficulty } from './dto'; export const difficultyFactorOfBook = (book: Book): number => factorMap[difficultyOfBook(book)] ?? 0; -export const difficultyOfBook = (book: Book): BookDifficulty => - difficultyMap[book.name]; +export const difficultyOfBook = (book: Book): BookDifficulty => difficultyMap[book.name]; const factorMap: Record = { Easy: 0.8, diff --git a/src/components/scripture/dto/scripture-range.dto.ts b/src/components/scripture/dto/scripture-range.dto.ts index a0308b6665..9d76f230d9 100644 --- a/src/components/scripture/dto/scripture-range.dto.ts +++ b/src/components/scripture/dto/scripture-range.dto.ts @@ -1,10 +1,5 @@ import { applyDecorators } from '@nestjs/common'; -import { - Field, - type FieldOptions, - InputType, - ObjectType, -} from '@nestjs/graphql'; +import { Field, type FieldOptions, InputType, ObjectType } from '@nestjs/graphql'; import { Book, mergeVerseRanges, Verse } from '@seedcompany/scripture'; import { Transform, Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; @@ -12,24 +7,15 @@ import { stripIndent } from 'common-tags'; import { random, sumBy, times } from 'lodash'; import { type Range, SecuredPropertyList } from '~/common'; import { IsValidOrder } from './scripture-range.validator'; -import { - ScriptureReference, - ScriptureReferenceInput, -} from './scripture-reference.dto'; -import { - ScriptureEnd, - ScriptureStart, -} from './scripture-reference.transformer'; +import { ScriptureReference, ScriptureReferenceInput } from './scripture-reference.dto'; +import { ScriptureEnd, ScriptureStart } from './scripture-reference.transformer'; const description = stripIndent` A range of scripture. i.e. Matthew 1:1-2:10 `; -export const mapRange = ( - input: Range, - mapper: (point: T) => U, -): Range => ({ +export const mapRange = (input: Range, mapper: (point: T) => U): Range => ({ start: mapper(input.start), end: mapper(input.end), }); @@ -41,9 +27,7 @@ export const ScriptureField = (options: FieldOptions) => Type(() => ScriptureRangeInput), Transform(({ value }) => { try { - return value - ? mergeVerseRanges(value).map(ScriptureRange.fromVerses) - : value; + return value ? mergeVerseRanges(value).map(ScriptureRange.fromVerses) : value; } catch (e) { return value; } @@ -98,24 +82,14 @@ export abstract class ScriptureRange implements Range { static random() { const startBook = Book.at(random(1, Book.at(-1).index)); - const startChapter = startBook.chapter( - random(1, startBook.lastChapter.index), - ); - const startVerse = startChapter.verse( - random(1, startChapter.lastVerse.index), - ); + const startChapter = startBook.chapter(random(1, startBook.lastChapter.index)); + const startVerse = startChapter.verse(random(1, startChapter.lastVerse.index)); const endBook = Book.at(random(startBook.index, Book.at(-1).index)); const endChapter = endBook.chapter( - random( - endBook.equals(startBook) ? startChapter.index : 1, - endBook.lastChapter.index, - ), + random(endBook.equals(startBook) ? startChapter.index : 1, endBook.lastChapter.index), ); const endVerse = endChapter.verse( - random( - endChapter.equals(startChapter) ? startVerse.index : 1, - endChapter.lastVerse.index, - ), + random(endChapter.equals(startChapter) ? startVerse.index : 1, endChapter.lastVerse.index), ); return { start: startVerse.reference, @@ -124,23 +98,20 @@ export abstract class ScriptureRange implements Range { } static randomList(min = 2, max = 4) { - return mergeVerseRanges( - times(random(min, max)).map(ScriptureRange.random), - ).map(ScriptureRange.fromVerses); + return mergeVerseRanges(times(random(min, max)).map(ScriptureRange.random)).map( + ScriptureRange.fromVerses, + ); } } @ObjectType({ description: SecuredPropertyList.descriptionFor('scripture ranges'), }) -export class SecuredScriptureRanges extends SecuredPropertyList( - ScriptureRange, -) {} +export class SecuredScriptureRanges extends SecuredPropertyList(ScriptureRange) {} @ObjectType({ description: SecuredPropertyList.descriptionFor('scripture ranges override'), }) -export class SecuredScriptureRangesOverride extends SecuredPropertyList( - ScriptureRange, - { nullable: true }, -) {} +export class SecuredScriptureRangesOverride extends SecuredPropertyList(ScriptureRange, { + nullable: true, +}) {} diff --git a/src/components/scripture/dto/scripture-reference.dto.ts b/src/components/scripture/dto/scripture-reference.dto.ts index 8b8220da6c..727f580470 100644 --- a/src/components/scripture/dto/scripture-reference.dto.ts +++ b/src/components/scripture/dto/scripture-reference.dto.ts @@ -1,10 +1,6 @@ import { Field, InputType, Int, ObjectType } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; -import { - IsValidBook, - IsValidChapter, - IsValidVerse, -} from './scripture-reference.validator'; +import { IsValidBook, IsValidChapter, IsValidVerse } from './scripture-reference.validator'; @InputType({ description: 'A reference to a scripture verse', diff --git a/src/components/scripture/dto/scripture-reference.test.ts b/src/components/scripture/dto/scripture-reference.test.ts index efe05e621d..f036bd5f1b 100644 --- a/src/components/scripture/dto/scripture-reference.test.ts +++ b/src/components/scripture/dto/scripture-reference.test.ts @@ -33,11 +33,7 @@ describe('ScriptureReference', () => { chapter: 1, verse: 1, }); - expectSingleConstraintFailure( - result, - 'ScriptureBook', - 'Not a valid Bible book', - ); + expectSingleConstraintFailure(result, 'ScriptureBook', 'Not a valid Bible book'); }); it('Invalid chapter', async () => { const result = await runValidation({ @@ -45,11 +41,7 @@ describe('ScriptureReference', () => { chapter: 40, verse: 1, }); - expectSingleConstraintFailure( - result, - 'ScriptureChapter', - 'Matthew does not have chapter 40', - ); + expectSingleConstraintFailure(result, 'ScriptureChapter', 'Matthew does not have chapter 40'); }); it('Invalid verse', async () => { const result = await runValidation({ @@ -57,10 +49,6 @@ describe('ScriptureReference', () => { chapter: 1, verse: 1000, }); - expectSingleConstraintFailure( - result, - 'ScriptureVerse', - 'Matthew 1 does not have verse 1000', - ); + expectSingleConstraintFailure(result, 'ScriptureVerse', 'Matthew 1 does not have verse 1000'); }); }); diff --git a/src/components/scripture/dto/scripture-reference.validator.ts b/src/components/scripture/dto/scripture-reference.validator.ts index dabd7be213..7fce87b78c 100644 --- a/src/components/scripture/dto/scripture-reference.validator.ts +++ b/src/components/scripture/dto/scripture-reference.validator.ts @@ -8,13 +8,9 @@ import { type ScriptureReference } from './scripture-reference.dto'; import { type UnspecifiedScripturePortionInput } from './unspecified-scripture-portion.dto'; // We assume this is only used on the ScriptureReference object -type ValidationArgs = Merge< - ValidationArguments, - { object: ScriptureReference } ->; +type ValidationArgs = Merge; -export const IsValidBook = () => - applyDecorators(NormalizeBook(), IsScriptureBook()); +export const IsValidBook = () => applyDecorators(NormalizeBook(), IsScriptureBook()); const IsScriptureBook = createValidationDecorator({ name: 'ScriptureBook', diff --git a/src/components/scripture/dto/unspecified-scripture-portion.dto.ts b/src/components/scripture/dto/unspecified-scripture-portion.dto.ts index dfb9049f3e..60caa3c28a 100644 --- a/src/components/scripture/dto/unspecified-scripture-portion.dto.ts +++ b/src/components/scripture/dto/unspecified-scripture-portion.dto.ts @@ -1,18 +1,13 @@ import { Field, InputType, Int, ObjectType } from '@nestjs/graphql'; import { IsPositive } from 'class-validator'; import { SecuredProperty } from '~/common'; -import { - IsValidBook, - IsValidVerseTotal, -} from './scripture-reference.validator'; +import { IsValidBook, IsValidVerseTotal } from './scripture-reference.validator'; @InputType({ - description: - 'An unspecified range of scripture denoted only by the total number of verses', + description: 'An unspecified range of scripture denoted only by the total number of verses', }) @ObjectType({ - description: - 'An unspecified range of scripture denoted only by the total number of verses', + description: 'An unspecified range of scripture denoted only by the total number of verses', isAbstract: true, }) export abstract class UnspecifiedScripturePortionInput { @@ -32,10 +27,7 @@ export abstract class UnspecifiedScripturePortionInput { @ObjectType() export abstract class UnspecifiedScripturePortion extends UnspecifiedScripturePortionInput { - static isEqual( - a: UnspecifiedScripturePortion, - b: UnspecifiedScripturePortion, - ) { + static isEqual(a: UnspecifiedScripturePortion, b: UnspecifiedScripturePortion) { return a.book === b.book && a.totalVerses === b.totalVerses; } } diff --git a/src/components/scripture/gel.utils.ts b/src/components/scripture/gel.utils.ts index 732213bc09..0315693966 100644 --- a/src/components/scripture/gel.utils.ts +++ b/src/components/scripture/gel.utils.ts @@ -1,9 +1,5 @@ import { type Nil } from '@seedcompany/common'; -import { - labelOfVerseRange, - labelOfVerseRanges, - Verse, -} from '@seedcompany/scripture'; +import { labelOfVerseRange, labelOfVerseRanges, Verse } from '@seedcompany/scripture'; import type { Simplify } from 'type-fest'; import { type $, e } from '~/core/gel'; import { type $expr_Param } from '~/core/gel/generated-client/params'; @@ -34,11 +30,7 @@ export const type = e.tuple({ }); export const valueOptional = (input: readonly ScriptureRangeInput[] | Nil) => - input === undefined - ? undefined - : input && input.length > 0 - ? value(input) - : null; + input === undefined ? undefined : input && input.length > 0 ? value(input) : null; export const value = (input: readonly ScriptureRangeInput[]) => ({ label: labelOfVerseRanges(input), @@ -97,9 +89,7 @@ const hydrateVerseRange = e.shape(e.Scripture.VerseRange, () => ({ })); export const hydrate = < - T extends $expr_PathNode< - $.TypeSet<(typeof e.Scripture.Collection)['__element__']> - >, + T extends $expr_PathNode<$.TypeSet<(typeof e.Scripture.Collection)['__element__']>>, >( sc: T, ) => e.array_agg(e.select(sc.verses, hydrateVerseRange)); diff --git a/src/components/scripture/is-equal.ts b/src/components/scripture/is-equal.ts index 613394deac..607e7d7e89 100644 --- a/src/components/scripture/is-equal.ts +++ b/src/components/scripture/is-equal.ts @@ -1,19 +1,9 @@ -import { - mergeVerseRanges, - type Range, - type Verse, -} from '@seedcompany/scripture'; +import { mergeVerseRanges, type Range, type Verse } from '@seedcompany/scripture'; import { differenceWith } from 'lodash'; import { type ScriptureRange } from './dto'; -export const isScriptureEqual = ( - a: readonly ScriptureRange[], - b: readonly ScriptureRange[], -) => { - if ( - (a.length !== 0 && b.length === 0) || - (a.length === 0 && b.length !== 0) - ) { +export const isScriptureEqual = (a: readonly ScriptureRange[], b: readonly ScriptureRange[]) => { + if ((a.length !== 0 && b.length === 0) || (a.length === 0 && b.length !== 0)) { // If one is empty and the other is not, then we know they aren't equal return false; } diff --git a/src/components/scripture/scripture-collection.resolver.ts b/src/components/scripture/scripture-collection.resolver.ts index 95ad7a3912..d8def242de 100644 --- a/src/components/scripture/scripture-collection.resolver.ts +++ b/src/components/scripture/scripture-collection.resolver.ts @@ -1,12 +1,4 @@ -import { - Args, - Float, - Int, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Float, Int, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { labelOfVerseRanges, parseScripture } from '@seedcompany/scripture'; import { ScriptureRange } from './dto'; import { ScriptureCollection } from './dto/scripture-collection.dto'; @@ -40,8 +32,7 @@ export class ScriptureCollectionResolver { } @ResolveField(() => Float, { - description: - 'The total number of verse equivalents in this scripture collection', + description: 'The total number of verse equivalents in this scripture collection', }) totalVerseEquivalents(@Parent() { verses }: ScriptureCollection): number { return getTotalVerseEquivalents(...verses); diff --git a/src/components/scripture/scripture-range.resolver.ts b/src/components/scripture/scripture-range.resolver.ts index 9feed0c618..1402380c3f 100644 --- a/src/components/scripture/scripture-range.resolver.ts +++ b/src/components/scripture/scripture-range.resolver.ts @@ -34,8 +34,7 @@ export class ScriptureRangeResolver { return verseRange.end - verseRange.start + 1; } @ResolveField(() => Float, { - description: - 'The total number of verse equivalents in this scripture range', + description: 'The total number of verse equivalents in this scripture range', }) totalVerseEquivalents(@Parent() range: ScriptureRange): number { return getTotalVerseEquivalents(range); diff --git a/src/components/scripture/scripture-reference.repository.ts b/src/components/scripture/scripture-reference.repository.ts index fe18402d1e..9bf05bec4b 100644 --- a/src/components/scripture/scripture-reference.repository.ts +++ b/src/components/scripture/scripture-reference.repository.ts @@ -104,19 +104,10 @@ export class ScriptureReferenceRepository { .comment('ScriptureRefs.list()') .match([ node(nodeName), - relation( - 'out', - 'scriptureRangeRel', - dynamicRel ? undefined : relationName, - ACTIVE, - ), + relation('out', 'scriptureRangeRel', dynamicRel ? undefined : relationName, ACTIVE), node('scriptureRange', 'ScriptureRange'), ]) - .apply((q) => - dynamicRel - ? q.raw(`WHERE type(scriptureRangeRel) = ${relationName}`) - : q, - ) + .apply((q) => (dynamicRel ? q.raw(`WHERE type(scriptureRangeRel) = ${relationName}`) : q)) .return<{ scriptureReferences: DbScriptureReferences }>( collect('scriptureRange').as(outVar), ); diff --git a/src/components/scripture/scripture-reference.service.ts b/src/components/scripture/scripture-reference.service.ts index 34e3869901..cb13055505 100644 --- a/src/components/scripture/scripture-reference.service.ts +++ b/src/components/scripture/scripture-reference.service.ts @@ -36,16 +36,9 @@ export class ScriptureReferenceService { return; } - const rel = options.isOverriding - ? 'scriptureReferencesOverride' - : 'scriptureReferences'; + const rel = options.isOverriding ? 'scriptureReferencesOverride' : 'scriptureReferences'; - await this.repo.update( - options.isOverriding, - producibleId, - scriptureRefs, - rel, - ); + await this.repo.update(options.isOverriding, producibleId, scriptureRefs, rel); if (scriptureRefs !== null) { for (const sr of scriptureRefs) { diff --git a/src/components/scripture/verse-equivalents.ts b/src/components/scripture/verse-equivalents.ts index 6734b7ecfe..7734675cfc 100644 --- a/src/components/scripture/verse-equivalents.ts +++ b/src/components/scripture/verse-equivalents.ts @@ -1,17 +1,10 @@ -import { - Book, - mergeVerseRanges, - type Verse, - type VerseLike, -} from '@seedcompany/scripture'; +import { Book, mergeVerseRanges, type Verse, type VerseLike } from '@seedcompany/scripture'; import { sum } from 'lodash'; import { type Range } from '~/common'; import { difficultyFactorOfBook } from './book-difficulty-factor'; import { ScriptureRange, type UnspecifiedScripturePortion } from './dto'; -export const getTotalVerseEquivalents = ( - ...refs: ReadonlyArray> -) => { +export const getTotalVerseEquivalents = (...refs: ReadonlyArray>) => { const verses = mergeVerseRanges(refs) .flatMap((range) => [...splitRangeByBook(range)]) .map((range) => { @@ -21,9 +14,7 @@ export const getTotalVerseEquivalents = ( return sum(verses); }; -export const getVerseEquivalentsFromUnspecified = ( - portion: UnspecifiedScripturePortion, -) => { +export const getVerseEquivalentsFromUnspecified = (portion: UnspecifiedScripturePortion) => { const factor = difficultyFactorOfBook(Book.named(portion.book)); return factor * portion.totalVerses; }; diff --git a/src/components/search/dto/search-results.dto.ts b/src/components/search/dto/search-results.dto.ts index 23034e987e..6269559065 100644 --- a/src/components/search/dto/search-results.dto.ts +++ b/src/components/search/dto/search-results.dto.ts @@ -89,9 +89,7 @@ export type SearchableMap = { >; }; -export const SearchResultTypes = setOf( - entries(publicSearchable).map(([k]) => k), -); +export const SearchResultTypes = setOf(entries(publicSearchable).map(([k]) => k)); // __typename is a GQL thing to identify type at runtime // It makes sense to use this key to not conflict with actual properties and @@ -106,13 +104,10 @@ export type SearchResult = SearchResultMap[keyof SearchableMap]; export const SearchResult = createUnionType({ name: 'SearchResult', types: () => uniq(Object.values(searchable)), - resolveType: (value: SearchResult) => - simpleSwitch(value.__typename, searchable), + resolveType: (value: SearchResult) => simpleSwitch(value.__typename, searchable), }); -export type SearchType = - | keyof typeof publicSearchable - | keyof typeof searchableAbstracts; +export type SearchType = keyof typeof publicSearchable | keyof typeof searchableAbstracts; // Don't use outside of defining GQL schema export type GqlSearchType = EnumType; diff --git a/src/components/search/dto/search.dto.ts b/src/components/search/dto/search.dto.ts index cbfa17307b..50dbe9ef10 100644 --- a/src/components/search/dto/search.dto.ts +++ b/src/components/search/dto/search.dto.ts @@ -1,10 +1,6 @@ import { Field, InputType, ObjectType } from '@nestjs/graphql'; import { PaginationInput } from '~/common'; -import { - GqlSearchType, - SearchResult, - type SearchType, -} from './search-results.dto'; +import { GqlSearchType, SearchResult, type SearchType } from './search-results.dto'; @InputType() export class SearchInput extends PaginationInput { diff --git a/src/components/search/search.repository.ts b/src/components/search/search.repository.ts index ae6a365f20..8fb56ea291 100644 --- a/src/components/search/search.repository.ts +++ b/src/components/search/search.repository.ts @@ -2,11 +2,7 @@ import { Injectable } from '@nestjs/common'; import { node, relation } from 'cypher-query-builder'; import { type Merge } from 'type-fest'; import { CommonRepository, OnIndex, type OnIndexParams } from '~/core/database'; -import { - ACTIVE, - escapeLuceneSyntax, - FullTextIndex, -} from '~/core/database/query'; +import { ACTIVE, escapeLuceneSyntax, FullTextIndex } from '~/core/database/query'; import { type BaseNode } from '~/core/database/results'; import type { ResourceMap } from '~/core/resources'; import { type SearchInput } from './dto'; @@ -46,11 +42,9 @@ export class SearchRepository extends CommonRepository { ) .apply((q) => input.type - ? q - .with(['node', 'matchedProps']) - .raw('WHERE any(l in labels(node) where l in $types)', { - types: input.type, - }) + ? q.with(['node', 'matchedProps']).raw('WHERE any(l in labels(node) where l in $types)', { + types: input.type, + }) : q, ) .return<{ diff --git a/src/components/search/search.service.ts b/src/components/search/search.service.ts index 81cbf80005..058f304dbe 100644 --- a/src/components/search/search.service.ts +++ b/src/components/search/search.service.ts @@ -3,11 +3,7 @@ import { isNotNil, setHas, setOf } from '@seedcompany/common'; import { uniqBy } from 'lodash'; import type { ValueOf } from 'type-fest'; import { type ID, NotFoundException, ServerException } from '~/common'; -import { - type ResourceMap, - ResourceResolver, - ResourcesHost, -} from '~/core/resources'; +import { type ResourceMap, ResourceResolver, ResourcesHost } from '~/core/resources'; import { Privileges } from '../authorization'; import { LanguageService } from '../language'; import { PartnerService } from '../partner'; @@ -69,13 +65,10 @@ export class SearchService { input.type .flatMap((type) => { const resource = this.resources.enhance(type); - const implementations = - this.resources.getImplementations(resource); + const implementations = this.resources.getImplementations(resource); return [resource, ...implementations]; }) - .flatMap((type) => - setHas(SearchResultTypes, type.name) ? type.name : [], - ), + .flatMap((type) => (setHas(SearchResultTypes, type.name) ? type.name : [])), ) : // if a type filter isn't specified default to all types SearchResultTypes; @@ -138,27 +131,22 @@ export class SearchService { : []; }) // Do hydration data loading for each identified resource. - .map( - async ({ type, id, matchedProps }): Promise => { - const hydrator = this.hydrate(type); - const hydrated = await hydrator(id); - if ( - !hydrated || - !(hydrated.__typename in this.resources.getEnhancedMap()) - ) { - return null; - } + .map(async ({ type, id, matchedProps }): Promise => { + const hydrator = this.hydrate(type); + const hydrated = await hydrator(id); + if (!hydrated || !(hydrated.__typename in this.resources.getEnhancedMap())) { + return null; + } - const resource = this.resources.getByName(hydrated.__typename); - const perms = this.privileges.for(resource, hydrated).all; - return matchedProps.some((key) => - // @ts-expect-error strict typing is hard for this dynamic use case. - key in perms ? perms[key].read : true, - ) - ? hydrated - : null; - }, - ), + const resource = this.resources.getByName(hydrated.__typename); + const perms = this.privileges.for(resource, hydrated).all; + return matchedProps.some((key) => + // @ts-expect-error strict typing is hard for this dynamic use case. + key in perms ? perms[key].read : true, + ) + ? hydrated + : null; + }), ); const hydrated = maybeHydrated.filter(isNotNil); @@ -175,11 +163,8 @@ export class SearchService { } private hydrate(type: K) { - return async ( - ...args: Parameters> - ): Promise => { - const hydrator = - type in this.customHydrators ? this.customHydrators[type] : undefined; + return async (...args: Parameters>): Promise => { + const hydrator = type in this.customHydrators ? this.customHydrators[type] : undefined; try { const obj = hydrator ? await hydrator(...args) diff --git a/src/components/story/story.gel.repository.ts b/src/components/story/story.gel.repository.ts index 789973f704..e2edf0225f 100644 --- a/src/components/story/story.gel.repository.ts +++ b/src/components/story/story.gel.repository.ts @@ -17,16 +17,13 @@ export class StoryGelRepository implements PublicOf { async create(input: CreateStory): Promise> { - const query = e.params( - { name: e.str, scripture: e.optional(scripture.type) }, - ($) => { - const created = e.insert(this.resource.db, { - name: $.name, - scripture: scripture.insert($.scripture), - }); - return e.select(created, this.hydrate); - }, - ); + const query = e.params({ name: e.str, scripture: e.optional(scripture.type) }, ($) => { + const created = e.insert(this.resource.db, { + name: $.name, + scripture: scripture.insert($.scripture), + }); + return e.select(created, this.hydrate); + }); return await this.db.run(query, { name: input.name, scripture: scripture.valueOptional(input.scriptureReferences), diff --git a/src/components/story/story.repository.ts b/src/components/story/story.repository.ts index 7bdb2c876f..6d7d25d9bb 100644 --- a/src/components/story/story.repository.ts +++ b/src/components/story/story.repository.ts @@ -10,23 +10,9 @@ import { type UnsecuredDto, } from '~/common'; import { type DbTypeOf, DtoRepository } from '~/core/database'; -import { - createNode, - matchProps, - merge, - paginate, - sorting, -} from '~/core/database/query'; -import { - ScriptureReferenceRepository, - ScriptureReferenceService, -} from '../scripture'; -import { - type CreateStory, - Story, - type StoryListInput, - type UpdateStory, -} from './dto'; +import { createNode, matchProps, merge, paginate, sorting } from '~/core/database/query'; +import { ScriptureReferenceRepository, ScriptureReferenceService } from '../scripture'; +import { type CreateStory, Story, type StoryListInput, type UpdateStory } from './dto'; @Injectable() export class StoryRepository extends DtoRepository(Story) { @@ -39,10 +25,7 @@ export class StoryRepository extends DtoRepository(Story) { async create(input: CreateStory) { if (!(await this.isUnique(input.name))) { - throw new DuplicateException( - 'story.name', - 'Story with this name already exists.', - ); + throw new DuplicateException('story.name', 'Story with this name already exists.'); } const initialProps = { @@ -59,15 +42,10 @@ export class StoryRepository extends DtoRepository(Story) { throw new CreationFailed(Story); } - await this.scriptureRefsService.create( - result.id, - input.scriptureReferences, - ); + await this.scriptureRefsService.create(result.id, input.scriptureReferences); return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Story) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Story) : e; }); } @@ -84,15 +62,11 @@ export class StoryRepository extends DtoRepository(Story) { return (await super.readOne(id)) as UnsecuredDto; } - async readMany( - ids: readonly ID[], - ): Promise>> { + async readMany(ids: readonly ID[]): Promise>> { const items = await super.readMany(ids); return items.map((r) => ({ ...r, - scriptureReferences: this.scriptureRefsService.parseList( - r.scriptureReferences, - ), + scriptureReferences: this.scriptureRefsService.parseList(r.scriptureReferences), })); } @@ -110,9 +84,7 @@ export class StoryRepository extends DtoRepository(Story) { ...result!, items: result!.items.map((r) => ({ ...r, - scriptureReferences: this.scriptureRefsService.parseList( - r.scriptureReferences, - ), + scriptureReferences: this.scriptureRefsService.parseList(r.scriptureReferences), })), }; } diff --git a/src/components/story/story.resolver.ts b/src/components/story/story.resolver.ts index cec59bc2b8..aaaec43af4 100644 --- a/src/components/story/story.resolver.ts +++ b/src/components/story/story.resolver.ts @@ -43,9 +43,7 @@ export class StoryResolver { @Mutation(() => CreateStoryOutput, { description: 'Create a story', }) - async createStory( - @Args('input') { story: input }: CreateStoryInput, - ): Promise { + async createStory(@Args('input') { story: input }: CreateStoryInput): Promise { const story = await this.storyService.create(input); return { story }; } @@ -53,9 +51,7 @@ export class StoryResolver { @Mutation(() => UpdateStoryOutput, { description: 'Update a story', }) - async updateStory( - @Args('input') { story: input }: UpdateStoryInput, - ): Promise { + async updateStory(@Args('input') { story: input }: UpdateStoryInput): Promise { const story = await this.storyService.update(input); return { story }; } diff --git a/src/components/story/story.service.ts b/src/components/story/story.service.ts index 6e4209a5fa..7df8864aad 100644 --- a/src/components/story/story.service.ts +++ b/src/components/story/story.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - type ID, - type ObjectView, - ServerException, - type UnsecuredDto, -} from '~/common'; +import { type ID, type ObjectView, ServerException, type UnsecuredDto } from '~/common'; import { HandleIdLookup } from '~/core'; import { ifDiff } from '~/core/database/changes'; import { Privileges } from '../authorization'; @@ -20,10 +15,7 @@ import { StoryRepository } from './story.repository'; @Injectable() export class StoryService { - constructor( - private readonly privileges: Privileges, - private readonly repo: StoryRepository, - ) {} + constructor(private readonly privileges: Privileges, private readonly repo: StoryRepository) {} async create(input: CreateStory): Promise { const dto = await this.repo.create(input); diff --git a/src/components/timezone/timezone.resolver.ts b/src/components/timezone/timezone.resolver.ts index 0727772120..e88c695e8a 100644 --- a/src/components/timezone/timezone.resolver.ts +++ b/src/components/timezone/timezone.resolver.ts @@ -25,9 +25,7 @@ export class TimeZoneResolver { } @Query(() => IanaCountry, { nullable: true }) - async ianaCountry( - @Args('code') code: string, - ): Promise { + async ianaCountry(@Args('code') code: string): Promise { const countries = await this.service.countries(); return countries[code]; } diff --git a/src/components/user/assignable-roles.resolver.ts b/src/components/user/assignable-roles.resolver.ts index c5b4eb1932..50ced094aa 100644 --- a/src/components/user/assignable-roles.resolver.ts +++ b/src/components/user/assignable-roles.resolver.ts @@ -7,8 +7,7 @@ export class AssignableRolesResolver { constructor(private readonly service: UserService) {} @ResolveField(() => [Role], { - description: - 'All of the roles that _you_ have permission to assign to this user', + description: 'All of the roles that _you_ have permission to assign to this user', }) async assignableRoles() { return [...this.service.getAssignableRoles()]; diff --git a/src/components/user/dto/user-status.enum.ts b/src/components/user/dto/user-status.enum.ts index f49a3cb2f2..42a5e2d5d0 100644 --- a/src/components/user/dto/user-status.enum.ts +++ b/src/components/user/dto/user-status.enum.ts @@ -1,10 +1,5 @@ import { ObjectType } from '@nestjs/graphql'; -import { - type EnumType, - makeEnum, - SecuredEnum, - SecuredProperty, -} from '~/common'; +import { type EnumType, makeEnum, SecuredEnum, SecuredProperty } from '~/common'; export type UserStatus = EnumType; export const UserStatus = makeEnum({ diff --git a/src/components/user/education/dto/education.dto.ts b/src/components/user/education/dto/education.dto.ts index 1dba04ef48..5797b03a63 100644 --- a/src/components/user/education/dto/education.dto.ts +++ b/src/components/user/education/dto/education.dto.ts @@ -14,14 +14,7 @@ import { RegisterResource } from '~/core/resources'; export type Degree = EnumType; export const Degree = makeEnum({ name: 'Degree', - values: [ - 'Primary', - 'Secondary', - 'Associates', - 'Bachelors', - 'Masters', - 'Doctorate', - ], + values: ['Primary', 'Secondary', 'Associates', 'Bachelors', 'Masters', 'Doctorate'], }); @ObjectType({ diff --git a/src/components/user/education/dto/list-education.dto.ts b/src/components/user/education/dto/list-education.dto.ts index 5dd37c603c..07a543e723 100644 --- a/src/components/user/education/dto/list-education.dto.ts +++ b/src/components/user/education/dto/list-education.dto.ts @@ -14,9 +14,7 @@ export abstract class EducationFilters { } @InputType() -export class EducationListInput extends SortablePaginationInput< - keyof Education ->({ +export class EducationListInput extends SortablePaginationInput({ defaultSort: 'institution', }) { @FilterField(() => EducationFilters, { internal: true }) diff --git a/src/components/user/education/education.loader.ts b/src/components/user/education/education.loader.ts index 1cbbc92deb..bfbbf6b034 100644 --- a/src/components/user/education/education.loader.ts +++ b/src/components/user/education/education.loader.ts @@ -4,9 +4,7 @@ import { Education } from './dto'; import { EducationService } from './education.service'; @LoaderFactory(() => Education) -export class EducationLoader - implements DataLoaderStrategy> -{ +export class EducationLoader implements DataLoaderStrategy> { constructor(private readonly educations: EducationService) {} async loadMany(ids: ReadonlyArray>) { diff --git a/src/components/user/education/education.repository.ts b/src/components/user/education/education.repository.ts index 2f48e4eb63..5e6649efd0 100644 --- a/src/components/user/education/education.repository.ts +++ b/src/components/user/education/education.repository.ts @@ -1,19 +1,8 @@ import { Injectable } from '@nestjs/common'; import { node, relation } from 'cypher-query-builder'; -import { - CreationFailed, - type ID, - NotFoundException, - ReadAfterCreationFailed, -} from '~/common'; +import { CreationFailed, type ID, NotFoundException, ReadAfterCreationFailed } from '~/common'; import { DtoRepository } from '~/core/database'; -import { - ACTIVE, - createNode, - createRelationships, - paginate, - sorting, -} from '~/core/database/query'; +import { ACTIVE, createNode, createRelationships, paginate, sorting } from '~/core/database/query'; import { type CreateEducation, Education, @@ -45,9 +34,7 @@ export class EducationRepository extends DtoRepository(Education) { throw new CreationFailed(Education); } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Education) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Education) : e; }); } diff --git a/src/components/user/fullName.ts b/src/components/user/fullName.ts index 97ceceb065..2f259b8a2c 100644 --- a/src/components/user/fullName.ts +++ b/src/components/user/fullName.ts @@ -3,23 +3,14 @@ import { type User } from './dto'; export const fullName = ( user: Partial< - Pick< - User, - 'realFirstName' | 'realLastName' | 'displayFirstName' | 'displayLastName' - > + Pick >, ) => { - const realName = cleanJoin(' ', [ - user.realFirstName?.value, - user.realLastName?.value, - ]); + const realName = cleanJoin(' ', [user.realFirstName?.value, user.realLastName?.value]); if (realName) { return realName; } - const displayName = cleanJoin(' ', [ - user.displayFirstName?.value, - user.displayLastName?.value, - ]); + const displayName = cleanJoin(' ', [user.displayFirstName?.value, user.displayLastName?.value]); if (displayName) { return displayName; } diff --git a/src/components/user/known-language.repository.ts b/src/components/user/known-language.repository.ts index 84bf8c706b..999bad358d 100644 --- a/src/components/user/known-language.repository.ts +++ b/src/components/user/known-language.repository.ts @@ -8,11 +8,7 @@ import { KnownLanguage, type ModifyKnownLanguageArgs } from './dto'; @Injectable() export class KnownLanguageRepository extends DtoRepository(KnownLanguage) { - async create({ - userId, - languageId, - languageProficiency, - }: ModifyKnownLanguageArgs) { + async create({ userId, languageId, languageProficiency }: ModifyKnownLanguageArgs) { await this.delete({ userId, languageId, languageProficiency }); await this.db @@ -31,11 +27,7 @@ export class KnownLanguageRepository extends DtoRepository(KnownLanguage) { .run(); } - async delete({ - userId, - languageId, - languageProficiency, - }: ModifyKnownLanguageArgs) { + async delete({ userId, languageId, languageProficiency }: ModifyKnownLanguageArgs) { await this.db .query() .matchNode('user', 'User', { id: userId }) @@ -66,10 +58,7 @@ export class KnownLanguageRepository extends DtoRepository(KnownLanguage) { ]) .with('collect(distinct user) as users, node, knownLanguageRel') .raw(`unwind users as user`) - .return([ - 'knownLanguageRel.value as proficiency', - 'node.id as language', - ]) + .return(['knownLanguageRel.value as proficiency', 'node.id as language']) .run(); return results; } diff --git a/src/components/user/system-agent.neo4j.repository.ts b/src/components/user/system-agent.neo4j.repository.ts index 9151d83eaf..edc1f8990e 100644 --- a/src/components/user/system-agent.neo4j.repository.ts +++ b/src/components/user/system-agent.neo4j.repository.ts @@ -24,9 +24,7 @@ export class SystemAgentNeo4jRepository extends SystemAgentRepository { 'agent.roles': roles ?? [], }, }) - .return<{ agent: SystemAgent }>( - merge('agent', { __typename: '"SystemAgent"' }).as('agent'), - ) + .return<{ agent: SystemAgent }>(merge('agent', { __typename: '"SystemAgent"' }).as('agent')) .first(); return res!.agent; } diff --git a/src/components/user/system-agent.repository.ts b/src/components/user/system-agent.repository.ts index 9af2ca1ef4..bbef0d3511 100644 --- a/src/components/user/system-agent.repository.ts +++ b/src/components/user/system-agent.repository.ts @@ -25,8 +25,5 @@ export abstract class SystemAgentRepository { return await this.upsertAgent('External Mailing Group', ['Leadership']); } - protected abstract upsertAgent( - name: string, - roles?: readonly Role[], - ): Promise; + protected abstract upsertAgent(name: string, roles?: readonly Role[]): Promise; } diff --git a/src/components/user/unavailability/dto/list-unavailabilities.dto.ts b/src/components/user/unavailability/dto/list-unavailabilities.dto.ts index 8ba196ac22..2d9993e34e 100644 --- a/src/components/user/unavailability/dto/list-unavailabilities.dto.ts +++ b/src/components/user/unavailability/dto/list-unavailabilities.dto.ts @@ -15,9 +15,7 @@ export abstract class UnavailabilityFilters { } @InputType() -export class UnavailabilityListInput extends SortablePaginationInput< - keyof Unavailability ->({ +export class UnavailabilityListInput extends SortablePaginationInput({ defaultSort: 'start', defaultOrder: Order.DESC, }) { @@ -30,6 +28,4 @@ export class UnavailabilityListOutput extends PaginatedList(Unavailability) {} @ObjectType({ description: SecuredList.descriptionFor('unavailabilities'), }) -export abstract class SecuredUnavailabilityList extends SecuredList( - Unavailability, -) {} +export abstract class SecuredUnavailabilityList extends SecuredList(Unavailability) {} diff --git a/src/components/user/unavailability/unavailability.repository.ts b/src/components/user/unavailability/unavailability.repository.ts index 614f8ebcff..4bc79e84d4 100644 --- a/src/components/user/unavailability/unavailability.repository.ts +++ b/src/components/user/unavailability/unavailability.repository.ts @@ -1,19 +1,8 @@ import { Injectable } from '@nestjs/common'; import { node, relation } from 'cypher-query-builder'; -import { - CreationFailed, - type ID, - NotFoundException, - ReadAfterCreationFailed, -} from '~/common'; +import { CreationFailed, type ID, NotFoundException, ReadAfterCreationFailed } from '~/common'; import { DtoRepository } from '~/core/database'; -import { - ACTIVE, - createNode, - createRelationships, - paginate, - sorting, -} from '~/core/database/query'; +import { ACTIVE, createNode, createRelationships, paginate, sorting } from '~/core/database/query'; import { type CreateUnavailability, Unavailability, @@ -43,9 +32,7 @@ export class UnavailabilityRepository extends DtoRepository(Unavailability) { throw new CreationFailed(Unavailability); } return await this.readOne(result.id).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(Unavailability) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(Unavailability) : e; }); } diff --git a/src/components/user/unavailability/unavailability.resolver.ts b/src/components/user/unavailability/unavailability.resolver.ts index 82ff66d369..ca7e7ae1bf 100644 --- a/src/components/user/unavailability/unavailability.resolver.ts +++ b/src/components/user/unavailability/unavailability.resolver.ts @@ -69,9 +69,7 @@ export class UnavailabilityResolver { description: 'Delete an unavailability', deprecationReason: `This is unfinished functionality, don't use`, }) - async deleteUnavailability( - @IdArg() id: ID, - ): Promise { + async deleteUnavailability(@IdArg() id: ID): Promise { await this.service.delete(id); return { success: true }; } diff --git a/src/components/user/unavailability/unavailability.service.ts b/src/components/user/unavailability/unavailability.service.ts index 1d547bcb77..74a7554b1d 100644 --- a/src/components/user/unavailability/unavailability.service.ts +++ b/src/components/user/unavailability/unavailability.service.ts @@ -47,9 +47,7 @@ export class UnavailabilityService { const changes = this.repo.getActualChanges(unavailability, input); // TODO move this condition into policies if (!this.identity.isSelf(result.id)) { - this.privileges - .for(Unavailability, unavailability) - .verifyChanges(changes); + this.privileges.for(Unavailability, unavailability).verifyChanges(changes); } const updated = await this.repo.update({ id: input.id, ...changes }); return this.secure(updated); @@ -60,9 +58,7 @@ export class UnavailabilityService { await this.repo.deleteNode(ua); } - async list( - input: UnavailabilityListInput, - ): Promise { + async list(input: UnavailabilityListInput): Promise { const results = await this.repo.list(input); return { ...results, diff --git a/src/components/user/user.gel.repository.ts b/src/components/user/user.gel.repository.ts index f68b5e3e8a..63a4c7e836 100644 --- a/src/components/user/user.gel.repository.ts +++ b/src/components/user/user.gel.repository.ts @@ -27,16 +27,13 @@ export class UserGelRepository const res = await this.db.run(this.readManyActorsQuery, { ids }); return [...res.users, ...res.agents]; } - private readonly readManyActorsQuery = e.params( - { ids: e.array(e.uuid) }, - ({ ids }) => { - const actors = e.cast(e.Actor, e.array_unpack(ids)); - return e.select({ - users: e.select(actors.is(e.User), hydrateUser), - agents: e.select(actors.is(e.SystemAgent), hydrateSystemAgent), - }); - }, - ); + private readonly readManyActorsQuery = e.params({ ids: e.array(e.uuid) }, ({ ids }) => { + const actors = e.cast(e.Actor, e.array_unpack(ids)); + return e.select({ + users: e.select(actors.is(e.User), hydrateUser), + agents: e.select(actors.is(e.SystemAgent), hydrateSystemAgent), + }); + }); async doesEmailAddressExist(email: string) { const query = e.select(e.User, () => ({ @@ -58,16 +55,11 @@ export class UserGelRepository protected listFilters(user: ScopeOf, input: UserListInput) { if (!input.filter) return []; return [ - input.filter.pinned != null && - e.op(user.pinned, '=', input.filter.pinned), + input.filter.pinned != null && e.op(user.pinned, '=', input.filter.pinned), (input.filter.roles?.length ?? 0) > 0 && e.op( 'exists', - e.op( - user.roles, - 'intersect', - e.cast(e.Role, e.set(...input.filter.roles!)), - ), + e.op(user.roles, 'intersect', e.cast(e.Role, e.set(...input.filter.roles!))), ), // TODO: title fuzzy search // More filters here when needed... diff --git a/src/components/user/user.repository.ts b/src/components/user/user.repository.ts index cb113f5d9b..4fd5813d3e 100644 --- a/src/components/user/user.repository.ts +++ b/src/components/user/user.repository.ts @@ -63,9 +63,7 @@ export class UserRepository extends DtoRepository(User) { } private readonly roleProperties = (roles?: readonly Role[]) => - (roles || []).flatMap((role) => - property('roles', role, 'node', `role${role}`), - ); + (roles || []).flatMap((role) => property('roles', role, 'node', `role${role}`)); async create(input: CreatePerson) { const initialProps = { @@ -86,9 +84,7 @@ export class UserRepository extends DtoRepository(User) { .query() .apply(await createNode(User, { initialProps })) .apply((q) => - input.roles && input.roles.length > 0 - ? q.create([...this.roleProperties(input.roles)]) - : q, + input.roles && input.roles.length > 0 ? q.create([...this.roleProperties(input.roles)]) : q, ) .return<{ id: ID }>('node.id as id'); let result; @@ -96,11 +92,7 @@ export class UserRepository extends DtoRepository(User) { result = await query.first(); } catch (e) { if (e instanceof UniquenessError && e.label === 'EmailAddress') { - throw new DuplicateException( - 'person.email', - 'Email address is already in use', - e, - ); + throw new DuplicateException('person.email', 'Email address is already in use', e); } throw new CreationFailed(User, { cause: e }); } @@ -129,11 +121,7 @@ export class UserRepository extends DtoRepository(User) { query .subQuery('node', (sub) => sub - .match([ - node('node'), - relation('out', '', 'roles', ACTIVE), - node('role', 'Property'), - ]) + .match([node('node'), relation('out', '', 'roles', ACTIVE), node('role', 'Property')]) .return('collect(role.value) as roles'), ) .apply(matchProps()) @@ -146,31 +134,20 @@ export class UserRepository extends DtoRepository(User) { ); } - private async updateEmail( - id: ID, - email: string | null | undefined, - ): Promise { + private async updateEmail(id: ID, email: string | null | undefined): Promise { const query = this.db .query() .matchNode('node', 'User', { id }) .apply(deactivateProperty({ resource: User, key: 'email' })) .apply((q) => - email - ? q.apply( - createProperty({ resource: User, key: 'email', value: email }), - ) - : q, + email ? q.apply(createProperty({ resource: User, key: 'email', value: email })) : q, ) .return('*'); try { await query.run(); } catch (e) { if (e instanceof UniquenessError && e.label === 'EmailAddress') { - throw new DuplicateException( - 'person.email', - 'Email address is already in use', - e, - ); + throw new DuplicateException('person.email', 'Email address is already in use', e); } throw e; } @@ -262,25 +239,14 @@ export class UserRepository extends DtoRepository(User) { return result?.dto ?? null; } - async assignOrganizationToUser({ - userId, - orgId, - primary, - }: AssignOrganizationToUser) { + async assignOrganizationToUser({ userId, orgId, primary }: AssignOrganizationToUser) { await this.db .query() - .match([ - [node('user', 'User', { id: userId })], - [node('org', 'Organization', { id: orgId })], - ]) + .match([[node('user', 'User', { id: userId })], [node('org', 'Organization', { id: orgId })]]) .subQuery((sub) => sub .with('user, org') - .match([ - node('user'), - relation('out', 'oldRel', 'organization', ACTIVE), - node('org'), - ]) + .match([node('user'), relation('out', 'oldRel', 'organization', ACTIVE), node('org')]) .setValues({ 'oldRel.active': false }) .return('oldRel') .union() @@ -318,14 +284,8 @@ export class UserRepository extends DtoRepository(User) { ]; const result = await this.db .query() - .match([ - [node('org', 'Organization', { id: orgId })], - [node('user', 'User', { id: userId })], - ]) - .create([ - userToOrg('organization'), - ...(primary ? [userToOrg('primaryOrganization')] : []), - ]) + .match([[node('org', 'Organization', { id: orgId })], [node('user', 'User', { id: userId })]]) + .create([userToOrg('organization'), ...(primary ? [userToOrg('primaryOrganization')] : [])]) .return('org.id') .first(); if (!result) { @@ -394,11 +354,7 @@ export const userFilters = filter.define(() => UserFilters, { name: filter.fullText({ index: () => NameIndex, matchToNode: (q) => - q.match([ - node('node', 'User'), - relation('out', '', undefined, ACTIVE), - node('match'), - ]), + q.match([node('node', 'User'), relation('out', '', undefined, ACTIVE), node('match')]), // Treat each word as a separate search term // Each word could point to a different node // i.e. "first last" @@ -412,16 +368,8 @@ export const userFilters = filter.define(() => UserFilters, { export const userSorters = defineSorters(User, { fullName: (query) => query - .match([ - node('node'), - relation('out', '', 'realFirstName', ACTIVE), - node('firstName'), - ]) - .match([ - node('node'), - relation('out', '', 'realLastName', ACTIVE), - node('lastName'), - ]) + .match([node('node'), relation('out', '', 'realFirstName', ACTIVE), node('firstName')]) + .match([node('node'), relation('out', '', 'realLastName', ACTIVE), node('lastName')]) .return(multiPropsAsSortString('firstName', 'lastName')), }); diff --git a/src/components/user/user.resolver.ts b/src/components/user/user.resolver.ts index d7e36dcf15..a8c3c5c7bc 100644 --- a/src/components/user/user.resolver.ts +++ b/src/components/user/user.resolver.ts @@ -1,12 +1,4 @@ -import { - Args, - ArgsType, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, ArgsType, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { firstLettersOfWords, type ID, @@ -21,10 +13,7 @@ import { Identity } from '~/core/authentication'; import { LocationLoader } from '../location'; import { LocationListInput, SecuredLocationList } from '../location/dto'; import { OrganizationLoader } from '../organization'; -import { - OrganizationListInput, - SecuredOrganizationList, -} from '../organization/dto'; +import { OrganizationListInput, SecuredOrganizationList } from '../organization/dto'; import { PartnerLoader } from '../partner'; import { PartnerListInput, SecuredPartnerList } from '../partner/dto'; import { TimeZoneService } from '../timezone'; @@ -50,10 +39,7 @@ import { EducationLoader } from './education'; import { EducationListInput, SecuredEducationList } from './education/dto'; import { fullName } from './fullName'; import { UnavailabilityLoader } from './unavailability'; -import { - SecuredUnavailabilityList, - UnavailabilityListInput, -} from './unavailability/dto'; +import { SecuredUnavailabilityList, UnavailabilityListInput } from './unavailability/dto'; import { UserLoader } from './user.loader'; import { UserService } from './user.service'; @@ -77,10 +63,7 @@ export class UserResolver { @Query(() => User, { description: 'Look up a user by its ID', }) - async user( - @Loader(UserLoader) users: LoaderOf, - @IdArg() id: ID, - ): Promise { + async user(@Loader(UserLoader) users: LoaderOf, @IdArg() id: ID): Promise { return await users.load(id); } @@ -198,9 +181,7 @@ export class UserResolver { } @ResolveField(() => [KnownLanguage]) - async knownLanguages( - @Parent() { id }: User, - ): Promise { + async knownLanguages(@Parent() { id }: User): Promise { return await this.userService.listKnownLanguages(id); } @@ -212,9 +193,7 @@ export class UserResolver { ): Promise { const userId = await this.userService.create(input); const user = await this.userService.readOne(userId).catch((e) => { - throw e instanceof NotFoundException - ? new ReadAfterCreationFailed(User) - : e; + throw e instanceof NotFoundException ? new ReadAfterCreationFailed(User) : e; }); return { user }; } @@ -222,9 +201,7 @@ export class UserResolver { @Mutation(() => UpdateUserOutput, { description: 'Update a user', }) - async updateUser( - @Args('input') { user: input }: UpdateUserInput, - ): Promise { + async updateUser(@Args('input') { user: input }: UpdateUserInput): Promise { const user = await this.userService.update(input); return { user }; } @@ -240,9 +217,7 @@ export class UserResolver { @Mutation(() => User, { description: 'Add a location to a user', }) - async addLocationToUser( - @Args() { userId, locationId }: ModifyLocationArgs, - ): Promise { + async addLocationToUser(@Args() { userId, locationId }: ModifyLocationArgs): Promise { await this.userService.addLocation(userId, locationId); return await this.userService.readOne(userId); } @@ -250,9 +225,7 @@ export class UserResolver { @Mutation(() => User, { description: 'Remove a location from a user', }) - async removeLocationFromUser( - @Args() { userId, locationId }: ModifyLocationArgs, - ): Promise { + async removeLocationFromUser(@Args() { userId, locationId }: ModifyLocationArgs): Promise { await this.userService.removeLocation(userId, locationId); return await this.userService.readOne(userId); } @@ -280,9 +253,7 @@ export class UserResolver { @Mutation(() => User, { description: 'Create known language to user', }) - async createKnownLanguage( - @Args() args: ModifyKnownLanguageArgs, - ): Promise { + async createKnownLanguage(@Args() args: ModifyKnownLanguageArgs): Promise { await this.userService.createKnownLanguage(args); return await this.userService.readOne(args.userId); } @@ -290,9 +261,7 @@ export class UserResolver { @Mutation(() => User, { description: 'Delete known language from user', }) - async deleteKnownLanguage( - @Args() args: ModifyKnownLanguageArgs, - ): Promise { + async deleteKnownLanguage(@Args() args: ModifyKnownLanguageArgs): Promise { await this.userService.deleteKnownLanguage(args); return await this.userService.readOne(args.userId); } diff --git a/src/components/user/user.service.ts b/src/components/user/user.service.ts index 5fe80a2ba5..d16f7f9d2e 100644 --- a/src/components/user/user.service.ts +++ b/src/components/user/user.service.ts @@ -15,15 +15,9 @@ import { IEventBus } from '~/core/events'; import { Privileges } from '../authorization'; import { AssignableRoles } from '../authorization/dto/assignable-roles.dto'; import { LocationService } from '../location'; -import { - type LocationListInput, - type SecuredLocationList, -} from '../location/dto'; +import { type LocationListInput, type SecuredLocationList } from '../location/dto'; import { OrganizationService } from '../organization'; -import { - type OrganizationListInput, - type SecuredOrganizationList, -} from '../organization/dto'; +import { type OrganizationListInput, type SecuredOrganizationList } from '../organization/dto'; import { PartnerService } from '../partner'; import { type PartnerListInput, type SecuredPartnerList } from '../partner/dto'; import { @@ -38,17 +32,11 @@ import { type UserListOutput, } from './dto'; import { EducationService } from './education'; -import { - type EducationListInput, - type SecuredEducationList, -} from './education/dto'; +import { type EducationListInput, type SecuredEducationList } from './education/dto'; import { UserUpdatedEvent } from './events/user-updated.event'; import { KnownLanguageRepository } from './known-language.repository'; import { UnavailabilityService } from './unavailability'; -import { - type SecuredUnavailabilityList, - type UnavailabilityListInput, -} from './unavailability/dto'; +import { type SecuredUnavailabilityList, type UnavailabilityListInput } from './unavailability/dto'; import { UserRepository } from './user.repository'; @Injectable() @@ -97,9 +85,7 @@ export class UserService { return users.map((dto) => this.secure(dto)); } - async readManyActors( - ids: readonly ID[], - ): Promise> { + async readManyActors(ids: readonly ID[]): Promise> { const users = await this.userRepo.readManyActors(ids); return users.map((dto) => dto.__typename === 'User' ? this.secure(dto) : (dto as SystemAgent), @@ -154,9 +140,7 @@ export class UserService { getAssignableRoles() { const privileges = this.privileges.for(AssignableRoles); - const assignableRoles = new Set( - [...Role].filter((role) => privileges.can('edit', role)), - ); + const assignableRoles = new Set([...Role].filter((role) => privileges.can('edit', role))); return assignableRoles; } @@ -172,10 +156,7 @@ export class UserService { ); } - async listEducations( - userId: ID, - input: EducationListInput, - ): Promise { + async listEducations(userId: ID, input: EducationListInput): Promise { const user = await this.userRepo.readOne(userId); const perms = this.privileges.for(User, user).all.education; @@ -220,10 +201,7 @@ export class UserService { }; } - async listPartners( - userId: ID, - input: PartnerListInput, - ): Promise { + async listPartners(userId: ID, input: PartnerListInput): Promise { const user = await this.userRepo.readOne(userId); const perms = this.privileges.for(User, user).all.partner; const result = await this.partners.list({ @@ -267,12 +245,7 @@ export class UserService { async addLocation(userId: ID, locationId: ID): Promise { try { - await this.locationService.addLocationToNode( - 'User', - userId, - 'locations', - locationId, - ); + await this.locationService.addLocationToNode('User', userId, 'locations', locationId); } catch (e) { throw new ServerException('Could not add location to user', e); } @@ -280,21 +253,13 @@ export class UserService { async removeLocation(userId: ID, locationId: ID): Promise { try { - await this.locationService.removeLocationFromNode( - 'User', - userId, - 'locations', - locationId, - ); + await this.locationService.removeLocationFromNode('User', userId, 'locations', locationId); } catch (e) { throw new ServerException('Could not remove location from user', e); } } - async listLocations( - user: User, - input: LocationListInput, - ): Promise { + async listLocations(user: User, input: LocationListInput): Promise { return await this.locationService.listLocationForResource( this.privileges.for(User, user).forEdge('locations'), user, @@ -333,9 +298,7 @@ export class UserService { await this.userRepo.assignOrganizationToUser(request); } - async removeOrganizationFromUser( - request: RemoveOrganizationFromUser, - ): Promise { + async removeOrganizationFromUser(request: RemoveOrganizationFromUser): Promise { await this.userRepo.removeOrganizationFromUser(request); } } diff --git a/src/components/workflow/define-workflow.ts b/src/components/workflow/define-workflow.ts index 321256eed0..431206d2b4 100644 --- a/src/components/workflow/define-workflow.ts +++ b/src/components/workflow/define-workflow.ts @@ -16,9 +16,7 @@ export const defineWorkflow = < const Name extends string, const Context, - const EventResource extends ResourceShape< - ReturnType['prototype'] - >, + const EventResource extends ResourceShape['prototype']>, const StateEnum extends MadeEnum, const State extends EnumType, >(input: { @@ -51,21 +49,11 @@ export const defineWorkflow = conditions: maybeMany(transition.conditions) ?? [], notifiers: maybeMany(transition.notifiers) ?? [], }; - const enhanced = enhancers.reduce( - (t, enhancer) => enhancer(t), - normalized, - ); + const enhanced = enhancers.reduce((t, enhancer) => enhancer(t), normalized); return enhanced; }); - const workflow: Workflow< - Name, - Context, - EventResource, - State, - StateEnum, - TransitionNames - > = { + const workflow: Workflow = { ...input, id: input.id as ID, transitions, @@ -88,9 +76,8 @@ export const defineWorkflow = } return transition; }, - pickNames: ( - ...transitions: Array> - ) => setOf(transitions.flat() as Names[]), + pickNames: (...transitions: Array>) => + setOf(transitions.flat() as Names[]), // type-only props event: undefined as any, state: undefined as any, @@ -118,11 +105,11 @@ export interface Workflow< State extends string = string, StateEnum extends MadeEnum = MadeEnum, TransitionNames extends string = string, - Transition extends InternalTransition< + Transition extends InternalTransition = InternalTransition< State, TransitionNames, Context - > = InternalTransition, + >, Transitions extends readonly Transition[] = readonly Transition[], > { readonly name: Name; @@ -141,9 +128,7 @@ export interface Workflow< /** type only */ readonly resolvedTransition: Omit & { to: State }; readonly transitionByKey: (key: ID) => Transition; - readonly transitionByName: ( - name: Names, - ) => Transition; + readonly transitionByName: (name: Names) => Transition; readonly pickNames: ( ...keys: Array> ) => ReadonlySet; diff --git a/src/components/workflow/dto/execute-transition.input.ts b/src/components/workflow/dto/execute-transition.input.ts index 0cf6cc4551..c0a1552c96 100644 --- a/src/components/workflow/dto/execute-transition.input.ts +++ b/src/components/workflow/dto/execute-transition.input.ts @@ -1,16 +1,8 @@ import { Field, InputType } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; -import { - type ID, - IdField, - type MadeEnum, - RichTextDocument, - RichTextField, -} from '~/common'; +import { type ID, IdField, type MadeEnum, RichTextDocument, RichTextField } from '~/common'; -export function ExecuteTransitionInput( - state: MadeEnum, -) { +export function ExecuteTransitionInput(state: MadeEnum) { @InputType({ isAbstract: true }) abstract class ExecuteTransitionInputClass { @IdField({ diff --git a/src/components/workflow/dto/serialized-workflow.dto.ts b/src/components/workflow/dto/serialized-workflow.dto.ts index fc77e26090..8595e7eb77 100644 --- a/src/components/workflow/dto/serialized-workflow.dto.ts +++ b/src/components/workflow/dto/serialized-workflow.dto.ts @@ -36,18 +36,11 @@ export class SerializedWorkflowTransitionDynamicTo extends DataObject { export const SerializedWorkflowTransitionTo = createUnionType({ name: 'WorkflowTransitionTo', - types: () => [ - SerializedWorkflowTransitionStaticTo, - SerializedWorkflowTransitionDynamicTo, - ], + types: () => [SerializedWorkflowTransitionStaticTo, SerializedWorkflowTransitionDynamicTo], resolveType: ( - value: - | SerializedWorkflowTransitionStaticTo - | SerializedWorkflowTransitionDynamicTo, + value: SerializedWorkflowTransitionStaticTo | SerializedWorkflowTransitionDynamicTo, ) => - 'state' in value - ? SerializedWorkflowTransitionStaticTo - : SerializedWorkflowTransitionDynamicTo, + 'state' in value ? SerializedWorkflowTransitionStaticTo : SerializedWorkflowTransitionDynamicTo, }); @ObjectType('WorkflowCondition') @@ -77,8 +70,7 @@ export class SerializedWorkflowTransitionPermission extends DataObject { role: Role; @Field({ - description: - 'The action for this permission is conditional, described by this field.', + description: 'The action for this permission is conditional, described by this field.', nullable: true, }) condition?: string; @@ -168,8 +160,7 @@ export class SerializedWorkflow extends DataObject { : { id: dynamicToId(transition.to), label: transition.to.description, - relatedStates: - transition.to.relatedStates?.map(serializeState) ?? [], + relatedStates: transition.to.relatedStates?.map(serializeState) ?? [], }, conditions: transition.conditions.map((condition) => ({ label: condition.description, diff --git a/src/components/workflow/dto/workflow-transition.dto.ts b/src/components/workflow/dto/workflow-transition.dto.ts index a01b12f6c2..ea75e06393 100644 --- a/src/components/workflow/dto/workflow-transition.dto.ts +++ b/src/components/workflow/dto/workflow-transition.dto.ts @@ -1,12 +1,6 @@ import { Field, ObjectType } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; -import { - type EnumType, - type ID, - IdField, - type MadeEnum, - makeEnum, -} from '~/common'; +import { type EnumType, type ID, IdField, type MadeEnum, makeEnum } from '~/common'; export type TransitionType = EnumType; export const TransitionType = makeEnum({ @@ -14,9 +8,7 @@ export const TransitionType = makeEnum({ values: ['Neutral', 'Approve', 'Reject'], }); -export function WorkflowTransition( - state: MadeEnum, -) { +export function WorkflowTransition(state: MadeEnum) { @ObjectType({ isAbstract: true }) abstract class WorkflowTransitionClass { @IdField({ diff --git a/src/components/workflow/permission.serializer.ts b/src/components/workflow/permission.serializer.ts index 3ffdcc123e..863221a564 100644 --- a/src/components/workflow/permission.serializer.ts +++ b/src/components/workflow/permission.serializer.ts @@ -8,11 +8,7 @@ import { type SerializedWorkflowTransitionPermission as SerializedTransitionPerm import { TransitionCondition } from './workflow.granter'; export const transitionPermissionSerializer = - ( - workflow: W, - privileges: Privileges, - identity: Identity, - ) => + (workflow: W, privileges: Privileges, identity: Identity) => (transition: W['transition']): readonly SerializedTransitionPermission[] => { const all = [...Role].flatMap((role) => { return identity.asRole(role, () => { @@ -35,17 +31,11 @@ export const transitionPermissionSerializer = }); // Remove roles that are never applicable. - const applicableRoles = setOf( - all.flatMap((p) => (p.readEvent || p.execute ? p.role : [])), - ); + const applicableRoles = setOf(all.flatMap((p) => (p.readEvent || p.execute ? p.role : []))); return all.filter((p) => applicableRoles.has(p.role)); }; -const resolve = ( - p: UserResourcePrivileges, - action: string, - transitionKey: ID, -) => +const resolve = (p: UserResourcePrivileges, action: string, transitionKey: ID) => p.resolve({ action, optimizeConditions: true, diff --git a/src/components/workflow/transitions/types.ts b/src/components/workflow/transitions/types.ts index 1f2658d519..5a7a4f48b4 100644 --- a/src/components/workflow/transitions/types.ts +++ b/src/components/workflow/transitions/types.ts @@ -16,11 +16,7 @@ export type TransitionInput = Merge< }> >; -export type InternalTransition< - State extends string, - Names extends string, - Context, -> = Merge< +export type InternalTransition = Merge< TransitionInput, Readonly<{ name: Names; diff --git a/src/components/workflow/workflow.flowchart.ts b/src/components/workflow/workflow.flowchart.ts index 3f13ef6cc8..910632fc93 100644 --- a/src/components/workflow/workflow.flowchart.ts +++ b/src/components/workflow/workflow.flowchart.ts @@ -1,11 +1,4 @@ -import { - cacheable, - cleanJoin, - cmpBy, - groupBy, - many, - simpleSwitch, -} from '@seedcompany/common'; +import { cacheable, cleanJoin, cmpBy, groupBy, many, simpleSwitch } from '@seedcompany/common'; import open from 'open'; import * as uuid from 'uuid'; import { deflateSync as deflate } from 'zlib'; @@ -21,8 +14,7 @@ export const WorkflowFlowchart = (workflow: () => W) => { /** Generate a flowchart in mermaid markup. */ generateMarkup() { - const rgbHexAddAlpha = (rgb: string, alpha: number) => - rgb + alpha.toString(16).slice(2, 4); + const rgbHexAddAlpha = (rgb: string, alpha: number) => rgb + alpha.toString(16).slice(2, 4); const colorStyle = (color: string) => ({ fill: color, stroke: color.slice(0, 7), @@ -37,9 +29,8 @@ export const WorkflowFlowchart = (workflow: () => W) => { stroke: '#ff0000', }, }; - const dynamicToId = cacheable( - new Map, string>(), - () => uuid.v1().replaceAll(/-/g, ''), + const dynamicToId = cacheable(new Map, string>(), () => + uuid.v1().replaceAll(/-/g, ''), ); const usedStates = new Set(); const useState = (state: W['state']) => { @@ -54,22 +45,18 @@ export const WorkflowFlowchart = (workflow: () => W) => { const transitions = this.workflow.transitions .toSorted( cmpBy((t) => { - const endState = - typeof t.to === 'string' ? t.to : t.to.relatedStates?.[0]; + const endState = typeof t.to === 'string' ? t.to : t.to.relatedStates?.[0]; return this.workflow.states.indexOf(endState!); }), ) .map((t) => { - const endStateId = - typeof t.to === 'string' ? useState(t.to) : dynamicToId(t.to); + const endStateId = typeof t.to === 'string' ? useState(t.to) : dynamicToId(t.to); const endId = transitionEndIds(t.label + '\0' + endStateId); const to = typeof t.to === 'string' ? `--> ${useState(t.to)}` : t.to.relatedStates - ? `-."${t.to.description}".-> ${t.to.relatedStates - .map(useState) - .join(' & ')}` + ? `-."${t.to.description}".-> ${t.to.relatedStates.map(useState).join(' & ')}` : `--> ${dynamicToId(t.to)}`; const endHalf = `${endId}{{ ${t.label} }}:::${t.type} ${to}`; @@ -77,20 +64,17 @@ export const WorkflowFlowchart = (workflow: () => W) => { t.conditions.length > 0 ? '--"' + t.conditions.map((c) => c.description).join('\\n') + '"' : ''; - const from = (t.from ? [...t.from].map(useState) : ['*(*)']).join( - ' & ', - ); + const from = (t.from ? [...t.from].map(useState) : ['*(*)']).join(' & '); const startHalf = `${from} ${conditions}--> ${endId}`; return { transition: t, startHalf, endHalf }; }); - const transitionStarts = groupBy(transitions, (t) => t.startHalf).map( - (ts) => - ts - .map((t) => '%% ' + t.transition.name) - .concat(ts[0].startHalf) - .join('\n'), + const transitionStarts = groupBy(transitions, (t) => t.startHalf).map((ts) => + ts + .map((t) => '%% ' + t.transition.name) + .concat(ts[0].startHalf) + .join('\n'), ); const transitionEnds = groupBy(transitions, (t) => t.endHalf).map((ts) => ts diff --git a/src/components/workflow/workflow.granter.ts b/src/components/workflow/workflow.granter.ts index 1714ec4fc2..a9441bd940 100644 --- a/src/components/workflow/workflow.granter.ts +++ b/src/components/workflow/workflow.granter.ts @@ -11,10 +11,9 @@ import { } from '../authorization/policy/conditions'; import { type Workflow } from './define-workflow'; -export function WorkflowEventGranter< - W extends Workflow, - EventClass extends W['eventResource'], ->(workflow: () => W) { +export function WorkflowEventGranter( + workflow: () => W, +) { type State = W['state']; type Names = W['transition']['name']; @@ -37,8 +36,7 @@ export function WorkflowEventGranter< * Can read & execute all transitions. */ get executeAll(): this { - return this.transitions(workflow().transitions.map((t) => t.name)) - .execute; + return this.transitions(workflow().transitions.map((t) => t.name)).execute; } /** @@ -48,9 +46,7 @@ export function WorkflowEventGranter< return this[action]('create'); } - isTransitions( - ...transitions: Array | (() => Iterable)> - ) { + isTransitions(...transitions: Array | (() => Iterable)>) { return TransitionCondition.fromName( workflow(), transitions.flatMap((t) => (typeof t === 'function' ? [...t()] : t)), @@ -83,14 +79,10 @@ interface TransitionCheck { endState?: W['state']; } -export class TransitionCondition - implements Condition -{ +export class TransitionCondition implements Condition { readonly allowedTransitionKeys; - protected constructor( - private readonly checks: ReadonlyArray>, - ) { + protected constructor(private readonly checks: ReadonlyArray>) { this.allowedTransitionKeys = new Set(checks.flatMap((c) => c.key)); } @@ -107,10 +99,7 @@ export class TransitionCondition ); } - static fromEndState( - workflow: W, - states: ReadonlyArray, - ) { + static fromEndState(workflow: W, states: ReadonlyArray) { const allowed = new Set(states); return new TransitionCondition( [...allowed].map((endState) => ({ @@ -138,20 +127,13 @@ export class TransitionCondition asCypherCondition(query: Query) { // TODO bypasses to statuses won't work with this. How should these be filtered? - const required = query.params.addParam( - this.allowedTransitionKeys, - 'allowedTransitions', - ); + const required = query.params.addParam(this.allowedTransitionKeys, 'allowedTransitions'); return `node.transition IN ${String(required)}`; } asEdgeQLCondition() { // TODO bypasses to statuses won't work with this. How should these be filtered? - const transitionAllowed = eqlInLiteralSet( - '.transitionKey', - this.allowedTransitionKeys, - 'uuid', - ); + const transitionAllowed = eqlInLiteralSet('.transitionKey', this.allowedTransitionKeys, 'uuid'); // If no transition then false return `((${transitionAllowed}) ?? false)`; } @@ -162,9 +144,7 @@ export class TransitionCondition conditions .flatMap((condition) => condition.checks) .map((check) => { - const key = check.name - ? `name:${check.name}` - : `state:${check.endState!}`; + const key = check.name ? `name:${check.name}` : `state:${check.endState!}`; return [key, check]; }), ).values(), @@ -176,8 +156,7 @@ export class TransitionCondition const checks = [...conditions[0].checks].filter((check1) => conditions.every((cond) => cond.checks.some( - (check2) => - check1.name === check2.name || check1.endState === check2.endState, + (check2) => check1.name === check2.name || check1.endState === check2.endState, ), ), ); @@ -194,12 +173,8 @@ export class TransitionCondition } const checkNames = this.checks.flatMap((c) => c.name ?? []); const checkEndStates = this.checks.flatMap((c) => c.endState ?? []); - const transitions = - checkNames.length > 0 ? render('Transitions', checkNames) : undefined; - const endStates = - checkEndStates.length > 0 - ? render('End States', checkEndStates) - : undefined; + const transitions = checkNames.length > 0 ? render('Transitions', checkNames) : undefined; + const endStates = checkEndStates.length > 0 ? render('End States', checkEndStates) : undefined; if (transitions && endStates) { return `(${transitions} OR ${endStates})`; } @@ -209,10 +184,7 @@ export class TransitionCondition const TransitionKey = Symbol('TransitionKey'); -export const withTransitionKey = ( - obj: T, - transitionKey: ID, -) => +export const withTransitionKey = (obj: T, transitionKey: ID) => Object.defineProperty(obj, TransitionKey, { value: transitionKey, enumerable: false, diff --git a/src/components/workflow/workflow.service.ts b/src/components/workflow/workflow.service.ts index 522b1ba737..6329d49d88 100644 --- a/src/components/workflow/workflow.service.ts +++ b/src/components/workflow/workflow.service.ts @@ -5,16 +5,11 @@ import { Identity } from '~/core/authentication'; import { Privileges } from '../authorization'; import { MissingContextException } from '../authorization/policy/conditions'; import { type Workflow } from './define-workflow'; -import { - type ExecuteTransitionInput as ExecuteTransitionInputFn, - SerializedWorkflow, -} from './dto'; +import { type ExecuteTransitionInput as ExecuteTransitionInputFn, SerializedWorkflow } from './dto'; import { transitionPermissionSerializer } from './permission.serializer'; import { withTransitionKey } from './workflow.granter'; -type ExecuteTransitionInput = ReturnType< - typeof ExecuteTransitionInputFn ->['prototype']; +type ExecuteTransitionInput = ReturnType['prototype']; export const WorkflowService = (workflow: () => W) => { @Injectable() @@ -46,18 +41,14 @@ export const WorkflowService = (workflow: () => W) => { let available = this.workflow.transitions; // Filter out non applicable transitions - available = available.filter((t) => - t.from ? t.from.has(currentState) : true, - ); + available = available.filter((t) => (t.from ? t.from.has(currentState) : true)); // Filter out transitions without authorization to execute const p = this.privileges.for(this.workflow.eventResource); available = available.filter((t) => // I don't have a good way to type this right now. // Context usage is still fuzzy when conditions need different shapes. - p - .forContext(withTransitionKey(privilegeContext, t.key) as any) - .can('create'), + p.forContext(withTransitionKey(privilegeContext, t.key) as any).can('create'), ); // Resolve conditions & filter as needed @@ -65,8 +56,7 @@ export const WorkflowService = (workflow: () => W) => { const resolvedConditions = new Map( await Promise.all( [...new Set(conditions)].map( - async (condition) => - [condition, await condition.resolve(dynamicContext)] as const, + async (condition) => [condition, await condition.resolve(dynamicContext)] as const, ), ), ); @@ -89,9 +79,7 @@ export const WorkflowService = (workflow: () => W) => { }); // Resolve dynamic to steps - const dynamicTos = available.flatMap((t) => - typeof t.to !== 'string' ? t.to : [], - ); + const dynamicTos = available.flatMap((t) => (typeof t.to !== 'string' ? t.to : [])); const resolvedTos = new Map( await Promise.all( dynamicTos.map(async (to) => { @@ -118,9 +106,7 @@ export const WorkflowService = (workflow: () => W) => { } } - protected getBypassIfValid( - input: ExecuteTransitionInput, - ): W['state'] | undefined { + protected getBypassIfValid(input: ExecuteTransitionInput): W['state'] | undefined { // Verify transition key is valid if (input.transition) { this.workflow.transitionByKey(input.transition); @@ -140,11 +126,7 @@ export const WorkflowService = (workflow: () => W) => { serialize() { return SerializedWorkflow.from( this.workflow, - transitionPermissionSerializer( - this.workflow, - this.privileges, - this.identity, - ), + transitionPermissionSerializer(this.workflow, this.privileges, this.identity), ); } } diff --git a/src/core/authentication/authentication.gel.repository.ts b/src/core/authentication/authentication.gel.repository.ts index eacf6c6ec7..62ff9c9fa2 100644 --- a/src/core/authentication/authentication.gel.repository.ts +++ b/src/core/authentication/authentication.gel.repository.ts @@ -9,9 +9,7 @@ import { type Session } from './session/session.dto'; @Injectable() @DbTraceLayer.applyToClass() -export class AuthenticationGelRepository - implements PublicOf -{ +export class AuthenticationGelRepository implements PublicOf { private readonly db: Gel; constructor(db: Gel) { this.db = db.withOptions(disableAccessPolicies); @@ -37,9 +35,8 @@ export class AuthenticationGelRepository async saveSessionToken(token: string) { await this.db.run(this.saveSessionTokenQuery, { token }); } - private readonly saveSessionTokenQuery = e.params( - { token: e.str }, - ({ token }) => e.insert(e.Auth.Session, { token }), + private readonly saveSessionTokenQuery = e.params({ token: e.str }, ({ token }) => + e.insert(e.Auth.Session, { token }), ); async savePasswordHashOnUser(userId: ID, passwordHash: string) { @@ -52,30 +49,25 @@ export class AuthenticationGelRepository { userId: e.uuid, passwordHash: e.str }, ({ userId, passwordHash }) => { const user = e.cast(e.User, userId); - return e - .insert(e.Auth.Identity, { user, passwordHash }) - .unlessConflict((identity) => ({ - on: identity.user, - else: e.update(e.Auth.Identity, () => ({ - filter_single: { user }, - set: { passwordHash }, - })), - })); + return e.insert(e.Auth.Identity, { user, passwordHash }).unlessConflict((identity) => ({ + on: identity.user, + else: e.update(e.Auth.Identity, () => ({ + filter_single: { user }, + set: { passwordHash }, + })), + })); }, ); async getPasswordHash({ email }: LoginInput) { return await this.db.run(this.getPasswordHashQuery, { email }); } - private readonly getPasswordHashQuery = e.params( - { email: e.str }, - ({ email }) => { - const identity = e.select(e.Auth.Identity, (identity) => ({ - filter_single: e.op(identity.user.email, '=', email), - })); - return identity.passwordHash; - }, - ); + private readonly getPasswordHashQuery = e.params({ email: e.str }, ({ email }) => { + const identity = e.select(e.Auth.Identity, (identity) => ({ + filter_single: e.op(identity.user.email, '=', email), + })); + return identity.passwordHash; + }); async connectSessionToUser(input: LoginInput, session: Session): Promise { try { @@ -113,13 +105,11 @@ export class AuthenticationGelRepository async disconnectUserFromSession(token: string): Promise { await this.db.run(this.disconnectUserFromSessionQuery, { token }); } - private readonly disconnectUserFromSessionQuery = e.params( - { token: e.str }, - ({ token }) => - e.update(e.Auth.Session, () => ({ - filter_single: { token }, - set: { user: null }, - })), + private readonly disconnectUserFromSessionQuery = e.params({ token: e.str }, ({ token }) => + e.update(e.Auth.Session, () => ({ + filter_single: { token }, + set: { user: null }, + })), ); async resumeSession(token: string, impersonateeId?: ID) { @@ -147,13 +137,10 @@ export class AuthenticationGelRepository async rolesForUser(userId: ID) { return await this.db.run(this.rolesForUserQuery, { userId }); } - private readonly rolesForUserQuery = e.params( - { userId: e.uuid }, - ({ userId }) => { - const user = e.cast(e.User, userId); - return user.roles; - }, - ); + private readonly rolesForUserQuery = e.params({ userId: e.uuid }, ({ userId }) => { + const user = e.cast(e.User, userId); + return user.roles; + }); async getCurrentPasswordHash() { return await this.db.run(this.getCurrentPasswordHashQuery, {}); @@ -171,33 +158,27 @@ export class AuthenticationGelRepository passwordHash: newPasswordHash, }); } - private readonly updatePasswordQuery = e.params( - { passwordHash: e.str }, - ({ passwordHash }) => { - const user = e.global.currentUser; - const identity = e.assert_exists( - e.select(e.Auth.Identity, () => ({ - filter_single: { user }, - })), - ); - return e.update(identity, () => ({ - set: { passwordHash }, - })); - }, - ); + private readonly updatePasswordQuery = e.params({ passwordHash: e.str }, ({ passwordHash }) => { + const user = e.global.currentUser; + const identity = e.assert_exists( + e.select(e.Auth.Identity, () => ({ + filter_single: { user }, + })), + ); + return e.update(identity, () => ({ + set: { passwordHash }, + })); + }); async userByEmail(email: string) { return await this.db.run(this.userByEmailQuery, { email }); } - private readonly userByEmailQuery = e.params( - { email: e.str }, - ({ email }) => { - const user = e.select(e.User, () => ({ - filter_single: { email }, - })); - return user.id; - }, - ); + private readonly userByEmailQuery = e.params({ email: e.str }, ({ email }) => { + const user = e.select(e.User, () => ({ + filter_single: { email }, + })); + return user.id; + }); async doesEmailAddressExist(email: string) { return !!(await this.userByEmail(email)); @@ -214,20 +195,15 @@ export class AuthenticationGelRepository async findEmailToken(token: string) { return await this.db.run(this.findEmailTokenQuery, { token }); } - private readonly findEmailTokenQuery = e.params( - { token: e.str }, - ({ token }) => - e.select(e.Auth.EmailToken, (et) => ({ - ...et['*'], - createdOn: et.createdAt, // backwards compatibility - filter_single: { token }, - })), + private readonly findEmailTokenQuery = e.params({ token: e.str }, ({ token }) => + e.select(e.Auth.EmailToken, (et) => ({ + ...et['*'], + createdOn: et.createdAt, // backwards compatibility + filter_single: { token }, + })), ); - async updatePasswordViaEmailToken( - { email }: { email: string }, - passwordHash: string, - ) { + async updatePasswordViaEmailToken({ email }: { email: string }, passwordHash: string) { const userId = await this.userByEmail(email); await this.savePasswordHashOnUser(userId!, passwordHash); } @@ -235,12 +211,10 @@ export class AuthenticationGelRepository async removeAllEmailTokensForEmail(email: string) { await this.db.run(this.removeAllEmailTokensForEmailQuery, { email }); } - private readonly removeAllEmailTokensForEmailQuery = e.params( - { email: e.str }, - ({ email }) => - e.delete(e.Auth.EmailToken, (et) => ({ - filter: e.op(et.email, '=', email), - })), + private readonly removeAllEmailTokensForEmailQuery = e.params({ email: e.str }, ({ email }) => + e.delete(e.Auth.EmailToken, (et) => ({ + filter: e.op(et.email, '=', email), + })), ); async deactivateAllOtherSessions(session: Session) { @@ -253,11 +227,7 @@ export class AuthenticationGelRepository { userId: e.uuid, token: e.str }, ({ userId, token }) => e.update(e.Auth.Session, (s) => ({ - filter: e.op( - e.op(s.user.id, '=', userId), - 'and', - e.op(s.token, '!=', token), - ), + filter: e.op(e.op(s.user.id, '=', userId), 'and', e.op(s.token, '!=', token)), set: { user: null }, })), ); @@ -272,11 +242,7 @@ export class AuthenticationGelRepository { email: e.str, token: e.str }, ({ email, token }) => e.update(e.Auth.Session, (s) => ({ - filter: e.op( - e.op(s.user.email, '=', email), - 'and', - e.op(s.token, '!=', token), - ), + filter: e.op(e.op(s.user.email, '=', email), 'and', e.op(s.token, '!=', token)), set: { user: null }, })), ); diff --git a/src/core/authentication/authentication.repository.ts b/src/core/authentication/authentication.repository.ts index 31ed34e5eb..a574ca22df 100644 --- a/src/core/authentication/authentication.repository.ts +++ b/src/core/authentication/authentication.repository.ts @@ -3,12 +3,7 @@ import { node, relation } from 'cypher-query-builder'; import { DateTime } from 'luxon'; import { type ID, type Role, ServerException } from '~/common'; import { DatabaseService, DbTraceLayer, OnIndex } from '../database'; -import { - ACTIVE, - currentUser, - matchUserGloballyScopedRoles, - variable, -} from '../database/query'; +import { ACTIVE, currentUser, matchUserGloballyScopedRoles, variable } from '../database/query'; import { type LoginInput } from './dto'; import { type Session } from './session/session.dto'; @@ -174,26 +169,15 @@ export class AuthenticationRepository { const result = await this.db .query() .raw('MATCH (token:Token { active: true, value: $token })', { token }) - .optionalMatch([ - node('token'), - relation('in', '', 'token', ACTIVE), - node('user', 'User'), - ]) + .optionalMatch([node('token'), relation('in', '', 'token', ACTIVE), node('user', 'User')]) .apply(matchUserGloballyScopedRoles('user', 'roles')) .apply( impersonatee ? (q) => q.subQuery((sub) => sub - .optionalMatch( - node('impersonatee', 'User', { id: impersonatee }), - ) - .apply( - matchUserGloballyScopedRoles( - 'impersonatee', - 'impersonateeRoles', - ), - ) + .optionalMatch(node('impersonatee', 'User', { id: impersonatee })) + .apply(matchUserGloballyScopedRoles('impersonatee', 'impersonateeRoles')) .return('impersonateeRoles'), ) : null, @@ -202,11 +186,7 @@ export class AuthenticationRepository { userId: ID | null; roles: readonly Role[]; impersonateeRoles: readonly Role[] | null; - }>([ - 'user.id as userId', - 'roles', - impersonatee ? 'impersonateeRoles' : '', - ]) + }>(['user.id as userId', 'roles', impersonatee ? 'impersonateeRoles' : '']) .first(); return result ?? null; @@ -225,11 +205,7 @@ export class AuthenticationRepository { async getCurrentPasswordHash() { const result = await this.db .query() - .match([ - currentUser, - relation('out', '', 'password', ACTIVE), - node('password', 'Property'), - ]) + .match([currentUser, relation('out', '', 'password', ACTIVE), node('password', 'Property')]) .return('password.value as passwordHash') .asResult<{ passwordHash: string }>() .first(); @@ -239,11 +215,7 @@ export class AuthenticationRepository { async updatePassword(newPasswordHash: string): Promise { await this.db .query() - .match([ - currentUser, - relation('out', '', 'password', ACTIVE), - node('password', 'Property'), - ]) + .match([currentUser, relation('out', '', 'password', ACTIVE), node('password', 'Property')]) .setValues({ 'password.value': newPasswordHash, }) @@ -290,10 +262,7 @@ export class AuthenticationRepository { return result ?? null; } - async updatePasswordViaEmailToken( - { token, email }: EmailToken, - pash: string, - ): Promise { + async updatePasswordViaEmailToken({ token, email }: EmailToken, pash: string): Promise { await this.db .query() .raw( @@ -327,11 +296,7 @@ export class AuthenticationRepository { async deactivateAllOtherSessions(session: Session) { await this.db .query() - .match([ - currentUser, - relation('out', 'oldRel', 'token', ACTIVE), - node('token', 'Token'), - ]) + .match([currentUser, relation('out', 'oldRel', 'token', ACTIVE), node('token', 'Token')]) .raw('WHERE NOT token.value = $token', { token: session.token }) .setValues({ 'oldRel.active': false }) .run(); diff --git a/src/core/authentication/authentication.service.ts b/src/core/authentication/authentication.service.ts index 0d50896bdd..0dcf41cd62 100644 --- a/src/core/authentication/authentication.service.ts +++ b/src/core/authentication/authentication.service.ts @@ -79,10 +79,7 @@ export class AuthenticationService { throw new UnauthenticatedException('Invalid credentials'); } - const userId = await this.repo.connectSessionToUser( - input, - this.sessionHost.current, - ); + const userId = await this.repo.connectSessionToUser(input, this.sessionHost.current); if (!userId) { throw new ServerException('Login failed'); @@ -98,12 +95,8 @@ export class AuthenticationService { refresh && (await this.sessionManager.refreshCurrentSession()); } - async changePassword( - oldPassword: string, - newPassword: string, - ): Promise { - if (!oldPassword) - throw new InputException('Old Password Required', 'oldPassword'); + async changePassword(oldPassword: string, newPassword: string): Promise { + if (!oldPassword) throw new InputException('Old Password Required', 'oldPassword'); const hash = await this.repo.getCurrentPasswordHash(); @@ -145,10 +138,7 @@ export class AuthenticationService { const pash = await this.crypto.hash(password); await this.repo.updatePasswordViaEmailToken(emailToken, pash); - await this.repo.deactivateAllOtherSessionsByEmail( - emailToken.email, - this.sessionHost.current, - ); + await this.repo.deactivateAllOtherSessionsByEmail(emailToken.email, this.sessionHost.current); await this.repo.removeAllEmailTokensForEmail(emailToken.email); } } diff --git a/src/core/authentication/resolvers/login.resolver.ts b/src/core/authentication/resolvers/login.resolver.ts index 5fd0836693..f1875e1752 100644 --- a/src/core/authentication/resolvers/login.resolver.ts +++ b/src/core/authentication/resolvers/login.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { Loader, type LoaderOf } from '~/core'; import { UserLoader } from '../../../components/user'; diff --git a/src/core/authentication/resolvers/password.resolver.ts b/src/core/authentication/resolvers/password.resolver.ts index aa2b890326..fa144d6e6b 100644 --- a/src/core/authentication/resolvers/password.resolver.ts +++ b/src/core/authentication/resolvers/password.resolver.ts @@ -32,9 +32,7 @@ export class PasswordResolver { description: 'Forgot password; send password reset email', }) @AuthLevel('anonymous') - async forgotPassword( - @Args() { email }: ForgotPasswordArgs, - ): Promise { + async forgotPassword(@Args() { email }: ForgotPasswordArgs): Promise { await this.authentication.forgotPassword(email); return { success: true }; } @@ -46,9 +44,7 @@ export class PasswordResolver { `, }) @AuthLevel('anonymous') - async resetPassword( - @Args('input') input: ResetPasswordInput, - ): Promise { + async resetPassword(@Args('input') input: ResetPasswordInput): Promise { await this.authentication.resetPassword(input); return { success: true }; } diff --git a/src/core/authentication/resolvers/register.resolver.ts b/src/core/authentication/resolvers/register.resolver.ts index 26f9031a47..778b15c3e8 100644 --- a/src/core/authentication/resolvers/register.resolver.ts +++ b/src/core/authentication/resolvers/register.resolver.ts @@ -1,10 +1,4 @@ -import { - Args, - Mutation, - Parent, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Mutation, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { stripIndent } from 'common-tags'; import { Loader, type LoaderOf } from '~/core'; import { UserLoader } from '../../../components/user'; diff --git a/src/core/authentication/resolvers/session.resolver.ts b/src/core/authentication/resolvers/session.resolver.ts index bfb97a39bf..d7a05e7fc1 100644 --- a/src/core/authentication/resolvers/session.resolver.ts +++ b/src/core/authentication/resolvers/session.resolver.ts @@ -1,11 +1,4 @@ -import { - Args, - Context, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql'; +import { Args, Context, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { DateTime } from 'luxon'; import { type GqlContextType, ServerException } from '~/common'; import { ConfigService, Loader, type LoaderOf } from '~/core'; @@ -55,15 +48,11 @@ export class SessionResolver { if (browser) { const { name, expires, ...options } = this.config.sessionCookie(request); if (!response) { - throw new ServerException( - 'Cannot use cookie session without a response object', - ); + throw new ServerException('Cannot use cookie session without a response object'); } this.http.setCookie(response, name, session.token, { ...options, - expires: expires - ? DateTime.local().plus(expires).toJSDate() - : undefined, + expires: expires ? DateTime.local().plus(expires).toJSDate() : undefined, }); } @@ -76,8 +65,7 @@ export class SessionResolver { @ResolveField(() => User, { nullable: true, - description: - 'Only returned if there is a logged-in user tied to the current session.', + description: 'Only returned if there is a logged-in user tied to the current session.', }) async user( @Parent() output: SessionOutput, @@ -88,12 +76,9 @@ export class SessionResolver { @ResolveField(() => User, { nullable: true, - description: - 'The impersonator if the user is logged in and impersonating someone else', + description: 'The impersonator if the user is logged in and impersonating someone else', }) - async impersonator( - @Parent() { session }: SessionOutput, - ): Promise { + async impersonator(@Parent() { session }: SessionOutput): Promise { const { impersonator } = session; if (session.anonymous || !impersonator) { return null; diff --git a/src/core/authentication/session/session.host.ts b/src/core/authentication/session/session.host.ts index 7c1364fe7b..e2f78cc7b6 100644 --- a/src/core/authentication/session/session.host.ts +++ b/src/core/authentication/session/session.host.ts @@ -9,9 +9,7 @@ import { type Session } from './session.dto'; * A service holding the current session / user */ export class SessionHost implements OnModuleDestroy { - private readonly als = new AsyncLocalStorage< - BehaviorSubject - >(); + private readonly als = new AsyncLocalStorage>(); /** * Retrieve the current session. @@ -46,9 +44,7 @@ export class SessionHost implements OnModuleDestroy { get current$() { const subject = this.als.getStore(); if (!subject) { - throw new AsyncLocalStorageNoContextException( - 'A session context has not been declared', - ); + throw new AsyncLocalStorageNoContextException('A session context has not been declared'); } return subject; } @@ -66,14 +62,8 @@ export class SessionHost implements OnModuleDestroy { /** * Run a function with a given session. */ - withSession( - session: BehaviorSubject | Session | undefined, - fn: () => R, - ) { - const session$ = - session instanceof BehaviorSubject - ? session - : new BehaviorSubject(session); + withSession(session: BehaviorSubject | Session | undefined, fn: () => R) { + const session$ = session instanceof BehaviorSubject ? session : new BehaviorSubject(session); return this.als.run(session$, fn); } diff --git a/src/core/authentication/session/session.initiator.ts b/src/core/authentication/session/session.initiator.ts index 9f9a6afcc8..d975638576 100644 --- a/src/core/authentication/session/session.initiator.ts +++ b/src/core/authentication/session/session.initiator.ts @@ -39,10 +39,7 @@ export class SessionInitiator { if (!(exception instanceof UnauthenticatedException)) { throw exception; } - this.logger.debug( - 'Failed to use existing session token, creating new one.', - { exception }, - ); + this.logger.debug('Failed to use existing session token, creating new one.', { exception }); token = await this.sessionManager.createToken(); session = await this.sessionManager.resumeSession(token, impersonatee); } @@ -60,9 +57,7 @@ export class SessionInitiator { } private getToken(request: IRequest): string | null { - return ( - this.getTokenFromAuthHeader(request) ?? this.getTokenFromCookie(request) - ); + return this.getTokenFromAuthHeader(request) ?? this.getTokenFromCookie(request); } private getTokenFromAuthHeader(req: IRequest): string | null { @@ -85,9 +80,7 @@ export class SessionInitiator { private getImpersonatee(request: IRequest): Session['impersonatee'] { const user = request.headers?.['x-cord-impersonate-user'] as ID | undefined; if (user && !isIdLike(user)) { - throw new InputException( - `Invalid user ID given in "X-CORD-Impersonate-User" header`, - ); + throw new InputException(`Invalid user ID given in "X-CORD-Impersonate-User" header`); } const rawRoles = csvHeader(request?.headers?.['x-cord-impersonate-role']); @@ -106,9 +99,7 @@ const assertValidRole = (role: string): Role => { if (Role.has(role)) { return role; } - throw new InputException( - `Invalid role "${role}" from "X-CORD-Impersonate-Role" header`, - ); + throw new InputException(`Invalid role "${role}" from "X-CORD-Impersonate-Role" header`); }; function csvHeader(headerVal: Many | undefined) { diff --git a/src/core/authentication/session/session.interceptor.ts b/src/core/authentication/session/session.interceptor.ts index 1ea4fcb271..b7813be9ed 100644 --- a/src/core/authentication/session/session.interceptor.ts +++ b/src/core/authentication/session/session.interceptor.ts @@ -23,16 +23,12 @@ export class SessionInterceptor implements NestInterceptor { private readonly identity: Identity, ) {} - private readonly sessionByRequest = new AsyncLocalStorage< - SessionHost['current$'] - >(); + private readonly sessionByRequest = new AsyncLocalStorage(); @GlobalHttpHook() onRequest(...[_req, _reply, next]: Parameters) { // Create a holder to use later to declare the session after it is constructed - const sessionForTheRequest = new BehaviorSubject( - undefined, - ); + const sessionForTheRequest = new BehaviorSubject(undefined); // Store this as the current holder for the current request. // This is our private store, so the code below won't interfere with // a different SessionHost context. @@ -60,9 +56,7 @@ export class SessionInterceptor implements NestInterceptor { } const request = this.getRequest(executionContext); - const session = request - ? await this.sessionInitiator.resume(request) - : undefined; + const session = request ? await this.sessionInitiator.resume(request) : undefined; if (session) { session$.next(session); if (authLevel === 'authenticated') { @@ -76,8 +70,7 @@ export class SessionInterceptor implements NestInterceptor { private isMutation(executionContext: ExecutionContext) { switch (executionContext.getType()) { case 'graphql': { - const gqlExecutionContext = - GqlExecutionContext.create(executionContext); + const gqlExecutionContext = GqlExecutionContext.create(executionContext); const op = gqlExecutionContext.getInfo().operation; return op.operation === 'mutation'; } @@ -93,8 +86,7 @@ export class SessionInterceptor implements NestInterceptor { private getRequest(executionContext: ExecutionContext) { switch (executionContext.getType()) { case 'graphql': { - const gqlExecutionContext = - GqlExecutionContext.create(executionContext); + const gqlExecutionContext = GqlExecutionContext.create(executionContext); const ctx = gqlExecutionContext.getContext(); return ctx.request; } diff --git a/src/core/authentication/session/session.manager.ts b/src/core/authentication/session/session.manager.ts index bbcfb25a4b..eab32afd66 100644 --- a/src/core/authentication/session/session.manager.ts +++ b/src/core/authentication/session/session.manager.ts @@ -2,13 +2,7 @@ import { Injectable } from '@nestjs/common'; import { CachedByArg, setOf } from '@seedcompany/common'; import { DateTime } from 'luxon'; import type { Writable } from 'ts-essentials'; -import { - type ID, - Poll, - type Role, - ServerException, - UnauthorizedException, -} from '~/common'; +import { type ID, Poll, type Role, ServerException, UnauthorizedException } from '~/common'; import { IEventBus } from '~/core/events'; import { ILogger, Logger } from '~/core/logger'; import { SystemAgentRepository } from '../../../components/user/system-agent.repository'; @@ -47,10 +41,7 @@ export class SessionManager { return newSession; } - async resumeSession( - token: string, - impersonatee?: Session['impersonatee'], - ): Promise { + async resumeSession(token: string, impersonatee?: Session['impersonatee']): Promise { this.logger.debug('Decoding token', { token }); const { iat } = this.jwt.decode(token); @@ -68,20 +59,14 @@ export class SessionManager { if (!result) { this.logger.debug('Failed to find active token in database', { token }); - throw new NoSessionException( - 'Session has not been established', - 'NoSession', - ); + throw new NoSessionException('Session has not been established', 'NoSession'); } impersonatee = impersonatee && result.userId ? { id: impersonatee?.id ?? ghost?.id, - roles: setOf([ - ...(impersonatee.roles ?? []), - ...(result.impersonateeRoles ?? []), - ]), + roles: setOf([...(impersonatee.roles ?? []), ...(result.impersonateeRoles ?? [])]), } : undefined; @@ -105,18 +90,13 @@ export class SessionManager { if (impersonatee) { const allowImpersonation = new Poll(); await this.sessionHost.withSession(requesterSession, async () => { - const event = new CanImpersonateEvent( - requesterSession, - allowImpersonation, - ); + const event = new CanImpersonateEvent(requesterSession, allowImpersonation); await this.events.publish(event); }); if (!(allowImpersonation.plurality && !allowImpersonation.vetoed)) { // Don't expose what the requester is unable to do as this could leak // private information. - throw new UnauthorizedException( - 'You are not authorized to perform this impersonation', - ); + throw new UnauthorizedException('You are not authorized to perform this impersonation'); } } @@ -144,9 +124,7 @@ export class SessionManager { return new Proxy(session, { get: (target: Session, p: string | symbol, receiver: any) => { if (p === 'userId' && target.userId === unresolvedId) { - throw new ServerException( - 'Have not yet connected to database to get root user ID', - ); + throw new ServerException('Have not yet connected to database to get root user ID'); } if (p === 'withRoles') { return (...roles: Role[]) => @@ -155,8 +133,7 @@ export class SessionManager { }); } if (p === 'then') { - return (...args: any) => - promiseOfRootId.then(() => session).then(...args); + return (...args: any) => promiseOfRootId.then(() => session).then(...args); } return Reflect.get(target, p, receiver); }, @@ -168,12 +145,8 @@ export class SessionManager { return this.repo.waitForRootUserId(); } - async asUser( - user: ID<'User'> | Session, - fn: (session: Session) => Promise, - ): Promise { - const session = - typeof user === 'string' ? await this.sessionForUser(user) : user; + async asUser(user: ID<'User'> | Session, fn: (session: Session) => Promise): Promise { + const session = typeof user === 'string' ? await this.sessionForUser(user) : user; return await this.sessionHost.withSession(session, () => fn(session)); } diff --git a/src/core/cli/command.discovery.ts b/src/core/cli/command.discovery.ts index bb25d99aa2..e9e76996e7 100644 --- a/src/core/cli/command.discovery.ts +++ b/src/core/cli/command.discovery.ts @@ -1,7 +1,4 @@ -import { - type DiscoveredModule, - DiscoveryService, -} from '@golevelup/nestjs-discovery'; +import { type DiscoveredModule, DiscoveryService } from '@golevelup/nestjs-discovery'; import { Injectable } from '@nestjs/common'; import { Command } from 'clipanion'; @@ -10,9 +7,7 @@ export class CommandDiscovery { constructor(private readonly discovery: DiscoveryService) {} async discover() { - const discovered = await this.discovery.providersWithMetaAtKey( - Command as any, - ); + const discovered = await this.discovery.providersWithMetaAtKey(Command as any); return discovered.map((d) => { const command = d.discoveredClass as DiscoveredModule; return new Proxy(command.dependencyType, { diff --git a/src/core/config/config.service.ts b/src/core/config/config.service.ts index 3f37f0e7bf..4be2cd6179 100644 --- a/src/core/config/config.service.ts +++ b/src/core/config/config.service.ts @@ -1,8 +1,5 @@ import { csv } from '@seedcompany/common'; -import type { - EmailModuleOptions, - EmailOptionsFactory, -} from '@seedcompany/nestjs-email'; +import type { EmailModuleOptions, EmailOptionsFactory } from '@seedcompany/nestjs-email'; import type { Server as HttpServer } from 'http'; import { type LRUCache } from 'lru-cache'; import { DateTime, Duration, type DurationLike } from 'luxon'; @@ -65,10 +62,7 @@ export const makeConfig = (env: EnvironmentService) => request: env.duration('HTTP_REQUEST_TIMEOUT').optional(0), }; - applyTimeouts = ( - http: HttpServer, - timeouts: Partial, - ) => { + applyTimeouts = (http: HttpServer, timeouts: Partial) => { if (timeouts.keepAlive != null) { http.keepAliveTimeout = timeouts.keepAlive.toMillis(); } @@ -98,14 +92,10 @@ export const makeConfig = (env: EnvironmentService) => createEmailOptions = () => { const send = env.boolean('EMAIL_SEND').optional(false); return { - from: env - .string('EMAIL_FROM') - .optional('CORD Field '), + from: env.string('EMAIL_FROM').optional('CORD Field '), replyTo: env.string('EMAIL_REPLY_TO').optional() || undefined, // falsy -> undefined send, - open: this.jest - ? false - : env.boolean('EMAIL_OPEN').optional(!send && isDev), + open: this.jest ? false : env.boolean('EMAIL_OPEN').optional(!send && isDev), ses: { region: env.string('SES_REGION').optional(), }, @@ -117,12 +107,8 @@ export const makeConfig = (env: EnvironmentService) => }; email = { - notifyDistributionLists: env - .boolean('NOTIFY_DISTRIBUTION_LIST') - .optional(false), - notifyProjectStepChanges: env - .boolean('NOTIFY_PROJECT_STEP_CHANGES') - .optional(true), + notifyDistributionLists: env.boolean('NOTIFY_DISTRIBUTION_LIST').optional(false), + notifyProjectStepChanges: env.boolean('NOTIFY_PROJECT_STEP_CHANGES').optional(true), }; progressReportStatusChange = { @@ -145,9 +131,7 @@ export const makeConfig = (env: EnvironmentService) => }, }; - defaultTimeZone = env - .string('DEFAULT_TIMEZONE') - .optional('America/Chicago'); + defaultTimeZone = env.string('DEFAULT_TIMEZONE').optional('America/Chicago'); frontendUrl = env.string('FRONTEND_URL').optional('http://localhost:3001'); @@ -155,15 +139,10 @@ export const makeConfig = (env: EnvironmentService) => const driverConfig: Neo4JDriverConfig = {}; let url = env.string('NEO4J_URL').optional('bolt://localhost'); const parsed = new URL(url); - const username = env - .string('NEO4J_USERNAME') - .optional(parsed.username || 'neo4j'); - const password = env - .string('NEO4J_PASSWORD') - .optional(parsed.password || 'admin'); + const username = env.string('NEO4J_USERNAME').optional(parsed.username || 'neo4j'); + const password = env.string('NEO4J_PASSWORD').optional(parsed.password || 'admin'); const database = - env.string('NEO4J_DBNAME').optional() ?? - (parsed.pathname.slice(1) || undefined); + env.string('NEO4J_DBNAME').optional() ?? (parsed.pathname.slice(1) || undefined); if (parsed.username || parsed.password || parsed.pathname) { parsed.username = ''; parsed.password = ''; @@ -175,9 +154,10 @@ export const makeConfig = (env: EnvironmentService) => username, password, database: this.jest - ? `test.${DateTime.now().toFormat( - 'y-MM-dd.HH-mm-ss', - )}.${customAlphabet('abcdefghjkmnpqrstuvwxyz', 7)()}` + ? `test.${DateTime.now().toFormat('y-MM-dd.HH-mm-ss')}.${customAlphabet( + 'abcdefghjkmnpqrstuvwxyz', + 7, + )()}` : database, ephemeral: this.jest, driverConfig, @@ -188,9 +168,7 @@ export const makeConfig = (env: EnvironmentService) => // Control which database is prioritized, while we migrate. databaseEngine = env.string('DATABASE').optional('neo4j').toLowerCase(); - dbIndexesCreate = env - .boolean('DB_CREATE_INDEXES') - .optional(isDev ? this.neo4j.isLocal : true); + dbIndexesCreate = env.boolean('DB_CREATE_INDEXES').optional(isDev ? this.neo4j.isLocal : true); dbAutoMigrate = env .boolean('DB_AUTO_MIGRATE') .optional(isDev && this.neo4j.isLocal && !this.jest); @@ -243,10 +221,7 @@ export const makeConfig = (env: EnvironmentService) => const rawOrigin = env.string('CORS_ORIGIN').optional('*'); // Always use regex instead of literal `*` so the current origin is returned // instead of `*`. fetch credentials="include" requires specific origin. - const origin = - rawOrigin === '*' - ? /.*/ - : rawOrigin.split(',').map((o) => new RegExp(o)); + const origin = rawOrigin === '*' ? /.*/ : rawOrigin.split(',').map((o) => new RegExp(o)); return { origin, credentials: true, @@ -268,8 +243,7 @@ export const makeConfig = (env: EnvironmentService) => } const userAgent = req.headers['user-agent']; - const isSafari = - userAgent && /^((?!chrome|android).)*safari/i.test(userAgent); + const isSafari = userAgent && /^((?!chrome|android).)*safari/i.test(userAgent); return { name, @@ -290,9 +264,7 @@ export const makeConfig = (env: EnvironmentService) => }; xray = { - daemonAddress: this.jest - ? undefined - : env.string('AWS_XRAY_DAEMON_ADDRESS').optional(), + daemonAddress: this.jest ? undefined : env.string('AWS_XRAY_DAEMON_ADDRESS').optional(), }; redis = { diff --git a/src/core/config/environment.service.ts b/src/core/config/environment.service.ts index de7a288d57..ec5be23228 100644 --- a/src/core/config/environment.service.ts +++ b/src/core/config/environment.service.ts @@ -31,12 +31,9 @@ export class EnvironmentService implements Iterable<[string, string]> { // as pairs could be passed in instead of in env files this.env = pickBy(process.env) as Record; - const files = [ - `.env.${env}.local`, - `.env.${env}`, - `.env.local`, - `.env`, - ].map((file) => join(rootPath, file)); + const files = [`.env.${env}.local`, `.env.${env}`, `.env.local`, `.env`].map((file) => + join(rootPath, file), + ); for (const file of files) { if (!fs.existsSync(file)) { @@ -120,33 +117,33 @@ export class EnvironmentService implements Iterable<[string, string]> { keySeparator?: string; }, ) { - return this.wrap< - ReadonlyMap, - string | ReadonlyMap | Partial> - >(key, (raw) => { - if (raw instanceof Map) { - return raw; - } - if (typeof raw === 'object') { - return new Map(Object.entries(raw)); - } - const { pairSeparator = ';', keySeparator = '=' } = options; - - const parseKey = - typeof options.parseKey === 'function' - ? options.parseKey - : options.parseKey - ? verifyInSet(key, options.parseKey) - : identity; - const parseValue = options.parseValue ?? identity; - - return new Map( - (raw ?? '').split(pairSeparator).map((item) => { - const [key, value] = item.trim().split(keySeparator); - return [parseKey(key), parseValue(value)] as const; - }), - ); - }); + return this.wrap, string | ReadonlyMap | Partial>>( + key, + (raw) => { + if (raw instanceof Map) { + return raw; + } + if (typeof raw === 'object') { + return new Map(Object.entries(raw)); + } + const { pairSeparator = ';', keySeparator = '=' } = options; + + const parseKey = + typeof options.parseKey === 'function' + ? options.parseKey + : options.parseKey + ? verifyInSet(key, options.parseKey) + : identity; + const parseValue = options.parseValue ?? identity; + + return new Map( + (raw ?? '').split(pairSeparator).map((item) => { + const [key, value] = item.trim().split(keySeparator); + return [parseKey(key), parseValue(value)] as const; + }), + ); + }, + ); } *[Symbol.iterator]() { diff --git a/src/core/config/root-user.config.ts b/src/core/config/root-user.config.ts index 38b214d3ee..ff60af2582 100644 --- a/src/core/config/root-user.config.ts +++ b/src/core/config/root-user.config.ts @@ -12,8 +12,7 @@ export const determineRootUser = (env: EnvironmentService) => { const user = JSON.parse(env.string('ROOT_USER').optional('{}')) as { [_ in 'id' | 'email' | 'password']?: string; }; - const email = - env.string('ROOT_USER_EMAIL').optional() ?? user.email ?? 'devops@tsco.org'; + const email = env.string('ROOT_USER_EMAIL').optional() ?? user.email ?? 'devops@tsco.org'; const password = env.string('ROOT_USER_PASSWORD').optional() ?? user.password ?? diff --git a/src/core/config/version.service.ts b/src/core/config/version.service.ts index abef37eb3c..f259754c31 100644 --- a/src/core/config/version.service.ts +++ b/src/core/config/version.service.ts @@ -74,16 +74,10 @@ export class VersionService implements OnModuleInit { } export class Version { - constructor( - readonly hash?: string, - readonly branch?: string, - readonly packageJson?: string, - ) {} + constructor(readonly hash?: string, readonly branch?: string, readonly packageJson?: string) {} get known() { - return ( - Boolean(this.branch) || Boolean(this.hash) || Boolean(this.packageJson) - ); + return Boolean(this.branch) || Boolean(this.hash) || Boolean(this.packageJson); } toString() { diff --git a/src/core/data-loader/data-loader.config.ts b/src/core/data-loader/data-loader.config.ts index 6714f168e3..2a47389c3b 100644 --- a/src/core/data-loader/data-loader.config.ts +++ b/src/core/data-loader/data-loader.config.ts @@ -1,18 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { - type DataLoaderOptions, - lifetimeIdFromExecutionContext, -} from '@seedcompany/data-loader'; +import { type DataLoaderOptions, lifetimeIdFromExecutionContext } from '@seedcompany/data-loader'; import { NotFoundException } from '~/common'; import { Identity } from '../authentication'; import { ConfigService } from '../config/config.service'; @Injectable() export class DataLoaderConfig { - constructor( - private readonly config: ConfigService, - private readonly identity: Identity, - ) {} + constructor(private readonly config: ConfigService, private readonly identity: Identity) {} create(): DataLoaderOptions { return { @@ -20,9 +14,7 @@ export class DataLoaderConfig { batchScheduleFn: (cb) => setTimeout(cb, 10), maxBatchSize: 100, createError: ({ typeName, cacheKey }) => - new NotFoundException( - `Could not find ${String(typeName)} (${String(cacheKey)})`, - ), + new NotFoundException(`Could not find ${String(typeName)} (${String(cacheKey)})`), getLifetimeId: (context) => { // If we have a session, use that as the cache key. // It will always be created / scoped within the GQL operation. diff --git a/src/core/data-loader/loader-factory.decorator.ts b/src/core/data-loader/loader-factory.decorator.ts index fed8652692..778ef911ab 100644 --- a/src/core/data-loader/loader-factory.decorator.ts +++ b/src/core/data-loader/loader-factory.decorator.ts @@ -47,7 +47,6 @@ export const LoaderFactory = LoaderFactoryMetadata(resource, { ...options, objectViewAware: - options?.objectViewAware ?? - Object.getPrototypeOf(target) === ObjectViewAwareLoader, + options?.objectViewAware ?? Object.getPrototypeOf(target) === ObjectViewAwareLoader, })(target); }; diff --git a/src/core/data-loader/object-view-aware-loader.strategy.ts b/src/core/data-loader/object-view-aware-loader.strategy.ts index 73f7020d61..2c159f6576 100644 --- a/src/core/data-loader/object-view-aware-loader.strategy.ts +++ b/src/core/data-loader/object-view-aware-loader.strategy.ts @@ -1,8 +1,5 @@ import { groupBy } from '@seedcompany/common'; -import { - type DataLoaderOptions, - type DataLoaderStrategy, -} from '@seedcompany/data-loader'; +import { type DataLoaderOptions, type DataLoaderStrategy } from '@seedcompany/data-loader'; import { type ID, type ObjectView, viewOfChangeset } from '~/common'; import { type ChangesetAware } from '../../components/changeset/dto'; import type { ResourceNameLike } from '../resources'; @@ -23,10 +20,7 @@ export abstract class ObjectViewAwareLoader< Kind extends ResourceNameLike | object = T, > implements DataLoaderStrategy, string> { - abstract loadManyByView( - ids: ReadonlyArray>, - view: ObjectView, - ): Promise; + abstract loadManyByView(ids: ReadonlyArray>, view: ObjectView): Promise; async loadMany(keys: ReadonlyArray>): Promise { const grouped = groupBy(keys, (key) => viewId(key.view)); diff --git a/src/core/data-loader/options.type.ts b/src/core/data-loader/options.type.ts index 9b68e1a2dd..6c9b181fc2 100644 --- a/src/core/data-loader/options.type.ts +++ b/src/core/data-loader/options.type.ts @@ -1,12 +1,6 @@ -import { - type DataLoaderOptions, - type DataLoaderStrategy, -} from '@seedcompany/data-loader'; +import { type DataLoaderOptions, type DataLoaderStrategy } from '@seedcompany/data-loader'; -export type LoaderOptionsOf< - Strategy, - CachedKey = unknown, -> = Strategy extends Pick< +export type LoaderOptionsOf = Strategy extends Pick< DataLoaderStrategy, 'loadMany' > diff --git a/src/core/data-loader/single-item-loader.strategy.ts b/src/core/data-loader/single-item-loader.strategy.ts index c53d74a338..a742afb35b 100644 --- a/src/core/data-loader/single-item-loader.strategy.ts +++ b/src/core/data-loader/single-item-loader.strategy.ts @@ -1,7 +1,4 @@ -import { - type DataLoaderOptions, - type DataLoaderStrategy, -} from '@seedcompany/data-loader'; +import { type DataLoaderOptions, type DataLoaderStrategy } from '@seedcompany/data-loader'; import { type ID, NotFoundException } from '~/common'; /** @@ -10,9 +7,7 @@ import { type ID, NotFoundException } from '~/common'; * Using this is only encouraged to adopt the DataLoader pattern, which can provide * caching even without batching. */ -export abstract class SingleItemLoader - implements DataLoaderStrategy -{ +export abstract class SingleItemLoader implements DataLoaderStrategy { abstract loadOne(key: Key): Promise; async loadMany(keys: readonly Key[]): Promise { diff --git a/src/core/database/abstract-transactional-mutations.interceptor.ts b/src/core/database/abstract-transactional-mutations.interceptor.ts index 2e1bb3d433..4aad6afd37 100644 --- a/src/core/database/abstract-transactional-mutations.interceptor.ts +++ b/src/core/database/abstract-transactional-mutations.interceptor.ts @@ -13,9 +13,7 @@ import { RollbackManager } from './rollback-manager'; * This allows automatic rollbacks on error. */ @Injectable() -export abstract class TransactionalMutationsInterceptor - implements NestInterceptor -{ +export abstract class TransactionalMutationsInterceptor implements NestInterceptor { constructor(private readonly rollbacks: RollbackManager) {} async intercept(context: ExecutionContext, next: CallHandler) { diff --git a/src/core/database/changes.ts b/src/core/database/changes.ts index e263f821a1..2b0fe09fc3 100644 --- a/src/core/database/changes.ts +++ b/src/core/database/changes.ts @@ -35,25 +35,18 @@ export interface SetChangeType { } export type AnyChangesOf = { - [Key in keyof T & string as ChangeKey< - Exclude, - T - >]?: ChangeOf; + [Key in keyof T & string as ChangeKey, T>]?: ChangeOf; } & { // allow id to be passed in and removed from changes as it's what we are doing // all over the app. id?: ID; }; -export type ChangesOf< - TResource, - Changes extends AnyChangesOf, -> = Partial & AndModifiedAt>; +export type ChangesOf> = Partial< + Omit & AndModifiedAt +>; -type ChangeKey = T[Key] extends SetChangeType< - infer Override, - any -> +type ChangeKey = T[Key] extends SetChangeType ? Override extends string ? Override : never @@ -67,9 +60,7 @@ type ChangeKey = T[Key] extends SetChangeType< type ChangeOf = Val extends SetChangeType ? Override - : - | RawChangeOf & {}> - | (null extends UnwrapSecured ? null : never); + : RawChangeOf & {}> | (null extends UnwrapSecured ? null : never); export type RawChangeOf = IsFileField extends true ? CreateDefinedFileVersionInput @@ -102,9 +93,7 @@ type DbAllowableChanges = { >]?: UnwrapSecured | Variable; }; -type AndModifiedAt = T extends { modifiedAt: DateTime } - ? Pick - : unknown; +type AndModifiedAt = T extends { modifiedAt: DateTime } ? Pick : unknown; /** * Given the existing object and proposed changes, return only the changes @@ -179,10 +168,7 @@ export const getChanges = export const isRelation = >( resource: EnhancedResource, prop: string, -) => - !resource.props.has(prop) && - prop.endsWith('Id') && - resource.props.has(prop.slice(0, -2)); +) => !resource.props.has(prop) && prop.endsWith('Id') && resource.props.has(prop.slice(0, -2)); export const compareNullable = (fn: (a: T, b: T) => boolean) => @@ -201,10 +187,7 @@ export const isSame = compareNullable((a: unknown, b: unknown) => { return +(a as number) === +(b as number); } if (Array.isArray(a) && Array.isArray(b)) { - return ( - entries(difference(a, b)).length === 0 && - entries(difference(b, a)).length === 0 - ); + return entries(difference(a, b)).length === 0 && entries(difference(b, a)).length === 0; } return a === b; }); diff --git a/src/core/database/common.repository.ts b/src/core/database/common.repository.ts index 3b5bff6878..9089e072e1 100644 --- a/src/core/database/common.repository.ts +++ b/src/core/database/common.repository.ts @@ -127,9 +127,7 @@ export class CommonRepository { if (res.stats.totalCount !== newList.length) { const validNodes = await this.getBaseNodes(newList); const validIds = setOf(validNodes.map((n) => n.properties.id)); - throw new InvalidReferencesException( - newList.filter((id) => !validIds.has(id)), - ); + throw new InvalidReferencesException(newList.filter((id) => !validIds.has(id))); } return res.stats; } @@ -139,9 +137,7 @@ export class CommonRepository { { changeset, resource }: { changeset?: ID; resource?: ResourceLike } = {}, ) { const id = isIdLike(objectOrId) ? objectOrId : objectOrId.id; - const label = resource - ? this.resources.enhance(resource).dbLabel - : 'BaseNode'; + const label = resource ? this.resources.enhance(resource).dbLabel : 'BaseNode'; if (!changeset) { await this.db .query() @@ -156,11 +152,7 @@ export class CommonRepository { .query() .match(node('node', label, { id })) .match(node('changeset', 'Changeset', { id: changeset })) - .merge([ - node('changeset'), - relation('out', 'rel', 'changeset'), - node('node'), - ]) + .merge([node('changeset'), relation('out', 'rel', 'changeset'), node('node')]) .setValues({ 'rel.active': true, 'rel.deleting': true }) .run(); } catch (e) { @@ -174,9 +166,7 @@ export class CommonRepository { ...(props.has('id') ? [createUniqueConstraint(dbLabel, 'id')] : []), ...[...props].flatMap((prop) => { const label = DbUnique.get(resource, prop); - return label - ? createUniqueConstraint(label, 'value', `${resource.name}_${prop}`) - : []; + return label ? createUniqueConstraint(label, 'value', `${resource.name}_${prop}`) : []; }), ]; } diff --git a/src/core/database/cypher.factory.ts b/src/core/database/cypher.factory.ts index a48e089b0c..b0d526e35f 100644 --- a/src/core/database/cypher.factory.ts +++ b/src/core/database/cypher.factory.ts @@ -15,11 +15,7 @@ import { type ILogger, LoggerToken, LogLevel } from '../logger'; import { AFTER_MESSAGE } from '../logger/formatters'; import { TracingService } from '../tracing'; import { DbTraceLayer } from './database.service'; -import { - createBetterError, - isNeo4jError, - ServiceUnavailableError, -} from './errors'; +import { createBetterError, isNeo4jError, ServiceUnavailableError } from './errors'; import { highlight } from './highlight-cypher.util'; import { ParameterTransformer } from './parameter-transformer.service'; // eslint-disable-next-line import/no-duplicates @@ -79,24 +75,20 @@ export const CypherFactory: FactoryProvider = { } = config.neo4j; const driverLoggerAdapter: LoggerFunction = (neoLevel, message) => { - const level = - neoLevel === 'warn' ? LogLevel.WARNING : (neoLevel as LogLevel); + const level = neoLevel === 'warn' ? LogLevel.WARNING : (neoLevel as LogLevel); if (message.startsWith('Updated routing table')) { const routingTable = parseRoutingTable(message); driverLogger.info('Updated routing table', { routingTable }); } else if (message.startsWith('Routing table is stale for database')) { const routingTable = parseRoutingTable(message); - const matched = /for database: "(.*)" and access mode: "(.+)":/.exec( - message, - ); + const matched = /for database: "(.*)" and access mode: "(.+)":/.exec(message); driverLogger.info('Routing table is stale', { database: matched?.[1] || null, accessMode: matched?.[2], routingTable, }); } else if ( - (level === LogLevel.WARNING && - message.includes('Failed to connect to server')) || + (level === LogLevel.WARNING && message.includes('Failed to connect to server')) || (level === LogLevel.ERROR && message.includes('"retriable":true')) ) { // Change retriable failure messages to debug. @@ -261,18 +253,14 @@ const wrapQueryRun = ( ...(parameters?.logIt ? { statement: - process.env.NODE_ENV !== 'production' - ? highlight(statement) - : statement, + process.env.NODE_ENV !== 'production' ? highlight(statement) : statement, } : {}), ...parameters, }, ); - const params = parameters - ? parameterTransformer.transform(parameters) - : undefined; + const params = parameters ? parameterTransformer.transform(parameters) : undefined; const result = origRun(statement, params); const tweakError = (e: Error) => { diff --git a/src/core/database/database.service.ts b/src/core/database/database.service.ts index 97be566898..4b693ce736 100644 --- a/src/core/database/database.service.ts +++ b/src/core/database/database.service.ts @@ -4,13 +4,7 @@ import { Connection, node, type Query, relation } from 'cypher-query-builder'; import { LazyGetter } from 'lazy-get-decorator'; import { pickBy, startCase } from 'lodash'; import { DateTime, Duration } from 'luxon'; -import { - defer, - EmptyError, - firstValueFrom, - shareReplay, - takeUntil, -} from 'rxjs'; +import { defer, EmptyError, firstValueFrom, shareReplay, takeUntil } from 'rxjs'; import { DuplicateException, type ID, @@ -28,11 +22,7 @@ import { ConfigService } from '../config/config.service'; import { ILogger, Logger } from '../logger'; import { ShutdownHook } from '../shutdown.hook'; import { type DbChanges } from './changes'; -import { - createBetterError, - ServiceUnavailableError, - UniquenessError, -} from './errors'; +import { createBetterError, ServiceUnavailableError, UniquenessError } from './errors'; import { ACTIVE, deleteBaseNode, @@ -58,10 +48,7 @@ interface DbInfo { error?: string; } -type PermanentAfterOption = Pick< - UpdatePropertyOptions, - 'permanentAfter' ->; +type PermanentAfterOption = Pick, 'permanentAfter'>; @Injectable() export class DatabaseService { @@ -85,9 +72,7 @@ export class DatabaseService { * If connection to database fails while executing function it will keep * retrying (after another successful connection) until the function finishes. */ - async runOnceUntilCompleteAfterConnecting( - run: (info: ServerInfo) => Promise, - ) { + async runOnceUntilCompleteAfterConnecting(run: (info: ServerInfo) => Promise) { await this.waitForConnection( { forever: true, @@ -102,10 +87,7 @@ export class DatabaseService { * Wait for database connection. * Optionally run a function in retry context after connecting. */ - async waitForConnection( - options?: RetryOptions, - then?: (info: ServerInfo) => Promise, - ) { + async waitForConnection(options?: RetryOptions, then?: (info: ServerInfo) => Promise) { await retry(async () => { try { const info = await this.getServerInfo(); @@ -126,10 +108,7 @@ export class DatabaseService { * @example * query('match (n) return n').run(); */ - query( - query?: string, - parameters?: Record, - ): Query { + query(query?: string, parameters?: Record): Query { const q = this.db.query() as Query; q.params.addParam(this.identity.currentIfInCtx?.userId, 'currentUser'); if (query) { @@ -170,9 +149,7 @@ export class DatabaseService { throw new ServerException('Unable to determine server info'); } // "Administration" command doesn't work with read transactions - const dbs = await session.executeWrite((tx) => - tx.run('show databases yield *'), - ); + const dbs = await session.executeWrite((tx) => tx.run('show databases yield *')); const versionParts = (info.get('version') as string).split('.'); const version = versionParts.map(Number) as ServerInfo['version']; const versionXY = Number(versionParts.slice(0, 2).join('.')); @@ -183,8 +160,7 @@ export class DatabaseService { databases: dbs.records.map((r) => ({ name: r.get('name'), status: r.get('currentStatus'), - error: - r.get(version[0] >= 5 ? 'statusMessage' : 'error') || undefined, + error: r.get(version[0] >= 5 ? 'statusMessage' : 'error') || undefined, })), }; } catch (e) { @@ -380,11 +356,7 @@ export class DatabaseService { const update = this.db .query() .match(node('node', label, { id })) - .apply( - changeset - ? (q) => q.match(node('changeset', 'Changeset', { id: changeset })) - : null, - ) + .apply(changeset ? (q) => q.match(node('changeset', 'Changeset', { id: changeset })) : null) .apply( updateProperty({ resource: type, diff --git a/src/core/database/db-type.ts b/src/core/database/db-type.ts index b65d409d29..fac78b247e 100644 --- a/src/core/database/db-type.ts +++ b/src/core/database/db-type.ts @@ -21,6 +21,4 @@ export type DbTypeOf = { [K in keyof Dto as Exclude]: DbValueOf; }; -type DbValueOf = Val extends SetDbType - ? Override - : UnwrapSecured; +type DbValueOf = Val extends SetDbType ? Override : UnwrapSecured; diff --git a/src/core/database/dto.repository.ts b/src/core/database/dto.repository.ts index 63eeb71478..b5741d8acf 100644 --- a/src/core/database/dto.repository.ts +++ b/src/core/database/dto.repository.ts @@ -69,9 +69,7 @@ export const DtoRepository = < (p: string) => DbUnique.get(resource, p) ?? [], ); if (labels.length === 0) { - return new ServerException( - `No unique properties found in ${resource.name}`, - ); + return new ServerException(`No unique properties found in ${resource.name}`); } if (labels.length > 1) { return new ServerException( @@ -88,9 +86,7 @@ export const DtoRepository = < async readOne(id: ID, ...args: HydrateArgs) { const rows = await this.readMany([id], ...args); if (!rows[0]) { - throw new NotFoundException( - `Could not find ${lowerCase(resource.name)}`, - ); + throw new NotFoundException(`Could not find ${lowerCase(resource.name)}`); } return rows[0]; } @@ -134,13 +130,7 @@ export const DtoRepository = < id: ID, otherId: ID | null, ) { - await super.updateRelation( - relationName, - otherLabel, - id, - otherId, - this.resource.dbLabel, - ); + await super.updateRelation(relationName, otherLabel, id, otherId, this.resource.dbLabel); } protected async updateRelationList( @@ -160,9 +150,7 @@ export const DtoRepository = < */ protected hydrate(..._args: HydrateArgs) { return (query: Query) => - query - .apply(matchProps()) - .return<{ dto: DbTypeOf }>('props as dto'); + query.apply(matchProps()).return<{ dto: DbTypeOf }>('props as dto'); } @OnIndex() diff --git a/src/core/database/errors.ts b/src/core/database/errors.ts index 81b2619f7d..74072b397d 100644 --- a/src/core/database/errors.ts +++ b/src/core/database/errors.ts @@ -64,15 +64,13 @@ export class ConnectionTimeoutError extends Neo4jError { static readonly code = defaultCode; static [Symbol.hasInstance](object: unknown) { return ( - isNeo4jError(object) && - object.message.startsWith('Connection acquisition timed out in ') + isNeo4jError(object) && object.message.startsWith('Connection acquisition timed out in ') ); } } export class ConstraintError extends Neo4jError { - static readonly code = - 'Neo.ClientError.Schema.ConstraintValidationFailed' as const; + static readonly code = 'Neo.ClientError.Schema.ConstraintValidationFailed' as const; static [Symbol.hasInstance](object: unknown) { return isNeo4jError(object) && object.code === ConstraintError.code; } @@ -87,8 +85,7 @@ export class UniquenessError extends ConstraintError { static [Symbol.hasInstance](object: unknown) { return ( - object instanceof ConstraintError && - object.message.includes('already exists with label') + object instanceof ConstraintError && object.message.includes('already exists with label') ); } @@ -163,9 +160,7 @@ const getUniqueFailureInfo = (e: Neo4jError) => { // https://github.com/SeedCompany/cord-api-v3/blob/f363f89c278d099cf76a1f7d3f08edd9578bf2be/src/core/database/common.repository.ts#L180 if (constraint) { const constraintNameParts = constraint.name.split('_'); - uniq.label = uniq.label.startsWith('Label[') - ? constraintNameParts[0] - : uniq.label; + uniq.label = uniq.label.startsWith('Label[') ? constraintNameParts[0] : uniq.label; uniq.property = uniq.property.startsWith('PropertyKey[') ? constraintNameParts[1] : uniq.property; diff --git a/src/core/database/highlight-cypher.util.ts b/src/core/database/highlight-cypher.util.ts index 63e4d67a69..ce13214495 100644 --- a/src/core/database/highlight-cypher.util.ts +++ b/src/core/database/highlight-cypher.util.ts @@ -28,13 +28,9 @@ const cypher = Prism.languages.insertBefore('cypher', 'keyword', { function: /\b[\w.]+\b(?=\()/, }); // Fix matching relationship labels without properties: `[:abc]` -(cypher['class-name'] as TokenObject).pattern = - /(:\s*)(?:\w+|`[^`\\\r\n]*`)(?=\s*[{):]|])/; +(cypher['class-name'] as TokenObject).pattern = /(:\s*)(?:\w+|`[^`\\\r\n]*`)(?=\s*[{):]|])/; // Remove "node" from keywords -cypher.keyword = RegExp( - String(cypher.keyword).slice(1, -2).replace('|NODE', ''), - 'i', -); +cypher.keyword = RegExp(String(cypher.keyword).slice(1, -2).replace('|NODE', ''), 'i'); const theme = { keyword: chalk.hex('#af63e5'), diff --git a/src/core/database/indexer/create-indexes.decorator.ts b/src/core/database/indexer/create-indexes.decorator.ts index 923d3bc5e6..859291ecc6 100644 --- a/src/core/database/indexer/create-indexes.decorator.ts +++ b/src/core/database/indexer/create-indexes.decorator.ts @@ -11,8 +11,7 @@ export type IndexMode = 'write' | 'schema'; * It should be used on a provider method. * It's passed a db Connection for convenience. */ -export const OnIndex = (mode: IndexMode = 'write') => - SetMetadata(DB_INDEX_KEY, mode); +export const OnIndex = (mode: IndexMode = 'write') => SetMetadata(DB_INDEX_KEY, mode); export interface OnIndexParams { db: DatabaseService; @@ -20,9 +19,7 @@ export interface OnIndexParams { serverInfo: ServerInfo; } -export type Indexer = ( - params: OnIndexParams, -) => Promise; +export type Indexer = (params: OnIndexParams) => Promise; export const createUniqueConstraint = ( nodeName: string, diff --git a/src/core/database/indexer/indexer.module.ts b/src/core/database/indexer/indexer.module.ts index d2f349e27a..745f7de91f 100644 --- a/src/core/database/indexer/indexer.module.ts +++ b/src/core/database/indexer/indexer.module.ts @@ -28,24 +28,18 @@ export class IndexerModule implements OnModuleInit { return; } - const discovered = - await this.discover.providerMethodsWithMetaAtKey(DB_INDEX_KEY); + const discovered = await this.discover.providerMethodsWithMetaAtKey(DB_INDEX_KEY); this.logger.debug('Discovered indexers', { count: discovered.length }); const groupedByMode = groupToMapBy(discovered, (d) => d.meta); - const finishing = this.db.runOnceUntilCompleteAfterConnecting( - async (serverInfo) => { - for (const [mode, discoveredOfMode] of groupedByMode.entries()) { - await this.db.conn.runInTransaction( - () => this.doIndexing(discoveredOfMode, serverInfo), - { - queryLogger: this.logger, - }, - ); - this.logger.debug(`Finished syncing ${mode} indexes`); - } - }, - ); + const finishing = this.db.runOnceUntilCompleteAfterConnecting(async (serverInfo) => { + for (const [mode, discoveredOfMode] of groupedByMode.entries()) { + await this.db.conn.runInTransaction(() => this.doIndexing(discoveredOfMode, serverInfo), { + queryLogger: this.logger, + }); + this.logger.debug(`Finished syncing ${mode} indexes`); + } + }); // Wait for indexing to finish when running tests, else just let it run in // background and allow webserver to start. if (this.config.jest) { @@ -76,20 +70,14 @@ export class IndexerModule implements OnModuleInit { : statement.replace(' FOR ', ' ON ').replace(' REQUIRE ', ' ASSERT '), ); for (const [i, statement] of Object.entries(statements)) { - if ( - serverInfo.edition === 'community' && - statement.toUpperCase().includes('IS UNIQUE') - ) { - this.logger.debug( - 'Skipping constraint not supported on Neo4j Community Edition', - { constraint: statement }, - ); + if (serverInfo.edition === 'community' && statement.toUpperCase().includes('IS UNIQUE')) { + this.logger.debug('Skipping constraint not supported on Neo4j Community Edition', { + constraint: statement, + }); continue; } - const indexName = statement.match( - /create (?:index|constraint) ([\w_]+)/i, - )?.[1]; + const indexName = statement.match(/create (?:index|constraint) ([\w_]+)/i)?.[1]; const src = `${parentClass.name}.${methodName}`; const indexStr = Number(i) > 0 ? ` #${Number(i) + 1}` : ''; const name = indexName ? `${indexName} (${src})` : `${src}${indexStr}`; @@ -103,10 +91,9 @@ export class IndexerModule implements OnModuleInit { e.code === 'Neo.DatabaseError.Schema.ConstraintCreationFailed' && e.message.includes('constraint requires Neo4j Enterprise Edition') ) { - this.logger.debug( - 'Skipping constraint not supported on Neo4j Community Edition', - { constraint: statement }, - ); + this.logger.debug('Skipping constraint not supported on Neo4j Community Edition', { + constraint: statement, + }); } else { this.logger.error('Failed to apply index', { index: name, diff --git a/src/core/database/migration/base-migration.service.ts b/src/core/database/migration/base-migration.service.ts index 6656a460e2..49a74285fe 100644 --- a/src/core/database/migration/base-migration.service.ts +++ b/src/core/database/migration/base-migration.service.ts @@ -57,15 +57,7 @@ export abstract class BaseMigration { const result = await this.db .query() .matchNode('node', resource.dbLabel) - .where( - not( - path([ - node('node'), - relation('out', '', property, ACTIVE), - node('', 'Property'), - ]), - ), - ) + .where(not(path([node('node'), relation('out', '', property, ACTIVE), node('', 'Property')]))) .create([ node('node'), relation('out', undefined, property, { @@ -78,14 +70,10 @@ export abstract class BaseMigration { value, }), ]) - .return<{ numPropsCreated: number }>( - 'count(newPropNode) as numPropsCreated', - ) + .return<{ numPropsCreated: number }>('count(newPropNode) as numPropsCreated') .first(); this.logger.info( - `Created ${result?.numPropsCreated ?? 0} ${ - resource.name - }.${property} default props`, + `Created ${result?.numPropsCreated ?? 0} ${resource.name}.${property} default props`, ); } } diff --git a/src/core/database/migration/migration-discovery.service.ts b/src/core/database/migration/migration-discovery.service.ts index a2b299f744..9ad034195e 100644 --- a/src/core/database/migration/migration-discovery.service.ts +++ b/src/core/database/migration/migration-discovery.service.ts @@ -11,9 +11,7 @@ export class MigrationDiscovery { constructor(private readonly discover: DiscoveryService) {} async getMigrations() { - const discovered = await this.discover.providersWithMetaAtKey( - DB_MIGRATION_KEY, - ); + const discovered = await this.discover.providersWithMetaAtKey(DB_MIGRATION_KEY); const mapped = discovered.map((d): DiscoveredMigration => { const instance = d.discoveredClass.instance as BaseMigration; const name = instance.constructor.name.replace('Migration', ''); diff --git a/src/core/database/migration/migration-runner.service.ts b/src/core/database/migration/migration-runner.service.ts index c81d400e45..3a9d971da9 100644 --- a/src/core/database/migration/migration-runner.service.ts +++ b/src/core/database/migration/migration-runner.service.ts @@ -4,10 +4,7 @@ import { DateTime } from 'luxon'; import { ConfigService } from '../../config/config.service'; import { ILogger, Logger } from '../../logger'; import { DatabaseService, DbTraceLayer } from '../database.service'; -import { - type DiscoveredMigration, - MigrationDiscovery, -} from './migration-discovery.service'; +import { type DiscoveredMigration, MigrationDiscovery } from './migration-discovery.service'; @Injectable() @DbTraceLayer.applyToClass() @@ -61,9 +58,7 @@ export class MigrationRunner { await this.setSchemaVersion(current); } if (current < latest) { - this.logger.info( - 'Schema migration finished incompletely due to error(s)', - ); + this.logger.info('Schema migration finished incompletely due to error(s)'); } else { this.logger.info('Schema is now up to date'); } diff --git a/src/core/database/migration/migration.command.ts b/src/core/database/migration/migration.command.ts index cd9b63d41f..49068b11ca 100644 --- a/src/core/database/migration/migration.command.ts +++ b/src/core/database/migration/migration.command.ts @@ -6,8 +6,7 @@ import { MigrationRunner } from './migration-runner.service'; export class DatabaseMigrationCommand extends Command { static paths = [['db', 'migrate']]; static usage = Command.Usage({ - description: - 'Run database migrations needed to sync schema to current version', + description: 'Run database migrations needed to sync schema to current version', }); constructor(private readonly runner: MigrationRunner) { super(); diff --git a/src/core/database/migration/migration.module.ts b/src/core/database/migration/migration.module.ts index 572c540b7c..52071ce531 100644 --- a/src/core/database/migration/migration.module.ts +++ b/src/core/database/migration/migration.module.ts @@ -19,11 +19,7 @@ export class MigrationModule implements OnModuleInit { async onModuleInit() { const entryCmd = process.argv.join(''); - if ( - !this.config.dbAutoMigrate || - entryCmd.includes('console') || - entryCmd.includes('repl') - ) { + if (!this.config.dbAutoMigrate || entryCmd.includes('console') || entryCmd.includes('repl')) { return; } diff --git a/src/core/database/parameter-transformer.service.ts b/src/core/database/parameter-transformer.service.ts index 557be50d39..7013bfa5fe 100644 --- a/src/core/database/parameter-transformer.service.ts +++ b/src/core/database/parameter-transformer.service.ts @@ -43,12 +43,7 @@ export class ParameterTransformer { if (value instanceof Duration) { value = value.shiftTo('months', 'days', 'seconds', 'milliseconds'); - return new Neo.Duration( - value.months, - value.days, - value.seconds, - value.milliseconds * 1e6, - ); + return new Neo.Duration(value.months, value.days, value.seconds, value.milliseconds * 1e6); } if (value instanceof RichTextDocument) { @@ -69,11 +64,6 @@ export class ParameterTransformer { private isPlainValue(value: any) { const type = typeof value; - return ( - value == null || - type === 'string' || - type === 'boolean' || - type === 'number' - ); + return value == null || type === 'string' || type === 'boolean' || type === 'number'; } } diff --git a/src/core/database/query-augmentation/apply.ts b/src/core/database/query-augmentation/apply.ts index 9a0733ea0c..f581aca058 100644 --- a/src/core/database/query-augmentation/apply.ts +++ b/src/core/database/query-augmentation/apply.ts @@ -33,9 +33,7 @@ declare module 'cypher-query-builder/dist/typings/query' { } } -export type QueryFragment = ( - query: Query, -) => Query; +export type QueryFragment = (query: Query) => Query; Query.prototype.apply = function apply( fn: ((q: Query) => R) | null | undefined, diff --git a/src/core/database/query-augmentation/call.ts b/src/core/database/query-augmentation/call.ts index c16d713b40..7808b79916 100644 --- a/src/core/database/query-augmentation/call.ts +++ b/src/core/database/query-augmentation/call.ts @@ -24,9 +24,7 @@ Query.prototype.call = function call( args?: ProcedureArgs, ) { const call = - typeof procedure === 'string' - ? { procedureName: procedure, args: args ?? [] } - : procedure; + typeof procedure === 'string' ? { procedureName: procedure, args: args ?? [] } : procedure; const clause = new Procedure(call.procedureName, call.args); const next = this.continueChainClause(clause); return call.yieldTerms ? next.yield(call.yieldTerms) : next; @@ -47,9 +45,7 @@ class Procedure extends Clause { Array.isArray(args) ? args.map((value) => [undefined, value] as const) : entries(this.args as Record) - ).map(([key, value]) => - isExp(value) ? variable(value) : this.addParam(value, key), - ); + ).map(([key, value]) => (isExp(value) ? variable(value) : this.addParam(value, key))); } build() { return `CALL ${this.name}(${this.params.join(', ')})`; @@ -66,12 +62,9 @@ export const procedure = procedureName, args, yield: (yieldTerms: YieldTerms) => - Object.assign( - (query: Query) => query.call(procedureName, args).yield(yieldTerms), - { - procedureName, - args, - yieldTerms, - }, - ), + Object.assign((query: Query) => query.call(procedureName, args).yield(yieldTerms), { + procedureName, + args, + yieldTerms, + }), }); diff --git a/src/core/database/query-augmentation/comment.ts b/src/core/database/query-augmentation/comment.ts index 612ee5dc7e..c8120c470c 100644 --- a/src/core/database/query-augmentation/comment.ts +++ b/src/core/database/query-augmentation/comment.ts @@ -27,11 +27,7 @@ declare module 'cypher-query-builder/dist/typings/query' { } } -Query.prototype.comment = function comment( - this: Query, - comment: CommentIn, - ...args: unknown[] -) { +Query.prototype.comment = function comment(this: Query, comment: CommentIn, ...args: unknown[]) { return this.continueChainClause(new Comment(comment, ...args)); }; @@ -43,9 +39,7 @@ class Comment extends Clause { constructor(comment: CommentIn, ...args: unknown[]) { super(); this.comment = - typeof comment === 'string' - ? stripIndent(comment) - : stripIndent(comment, ...args); + typeof comment === 'string' ? stripIndent(comment) : stripIndent(comment, ...args); } build() { diff --git a/src/core/database/query-augmentation/condition-variables.ts b/src/core/database/query-augmentation/condition-variables.ts index a4c96705ff..533e90bcf0 100644 --- a/src/core/database/query-augmentation/condition-variables.ts +++ b/src/core/database/query-augmentation/condition-variables.ts @@ -26,22 +26,14 @@ ParameterBag.prototype.addParam = function addParam( value: any | Variable, name?: string, ) { - return value instanceof Variable - ? value - : origAddParam.call(this, value, name); + return value instanceof Variable ? value : origAddParam.call(this, value, name); }; -Pattern.prototype.setExpandedConditions = function ( - this: Pattern, - expanded: boolean, -) { +Pattern.prototype.setExpandedConditions = function (this: Pattern, expanded: boolean) { if (this.useExpandedConditions !== expanded) { // If trying to join conditions into a single parameter check if there are any variables being referenced. // If so, ignore this as we need it expanded for query to be correct. - if ( - !expanded && - Object.values(this.conditions).some((cond) => cond instanceof Variable) - ) { + if (!expanded && Object.values(this.conditions).some((cond) => cond instanceof Variable)) { return; } this.useExpandedConditions = expanded; diff --git a/src/core/database/query-augmentation/foreach.ts b/src/core/database/query-augmentation/foreach.ts index 0050c7d3ea..0d442f887c 100644 --- a/src/core/database/query-augmentation/foreach.ts +++ b/src/core/database/query-augmentation/foreach.ts @@ -26,11 +26,7 @@ declare module 'cypher-query-builder/dist/typings/query' { * * @see https://neo4j.com/docs/cypher-manual/current/clauses/foreach/ */ - forEach( - variable: string, - list: string, - each: string | ((query: Query) => Query), - ): this; + forEach(variable: string, list: string, each: string | ((query: Query) => Query)): this; } } @@ -47,18 +43,11 @@ Query.prototype.forEach = function forEach( }; class ForEachClause extends SubClauseCollection { - constructor( - private readonly variable: string, - private readonly list: string, - ) { + constructor(private readonly variable: string, private readonly list: string) { super(); } build() { - return this.wrapBuild( - `FOREACH (${this.variable} IN ${this.list} | `, - ')', - super.build(), - ); + return this.wrapBuild(`FOREACH (${this.variable} IN ${this.list} | `, ')', super.build()); } } diff --git a/src/core/database/query-augmentation/interpolate.ts b/src/core/database/query-augmentation/interpolate.ts index 41f5730875..9fa26be629 100644 --- a/src/core/database/query-augmentation/interpolate.ts +++ b/src/core/database/query-augmentation/interpolate.ts @@ -53,18 +53,14 @@ function stringifyValue(value: unknown): string { : `'${neo.Integer.toString(value)}'`; } if (typeof value === 'object') { - const pairs = entries(value).map( - ([key, el]) => `${quoteKey(key)}: ${stringifyValue(el)}`, - ); + const pairs = entries(value).map(([key, el]) => `${quoteKey(key)}: ${stringifyValue(el)}`); const str = pairs.join(', '); return `{ ${str} }`; } return ''; } -export const quoteKey = (key: string): string => - SAFE_KEY.exec(key) ? key : `\`${key}\``; +export const quoteKey = (key: string): string => (SAFE_KEY.exec(key) ? key : `\`${key}\``); const SAFE_KEY = /^[a-zA-Z][a-zA-Z0-9]*$/; -const isNeoInteger = (value: unknown): value is Integer => - neo.Integer.isInteger(value as any); +const isNeoInteger = (value: unknown): value is Integer => neo.Integer.isInteger(value as any); diff --git a/src/core/database/query-augmentation/pattern-formatting.ts b/src/core/database/query-augmentation/pattern-formatting.ts index 05f64b1f20..a755f78970 100644 --- a/src/core/database/query-augmentation/pattern-formatting.ts +++ b/src/core/database/query-augmentation/pattern-formatting.ts @@ -55,11 +55,7 @@ const origStringifyTerm = TermListClause.prototype.stringifyTerm; // @ts-expect-error private but we are replacing it TermListClause.prototype.stringifyTerm = function stringifyTerm(term: Term) { const stripped = - typeof term === 'string' - ? stripIndent(term) - : isExp(term) - ? term.toString() - : term; + typeof term === 'string' ? stripIndent(term) : isExp(term) ? term.toString() : term; // Remove empty strings, so they don't cause problems with double commas if (!stripped) { return []; @@ -94,10 +90,7 @@ for (const Cls of [Match, Create, Merge]) { }; } -ClauseCollection.prototype.addClause = function addClause( - this: ClauseCollection, - clause: Clause, -) { +ClauseCollection.prototype.addClause = function addClause(this: ClauseCollection, clause: Clause) { // Merge sibling where clauses into a single where clause if (clause instanceof Where && this.clauses.at(-1) instanceof Where) { const prev = this.clauses.at(-1) as Where; @@ -122,9 +115,7 @@ ClauseCollection.prototype.build = function build(this: ClauseCollection) { // (:User { id: "" }) -> $userId instead of $id // eslint-disable-next-line @typescript-eslint/unbound-method const origRebind = NodePattern.prototype.rebindConditionParams; -NodePattern.prototype.rebindConditionParams = function rebindConditionParams( - this: NodePattern, -) { +NodePattern.prototype.rebindConditionParams = function rebindConditionParams(this: NodePattern) { origRebind.call(this); const params = isPlainObject(this.conditionParams) ? (this.conditionParams as Record) diff --git a/src/core/database/query-augmentation/subquery.ts b/src/core/database/query-augmentation/subquery.ts index 0aa17bfed3..6d23458dab 100644 --- a/src/core/database/query-augmentation/subquery.ts +++ b/src/core/database/query-augmentation/subquery.ts @@ -25,9 +25,7 @@ declare module 'cypher-query-builder/dist/typings/query' { * ) * .return(['x', 'y']) */ - subQuery( - sub: (query: Query) => Query, - ): Query; + subQuery(sub: (query: Query) => Query): Query; subQuery( importVars: Many, sub: (query: Query) => Query, @@ -37,9 +35,7 @@ declare module 'cypher-query-builder/dist/typings/query' { Query.prototype.subQuery = function subQuery( this: Query, - subOrImport: - | Many - | ((query: Query) => void), + subOrImport: Many | ((query: Query) => void), maybeSub?: (query: Query) => void, ) { const subClause = new SubQueryClause(); diff --git a/src/core/database/query-augmentation/summary.ts b/src/core/database/query-augmentation/summary.ts index 044ff2f1bb..2581b58e3b 100644 --- a/src/core/database/query-augmentation/summary.ts +++ b/src/core/database/query-augmentation/summary.ts @@ -30,55 +30,50 @@ declare module 'cypher-query-builder/dist/typings/query' { } // Same body as `connection.run` other than the `summary()` call -Connection.prototype.executeAndReturnSummary = - async function executeAndReturnSummary(this: Connection, query: Query) { - if (!this.open) { - throw new Error('Cannot run query; connection is not open.'); - } +Connection.prototype.executeAndReturnSummary = async function executeAndReturnSummary( + this: Connection, + query: Query, +) { + if (!this.open) { + throw new Error('Cannot run query; connection is not open.'); + } - if (query.getClauses().length === 0) { - throw new Error('Cannot run query: no clauses attached to the query.'); - } + if (query.getClauses().length === 0) { + throw new Error('Cannot run query: no clauses attached to the query.'); + } - const session = this.session(); - if (!session) { - throw new Error('Cannot run query: connection is not open.'); - } + const session = this.session(); + if (!session) { + throw new Error('Cannot run query: connection is not open.'); + } - const queryObj = query.buildQueryObject(); + const queryObj = query.buildQueryObject(); - try { - return await session.run(queryObj.query, queryObj.params).summary(); - } finally { - await session.close(); - } - }; + try { + return await session.run(queryObj.query, queryObj.params).summary(); + } finally { + await session.close(); + } +}; // Same body as `Query.run` just a different connection call. -Query.prototype.executeAndReturnSummary = - async function executeAndReturnSummary(this: Query) { - if (!this.connection) { - throw new Error('Cannot run query; no connection object available.'); - } +Query.prototype.executeAndReturnSummary = async function executeAndReturnSummary(this: Query) { + if (!this.connection) { + throw new Error('Cannot run query; no connection object available.'); + } - return await this.connection.executeAndReturnSummary(this); - }; + return await this.connection.executeAndReturnSummary(this); +}; -Query.prototype.executeAndReturnStats = async function executeAndReturnStats( - this: Query, -) { +Query.prototype.executeAndReturnStats = async function executeAndReturnStats(this: Query) { const summary = await this.executeAndReturnSummary(); return summary.updateStatistics.updates(); }; -Query.prototype.executeAndLogStats = async function executeAndLogStats( - this: Query, -) { +Query.prototype.executeAndLogStats = async function executeAndLogStats(this: Query) { const summary = await this.executeAndReturnSummary(); const stats = summary.updateStatistics.updates(); - const filteredStats = Object.fromEntries( - Object.entries(stats).filter(([_, num]) => num > 0), - ); + const filteredStats = Object.fromEntries(Object.entries(stats).filter(([_, num]) => num > 0)); const name = String((this as any).name ?? 'Query'); Logger.log({ message: name, ...filteredStats }, 'database:results:stats'); return stats; diff --git a/src/core/database/query-augmentation/yield.ts b/src/core/database/query-augmentation/yield.ts index e885b756b8..c926d29bfa 100644 --- a/src/core/database/query-augmentation/yield.ts +++ b/src/core/database/query-augmentation/yield.ts @@ -1,10 +1,4 @@ -import { - isNotFalsy, - isPlainObject, - many, - type Many, - type Nil, -} from '@seedcompany/common'; +import { isNotFalsy, isPlainObject, many, type Many, type Nil } from '@seedcompany/common'; import { Clause, Query } from 'cypher-query-builder'; declare module 'cypher-query-builder/dist/typings/query' { diff --git a/src/core/database/query/create-node.ts b/src/core/database/query/create-node.ts index ffec3d30f9..aab29102ac 100644 --- a/src/core/database/query/create-node.ts +++ b/src/core/database/query/create-node.ts @@ -15,16 +15,13 @@ import { Variable } from '../query-augmentation/condition-variables'; export interface CreateNodeOptions> { initialProps?: InitialPropsOf< - UnsecuredDto> & - Partial> + UnsecuredDto> & Partial> >; baseNodeProps?: Record; } type InitialPropsOf = { - [K in keyof T & string]?: - | Variable - | (T[K] & {} extends FileId | LinkTo<'File'> ? ID : T[K]); + [K in keyof T & string]?: Variable | (T[K] & {} extends FileId | LinkTo<'File'> ? ID : T[K]); }; /** diff --git a/src/core/database/query/create-relationships.ts b/src/core/database/query/create-relationships.ts index a58dbd3a4c..57fe02e239 100644 --- a/src/core/database/query/create-relationships.ts +++ b/src/core/database/query/create-relationships.ts @@ -10,16 +10,11 @@ import { currentUser } from './matching'; type RelationshipDefinition = Record< string, - | [ - baseNodeLabel: keyof ResourceMap | 'BaseNode', - id: Nullable | readonly ID[], - ] + | [baseNodeLabel: keyof ResourceMap | 'BaseNode', id: Nullable | readonly ID[]] | Variable | typeof currentUser >; -type AnyDirectionalDefinition = Partial< - Record ->; +type AnyDirectionalDefinition = Partial>; /** * Creates relationships to/from `node` to/from other base nodes. @@ -95,27 +90,24 @@ export function createRelationships>( ? { [directionOrDefinition]: maybeLabelsToRelationships } : directionOrDefinition; - const flattened = Object.entries(normalizedArgs).flatMap( - ([direction, relationships]) => - Object.entries(relationships ?? {}).flatMap(([relLabel, varOrTuple]) => - many(Array.isArray(varOrTuple) ? varOrTuple[1] ?? [] : varOrTuple).map( - (id, i) => ({ - nodeLabel: Array.isArray(varOrTuple) ? varOrTuple[0] : undefined, // no labels for variables - id, - direction: direction as RelationDirection, - relLabel: relLabel, - variable: !Array.isArray(varOrTuple) - ? varOrTuple instanceof Variable - ? varOrTuple.value - : currentUser.is(varOrTuple) - ? relLabel - : undefined - : Array.isArray(varOrTuple[1]) - ? `${relLabel}${i}` - : relLabel, - }), - ), - ), + const flattened = Object.entries(normalizedArgs).flatMap(([direction, relationships]) => + Object.entries(relationships ?? {}).flatMap(([relLabel, varOrTuple]) => + many(Array.isArray(varOrTuple) ? varOrTuple[1] ?? [] : varOrTuple).map((id, i) => ({ + nodeLabel: Array.isArray(varOrTuple) ? varOrTuple[0] : undefined, // no labels for variables + id, + direction: direction as RelationDirection, + relLabel: relLabel, + variable: !Array.isArray(varOrTuple) + ? varOrTuple instanceof Variable + ? varOrTuple.value + : currentUser.is(varOrTuple) + ? relLabel + : undefined + : Array.isArray(varOrTuple[1]) + ? `${relLabel}${i}` + : relLabel, + })), + ), ); if (flattened.length === 0) { @@ -130,9 +122,7 @@ export function createRelationships>( const createdAt = DateTime.local(); - const returnTerms = flattened.flatMap((f) => - f.id instanceof Variable ? [] : f.variable ?? [], - ); + const returnTerms = flattened.flatMap((f) => (f.id instanceof Variable ? [] : f.variable ?? [])); if (returnTerms.length === 0) { // Create hash based on input to use as a unique return since a return // statement is required for sub-queries but not needed here. @@ -146,12 +136,7 @@ export function createRelationships>( return query.comment` createRelationships(${resource.name}) `.subQuery( - [ - 'node', - ...flattened.flatMap(({ id }) => - id instanceof Variable ? id.value : [], - ), - ], + ['node', ...flattened.flatMap(({ id }) => (id instanceof Variable ? id.value : []))], (sub) => sub .match( @@ -170,11 +155,7 @@ export function createRelationships>( // When creating inside of changeset, all relationships into the // node (besides changeset relation) are marked as inactive until // changeset is applied - active: !( - inChangeset && - direction === 'in' && - relLabel !== 'changeset' - ), + active: !(inChangeset && direction === 'in' && relLabel !== 'changeset'), createdAt, }), node(variable), diff --git a/src/core/database/query/cypher-expression.ts b/src/core/database/query/cypher-expression.ts index a88390a77b..06bf061b65 100644 --- a/src/core/database/query/cypher-expression.ts +++ b/src/core/database/query/cypher-expression.ts @@ -81,17 +81,13 @@ const buildExp = (exp: ExpressionInput): string => { } const list = exp.filter((e) => e !== undefined).map(buildExp); - return shouldMultiline(list) - ? `[${makeMultiline(list)}]` - : `[${list.join(', ')}]`; + return shouldMultiline(list) ? `[${makeMultiline(list)}]` : `[${list.join(', ')}]`; } const pairs = Object.entries(exp).flatMap(([key, value]) => value !== undefined ? `${quoteKey(key)}: ${buildExp(value)}` : [], ); - return shouldMultiline(pairs) - ? `{${makeMultiline(pairs)}}` - : `{ ${pairs.join(', ')} }`; + return shouldMultiline(pairs) ? `{${makeMultiline(pairs)}}` : `{ ${pairs.join(', ')} }`; }; const shouldMultiline = (list: string[]) => { diff --git a/src/core/database/query/cypher-functions.ts b/src/core/database/query/cypher-functions.ts index 71d905a018..2e57994645 100644 --- a/src/core/database/query/cypher-functions.ts +++ b/src/core/database/query/cypher-functions.ts @@ -104,8 +104,7 @@ export const apoc = { /** * @see https://neo4j.com/docs/apoc/current/overview/apoc.text/apoc.text.join/ */ - join: (list: ExpressionInput, delimiter: string) => - fn('apoc.text.join')(list, delimiter), + join: (list: ExpressionInput, delimiter: string) => fn('apoc.text.join')(list, delimiter), }, }; @@ -151,11 +150,8 @@ export const reduce = ( /** * @see https://neo4j.com/docs/cypher-manual/current/functions/predicate/#functions-any */ -export const any = ( - variable: string, - list: ExpressionInput, - predicate: ExpressionInput, -) => fn('any')(`${variable} IN ${exp(list)} WHERE ${exp(predicate)}`); +export const any = (variable: string, list: ExpressionInput, predicate: ExpressionInput) => + fn('any')(`${variable} IN ${exp(list)} WHERE ${exp(predicate)}`); export const db = { index: { diff --git a/src/core/database/query/deletes.ts b/src/core/database/query/deletes.ts index e38f194b4d..e86c2b5f4b 100644 --- a/src/core/database/query/deletes.ts +++ b/src/core/database/query/deletes.ts @@ -83,12 +83,8 @@ export const prefixNodeLabelsWithDeleted = (node: string) => (query: Query) => `, ]) // yielding node is necessary even though unused - .raw( - `call apoc.create.removeLabels(${node}, labels(${node})) yield node as nodeRemoved`, - ) + .raw(`call apoc.create.removeLabels(${node}, labels(${node})) yield node as nodeRemoved`) .with([node, 'deletedLabels']) // Is this really needed? - .raw( - `call apoc.create.addLabels(${node}, deletedLabels) yield node as nodeAdded`, - ) + .raw(`call apoc.create.addLabels(${node}, deletedLabels) yield node as nodeAdded`) .return('1 as deletedPrefixDone'), ); diff --git a/src/core/database/query/filters.ts b/src/core/database/query/filters.ts index cf40cc0c8e..6059ddbfeb 100644 --- a/src/core/database/query/filters.ts +++ b/src/core/database/query/filters.ts @@ -1,10 +1,4 @@ -import { - cleanSplit, - entries, - many, - type Many, - type Nil, -} from '@seedcompany/common'; +import { cleanSplit, entries, many, type Many, type Nil } from '@seedcompany/common'; import { comparisions, greaterThan, @@ -54,9 +48,7 @@ export const define = (filters) => builder(filters ?? {}, builders); -export type FilterFn> = ( - filters: T | Nil, -) => (query: Query) => void; +export type FilterFn> = (filters: T | Nil) => (query: Query) => void; /** * A helper to split filters given and call their respective functions. @@ -117,9 +109,7 @@ export const baseNodeProp = ({ key, value }) => ({ [`node.${prop ?? key}`]: value }); export const propPartialVal = - , string>>( - prop?: string, - ): Builder => + , string>>(prop?: string): Builder => ({ key, value: v, query }) => { const value = v as string; // don't know why TS can't figure this out if (!value.trim()) { @@ -134,23 +124,15 @@ export const propPartialVal = }; export const intersectsProp = - , readonly string[]>>( - prop?: string, - ): Builder => + , readonly string[]>>(prop?: string): Builder => ({ key, value, query }) => { prop ??= key; - query.match([ - node('node'), - relation('out', '', prop, ACTIVE), - node(prop, 'Property'), - ]); + query.match([node('node'), relation('out', '', prop, ACTIVE), node(prop, 'Property')]); return { [`${prop}.value`]: intersects(value as readonly string[], prop) }; }; export const stringListProp = - , readonly string[]>>( - prop?: string, - ): Builder => + , readonly string[]>>(prop?: string): Builder => ({ key, value, query }) => { query.match([ node('node'), @@ -161,9 +143,7 @@ export const stringListProp = }; export const stringListBaseNodeProp = - , readonly string[]>>( - prop?: string, - ): Builder => + , readonly string[]>>(prop?: string): Builder => ({ key, value }) => ({ node: { [prop ?? key]: inArray(value as any) }, }); @@ -188,17 +168,11 @@ export const isPinned = pathExists<{ pinned?: boolean }, 'pinned'>([ node('node'), ]); -export const dateTimeBaseNodeProp = < - T, - K extends ConditionalKeys, ->( +export const dateTimeBaseNodeProp = >( prop?: string, ): Builder => dateTime(({ key }) => `node.${prop ?? key}`); -export const dateTimeProp = < - T, - K extends ConditionalKeys, ->( +export const dateTimeProp = >( prop?: string, ): Builder => dateTime(({ key, query }) => { @@ -219,9 +193,7 @@ export const dateTime = return comparator ? { [getLhs(args)]: comparator } : null; }; -export const comparisonOfDateTimeFilter = ( - input: DateTimeFilter, -): Comparator | undefined => { +export const comparisonOfDateTimeFilter = (input: DateTimeFilter): Comparator | undefined => { const comparatorMap = { afterInclusive: comparisions.greaterEqualTo, after: comparisions.greaterThan, @@ -304,9 +276,7 @@ export const fullText = const normalized = normalizeInput ? normalizeInput(value) : value; - let input = separateQueryForEachWord - ? cleanSplit(normalized, ' ') - : [normalized]; + let input = separateQueryForEachWord ? cleanSplit(normalized, ' ') : [normalized]; input = escapeLucene ? input.map(escapeLuceneSyntax) : input; diff --git a/src/core/database/query/full-text.ts b/src/core/database/query/full-text.ts index a8f2d56773..dfa7891165 100644 --- a/src/core/database/query/full-text.ts +++ b/src/core/database/query/full-text.ts @@ -1,10 +1,4 @@ -import { - entries, - isNotNil, - many, - type Many, - mapKeys, -} from '@seedcompany/common'; +import { entries, isNotNil, many, type Many, mapKeys } from '@seedcompany/common'; import { type Query } from 'cypher-query-builder'; import { pickBy } from 'lodash'; import { type LiteralUnion } from 'type-fest'; @@ -48,8 +42,7 @@ export const FullTextIndex = (config: { const options = entries(pickBy(parsedConfig, (v) => v !== undefined)).length > 0 ? { - indexConfig: mapKeys(parsedConfig, (k) => `fulltext.${k}`) - .asRecord, + indexConfig: mapKeys(parsedConfig, (k) => `fulltext.${k}`).asRecord, } : undefined; const query = ` @@ -105,8 +98,7 @@ export const IndexFullTextQueryNodes = ( procedure('db.index.fulltext.queryNodes', ['node', 'score'])({ indexName, query, - ...(options && - (Object.values(options).filter(isNotNil).length > 0 || isExp(options)) + ...(options && (Object.values(options).filter(isNotNil).length > 0 || isExp(options)) ? { options } : undefined), }); diff --git a/src/core/database/query/lists.ts b/src/core/database/query/lists.ts index ab46d89d5b..aa5b3be55a 100644 --- a/src/core/database/query/lists.ts +++ b/src/core/database/query/lists.ts @@ -1,9 +1,5 @@ import { type Query } from 'cypher-query-builder'; -import { - type ID, - type PaginatedListType, - type PaginationInput, -} from '~/common'; +import { type ID, type PaginatedListType, type PaginationInput } from '~/common'; import { collect } from './cypher-functions'; /** @@ -18,10 +14,7 @@ import { collect } from './cypher-functions'; * Note that only `node` is available in this function. */ export const paginate = - ( - { count, page }: PaginationInput, - hydrate?: (query: Query) => Query<{ dto: R }>, - ) => + ({ count, page }: PaginationInput, hydrate?: (query: Query) => Query<{ dto: R }>) => (query: Query) => { let list = 'list'; const params: Record = { diff --git a/src/core/database/query/match-changeset-and-changed-props.ts b/src/core/database/query/match-changeset-and-changed-props.ts index 5468022784..34a1898c1d 100644 --- a/src/core/database/query/match-changeset-and-changed-props.ts +++ b/src/core/database/query/match-changeset-and-changed-props.ts @@ -2,20 +2,17 @@ import { node, type Query } from 'cypher-query-builder'; import { type ID } from '~/common'; import { matchProps } from './matching'; -export const matchChangesetAndChangedProps = - (changeset?: ID) => (query: Query) => { - query.comment`matchChangesetAndChangedProps()`; - return changeset - ? query - .apply( - matchProps({ - view: { changeset }, - outputVar: 'changedProps', - optional: true, - }), - ) - .match(node('changeset', 'Changeset', { id: changeset })) - : query.subQuery((sub) => - sub.return(['null as changeset', '{} as changedProps']), - ); - }; +export const matchChangesetAndChangedProps = (changeset?: ID) => (query: Query) => { + query.comment`matchChangesetAndChangedProps()`; + return changeset + ? query + .apply( + matchProps({ + view: { changeset }, + outputVar: 'changedProps', + optional: true, + }), + ) + .match(node('changeset', 'Changeset', { id: changeset })) + : query.subQuery((sub) => sub.return(['null as changeset', '{} as changedProps'])); +}; diff --git a/src/core/database/query/match-project-based-props.ts b/src/core/database/query/match-project-based-props.ts index 857060714b..6114cd90ee 100644 --- a/src/core/database/query/match-project-based-props.ts +++ b/src/core/database/query/match-project-based-props.ts @@ -4,20 +4,8 @@ import { type Role, type Sensitivity } from '~/common'; import { type QueryFragment } from '~/core/database/query'; import { type ScopedRole } from '../../../components/authorization/dto'; import { ProjectType } from '../../../components/project/dto/project-type.enum'; -import { - apoc, - coalesce, - collect, - listConcat, - merge, - reduce, -} from './cypher-functions'; -import { - ACTIVE, - currentUser, - matchProps, - type MatchPropsOptions, -} from './matching'; +import { apoc, coalesce, collect, listConcat, merge, reduce } from './cypher-functions'; +import { ACTIVE, currentUser, matchProps, type MatchPropsOptions } from './matching'; export const matchPropsAndProjectSensAndScopedRoles = (propsOptions?: MatchPropsOptions) => @@ -99,9 +87,7 @@ export const matchProjectSens = sub .with(projectVar) // import .with(projectVar) // needed for where clause - .raw( - `WHERE ${projectVar} IS NOT NULL AND ${projectVar}.type = "${ProjectType.Internship}"`, - ) + .raw(`WHERE ${projectVar} IS NOT NULL AND ${projectVar}.type = "${ProjectType.Internship}"`) .match([ node(projectVar), relation('out', '', 'sensitivity', ACTIVE), @@ -140,19 +126,12 @@ export const matchProjectSens = ); export const matchUserGloballyScopedRoles = - ( - userVar: string, - outputVar = 'globalRoles' as Output, - ) => + (userVar: string, outputVar = 'globalRoles' as Output) => (query: Query) => query.comment('matchUserGloballyScopedRoles()').subQuery((sub) => sub .with(userVar) - .match([ - node(userVar), - relation('out', '', 'roles', ACTIVE), - node('role', 'Property'), - ]) + .match([node(userVar), relation('out', '', 'roles', ACTIVE), node('role', 'Property')]) .return<{ [K in Output]: readonly Role[] }>( apoc.coll.flatten(collect('role.value')).as(outputVar), ), diff --git a/src/core/database/query/matching.ts b/src/core/database/query/matching.ts index 06fa0f6a60..a9e1f3ded8 100644 --- a/src/core/database/query/matching.ts +++ b/src/core/database/query/matching.ts @@ -1,9 +1,4 @@ -import { - node, - type NodePattern, - type Query, - relation, -} from 'cypher-query-builder'; +import { node, type NodePattern, type Query, relation } from 'cypher-query-builder'; import { uniq } from 'lodash'; import { DateTime } from 'luxon'; import { type Tagged } from 'type-fest'; @@ -21,8 +16,7 @@ const makeCurrentUser = (name: string) => ) as Tagged, { as: makeCurrentUser, - is: (obj: object): obj is typeof currentUser => - Object.hasOwn(obj, currentUserFlag), + is: (obj: object): obj is typeof currentUser => Object.hasOwn(obj, currentUserFlag), }, ); export const currentUser = makeCurrentUser(''); @@ -78,9 +72,7 @@ export const matchProps = (options: MatchPropsOptions = {}) => { ] : []), ]); - const collectProps = collect( - apoc.map.fromValues(['type(r)', 'prop.value']), - ); + const collectProps = collect(apoc.map.fromValues(['type(r)', 'prop.value'])); return query.comment`matchProps(${nodeName})`.subQuery(nodeName, (sub) => // If optional match in another sub-query where the return clause's @@ -90,21 +82,11 @@ export const matchProps = (options: MatchPropsOptions = {}) => { // with complex queries. // Error is "Tried overwriting already taken variable name" with v4.2.8 (optional - ? sub.subQuery(nodeName, (sub2) => - sub2.apply(lookupProps).return(collectProps.as('props')), - ) + ? sub.subQuery(nodeName, (sub2) => sub2.apply(lookupProps).return(collectProps.as('props'))) : sub.apply(lookupProps) ) - .with([ - nodeName, - `${optional ? 'props' : collectProps} as collectedProps`, - ]) - .with( - listConcat( - `[${excludeBaseProps ? '' : nodeName}]`, - 'collectedProps', - ).as('propList'), - ) + .with([nodeName, `${optional ? 'props' : collectProps} as collectedProps`]) + .with(listConcat(`[${excludeBaseProps ? '' : nodeName}]`, 'collectedProps').as('propList')) .return(merge('propList').as(outputVar)), ); }; @@ -129,8 +111,4 @@ export const property = ( ], ]; -export const pinned = exists([ - currentUser, - relation('out', '', 'pinned'), - node('node'), -]); +export const pinned = exists([currentUser, relation('out', '', 'pinned'), node('node')]); diff --git a/src/core/database/query/properties/create-property.ts b/src/core/database/query/properties/create-property.ts index a9e2e3ad54..6010f042b1 100644 --- a/src/core/database/query/properties/create-property.ts +++ b/src/core/database/query/properties/create-property.ts @@ -48,9 +48,7 @@ export const createProperty = (query: Query) => { resource = resource ? EnhancedResource.of(resource) : undefined; - const now = ( - nowIn ?? query.params.addParam(DateTime.now(), 'now') - ).toString(); + const now = (nowIn ?? query.params.addParam(DateTime.now(), 'now')).toString(); // Grab labels for property if it's statically given. // Also, do not give properties unique labels if inside a changeset. @@ -75,18 +73,12 @@ export const createProperty = ? q .optionalMatch([ node(nodeName), - relation( - 'out', - 'existingPropRel', - key instanceof Variable ? [] : key, - ACTIVE, - ), + relation('out', 'existingPropRel', key instanceof Variable ? [] : key, ACTIVE), node('existingProp', 'Property'), ]) .apply( maybeWhereAnd( - key instanceof Variable && - `type(existingPropRel) = ${key.toString()}`, + key instanceof Variable && `type(existingPropRel) = ${key.toString()}`, // Don't create a new "change value" if the value is the same as // the value outside the changeset. `existingProp.value <> ${( @@ -104,10 +96,7 @@ export const createProperty = value, }), ...(changeset - ? [ - relation('in', '', 'changeset', ACTIVE), - node(changeset.toString()), - ] + ? [relation('in', '', 'changeset', ACTIVE), node(changeset.toString())] : []), ]) .return(['newPropNode']), diff --git a/src/core/database/query/properties/deactivate-property.ts b/src/core/database/query/properties/deactivate-property.ts index 30407651cf..79819130c3 100644 --- a/src/core/database/query/properties/deactivate-property.ts +++ b/src/core/database/query/properties/deactivate-property.ts @@ -1,10 +1,6 @@ import { node, type Query, relation } from 'cypher-query-builder'; import { DateTime } from 'luxon'; -import { - type ID, - type MaybeUnsecuredInstance, - type ResourceShape, -} from '~/common'; +import { type ID, type MaybeUnsecuredInstance, type ResourceShape } from '~/common'; import { type DbChanges } from '../../changes'; import { prefixNodeLabelsWithDeleted } from '../deletes'; import { ACTIVE, Variable, variable as varRef } from '../index'; @@ -40,9 +36,7 @@ export const deactivateProperty = }: DeactivatePropertyOptions) => (query: Query) => { const imports = [nodeName, key instanceof Variable ? key : '', changeset]; - const now = ( - nowIn ?? query.params.addParam(DateTime.now(), 'now') - ).toString(); + const now = (nowIn ?? query.params.addParam(DateTime.now(), 'now')).toString(); const docKey = key instanceof Variable ? `[${key.toString()}]` : `.${key}`; const docSignature = `deactivateProperty(${nodeName}${docKey})`; @@ -55,17 +49,10 @@ export const deactivateProperty = }), node('oldPropVar', 'Property'), ...(changeset - ? [ - relation('in', 'oldChange', 'changeset', ACTIVE), - node(changeset.toString()), - ] + ? [relation('in', 'oldChange', 'changeset', ACTIVE), node(changeset.toString())] : []), ]) - .apply( - maybeWhereAnd( - key instanceof Variable && `type(oldToProp) = ${key.toString()}`, - ), - ) + .apply(maybeWhereAnd(key instanceof Variable && `type(oldToProp) = ${key.toString()}`)) .setValues({ [`${changeset ? 'oldChange' : 'oldToProp'}.active`]: false, 'oldPropVar.deletedAt': varRef(now), diff --git a/src/core/database/query/properties/update-properties.ts b/src/core/database/query/properties/update-properties.ts index 85013dda2f..e461b46168 100644 --- a/src/core/database/query/properties/update-properties.ts +++ b/src/core/database/query/properties/update-properties.ts @@ -51,30 +51,22 @@ export const updateProperties = : [], ); - return query - .comment(`updateProperties(${resource.dbLabel})`) - .subQuery(nodeName, (sub) => - sub - .unwind(propEntries, 'prop') - .apply( - updateProperty({ - key: variable('prop.key'), - value: variable('prop.value'), - labels: variable('prop.labels'), - changeset, - nodeName, - now: - now instanceof Variable - ? now - : query.params.addParam(now ?? DateTime.local(), 'now'), - }), - ) - .return<{ - stats: { [K in keyof DbChanges]?: PropUpdateStat }; - }>( - merge(collect(apoc.map.fromValues(['prop.key', 'stats']))).as( - outputStatsVar, - ), - ), - ); + return query.comment(`updateProperties(${resource.dbLabel})`).subQuery(nodeName, (sub) => + sub + .unwind(propEntries, 'prop') + .apply( + updateProperty({ + key: variable('prop.key'), + value: variable('prop.value'), + labels: variable('prop.labels'), + changeset, + nodeName, + now: + now instanceof Variable ? now : query.params.addParam(now ?? DateTime.local(), 'now'), + }), + ) + .return<{ + stats: { [K in keyof DbChanges]?: PropUpdateStat }; + }>(merge(collect(apoc.map.fromValues(['prop.key', 'stats']))).as(outputStatsVar)), + ); }; diff --git a/src/core/database/query/properties/update-property.ts b/src/core/database/query/properties/update-property.ts index 02e14b2b20..857ad52a95 100644 --- a/src/core/database/query/properties/update-property.ts +++ b/src/core/database/query/properties/update-property.ts @@ -1,9 +1,5 @@ import { node, type Query, relation } from 'cypher-query-builder'; -import { - DateTime, - Duration, - type DurationLikeObject as MyDuration, -} from 'luxon'; +import { DateTime, Duration, type DurationLikeObject as MyDuration } from 'luxon'; import { type DurationIn, type ID, @@ -12,21 +8,10 @@ import { } from '~/common'; import { type DbChanges } from '../../changes'; import { varInExp } from '../../query-augmentation/subquery'; -import { - ACTIVE, - coalesce, - exp, - INACTIVE, - type QueryFragment, - variable, - Variable, -} from '../index'; +import { ACTIVE, coalesce, exp, INACTIVE, type QueryFragment, variable, Variable } from '../index'; import { maybeWhereAnd } from '../maybe-where-and'; import { createProperty, type CreatePropertyOptions } from './create-property'; -import { - deactivateProperty, - type DeactivatePropertyOptions, -} from './deactivate-property'; +import { deactivateProperty, type DeactivatePropertyOptions } from './deactivate-property'; export const defaultPermanentAfter: MyDuration = { minutes: 30 }; @@ -81,10 +66,7 @@ export const updateProperty = ? options.value : variable(query.params.addParam(options.value, 'value').toString()), now: options.now ?? query.params.addParam(DateTime.now(), 'now'), - permanentAfter: permanentAfterAsVar( - options.permanentAfter ?? defaultPermanentAfter, - query, - ), + permanentAfter: permanentAfterAsVar(options.permanentAfter ?? defaultPermanentAfter, query), }; const { nodeName, key, value, now } = resolved; @@ -139,11 +121,7 @@ export const updateProperty = }; const loadExistingProp = - ( - nodeName: string, - key: string | Variable, - changeset?: Variable, - ): QueryFragment => + (nodeName: string, key: string | Variable, changeset?: Variable): QueryFragment => (query) => query .optionalMatch([ @@ -155,26 +133,12 @@ const loadExistingProp = changeset ? INACTIVE : ACTIVE, ), node('existingProp', 'Property'), - ...(changeset - ? [ - relation('in', '', 'changeset', ACTIVE), - node(changeset.toString()), - ] - : []), + ...(changeset ? [relation('in', '', 'changeset', ACTIVE), node(changeset.toString())] : []), ]) - .apply( - maybeWhereAnd( - key instanceof Variable && - `type(existingPropRel) = ${key.toString()}`, - ), - ); + .apply(maybeWhereAnd(key instanceof Variable && `type(existingPropRel) = ${key.toString()}`)); export const determineIfPermanent = - ( - permanentAfter: string, - now: Variable, - nodeName = 'existingProp', - ): QueryFragment => + (permanentAfter: string, now: Variable, nodeName = 'existingProp'): QueryFragment => (query) => query.subQuery([nodeName], (sub) => sub.return( @@ -185,10 +149,7 @@ export const determineIfPermanent = ), ); -export function permanentAfterAsVar( - permanentAfter: Variable | DurationIn, - query: Query, -) { +export function permanentAfterAsVar(permanentAfter: Variable | DurationIn, query: Query) { if (permanentAfter instanceof Variable) { return permanentAfter.toString(); } diff --git a/src/core/database/query/properties/update-relation-list.ts b/src/core/database/query/properties/update-relation-list.ts index d98d910894..19f081ba89 100644 --- a/src/core/database/query/properties/update-relation-list.ts +++ b/src/core/database/query/properties/update-relation-list.ts @@ -17,19 +17,13 @@ export const updateRelationList = const relName = options.relation instanceof Variable ? options.relation - : variable( - query.params.addParam(options.relation, 'relationName').toString(), - ); + : variable(query.params.addParam(options.relation, 'relationName').toString()); const newList = options.newList instanceof Variable ? options.newList - : variable( - query.params.addParam(options.newList, 'newList').toString(), - ); + : variable(query.params.addParam(options.newList, 'newList').toString()); - query.comment( - `updateListProperty(${String(nodeName)}, ${String(relName)})`, - ); + query.comment(`updateListProperty(${String(nodeName)}, ${String(relName)})`); return query.subQuery([nodeName, relName, newList], (sub) => sub .with([ diff --git a/src/core/database/query/sorting.ts b/src/core/database/query/sorting.ts index 852cc4fe32..78fa81dedb 100644 --- a/src/core/database/query/sorting.ts +++ b/src/core/database/query/sorting.ts @@ -147,9 +147,7 @@ export const defineSorters = >( return { ...common, matcher: subCustom, sort: subField }; } - const baseNodeProps = new Set( - resource.BaseNodeProps ?? EnhancedResource.of(Resource).props, - ); + const baseNodeProps = new Set(resource.BaseNodeProps ?? EnhancedResource.of(Resource).props); const isBaseNodeProp = baseNodeProps.has(sort); const matcher = (isBaseNodeProp ? basePropSorter : propSorter)(sort); return { ...common, matcher }; @@ -164,11 +162,7 @@ export type DefinedSorters = (( export const propSorter = (prop: string) => (query: Query) => query - .match([ - node('node'), - relation('out', '', prop, ACTIVE), - node('sortProp', 'Property'), - ]) + .match([node('node'), relation('out', '', prop, ACTIVE), node('sortProp', 'Property')]) .return('sortProp.value as sortValue'); export const basePropSorter = (prop: string) => (query: Query) => @@ -179,17 +173,17 @@ export interface SortCol { } // TODO stricter -export type SortFieldOf> = - LiteralUnion; +export type SortFieldOf> = LiteralUnion< + keyof TResourceStatic['prototype'] & string, + string +>; export type SortMatcher = ( query: Query, input: Sort & SortInternals, ) => Query; -type SortMatchers = Partial< - Record> ->; +type SortMatchers = Partial>>; interface Sort { sort: Field; diff --git a/src/core/database/query/where-and-list.ts b/src/core/database/query/where-and-list.ts index 9e787a8216..d72fbabfad 100644 --- a/src/core/database/query/where-and-list.ts +++ b/src/core/database/query/where-and-list.ts @@ -13,8 +13,7 @@ export class WhereAndList extends WhereOp { evaluate(params: ParameterBag, precedence = Precedence.None, name = '') { // If this operator will not be used, precedence should not be altered - const newPrecedence = - this.conditions.length < 2 ? precedence : Precedence.And; + const newPrecedence = this.conditions.length < 2 ? precedence : Precedence.And; const strings = this.conditions.map((condition) => stringCons(params, condition, newPrecedence, name), ); diff --git a/src/core/database/query/where-path.ts b/src/core/database/query/where-path.ts index 9b2acf5892..88baba1ee5 100644 --- a/src/core/database/query/where-path.ts +++ b/src/core/database/query/where-path.ts @@ -6,9 +6,7 @@ import { type Comparator } from 'cypher-query-builder/dist/typings/clauses/where * A path condition for a WHERE clause. * It doesn't matter what key this is assigned to it - it is ignored. */ -export const path = ( - pattern: Exclude, -): Comparator => { +export const path = (pattern: Exclude): Comparator => { // Using merge as shortcut to compile path to string. const clause = new Merge(pattern); return (params) => { diff --git a/src/core/database/results/types.ts b/src/core/database/results/types.ts index 6270bd05ce..98cf704583 100644 --- a/src/core/database/results/types.ts +++ b/src/core/database/results/types.ts @@ -5,6 +5,4 @@ export type NativeDbProps> = { [Key in keyof Dto]: Dto[Key] extends NativeDbValue ? Dto[Key] : unknown; }; -export type NativeDbValue = Many< - boolean | string | number | DateTime | RichTextDocument | null ->; +export type NativeDbValue = Many; diff --git a/src/core/database/rollback-manager.ts b/src/core/database/rollback-manager.ts index 46a89df7a1..9a514895d2 100644 --- a/src/core/database/rollback-manager.ts +++ b/src/core/database/rollback-manager.ts @@ -10,10 +10,7 @@ interface RollbackRef { @Injectable() export class RollbackManager { - private readonly functionsByContext = new WeakMap< - GqlContextHost['context'], - Set - >(); + private readonly functionsByContext = new WeakMap>(); constructor(private readonly contextHost: GqlContextHost) {} diff --git a/src/core/database/split-db.provider.ts b/src/core/database/split-db.provider.ts index a19fe39058..0e061f956c 100644 --- a/src/core/database/split-db.provider.ts +++ b/src/core/database/split-db.provider.ts @@ -3,16 +3,12 @@ import { ModuleRef } from '@nestjs/core'; import type { PublicOf } from '~/common'; import { ConfigService } from '../config/config.service'; -export const splitDb = ( - neo4jRepository: Type, - gelRepository: Type>, -) => +export const splitDb = (neo4jRepository: Type, gelRepository: Type>) => ({ provide: neo4jRepository, inject: [ModuleRef, ConfigService], useFactory: async (moduleRef: ModuleRef, config: ConfigService) => { - const cls = - config.databaseEngine === 'gel' ? gelRepository : neo4jRepository; + const cls = config.databaseEngine === 'gel' ? gelRepository : neo4jRepository; return await moduleRef.create(cls); }, } satisfies Provider); diff --git a/src/core/database/transaction.ts b/src/core/database/transaction.ts index 950ca1a70d..b3beb34b59 100644 --- a/src/core/database/transaction.ts +++ b/src/core/database/transaction.ts @@ -88,9 +88,7 @@ Connection.prototype.runInTransaction = async function withTransaction( // @ts-expect-error not typed, but js is there. const isExistingRead = outer._connectionHolder._mode === 'READ'; if (isExistingRead && options?.mode !== 'read') { - throw new ServerException( - 'A write transaction cannot be started within a read transaction', - ); + throw new ServerException('A write transaction cannot be started within a read transaction'); } return await inner(); @@ -123,8 +121,7 @@ Connection.prototype.runInTransaction = async function withTransaction( const maybeRetryableError = getCauseList(error).find(isNeo4jError); if (maybeRetryableError) { errorMap.set(maybeRetryableError, error); - const override = - this.retryInformer.shouldRetry(maybeRetryableError); + const override = this.retryInformer.shouldRetry(maybeRetryableError); if (override != null) { maybeRetryableError.retriable = override; } @@ -134,9 +131,7 @@ Connection.prototype.runInTransaction = async function withTransaction( } }, { - timeout: options?.timeout - ? Duration.from(options.timeout).toMillis() - : undefined, + timeout: options?.timeout ? Duration.from(options.timeout).toMillis() : undefined, metadata: options?.metadata, }, ); diff --git a/src/core/database/transactional.decorator.ts b/src/core/database/transactional.decorator.ts index 4acf95f9e0..ff07442397 100644 --- a/src/core/database/transactional.decorator.ts +++ b/src/core/database/transactional.decorator.ts @@ -31,9 +31,7 @@ export function Transactional(options?: TransactionOptions) { const clsName: string = target.constructor.name; const methodDescription = - typeof methodName === 'symbol' - ? methodName.description ?? 'symbol' - : methodName; + typeof methodName === 'symbol' ? methodName.description ?? 'symbol' : methodName; const initiator = `${clsName}.${methodDescription}`; // Wrap the method in a runInTransaction call @@ -41,16 +39,13 @@ export function Transactional(options?: TransactionOptions) { descriptor.value = async function (...args: any[]) { // @ts-expect-error this works but TS still has problems with indexing on symbols const connection: Connection = this[ConnKey]; - return await connection.runInTransaction( - () => origMethod.apply(this, args), - { - ...options, - metadata: { - initiator, - ...options?.metadata, - }, + return await connection.runInTransaction(() => origMethod.apply(this, args), { + ...options, + metadata: { + initiator, + ...options?.metadata, }, - ); + }); }; }) as MethodDecorator; } diff --git a/src/core/database/transformer.ts b/src/core/database/transformer.ts index 4d1ca6ca6d..1667d6a07c 100644 --- a/src/core/database/transformer.ts +++ b/src/core/database/transformer.ts @@ -11,8 +11,7 @@ class PatchedTransformer extends Transformer { } } -export const isNeoDate = (value: unknown): value is Neo.Date => - Neo.isDate(value as any); +export const isNeoDate = (value: unknown): value is Neo.Date => Neo.isDate(value as any); export const isNeoDateTime = (value: unknown): value is Neo.DateTime => Neo.isDateTime(value as any); diff --git a/src/core/email/templates/base.tsx b/src/core/email/templates/base.tsx index 250933ab43..6a5d870575 100644 --- a/src/core/email/templates/base.tsx +++ b/src/core/email/templates/base.tsx @@ -20,13 +20,7 @@ import { import type { ComponentProps, ReactElement, ReactNode } from 'react'; import { useFrontendUrl } from './frontend-url'; -export const EmailTemplate = ({ - title, - children, -}: { - title: string; - children: ReactNode; -}) => ( +export const EmailTemplate = ({ title, children }: { title: string; children: ReactNode }) => ( {`${title} - CORD Field`} @@ -50,11 +44,7 @@ export const EmailTemplate = ({ - + {children} @@ -71,20 +61,10 @@ export const Theme = () => (
{}
{} - - + ); @@ -94,13 +74,7 @@ export const Branding = (): ReactElement => { return (
- + @@ -125,13 +99,7 @@ export const Heading = (props: ComponentProps) => (
); -export const Link = ({ - href, - children, -}: { - href: string; - children?: ReactNode; -}) => ( +export const Link = ({ href, children }: { href: string; children?: ReactNode }) => ( {children || href} @@ -144,8 +112,8 @@ export const ReplyInfoFooter = () => ( - If you are having any issues with your account, please don't hesitate to - contact us by replying to this email. + If you are having any issues with your account, please don't hesitate to contact us by + replying to this email.
Thanks!
diff --git a/src/core/email/templates/forgot-password.template.tsx b/src/core/email/templates/forgot-password.template.tsx index 2a29827aea..643f1415cd 100644 --- a/src/core/email/templates/forgot-password.template.tsx +++ b/src/core/email/templates/forgot-password.template.tsx @@ -22,8 +22,7 @@ export function ForgotPassword({ token }: ForgotPasswordProps) {
- If it was you, confirm the password change{' '} - by clicking this link + If it was you, confirm the password change by clicking this link diff --git a/src/core/email/templates/formatted-date-time.tsx b/src/core/email/templates/formatted-date-time.tsx index f0573dae16..fe6381ff9f 100644 --- a/src/core/email/templates/formatted-date-time.tsx +++ b/src/core/email/templates/formatted-date-time.tsx @@ -16,13 +16,7 @@ export const FormattedDateTime = (props: FormattedDateTimeProps) => { return <>{formatted}; }; -const DefaultTimezoneContext = createContext( - Settings.defaultZone, -); +const DefaultTimezoneContext = createContext(Settings.defaultZone); export const DefaultTimezoneWrapper = (zone: ZoneLike) => (el: ReactElement) => - ( - - {el} - - ); + {el}; diff --git a/src/core/email/templates/progress-report-status-changed.template.tsx b/src/core/email/templates/progress-report-status-changed.template.tsx index 414e673359..57d3f25a41 100644 --- a/src/core/email/templates/progress-report-status-changed.template.tsx +++ b/src/core/email/templates/progress-report-status-changed.template.tsx @@ -1,9 +1,4 @@ -import { - Button, - Column, - Section, - Text, -} from '@seedcompany/nestjs-email/templates'; +import { Button, Column, Section, Text } from '@seedcompany/nestjs-email/templates'; import { fiscalQuarter, fiscalYear } from '~/common'; import { type Language } from '../../../components/language/dto'; import { type PeriodicReport } from '../../../components/periodic-report/dto'; @@ -18,10 +13,7 @@ import { UserRef, type UserRefProps } from './user-ref'; export interface ProgressReportStatusChangedProps { changedBy: UserRefProps; - recipient: Pick< - User, - 'email' | 'displayFirstName' | 'displayLastName' | 'timezone' - >; + recipient: Pick; project: Pick; language: Pick; report: Pick; @@ -44,16 +36,14 @@ export function ProgressReportStatusChanged({ const projectName = project.name.value || ''; const languageName = language.name.value || ''; const reportUrl = useFrontendUrl(`/progress-reports/${report.id}`); - const reportLabel = `Quarterly Report - Q${fiscalQuarter( + const reportLabel = `Quarterly Report - Q${fiscalQuarter(report.start)} FY${fiscalYear( report.start, - )} FY${fiscalYear(report.start)}`; + )}`; const oldStatus = previousStatusVal ? ProgressReportStatus.entry(previousStatusVal).label : undefined; - const newStatus = newStatusVal - ? ProgressReportStatus.entry(newStatusVal).label - : undefined; + const newStatus = newStatusVal ? ProgressReportStatus.entry(newStatusVal).label : undefined; return ( - has changed{' '} - {reportLabel}{' '} + has changed {reportLabel}{' '} {newStatus ? ( <> {oldStatus ? ( @@ -90,10 +79,7 @@ export function ProgressReportStatusChanged({ ) : null} <> for {languageName} in {projectName} on{' '} - +