From 54bf1204620a79e4e73eea2ce423ce52b2f17946 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Mon, 26 Aug 2024 17:20:59 +0300 Subject: [PATCH 01/17] Draft: Audit log GraphQL API codegen setup fix some some some some more ? ?? SOME? ?? ? fix some? SOME ??? more some some test fix some code review! ? fix some clean! fix codegen path ??? ? ?? ? ? added scope ti auditlogmanager scheme publish & check & delete members role CLEAN MORE target project prettier fix resolver schema published lint fix code review! ? ? prettier migration fix! clean fix filters clean ?? ? resolve user eventTime: e => e.event_time, scalar DateTime user: e => { eventTime clean changes Fix up mapper names and types.next imports fix resolvers :) clean all lint clean sentry fix some code review more and more Support && Laboratory Collection CRUD query in collection User role org and target fix code review resolvers again! prettier clean revert `inviteToOrganizationByEmail` revert `updateSchemaPolicyForProject` user invite action for clickhouse --- codegen.mts | 1 + .../src/clickhouse-actions/011-audit-logs.ts | 20 + packages/migrations/src/clickhouse.ts | 1 + packages/services/api/src/create.ts | 2 + .../api/src/modules/audit-logs/helpers.ts | 16 + .../api/src/modules/audit-logs/index.ts | 13 + .../audit-logs/module.graphql.mappers.ts | 48 + .../src/modules/audit-logs/module.graphql.ts | 349 +++ .../providers/audit-logs-manager.ts | 160 ++ .../audit-logs/providers/audit-logs-types.ts | 348 +++ .../resolvers/CollectionCreatedAuditLog.ts | 20 + .../resolvers/CollectionDeletedAuditLog.ts | 19 + .../resolvers/CollectionUpdatedAuditLog.ts | 20 + ...tionInDocumentCollectionCreatedAuditLog.ts | 25 + ...tionInDocumentCollectionDeletedAuditLog.ts | 22 + ...tionInDocumentCollectionUpdatedAuditLog.ts | 23 + .../resolvers/OrganizationCreatedAuditLog.ts | 19 + .../resolvers/OrganizationDeletedAuditLog.ts | 18 + .../OrganizationSettingsUpdatedAuditLog.ts | 18 + .../OrganizationTransferredAuditLog.ts | 19 + .../OrganizationTransferredRequestAuditLog.ts | 20 + .../OrganizationUpdatedIntegrationAuditLog.ts | 20 + .../resolvers/ProjectCreatedAuditLog.ts | 19 + .../resolvers/ProjectDeletedAuditLog.ts | 19 + .../ProjectSettingsUpdatedAuditLog.ts | 19 + .../audit-logs/resolvers/Query/auditLogs.ts | 29 + .../resolvers/RoleAssignedAuditLog.ts | 21 + .../resolvers/RoleCreatedAuditLog.ts | 19 + .../resolvers/RoleDeletedAuditLog.ts | 19 + .../resolvers/RoleUpdatedAuditLog.ts | 20 + .../resolvers/SchemaCheckedAuditLog.ts | 20 + .../SchemaPolicySettingsUpdatedAuditLog.ts | 19 + .../resolvers/SchemaPublishAuditLog.ts | 23 + .../resolvers/SubscriptionCanceledAuditLog.ts | 19 + .../resolvers/SubscriptionCreatedAuditLog.ts | 21 + .../resolvers/SubscriptionUpdatedAuditLog.ts | 18 + .../resolvers/SupportTicketCreatedAuditLog.ts | 21 + .../resolvers/SupportTicketUpdatedAuditLog.ts | 19 + .../resolvers/TargetCreatedAuditLog.ts | 20 + .../resolvers/TargetDeletedAuditLog.ts | 20 + .../TargetSettingsUpdatedAuditLog.ts | 20 + .../resolvers/UserInvitedAuditLog.ts | 19 + .../resolvers/UserJoinedAuditLog.ts | 19 + .../resolvers/UserRemovedAuditLog.ts | 19 + .../resolvers/UserSettingsUpdatedAuditLog.ts | 18 + .../resolvers/Mutation/downgradeToHobby.ts | 18 + .../Mutation/generateStripePortalLink.ts | 24 +- .../resolvers/Mutation/updateOrgRateLimit.ts | 27 +- .../resolvers/Mutation/upgradeToPro.ts | 20 + .../Mutation/createCdnAccessToken.ts | 23 + .../Mutation/deleteCdnAccessToken.ts | 23 + .../api/src/modules/collection/resolvers.ts | 119 + .../Mutation/addGitHubIntegration.ts | 23 + .../resolvers/Mutation/addSlackIntegration.ts | 23 + .../Mutation/deleteGitHubIntegration.ts | 23 + .../Mutation/deleteSlackIntegration.ts | 23 + .../enableProjectNameInGithubCheck.ts | 27 +- .../Mutation/createOIDCIntegration.ts | 22 + .../Mutation/deleteOIDCIntegration.ts | 22 + .../answerOrganizationTransferRequest.ts | 19 + .../resolvers/Mutation/assignMemberRole.ts | 27 +- .../resolvers/Mutation/createMemberRole.ts | 25 +- .../resolvers/Mutation/createOrganization.ts | 17 + .../resolvers/Mutation/deleteMemberRole.ts | 25 +- .../resolvers/Mutation/deleteOrganization.ts | 19 + .../Mutation/deleteOrganizationInvitation.ts | 22 + .../Mutation/deleteOrganizationMember.ts | 22 + .../Mutation/inviteToOrganizationByEmail.ts | 24 +- .../resolvers/Mutation/joinOrganization.ts | 19 + .../resolvers/Mutation/leaveOrganization.ts | 19 + .../Mutation/requestOrganizationTransfer.ts | 23 +- .../resolvers/Mutation/updateMemberRole.ts | 32 +- .../updateOrganizationMemberAccess.ts | 22 + .../Mutation/updateOrganizationSlug.ts | 20 + .../updateSchemaPolicyForOrganization.ts | 46 + .../resolvers/Mutation/createProject.ts | 19 + .../resolvers/Mutation/deleteProject.ts | 20 + .../api/src/modules/schema/resolvers.ts | 2298 +++++++++++++++++ .../resolvers/Mutation/supportTicketCreate.ts | 21 + .../resolvers/Mutation/supportTicketReply.ts | 22 + .../target/resolvers/Mutation/createTarget.ts | 21 + .../target/resolvers/Mutation/deleteTarget.ts | 21 + ...rimental__updateTargetSchemaComposition.ts | 28 +- .../resolvers/Mutation/setTargetValidation.ts | 38 +- .../updateTargetGraphQLEndpointUrl.ts | 22 + .../resolvers/Mutation/updateTargetSlug.ts | 22 + .../updateTargetValidationSettings.ts | 27 + .../token/resolvers/Mutation/createToken.ts | 23 + .../token/resolvers/Mutation/deleteTokens.ts | 27 +- 89 files changed, 5052 insertions(+), 16 deletions(-) create mode 100644 packages/migrations/src/clickhouse-actions/011-audit-logs.ts create mode 100644 packages/services/api/src/modules/audit-logs/helpers.ts create mode 100644 packages/services/api/src/modules/audit-logs/index.ts create mode 100644 packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts create mode 100644 packages/services/api/src/modules/audit-logs/module.graphql.ts create mode 100644 packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts create mode 100644 packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/schema/resolvers.ts diff --git a/codegen.mts b/codegen.mts index 9b7f02ff63..32bd309759 100644 --- a/codegen.mts +++ b/codegen.mts @@ -47,6 +47,7 @@ const config: CodegenConfig = { excludeTypes: [ 'TokenInfoPayload', 'OrganizationByInviteCodePayload', + 'AuditLog', 'JoinOrganizationPayload', 'Schema', 'GraphQLNamedType', diff --git a/packages/migrations/src/clickhouse-actions/011-audit-logs.ts b/packages/migrations/src/clickhouse-actions/011-audit-logs.ts new file mode 100644 index 0000000000..4ebc5c4a43 --- /dev/null +++ b/packages/migrations/src/clickhouse-actions/011-audit-logs.ts @@ -0,0 +1,20 @@ +import type { Action } from '../clickhouse'; + +export const action: Action = async exec => { + await exec(` + CREATE TABLE IF NOT EXISTS "audit_log" + ( + "id" UUID DEFAULT generateUUIDv4() CODEC(ZSTD(1)), + "event_time" DateTime CODEC(ZSTD(1)), + "user_id" String CODEC(ZSTD(1)), + "user_email" String CODEC(ZSTD(1)), + "organization_id" String CODEC(ZSTD(1)), + "event_action" String CODEC(ZSTD(1)), + "metadata" String CODEC(ZSTD(1)) + ) + ENGINE = ReplacingMergeTree + ORDER BY ("event_time", "user_id", "organization_id") + TTL event_time + INTERVAL 1 YEAR + SETTINGS index_granularity = 8192 + `); +}; diff --git a/packages/migrations/src/clickhouse.ts b/packages/migrations/src/clickhouse.ts index d0b1e1f5b1..3e311b8351 100644 --- a/packages/migrations/src/clickhouse.ts +++ b/packages/migrations/src/clickhouse.ts @@ -166,6 +166,7 @@ export async function migrateClickHouse( import('./clickhouse-actions/008-daily-operations-log'), import('./clickhouse-actions/009-ttl-1-year'), import('./clickhouse-actions/010-app-deployment-operations'), + import('./clickhouse-actions/011-audit-logs'), ]); async function actionRunner(action: Action, index: number) { diff --git a/packages/services/api/src/create.ts b/packages/services/api/src/create.ts index faf2a1cf2a..f00b5d2ac9 100644 --- a/packages/services/api/src/create.ts +++ b/packages/services/api/src/create.ts @@ -5,6 +5,7 @@ import { alertsModule } from './modules/alerts'; import { WEBHOOKS_CONFIG, WebhooksConfig } from './modules/alerts/providers/tokens'; import { appDeploymentsModule } from './modules/app-deployments'; import { APP_DEPLOYMENTS_ENABLED } from './modules/app-deployments/providers/app-deployments-enabled-token'; +import { auditLogsModule } from './modules/audit-logs'; import { authModule } from './modules/auth'; import { billingModule } from './modules/billing'; import { BILLING_CONFIG, BillingConfig } from './modules/billing/providers/tokens'; @@ -82,6 +83,7 @@ const modules = [ integrationsModule, alertsModule, feedbackModule, + auditLogsModule, cdnModule, adminModule, usageEstimationModule, diff --git a/packages/services/api/src/modules/audit-logs/helpers.ts b/packages/services/api/src/modules/audit-logs/helpers.ts new file mode 100644 index 0000000000..7531b9f21a --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/helpers.ts @@ -0,0 +1,16 @@ +import { Injector } from 'graphql-modules'; +import { OrganizationManager } from '../organization/providers/organization-manager'; +import { AuditLogModel } from './providers/audit-logs-manager'; + +export function resolveRecordAuditLog(event: AuditLogModel, injector: Injector) { + const currentOrganization = injector.get(OrganizationManager).getOrganization({ + organization: event.organization_id, + }); + return { + userEmail: event.user_email, + userId: event.user_id, + organizationId: event.organization_id, + user: event.metadata.user, + organization: currentOrganization, + }; +} diff --git a/packages/services/api/src/modules/audit-logs/index.ts b/packages/services/api/src/modules/audit-logs/index.ts new file mode 100644 index 0000000000..e2c2b875a7 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/index.ts @@ -0,0 +1,13 @@ +import { createModule } from 'graphql-modules'; +import { ClickHouse } from '../operations/providers/clickhouse-client'; +import { AuditLogManager } from './providers/audit-logs-manager'; +import { resolvers } from './resolvers.generated'; +import { typeDefs } from './module.graphql'; + +export const auditLogsModule = createModule({ + id: 'audit-logs', + dirname: __dirname, + typeDefs, + resolvers, + providers: [AuditLogManager, ClickHouse], +}); diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts b/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts new file mode 100644 index 0000000000..42546e2417 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts @@ -0,0 +1,48 @@ +import { AuditLogModel } from './providers/audit-logs-manager'; + +export type AuditLogMapper = AuditLogModel; +// Schema +export type SchemaPolicySettingsUpdatedAuditLogMapper = AuditLogModel; +export type SchemaCheckedAuditLogMapper = AuditLogModel; +export type SchemaPublishAuditLogMapper = AuditLogModel; +export type SchemaDeletedAuditLogMapper = AuditLogModel; +// Organization +export type OrganizationSettingsUpdatedAuditLogMapper = AuditLogModel; +export type OrganizationTransferredAuditLogMapper = AuditLogModel; +export type OrganizationTransferredRequestAuditLogMapper = AuditLogModel; +export type OrganizationCreatedAuditLogMapper = AuditLogModel; +export type OrganizationDeletedAuditLogMapper = AuditLogModel; +export type OrganizationUpdatedIntegrationAuditLogMapper = AuditLogModel; +// Project +export type ProjectCreatedAuditLogMapper = AuditLogModel; +export type ProjectSettingsUpdatedAuditLogMapper = AuditLogModel; +export type ProjectDeletedAuditLogMapper = AuditLogModel; +// User Role +export type RoleCreatedAuditLogMapper = AuditLogModel; +export type RoleAssignedAuditLogMapper = AuditLogModel; +export type RoleDeletedAuditLogMapper = AuditLogModel; +export type RoleUpdatedAuditLogMapper = AuditLogModel; +// Support +export type SupportTicketCreatedAuditLogMapper = AuditLogModel; +export type SupportTicketUpdatedAuditLogMapper = AuditLogModel; +// Laboratory Collection +export type CollectionCreatedAuditLogMapper = AuditLogModel; +export type CollectionDeletedAuditLogMapper = AuditLogModel; +export type CollectionUpdatedAuditLogMapper = AuditLogModel; +// Laboratory Collection Operation +export type OperationInDocumentCollectionCreatedAuditLogMapper = AuditLogModel; +export type OperationInDocumentCollectionUpdatedAuditLogMapper = AuditLogModel; +export type OperationInDocumentCollectionDeletedAuditLogMapper = AuditLogModel; +// User +export type UserInvitedAuditLogMapper = AuditLogModel; +export type UserJoinedAuditLogMapper = AuditLogModel; +export type UserRemovedAuditLogMapper = AuditLogModel; +export type UserSettingsUpdatedAuditLogMapper = AuditLogModel; +// Target +export type TargetCreatedAuditLogMapper = AuditLogModel; +export type TargetSettingsUpdatedAuditLogMapper = AuditLogModel; +export type TargetDeletedAuditLogMapper = AuditLogModel; +// Subscription +export type SubscriptionCreatedAuditLogMapper = AuditLogModel; +export type SubscriptionUpdatedAuditLogMapper = AuditLogModel; +export type SubscriptionCanceledAuditLogMapper = AuditLogModel; diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts new file mode 100644 index 0000000000..a3d02d77f5 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -0,0 +1,349 @@ +import { gql } from 'graphql-modules'; + +export const typeDefs = gql` + interface AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + } + + type AuditLogIdRecord { + userId: ID! + userEmail: String! + organizationId: ID! + user: User # This one is nullable because it can be deleted! + organization: Organization # This one is nullable because it can be deleted! + } + + type AuditLogConnection { + nodes: [AuditLog!]! + total: Int! + } + + # todo: Handle the rest of the AuditLog types + input AuditLogFilter { + startDate: DateTime + endDate: DateTime + userId: ID + } + + input AuditLogPaginationInput { + limit: Int! = 25 + offset: Int! = 0 + } + + extend type Query { + auditLogs( + selector: OrganizationSelectorInput! + filter: AuditLogFilter + pagination: AuditLogPaginationInput + ): AuditLogConnection! + } + + type SchemaPolicySettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + updatedFields: JSON! + } + + type SchemaCheckedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + targetId: String! + checkId: String! + } + + type SchemaPublishAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + targetId: String! + serviceName: String + schemaVersionId: String + isSchemaPublishMissingUrlErrorSelected: Boolean! + } + + type ServiceDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + targetId: String! + serviceName: String! + } + + # Project Audit Logs + type ProjectCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + projectName: String! + } + + type ProjectSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + updatedFields: JSON! + } + + type ProjectDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + projectName: String! + } + + # User Role Audit Logs + type RoleCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + roleId: String! + roleName: String! + } + + type RoleAssignedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + roleId: String! + updatedMember: String! + previousMemberRole: String # This one is nullable because can be without a previous role + userIdAssigned: String! + } + + type RoleDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + roleId: String! + roleName: String! + } + + type RoleUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + roleId: String! + roleName: String! + updatedFields: JSON! + } + + # Support Ticket Audit Logs + type SupportTicketCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + ticketId: String! + ticketSubject: String! + ticketDescription: String! + ticketPriority: String! + } + + type SupportTicketUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + ticketId: String! + updatedFields: JSON! + } + + # Laboratory Collection Audit Logs + type CollectionCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + collectionId: String! + collectionName: String! + targetId: String! + } + + type CollectionUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + collectionId: String! + collectionName: String! + updatedFields: JSON! + } + + type CollectionDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + collectionId: String! + collectionName: String! + } + # Operation In Document Collection Audit Logs + type OperationInDocumentCollectionCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + collectionId: String! + collectionName: String! + targetId: String! + operationId: String! + operationQuery: String! + } + + type OperationInDocumentCollectionUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + collectionId: String! + collectionName: String! + operationId: String! + updatedFields: JSON! + } + + type OperationInDocumentCollectionDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + collectionId: String! + collectionName: String! + operationId: String! + } + + # Organization Audit Logs + type OrganizationSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + updatedFields: JSON! + } + + type OrganizationTransferredAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + newOwnerId: String! + newOwnerEmail: String! + } + + type OrganizationTransferredRequestAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + newOwnerId: String! + newOwnerEmail: String # This one is nullable because the mutation can fail + } + + type OrganizationCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + organizationName: String! + organizationId: String + } + + type OrganizationDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + organizationId: String + } + + type OrganizationUpdatedIntegrationAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + integrationId: String + updatedFields: JSON! + } + + # Target + type TargetCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + targetId: String! + targetName: String! + } + + type TargetSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + targetId: String! + updatedFields: JSON! + } + + type TargetDeletedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + projectId: String! + targetId: String! + targetName: String! + } + + # User + type UserInvitedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + inviteeId: String! + inviteeEmail: String! + } + + type UserJoinedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + inviteeId: String! + inviteeEmail: String! + } + + type UserRemovedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + removedUserId: String! + removedUserEmail: String! + } + + type UserSettingsUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + updatedFields: JSON! + } + + # Subscription + type SubscriptionCreatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + paymentMethodId: String + operations: Int! + previousPlan: String! + newPlan: String! + } + + type SubscriptionUpdatedAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + updatedFields: JSON! + } + + type SubscriptionCanceledAuditLog implements AuditLog { + id: ID! + eventTime: DateTime! + record: AuditLogIdRecord! + previousPlan: String! + newPlan: String! + } +`; diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts new file mode 100644 index 0000000000..0d8fb71133 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -0,0 +1,160 @@ +import { Injectable, Scope } from 'graphql-modules'; +import { z } from 'zod'; +import * as Sentry from '@sentry/node'; +import { QueryAuditLogsArgs } from '../../../__generated__/types.next'; +import { User } from '../../../shared/entities'; +import { ClickHouse, sql } from '../../operations/providers/clickhouse-client'; +import { SqlValue } from '../../operations/providers/sql'; +import { Logger } from '../../shared/providers/logger'; +import { AuditLogEvent, auditLogSchema } from './audit-logs-types'; + +export const AUDIT_LOG_CLICKHOUSE_OBJECT = z.object({ + id: z.string(), + event_time: z.string(), + user_id: z.string(), + user_email: z.string(), + organization_id: z.string(), + event_action: z.string(), + metadata: z.string().transform(x => JSON.parse(x)), +}); + +export type AuditLogModel = z.infer; + +const AUDIT_LOG_CLICKHOUSE_ARRAY = z.array(AUDIT_LOG_CLICKHOUSE_OBJECT); + +type AuditLogRecordEvent = { + userId: string; + userEmail: string; + organizationId: string; + user: (User & { isAdmin: boolean }) | null; +}; + +@Injectable({ + scope: Scope.Operation, + global: true, +}) +export class AuditLogManager { + private logger: Logger; + + constructor( + logger: Logger, + private clickHouse: ClickHouse, + ) { + this.logger = logger.child({ source: 'AuditLogsManager' }); + } + + createLogAuditEvent(event: AuditLogEvent, record: AuditLogRecordEvent): void { + void this.internalCreateLogAuditEvent(event, record); + } + + private async internalCreateLogAuditEvent( + event: AuditLogEvent, + record: AuditLogRecordEvent, + ): Promise { + try { + const { eventType } = event; + const { organizationId, userEmail, userId } = record; + this.logger.debug('Creating a log audit event (event=%o)', event); + + const parsedEvent = auditLogSchema.parse(event); + const metadata = { + user: record.user, + ...parsedEvent, + }; + + const eventMetadata = JSON.stringify(metadata); + const eventTime = new Date(); + + const values = [eventTime, userId, userEmail, organizationId, eventType, eventMetadata]; + + await this.clickHouse.insert({ + query: sql` + INSERT INTO audit_log + (event_time, user_id, user_email, organization_id, event_action, metadata) + FORMAT CSV`, + data: [values], + timeout: 5000, + queryId: 'create-audit-log', + }); + } catch (error) { + this.logger.error('Failed to create audit log event', error); + Sentry.captureException(error, { + extra: { + event, + }, + }); + } + } + + async getPaginatedAuditLogs( + props: QueryAuditLogsArgs, + ): Promise<{ total: number; data: AuditLogModel[] }> { + this.logger.info( + 'Getting paginated audit logs (limit=%s, offset=%s, orgId=%s, userId=%s, action=%s)', + props.selector.organization, + props.filter?.endDate, + props.filter?.startDate, + props.filter?.userId, + ); + + // Handle the limit and offset for pagination + // let limit: SqlValue[] = []; + // let offset: SqlValue[] = []; + // if (props?.pagination?.limit) { + // limit.push(sql`LIMIT ${String(props.pagination.limit)}`); + // } else { + // limit.push(sql`LIMIT 25`); + // } + // if (props?.pagination?.offset) { + // offset.push(sql`OFFSET ${String(props.pagination.offset)}`); + // } else { + // offset.push(sql`OFFSET 0`); + // } + const limit = props.pagination?.limit ?? 25; + const sqlLimit = sql.raw(limit.toString()); + const offset = props.pagination?.offset ?? 0; + const sqlOffset = sql.raw(offset.toString()); + + const where: SqlValue[] = []; + if (props.selector.organization) { + where.push(sql`organization_id = ${props.selector.organization}`); + } else { + // Handle case where organization_id is not provided + this.logger.warn('No organization_id provided in query'); + } + + if (props.filter) { + // if (props.filter?.startDate) { + // const dateIso = new Date(props.filter.startDate).toISOString(); + // where.push(sql`event_time >= ${dateIso}`); + // } + // if (props.filter?.endDate) { + // const dateIso = new Date(props.filter.endDate).toISOString(); + // where.push(sql`event_time <= ${dateIso}`); + // } + if (props.filter?.userId) { + where.push(sql`user_id = ${props.filter.userId}`); + } + } + + const whereClause = where.length > 0 ? sql`WHERE ${sql.join(where, ' AND ')}` : sql``; + + const result = await this.clickHouse.query({ + query: sql` + SELECT * + FROM audit_log + ${whereClause} + ORDER BY event_time DESC + LIMIT ${sqlLimit} + OFFSET ${sqlOffset} + `, + queryId: 'get-audit-logs', + timeout: 5000, + }); + + return { + total: result.rows, + data: AUDIT_LOG_CLICKHOUSE_ARRAY.parse(result.data), + }; + } +} diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts new file mode 100644 index 0000000000..fdf5eea95e --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts @@ -0,0 +1,348 @@ +import { z } from 'zod'; + +const schemaPolicySettingsUpdatedAuditLogSchema = z.object({ + projectId: z.string(), + updatedFields: z.string(), +}); + +const schemaCheckedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + checkId: z.string().nullable(), +}); + +const schemaPublishAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + serviceName: z.string().nullish(), + schemaVersionId: z.string().nullish(), + isSchemaPublishMissingUrlErrorSelected: z.boolean(), +}); + +const schemaDeletedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + serviceName: z.string(), +}); + +// Role +const roleCreatedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), +}); + +const roleAssignedAuditLogSchema = z.object({ + roleId: z.string(), + updatedMember: z.string(), + previousMemberRole: z.string().nullable(), + userIdAssigned: z.string(), +}); + +const roleDeletedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), +}); + +const roleUpdatedAuditLogSchema = z.object({ + roleId: z.string(), + roleName: z.string(), + updatedFields: z.string(), +}); + +// Support +const supportTicketCreatedAuditLogSchema = z.object({ + ticketId: z.string(), + ticketSubject: z.string(), + ticketDescription: z.string(), + ticketPriority: z.string(), +}); + +const supportTicketUpdatedAuditLogSchema = z.object({ + ticketId: z.string(), + updatedFields: z.string(), +}); + +// Laboratory Collection +const collectionCreatedAuditLogSchema = z.object({ + collectionId: z.string(), + collectionName: z.string(), + targetId: z.string(), +}); + +const collectionUpdatedAuditLogSchema = z.object({ + collectionId: z.string(), + collectionName: z.string(), + updatedFields: z.string(), +}); + +const collectionDeletedAuditLogSchema = z.object({ + collectionId: z.string(), + collectionName: z.string(), +}); + +// Laboratory Collection Operations +const operationInDocumentCollectionCreatedAuditLogSchema = z.object({ + collectionId: z.string(), + collectionName: z.string(), + targetId: z.string(), + operationId: z.string(), + operationQuery: z.string(), +}); + +const operationInDocumentCollectionUpdatedAuditLogSchema = z.object({ + collectionId: z.string(), + collectionName: z.string(), + operationId: z.string(), + updatedFields: z.string(), +}); + +const operationInDocumentCollectionDeletedAuditLogSchema = z.object({ + collectionId: z.string(), + collectionName: z.string(), + operationId: z.string(), +}); + +// Organization +const organizationSettingsUpdatedAuditLogSchema = z.object({ + updatedFields: z.string(), +}); + +const organizationTransferredAuditLogSchema = z.object({ + newOwnerId: z.string(), + newOwnerEmail: z.string(), +}); + +const organizationTransferredRequestAuditLogSchema = z.object({ + newOwnerId: z.string(), + newOwnerEmail: z.string().nullable(), +}); + +const organizationCreatedAuditLogSchema = z.object({ + organizationName: z.string(), + organizationId: z.string(), +}); + +const organizationDeletedAuditLogSchema = z.object({ + organizationId: z.string(), +}); + +const organizationUpdatedIntegrationAuditLogSchema = z.object({ + integrationId: z.string().nullable(), + updatedFields: z.string(), +}); + +// Target +const targetCreatedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + targetName: z.string(), +}); + +const targetSettingsUpdatedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + updatedFields: z.string(), +}); + +const targetDeletedAuditLogSchema = z.object({ + projectId: z.string(), + targetId: z.string(), + targetName: z.string(), +}); + +// User +const userInvitedAuditLogSchema = z.object({ + inviteeId: z.string(), + inviteeEmail: z.string(), +}); +const userJoinedAuditLogSchema = z.object({ + inviteeId: z.string(), + inviteeEmail: z.string(), +}); + +const userRemovedAuditLogSchema = z.object({ + removedUserId: z.string(), + removedUserEmail: z.string(), +}); + +const userSettingsUpdatedAuditLogSchema = z.object({ + updatedFields: z.string(), +}); + +// Subscriptions +const subscriptionCreatedAuditLogSchema = z.object({ + paymentMethodId: z.string().nullish(), + operations: z.number(), + previousPlan: z.string(), + newPlan: z.string(), +}); + +const subscriptionUpdatedAuditLogSchema = z.object({ + updatedFields: z.string(), +}); + +const subscriptionCanceledAuditLogSchema = z.object({ + previousPlan: z.string(), + newPlan: z.string(), +}); + +// Project +const projectCreatedAuditLogSchema = z.object({ + projectId: z.string(), + projectName: z.string(), +}); + +const projectSettingsUpdatedAuditLogSchema = z.object({ + projectId: z.string(), + updatedFields: z.string(), +}); + +const projectDeletedAuditLogSchema = z.object({ + projectId: z.string(), + projectName: z.string(), +}); + +export const auditLogSchema = z.discriminatedUnion('eventType', [ + z.object({ + eventType: z.literal('USER_INVITED'), + userInvitedAuditLogSchema, + }), + z.object({ + eventType: z.literal('USER_JOINED'), + userJoinedAuditLogSchema, + }), + z.object({ + eventType: z.literal('USER_REMOVED'), + userRemovedAuditLogSchema, + }), + z.object({ + eventType: z.literal('USER_SETTINGS_UPDATED'), + userSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_SETTINGS_UPDATED'), + organizationSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_TRANSFERRED'), + organizationTransferredAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_TRANSFERRED_REQUEST'), + organizationTransferredRequestAuditLogSchema, + }), + z.object({ + eventType: z.literal('PROJECT_CREATED'), + projectCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('PROJECT_SETTINGS_UPDATED'), + projectSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('PROJECT_DELETED'), + projectDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('TARGET_CREATED'), + targetCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('TARGET_SETTINGS_UPDATED'), + targetSettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('TARGET_DELETED'), + targetDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_POLICY_SETTINGS_UPDATED'), + schemaPolicySettingsUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_CHECKED'), + schemaCheckedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_PUBLISH'), + schemaPublishAuditLogSchema, + }), + z.object({ + eventType: z.literal('SCHEMA_DELETED'), + schemaDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_CREATED'), + roleCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_ASSIGNED'), + roleAssignedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_DELETED'), + roleDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ROLE_UPDATED'), + roleUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SUPPORT_TICKET_CREATED'), + supportTicketCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SUPPORT_TICKET_UPDATED'), + supportTicketUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('COLLECTION_CREATED'), + collectionCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('COLLECTION_UPDATED'), + collectionUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('COLLECTION_DELETED'), + collectionDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('OPERATION_IN_DOCUMENT_COLLECTION_CREATED'), + operationInDocumentCollectionCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('OPERATION_IN_DOCUMENT_COLLECTION_UPDATED'), + operationInDocumentCollectionUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('OPERATION_IN_DOCUMENT_COLLECTION_DELETED'), + operationInDocumentCollectionDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_CREATED'), + organizationCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_DELETED'), + organizationDeletedAuditLogSchema, + }), + z.object({ + eventType: z.literal('ORGANIZATION_UPDATED_INTEGRATION'), + organizationUpdatedIntegrationAuditLogSchema, + }), + z.object({ + eventType: z.literal('SUBSCRIPTION_CREATED'), + subscriptionCreatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SUBSCRIPTION_UPDATED'), + subscriptionUpdatedAuditLogSchema, + }), + z.object({ + eventType: z.literal('SUBSCRIPTION_CANCELED'), + subscriptionCanceledAuditLogSchema, + }), +]); + +export type AuditLogEvent = z.infer; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts new file mode 100644 index 0000000000..2c9844a567 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { CollectionCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "CollectionCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const CollectionCreatedAuditLog: CollectionCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'COLLECTION_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + collectionId: e => e.metadata.collectionCreatedAuditLogSchema.collectionId, + collectionName: e => e.metadata.collectionCreatedAuditLogSchema.collectionName, + targetId: e => e.metadata.collectionCreatedAuditLogSchema.targetId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts new file mode 100644 index 0000000000..24b8e9d0a3 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { CollectionDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "CollectionDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const CollectionDeletedAuditLog: CollectionDeletedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'COLLECTION_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + collectionId: e => e.metadata.collectionDeletedAuditLogSchema.collectionId, + collectionName: e => e.metadata.collectionDeletedAuditLogSchema.collectionName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts new file mode 100644 index 0000000000..25a152f27e --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { CollectionUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "CollectionUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const CollectionUpdatedAuditLog: CollectionUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'COLLECTION_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + collectionId: e => e.metadata.collectionUpdatedAuditLogSchema.collectionId, + updatedFields: e => e.metadata.collectionUpdatedAuditLogSchema.updatedFields, + collectionName: e => e.metadata.collectionUpdatedAuditLogSchema.collectionName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts new file mode 100644 index 0000000000..f380f18c1b --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts @@ -0,0 +1,25 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OperationInDocumentCollectionCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OperationInDocumentCollectionCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OperationInDocumentCollectionCreatedAuditLog: OperationInDocumentCollectionCreatedAuditLogResolvers = + { + __isTypeOf: e => e.event_action === 'OPERATION_IN_DOCUMENT_COLLECTION_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + collectionId: e => e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.collectionId, + operationId: e => e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.operationId, + operationQuery: e => + e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.operationQuery, + targetId: e => e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.targetId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + collectionName: e => + e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.collectionName, + }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts new file mode 100644 index 0000000000..d378929f8e --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts @@ -0,0 +1,22 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OperationInDocumentCollectionDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OperationInDocumentCollectionDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OperationInDocumentCollectionDeletedAuditLog: OperationInDocumentCollectionDeletedAuditLogResolvers = + { + __isTypeOf: e => e.event_action === 'OPERATION_IN_DOCUMENT_COLLECTION_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + collectionId: e => e.metadata.operationInDocumentCollectionDeletedAuditLogSchema.collectionId, + collectionName: e => + e.metadata.operationInDocumentCollectionDeletedAuditLogSchema.collectionName, + operationId: e => e.metadata.operationInDocumentCollectionDeletedAuditLogSchema.operationId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts new file mode 100644 index 0000000000..f2995b153e --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts @@ -0,0 +1,23 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OperationInDocumentCollectionUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OperationInDocumentCollectionUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OperationInDocumentCollectionUpdatedAuditLog: OperationInDocumentCollectionUpdatedAuditLogResolvers = + { + __isTypeOf: e => e.event_action === 'OPERATION_IN_DOCUMENT_COLLECTION_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + collectionId: e => e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.collectionId, + collectionName: e => + e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.collectionName, + updatedFields: e => e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.updatedFields, + operationId: e => e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.operationId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts new file mode 100644 index 0000000000..2c59091c6d --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OrganizationCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OrganizationCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OrganizationCreatedAuditLog: OrganizationCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ORGANIZATION_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + organizationId: e => e.metadata.organizationCreatedAuditLogSchema.organizationId, + organizationName: e => e.metadata.organizationCreatedAuditLogSchema.organizationName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts new file mode 100644 index 0000000000..3f1e90ec70 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts @@ -0,0 +1,18 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OrganizationDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OrganizationDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OrganizationDeletedAuditLog: OrganizationDeletedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ORGANIZATION_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + organizationId: e => e.metadata.organizationDeletedAuditLogSchema.organizationId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts new file mode 100644 index 0000000000..eef822ec07 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts @@ -0,0 +1,18 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OrganizationSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OrganizationSettingsUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OrganizationSettingsUpdatedAuditLog: OrganizationSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ORGANIZATION_SETTINGS_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + updatedFields: e => e.metadata.organizationSettingsUpdatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts new file mode 100644 index 0000000000..0237fa6983 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OrganizationTransferredAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OrganizationTransferredAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OrganizationTransferredAuditLog: OrganizationTransferredAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ORGANIZATION_TRANSFERRED', + eventTime: e => new Date(e.event_time).toISOString(), + newOwnerEmail: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerEmail, + newOwnerId: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts new file mode 100644 index 0000000000..2a532db17a --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OrganizationTransferredRequestAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OrganizationTransferredRequestAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OrganizationTransferredRequestAuditLog: OrganizationTransferredRequestAuditLogResolvers = + { + __isTypeOf: e => e.event_action === 'ORGANIZATION_TRANSFERRED_REQUEST', + eventTime: e => new Date(e.event_time).toISOString(), + newOwnerEmail: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerEmail, + newOwnerId: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts new file mode 100644 index 0000000000..ac7e75b1d2 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { OrganizationUpdatedIntegrationAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "OrganizationUpdatedIntegrationAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const OrganizationUpdatedIntegrationAuditLog: OrganizationUpdatedIntegrationAuditLogResolvers = + { + __isTypeOf: e => e.event_action === 'ORGANIZATION_UPDATED_INTEGRATION', + eventTime: e => new Date(e.event_time).toISOString(), + updatedFields: e => e.metadata.organizationUpdatedIntegrationAuditLogSchema.updatedFields, + integrationId: e => e.metadata.organizationUpdatedIntegrationAuditLogSchema.integrationId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts new file mode 100644 index 0000000000..e3615e6991 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { ProjectCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "ProjectCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const ProjectCreatedAuditLog: ProjectCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'PROJECT_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.projectCreatedAuditLogSchema.projectId, + projectName: e => e.metadata.projectCreatedAuditLogSchema.projectName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts new file mode 100644 index 0000000000..2504a051cb --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { ProjectDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "ProjectDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const ProjectDeletedAuditLog: ProjectDeletedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'PROJECT_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.projectDeletedAuditLogSchema.projectId, + projectName: e => e.metadata.projectDeletedAuditLogSchema.projectName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts new file mode 100644 index 0000000000..ea8bd07ccc --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { ProjectSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "ProjectSettingsUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const ProjectSettingsUpdatedAuditLog: ProjectSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'PROJECT_SETTINGS_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.projectSettingsUpdatedAuditLogSchema.projectId, + updatedFields: e => e.metadata.projectSettingsUpdatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts new file mode 100644 index 0000000000..79761f0c6b --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts @@ -0,0 +1,29 @@ +import { GraphQLError } from 'graphql'; +import { AuthManager } from '../../../auth/providers/auth-manager'; +import { AuditLogManager } from '../../providers/audit-logs-manager'; +import type { QueryResolvers } from './../../../../__generated__/types.next'; + +export const auditLogs: NonNullable = async (_parent, args, ctx) => { + const isAdmin = ctx.injector + .get(AuthManager) + .getCurrentUser() + ?.then(user => user.isAdmin); + if (!isAdmin) { + throw new GraphQLError('Unauthorized: You are not authorized to perform this action'); + } + + const { selector, filter, pagination } = args; + const auditLogs = await ctx.injector.get(AuditLogManager).getPaginatedAuditLogs({ + selector: selector, + filter: filter, + pagination: pagination, + }); + + console.log('auditLogs', auditLogs); + + return { + nodes: auditLogs.data, + total: auditLogs.total, + __typename: 'AuditLogConnection', + }; +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts new file mode 100644 index 0000000000..dd49a866a8 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts @@ -0,0 +1,21 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { RoleAssignedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "RoleAssignedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const RoleAssignedAuditLog: RoleAssignedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ROLE_ASSIGNED', + eventTime: e => new Date(e.event_time).toISOString(), + previousMemberRole: e => e.metadata.roleAssignedAuditLogSchema.previousMemberRole, + roleId: e => e.metadata.roleAssignedAuditLogSchema.roleId, + updatedMember: e => e.metadata.roleAssignedAuditLogSchema.updatedMember, + userIdAssigned: e => e.metadata.roleAssignedAuditLogSchema.userIdAssigned, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts new file mode 100644 index 0000000000..2dfad5bf52 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { RoleCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "RoleCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const RoleCreatedAuditLog: RoleCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ROLE_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + roleId: e => e.metadata.roleCreatedAuditLogSchema.roleId, + roleName: e => e.metadata.roleCreatedAuditLogSchema.roleName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts new file mode 100644 index 0000000000..9ca7bcbefa --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { RoleDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "RoleDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const RoleDeletedAuditLog: RoleDeletedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ROLE_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + roleId: e => e.metadata.roleDeletedAuditLogSchema.roleId, + roleName: e => e.metadata.roleDeletedAuditLogSchema.roleName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts new file mode 100644 index 0000000000..494c930a40 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { RoleUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "RoleUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const RoleUpdatedAuditLog: RoleUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'ROLE_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + roleId: e => e.metadata.roleUpdatedAuditLogSchema.roleId, + updatedFields: e => e.metadata.roleUpdatedAuditLogSchema.updatedFields, + roleName: e => e.metadata.roleUpdatedAuditLogSchema.roleName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts new file mode 100644 index 0000000000..4e4fa9c5bc --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SchemaCheckedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SchemaCheckedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SchemaCheckedAuditLog: SchemaCheckedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SCHEMA_CHECKED', + eventTime: e => new Date(e.event_time).toISOString(), + checkId: e => e.metadata.schemaCheckedAuditLogSchema.checkId, + projectId: e => e.metadata.schemaCheckedAuditLogSchema.projectId, + targetId: e => e.metadata.schemaCheckedAuditLogSchema.targetId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts new file mode 100644 index 0000000000..0dcc6648cf --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SchemaPolicySettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SchemaPolicySettingsUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SchemaPolicySettingsUpdatedAuditLog: SchemaPolicySettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SCHEMA_POLICY_SETTINGS_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.schemaCheckedAuditLogSchema.projectId, + updatedFields: e => e.metadata.schemaCheckedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts new file mode 100644 index 0000000000..2fafdee859 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts @@ -0,0 +1,23 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SchemaPublishAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SchemaPublishAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SchemaPublishAuditLog: SchemaPublishAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SCHEMA_PUBLISH', + eventTime: e => new Date(e.event_time).toISOString(), + isSchemaPublishMissingUrlErrorSelected: e => + e.metadata.schemaPublishAuditLogSchema.isSchemaPublishMissingUrlErrorSelected, + projectId: e => e.metadata.schemaPublishAuditLogSchema.projectId, + schemaVersionId: e => e.metadata.schemaPublishAuditLogSchema.schemaVersionId, + serviceName: e => e.metadata.schemaPublishAuditLogSchema.serviceName, + targetId: e => e.metadata.schemaPublishAuditLogSchema.targetId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts new file mode 100644 index 0000000000..3aa41069aa --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SubscriptionCanceledAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SubscriptionCanceledAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SubscriptionCanceledAuditLog: SubscriptionCanceledAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SUBSCRIPTION_CANCELED', + eventTime: e => new Date(e.event_time).toISOString(), + newPlan: e => e.metadata.subscriptionCanceledAuditLogSchema.newPlan, + previousPlan: e => e.metadata.subscriptionCanceledAuditLogSchema.previousPlan, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts new file mode 100644 index 0000000000..fa9e715e4c --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts @@ -0,0 +1,21 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SubscriptionCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SubscriptionCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SubscriptionCreatedAuditLog: SubscriptionCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SUBSCRIPTION_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + newPlan: e => e.metadata.subscriptionCreatedAuditLogSchema.newPlan, + operations: e => e.metadata.subscriptionCreatedAuditLogSchema.operations, + paymentMethodId: e => e.metadata.subscriptionCreatedAuditLogSchema.paymentMethodId, + previousPlan: e => e.metadata.subscriptionCreatedAuditLogSchema.previousPlan, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts new file mode 100644 index 0000000000..a48a14fe68 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts @@ -0,0 +1,18 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SubscriptionUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SubscriptionUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SubscriptionUpdatedAuditLog: SubscriptionUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SUBSCRIPTION_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + updatedFields: e => e.metadata.subscriptionUpdatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts new file mode 100644 index 0000000000..dde21cad26 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts @@ -0,0 +1,21 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SupportTicketCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SupportTicketCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SupportTicketCreatedAuditLog: SupportTicketCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SUPPORT_TICKET_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + ticketDescription: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketDescription, + ticketId: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketId, + ticketPriority: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketPriority, + ticketSubject: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketSubject, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts new file mode 100644 index 0000000000..de415bdc74 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { SupportTicketUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "SupportTicketUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const SupportTicketUpdatedAuditLog: SupportTicketUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SUPPORT_TICKET_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + ticketId: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketId, + updatedFields: e => e.metadata.supportTicketCreatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts new file mode 100644 index 0000000000..96fda6f5c5 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { TargetCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "TargetCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const TargetCreatedAuditLog: TargetCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'TARGET_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.targetCreatedAuditLogSchema.projectId, + targetId: e => e.metadata.targetCreatedAuditLogSchema.targetId, + targetName: e => e.metadata.targetCreatedAuditLogSchema.targetName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts new file mode 100644 index 0000000000..72019baf3b --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { TargetDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "TargetDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const TargetDeletedAuditLog: TargetDeletedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'TARGET_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.targetCreatedAuditLogSchema.projectId, + targetId: e => e.metadata.targetCreatedAuditLogSchema.targetId, + targetName: e => e.metadata.targetCreatedAuditLogSchema.targetName, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts new file mode 100644 index 0000000000..cfa881ae9d --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { TargetSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "TargetSettingsUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const TargetSettingsUpdatedAuditLog: TargetSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'TARGET_SETTINGS_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.targetCreatedAuditLogSchema.projectId, + targetId: e => e.metadata.targetCreatedAuditLogSchema.targetId, + updatedFields: e => e.metadata.targetSettingsUpdatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts new file mode 100644 index 0000000000..09b93e6115 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { UserInvitedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "UserInvitedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const UserInvitedAuditLog: UserInvitedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'USER_INVITED', + eventTime: e => new Date(e.event_time).toISOString(), + inviteeEmail: e => e.metadata.userInvitedAuditLogSchema.inviteeEmail, + inviteeId: e => e.metadata.userInvitedAuditLogSchema.inviteeId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts new file mode 100644 index 0000000000..cf9d2fae5d --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { UserJoinedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "UserJoinedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const UserJoinedAuditLog: UserJoinedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'USER_JOINED', + eventTime: e => new Date(e.event_time).toISOString(), + inviteeEmail: e => e.metadata.userInvitedAuditLogSchema.inviteeEmail, + inviteeId: e => e.metadata.userInvitedAuditLogSchema.inviteeId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts new file mode 100644 index 0000000000..5584bd59bc --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { UserRemovedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "UserRemovedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const UserRemovedAuditLog: UserRemovedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'USER_REMOVED', + eventTime: e => new Date(e.event_time).toISOString(), + removedUserEmail: e => e.metadata.userRemovedAuditLogSchema.removedUserEmail, + removedUserId: e => e.metadata.userRemovedAuditLogSchema.removedUserId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts new file mode 100644 index 0000000000..04e0462147 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts @@ -0,0 +1,18 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { UserSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "UserSettingsUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const UserSettingsUpdatedAuditLog: UserSettingsUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'USER_SETTINGS_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + updatedFields: e => e.metadata.userSettingsUpdatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts b/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts index f32748c106..9981b203b6 100644 --- a/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts +++ b/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts @@ -1,4 +1,5 @@ import { GraphQLError } from 'graphql'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationAccessScope } from '../../../auth/providers/organization-access'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; @@ -44,6 +45,23 @@ export const downgradeToHobby: NonNullable = asyn operations: Math.floor(args.input.monthlyLimits.operations / 1_000_000), }, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SUBSCRIPTION_CREATED', + subscriptionCreatedAuditLogSchema: { + operations: args.input.monthlyLimits.operations, + paymentMethodId: args.input.paymentMethodId, + newPlan: 'PRO', + previousPlan: 'HOBBY', + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); } catch (e) { if (e instanceof TRPCClientError) { throw new GraphQLError(`Falied to upgrade: ${e.message}`); diff --git a/packages/services/api/src/modules/cdn/resolvers/Mutation/createCdnAccessToken.ts b/packages/services/api/src/modules/cdn/resolvers/Mutation/createCdnAccessToken.ts index 1e55d8adf1..3698e3a8d8 100644 --- a/packages/services/api/src/modules/cdn/resolvers/Mutation/createCdnAccessToken.ts +++ b/packages/services/api/src/modules/cdn/resolvers/Mutation/createCdnAccessToken.ts @@ -1,4 +1,6 @@ import { HiveError } from '../../../../shared/errors'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { CdnProvider } from '../../providers/cdn.provider'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -36,6 +38,27 @@ export const createCdnAccessToken: NonNullable { const organization = await injector.get(IdTranslator).translateOrganizationId(input); + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_UPDATED_INTEGRATION', + organizationUpdatedIntegrationAuditLogSchema: { + integrationId: input.installationId, + updatedFields: JSON.stringify({ + github: { + added: true, + }, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + await injector.get(GitHubIntegrationManager).register({ organization, installationId: input.installationId, diff --git a/packages/services/api/src/modules/integrations/resolvers/Mutation/addSlackIntegration.ts b/packages/services/api/src/modules/integrations/resolvers/Mutation/addSlackIntegration.ts index 31cbcd4f3c..1aa5430b72 100644 --- a/packages/services/api/src/modules/integrations/resolvers/Mutation/addSlackIntegration.ts +++ b/packages/services/api/src/modules/integrations/resolvers/Mutation/addSlackIntegration.ts @@ -1,5 +1,7 @@ import { z } from 'zod'; import { HiveError } from '../../../../shared/errors'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { SlackIntegrationManager } from '../../providers/slack-integration-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -36,5 +38,26 @@ export const addSlackIntegration: NonNullable { const organizationId = await injector.get(IdTranslator).translateOrganizationId(input); - return injector.get(OrganizationManager).assignMemberRole({ + const result = await injector.get(OrganizationManager).assignMemberRole({ organizationId, userId: input.user, roleId: input.role, }); + + if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ROLE_ASSIGNED', + roleAssignedAuditLogSchema: { + previousMemberRole: result.ok.previousMemberRole ? result.ok.previousMemberRole.id : null, + roleId: input.role, + updatedMember: result.ok.updatedMember.user.id, + userIdAssigned: input.user, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + } + + return result; }; diff --git a/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts b/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts index fe87bf7b5d..29fdaff775 100644 --- a/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts +++ b/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { OrganizationManager } from '../../providers/organization-manager'; import { createOrUpdateMemberRoleInputSchema } from '../../validation'; @@ -27,7 +29,7 @@ export const createMemberRole: NonNullable { const organizationId = await injector.get(IdTranslator).translateOrganizationId(input); - return injector.get(OrganizationManager).deleteMemberRole({ + const result = await injector.get(OrganizationManager).deleteMemberRole({ organizationId, roleId: input.role, }); + + if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ROLE_DELETED', + roleDeletedAuditLogSchema: { + roleId: input.role, + roleName: result.ok.updatedOrganization.name, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + } + + return result; }; diff --git a/packages/services/api/src/modules/organization/resolvers/Mutation/deleteOrganization.ts b/packages/services/api/src/modules/organization/resolvers/Mutation/deleteOrganization.ts index 0ed0c5ab44..ff1b2eda29 100644 --- a/packages/services/api/src/modules/organization/resolvers/Mutation/deleteOrganization.ts +++ b/packages/services/api/src/modules/organization/resolvers/Mutation/deleteOrganization.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { OrganizationManager } from '../../providers/organization-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -14,6 +16,23 @@ export const deleteOrganization: NonNullable = async (_, { input }, { injector }) => { const organization = await injector.get(IdTranslator).translateOrganizationId(input); - return injector.get(OrganizationManager).requestOwnershipTransfer({ + const result = await injector.get(OrganizationManager).requestOwnershipTransfer({ organization, user: input.user, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_TRANSFERRED_REQUEST', + organizationTransferredRequestAuditLogSchema: { + newOwnerEmail: result.ok ? result.ok.email : null, + newOwnerId: input.user, + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return result; }; diff --git a/packages/services/api/src/modules/organization/resolvers/Mutation/updateMemberRole.ts b/packages/services/api/src/modules/organization/resolvers/Mutation/updateMemberRole.ts index 6630f63e12..8ee380c0d7 100644 --- a/packages/services/api/src/modules/organization/resolvers/Mutation/updateMemberRole.ts +++ b/packages/services/api/src/modules/organization/resolvers/Mutation/updateMemberRole.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { OrganizationManager } from '../../providers/organization-manager'; import { createOrUpdateMemberRoleInputSchema } from '../../validation'; @@ -26,7 +28,7 @@ export const updateMemberRole: NonNullable = async (_, { input }, { injector }) => { const organization = await injector.get(IdTranslator).translateOrganizationId(input); + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + members: { + updated: input, + }, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { selector: { organization: input.organization, diff --git a/packages/services/api/src/modules/organization/resolvers/Mutation/updateOrganizationSlug.ts b/packages/services/api/src/modules/organization/resolvers/Mutation/updateOrganizationSlug.ts index 72fcb335af..f21a7ab5c5 100644 --- a/packages/services/api/src/modules/organization/resolvers/Mutation/updateOrganizationSlug.ts +++ b/packages/services/api/src/modules/organization/resolvers/Mutation/updateOrganizationSlug.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { OrganizationManager } from '../../providers/organization-manager'; import { OrganizationSlugModel } from '../../validation'; @@ -24,6 +26,24 @@ export const updateOrganizationSlug: NonNullable< organization: organizationId, }); + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + newSlug: input.slug, + }), + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + if (result.ok) { return { ok: { diff --git a/packages/services/api/src/modules/policy/resolvers/Mutation/updateSchemaPolicyForOrganization.ts b/packages/services/api/src/modules/policy/resolvers/Mutation/updateSchemaPolicyForOrganization.ts index 6945a9c658..a36066b1d6 100644 --- a/packages/services/api/src/modules/policy/resolvers/Mutation/updateSchemaPolicyForOrganization.ts +++ b/packages/services/api/src/modules/policy/resolvers/Mutation/updateSchemaPolicyForOrganization.ts @@ -1,4 +1,6 @@ import { TRPCClientError } from '@trpc/client'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { SchemaPolicyApiProvider } from '../../providers/schema-policy-api.provider'; @@ -17,6 +19,50 @@ export const updateSchemaPolicyForOrganization: NonNullable< .get(SchemaPolicyProvider) .setOrganizationPolicy({ organization }, config, allowOverrides); + if (allowOverrides === true) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + schemaPolicy: { + updated: config, + allowOverrides: true, + }, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + } else if (allowOverrides === false) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + schemaPolicy: { + updated: config, + allowOverrides: false, + }, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + } + return { ok: { updatedPolicy, diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts index 58f2695f72..64fa0292ac 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts @@ -8,6 +8,8 @@ import { TargetManager } from '../../../target/providers/target-manager'; import { ProjectManager } from '../../providers/project-manager'; import { ProjectSlugModel } from '../../validation'; import type { MutationResolvers } from './../../../../__generated__/types.next'; +import { AuthManager } from '../../../auth/providers/auth-manager'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; export const createProject: NonNullable = async ( _, @@ -86,6 +88,23 @@ export const createProject: NonNullable = as } } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'PROJECT_CREATED', + projectCreatedAuditLogSchema: { + projectId: result.project.id, + projectName: result.project.name, + }, + }, + { + organizationId: organization.id, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { ok: { createdProject: result.project, diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts index 27970f54f7..e3159f14f2 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { ProjectManager } from '../../providers/project-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -21,6 +23,24 @@ export const deleteProject: NonNullable = as organization: organizationId, project: projectId, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'PROJECT_DELETED', + projectDeletedAuditLogSchema: { + projectId: projectId, + projectName: deletedProject.name, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { selector: { organization: organizationId, diff --git a/packages/services/api/src/modules/schema/resolvers.ts b/packages/services/api/src/modules/schema/resolvers.ts new file mode 100644 index 0000000000..63e2acdf21 --- /dev/null +++ b/packages/services/api/src/modules/schema/resolvers.ts @@ -0,0 +1,2298 @@ +import { createHash } from 'node:crypto'; +import type { + GraphQLEnumTypeMapper, + GraphQLInputObjectTypeMapper, + GraphQLInterfaceTypeMapper, + GraphQLNamedTypeMapper, + GraphQLObjectTypeMapper, + GraphQLScalarTypeMapper, + GraphQLUnionTypeMapper, + SchemaCoordinateUsageForUnusedExplorer, + SchemaCoordinateUsageMapper, + WithGraphQLParentInfo, + WithSchemaCoordinatesUsage, +} from './module.graphql.mappers'; +import stringify from 'fast-json-stable-stringify'; +import { + ConstDirectiveNode, + DEFAULT_DEPRECATION_REASON, + DocumentNode, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLUnionType, + isEnumType, + isInputObjectType, + isInterfaceType, + isObjectType, + isScalarType, + isUnionType, + Kind, + print, +} from 'graphql'; +import { parseResolveInfo } from 'graphql-parse-resolve-info'; +import { z } from 'zod'; +import { CriticalityLevel } from '@graphql-inspector/core'; +import type * as Types from '../../__generated__/types'; +import { type DateRange } from '../../shared/entities'; +import { createPeriod, parseDateRangeInput, PromiseOrValue } from '../../shared/helpers'; +import { buildASTSchema, createConnection, createDummyConnection } from '../../shared/schema'; +import { AuditLogManager } from '../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../auth/providers/auth-manager'; +import { OperationsManager } from '../operations/providers/operations-manager'; +import { OrganizationManager } from '../organization/providers/organization-manager'; +import { ProjectManager } from '../project/providers/project-manager'; +import { IdTranslator } from '../shared/providers/id-translator'; +import { TargetSelector } from '../shared/providers/storage'; +import { TargetManager } from '../target/providers/target-manager'; +import type { SchemaModule } from './__generated__/types'; +import { onlyDeprecatedDocumentNode } from './lib/deprecated-graphql'; +import { extractSuperGraphInformation, SuperGraphInformation } from './lib/federation-super-graph'; +import { stripUsedSchemaCoordinatesFromDocumentNode } from './lib/unused-graphql'; +import { BreakingSchemaChangeUsageHelper } from './providers/breaking-schema-changes-helper'; +import { ContractsManager } from './providers/contracts-manager'; +import { SchemaCheckManager } from './providers/schema-check-manager'; +import { SchemaManager } from './providers/schema-manager'; +import { SchemaPublisher } from './providers/schema-publisher'; +import { SchemaVersionHelper } from './providers/schema-version-helper'; +import { toGraphQLSchemaCheck, toGraphQLSchemaCheckCurry } from './to-graphql-schema-check'; + +const MaybeModel = (value: T) => z.union([z.null(), z.undefined(), value]); +const GraphQLSchemaStringModel = z.string().max(5_000_000).min(0); + +function isSchemaCoordinateUsageForUnusedExplorer( + value: unknown, +): value is SchemaCoordinateUsageForUnusedExplorer { + return 'isUsed' in (value as any); +} + +function usage( + source: + | WithSchemaCoordinatesUsage<{ + entity: { + name: string; + }; + }> + | WithGraphQLParentInfo< + WithSchemaCoordinatesUsage<{ + entity: { + name: string; + }; + }> + >, + _: unknown, +): Promise | SchemaCoordinateUsageMapper { + const coordinate = + 'parent' in source ? `${source.parent.coordinate}.${source.entity.name}` : source.entity.name; + + const usage = source.usage(); + + if (isSchemaCoordinateUsageForUnusedExplorer(usage)) { + if (usage.usedCoordinates.has(coordinate)) { + return { + // TODO: This is a hack to mark the field as used but without passing exact number as we don't need the exact number in "Unused schema view". + total: 1, + isUsed: true, + usedByClients: () => [], + period: usage.period, + organization: usage.organization, + project: usage.project, + target: usage.target, + coordinate: coordinate, + }; + } + + return { + total: 0, + isUsed: false, + usedByClients: () => [], + }; + } + + return Promise.resolve(usage).then(usage => { + const coordinateUsage = usage[coordinate]; + + return coordinateUsage && coordinateUsage.total > 0 + ? { + total: coordinateUsage.total, + isUsed: true, + usedByClients: coordinateUsage.usedByClients, + period: coordinateUsage.period, + organization: coordinateUsage.organization, + project: coordinateUsage.project, + target: coordinateUsage.target, + coordinate: coordinate, + } + : { + total: 0, + isUsed: false, + usedByClients: () => [], + }; + }); +} + +function __isTypeOf< + T extends GraphQLNamedTypeMapper, + K extends GraphQLNamedTypeMapper['entity']['kind'], +>(kind: K): (type: T) => boolean { + return ({ entity }: { entity: GraphQLNamedTypeMapper['entity'] }) => entity.kind === kind; +} + +export const resolvers: SchemaModule.Resolvers = { + Mutation: { + async schemaCheck(_, { input }, { injector }) { + const [organization, project, target] = await Promise.all([ + injector.get(OrganizationManager).getOrganizationIdByToken(), + injector.get(ProjectManager).getProjectIdByToken(), + injector.get(TargetManager).getTargetIdByToken(), + ]); + + const result = await injector.get(SchemaPublisher).check({ + ...input, + service: input.service?.toLowerCase(), + organization, + project, + target, + }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const [organizationId, projectId, targetId] = await Promise.all([ + injector.get(IdTranslator).translateOrganizationId({ + organization, + }), + injector.get(IdTranslator).translateProjectId({ + organization, + project, + }), + injector.get(IdTranslator).translateTargetId({ + organization, + project, + target, + }), + ]); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_CHECKED', + schemaCheckedAuditLogSchema: { + projectId: projectId, + targetId: targetId, + checkId: result.__typename === 'SchemaCheckSuccess' ? result.schemaCheck.id : null, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + if ('changes' in result && result.changes) { + return { + ...result, + changes: result.changes, + errors: + result.errors?.map(error => ({ + ...error, + path: 'path' in error ? error.path?.split('.') : null, + })) ?? [], + }; + } + + return result; + }, + async approveFailedSchemaCheck(_, { input }, { injector }) { + const [organizationId, projectId, targetId] = await Promise.all([ + injector.get(IdTranslator).translateOrganizationId(input), + injector.get(IdTranslator).translateProjectId(input), + injector.get(IdTranslator).translateTargetId(input), + ]); + + const result = await injector.get(SchemaManager).approveFailedSchemaCheck({ + organizationId, + projectId, + targetId, + schemaCheckId: input.schemaCheckId, + comment: input.comment, + }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_CHECKED', + schemaCheckedAuditLogSchema: { + projectId: projectId, + targetId: targetId, + checkId: input.schemaCheckId, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + if (result.type === 'error') { + return { + error: { + message: result.reason, + }, + }; + } + + return { + ok: { + schemaCheck: toGraphQLSchemaCheck( + { + organizationId, + projectId, + }, + result.schemaCheck, + ), + }, + }; + }, + async schemaPublish(_, { input }, { injector, request }, info) { + const [organization, project, target] = await Promise.all([ + injector.get(OrganizationManager).getOrganizationIdByToken(), + injector.get(ProjectManager).getProjectIdByToken(), + injector.get(TargetManager).getTargetIdByToken(), + ]); + + // We only want to resolve to SchemaPublishMissingUrlError if it is selected by the operation. + // NOTE: This should be removed once the usage of cli versions that don't request on 'SchemaPublishMissingUrlError' is becomes pretty low. + const parsedResolveInfoFragment = parseResolveInfo(info); + const isSchemaPublishMissingUrlErrorSelected = + !!parsedResolveInfoFragment?.fieldsByTypeName['SchemaPublishMissingUrlError']; + + const result = await injector.get(SchemaPublisher).publish( + { + ...input, + service: input.service?.toLowerCase(), + organization, + project, + target, + isSchemaPublishMissingUrlErrorSelected, + }, + request.signal, + ); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const [organizationId, projectId, targetId] = await Promise.all([ + injector.get(IdTranslator).translateOrganizationId({ + organization, + }), + injector.get(IdTranslator).translateProjectId({ + organization, + project, + }), + injector.get(IdTranslator).translateTargetId({ + organization, + project, + target, + }), + ]); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_PUBLISH', + schemaPublishAuditLogSchema: { + projectId: projectId, + targetId: targetId, + isSchemaPublishMissingUrlErrorSelected, + serviceName: input?.service?.toLowerCase(), + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + if ('changes' in result) { + return { + ...result, + changes: result.changes, + }; + } + + return result; + }, + async schemaDelete(_, { input }, { injector, request }) { + const [organization, project, target] = await Promise.all([ + injector.get(OrganizationManager).getOrganizationIdByToken(), + injector.get(ProjectManager).getProjectIdByToken(), + injector.get(TargetManager).getTargetFromToken(), + ]); + + const token = injector.get(AuthManager).ensureApiToken(); + + const checksum = createHash('md5') + .update( + stringify({ + ...input, + serviceName: input.serviceName.toLowerCase(), + }), + ) + .update(token) + .digest('base64'); + + const result = await injector.get(SchemaPublisher).delete( + { + dryRun: input.dryRun, + serviceName: input.serviceName.toLowerCase(), + organization, + project, + target, + checksum, + }, + request.signal, + ); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const [organizationId, projectId] = await Promise.all([ + injector.get(IdTranslator).translateOrganizationId({ + organization, + }), + injector.get(IdTranslator).translateProjectId({ + organization, + project, + }), + ]); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_DELETED', + schemaDeletedAuditLogSchema: { + projectId: projectId, + serviceName: input.serviceName.toLowerCase(), + targetId: target.id, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return { + ...result, + changes: result.changes, + errors: result.errors?.map(error => ({ + ...error, + path: 'path' in error ? error.path?.split('.') : null, + })), + }; + }, + async schemaCompose(_, { input }, { injector }) { + const [organization, project, target] = await Promise.all([ + injector.get(OrganizationManager).getOrganizationIdByToken(), + injector.get(ProjectManager).getProjectIdByToken(), + injector.get(TargetManager).getTargetIdByToken(), + ]); + + const result = await injector.get(SchemaManager).compose({ + onlyComposable: input.useLatestComposableVersion === true, + services: input.services, + organization, + project, + target, + }); + + if (result.kind === 'error') { + return { + __typename: 'SchemaComposeError', + message: result.message, + }; + } + + return { + __typename: 'SchemaComposeSuccess', + valid: 'supergraphSDL' in result && result.supergraphSDL !== null, + compositionResult: { + errors: result.errors, + supergraphSdl: result.supergraphSDL, + }, + }; + }, + async updateSchemaVersionStatus(_, { input }, { injector }) { + const translator = injector.get(IdTranslator); + const [organization, project, target] = await Promise.all([ + translator.translateOrganizationId(input), + translator.translateProjectId(input), + translator.translateTargetId(input), + ]); + + return injector.get(SchemaPublisher).updateVersionStatus({ + version: input.version, + valid: input.valid, + organization, + project, + target, + }); + }, + async updateBaseSchema(_, { input }, { injector }) { + const UpdateBaseSchemaModel = z.object({ + newBase: MaybeModel(GraphQLSchemaStringModel), + }); + + const result = UpdateBaseSchemaModel.safeParse(input); + + if (!result.success) { + return { + error: { + message: + result.error.formErrors.fieldErrors?.newBase?.[0] ?? 'Please check your input.', + }, + }; + } + + const schemaManager = injector.get(SchemaManager); + const translator = injector.get(IdTranslator); + const [organization, project, target] = await Promise.all([ + translator.translateOrganizationId(input), + translator.translateProjectId(input), + translator.translateTargetId(input), + ]); + + const selector = { organization, project, target }; + await schemaManager.updateBaseSchema(selector, input.newBase ? input.newBase : null); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'TARGET_SETTINGS_UPDATED', + targetSettingsUpdatedAuditLogSchema: { + targetId: target, + projectId: project, + updatedFields: JSON.stringify({ + newBase: input.newBase, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return { + ok: { + updatedTarget: await injector.get(TargetManager).getTarget({ + organization, + target, + project, + }), + }, + }; + }, + async disableExternalSchemaComposition(_, { input }, { injector }) { + const translator = injector.get(IdTranslator); + const [organization, project] = await Promise.all([ + translator.translateOrganizationId(input), + translator.translateProjectId(input), + ]); + + const result = await injector.get(SchemaManager).disableExternalSchemaComposition({ + project, + organization, + }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + disableExternalSchemaComposition: true, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return result; + }, + async enableExternalSchemaComposition(_, { input }, { injector }) { + const translator = injector.get(IdTranslator); + const [organization, project] = await Promise.all([ + translator.translateOrganizationId(input), + translator.translateProjectId(input), + ]); + + const result = await injector.get(SchemaManager).enableExternalSchemaComposition({ + project, + organization, + endpoint: input.endpoint, + secret: input.secret, + }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + disableExternalSchemaComposition: false, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return result; + }, + async updateNativeFederation(_, { input }, { injector }) { + const translator = injector.get(IdTranslator); + const [organization, project] = await Promise.all([ + translator.translateOrganizationId(input), + translator.translateProjectId(input), + ]); + + const result = { + ok: await injector.get(SchemaManager).updateNativeSchemaComposition({ + project, + organization, + enabled: input.enabled, + }), + }; + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'ORGANIZATION_SETTINGS_UPDATED', + organizationSettingsUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + enableNativeFederation: input.enabled, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return result; + }, + async updateProjectRegistryModel(_, { input }, { injector }) { + const translator = injector.get(IdTranslator); + const [organization, project] = await Promise.all([ + translator.translateOrganizationId(input), + translator.translateProjectId(input), + ]); + + return { + ok: await injector.get(SchemaManager).updateRegistryModel({ + project, + organization, + model: input.model, + }), + }; + }, + async createContract(_, args, context) { + const result = await context.injector.get(ContractsManager).createContract({ + contract: { + targetId: args.input.targetId, + contractName: args.input.contractName, + excludeTags: (args.input.excludeTags as Array | null) ?? null, + includeTags: (args.input.includeTags as Array | null) ?? null, + removeUnreachableTypesFromPublicApiSchema: + args.input.removeUnreachableTypesFromPublicApiSchema, + }, + }); + + if (result.type === 'success') { + return { + ok: { + createdContract: result.contract, + }, + }; + } + + return { + error: { + message: 'Something went wrong.', + details: result.errors, + }, + }; + }, + async disableContract(_, args, context) { + const result = await context.injector.get(ContractsManager).disableContract({ + contractId: args.input.contractId, + }); + + if (result.type === 'success') { + return { + ok: { + disabledContract: result.contract, + }, + }; + } + + return { + error: { + message: result.message, + }, + }; + }, + }, + Query: { + async latestVersion(_, __, { injector }) { + const target = await injector.get(TargetManager).getTargetFromToken(); + + return injector.get(SchemaManager).getMaybeLatestVersion({ + organization: target.orgId, + project: target.projectId, + target: target.id, + }); + }, + async latestValidVersion(_, __, { injector }) { + const target = await injector.get(TargetManager).getTargetFromToken(); + + return injector.get(SchemaManager).getMaybeLatestValidVersion({ + organization: target.orgId, + project: target.projectId, + target: target.id, + }); + }, + async testExternalSchemaComposition(_, { selector }, { injector }) { + const translator = injector.get(IdTranslator); + const [organizationId, projectId] = await Promise.all([ + translator.translateOrganizationId(selector), + translator.translateProjectId(selector), + ]); + + const schemaManager = injector.get(SchemaManager); + + const result = await schemaManager.testExternalSchemaComposition({ + organizationId, + projectId, + }); + + if (result.kind === 'success') { + return { + ok: result.project, + }; + } + + return { + error: { + message: result.error, + }, + }; + }, + async schemaVersionForActionId(_, { actionId }, { injector }) { + return injector.get(SchemaManager).getSchemaVersionByActionId({ + actionId, + }); + }, + }, + Target: { + async schemaVersions(target, args, { injector }) { + return injector.get(SchemaManager).getPaginatedSchemaVersionsForTargetId({ + targetId: target.id, + organizationId: target.orgId, + projectId: target.projectId, + cursor: args.after ?? null, + first: args.first ?? null, + }); + }, + async schemaVersion(target, args, { injector }) { + const schemaVersion = await injector.get(SchemaManager).getSchemaVersion({ + organization: target.orgId, + project: target.projectId, + target: target.id, + version: args.id, + }); + + if (schemaVersion === null) { + return null; + } + + return { + ...schemaVersion, + organization: target.orgId, + project: target.projectId, + target: target.id, + }; + }, + latestSchemaVersion(target, _, { injector }) { + return injector.get(SchemaManager).getMaybeLatestVersion({ + target: target.id, + project: target.projectId, + organization: target.orgId, + }); + }, + async latestValidSchemaVersion(target, __, { injector }) { + return injector.get(SchemaManager).getMaybeLatestValidVersion({ + organization: target.orgId, + project: target.projectId, + target: target.id, + }); + }, + baseSchema(target, _, { injector }) { + return injector.get(SchemaManager).getBaseSchema({ + target: target.id, + project: target.projectId, + organization: target.orgId, + }); + }, + hasSchema(target, _, { injector }) { + return injector.get(SchemaManager).hasSchema({ + target: target.id, + project: target.projectId, + organization: target.orgId, + }); + }, + async schemaCheck(target, args, { injector }) { + const schemaCheck = await injector.get(SchemaManager).findSchemaCheck({ + targetId: target.id, + projectId: target.projectId, + organizationId: target.orgId, + schemaCheckId: args.id, + }); + + if (schemaCheck == null) { + return null; + } + + return toGraphQLSchemaCheck( + { + organizationId: target.orgId, + projectId: target.projectId, + }, + schemaCheck, + ); + }, + async schemaChecks(target, args, { injector }) { + const result = await injector.get(SchemaManager).getPaginatedSchemaChecksForTarget({ + targetId: target.id, + projectId: target.projectId, + organizationId: target.orgId, + first: args.first ?? null, + cursor: args.after ?? null, + filters: args.filters ?? null, + transformNode: toGraphQLSchemaCheckCurry({ + organizationId: target.orgId, + projectId: target.projectId, + }), + }); + + return { + edges: result.items, + pageInfo: result.pageInfo, + }; + }, + schemaVersionsCount(target, { period }, { injector }) { + return injector.get(SchemaManager).countSchemaVersionsOfTarget({ + organization: target.orgId, + project: target.projectId, + target: target.id, + period: period ? parseDateRangeInput(period) : null, + }); + }, + async contracts(target, args, { injector }) { + return await injector.get(ContractsManager).getPaginatedContractsForTarget({ + target, + cursor: args.after ?? null, + first: args.first ?? null, + }); + }, + async activeContracts(target, args, { injector }) { + return await injector.get(ContractsManager).getPaginatedActiveContractsForTarget({ + target, + cursor: args.after ?? null, + first: args.first ?? null, + }); + }, + async hasCollectedSubscriptionOperations(target, _, { injector }) { + return await injector.get(OperationsManager).hasCollectedSubscriptionOperations({ + target: target.id, + project: target.projectId, + organization: target.orgId, + }); + }, + }, + SchemaVersion: { + isComposable(version) { + return version.schemaCompositionErrors === null; + }, + hasSchemaChanges(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getHasSchemaChanges(version); + }, + async log(version, _, { injector }) { + const log = await injector.get(SchemaManager).getSchemaLog({ + commit: version.actionId, + organization: version.organization, + project: version.project, + target: version.target, + }); + + if (log.kind === 'single') { + return { + __typename: 'PushedSchemaLog', + author: log.author, + commit: log.commit, + date: log.date as any, + id: log.id, + service: null, + serviceSdl: null, + }; + } + + if (log.action === 'DELETE') { + return { + __typename: 'DeletedSchemaLog', + author: 'system', + commit: 'system', + date: log.date as any, + id: log.id, + deletedService: log.service_name, + previousServiceSdl: await injector + .get(SchemaVersionHelper) + .getServiceSdlForPreviousVersionService(version, log.service_name), + }; + } + + return { + __typename: 'PushedSchemaLog', + author: log.author, + commit: log.commit, + date: log.date as any, + id: log.id, + service: log.service_name, + serviceSdl: log.sdl, + previousServiceSdl: await injector + .get(SchemaVersionHelper) + .getServiceSdlForPreviousVersionService(version, log.service_name), + }; + }, + schemas(version, _, { injector }) { + return injector.get(SchemaManager).getMaybeSchemasOfVersion({ + version: version.id, + organization: version.organization, + project: version.project, + target: version.target, + }); + }, + async schemaCompositionErrors(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getSchemaCompositionErrors(version); + }, + async breakingSchemaChanges(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getBreakingSchemaChanges(version); + }, + async safeSchemaChanges(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getSafeSchemaChanges(version); + }, + async supergraph(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getSupergraphSdl(version); + }, + async sdl(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getCompositeSchemaSdl(version); + }, + async baseSchema(version) { + return version.baseSchema ?? null; + }, + async explorer(version, { usage }, { injector }) { + const [schemaAst, supergraphAst] = await Promise.all([ + injector.get(SchemaVersionHelper).getCompositeSchemaAst(version), + injector.get(SchemaVersionHelper).getSupergraphAst(version), + ]); + + if (!schemaAst) { + return null; + } + + const supergraph = supergraphAst ? extractSuperGraphInformation(supergraphAst) : null; + + return { + schema: buildASTSchema(schemaAst), + usage: { + period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), + organization: version.organization, + project: version.project, + target: version.target, + }, + supergraph, + }; + }, + async unusedSchema(version, { usage }, { injector }) { + const [schemaAst, supergraphAst] = await Promise.all([ + injector.get(SchemaVersionHelper).getCompositeSchemaAst(version), + injector.get(SchemaVersionHelper).getSupergraphAst(version), + ]); + + if (!schemaAst) { + return null; + } + + const usedCoordinates = await injector.get(OperationsManager).getReportedSchemaCoordinates({ + targetId: version.target, + projectId: version.project, + organizationId: version.organization, + period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), + }); + + const supergraph = supergraphAst ? extractSuperGraphInformation(supergraphAst) : null; + + return { + sdl: stripUsedSchemaCoordinatesFromDocumentNode(schemaAst, usedCoordinates), + usage: { + period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), + organization: version.organization, + project: version.project, + target: version.target, + usedCoordinates, + }, + supergraph, + }; + }, + async deprecatedSchema(version, { usage }, { injector }) { + const [schemaAst, supergraphAst] = await Promise.all([ + injector.get(SchemaVersionHelper).getCompositeSchemaAst(version), + injector.get(SchemaVersionHelper).getSupergraphAst(version), + ]); + + if (!schemaAst) { + return null; + } + + const supergraph = supergraphAst ? extractSuperGraphInformation(supergraphAst) : null; + + return { + sdl: onlyDeprecatedDocumentNode(schemaAst), + usage: { + period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), + organization: version.organization, + project: version.project, + target: version.target, + }, + supergraph, + }; + }, + date: version => version.createdAt, + githubMetadata(version, _, { injector }) { + return injector.get(SchemaManager).getGitHubMetadata(version); + }, + valid(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getIsValid(version); + }, + previousDiffableSchemaVersion(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getPreviousDiffableSchemaVersion(version); + }, + isFirstComposableVersion(version, _, { injector }) { + return injector.get(SchemaVersionHelper).getIsFirstComposableVersion(version); + }, + contractVersions(version, _, { injector }) { + return injector.get(ContractsManager).getContractVersionsForSchemaVersion(version); + }, + }, + SingleSchema: { + __isTypeOf(obj) { + return obj.kind === 'single'; + }, + source(schema) { + return schema.sdl; + }, + }, + CompositeSchema: { + __isTypeOf(obj) { + return obj.kind === 'composite' && obj.action === 'PUSH'; + }, + service(schema) { + return schema.service_name; + }, + source(schema) { + return schema.sdl; + }, + url(schema) { + return schema.service_url; + }, + }, + SchemaConnection: createConnection(), + SchemaChangeConnection: createConnection(), + SchemaChange: { + message: (change, args) => { + return args.withSafeBasedOnUsageNote && change.isSafeBasedOnUsage === true + ? `${change.message} (non-breaking based on usage)` + : change.message; + }, + path: change => change.path?.split('.') ?? null, + criticality: change => criticalityMap[change.criticality], + criticalityReason: change => change.reason, + approval: change => change.approvalMetadata, + isSafeBasedOnUsage: change => change.isSafeBasedOnUsage, + usageStatistics: (change, _, { injector }) => + injector.get(BreakingSchemaChangeUsageHelper).getUsageDataForBreakingSchemaChange(change), + }, + SchemaChangeApproval: { + approvedBy: (approval, _, { injector }) => + injector.get(SchemaManager).getUserForSchemaChangeById({ userId: approval.userId }), + approvedAt: approval => approval.date, + }, + SchemaErrorConnection: createConnection(), + SchemaWarningConnection: createConnection(), + SchemaCheckSuccess: { + __isTypeOf(obj) { + return obj.valid; + }, + }, + SchemaCheckError: { + __isTypeOf(obj) { + return !obj.valid; + }, + }, + Project: { + externalSchemaComposition(project) { + if (project.externalComposition.enabled && project.externalComposition.endpoint) { + return { + endpoint: project.externalComposition.endpoint, + }; + } + + return null; + }, + registryModel(project) { + return project.legacyRegistryModel ? 'LEGACY' : 'MODERN'; + }, + schemaVersionsCount(project, { period }, { injector }) { + return injector.get(SchemaManager).countSchemaVersionsOfProject({ + organization: project.orgId, + project: project.id, + period: period ? parseDateRangeInput(period) : null, + }); + }, + isNativeFederationEnabled(project) { + return project.nativeFederation === true; + }, + nativeFederationCompatibility(project, _, { injector }) { + return injector.get(SchemaManager).getNativeFederationCompatibilityStatus(project); + }, + }, + SchemaCoordinateUsage: { + topOperations(source, { limit }, { injector }) { + if (!source.isUsed) { + return []; + } + + return injector + .get(OperationsManager) + .getTopOperationForCoordinate({ + organizationId: source.organization, + projectId: source.project, + targetId: source.target, + coordinate: source.coordinate, + period: source.period, + limit, + }) + .then(operations => + operations.map(op => ({ + name: op.operationName, + hash: op.operationHash, + count: op.count, + })), + ); + }, + // Why? GraphQL-JIT goes crazy without this (Expected Iterable, but did not find one for field SchemaCoordinateUsage.usedByClients). + // That's why we switched from a getter to a function. + usedByClients(parent) { + return parent.usedByClients(); + }, + }, + SchemaExplorer: { + async type(source, { name }, { injector }) { + const entity = source.schema.getType(name); + const operationsManager = injector.get(OperationsManager); + + if (!entity) { + return null; + } + + const { supergraph } = source; + const usage = () => + injector + .get(OperationsManager) + .countCoordinatesOfType({ + typename: entity.name, + organization: source.usage.organization, + project: source.usage.project, + target: source.usage.target, + period: source.usage.period, + }) + .then(usage => + withUsedByClients(usage, { + selector: source.usage, + period: source.usage.period, + operationsManager, + typename: entity.name, + }), + ); + + if (isObjectType(entity)) { + return { + entity: transformGraphQLObjectType(entity), + usage, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? + null, + } + : null, + } satisfies GraphQLObjectTypeMapper; + } + if (isInterfaceType(entity)) { + return { + entity: transformGraphQLInterfaceType(entity), + usage, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? + null, + } + : null, + } satisfies GraphQLInterfaceTypeMapper; + } + if (isEnumType(entity)) { + return { + entity: transformGraphQLEnumType(entity), + usage, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getEnumValueOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? + null, + } + : null, + } satisfies GraphQLEnumTypeMapper; + } + if (isUnionType(entity)) { + return { + entity: transformGraphQLUnionType(entity), + usage, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getUnionMemberOwnedByServices: (memberName: string) => + supergraph.schemaCoordinateServicesMappings.get(memberName) ?? null, + } + : null, + } satisfies GraphQLUnionTypeMapper; + } + if (isInputObjectType(entity)) { + return { + entity: transformGraphQLInputObjectType(entity), + usage, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getInputFieldOwnedByServices: (inputFieldName: string) => + supergraph.schemaCoordinateServicesMappings.get( + `${entity.name}.${inputFieldName}`, + ) ?? null, + } + : null, + } satisfies GraphQLInputObjectTypeMapper; + } + if (isScalarType(entity)) { + return { + entity: transformGraphQLScalarType(entity), + usage, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + } + : null, + } satisfies GraphQLScalarTypeMapper; + } + + throw new Error('Illegal state: unknown type kind'); + }, + async types({ schema, usage, supergraph }, _, { injector }) { + const types: Array< + | GraphQLObjectTypeMapper + | GraphQLInterfaceTypeMapper + | GraphQLUnionTypeMapper + | GraphQLEnumTypeMapper + | GraphQLInputObjectTypeMapper + | GraphQLScalarTypeMapper + > = []; + const typeMap = schema.getTypeMap(); + const operationsManager = injector.get(OperationsManager); + + async function getStats(typename: string) { + const stats = await operationsManager.countCoordinatesOfTarget({ + target: usage.target, + organization: usage.organization, + project: usage.project, + period: usage.period, + }); + + return withUsedByClients(stats, { + selector: usage, + period: usage.period, + operationsManager, + typename, + }); + } + + for (const typename in typeMap) { + if (typename.startsWith('__')) { + continue; + } + + const entity = typeMap[typename]; + + if (isObjectType(entity)) { + types.push({ + entity: transformGraphQLObjectType(entity), + usage() { + return getStats(entity.name); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${typename}.${fieldName}`) ?? + null, + } + : null, + }); + } else if (isInterfaceType(entity)) { + types.push({ + entity: transformGraphQLInterfaceType(entity), + usage() { + return getStats(entity.name); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${typename}.${fieldName}`) ?? + null, + } + : null, + }); + } else if (isEnumType(entity)) { + types.push({ + entity: transformGraphQLEnumType(entity), + usage() { + return getStats(entity.name); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, + getEnumValueOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${typename}.${fieldName}`) ?? + null, + } + : null, + }); + } else if (isUnionType(entity)) { + types.push({ + entity: transformGraphQLUnionType(entity), + usage() { + return getStats(entity.name); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, + getUnionMemberOwnedByServices: (memberName: string) => + supergraph.schemaCoordinateServicesMappings.get(memberName) ?? null, + } + : null, + }); + } else if (isInputObjectType(entity)) { + types.push({ + entity: transformGraphQLInputObjectType(entity), + usage() { + return getStats(entity.name); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, + getInputFieldOwnedByServices: (inputFieldName: string) => + supergraph.schemaCoordinateServicesMappings.get( + `${typename}.${inputFieldName}`, + ) ?? null, + } + : null, + }); + } else if (isScalarType(entity)) { + types.push({ + entity: transformGraphQLScalarType(entity), + usage() { + return getStats(entity.name); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + } + : null, + }); + } + } + + types.sort((a, b) => a.entity.name.localeCompare(b.entity.name)); + + return types; + }, + async query({ schema, supergraph, usage }, _, { injector }) { + const entity = schema.getQueryType(); + + if (!entity) { + return null; + } + + const operationsManager = injector.get(OperationsManager); + + return { + entity: transformGraphQLObjectType(entity), + usage() { + return operationsManager + .countCoordinatesOfType({ + typename: entity.name, + organization: usage.organization, + project: usage.project, + target: usage.target, + period: usage.period, + }) + .then(stats => + withUsedByClients(stats, { + selector: usage, + period: usage.period, + operationsManager, + typename: entity.name, + }), + ); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? + null, + } + : null, + }; + }, + async mutation({ schema, supergraph, usage }, _, { injector }) { + const entity = schema.getMutationType(); + + if (!entity) { + return null; + } + + const operationsManager = injector.get(OperationsManager); + + return { + entity: transformGraphQLObjectType(entity), + usage() { + return operationsManager + .countCoordinatesOfType({ + typename: entity.name, + organization: usage.organization, + project: usage.project, + target: usage.target, + period: usage.period, + }) + .then(stats => + withUsedByClients(stats, { + selector: usage, + period: usage.period, + operationsManager, + typename: entity.name, + }), + ); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? + null, + } + : null, + }; + }, + + async subscription({ schema, supergraph, usage }, _, { injector }) { + const entity = schema.getSubscriptionType(); + + if (!entity) { + return null; + } + + const operationsManager = injector.get(OperationsManager); + + return { + entity: transformGraphQLObjectType(entity), + usage() { + return operationsManager + .countCoordinatesOfType({ + typename: entity.name, + organization: usage.organization, + project: usage.project, + target: usage.target, + period: usage.period, + }) + .then(stats => + withUsedByClients(stats, { + selector: usage, + period: usage.period, + operationsManager, + typename: entity.name, + }), + ); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? + null, + } + : null, + }; + }, + }, + DeprecatedSchemaExplorer: { + types({ sdl, supergraph, usage }, _, { injector }) { + const operationsManager = injector.get(OperationsManager); + + async function getStats(typename: string) { + const stats = await operationsManager.countCoordinatesOfTarget({ + target: usage.target, + organization: usage.organization, + project: usage.project, + period: usage.period, + }); + + return withUsedByClients(stats, { + selector: usage, + period: usage.period, + operationsManager, + typename, + }); + } + + return buildGraphQLTypesFromSDL(sdl, getStats, supergraph).sort((a, b) => + a.entity.name.localeCompare(b.entity.name), + ); + }, + }, + UnusedSchemaExplorer: { + types({ sdl, supergraph, usage }) { + const unused = () => + ({ + isUsed: false, + usedCoordinates: usage.usedCoordinates, + period: usage.period, + organization: usage.organization, + project: usage.project, + target: usage.target, + }) as const; + + return buildGraphQLTypesFromSDL(sdl, unused, supergraph).sort((a, b) => + a.entity.name.localeCompare(b.entity.name), + ); + }, + }, + GraphQLObjectType: { + __isTypeOf: __isTypeOf(Kind.OBJECT_TYPE_DEFINITION), + name: t => t.entity.name, + description: t => t.entity.description ?? null, + fields: t => + t.entity.fields.map(f => ({ + entity: f, + parent: { + coordinate: t.entity.name, + }, + usage: t.usage, + supergraph: t.supergraph + ? { ownedByServiceNames: t.supergraph.getFieldOwnedByServices(f.name) } + : null, + })), + interfaces: t => t.entity.interfaces, + usage, + supergraphMetadata: t => + t.supergraph + ? { + ownedByServiceNames: t.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLInterfaceType: { + __isTypeOf: __isTypeOf(Kind.INTERFACE_TYPE_DEFINITION), + name: t => t.entity.name, + description: t => t.entity.description ?? null, + fields: t => + t.entity.fields.map(f => ({ + entity: f, + parent: { + coordinate: t.entity.name, + }, + usage: t.usage, + supergraph: t.supergraph + ? { ownedByServiceNames: t.supergraph.getFieldOwnedByServices(f.name) } + : null, + })), + interfaces: t => t.entity.interfaces, + usage, + supergraphMetadata: t => + t.supergraph + ? { + ownedByServiceNames: t.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLUnionType: { + __isTypeOf: __isTypeOf(Kind.UNION_TYPE_DEFINITION), + name: t => t.entity.name, + description: t => t.entity.description ?? null, + members: t => + t.entity.members.map(i => { + return { + entity: i, + usage: t.usage, + parent: { + coordinate: t.entity.name, + }, + supergraph: t.supergraph + ? { + ownedByServiceNames: t.supergraph.getUnionMemberOwnedByServices(i.name), + } + : null, + }; + }), + usage, + supergraphMetadata: t => + t.supergraph + ? { + ownedByServiceNames: t.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLEnumType: { + __isTypeOf: __isTypeOf(Kind.ENUM_TYPE_DEFINITION), + name: t => t.entity.name, + description: t => t.entity.description ?? null, + values: t => + t.entity.values.map(v => ({ + entity: v, + parent: { + coordinate: t.entity.name, + }, + usage: t.usage, + supergraph: t.supergraph + ? { ownedByServiceNames: t.supergraph.getEnumValueOwnedByServices(v.name) } + : null, + })), + usage, + supergraphMetadata: t => + t.supergraph + ? { + ownedByServiceNames: t.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLInputObjectType: { + __isTypeOf: __isTypeOf(Kind.INPUT_OBJECT_TYPE_DEFINITION), + name: t => t.entity.name, + description: t => t.entity.description ?? null, + fields: t => + t.entity.fields.map(f => ({ + entity: f, + parent: { + coordinate: t.entity.name, + }, + usage: t.usage, + supergraph: t.supergraph + ? { + ownedByServiceNames: t.supergraph.getInputFieldOwnedByServices(f.name), + } + : null, + })), + usage, + supergraphMetadata: t => + t.supergraph + ? { + ownedByServiceNames: t.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLScalarType: { + __isTypeOf: __isTypeOf(Kind.SCALAR_TYPE_DEFINITION), + name: t => t.entity.name, + description: t => t.entity.description ?? null, + usage, + supergraphMetadata: t => + t.supergraph ? { ownedByServiceNames: t.supergraph.ownedByServiceNames } : null, + }, + GraphQLEnumValue: { + name: v => v.entity.name, + description: v => v.entity.description ?? null, + isDeprecated: v => typeof v.entity.deprecationReason === 'string', + deprecationReason: v => v.entity.deprecationReason ?? null, + usage, + supergraphMetadata: v => + v.supergraph ? { ownedByServiceNames: v.supergraph.ownedByServiceNames } : null, + }, + GraphQLUnionTypeMember: { + name: m => m.entity.name, + usage, + supergraphMetadata: m => + m.supergraph ? { ownedByServiceNames: m.supergraph.ownedByServiceNames } : null, + }, + GraphQLField: { + name: f => f.entity.name, + description: f => f.entity.description ?? null, + isDeprecated: f => typeof f.entity.deprecationReason === 'string', + deprecationReason: f => f.entity.deprecationReason ?? null, + type: f => f.entity.type, + args: f => + f.entity.args.map(a => ({ + entity: a, + parent: { + coordinate: `${f.parent.coordinate}.${f.entity.name}`, + }, + usage: f.usage, + })), + usage, + supergraphMetadata: f => + f.supergraph + ? { + ownedByServiceNames: f.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLInputField: { + name: f => f.entity.name, + description: f => f.entity.description ?? null, + type: f => f.entity.type, + defaultValue: f => stringifyDefaultValue(f.entity.defaultValue), + isDeprecated: f => typeof f.entity.deprecationReason === 'string', + deprecationReason: f => f.entity.deprecationReason ?? null, + usage, + supergraphMetadata: f => + f.supergraph + ? { + ownedByServiceNames: f.supergraph.ownedByServiceNames, + } + : null, + }, + GraphQLArgument: { + name: a => a.entity.name, + description: a => a.entity.description ?? null, + type: a => a.entity.type, + defaultValue: a => stringifyDefaultValue(a.entity.defaultValue), + deprecationReason: a => a.entity.deprecationReason ?? null, + isDeprecated: a => typeof a.entity.deprecationReason === 'string', + usage, + }, + SuccessfulSchemaCheck: { + schemaVersion(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getSchemaVersion(schemaCheck); + }, + safeSchemaChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getSafeSchemaChanges(schemaCheck); + }, + breakingSchemaChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getBreakingSchemaChanges(schemaCheck); + }, + hasSchemaCompositionErrors(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getHasSchemaCompositionErrors(schemaCheck); + }, + hasSchemaChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getHasSchemaChanges(schemaCheck); + }, + hasUnapprovedBreakingChanges() { + return false; + }, + webUrl(schemaCheck, _, { injector }) { + return injector.get(SchemaManager).getSchemaCheckWebUrl({ + schemaCheckId: schemaCheck.id, + targetId: schemaCheck.targetId, + }); + }, + isApproved(schemaCheck) { + return schemaCheck.isManuallyApproved; + }, + approvedBy(schemaCheck, _, { injector }) { + return schemaCheck.isManuallyApproved + ? injector.get(SchemaManager).getApprovedByUser({ + organizationId: schemaCheck.selector.organizationId, + userId: schemaCheck.manualApprovalUserId, + }) + : null; + }, + approvalComment(schemaCheck) { + return schemaCheck.isManuallyApproved ? schemaCheck.manualApprovalComment : null; + }, + contractChecks(schemaCheck, _, { injector }) { + return injector.get(ContractsManager).getContractsChecksForSchemaCheck(schemaCheck); + }, + previousSchemaSDL(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getPreviousSchemaSDL(schemaCheck); + }, + conditionalBreakingChangeMetadata(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getConditionalBreakingChangeMetadata(schemaCheck); + }, + }, + FailedSchemaCheck: { + schemaVersion(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getSchemaVersion(schemaCheck); + }, + safeSchemaChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getSafeSchemaChanges(schemaCheck); + }, + breakingSchemaChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getBreakingSchemaChanges(schemaCheck); + }, + compositionErrors(schemaCheck) { + return schemaCheck.schemaCompositionErrors; + }, + hasSchemaCompositionErrors(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getHasSchemaCompositionErrors(schemaCheck); + }, + hasSchemaChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getHasSchemaChanges(schemaCheck); + }, + hasUnapprovedBreakingChanges(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getHasUnapprovedBreakingChanges(schemaCheck); + }, + webUrl(schemaCheck, _, { injector }) { + return injector.get(SchemaManager).getSchemaCheckWebUrl({ + schemaCheckId: schemaCheck.id, + targetId: schemaCheck.targetId, + }); + }, + async canBeApproved(schemaCheck, _, { injector }) { + return injector.get(SchemaManager).getFailedSchemaCheckCanBeApproved(schemaCheck); + }, + async canBeApprovedByViewer(schemaCheck, _, { injector }) { + return injector.get(SchemaManager).getFailedSchemaCheckCanBeApprovedByViewer(schemaCheck); + }, + contractChecks(schemaCheck, _, { injector }) { + return injector.get(ContractsManager).getContractsChecksForSchemaCheck(schemaCheck); + }, + previousSchemaSDL(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getPreviousSchemaSDL(schemaCheck); + }, + conditionalBreakingChangeMetadata(schemaCheck, _, { injector }) { + return injector.get(SchemaCheckManager).getConditionalBreakingChangeMetadata(schemaCheck); + }, + }, + BreakingChangeMetadataTarget: { + target(record, _, { injector }) { + return injector + .get(TargetManager) + .getTargetById({ targetId: record.id }) + .catch(() => null); + }, + }, + SchemaPolicyWarningConnection: createDummyConnection(warning => ({ + ...warning, + start: { + column: warning.column, + line: warning.line, + }, + end: + warning.endColumn && warning.endLine + ? { + column: warning.endColumn, + line: warning.endLine, + } + : null, + })), + Contract: { + target(contract, _, context) { + return context.injector.get(TargetManager).getTargetById({ + targetId: contract.targetId, + }); + }, + viewerCanDisableContract(contract, _, context) { + return context.injector + .get(ContractsManager) + .getViewerCanDisableContractForContract(contract); + }, + }, + ContractCheck: { + contractVersion(contractCheck, _, context) { + return context.injector + .get(ContractsManager) + .getContractVersionForContractCheck(contractCheck); + }, + compositeSchemaSDL: contractCheck => contractCheck.compositeSchemaSdl, + supergraphSDL: contractCheck => contractCheck.supergraphSdl, + hasSchemaCompositionErrors(contractCheck, _, { injector }) { + return injector + .get(ContractsManager) + .getHasSchemaCompositionErrorsForContractCheck(contractCheck); + }, + hasUnapprovedBreakingChanges(contractCheck, _, { injector }) { + return injector + .get(ContractsManager) + .getHasUnapprovedBreakingChangesForContractCheck(contractCheck); + }, + hasSchemaChanges(contractCheck, _, { injector }) { + return injector.get(ContractsManager).getHasSchemaChangesForContractCheck(contractCheck); + }, + }, + ContractVersion: { + isComposable(contractVersion) { + return contractVersion.schemaCompositionErrors === null; + }, + hasSchemaChanges(contractVersion, _, context) { + return context.injector + .get(ContractsManager) + .getHasSchemaChangesForContractVersion(contractVersion); + }, + breakingSchemaChanges(contractVersion, _, context) { + return context.injector + .get(ContractsManager) + .getBreakingChangesForContractVersion(contractVersion); + }, + safeSchemaChanges(contractVersion, _, context) { + return context.injector + .get(ContractsManager) + .getSafeChangesForContractVersion(contractVersion); + }, + compositeSchemaSDL: contractVersion => contractVersion.compositeSchemaSdl, + supergraphSDL: contractVersion => contractVersion.supergraphSdl, + previousContractVersion: (contractVersion, _, context) => + context.injector + .get(ContractsManager) + .getPreviousContractVersionForContractVersion(contractVersion), + previousDiffableContractVersion: (contractVersion, _, context) => + context.injector + .get(ContractsManager) + .getDiffableContractVersionForContractVersion(contractVersion), + isFirstComposableVersion: (contractVersion, _, context) => + context.injector + .get(ContractsManager) + .getIsFirstComposableVersionForContractVersion(contractVersion), + }, +}; + +function stringifyDefaultValue(value: unknown): string | null { + if (typeof value !== 'undefined') { + return stringify(value); + } + return null; +} + +function withUsedByClients< + T extends { + isUsed: boolean; + }, +>( + input: Record, + deps: { + operationsManager: OperationsManager; + selector: TargetSelector; + period: DateRange; + typename: string; + }, +): Record< + string, + T & { + usedByClients: () => PromiseOrValue>; + period: DateRange; + organization: string; + project: string; + target: string; + typename: string; + } +> { + return Object.fromEntries( + Object.entries(input).map(([schemaCoordinate, record]) => [ + schemaCoordinate, + { + selector: deps.selector, + period: deps.period, + typename: deps.typename, + organization: deps.selector.organization, + project: deps.selector.project, + target: deps.selector.target, + ...record, + usedByClients() { + if (record.isUsed === false) { + return []; + } + + // It's using DataLoader under the hood so it's safe to call it multiple times for different coordinates + return deps.operationsManager.getClientNamesPerCoordinateOfType({ + ...deps.selector, + period: deps.period, + typename: deps.typename, + schemaCoordinate, + }); + }, + }, + ]), + ); +} + +function transformGraphQLObjectType(entity: GraphQLObjectType): GraphQLObjectTypeMapper['entity'] { + return { + kind: Kind.OBJECT_TYPE_DEFINITION, + name: entity.name, + description: entity.description, + interfaces: entity.getInterfaces().map(iface => iface.name), + fields: Object.values(entity.getFields()).map(field => ({ + name: field.name, + description: field.description, + deprecationReason: field.deprecationReason, + type: field.type.toString(), + args: field.args.map(arg => ({ + name: arg.name, + description: arg.description, + defaultValue: arg.defaultValue, + type: arg.type.toString(), + deprecationReason: arg.deprecationReason, + })), + })), + }; +} + +function transformGraphQLInterfaceType( + entity: GraphQLInterfaceType, +): GraphQLInterfaceTypeMapper['entity'] { + return { + kind: Kind.INTERFACE_TYPE_DEFINITION, + name: entity.name, + description: entity.description, + interfaces: entity.getInterfaces().map(iface => iface.name), + fields: Object.values(entity.getFields()).map(field => ({ + name: field.name, + description: field.description, + deprecationReason: field.deprecationReason, + type: field.type.toString(), + args: field.args.map(arg => ({ + name: arg.name, + description: arg.description, + defaultValue: arg.defaultValue, + type: arg.type.toString(), + deprecationReason: arg.deprecationReason, + })), + })), + }; +} + +function transformGraphQLEnumType(entity: GraphQLEnumType): GraphQLEnumTypeMapper['entity'] { + return { + kind: Kind.ENUM_TYPE_DEFINITION, + name: entity.name, + description: entity.description, + values: entity.getValues().map(value => ({ + name: value.name, + description: value.description, + deprecationReason: value.deprecationReason, + })), + }; +} + +function transformGraphQLUnionType(entity: GraphQLUnionType): GraphQLUnionTypeMapper['entity'] { + return { + kind: Kind.UNION_TYPE_DEFINITION, + name: entity.name, + description: entity.description, + members: entity.getTypes().map(type => ({ + name: type.name, + })), + }; +} + +function transformGraphQLInputObjectType( + entity: GraphQLInputObjectType, +): GraphQLInputObjectTypeMapper['entity'] { + return { + kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, + name: entity.name, + description: entity.description, + fields: Object.values(entity.getFields()).map(field => ({ + name: field.name, + description: field.description, + deprecationReason: field.deprecationReason, + defaultValue: field.defaultValue, + type: field.type.toString(), + })), + }; +} + +function transformGraphQLScalarType(entity: GraphQLScalarType): GraphQLScalarTypeMapper['entity'] { + return { + kind: Kind.SCALAR_TYPE_DEFINITION, + name: entity.name, + description: entity.description, + }; +} + +const criticalityMap: Record = { + [CriticalityLevel.Breaking]: 'Breaking', + [CriticalityLevel.NonBreaking]: 'Safe', + [CriticalityLevel.Dangerous]: 'Dangerous', +}; + +function deprecationReasonFromDirectives(directives: readonly ConstDirectiveNode[] | undefined) { + if (!directives) { + return null; + } + + const deprecatedDirective = directives.find(d => d.name.value === 'deprecated'); + + if (!deprecatedDirective) { + return null; + } + + const reasonArgument = deprecatedDirective.arguments?.find(a => a.name.value === 'reason'); + + if (!reasonArgument) { + return DEFAULT_DEPRECATION_REASON; + } + + if (reasonArgument.value.kind !== 'StringValue') { + throw new Error('Expected @deprecated(reason:) to be StringValue'); + } + + return reasonArgument.value.value; +} + +function buildGraphQLTypesFromSDL( + sdl: DocumentNode, + getStats: ( + typeName: string, + ) => ReturnType< + | GraphQLObjectTypeMapper['usage'] + | GraphQLInterfaceTypeMapper['usage'] + | GraphQLUnionTypeMapper['usage'] + | GraphQLEnumTypeMapper['usage'] + | GraphQLInputObjectTypeMapper['usage'] + | GraphQLScalarTypeMapper['usage'] + >, + supergraph: SuperGraphInformation | null, +) { + const types: Array< + | GraphQLObjectTypeMapper + | GraphQLInterfaceTypeMapper + | GraphQLUnionTypeMapper + | GraphQLEnumTypeMapper + | GraphQLInputObjectTypeMapper + | GraphQLScalarTypeMapper + > = []; + + for (const typeDefinition of sdl.definitions) { + if (typeDefinition.kind === Kind.OBJECT_TYPE_DEFINITION) { + types.push({ + entity: { + kind: Kind.OBJECT_TYPE_DEFINITION, + name: typeDefinition.name.value, + description: typeDefinition.description?.value, + interfaces: typeDefinition.interfaces?.map(i => i.name.value) ?? [], + fields: + typeDefinition.fields?.map(f => ({ + name: f.name.value, + description: f.description?.value, + type: print(f.type), + deprecationReason: deprecationReasonFromDirectives(f.directives), + args: + f.arguments?.map(a => ({ + name: a.name.value, + description: a.description?.value, + deprecationReason: deprecationReasonFromDirectives(a.directives), + type: print(a.type), + })) ?? [], + })) ?? [], + }, + usage() { + return getStats(typeDefinition.name.value); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get( + `${typeDefinition.name.value}.${fieldName}`, + ) ?? null, + } + : null, + } satisfies GraphQLObjectTypeMapper); + } else if (typeDefinition.kind === Kind.INTERFACE_TYPE_DEFINITION) { + types.push({ + entity: { + kind: Kind.INTERFACE_TYPE_DEFINITION, + name: typeDefinition.name.value, + description: typeDefinition.description?.value, + interfaces: typeDefinition.interfaces?.map(i => i.name.value) ?? [], + fields: + typeDefinition.fields?.map(f => ({ + name: f.name.value, + description: f.description?.value, + deprecationReason: deprecationReasonFromDirectives(f.directives), + type: print(f.type), + args: + f.arguments?.map(a => ({ + name: a.name.value, + description: a.description?.value, + deprecationReason: deprecationReasonFromDirectives(a.directives), + type: print(a.type), + })) ?? [], + })) ?? [], + }, + usage() { + return getStats(typeDefinition.name.value); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, + getFieldOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get( + `${typeDefinition.name.value}.${fieldName}`, + ) ?? null, + } + : null, + } satisfies GraphQLInterfaceTypeMapper); + } else if (typeDefinition.kind === Kind.ENUM_TYPE_DEFINITION) { + types.push({ + entity: { + kind: Kind.ENUM_TYPE_DEFINITION, + name: typeDefinition.name.value, + description: typeDefinition.description?.value, + values: + typeDefinition.values?.map(value => ({ + name: value.name.value, + description: value.description?.value, + deprecationReason: deprecationReasonFromDirectives(value.directives), + })) ?? [], + }, + usage() { + return getStats(typeDefinition.name.value); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, + getEnumValueOwnedByServices: (fieldName: string) => + supergraph.schemaCoordinateServicesMappings.get( + `${typeDefinition.name.value}.${fieldName}`, + ) ?? null, + } + : null, + } satisfies GraphQLEnumTypeMapper); + } else if (typeDefinition.kind === Kind.UNION_TYPE_DEFINITION) { + types.push({ + entity: { + kind: Kind.UNION_TYPE_DEFINITION, + name: typeDefinition.name.value, + description: typeDefinition.description?.value, + members: + typeDefinition.types?.map(t => ({ + name: t.name.value, + })) ?? [], + }, + usage() { + return getStats(typeDefinition.name.value); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, + getUnionMemberOwnedByServices: (memberName: string) => + supergraph.schemaCoordinateServicesMappings.get(memberName) ?? null, + } + : null, + } satisfies GraphQLUnionTypeMapper); + } else if (typeDefinition.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION) { + types.push({ + entity: { + kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, + name: typeDefinition.name.value, + description: typeDefinition.description?.value, + fields: + typeDefinition.fields?.map(f => ({ + name: f.name.value, + defaultValue: f.defaultValue ? print(f.defaultValue) : undefined, + description: f.description?.value, + deprecationReason: deprecationReasonFromDirectives(f.directives), + type: print(f.type), + })) ?? [], + }, + usage() { + return getStats(typeDefinition.name.value); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, + getInputFieldOwnedByServices: (inputFieldName: string) => + supergraph.schemaCoordinateServicesMappings.get( + `${typeDefinition.name.value}.${inputFieldName}`, + ) ?? null, + } + : null, + } satisfies GraphQLInputObjectTypeMapper); + } else if (typeDefinition.kind === Kind.SCALAR_TYPE_DEFINITION) { + types.push({ + entity: { + kind: Kind.SCALAR_TYPE_DEFINITION, + name: typeDefinition.name.value, + description: typeDefinition.description?.value, + }, + usage() { + return getStats(typeDefinition.name.value); + }, + supergraph: supergraph + ? { + ownedByServiceNames: + supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, + } + : null, + } satisfies GraphQLScalarTypeMapper); + } + } + + return types; +} diff --git a/packages/services/api/src/modules/support/resolvers/Mutation/supportTicketCreate.ts b/packages/services/api/src/modules/support/resolvers/Mutation/supportTicketCreate.ts index 365394c9a7..58c906a434 100644 --- a/packages/services/api/src/modules/support/resolvers/Mutation/supportTicketCreate.ts +++ b/packages/services/api/src/modules/support/resolvers/Mutation/supportTicketCreate.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { SupportManager } from '../../providers/support-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -13,5 +15,24 @@ export const supportTicketCreate: NonNullable = asyn }); if (result.ok) { + const currentUser = await injector.get(AuthManager).getCurrentUser(); + + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'TARGET_CREATED', + targetCreatedAuditLogSchema: { + projectId: project, + targetId: result.target.id, + targetName: result.target.name, + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { ok: { selector: { diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts b/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts index fa04e6d095..08fe21e39d 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/deleteTarget.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../providers/target-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -27,6 +29,25 @@ export const deleteTarget: NonNullable = asyn project: projectId, target: targetId, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'TARGET_DELETED', + targetDeletedAuditLogSchema: { + targetId: target.id, + targetName: target.name, + projectId: projectId, + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { selector: { organization: organizationId, diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts b/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts index 6e3beb25ba..e48094d4f7 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/experimental__updateTargetSchemaComposition.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../providers/target-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -12,10 +14,34 @@ export const experimental__updateTargetSchemaComposition: NonNullable< translator.translateTargetId(input), ]); - return injector.get(TargetManager).updateTargetSchemaComposition({ + const result = await injector.get(TargetManager).updateTargetSchemaComposition({ organizationId, projectId, targetId, nativeComposition: input.nativeComposition, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'TARGET_SETTINGS_UPDATED', + targetSettingsUpdatedAuditLogSchema: { + projectId: projectId, + targetId: targetId, + updatedFields: JSON.stringify({ + schemaComposition: { + updated: input.nativeComposition, + }, + }), + }, + }, + { + organizationId: organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return result; }; diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts b/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts index 8dddfc24c8..6e1654082a 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/setTargetValidation.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../providers/target-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -22,9 +24,35 @@ export const setTargetValidation: NonNullable = async targetScopes: input.targetScopes, }); + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'TARGET_SETTINGS_UPDATED', + targetSettingsUpdatedAuditLogSchema: { + targetId: target, + projectId: project, + updatedFields: JSON.stringify({ + createNewToken: true, + name: input.name, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { ok: { selector: { diff --git a/packages/services/api/src/modules/token/resolvers/Mutation/deleteTokens.ts b/packages/services/api/src/modules/token/resolvers/Mutation/deleteTokens.ts index 01de722b0f..ff85c53aef 100644 --- a/packages/services/api/src/modules/token/resolvers/Mutation/deleteTokens.ts +++ b/packages/services/api/src/modules/token/resolvers/Mutation/deleteTokens.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TokenManager } from '../../providers/token-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -13,7 +15,7 @@ export const deleteTokens: NonNullable = asyn translator.translateProjectId(input), translator.translateTargetId(input), ]); - return { + const result = { selector: { organization: input.organization, project: input.project, @@ -26,4 +28,27 @@ export const deleteTokens: NonNullable = asyn tokens: input.tokens, }), }; + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'TARGET_SETTINGS_UPDATED', + targetSettingsUpdatedAuditLogSchema: { + targetId: target, + projectId: project, + updatedFields: JSON.stringify({ + deleteTokens: true, + tokens: input.tokens, + }), + }, + }, + { + organizationId: organization, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + + return result; }; From 6589d0ee751c3f929e2b9c3e0999beaedc8241d6 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Sun, 13 Oct 2024 16:11:12 +0300 Subject: [PATCH 02/17] remove `await` from schema --- packages/services/api/src/modules/schema/resolvers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/services/api/src/modules/schema/resolvers.ts b/packages/services/api/src/modules/schema/resolvers.ts index 63e2acdf21..96862315bf 100644 --- a/packages/services/api/src/modules/schema/resolvers.ts +++ b/packages/services/api/src/modules/schema/resolvers.ts @@ -501,7 +501,7 @@ export const resolvers: SchemaModule.Resolvers = { translator.translateProjectId(input), ]); - const result = await injector.get(SchemaManager).disableExternalSchemaComposition({ + const result = injector.get(SchemaManager).disableExternalSchemaComposition({ project, organization, }); @@ -533,7 +533,7 @@ export const resolvers: SchemaModule.Resolvers = { translator.translateProjectId(input), ]); - const result = await injector.get(SchemaManager).enableExternalSchemaComposition({ + const result = injector.get(SchemaManager).enableExternalSchemaComposition({ project, organization, endpoint: input.endpoint, From f85930a716a76e01cc13f318ecda57e13a5bda54 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Mon, 14 Oct 2024 19:29:38 +0300 Subject: [PATCH 03/17] fix code review --- .../src/modules/audit-logs/module.graphql.ts | 13 ++-- .../providers/audit-logs-manager.ts | 73 +++++++++---------- .../audit-logs/resolvers/Query/auditLogs.ts | 11 +-- 3 files changed, 44 insertions(+), 53 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts index a3d02d77f5..c36a5b8d9a 100644 --- a/packages/services/api/src/modules/audit-logs/module.graphql.ts +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -20,23 +20,22 @@ export const typeDefs = gql` total: Int! } - # todo: Handle the rest of the AuditLog types input AuditLogFilter { - startDate: DateTime - endDate: DateTime + from: DateTime + to: DateTime userId: ID } - input AuditLogPaginationInput { - limit: Int! = 25 - offset: Int! = 0 + input AuditLogPaginationFilter { + limit: Int = 25 + offset: Int = 0 } extend type Query { auditLogs( selector: OrganizationSelectorInput! filter: AuditLogFilter - pagination: AuditLogPaginationInput + pagination: AuditLogPaginationFilter ): AuditLogConnection! } diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index 0d8fb71133..a7e12de41c 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import * as Sentry from '@sentry/node'; import { QueryAuditLogsArgs } from '../../../__generated__/types.next'; import { User } from '../../../shared/entities'; +import { parseDateRangeInput } from '../../../shared/helpers'; import { ClickHouse, sql } from '../../operations/providers/clickhouse-client'; import { SqlValue } from '../../operations/providers/sql'; import { Logger } from '../../shared/providers/logger'; @@ -40,7 +41,7 @@ export class AuditLogManager { logger: Logger, private clickHouse: ClickHouse, ) { - this.logger = logger.child({ source: 'AuditLogsManager' }); + this.logger = logger.child({ source: 'AuditLogManager' }); } createLogAuditEvent(event: AuditLogEvent, record: AuditLogRecordEvent): void { @@ -90,53 +91,36 @@ export class AuditLogManager { props: QueryAuditLogsArgs, ): Promise<{ total: number; data: AuditLogModel[] }> { this.logger.info( - 'Getting paginated audit logs (limit=%s, offset=%s, orgId=%s, userId=%s, action=%s)', + 'Getting paginated audit logs (organization=%s, filter=%o, pagination=%o)', props.selector.organization, - props.filter?.endDate, - props.filter?.startDate, - props.filter?.userId, + props.filter, + props.pagination, ); - // Handle the limit and offset for pagination - // let limit: SqlValue[] = []; - // let offset: SqlValue[] = []; - // if (props?.pagination?.limit) { - // limit.push(sql`LIMIT ${String(props.pagination.limit)}`); - // } else { - // limit.push(sql`LIMIT 25`); - // } - // if (props?.pagination?.offset) { - // offset.push(sql`OFFSET ${String(props.pagination.offset)}`); - // } else { - // offset.push(sql`OFFSET 0`); - // } - const limit = props.pagination?.limit ?? 25; - const sqlLimit = sql.raw(limit.toString()); - const offset = props.pagination?.offset ?? 0; - const sqlOffset = sql.raw(offset.toString()); - - const where: SqlValue[] = []; - if (props.selector.organization) { - where.push(sql`organization_id = ${props.selector.organization}`); - } else { - // Handle case where organization_id is not provided - this.logger.warn('No organization_id provided in query'); + if (!props.selector.organization) { + throw new Error('Organization ID is required'); } + const sqlLimit = sql.raw(props.pagination?.limit?.toString()!); + const sqlOffset = sql.raw(props.pagination?.offset?.toString()!); + + let where: SqlValue[] = []; + where.push(sql`organization_id = ${props.selector.organization}`); + if (props.filter) { - // if (props.filter?.startDate) { - // const dateIso = new Date(props.filter.startDate).toISOString(); - // where.push(sql`event_time >= ${dateIso}`); - // } - // if (props.filter?.endDate) { - // const dateIso = new Date(props.filter.endDate).toISOString(); - // where.push(sql`event_time <= ${dateIso}`); - // } if (props.filter?.userId) { where.push(sql`user_id = ${props.filter.userId}`); } + if (props.filter?.from && props.filter?.to) { + console.log('new Date', new Date()); + const periods = parseDateRangeInput({ + from: new Date(props.filter.from), + to: new Date(props.filter.to), + }); + where.push(sql`event_time >= ${periods.from.toISOString()}`); + where.push(sql`event_time <= ${periods.to.toISOString()}`); + } } - const whereClause = where.length > 0 ? sql`WHERE ${sql.join(where, ' AND ')}` : sql``; const result = await this.clickHouse.query({ @@ -152,8 +136,19 @@ export class AuditLogManager { timeout: 5000, }); + const totalResult = await this.clickHouse.query({ + query: sql` + SELECT * + FROM audit_log + ${whereClause} + ORDER BY event_time DESC + `, + queryId: 'get-audit-logs-total', + timeout: 5000, + }); + return { - total: result.rows, + total: totalResult.rows, data: AUDIT_LOG_CLICKHOUSE_ARRAY.parse(result.data), }; } diff --git a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts index 79761f0c6b..2ef8d0e9d0 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts @@ -4,11 +4,10 @@ import { AuditLogManager } from '../../providers/audit-logs-manager'; import type { QueryResolvers } from './../../../../__generated__/types.next'; export const auditLogs: NonNullable = async (_parent, args, ctx) => { - const isAdmin = ctx.injector - .get(AuthManager) - .getCurrentUser() - ?.then(user => user.isAdmin); - if (!isAdmin) { + const isOwner = ctx.injector.get(AuthManager).ensureOrganizationOwnership({ + organization: args.selector.organization, + }); + if (!isOwner) { throw new GraphQLError('Unauthorized: You are not authorized to perform this action'); } @@ -19,8 +18,6 @@ export const auditLogs: NonNullable = async (_paren pagination: pagination, }); - console.log('auditLogs', auditLogs); - return { nodes: auditLogs.data, total: auditLogs.total, From 5d40e4348d1ca0e25c531510b4d0b0a1f2ea4a33 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Mon, 14 Oct 2024 20:18:25 +0300 Subject: [PATCH 04/17] lint --- .../modules/audit-logs/providers/audit-logs-manager.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index a7e12de41c..8dafe2b803 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -100,8 +100,9 @@ export class AuditLogManager { if (!props.selector.organization) { throw new Error('Organization ID is required'); } - + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain const sqlLimit = sql.raw(props.pagination?.limit?.toString()!); + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain const sqlOffset = sql.raw(props.pagination?.offset?.toString()!); let where: SqlValue[] = []; @@ -112,15 +113,16 @@ export class AuditLogManager { where.push(sql`user_id = ${props.filter.userId}`); } if (props.filter?.from && props.filter?.to) { - console.log('new Date', new Date()); const periods = parseDateRangeInput({ from: new Date(props.filter.from), to: new Date(props.filter.to), }); - where.push(sql`event_time >= ${periods.from.toISOString()}`); - where.push(sql`event_time <= ${periods.to.toISOString()}`); + where.push( + sql`event_time >= ${periods.from.toISOString()} AND event_time <= ${periods.to.toISOString()}`, + ); } } + const whereClause = where.length > 0 ? sql`WHERE ${sql.join(where, ' AND ')}` : sql``; const result = await this.clickHouse.query({ From 6d33bf7274b28b7b35a4041a3e9594e810caeccd Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Tue, 15 Oct 2024 14:25:09 +0300 Subject: [PATCH 05/17] graphql comments --- .../src/modules/audit-logs/module.graphql.ts | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts index c36a5b8d9a..abf0cb9a5e 100644 --- a/packages/services/api/src/modules/audit-logs/module.graphql.ts +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -1,6 +1,11 @@ import { gql } from 'graphql-modules'; export const typeDefs = gql` + """ + AuditLog is a record of actions performed by users in + the organization. It is used to track changes and + actions performed by users. + """ interface AuditLog { id: ID! eventTime: DateTime! @@ -11,8 +16,14 @@ export const typeDefs = gql` userId: ID! userEmail: String! organizationId: ID! - user: User # This one is nullable because it can be deleted! - organization: Organization # This one is nullable because it can be deleted! + """ + User can be null if the user is deleted + """ + user: User + """ + Organization can be null if the organization is deleted + """ + organization: Organization } type AuditLogConnection { @@ -20,12 +31,20 @@ export const typeDefs = gql` total: Int! } + """ + AuditLogFilter is used to filter audit logs by + date range and user id. + """ input AuditLogFilter { from: DateTime to: DateTime userId: ID } + """ + AuditLogPaginationFilter is used to paginate audit logs. + By default, it returns 25 records with an offset of 0. + """ input AuditLogPaginationFilter { limit: Int = 25 offset: Int = 0 @@ -39,6 +58,9 @@ export const typeDefs = gql` ): AuditLogConnection! } + """ + Schema Policy Audit Logs + """ type SchemaPolicySettingsUpdatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -76,7 +98,9 @@ export const typeDefs = gql` serviceName: String! } - # Project Audit Logs + """ + Project Audit Logs + """ type ProjectCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -101,7 +125,9 @@ export const typeDefs = gql` projectName: String! } - # User Role Audit Logs + """ + User Role Audit Logs + """ type RoleCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -137,7 +163,9 @@ export const typeDefs = gql` updatedFields: JSON! } - # Support Ticket Audit Logs + """ + Support Ticket Audit Logs + """ type SupportTicketCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -156,7 +184,9 @@ export const typeDefs = gql` updatedFields: JSON! } - # Laboratory Collection Audit Logs + """ + Laboratory Collection Audit Logs + """ type CollectionCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -182,7 +212,10 @@ export const typeDefs = gql` collectionId: String! collectionName: String! } - # Operation In Document Collection Audit Logs + + """ + Operation In Document Collection Audit Logs + """ type OperationInDocumentCollectionCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -213,7 +246,9 @@ export const typeDefs = gql` operationId: String! } - # Organization Audit Logs + """ + Organization Audit Logs + """ type OrganizationSettingsUpdatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -234,7 +269,10 @@ export const typeDefs = gql` eventTime: DateTime! record: AuditLogIdRecord! newOwnerId: String! - newOwnerEmail: String # This one is nullable because the mutation can fail + """ + newOwnerEmail can be null if the mutation fails + """ + newOwnerEmail: String } type OrganizationCreatedAuditLog implements AuditLog { @@ -260,7 +298,9 @@ export const typeDefs = gql` updatedFields: JSON! } - # Target + """ + Target Audit Logs + """ type TargetCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -288,7 +328,9 @@ export const typeDefs = gql` targetName: String! } - # User + """ + User Audit Logs + """ type UserInvitedAuditLog implements AuditLog { id: ID! eventTime: DateTime! @@ -320,7 +362,9 @@ export const typeDefs = gql` updatedFields: JSON! } - # Subscription + """ + Subscription Audit Logs + """ type SubscriptionCreatedAuditLog implements AuditLog { id: ID! eventTime: DateTime! From 9bbfcec829cc14c103902b45f77e7687d3591812 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Tue, 15 Oct 2024 15:30:30 +0300 Subject: [PATCH 06/17] schema + app deployment --- .../Mutation/activateAppDeployment.ts | 24 ++++++++++++ .../Mutation/addDocumentsToAppDeployment.ts | 24 ++++++++++++ .../resolvers/Mutation/createAppDeployment.ts | 21 +++++++++++ .../resolvers/Mutation/retireAppDeployment.ts | 24 ++++++++++++ .../src/modules/audit-logs/module.graphql.ts | 28 ++++++++++++++ .../audit-logs/providers/audit-logs-types.ts | 29 +++++++++++++++ .../Mutation/approveFailedSchemaCheck.ts | 20 ++++++++++ .../disableExternalSchemaComposition.ts | 25 ++++++++++++- .../enableExternalSchemaComposition.ts | 25 ++++++++++++- .../schema/resolvers/Mutation/schemaCheck.ts | 21 +++++++++++ .../resolvers/Mutation/schemaCompose.ts | 22 +++++++++++ .../schema/resolvers/Mutation/schemaDelete.ts | 20 ++++++++++ .../resolvers/Mutation/schemaPublish.ts | 21 +++++++++++ .../resolvers/Mutation/updateBaseSchema.ts | 22 +++++++++++ .../Mutation/updateNativeFederation.ts | 33 ++++++++++++++--- .../Mutation/updateProjectRegistryModel.ts | 37 ++++++++++++++++--- .../Mutation/updateSchemaVersionStatus.ts | 27 +++++++++++++- 17 files changed, 410 insertions(+), 13 deletions(-) diff --git a/packages/services/api/src/modules/app-deployments/resolvers/Mutation/activateAppDeployment.ts b/packages/services/api/src/modules/app-deployments/resolvers/Mutation/activateAppDeployment.ts index 41e48475af..019d748be0 100644 --- a/packages/services/api/src/modules/app-deployments/resolvers/Mutation/activateAppDeployment.ts +++ b/packages/services/api/src/modules/app-deployments/resolvers/Mutation/activateAppDeployment.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { AppDeploymentsManager } from '../../providers/app-deployments-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -20,6 +22,28 @@ export const activateAppDeployment: NonNullable< }; } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const organization = await injector.get(AuthManager).getOrganizationOwnerByToken(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'APP_DEPLOYMENT_UPDATED', + appDeploymentUpdatedAuditLogSchema: { + deploymentId: result.appDeployment.id, + updatedFields: JSON.stringify({ + name: result.appDeployment.name, + version: result.appDeployment.version, + status: 'ACTIVATED', + }), + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization.id, + user: currentUser, + }, + ); + return { error: null, ok: { diff --git a/packages/services/api/src/modules/app-deployments/resolvers/Mutation/addDocumentsToAppDeployment.ts b/packages/services/api/src/modules/app-deployments/resolvers/Mutation/addDocumentsToAppDeployment.ts index 4ced99f869..8855f8832a 100644 --- a/packages/services/api/src/modules/app-deployments/resolvers/Mutation/addDocumentsToAppDeployment.ts +++ b/packages/services/api/src/modules/app-deployments/resolvers/Mutation/addDocumentsToAppDeployment.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { AppDeploymentsManager } from '../../providers/app-deployments-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -22,6 +24,28 @@ export const addDocumentsToAppDeployment: NonNullable< }; } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const organization = await injector.get(AuthManager).getOrganizationOwnerByToken(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'APP_DEPLOYMENT_UPDATED', + appDeploymentUpdatedAuditLogSchema: { + deploymentId: result.appDeployment.id, + updatedFields: JSON.stringify({ + name: result.appDeployment.name, + version: result.appDeployment.version, + documents: input.documents, + }), + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization.id, + user: currentUser, + }, + ); + return { error: null, ok: { diff --git a/packages/services/api/src/modules/app-deployments/resolvers/Mutation/createAppDeployment.ts b/packages/services/api/src/modules/app-deployments/resolvers/Mutation/createAppDeployment.ts index 2220214c78..befdc36816 100644 --- a/packages/services/api/src/modules/app-deployments/resolvers/Mutation/createAppDeployment.ts +++ b/packages/services/api/src/modules/app-deployments/resolvers/Mutation/createAppDeployment.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { AppDeploymentsManager } from '../../providers/app-deployments-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -23,6 +25,25 @@ export const createAppDeployment: NonNullable; diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/approveFailedSchemaCheck.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/approveFailedSchemaCheck.ts index 11661b54b6..27acbac0ea 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/approveFailedSchemaCheck.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/approveFailedSchemaCheck.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { SchemaManager } from '../../providers/schema-manager'; import { toGraphQLSchemaCheck } from '../../to-graphql-schema-check'; @@ -28,6 +30,24 @@ export const approveFailedSchemaCheck: NonNullable< }; } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_CHECKED', + schemaCheckedAuditLogSchema: { + checkId: result.schemaCheck.id, + projectId, + targetId, + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organizationId, + user: currentUser, + }, + ); + return { ok: { schemaCheck: toGraphQLSchemaCheck( diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/disableExternalSchemaComposition.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/disableExternalSchemaComposition.ts index 548f89a7f5..3d9dc83fe2 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/disableExternalSchemaComposition.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/disableExternalSchemaComposition.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { SchemaManager } from '../../providers/schema-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -11,8 +13,29 @@ export const disableExternalSchemaComposition: NonNullable< translator.translateProjectId(input), ]); - return injector.get(SchemaManager).disableExternalSchemaComposition({ + const result = injector.get(SchemaManager).disableExternalSchemaComposition({ project, organization, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_POLICY_SETTINGS_UPDATED', + schemaPolicySettingsUpdatedAuditLogSchema: { + projectId: project, + updatedFields: JSON.stringify({ + externalSchemaComposition: false, + }), + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization, + user: currentUser, + }, + ); + + return result; }; diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/enableExternalSchemaComposition.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/enableExternalSchemaComposition.ts index a9b5af5bf2..ab4cdc8a71 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/enableExternalSchemaComposition.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/enableExternalSchemaComposition.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { SchemaManager } from '../../providers/schema-manager'; import type { MutationResolvers } from './../../../../__generated__/types.next'; @@ -11,10 +13,31 @@ export const enableExternalSchemaComposition: NonNullable< translator.translateProjectId(input), ]); - return injector.get(SchemaManager).enableExternalSchemaComposition({ + const result = injector.get(SchemaManager).enableExternalSchemaComposition({ project, organization, endpoint: input.endpoint, secret: input.secret, }); + + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_POLICY_SETTINGS_UPDATED', + schemaPolicySettingsUpdatedAuditLogSchema: { + projectId: project, + updatedFields: JSON.stringify({ + externalSchemaComposition: true, + }), + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization, + user: currentUser, + }, + ); + + return result; }; diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCheck.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCheck.ts index 194346fea2..9069e142d3 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCheck.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCheck.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { ProjectManager } from '../../../project/providers/project-manager'; import { TargetManager } from '../../../target/providers/target-manager'; @@ -35,5 +37,24 @@ export const schemaCheck: NonNullable = async }; } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_CHECKED', + schemaCheckedAuditLogSchema: { + checkId: + result.__typename === 'GitHubSchemaCheckSuccess' ? String(result.checkRun.id) : null, + projectId: project, + targetId: target, + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization, + user: currentUser, + }, + ); + return result; }; diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCompose.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCompose.ts index 47e57b4097..0684ad3abc 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCompose.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaCompose.ts @@ -1,3 +1,5 @@ +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { ProjectManager } from '../../../project/providers/project-manager'; import { TargetManager } from '../../../target/providers/target-manager'; @@ -30,6 +32,26 @@ export const schemaCompose: NonNullable = as }; } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_POLICY_SETTINGS_UPDATED', + schemaPolicySettingsUpdatedAuditLogSchema: { + projectId: project, + updatedFields: JSON.stringify({ + supergraphSDL: result.supergraphSDL, + kind: result.kind, + }), + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization, + user: currentUser, + }, + ); + return { __typename: 'SchemaComposeSuccess', valid: 'supergraphSDL' in result && result.supergraphSDL !== null, diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaDelete.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaDelete.ts index 6ab9e5fbbb..18822f3af6 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaDelete.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaDelete.ts @@ -1,5 +1,6 @@ import { createHash } from 'node:crypto'; import stringify from 'fast-json-stable-stringify'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { ProjectManager } from '../../../project/providers/project-manager'; @@ -42,6 +43,25 @@ export const schemaDelete: NonNullable = asyn request.signal, ); + const currentUser = await injector.get(AuthManager).getCurrentUser(); + const projectId = await injector.get(ProjectManager).getProjectIdByToken(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_DELETED', + schemaDeletedAuditLogSchema: { + serviceName: input.serviceName.toLowerCase(), + projectId: projectId, + targetId: target.id, + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization, + user: currentUser, + }, + ); + return { ...result, changes: result.changes, diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaPublish.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaPublish.ts index 70bc1c562e..8f5662eb04 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/schemaPublish.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/schemaPublish.ts @@ -1,4 +1,6 @@ import { parseResolveInfo } from 'graphql-parse-resolve-info'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { ProjectManager } from '../../../project/providers/project-manager'; import { TargetManager } from '../../../target/providers/target-manager'; @@ -42,5 +44,24 @@ export const schemaPublish: NonNullable = as }; } + const currentUser = await injector.get(AuthManager).getCurrentUser(); + await injector.get(AuditLogManager).createLogAuditEvent( + { + eventType: 'SCHEMA_PUBLISH', + schemaPublishAuditLogSchema: { + isSchemaPublishMissingUrlErrorSelected: isSchemaPublishMissingUrlErrorSelected, + projectId: project, + targetId: target, + serviceName: input.service?.toLowerCase(), + }, + }, + { + userId: currentUser.id, + userEmail: currentUser.email, + organizationId: organization, + user: currentUser, + }, + ); + return result; }; diff --git a/packages/services/api/src/modules/schema/resolvers/Mutation/updateBaseSchema.ts b/packages/services/api/src/modules/schema/resolvers/Mutation/updateBaseSchema.ts index a21c2cdada..b00ae893a4 100644 --- a/packages/services/api/src/modules/schema/resolvers/Mutation/updateBaseSchema.ts +++ b/packages/services/api/src/modules/schema/resolvers/Mutation/updateBaseSchema.ts @@ -1,4 +1,6 @@ import { z } from 'zod'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { TargetManager } from '../../../target/providers/target-manager'; import { SchemaManager } from '../../providers/schema-manager'; @@ -37,6 +39,26 @@ export const updateBaseSchema: NonNullable Date: Tue, 15 Oct 2024 15:57:16 +0300 Subject: [PATCH 07/17] fix --- .../src/modules/audit-logs/providers/audit-logs-manager.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index 8dafe2b803..bbafa432d8 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -3,7 +3,6 @@ import { z } from 'zod'; import * as Sentry from '@sentry/node'; import { QueryAuditLogsArgs } from '../../../__generated__/types.next'; import { User } from '../../../shared/entities'; -import { parseDateRangeInput } from '../../../shared/helpers'; import { ClickHouse, sql } from '../../operations/providers/clickhouse-client'; import { SqlValue } from '../../operations/providers/sql'; import { Logger } from '../../shared/providers/logger'; @@ -113,12 +112,8 @@ export class AuditLogManager { where.push(sql`user_id = ${props.filter.userId}`); } if (props.filter?.from && props.filter?.to) { - const periods = parseDateRangeInput({ - from: new Date(props.filter.from), - to: new Date(props.filter.to), - }); where.push( - sql`event_time >= ${periods.from.toISOString()} AND event_time <= ${periods.to.toISOString()}`, + sql`event_time >= ${new Date(props.filter.from).toISOString()} AND event_time <= ${new Date(props.filter.to).toISOString()}`, ); } } From 28d15551ad71aeae2cd0ed8610f4c9fb8c9d9a13 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Tue, 15 Oct 2024 18:04:43 +0300 Subject: [PATCH 08/17] some --- packages/services/api/src/modules/audit-logs/helpers.ts | 5 +++++ .../src/modules/audit-logs/providers/audit-logs-manager.ts | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/helpers.ts b/packages/services/api/src/modules/audit-logs/helpers.ts index 7531b9f21a..25440b9a21 100644 --- a/packages/services/api/src/modules/audit-logs/helpers.ts +++ b/packages/services/api/src/modules/audit-logs/helpers.ts @@ -14,3 +14,8 @@ export function resolveRecordAuditLog(event: AuditLogModel, injector: Injector) organization: currentOrganization, }; } + +export function formatToClickhouseDateTime(date: string): string { + const d = new Date(date); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`; +} diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index bbafa432d8..141c5bef7a 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -6,6 +6,7 @@ import { User } from '../../../shared/entities'; import { ClickHouse, sql } from '../../operations/providers/clickhouse-client'; import { SqlValue } from '../../operations/providers/sql'; import { Logger } from '../../shared/providers/logger'; +import { formatToClickhouseDateTime } from '../helpers'; import { AuditLogEvent, auditLogSchema } from './audit-logs-types'; export const AUDIT_LOG_CLICKHOUSE_OBJECT = z.object({ @@ -112,9 +113,9 @@ export class AuditLogManager { where.push(sql`user_id = ${props.filter.userId}`); } if (props.filter?.from && props.filter?.to) { - where.push( - sql`event_time >= ${new Date(props.filter.from).toISOString()} AND event_time <= ${new Date(props.filter.to).toISOString()}`, - ); + const from = formatToClickhouseDateTime(props.filter.from.toISOString()); + const to = formatToClickhouseDateTime(props.filter.to.toISOString()); + where.push(sql`event_time >= ${from} AND event_time <= ${to}`); } } From c4234dedcb62700a046533e384eee73c591eb352 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Tue, 15 Oct 2024 18:31:03 +0300 Subject: [PATCH 09/17] clean and clear --- .../services/api/src/modules/audit-logs/module.graphql.ts | 4 ++-- .../src/modules/audit-logs/providers/audit-logs-manager.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts index 902e766ebd..f5880c710f 100644 --- a/packages/services/api/src/modules/audit-logs/module.graphql.ts +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -36,8 +36,8 @@ export const typeDefs = gql` date range and user id. """ input AuditLogFilter { - from: DateTime - to: DateTime + startDate: DateTime + endDate: DateTime userId: ID } diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index 141c5bef7a..530d7d4a30 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -112,9 +112,9 @@ export class AuditLogManager { if (props.filter?.userId) { where.push(sql`user_id = ${props.filter.userId}`); } - if (props.filter?.from && props.filter?.to) { - const from = formatToClickhouseDateTime(props.filter.from.toISOString()); - const to = formatToClickhouseDateTime(props.filter.to.toISOString()); + if (props.filter?.startDate && props.filter?.endDate) { + const from = formatToClickhouseDateTime(props.filter.startDate.toISOString()); + const to = formatToClickhouseDateTime(props.filter.endDate.toISOString()); where.push(sql`event_time >= ${from} AND event_time <= ${to}`); } } From 5d62dae13ed708a781e11ec2d663c43e2cf5d5e6 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Wed, 16 Oct 2024 18:10:21 +0300 Subject: [PATCH 10/17] some and some --- .../audit-logs/module.graphql.mappers.ts | 6 +++++- .../src/modules/audit-logs/module.graphql.ts | 2 +- .../audit-logs/providers/audit-logs-types.ts | 10 ++++++++++ .../resolvers/AppDeploymentCreatedAuditLog.ts | 20 +++++++++++++++++++ .../AppDeploymentPublishedAuditLog.ts | 19 ++++++++++++++++++ .../resolvers/AppDeploymentUpdatedAuditLog.ts | 19 ++++++++++++++++++ .../resolvers/ServiceDeletedAuditLog.ts | 20 +++++++++++++++++++ 7 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts create mode 100644 packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts b/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts index 42546e2417..b6599e1351 100644 --- a/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts +++ b/packages/services/api/src/modules/audit-logs/module.graphql.mappers.ts @@ -5,7 +5,7 @@ export type AuditLogMapper = AuditLogModel; export type SchemaPolicySettingsUpdatedAuditLogMapper = AuditLogModel; export type SchemaCheckedAuditLogMapper = AuditLogModel; export type SchemaPublishAuditLogMapper = AuditLogModel; -export type SchemaDeletedAuditLogMapper = AuditLogModel; +export type ServiceDeletedAuditLogMapper = AuditLogModel; // Organization export type OrganizationSettingsUpdatedAuditLogMapper = AuditLogModel; export type OrganizationTransferredAuditLogMapper = AuditLogModel; @@ -46,3 +46,7 @@ export type TargetDeletedAuditLogMapper = AuditLogModel; export type SubscriptionCreatedAuditLogMapper = AuditLogModel; export type SubscriptionUpdatedAuditLogMapper = AuditLogModel; export type SubscriptionCanceledAuditLogMapper = AuditLogModel; +// App Deployment +export type AppDeploymentCreatedAuditLogMapper = AuditLogModel; +export type AppDeploymentUpdatedAuditLogMapper = AuditLogModel; +export type AppDeploymentPublishedAuditLogMapper = AuditLogModel; diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts index f5880c710f..9a278f2159 100644 --- a/packages/services/api/src/modules/audit-logs/module.graphql.ts +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -410,7 +410,7 @@ export const typeDefs = gql` updatedFields: JSON! } - type appDeploymentPublishedAuditLog implements AuditLog { + type AppDeploymentPublishedAuditLog implements AuditLog { id: ID! eventTime: DateTime! record: AuditLogIdRecord! diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts index da1d6c9dfd..5b9551d9e7 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts @@ -219,6 +219,12 @@ const appDeploymentPublishedAuditLogSchema = z.object({ deploymentVersion: z.string(), }); +const serviceDeletedAuditLogSchema = z.object({ + serviceName: z.string(), + targetId: z.string(), + projectId: z.string(), +}); + export const auditLogSchema = z.discriminatedUnion('eventType', [ z.object({ eventType: z.literal('USER_INVITED'), @@ -288,6 +294,10 @@ export const auditLogSchema = z.discriminatedUnion('eventType', [ eventType: z.literal('SCHEMA_DELETED'), schemaDeletedAuditLogSchema, }), + z.object({ + eventType: z.literal('SERVICE_DELETED'), + serviceDeletedAuditLogSchema, + }), z.object({ eventType: z.literal('ROLE_CREATED'), roleCreatedAuditLogSchema, diff --git a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts new file mode 100644 index 0000000000..55ebb793fb --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { AppDeploymentCreatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "AppDeploymentCreatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const AppDeploymentCreatedAuditLog: AppDeploymentCreatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'APP_DEPLOYMENT_CREATED', + eventTime: e => new Date(e.event_time).toISOString(), + deploymentId: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentId, + deploymentName: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentName, + deploymentVersion: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentVersion, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts new file mode 100644 index 0000000000..61ffcfacec --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { AppDeploymentPublishedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "AppDeploymentPublishedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const AppDeploymentPublishedAuditLog: AppDeploymentPublishedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'APP_DEPLOYMENT_PUBLISHED', + eventTime: e => new Date(e.event_time).toISOString(), + deploymentId: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentId, + deploymentVersion: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentVersion, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts new file mode 100644 index 0000000000..c1822c6d3c --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts @@ -0,0 +1,19 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { AppDeploymentUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "AppDeploymentUpdatedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const AppDeploymentUpdatedAuditLog: AppDeploymentUpdatedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'APP_DEPLOYMENT_UPDATED', + eventTime: e => new Date(e.event_time).toISOString(), + deploymentId: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentId, + updatedFields: e => e.metadata.appDeploymentUpdatedAuditLogSchema.updatedFields, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts new file mode 100644 index 0000000000..f369b6ea57 --- /dev/null +++ b/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts @@ -0,0 +1,20 @@ +import { resolveRecordAuditLog } from '../helpers'; +import type { ServiceDeletedAuditLogResolvers } from './../../../__generated__/types.next'; + +/* + * Note: This object type is generated because "ServiceDeletedAuditLogMapper" is declared. This is to ensure runtime safety. + * + * When a mapper is used, it is possible to hit runtime errors in some scenarios: + * - given a field name, the schema type's field type does not match mapper's field type + * - or a schema type's field does not exist in the mapper's fields + * + * If you want to skip this file generation, remove the mapper or update the pattern in the `resolverGeneration.object` config. + */ +export const ServiceDeletedAuditLog: ServiceDeletedAuditLogResolvers = { + __isTypeOf: e => e.event_action === 'SERVICE_DELETED', + eventTime: e => new Date(e.event_time).toISOString(), + projectId: e => e.metadata.serviceDeletedAuditLogSchema.projectId, + serviceName: e => e.metadata.serviceDeletedAuditLogSchema.serviceName, + targetId: e => e.metadata.serviceDeletedAuditLogSchema.targetId, + record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), +}; From 95479b8ad4848ecac27806307e9b1431550fe987 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Sat, 19 Oct 2024 13:24:05 +0300 Subject: [PATCH 11/17] basic test --- .../tests/api/audit-log/creation.spec.ts | 41 +++++++++++++++++++ .../providers/audit-logs-manager.ts | 21 +++++----- .../resolvers/Mutation/createOrganization.ts | 6 +-- .../resolvers/Mutation/createProject.ts | 4 +- 4 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 integration-tests/tests/api/audit-log/creation.spec.ts diff --git a/integration-tests/tests/api/audit-log/creation.spec.ts b/integration-tests/tests/api/audit-log/creation.spec.ts new file mode 100644 index 0000000000..773240747d --- /dev/null +++ b/integration-tests/tests/api/audit-log/creation.spec.ts @@ -0,0 +1,41 @@ +import { graphql } from 'testkit/gql'; +import { execute } from '../../../testkit/graphql'; +import { initSeed } from '../../../testkit/seed'; + +describe('Audit Logs Creation', () => { + describe('Organization', () => { + const query = graphql(` + query MyQuery($selector: OrganizationSelectorInput!) { + auditLogs(selector: $selector) { + nodes { + eventTime + id + __typename + } + } + } + `); + test.concurrent( + 'Should be only one audit log for organization creation', + async ({ expect }) => { + const { ownerToken, createOrg } = await initSeed().createOwner(); + const { organization } = await createOrg(); + + const result = await execute({ + document: query, + variables: { + selector: { + organization: organization.id, + }, + }, + authToken: ownerToken, + }); + expect(result.rawBody.data?.auditLogs.nodes).not.toBeNull(); + expect(result.rawBody.data?.auditLogs.nodes.length).toBeGreaterThan(0); + expect(result.rawBody.data?.auditLogs.nodes[0].__typename).toBe( + 'OrganizationCreatedAuditLog', + ); + }, + ); + }); +}); diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index 530d7d4a30..1f06e7ceef 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -101,22 +101,21 @@ export class AuditLogManager { throw new Error('Organization ID is required'); } // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - const sqlLimit = sql.raw(props.pagination?.limit?.toString()!); + const sqlLimit = sql.raw(props.pagination?.limit?.toString() ?? '25'); // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - const sqlOffset = sql.raw(props.pagination?.offset?.toString()!); + const sqlOffset = sql.raw(props.pagination?.offset?.toString() ?? '0'); let where: SqlValue[] = []; where.push(sql`organization_id = ${props.selector.organization}`); - if (props.filter) { - if (props.filter?.userId) { - where.push(sql`user_id = ${props.filter.userId}`); - } - if (props.filter?.startDate && props.filter?.endDate) { - const from = formatToClickhouseDateTime(props.filter.startDate.toISOString()); - const to = formatToClickhouseDateTime(props.filter.endDate.toISOString()); - where.push(sql`event_time >= ${from} AND event_time <= ${to}`); - } + if (props.filter?.userId) { + where.push(sql`user_id = ${props.filter.userId}`); + } + + if (props.filter?.startDate && props.filter?.endDate) { + const from = formatToClickhouseDateTime(props.filter.startDate.toISOString()); + const to = formatToClickhouseDateTime(props.filter.endDate.toISOString()); + where.push(sql`event_time >= ${from} AND event_time <= ${to}`); } const whereClause = where.length > 0 ? sql`WHERE ${sql.join(where, ' AND ')}` : sql``; diff --git a/packages/services/api/src/modules/organization/resolvers/Mutation/createOrganization.ts b/packages/services/api/src/modules/organization/resolvers/Mutation/createOrganization.ts index c6e623654d..28ad890877 100644 --- a/packages/services/api/src/modules/organization/resolvers/Mutation/createOrganization.ts +++ b/packages/services/api/src/modules/organization/resolvers/Mutation/createOrganization.ts @@ -32,12 +32,12 @@ export const createOrganization: NonNullable = async ( _, From 990abf3b748de47721b8344f3707d31de25bf42b Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Sun, 20 Oct 2024 16:56:23 +0300 Subject: [PATCH 12/17] fix --- .../src/modules/audit-logs/module.graphql.ts | 1 + .../audit-logs/providers/audit-logs-types.ts | 1 + .../resolvers/ProjectCreatedAuditLog.ts | 1 + .../resolvers/Mutation/createProject.ts | 21 ++++++++++--------- .../resolvers/Mutation/updateProjectSlug.ts | 21 +++++++++++++++++++ 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/module.graphql.ts b/packages/services/api/src/modules/audit-logs/module.graphql.ts index 9a278f2159..68b87621bd 100644 --- a/packages/services/api/src/modules/audit-logs/module.graphql.ts +++ b/packages/services/api/src/modules/audit-logs/module.graphql.ts @@ -106,6 +106,7 @@ export const typeDefs = gql` eventTime: DateTime! record: AuditLogIdRecord! projectId: String! + projectType: String! projectName: String! } diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts index 5b9551d9e7..4f733b8cad 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-types.ts @@ -190,6 +190,7 @@ const subscriptionCanceledAuditLogSchema = z.object({ const projectCreatedAuditLogSchema = z.object({ projectId: z.string(), projectName: z.string(), + projectType: z.string(), }); const projectSettingsUpdatedAuditLogSchema = z.object({ diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts index e3615e6991..ad5b5bc544 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts @@ -15,5 +15,6 @@ export const ProjectCreatedAuditLog: ProjectCreatedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), projectId: e => e.metadata.projectCreatedAuditLogSchema.projectId, projectName: e => e.metadata.projectCreatedAuditLogSchema.projectName, + projectType: e => e.metadata.projectCreatedAuditLogSchema.projectType, record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts index 71578faf34..5078678200 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts @@ -78,22 +78,13 @@ export const createProject: NonNullable = as }), ]); - const logger = injector.get(Logger); - const targets: Target[] = []; - for (const result of targetResults) { - if (result.ok) { - targets.push(result.target); - } else { - logger.error('Failed to create a target: ' + result.message); - } - } - const currentUser = await injector.get(AuthManager).getCurrentUser(); injector.get(AuditLogManager).createLogAuditEvent( { eventType: 'PROJECT_CREATED', projectCreatedAuditLogSchema: { projectId: result.project.id, + projectType: result.project.type, projectName: result.project.name, }, }, @@ -105,6 +96,16 @@ export const createProject: NonNullable = as }, ); + const logger = injector.get(Logger); + const targets: Target[] = []; + for (const result of targetResults) { + if (result.ok) { + targets.push(result.target); + } else { + logger.error('Failed to create a target: ' + result.message); + } + } + return { ok: { createdProject: result.project, diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectSlug.ts b/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectSlug.ts index 4624e38207..6f3ef451b5 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectSlug.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/updateProjectSlug.ts @@ -1,4 +1,6 @@ import { z } from 'zod'; +import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; +import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { ProjectManager } from '../../providers/project-manager'; import { ProjectSlugModel } from '../../validation'; @@ -37,6 +39,25 @@ export const updateProjectSlug: NonNullable Date: Sun, 20 Oct 2024 19:02:05 +0300 Subject: [PATCH 13/17] fix rebase and conflicts --- .../api/src/modules/schema/resolvers.ts | 2298 ----------------- 1 file changed, 2298 deletions(-) delete mode 100644 packages/services/api/src/modules/schema/resolvers.ts diff --git a/packages/services/api/src/modules/schema/resolvers.ts b/packages/services/api/src/modules/schema/resolvers.ts deleted file mode 100644 index 96862315bf..0000000000 --- a/packages/services/api/src/modules/schema/resolvers.ts +++ /dev/null @@ -1,2298 +0,0 @@ -import { createHash } from 'node:crypto'; -import type { - GraphQLEnumTypeMapper, - GraphQLInputObjectTypeMapper, - GraphQLInterfaceTypeMapper, - GraphQLNamedTypeMapper, - GraphQLObjectTypeMapper, - GraphQLScalarTypeMapper, - GraphQLUnionTypeMapper, - SchemaCoordinateUsageForUnusedExplorer, - SchemaCoordinateUsageMapper, - WithGraphQLParentInfo, - WithSchemaCoordinatesUsage, -} from './module.graphql.mappers'; -import stringify from 'fast-json-stable-stringify'; -import { - ConstDirectiveNode, - DEFAULT_DEPRECATION_REASON, - DocumentNode, - GraphQLEnumType, - GraphQLInputObjectType, - GraphQLInterfaceType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLUnionType, - isEnumType, - isInputObjectType, - isInterfaceType, - isObjectType, - isScalarType, - isUnionType, - Kind, - print, -} from 'graphql'; -import { parseResolveInfo } from 'graphql-parse-resolve-info'; -import { z } from 'zod'; -import { CriticalityLevel } from '@graphql-inspector/core'; -import type * as Types from '../../__generated__/types'; -import { type DateRange } from '../../shared/entities'; -import { createPeriod, parseDateRangeInput, PromiseOrValue } from '../../shared/helpers'; -import { buildASTSchema, createConnection, createDummyConnection } from '../../shared/schema'; -import { AuditLogManager } from '../audit-logs/providers/audit-logs-manager'; -import { AuthManager } from '../auth/providers/auth-manager'; -import { OperationsManager } from '../operations/providers/operations-manager'; -import { OrganizationManager } from '../organization/providers/organization-manager'; -import { ProjectManager } from '../project/providers/project-manager'; -import { IdTranslator } from '../shared/providers/id-translator'; -import { TargetSelector } from '../shared/providers/storage'; -import { TargetManager } from '../target/providers/target-manager'; -import type { SchemaModule } from './__generated__/types'; -import { onlyDeprecatedDocumentNode } from './lib/deprecated-graphql'; -import { extractSuperGraphInformation, SuperGraphInformation } from './lib/federation-super-graph'; -import { stripUsedSchemaCoordinatesFromDocumentNode } from './lib/unused-graphql'; -import { BreakingSchemaChangeUsageHelper } from './providers/breaking-schema-changes-helper'; -import { ContractsManager } from './providers/contracts-manager'; -import { SchemaCheckManager } from './providers/schema-check-manager'; -import { SchemaManager } from './providers/schema-manager'; -import { SchemaPublisher } from './providers/schema-publisher'; -import { SchemaVersionHelper } from './providers/schema-version-helper'; -import { toGraphQLSchemaCheck, toGraphQLSchemaCheckCurry } from './to-graphql-schema-check'; - -const MaybeModel = (value: T) => z.union([z.null(), z.undefined(), value]); -const GraphQLSchemaStringModel = z.string().max(5_000_000).min(0); - -function isSchemaCoordinateUsageForUnusedExplorer( - value: unknown, -): value is SchemaCoordinateUsageForUnusedExplorer { - return 'isUsed' in (value as any); -} - -function usage( - source: - | WithSchemaCoordinatesUsage<{ - entity: { - name: string; - }; - }> - | WithGraphQLParentInfo< - WithSchemaCoordinatesUsage<{ - entity: { - name: string; - }; - }> - >, - _: unknown, -): Promise | SchemaCoordinateUsageMapper { - const coordinate = - 'parent' in source ? `${source.parent.coordinate}.${source.entity.name}` : source.entity.name; - - const usage = source.usage(); - - if (isSchemaCoordinateUsageForUnusedExplorer(usage)) { - if (usage.usedCoordinates.has(coordinate)) { - return { - // TODO: This is a hack to mark the field as used but without passing exact number as we don't need the exact number in "Unused schema view". - total: 1, - isUsed: true, - usedByClients: () => [], - period: usage.period, - organization: usage.organization, - project: usage.project, - target: usage.target, - coordinate: coordinate, - }; - } - - return { - total: 0, - isUsed: false, - usedByClients: () => [], - }; - } - - return Promise.resolve(usage).then(usage => { - const coordinateUsage = usage[coordinate]; - - return coordinateUsage && coordinateUsage.total > 0 - ? { - total: coordinateUsage.total, - isUsed: true, - usedByClients: coordinateUsage.usedByClients, - period: coordinateUsage.period, - organization: coordinateUsage.organization, - project: coordinateUsage.project, - target: coordinateUsage.target, - coordinate: coordinate, - } - : { - total: 0, - isUsed: false, - usedByClients: () => [], - }; - }); -} - -function __isTypeOf< - T extends GraphQLNamedTypeMapper, - K extends GraphQLNamedTypeMapper['entity']['kind'], ->(kind: K): (type: T) => boolean { - return ({ entity }: { entity: GraphQLNamedTypeMapper['entity'] }) => entity.kind === kind; -} - -export const resolvers: SchemaModule.Resolvers = { - Mutation: { - async schemaCheck(_, { input }, { injector }) { - const [organization, project, target] = await Promise.all([ - injector.get(OrganizationManager).getOrganizationIdByToken(), - injector.get(ProjectManager).getProjectIdByToken(), - injector.get(TargetManager).getTargetIdByToken(), - ]); - - const result = await injector.get(SchemaPublisher).check({ - ...input, - service: input.service?.toLowerCase(), - organization, - project, - target, - }); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - const [organizationId, projectId, targetId] = await Promise.all([ - injector.get(IdTranslator).translateOrganizationId({ - organization, - }), - injector.get(IdTranslator).translateProjectId({ - organization, - project, - }), - injector.get(IdTranslator).translateTargetId({ - organization, - project, - target, - }), - ]); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'SCHEMA_CHECKED', - schemaCheckedAuditLogSchema: { - projectId: projectId, - targetId: targetId, - checkId: result.__typename === 'SchemaCheckSuccess' ? result.schemaCheck.id : null, - }, - }, - { - organizationId: organizationId, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - if ('changes' in result && result.changes) { - return { - ...result, - changes: result.changes, - errors: - result.errors?.map(error => ({ - ...error, - path: 'path' in error ? error.path?.split('.') : null, - })) ?? [], - }; - } - - return result; - }, - async approveFailedSchemaCheck(_, { input }, { injector }) { - const [organizationId, projectId, targetId] = await Promise.all([ - injector.get(IdTranslator).translateOrganizationId(input), - injector.get(IdTranslator).translateProjectId(input), - injector.get(IdTranslator).translateTargetId(input), - ]); - - const result = await injector.get(SchemaManager).approveFailedSchemaCheck({ - organizationId, - projectId, - targetId, - schemaCheckId: input.schemaCheckId, - comment: input.comment, - }); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'SCHEMA_CHECKED', - schemaCheckedAuditLogSchema: { - projectId: projectId, - targetId: targetId, - checkId: input.schemaCheckId, - }, - }, - { - organizationId: organizationId, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - if (result.type === 'error') { - return { - error: { - message: result.reason, - }, - }; - } - - return { - ok: { - schemaCheck: toGraphQLSchemaCheck( - { - organizationId, - projectId, - }, - result.schemaCheck, - ), - }, - }; - }, - async schemaPublish(_, { input }, { injector, request }, info) { - const [organization, project, target] = await Promise.all([ - injector.get(OrganizationManager).getOrganizationIdByToken(), - injector.get(ProjectManager).getProjectIdByToken(), - injector.get(TargetManager).getTargetIdByToken(), - ]); - - // We only want to resolve to SchemaPublishMissingUrlError if it is selected by the operation. - // NOTE: This should be removed once the usage of cli versions that don't request on 'SchemaPublishMissingUrlError' is becomes pretty low. - const parsedResolveInfoFragment = parseResolveInfo(info); - const isSchemaPublishMissingUrlErrorSelected = - !!parsedResolveInfoFragment?.fieldsByTypeName['SchemaPublishMissingUrlError']; - - const result = await injector.get(SchemaPublisher).publish( - { - ...input, - service: input.service?.toLowerCase(), - organization, - project, - target, - isSchemaPublishMissingUrlErrorSelected, - }, - request.signal, - ); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - const [organizationId, projectId, targetId] = await Promise.all([ - injector.get(IdTranslator).translateOrganizationId({ - organization, - }), - injector.get(IdTranslator).translateProjectId({ - organization, - project, - }), - injector.get(IdTranslator).translateTargetId({ - organization, - project, - target, - }), - ]); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'SCHEMA_PUBLISH', - schemaPublishAuditLogSchema: { - projectId: projectId, - targetId: targetId, - isSchemaPublishMissingUrlErrorSelected, - serviceName: input?.service?.toLowerCase(), - }, - }, - { - organizationId: organizationId, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - if ('changes' in result) { - return { - ...result, - changes: result.changes, - }; - } - - return result; - }, - async schemaDelete(_, { input }, { injector, request }) { - const [organization, project, target] = await Promise.all([ - injector.get(OrganizationManager).getOrganizationIdByToken(), - injector.get(ProjectManager).getProjectIdByToken(), - injector.get(TargetManager).getTargetFromToken(), - ]); - - const token = injector.get(AuthManager).ensureApiToken(); - - const checksum = createHash('md5') - .update( - stringify({ - ...input, - serviceName: input.serviceName.toLowerCase(), - }), - ) - .update(token) - .digest('base64'); - - const result = await injector.get(SchemaPublisher).delete( - { - dryRun: input.dryRun, - serviceName: input.serviceName.toLowerCase(), - organization, - project, - target, - checksum, - }, - request.signal, - ); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - const [organizationId, projectId] = await Promise.all([ - injector.get(IdTranslator).translateOrganizationId({ - organization, - }), - injector.get(IdTranslator).translateProjectId({ - organization, - project, - }), - ]); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'SCHEMA_DELETED', - schemaDeletedAuditLogSchema: { - projectId: projectId, - serviceName: input.serviceName.toLowerCase(), - targetId: target.id, - }, - }, - { - organizationId: organizationId, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - return { - ...result, - changes: result.changes, - errors: result.errors?.map(error => ({ - ...error, - path: 'path' in error ? error.path?.split('.') : null, - })), - }; - }, - async schemaCompose(_, { input }, { injector }) { - const [organization, project, target] = await Promise.all([ - injector.get(OrganizationManager).getOrganizationIdByToken(), - injector.get(ProjectManager).getProjectIdByToken(), - injector.get(TargetManager).getTargetIdByToken(), - ]); - - const result = await injector.get(SchemaManager).compose({ - onlyComposable: input.useLatestComposableVersion === true, - services: input.services, - organization, - project, - target, - }); - - if (result.kind === 'error') { - return { - __typename: 'SchemaComposeError', - message: result.message, - }; - } - - return { - __typename: 'SchemaComposeSuccess', - valid: 'supergraphSDL' in result && result.supergraphSDL !== null, - compositionResult: { - errors: result.errors, - supergraphSdl: result.supergraphSDL, - }, - }; - }, - async updateSchemaVersionStatus(_, { input }, { injector }) { - const translator = injector.get(IdTranslator); - const [organization, project, target] = await Promise.all([ - translator.translateOrganizationId(input), - translator.translateProjectId(input), - translator.translateTargetId(input), - ]); - - return injector.get(SchemaPublisher).updateVersionStatus({ - version: input.version, - valid: input.valid, - organization, - project, - target, - }); - }, - async updateBaseSchema(_, { input }, { injector }) { - const UpdateBaseSchemaModel = z.object({ - newBase: MaybeModel(GraphQLSchemaStringModel), - }); - - const result = UpdateBaseSchemaModel.safeParse(input); - - if (!result.success) { - return { - error: { - message: - result.error.formErrors.fieldErrors?.newBase?.[0] ?? 'Please check your input.', - }, - }; - } - - const schemaManager = injector.get(SchemaManager); - const translator = injector.get(IdTranslator); - const [organization, project, target] = await Promise.all([ - translator.translateOrganizationId(input), - translator.translateProjectId(input), - translator.translateTargetId(input), - ]); - - const selector = { organization, project, target }; - await schemaManager.updateBaseSchema(selector, input.newBase ? input.newBase : null); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'TARGET_SETTINGS_UPDATED', - targetSettingsUpdatedAuditLogSchema: { - targetId: target, - projectId: project, - updatedFields: JSON.stringify({ - newBase: input.newBase, - }), - }, - }, - { - organizationId: organization, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - return { - ok: { - updatedTarget: await injector.get(TargetManager).getTarget({ - organization, - target, - project, - }), - }, - }; - }, - async disableExternalSchemaComposition(_, { input }, { injector }) { - const translator = injector.get(IdTranslator); - const [organization, project] = await Promise.all([ - translator.translateOrganizationId(input), - translator.translateProjectId(input), - ]); - - const result = injector.get(SchemaManager).disableExternalSchemaComposition({ - project, - organization, - }); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'ORGANIZATION_SETTINGS_UPDATED', - organizationSettingsUpdatedAuditLogSchema: { - updatedFields: JSON.stringify({ - disableExternalSchemaComposition: true, - }), - }, - }, - { - organizationId: organization, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - return result; - }, - async enableExternalSchemaComposition(_, { input }, { injector }) { - const translator = injector.get(IdTranslator); - const [organization, project] = await Promise.all([ - translator.translateOrganizationId(input), - translator.translateProjectId(input), - ]); - - const result = injector.get(SchemaManager).enableExternalSchemaComposition({ - project, - organization, - endpoint: input.endpoint, - secret: input.secret, - }); - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'ORGANIZATION_SETTINGS_UPDATED', - organizationSettingsUpdatedAuditLogSchema: { - updatedFields: JSON.stringify({ - disableExternalSchemaComposition: false, - }), - }, - }, - { - organizationId: organization, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - return result; - }, - async updateNativeFederation(_, { input }, { injector }) { - const translator = injector.get(IdTranslator); - const [organization, project] = await Promise.all([ - translator.translateOrganizationId(input), - translator.translateProjectId(input), - ]); - - const result = { - ok: await injector.get(SchemaManager).updateNativeSchemaComposition({ - project, - organization, - enabled: input.enabled, - }), - }; - - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'ORGANIZATION_SETTINGS_UPDATED', - organizationSettingsUpdatedAuditLogSchema: { - updatedFields: JSON.stringify({ - enableNativeFederation: input.enabled, - }), - }, - }, - { - organizationId: organization, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - - return result; - }, - async updateProjectRegistryModel(_, { input }, { injector }) { - const translator = injector.get(IdTranslator); - const [organization, project] = await Promise.all([ - translator.translateOrganizationId(input), - translator.translateProjectId(input), - ]); - - return { - ok: await injector.get(SchemaManager).updateRegistryModel({ - project, - organization, - model: input.model, - }), - }; - }, - async createContract(_, args, context) { - const result = await context.injector.get(ContractsManager).createContract({ - contract: { - targetId: args.input.targetId, - contractName: args.input.contractName, - excludeTags: (args.input.excludeTags as Array | null) ?? null, - includeTags: (args.input.includeTags as Array | null) ?? null, - removeUnreachableTypesFromPublicApiSchema: - args.input.removeUnreachableTypesFromPublicApiSchema, - }, - }); - - if (result.type === 'success') { - return { - ok: { - createdContract: result.contract, - }, - }; - } - - return { - error: { - message: 'Something went wrong.', - details: result.errors, - }, - }; - }, - async disableContract(_, args, context) { - const result = await context.injector.get(ContractsManager).disableContract({ - contractId: args.input.contractId, - }); - - if (result.type === 'success') { - return { - ok: { - disabledContract: result.contract, - }, - }; - } - - return { - error: { - message: result.message, - }, - }; - }, - }, - Query: { - async latestVersion(_, __, { injector }) { - const target = await injector.get(TargetManager).getTargetFromToken(); - - return injector.get(SchemaManager).getMaybeLatestVersion({ - organization: target.orgId, - project: target.projectId, - target: target.id, - }); - }, - async latestValidVersion(_, __, { injector }) { - const target = await injector.get(TargetManager).getTargetFromToken(); - - return injector.get(SchemaManager).getMaybeLatestValidVersion({ - organization: target.orgId, - project: target.projectId, - target: target.id, - }); - }, - async testExternalSchemaComposition(_, { selector }, { injector }) { - const translator = injector.get(IdTranslator); - const [organizationId, projectId] = await Promise.all([ - translator.translateOrganizationId(selector), - translator.translateProjectId(selector), - ]); - - const schemaManager = injector.get(SchemaManager); - - const result = await schemaManager.testExternalSchemaComposition({ - organizationId, - projectId, - }); - - if (result.kind === 'success') { - return { - ok: result.project, - }; - } - - return { - error: { - message: result.error, - }, - }; - }, - async schemaVersionForActionId(_, { actionId }, { injector }) { - return injector.get(SchemaManager).getSchemaVersionByActionId({ - actionId, - }); - }, - }, - Target: { - async schemaVersions(target, args, { injector }) { - return injector.get(SchemaManager).getPaginatedSchemaVersionsForTargetId({ - targetId: target.id, - organizationId: target.orgId, - projectId: target.projectId, - cursor: args.after ?? null, - first: args.first ?? null, - }); - }, - async schemaVersion(target, args, { injector }) { - const schemaVersion = await injector.get(SchemaManager).getSchemaVersion({ - organization: target.orgId, - project: target.projectId, - target: target.id, - version: args.id, - }); - - if (schemaVersion === null) { - return null; - } - - return { - ...schemaVersion, - organization: target.orgId, - project: target.projectId, - target: target.id, - }; - }, - latestSchemaVersion(target, _, { injector }) { - return injector.get(SchemaManager).getMaybeLatestVersion({ - target: target.id, - project: target.projectId, - organization: target.orgId, - }); - }, - async latestValidSchemaVersion(target, __, { injector }) { - return injector.get(SchemaManager).getMaybeLatestValidVersion({ - organization: target.orgId, - project: target.projectId, - target: target.id, - }); - }, - baseSchema(target, _, { injector }) { - return injector.get(SchemaManager).getBaseSchema({ - target: target.id, - project: target.projectId, - organization: target.orgId, - }); - }, - hasSchema(target, _, { injector }) { - return injector.get(SchemaManager).hasSchema({ - target: target.id, - project: target.projectId, - organization: target.orgId, - }); - }, - async schemaCheck(target, args, { injector }) { - const schemaCheck = await injector.get(SchemaManager).findSchemaCheck({ - targetId: target.id, - projectId: target.projectId, - organizationId: target.orgId, - schemaCheckId: args.id, - }); - - if (schemaCheck == null) { - return null; - } - - return toGraphQLSchemaCheck( - { - organizationId: target.orgId, - projectId: target.projectId, - }, - schemaCheck, - ); - }, - async schemaChecks(target, args, { injector }) { - const result = await injector.get(SchemaManager).getPaginatedSchemaChecksForTarget({ - targetId: target.id, - projectId: target.projectId, - organizationId: target.orgId, - first: args.first ?? null, - cursor: args.after ?? null, - filters: args.filters ?? null, - transformNode: toGraphQLSchemaCheckCurry({ - organizationId: target.orgId, - projectId: target.projectId, - }), - }); - - return { - edges: result.items, - pageInfo: result.pageInfo, - }; - }, - schemaVersionsCount(target, { period }, { injector }) { - return injector.get(SchemaManager).countSchemaVersionsOfTarget({ - organization: target.orgId, - project: target.projectId, - target: target.id, - period: period ? parseDateRangeInput(period) : null, - }); - }, - async contracts(target, args, { injector }) { - return await injector.get(ContractsManager).getPaginatedContractsForTarget({ - target, - cursor: args.after ?? null, - first: args.first ?? null, - }); - }, - async activeContracts(target, args, { injector }) { - return await injector.get(ContractsManager).getPaginatedActiveContractsForTarget({ - target, - cursor: args.after ?? null, - first: args.first ?? null, - }); - }, - async hasCollectedSubscriptionOperations(target, _, { injector }) { - return await injector.get(OperationsManager).hasCollectedSubscriptionOperations({ - target: target.id, - project: target.projectId, - organization: target.orgId, - }); - }, - }, - SchemaVersion: { - isComposable(version) { - return version.schemaCompositionErrors === null; - }, - hasSchemaChanges(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getHasSchemaChanges(version); - }, - async log(version, _, { injector }) { - const log = await injector.get(SchemaManager).getSchemaLog({ - commit: version.actionId, - organization: version.organization, - project: version.project, - target: version.target, - }); - - if (log.kind === 'single') { - return { - __typename: 'PushedSchemaLog', - author: log.author, - commit: log.commit, - date: log.date as any, - id: log.id, - service: null, - serviceSdl: null, - }; - } - - if (log.action === 'DELETE') { - return { - __typename: 'DeletedSchemaLog', - author: 'system', - commit: 'system', - date: log.date as any, - id: log.id, - deletedService: log.service_name, - previousServiceSdl: await injector - .get(SchemaVersionHelper) - .getServiceSdlForPreviousVersionService(version, log.service_name), - }; - } - - return { - __typename: 'PushedSchemaLog', - author: log.author, - commit: log.commit, - date: log.date as any, - id: log.id, - service: log.service_name, - serviceSdl: log.sdl, - previousServiceSdl: await injector - .get(SchemaVersionHelper) - .getServiceSdlForPreviousVersionService(version, log.service_name), - }; - }, - schemas(version, _, { injector }) { - return injector.get(SchemaManager).getMaybeSchemasOfVersion({ - version: version.id, - organization: version.organization, - project: version.project, - target: version.target, - }); - }, - async schemaCompositionErrors(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getSchemaCompositionErrors(version); - }, - async breakingSchemaChanges(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getBreakingSchemaChanges(version); - }, - async safeSchemaChanges(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getSafeSchemaChanges(version); - }, - async supergraph(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getSupergraphSdl(version); - }, - async sdl(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getCompositeSchemaSdl(version); - }, - async baseSchema(version) { - return version.baseSchema ?? null; - }, - async explorer(version, { usage }, { injector }) { - const [schemaAst, supergraphAst] = await Promise.all([ - injector.get(SchemaVersionHelper).getCompositeSchemaAst(version), - injector.get(SchemaVersionHelper).getSupergraphAst(version), - ]); - - if (!schemaAst) { - return null; - } - - const supergraph = supergraphAst ? extractSuperGraphInformation(supergraphAst) : null; - - return { - schema: buildASTSchema(schemaAst), - usage: { - period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), - organization: version.organization, - project: version.project, - target: version.target, - }, - supergraph, - }; - }, - async unusedSchema(version, { usage }, { injector }) { - const [schemaAst, supergraphAst] = await Promise.all([ - injector.get(SchemaVersionHelper).getCompositeSchemaAst(version), - injector.get(SchemaVersionHelper).getSupergraphAst(version), - ]); - - if (!schemaAst) { - return null; - } - - const usedCoordinates = await injector.get(OperationsManager).getReportedSchemaCoordinates({ - targetId: version.target, - projectId: version.project, - organizationId: version.organization, - period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), - }); - - const supergraph = supergraphAst ? extractSuperGraphInformation(supergraphAst) : null; - - return { - sdl: stripUsedSchemaCoordinatesFromDocumentNode(schemaAst, usedCoordinates), - usage: { - period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), - organization: version.organization, - project: version.project, - target: version.target, - usedCoordinates, - }, - supergraph, - }; - }, - async deprecatedSchema(version, { usage }, { injector }) { - const [schemaAst, supergraphAst] = await Promise.all([ - injector.get(SchemaVersionHelper).getCompositeSchemaAst(version), - injector.get(SchemaVersionHelper).getSupergraphAst(version), - ]); - - if (!schemaAst) { - return null; - } - - const supergraph = supergraphAst ? extractSuperGraphInformation(supergraphAst) : null; - - return { - sdl: onlyDeprecatedDocumentNode(schemaAst), - usage: { - period: usage?.period ? parseDateRangeInput(usage.period) : createPeriod('30d'), - organization: version.organization, - project: version.project, - target: version.target, - }, - supergraph, - }; - }, - date: version => version.createdAt, - githubMetadata(version, _, { injector }) { - return injector.get(SchemaManager).getGitHubMetadata(version); - }, - valid(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getIsValid(version); - }, - previousDiffableSchemaVersion(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getPreviousDiffableSchemaVersion(version); - }, - isFirstComposableVersion(version, _, { injector }) { - return injector.get(SchemaVersionHelper).getIsFirstComposableVersion(version); - }, - contractVersions(version, _, { injector }) { - return injector.get(ContractsManager).getContractVersionsForSchemaVersion(version); - }, - }, - SingleSchema: { - __isTypeOf(obj) { - return obj.kind === 'single'; - }, - source(schema) { - return schema.sdl; - }, - }, - CompositeSchema: { - __isTypeOf(obj) { - return obj.kind === 'composite' && obj.action === 'PUSH'; - }, - service(schema) { - return schema.service_name; - }, - source(schema) { - return schema.sdl; - }, - url(schema) { - return schema.service_url; - }, - }, - SchemaConnection: createConnection(), - SchemaChangeConnection: createConnection(), - SchemaChange: { - message: (change, args) => { - return args.withSafeBasedOnUsageNote && change.isSafeBasedOnUsage === true - ? `${change.message} (non-breaking based on usage)` - : change.message; - }, - path: change => change.path?.split('.') ?? null, - criticality: change => criticalityMap[change.criticality], - criticalityReason: change => change.reason, - approval: change => change.approvalMetadata, - isSafeBasedOnUsage: change => change.isSafeBasedOnUsage, - usageStatistics: (change, _, { injector }) => - injector.get(BreakingSchemaChangeUsageHelper).getUsageDataForBreakingSchemaChange(change), - }, - SchemaChangeApproval: { - approvedBy: (approval, _, { injector }) => - injector.get(SchemaManager).getUserForSchemaChangeById({ userId: approval.userId }), - approvedAt: approval => approval.date, - }, - SchemaErrorConnection: createConnection(), - SchemaWarningConnection: createConnection(), - SchemaCheckSuccess: { - __isTypeOf(obj) { - return obj.valid; - }, - }, - SchemaCheckError: { - __isTypeOf(obj) { - return !obj.valid; - }, - }, - Project: { - externalSchemaComposition(project) { - if (project.externalComposition.enabled && project.externalComposition.endpoint) { - return { - endpoint: project.externalComposition.endpoint, - }; - } - - return null; - }, - registryModel(project) { - return project.legacyRegistryModel ? 'LEGACY' : 'MODERN'; - }, - schemaVersionsCount(project, { period }, { injector }) { - return injector.get(SchemaManager).countSchemaVersionsOfProject({ - organization: project.orgId, - project: project.id, - period: period ? parseDateRangeInput(period) : null, - }); - }, - isNativeFederationEnabled(project) { - return project.nativeFederation === true; - }, - nativeFederationCompatibility(project, _, { injector }) { - return injector.get(SchemaManager).getNativeFederationCompatibilityStatus(project); - }, - }, - SchemaCoordinateUsage: { - topOperations(source, { limit }, { injector }) { - if (!source.isUsed) { - return []; - } - - return injector - .get(OperationsManager) - .getTopOperationForCoordinate({ - organizationId: source.organization, - projectId: source.project, - targetId: source.target, - coordinate: source.coordinate, - period: source.period, - limit, - }) - .then(operations => - operations.map(op => ({ - name: op.operationName, - hash: op.operationHash, - count: op.count, - })), - ); - }, - // Why? GraphQL-JIT goes crazy without this (Expected Iterable, but did not find one for field SchemaCoordinateUsage.usedByClients). - // That's why we switched from a getter to a function. - usedByClients(parent) { - return parent.usedByClients(); - }, - }, - SchemaExplorer: { - async type(source, { name }, { injector }) { - const entity = source.schema.getType(name); - const operationsManager = injector.get(OperationsManager); - - if (!entity) { - return null; - } - - const { supergraph } = source; - const usage = () => - injector - .get(OperationsManager) - .countCoordinatesOfType({ - typename: entity.name, - organization: source.usage.organization, - project: source.usage.project, - target: source.usage.target, - period: source.usage.period, - }) - .then(usage => - withUsedByClients(usage, { - selector: source.usage, - period: source.usage.period, - operationsManager, - typename: entity.name, - }), - ); - - if (isObjectType(entity)) { - return { - entity: transformGraphQLObjectType(entity), - usage, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? - null, - } - : null, - } satisfies GraphQLObjectTypeMapper; - } - if (isInterfaceType(entity)) { - return { - entity: transformGraphQLInterfaceType(entity), - usage, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? - null, - } - : null, - } satisfies GraphQLInterfaceTypeMapper; - } - if (isEnumType(entity)) { - return { - entity: transformGraphQLEnumType(entity), - usage, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getEnumValueOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? - null, - } - : null, - } satisfies GraphQLEnumTypeMapper; - } - if (isUnionType(entity)) { - return { - entity: transformGraphQLUnionType(entity), - usage, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getUnionMemberOwnedByServices: (memberName: string) => - supergraph.schemaCoordinateServicesMappings.get(memberName) ?? null, - } - : null, - } satisfies GraphQLUnionTypeMapper; - } - if (isInputObjectType(entity)) { - return { - entity: transformGraphQLInputObjectType(entity), - usage, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getInputFieldOwnedByServices: (inputFieldName: string) => - supergraph.schemaCoordinateServicesMappings.get( - `${entity.name}.${inputFieldName}`, - ) ?? null, - } - : null, - } satisfies GraphQLInputObjectTypeMapper; - } - if (isScalarType(entity)) { - return { - entity: transformGraphQLScalarType(entity), - usage, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - } - : null, - } satisfies GraphQLScalarTypeMapper; - } - - throw new Error('Illegal state: unknown type kind'); - }, - async types({ schema, usage, supergraph }, _, { injector }) { - const types: Array< - | GraphQLObjectTypeMapper - | GraphQLInterfaceTypeMapper - | GraphQLUnionTypeMapper - | GraphQLEnumTypeMapper - | GraphQLInputObjectTypeMapper - | GraphQLScalarTypeMapper - > = []; - const typeMap = schema.getTypeMap(); - const operationsManager = injector.get(OperationsManager); - - async function getStats(typename: string) { - const stats = await operationsManager.countCoordinatesOfTarget({ - target: usage.target, - organization: usage.organization, - project: usage.project, - period: usage.period, - }); - - return withUsedByClients(stats, { - selector: usage, - period: usage.period, - operationsManager, - typename, - }); - } - - for (const typename in typeMap) { - if (typename.startsWith('__')) { - continue; - } - - const entity = typeMap[typename]; - - if (isObjectType(entity)) { - types.push({ - entity: transformGraphQLObjectType(entity), - usage() { - return getStats(entity.name); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${typename}.${fieldName}`) ?? - null, - } - : null, - }); - } else if (isInterfaceType(entity)) { - types.push({ - entity: transformGraphQLInterfaceType(entity), - usage() { - return getStats(entity.name); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${typename}.${fieldName}`) ?? - null, - } - : null, - }); - } else if (isEnumType(entity)) { - types.push({ - entity: transformGraphQLEnumType(entity), - usage() { - return getStats(entity.name); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, - getEnumValueOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${typename}.${fieldName}`) ?? - null, - } - : null, - }); - } else if (isUnionType(entity)) { - types.push({ - entity: transformGraphQLUnionType(entity), - usage() { - return getStats(entity.name); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, - getUnionMemberOwnedByServices: (memberName: string) => - supergraph.schemaCoordinateServicesMappings.get(memberName) ?? null, - } - : null, - }); - } else if (isInputObjectType(entity)) { - types.push({ - entity: transformGraphQLInputObjectType(entity), - usage() { - return getStats(entity.name); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typename) ?? null, - getInputFieldOwnedByServices: (inputFieldName: string) => - supergraph.schemaCoordinateServicesMappings.get( - `${typename}.${inputFieldName}`, - ) ?? null, - } - : null, - }); - } else if (isScalarType(entity)) { - types.push({ - entity: transformGraphQLScalarType(entity), - usage() { - return getStats(entity.name); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - } - : null, - }); - } - } - - types.sort((a, b) => a.entity.name.localeCompare(b.entity.name)); - - return types; - }, - async query({ schema, supergraph, usage }, _, { injector }) { - const entity = schema.getQueryType(); - - if (!entity) { - return null; - } - - const operationsManager = injector.get(OperationsManager); - - return { - entity: transformGraphQLObjectType(entity), - usage() { - return operationsManager - .countCoordinatesOfType({ - typename: entity.name, - organization: usage.organization, - project: usage.project, - target: usage.target, - period: usage.period, - }) - .then(stats => - withUsedByClients(stats, { - selector: usage, - period: usage.period, - operationsManager, - typename: entity.name, - }), - ); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? - null, - } - : null, - }; - }, - async mutation({ schema, supergraph, usage }, _, { injector }) { - const entity = schema.getMutationType(); - - if (!entity) { - return null; - } - - const operationsManager = injector.get(OperationsManager); - - return { - entity: transformGraphQLObjectType(entity), - usage() { - return operationsManager - .countCoordinatesOfType({ - typename: entity.name, - organization: usage.organization, - project: usage.project, - target: usage.target, - period: usage.period, - }) - .then(stats => - withUsedByClients(stats, { - selector: usage, - period: usage.period, - operationsManager, - typename: entity.name, - }), - ); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? - null, - } - : null, - }; - }, - - async subscription({ schema, supergraph, usage }, _, { injector }) { - const entity = schema.getSubscriptionType(); - - if (!entity) { - return null; - } - - const operationsManager = injector.get(OperationsManager); - - return { - entity: transformGraphQLObjectType(entity), - usage() { - return operationsManager - .countCoordinatesOfType({ - typename: entity.name, - organization: usage.organization, - project: usage.project, - target: usage.target, - period: usage.period, - }) - .then(stats => - withUsedByClients(stats, { - selector: usage, - period: usage.period, - operationsManager, - typename: entity.name, - }), - ); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(entity.name) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get(`${entity.name}.${fieldName}`) ?? - null, - } - : null, - }; - }, - }, - DeprecatedSchemaExplorer: { - types({ sdl, supergraph, usage }, _, { injector }) { - const operationsManager = injector.get(OperationsManager); - - async function getStats(typename: string) { - const stats = await operationsManager.countCoordinatesOfTarget({ - target: usage.target, - organization: usage.organization, - project: usage.project, - period: usage.period, - }); - - return withUsedByClients(stats, { - selector: usage, - period: usage.period, - operationsManager, - typename, - }); - } - - return buildGraphQLTypesFromSDL(sdl, getStats, supergraph).sort((a, b) => - a.entity.name.localeCompare(b.entity.name), - ); - }, - }, - UnusedSchemaExplorer: { - types({ sdl, supergraph, usage }) { - const unused = () => - ({ - isUsed: false, - usedCoordinates: usage.usedCoordinates, - period: usage.period, - organization: usage.organization, - project: usage.project, - target: usage.target, - }) as const; - - return buildGraphQLTypesFromSDL(sdl, unused, supergraph).sort((a, b) => - a.entity.name.localeCompare(b.entity.name), - ); - }, - }, - GraphQLObjectType: { - __isTypeOf: __isTypeOf(Kind.OBJECT_TYPE_DEFINITION), - name: t => t.entity.name, - description: t => t.entity.description ?? null, - fields: t => - t.entity.fields.map(f => ({ - entity: f, - parent: { - coordinate: t.entity.name, - }, - usage: t.usage, - supergraph: t.supergraph - ? { ownedByServiceNames: t.supergraph.getFieldOwnedByServices(f.name) } - : null, - })), - interfaces: t => t.entity.interfaces, - usage, - supergraphMetadata: t => - t.supergraph - ? { - ownedByServiceNames: t.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLInterfaceType: { - __isTypeOf: __isTypeOf(Kind.INTERFACE_TYPE_DEFINITION), - name: t => t.entity.name, - description: t => t.entity.description ?? null, - fields: t => - t.entity.fields.map(f => ({ - entity: f, - parent: { - coordinate: t.entity.name, - }, - usage: t.usage, - supergraph: t.supergraph - ? { ownedByServiceNames: t.supergraph.getFieldOwnedByServices(f.name) } - : null, - })), - interfaces: t => t.entity.interfaces, - usage, - supergraphMetadata: t => - t.supergraph - ? { - ownedByServiceNames: t.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLUnionType: { - __isTypeOf: __isTypeOf(Kind.UNION_TYPE_DEFINITION), - name: t => t.entity.name, - description: t => t.entity.description ?? null, - members: t => - t.entity.members.map(i => { - return { - entity: i, - usage: t.usage, - parent: { - coordinate: t.entity.name, - }, - supergraph: t.supergraph - ? { - ownedByServiceNames: t.supergraph.getUnionMemberOwnedByServices(i.name), - } - : null, - }; - }), - usage, - supergraphMetadata: t => - t.supergraph - ? { - ownedByServiceNames: t.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLEnumType: { - __isTypeOf: __isTypeOf(Kind.ENUM_TYPE_DEFINITION), - name: t => t.entity.name, - description: t => t.entity.description ?? null, - values: t => - t.entity.values.map(v => ({ - entity: v, - parent: { - coordinate: t.entity.name, - }, - usage: t.usage, - supergraph: t.supergraph - ? { ownedByServiceNames: t.supergraph.getEnumValueOwnedByServices(v.name) } - : null, - })), - usage, - supergraphMetadata: t => - t.supergraph - ? { - ownedByServiceNames: t.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLInputObjectType: { - __isTypeOf: __isTypeOf(Kind.INPUT_OBJECT_TYPE_DEFINITION), - name: t => t.entity.name, - description: t => t.entity.description ?? null, - fields: t => - t.entity.fields.map(f => ({ - entity: f, - parent: { - coordinate: t.entity.name, - }, - usage: t.usage, - supergraph: t.supergraph - ? { - ownedByServiceNames: t.supergraph.getInputFieldOwnedByServices(f.name), - } - : null, - })), - usage, - supergraphMetadata: t => - t.supergraph - ? { - ownedByServiceNames: t.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLScalarType: { - __isTypeOf: __isTypeOf(Kind.SCALAR_TYPE_DEFINITION), - name: t => t.entity.name, - description: t => t.entity.description ?? null, - usage, - supergraphMetadata: t => - t.supergraph ? { ownedByServiceNames: t.supergraph.ownedByServiceNames } : null, - }, - GraphQLEnumValue: { - name: v => v.entity.name, - description: v => v.entity.description ?? null, - isDeprecated: v => typeof v.entity.deprecationReason === 'string', - deprecationReason: v => v.entity.deprecationReason ?? null, - usage, - supergraphMetadata: v => - v.supergraph ? { ownedByServiceNames: v.supergraph.ownedByServiceNames } : null, - }, - GraphQLUnionTypeMember: { - name: m => m.entity.name, - usage, - supergraphMetadata: m => - m.supergraph ? { ownedByServiceNames: m.supergraph.ownedByServiceNames } : null, - }, - GraphQLField: { - name: f => f.entity.name, - description: f => f.entity.description ?? null, - isDeprecated: f => typeof f.entity.deprecationReason === 'string', - deprecationReason: f => f.entity.deprecationReason ?? null, - type: f => f.entity.type, - args: f => - f.entity.args.map(a => ({ - entity: a, - parent: { - coordinate: `${f.parent.coordinate}.${f.entity.name}`, - }, - usage: f.usage, - })), - usage, - supergraphMetadata: f => - f.supergraph - ? { - ownedByServiceNames: f.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLInputField: { - name: f => f.entity.name, - description: f => f.entity.description ?? null, - type: f => f.entity.type, - defaultValue: f => stringifyDefaultValue(f.entity.defaultValue), - isDeprecated: f => typeof f.entity.deprecationReason === 'string', - deprecationReason: f => f.entity.deprecationReason ?? null, - usage, - supergraphMetadata: f => - f.supergraph - ? { - ownedByServiceNames: f.supergraph.ownedByServiceNames, - } - : null, - }, - GraphQLArgument: { - name: a => a.entity.name, - description: a => a.entity.description ?? null, - type: a => a.entity.type, - defaultValue: a => stringifyDefaultValue(a.entity.defaultValue), - deprecationReason: a => a.entity.deprecationReason ?? null, - isDeprecated: a => typeof a.entity.deprecationReason === 'string', - usage, - }, - SuccessfulSchemaCheck: { - schemaVersion(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getSchemaVersion(schemaCheck); - }, - safeSchemaChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getSafeSchemaChanges(schemaCheck); - }, - breakingSchemaChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getBreakingSchemaChanges(schemaCheck); - }, - hasSchemaCompositionErrors(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getHasSchemaCompositionErrors(schemaCheck); - }, - hasSchemaChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getHasSchemaChanges(schemaCheck); - }, - hasUnapprovedBreakingChanges() { - return false; - }, - webUrl(schemaCheck, _, { injector }) { - return injector.get(SchemaManager).getSchemaCheckWebUrl({ - schemaCheckId: schemaCheck.id, - targetId: schemaCheck.targetId, - }); - }, - isApproved(schemaCheck) { - return schemaCheck.isManuallyApproved; - }, - approvedBy(schemaCheck, _, { injector }) { - return schemaCheck.isManuallyApproved - ? injector.get(SchemaManager).getApprovedByUser({ - organizationId: schemaCheck.selector.organizationId, - userId: schemaCheck.manualApprovalUserId, - }) - : null; - }, - approvalComment(schemaCheck) { - return schemaCheck.isManuallyApproved ? schemaCheck.manualApprovalComment : null; - }, - contractChecks(schemaCheck, _, { injector }) { - return injector.get(ContractsManager).getContractsChecksForSchemaCheck(schemaCheck); - }, - previousSchemaSDL(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getPreviousSchemaSDL(schemaCheck); - }, - conditionalBreakingChangeMetadata(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getConditionalBreakingChangeMetadata(schemaCheck); - }, - }, - FailedSchemaCheck: { - schemaVersion(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getSchemaVersion(schemaCheck); - }, - safeSchemaChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getSafeSchemaChanges(schemaCheck); - }, - breakingSchemaChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getBreakingSchemaChanges(schemaCheck); - }, - compositionErrors(schemaCheck) { - return schemaCheck.schemaCompositionErrors; - }, - hasSchemaCompositionErrors(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getHasSchemaCompositionErrors(schemaCheck); - }, - hasSchemaChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getHasSchemaChanges(schemaCheck); - }, - hasUnapprovedBreakingChanges(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getHasUnapprovedBreakingChanges(schemaCheck); - }, - webUrl(schemaCheck, _, { injector }) { - return injector.get(SchemaManager).getSchemaCheckWebUrl({ - schemaCheckId: schemaCheck.id, - targetId: schemaCheck.targetId, - }); - }, - async canBeApproved(schemaCheck, _, { injector }) { - return injector.get(SchemaManager).getFailedSchemaCheckCanBeApproved(schemaCheck); - }, - async canBeApprovedByViewer(schemaCheck, _, { injector }) { - return injector.get(SchemaManager).getFailedSchemaCheckCanBeApprovedByViewer(schemaCheck); - }, - contractChecks(schemaCheck, _, { injector }) { - return injector.get(ContractsManager).getContractsChecksForSchemaCheck(schemaCheck); - }, - previousSchemaSDL(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getPreviousSchemaSDL(schemaCheck); - }, - conditionalBreakingChangeMetadata(schemaCheck, _, { injector }) { - return injector.get(SchemaCheckManager).getConditionalBreakingChangeMetadata(schemaCheck); - }, - }, - BreakingChangeMetadataTarget: { - target(record, _, { injector }) { - return injector - .get(TargetManager) - .getTargetById({ targetId: record.id }) - .catch(() => null); - }, - }, - SchemaPolicyWarningConnection: createDummyConnection(warning => ({ - ...warning, - start: { - column: warning.column, - line: warning.line, - }, - end: - warning.endColumn && warning.endLine - ? { - column: warning.endColumn, - line: warning.endLine, - } - : null, - })), - Contract: { - target(contract, _, context) { - return context.injector.get(TargetManager).getTargetById({ - targetId: contract.targetId, - }); - }, - viewerCanDisableContract(contract, _, context) { - return context.injector - .get(ContractsManager) - .getViewerCanDisableContractForContract(contract); - }, - }, - ContractCheck: { - contractVersion(contractCheck, _, context) { - return context.injector - .get(ContractsManager) - .getContractVersionForContractCheck(contractCheck); - }, - compositeSchemaSDL: contractCheck => contractCheck.compositeSchemaSdl, - supergraphSDL: contractCheck => contractCheck.supergraphSdl, - hasSchemaCompositionErrors(contractCheck, _, { injector }) { - return injector - .get(ContractsManager) - .getHasSchemaCompositionErrorsForContractCheck(contractCheck); - }, - hasUnapprovedBreakingChanges(contractCheck, _, { injector }) { - return injector - .get(ContractsManager) - .getHasUnapprovedBreakingChangesForContractCheck(contractCheck); - }, - hasSchemaChanges(contractCheck, _, { injector }) { - return injector.get(ContractsManager).getHasSchemaChangesForContractCheck(contractCheck); - }, - }, - ContractVersion: { - isComposable(contractVersion) { - return contractVersion.schemaCompositionErrors === null; - }, - hasSchemaChanges(contractVersion, _, context) { - return context.injector - .get(ContractsManager) - .getHasSchemaChangesForContractVersion(contractVersion); - }, - breakingSchemaChanges(contractVersion, _, context) { - return context.injector - .get(ContractsManager) - .getBreakingChangesForContractVersion(contractVersion); - }, - safeSchemaChanges(contractVersion, _, context) { - return context.injector - .get(ContractsManager) - .getSafeChangesForContractVersion(contractVersion); - }, - compositeSchemaSDL: contractVersion => contractVersion.compositeSchemaSdl, - supergraphSDL: contractVersion => contractVersion.supergraphSdl, - previousContractVersion: (contractVersion, _, context) => - context.injector - .get(ContractsManager) - .getPreviousContractVersionForContractVersion(contractVersion), - previousDiffableContractVersion: (contractVersion, _, context) => - context.injector - .get(ContractsManager) - .getDiffableContractVersionForContractVersion(contractVersion), - isFirstComposableVersion: (contractVersion, _, context) => - context.injector - .get(ContractsManager) - .getIsFirstComposableVersionForContractVersion(contractVersion), - }, -}; - -function stringifyDefaultValue(value: unknown): string | null { - if (typeof value !== 'undefined') { - return stringify(value); - } - return null; -} - -function withUsedByClients< - T extends { - isUsed: boolean; - }, ->( - input: Record, - deps: { - operationsManager: OperationsManager; - selector: TargetSelector; - period: DateRange; - typename: string; - }, -): Record< - string, - T & { - usedByClients: () => PromiseOrValue>; - period: DateRange; - organization: string; - project: string; - target: string; - typename: string; - } -> { - return Object.fromEntries( - Object.entries(input).map(([schemaCoordinate, record]) => [ - schemaCoordinate, - { - selector: deps.selector, - period: deps.period, - typename: deps.typename, - organization: deps.selector.organization, - project: deps.selector.project, - target: deps.selector.target, - ...record, - usedByClients() { - if (record.isUsed === false) { - return []; - } - - // It's using DataLoader under the hood so it's safe to call it multiple times for different coordinates - return deps.operationsManager.getClientNamesPerCoordinateOfType({ - ...deps.selector, - period: deps.period, - typename: deps.typename, - schemaCoordinate, - }); - }, - }, - ]), - ); -} - -function transformGraphQLObjectType(entity: GraphQLObjectType): GraphQLObjectTypeMapper['entity'] { - return { - kind: Kind.OBJECT_TYPE_DEFINITION, - name: entity.name, - description: entity.description, - interfaces: entity.getInterfaces().map(iface => iface.name), - fields: Object.values(entity.getFields()).map(field => ({ - name: field.name, - description: field.description, - deprecationReason: field.deprecationReason, - type: field.type.toString(), - args: field.args.map(arg => ({ - name: arg.name, - description: arg.description, - defaultValue: arg.defaultValue, - type: arg.type.toString(), - deprecationReason: arg.deprecationReason, - })), - })), - }; -} - -function transformGraphQLInterfaceType( - entity: GraphQLInterfaceType, -): GraphQLInterfaceTypeMapper['entity'] { - return { - kind: Kind.INTERFACE_TYPE_DEFINITION, - name: entity.name, - description: entity.description, - interfaces: entity.getInterfaces().map(iface => iface.name), - fields: Object.values(entity.getFields()).map(field => ({ - name: field.name, - description: field.description, - deprecationReason: field.deprecationReason, - type: field.type.toString(), - args: field.args.map(arg => ({ - name: arg.name, - description: arg.description, - defaultValue: arg.defaultValue, - type: arg.type.toString(), - deprecationReason: arg.deprecationReason, - })), - })), - }; -} - -function transformGraphQLEnumType(entity: GraphQLEnumType): GraphQLEnumTypeMapper['entity'] { - return { - kind: Kind.ENUM_TYPE_DEFINITION, - name: entity.name, - description: entity.description, - values: entity.getValues().map(value => ({ - name: value.name, - description: value.description, - deprecationReason: value.deprecationReason, - })), - }; -} - -function transformGraphQLUnionType(entity: GraphQLUnionType): GraphQLUnionTypeMapper['entity'] { - return { - kind: Kind.UNION_TYPE_DEFINITION, - name: entity.name, - description: entity.description, - members: entity.getTypes().map(type => ({ - name: type.name, - })), - }; -} - -function transformGraphQLInputObjectType( - entity: GraphQLInputObjectType, -): GraphQLInputObjectTypeMapper['entity'] { - return { - kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, - name: entity.name, - description: entity.description, - fields: Object.values(entity.getFields()).map(field => ({ - name: field.name, - description: field.description, - deprecationReason: field.deprecationReason, - defaultValue: field.defaultValue, - type: field.type.toString(), - })), - }; -} - -function transformGraphQLScalarType(entity: GraphQLScalarType): GraphQLScalarTypeMapper['entity'] { - return { - kind: Kind.SCALAR_TYPE_DEFINITION, - name: entity.name, - description: entity.description, - }; -} - -const criticalityMap: Record = { - [CriticalityLevel.Breaking]: 'Breaking', - [CriticalityLevel.NonBreaking]: 'Safe', - [CriticalityLevel.Dangerous]: 'Dangerous', -}; - -function deprecationReasonFromDirectives(directives: readonly ConstDirectiveNode[] | undefined) { - if (!directives) { - return null; - } - - const deprecatedDirective = directives.find(d => d.name.value === 'deprecated'); - - if (!deprecatedDirective) { - return null; - } - - const reasonArgument = deprecatedDirective.arguments?.find(a => a.name.value === 'reason'); - - if (!reasonArgument) { - return DEFAULT_DEPRECATION_REASON; - } - - if (reasonArgument.value.kind !== 'StringValue') { - throw new Error('Expected @deprecated(reason:) to be StringValue'); - } - - return reasonArgument.value.value; -} - -function buildGraphQLTypesFromSDL( - sdl: DocumentNode, - getStats: ( - typeName: string, - ) => ReturnType< - | GraphQLObjectTypeMapper['usage'] - | GraphQLInterfaceTypeMapper['usage'] - | GraphQLUnionTypeMapper['usage'] - | GraphQLEnumTypeMapper['usage'] - | GraphQLInputObjectTypeMapper['usage'] - | GraphQLScalarTypeMapper['usage'] - >, - supergraph: SuperGraphInformation | null, -) { - const types: Array< - | GraphQLObjectTypeMapper - | GraphQLInterfaceTypeMapper - | GraphQLUnionTypeMapper - | GraphQLEnumTypeMapper - | GraphQLInputObjectTypeMapper - | GraphQLScalarTypeMapper - > = []; - - for (const typeDefinition of sdl.definitions) { - if (typeDefinition.kind === Kind.OBJECT_TYPE_DEFINITION) { - types.push({ - entity: { - kind: Kind.OBJECT_TYPE_DEFINITION, - name: typeDefinition.name.value, - description: typeDefinition.description?.value, - interfaces: typeDefinition.interfaces?.map(i => i.name.value) ?? [], - fields: - typeDefinition.fields?.map(f => ({ - name: f.name.value, - description: f.description?.value, - type: print(f.type), - deprecationReason: deprecationReasonFromDirectives(f.directives), - args: - f.arguments?.map(a => ({ - name: a.name.value, - description: a.description?.value, - deprecationReason: deprecationReasonFromDirectives(a.directives), - type: print(a.type), - })) ?? [], - })) ?? [], - }, - usage() { - return getStats(typeDefinition.name.value); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get( - `${typeDefinition.name.value}.${fieldName}`, - ) ?? null, - } - : null, - } satisfies GraphQLObjectTypeMapper); - } else if (typeDefinition.kind === Kind.INTERFACE_TYPE_DEFINITION) { - types.push({ - entity: { - kind: Kind.INTERFACE_TYPE_DEFINITION, - name: typeDefinition.name.value, - description: typeDefinition.description?.value, - interfaces: typeDefinition.interfaces?.map(i => i.name.value) ?? [], - fields: - typeDefinition.fields?.map(f => ({ - name: f.name.value, - description: f.description?.value, - deprecationReason: deprecationReasonFromDirectives(f.directives), - type: print(f.type), - args: - f.arguments?.map(a => ({ - name: a.name.value, - description: a.description?.value, - deprecationReason: deprecationReasonFromDirectives(a.directives), - type: print(a.type), - })) ?? [], - })) ?? [], - }, - usage() { - return getStats(typeDefinition.name.value); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, - getFieldOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get( - `${typeDefinition.name.value}.${fieldName}`, - ) ?? null, - } - : null, - } satisfies GraphQLInterfaceTypeMapper); - } else if (typeDefinition.kind === Kind.ENUM_TYPE_DEFINITION) { - types.push({ - entity: { - kind: Kind.ENUM_TYPE_DEFINITION, - name: typeDefinition.name.value, - description: typeDefinition.description?.value, - values: - typeDefinition.values?.map(value => ({ - name: value.name.value, - description: value.description?.value, - deprecationReason: deprecationReasonFromDirectives(value.directives), - })) ?? [], - }, - usage() { - return getStats(typeDefinition.name.value); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, - getEnumValueOwnedByServices: (fieldName: string) => - supergraph.schemaCoordinateServicesMappings.get( - `${typeDefinition.name.value}.${fieldName}`, - ) ?? null, - } - : null, - } satisfies GraphQLEnumTypeMapper); - } else if (typeDefinition.kind === Kind.UNION_TYPE_DEFINITION) { - types.push({ - entity: { - kind: Kind.UNION_TYPE_DEFINITION, - name: typeDefinition.name.value, - description: typeDefinition.description?.value, - members: - typeDefinition.types?.map(t => ({ - name: t.name.value, - })) ?? [], - }, - usage() { - return getStats(typeDefinition.name.value); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, - getUnionMemberOwnedByServices: (memberName: string) => - supergraph.schemaCoordinateServicesMappings.get(memberName) ?? null, - } - : null, - } satisfies GraphQLUnionTypeMapper); - } else if (typeDefinition.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION) { - types.push({ - entity: { - kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, - name: typeDefinition.name.value, - description: typeDefinition.description?.value, - fields: - typeDefinition.fields?.map(f => ({ - name: f.name.value, - defaultValue: f.defaultValue ? print(f.defaultValue) : undefined, - description: f.description?.value, - deprecationReason: deprecationReasonFromDirectives(f.directives), - type: print(f.type), - })) ?? [], - }, - usage() { - return getStats(typeDefinition.name.value); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, - getInputFieldOwnedByServices: (inputFieldName: string) => - supergraph.schemaCoordinateServicesMappings.get( - `${typeDefinition.name.value}.${inputFieldName}`, - ) ?? null, - } - : null, - } satisfies GraphQLInputObjectTypeMapper); - } else if (typeDefinition.kind === Kind.SCALAR_TYPE_DEFINITION) { - types.push({ - entity: { - kind: Kind.SCALAR_TYPE_DEFINITION, - name: typeDefinition.name.value, - description: typeDefinition.description?.value, - }, - usage() { - return getStats(typeDefinition.name.value); - }, - supergraph: supergraph - ? { - ownedByServiceNames: - supergraph.schemaCoordinateServicesMappings.get(typeDefinition.name.value) ?? null, - } - : null, - } satisfies GraphQLScalarTypeMapper); - } - } - - return types; -} From 51e1bff0cf454929938d132c53e5a58d9b1f70df Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Sun, 20 Oct 2024 19:08:19 +0300 Subject: [PATCH 14/17] fix error --- .../src/modules/target/resolvers/Mutation/updateTargetSlug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/services/api/src/modules/target/resolvers/Mutation/updateTargetSlug.ts b/packages/services/api/src/modules/target/resolvers/Mutation/updateTargetSlug.ts index e27ddd1da2..4f8cc7d738 100644 --- a/packages/services/api/src/modules/target/resolvers/Mutation/updateTargetSlug.ts +++ b/packages/services/api/src/modules/target/resolvers/Mutation/updateTargetSlug.ts @@ -57,7 +57,7 @@ export const updateTargetSlug: NonNullable Date: Mon, 21 Oct 2024 19:56:53 +0300 Subject: [PATCH 15/17] fix code review --- .../api/src/modules/audit-logs/helpers.ts | 21 ------ .../providers/audit-logs-manager.ts | 71 ++++++++++++++----- .../resolvers/AppDeploymentCreatedAuditLog.ts | 4 +- .../AppDeploymentPublishedAuditLog.ts | 4 +- .../resolvers/AppDeploymentUpdatedAuditLog.ts | 4 +- .../resolvers/CollectionCreatedAuditLog.ts | 4 +- .../resolvers/CollectionDeletedAuditLog.ts | 4 +- .../resolvers/CollectionUpdatedAuditLog.ts | 4 +- ...tionInDocumentCollectionCreatedAuditLog.ts | 5 +- ...tionInDocumentCollectionDeletedAuditLog.ts | 5 +- ...tionInDocumentCollectionUpdatedAuditLog.ts | 5 +- .../resolvers/OrganizationCreatedAuditLog.ts | 4 +- .../resolvers/OrganizationDeletedAuditLog.ts | 4 +- .../OrganizationSettingsUpdatedAuditLog.ts | 4 +- .../OrganizationTransferredAuditLog.ts | 4 +- .../OrganizationTransferredRequestAuditLog.ts | 5 +- .../OrganizationUpdatedIntegrationAuditLog.ts | 5 +- .../resolvers/ProjectCreatedAuditLog.ts | 4 +- .../resolvers/ProjectDeletedAuditLog.ts | 4 +- .../ProjectSettingsUpdatedAuditLog.ts | 4 +- .../audit-logs/resolvers/Query/auditLogs.ts | 7 +- .../resolvers/RoleAssignedAuditLog.ts | 4 +- .../resolvers/RoleCreatedAuditLog.ts | 4 +- .../resolvers/RoleDeletedAuditLog.ts | 4 +- .../resolvers/RoleUpdatedAuditLog.ts | 4 +- .../resolvers/SchemaCheckedAuditLog.ts | 4 +- .../SchemaPolicySettingsUpdatedAuditLog.ts | 4 +- .../resolvers/SchemaPublishAuditLog.ts | 4 +- .../resolvers/ServiceDeletedAuditLog.ts | 4 +- .../resolvers/SubscriptionCanceledAuditLog.ts | 4 +- .../resolvers/SubscriptionCreatedAuditLog.ts | 4 +- .../resolvers/SubscriptionUpdatedAuditLog.ts | 4 +- .../resolvers/SupportTicketCreatedAuditLog.ts | 4 +- .../resolvers/SupportTicketUpdatedAuditLog.ts | 4 +- .../resolvers/TargetCreatedAuditLog.ts | 4 +- .../resolvers/TargetDeletedAuditLog.ts | 4 +- .../TargetSettingsUpdatedAuditLog.ts | 4 +- .../resolvers/UserInvitedAuditLog.ts | 4 +- .../resolvers/UserJoinedAuditLog.ts | 4 +- .../resolvers/UserRemovedAuditLog.ts | 4 +- .../resolvers/UserSettingsUpdatedAuditLog.ts | 4 +- .../resolvers/Mutation/downgradeToHobby.ts | 17 ----- 42 files changed, 138 insertions(+), 135 deletions(-) delete mode 100644 packages/services/api/src/modules/audit-logs/helpers.ts diff --git a/packages/services/api/src/modules/audit-logs/helpers.ts b/packages/services/api/src/modules/audit-logs/helpers.ts deleted file mode 100644 index 25440b9a21..0000000000 --- a/packages/services/api/src/modules/audit-logs/helpers.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injector } from 'graphql-modules'; -import { OrganizationManager } from '../organization/providers/organization-manager'; -import { AuditLogModel } from './providers/audit-logs-manager'; - -export function resolveRecordAuditLog(event: AuditLogModel, injector: Injector) { - const currentOrganization = injector.get(OrganizationManager).getOrganization({ - organization: event.organization_id, - }); - return { - userEmail: event.user_email, - userId: event.user_id, - organizationId: event.organization_id, - user: event.metadata.user, - organization: currentOrganization, - }; -} - -export function formatToClickhouseDateTime(date: string): string { - const d = new Date(date); - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`; -} diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index 1f06e7ceef..3e9c72e087 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -1,27 +1,43 @@ -import { Injectable, Scope } from 'graphql-modules'; +import { Injectable, Injector, Scope } from 'graphql-modules'; import { z } from 'zod'; import * as Sentry from '@sentry/node'; -import { QueryAuditLogsArgs } from '../../../__generated__/types.next'; import { User } from '../../../shared/entities'; import { ClickHouse, sql } from '../../operations/providers/clickhouse-client'; import { SqlValue } from '../../operations/providers/sql'; +import { OrganizationManager } from '../../organization/providers/organization-manager'; import { Logger } from '../../shared/providers/logger'; -import { formatToClickhouseDateTime } from '../helpers'; import { AuditLogEvent, auditLogSchema } from './audit-logs-types'; +const auditLogEventTypes = auditLogSchema.options.map(option => option.shape.eventType.value); + export const AUDIT_LOG_CLICKHOUSE_OBJECT = z.object({ id: z.string(), event_time: z.string(), user_id: z.string(), user_email: z.string(), organization_id: z.string(), - event_action: z.string(), + event_action: z.enum(auditLogEventTypes as [string, ...string[]]), metadata: z.string().transform(x => JSON.parse(x)), }); -export type AuditLogModel = z.infer; - -const AUDIT_LOG_CLICKHOUSE_ARRAY = z.array(AUDIT_LOG_CLICKHOUSE_OBJECT); +export type AuditLogType = z.infer; + +const AuditLogClickhouseArrayModel = z.array(AUDIT_LOG_CLICKHOUSE_OBJECT); + +type AuditLogsArgs = { + selector: { + organization: string; + }; + filter: { + userId: string; + startDate: Date; + endDate: Date; + }; + pagination: { + limit: number; + offset: number; + }; +}; type AuditLogRecordEvent = { userId: string; @@ -88,8 +104,8 @@ export class AuditLogManager { } async getPaginatedAuditLogs( - props: QueryAuditLogsArgs, - ): Promise<{ total: number; data: AuditLogModel[] }> { + props: AuditLogsArgs, + ): Promise<{ total: number; data: AuditLogType[] }> { this.logger.info( 'Getting paginated audit logs (organization=%s, filter=%o, pagination=%o)', props.selector.organization, @@ -100,12 +116,12 @@ export class AuditLogManager { if (!props.selector.organization) { throw new Error('Organization ID is required'); } - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - const sqlLimit = sql.raw(props.pagination?.limit?.toString() ?? '25'); - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - const sqlOffset = sql.raw(props.pagination?.offset?.toString() ?? '0'); + console.log('props.pagination.limit', props.pagination.limit); + console.log('props.pagination.offset', props.pagination.offset); + const sqlLimit = sql.raw(props.pagination.limit.toString()); + const sqlOffset = sql.raw(props.pagination.offset.toString()); - let where: SqlValue[] = []; + const where: SqlValue[] = []; where.push(sql`organization_id = ${props.selector.organization}`); if (props.filter?.userId) { @@ -113,8 +129,8 @@ export class AuditLogManager { } if (props.filter?.startDate && props.filter?.endDate) { - const from = formatToClickhouseDateTime(props.filter.startDate.toISOString()); - const to = formatToClickhouseDateTime(props.filter.endDate.toISOString()); + const from = this.formatToClickhouseDateTime(props.filter.startDate.toISOString()); + const to = this.formatToClickhouseDateTime(props.filter.endDate.toISOString()); where.push(sql`event_time >= ${from} AND event_time <= ${to}`); } @@ -135,10 +151,9 @@ export class AuditLogManager { const totalResult = await this.clickHouse.query({ query: sql` - SELECT * + SELECT COUNT(*) FROM audit_log ${whereClause} - ORDER BY event_time DESC `, queryId: 'get-audit-logs-total', timeout: 5000, @@ -146,7 +161,25 @@ export class AuditLogManager { return { total: totalResult.rows, - data: AUDIT_LOG_CLICKHOUSE_ARRAY.parse(result.data), + data: AuditLogClickhouseArrayModel.parse(result.data), + }; + } + + async resolveRecordAuditLog(event: AuditLogType, injector: Injector) { + const currentOrganization = await injector.get(OrganizationManager).getOrganization({ + organization: event.organization_id, + }); + return { + userEmail: event.user_email, + userId: event.user_id, + organizationId: event.organization_id, + user: event.metadata.user, + organization: currentOrganization, }; } + + formatToClickhouseDateTime(date: string): string { + const d = new Date(date); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`; + } } diff --git a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts index 55ebb793fb..9a223abdc0 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { AppDeploymentCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const AppDeploymentCreatedAuditLog: AppDeploymentCreatedAuditLogResolvers deploymentId: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentId, deploymentName: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentName, deploymentVersion: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentVersion, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts index 61ffcfacec..25a35456b1 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentPublishedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { AppDeploymentPublishedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const AppDeploymentPublishedAuditLog: AppDeploymentPublishedAuditLogResol eventTime: e => new Date(e.event_time).toISOString(), deploymentId: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentId, deploymentVersion: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentVersion, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts index c1822c6d3c..de6d23276f 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/AppDeploymentUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { AppDeploymentUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const AppDeploymentUpdatedAuditLog: AppDeploymentUpdatedAuditLogResolvers eventTime: e => new Date(e.event_time).toISOString(), deploymentId: e => e.metadata.appDeploymentCreatedAuditLogSchema.deploymentId, updatedFields: e => e.metadata.appDeploymentUpdatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts index 2c9844a567..7b4fe5b50f 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/CollectionCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { CollectionCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const CollectionCreatedAuditLog: CollectionCreatedAuditLogResolvers = { collectionId: e => e.metadata.collectionCreatedAuditLogSchema.collectionId, collectionName: e => e.metadata.collectionCreatedAuditLogSchema.collectionName, targetId: e => e.metadata.collectionCreatedAuditLogSchema.targetId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts index 24b8e9d0a3..75ac8de030 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/CollectionDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { CollectionDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const CollectionDeletedAuditLog: CollectionDeletedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), collectionId: e => e.metadata.collectionDeletedAuditLogSchema.collectionId, collectionName: e => e.metadata.collectionDeletedAuditLogSchema.collectionName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts index 25a152f27e..17efe98517 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/CollectionUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { CollectionUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const CollectionUpdatedAuditLog: CollectionUpdatedAuditLogResolvers = { collectionId: e => e.metadata.collectionUpdatedAuditLogSchema.collectionId, updatedFields: e => e.metadata.collectionUpdatedAuditLogSchema.updatedFields, collectionName: e => e.metadata.collectionUpdatedAuditLogSchema.collectionName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts index f380f18c1b..9433532d6f 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OperationInDocumentCollectionCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -19,7 +19,8 @@ export const OperationInDocumentCollectionCreatedAuditLog: OperationInDocumentCo operationQuery: e => e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.operationQuery, targetId: e => e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.targetId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => + injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), collectionName: e => e.metadata.operationInDocumentCollectionCreatedAuditLogSchema.collectionName, }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts index d378929f8e..f1f49ef1b3 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OperationInDocumentCollectionDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -18,5 +18,6 @@ export const OperationInDocumentCollectionDeletedAuditLog: OperationInDocumentCo collectionName: e => e.metadata.operationInDocumentCollectionDeletedAuditLogSchema.collectionName, operationId: e => e.metadata.operationInDocumentCollectionDeletedAuditLogSchema.operationId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => + injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts index f2995b153e..657bfa632a 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OperationInDocumentCollectionUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OperationInDocumentCollectionUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -19,5 +19,6 @@ export const OperationInDocumentCollectionUpdatedAuditLog: OperationInDocumentCo e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.collectionName, updatedFields: e => e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.updatedFields, operationId: e => e.metadata.operationInDocumentCollectionUpdatedAuditLogSchema.operationId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => + injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts index 2c59091c6d..77e78e9047 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OrganizationCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const OrganizationCreatedAuditLog: OrganizationCreatedAuditLogResolvers = eventTime: e => new Date(e.event_time).toISOString(), organizationId: e => e.metadata.organizationCreatedAuditLogSchema.organizationId, organizationName: e => e.metadata.organizationCreatedAuditLogSchema.organizationName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts index 3f1e90ec70..1050951308 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OrganizationDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -14,5 +14,5 @@ export const OrganizationDeletedAuditLog: OrganizationDeletedAuditLogResolvers = __isTypeOf: e => e.event_action === 'ORGANIZATION_DELETED', eventTime: e => new Date(e.event_time).toISOString(), organizationId: e => e.metadata.organizationDeletedAuditLogSchema.organizationId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts index eef822ec07..f7f4b3cf87 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationSettingsUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OrganizationSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -14,5 +14,5 @@ export const OrganizationSettingsUpdatedAuditLog: OrganizationSettingsUpdatedAud __isTypeOf: e => e.event_action === 'ORGANIZATION_SETTINGS_UPDATED', eventTime: e => new Date(e.event_time).toISOString(), updatedFields: e => e.metadata.organizationSettingsUpdatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts index 0237fa6983..9f0350251c 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OrganizationTransferredAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const OrganizationTransferredAuditLog: OrganizationTransferredAuditLogRes eventTime: e => new Date(e.event_time).toISOString(), newOwnerEmail: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerEmail, newOwnerId: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts index 2a532db17a..2a1204f593 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationTransferredRequestAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OrganizationTransferredRequestAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,6 @@ export const OrganizationTransferredRequestAuditLog: OrganizationTransferredRequ eventTime: e => new Date(e.event_time).toISOString(), newOwnerEmail: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerEmail, newOwnerId: e => e.metadata.organizationTransferredAuditLogSchema.newOwnerId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => + injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts index ac7e75b1d2..294524b49e 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/OrganizationUpdatedIntegrationAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { OrganizationUpdatedIntegrationAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,6 @@ export const OrganizationUpdatedIntegrationAuditLog: OrganizationUpdatedIntegrat eventTime: e => new Date(e.event_time).toISOString(), updatedFields: e => e.metadata.organizationUpdatedIntegrationAuditLogSchema.updatedFields, integrationId: e => e.metadata.organizationUpdatedIntegrationAuditLogSchema.integrationId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => + injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts index ad5b5bc544..aa1b88c117 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { ProjectCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const ProjectCreatedAuditLog: ProjectCreatedAuditLogResolvers = { projectId: e => e.metadata.projectCreatedAuditLogSchema.projectId, projectName: e => e.metadata.projectCreatedAuditLogSchema.projectName, projectType: e => e.metadata.projectCreatedAuditLogSchema.projectType, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts index 2504a051cb..147467eb89 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { ProjectDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const ProjectDeletedAuditLog: ProjectDeletedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), projectId: e => e.metadata.projectDeletedAuditLogSchema.projectId, projectName: e => e.metadata.projectDeletedAuditLogSchema.projectName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts index ea8bd07ccc..b0823baed5 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/ProjectSettingsUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { ProjectSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const ProjectSettingsUpdatedAuditLog: ProjectSettingsUpdatedAuditLogResol eventTime: e => new Date(e.event_time).toISOString(), projectId: e => e.metadata.projectSettingsUpdatedAuditLogSchema.projectId, updatedFields: e => e.metadata.projectSettingsUpdatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts index 2ef8d0e9d0..1fe0f8c7ef 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/Query/auditLogs.ts @@ -14,8 +14,11 @@ export const auditLogs: NonNullable = async (_paren const { selector, filter, pagination } = args; const auditLogs = await ctx.injector.get(AuditLogManager).getPaginatedAuditLogs({ selector: selector, - filter: filter, - pagination: pagination, + filter: filter as any, + pagination: { + limit: pagination?.limit ? pagination.limit : 25, + offset: pagination?.offset ? pagination.offset : 0, + }, }); return { diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts index dd49a866a8..f81dfcc04e 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleAssignedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { RoleAssignedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -17,5 +17,5 @@ export const RoleAssignedAuditLog: RoleAssignedAuditLogResolvers = { roleId: e => e.metadata.roleAssignedAuditLogSchema.roleId, updatedMember: e => e.metadata.roleAssignedAuditLogSchema.updatedMember, userIdAssigned: e => e.metadata.roleAssignedAuditLogSchema.userIdAssigned, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts index 2dfad5bf52..15017c04e7 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { RoleCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const RoleCreatedAuditLog: RoleCreatedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), roleId: e => e.metadata.roleCreatedAuditLogSchema.roleId, roleName: e => e.metadata.roleCreatedAuditLogSchema.roleName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts index 9ca7bcbefa..01bfad177e 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { RoleDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const RoleDeletedAuditLog: RoleDeletedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), roleId: e => e.metadata.roleDeletedAuditLogSchema.roleId, roleName: e => e.metadata.roleDeletedAuditLogSchema.roleName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts index 494c930a40..e825729e89 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/RoleUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { RoleUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const RoleUpdatedAuditLog: RoleUpdatedAuditLogResolvers = { roleId: e => e.metadata.roleUpdatedAuditLogSchema.roleId, updatedFields: e => e.metadata.roleUpdatedAuditLogSchema.updatedFields, roleName: e => e.metadata.roleUpdatedAuditLogSchema.roleName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts index 4e4fa9c5bc..46a200bb34 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaCheckedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SchemaCheckedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const SchemaCheckedAuditLog: SchemaCheckedAuditLogResolvers = { checkId: e => e.metadata.schemaCheckedAuditLogSchema.checkId, projectId: e => e.metadata.schemaCheckedAuditLogSchema.projectId, targetId: e => e.metadata.schemaCheckedAuditLogSchema.targetId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts index 0dcc6648cf..aa1ae61123 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPolicySettingsUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SchemaPolicySettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const SchemaPolicySettingsUpdatedAuditLog: SchemaPolicySettingsUpdatedAud eventTime: e => new Date(e.event_time).toISOString(), projectId: e => e.metadata.schemaCheckedAuditLogSchema.projectId, updatedFields: e => e.metadata.schemaCheckedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts index 2fafdee859..70f69aceb2 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SchemaPublishAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SchemaPublishAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -19,5 +19,5 @@ export const SchemaPublishAuditLog: SchemaPublishAuditLogResolvers = { schemaVersionId: e => e.metadata.schemaPublishAuditLogSchema.schemaVersionId, serviceName: e => e.metadata.schemaPublishAuditLogSchema.serviceName, targetId: e => e.metadata.schemaPublishAuditLogSchema.targetId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts index f369b6ea57..d9ba8b9abd 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/ServiceDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { ServiceDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const ServiceDeletedAuditLog: ServiceDeletedAuditLogResolvers = { projectId: e => e.metadata.serviceDeletedAuditLogSchema.projectId, serviceName: e => e.metadata.serviceDeletedAuditLogSchema.serviceName, targetId: e => e.metadata.serviceDeletedAuditLogSchema.targetId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts index 3aa41069aa..2c663953cf 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCanceledAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SubscriptionCanceledAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const SubscriptionCanceledAuditLog: SubscriptionCanceledAuditLogResolvers eventTime: e => new Date(e.event_time).toISOString(), newPlan: e => e.metadata.subscriptionCanceledAuditLogSchema.newPlan, previousPlan: e => e.metadata.subscriptionCanceledAuditLogSchema.previousPlan, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts index fa9e715e4c..da3f88a189 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SubscriptionCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -17,5 +17,5 @@ export const SubscriptionCreatedAuditLog: SubscriptionCreatedAuditLogResolvers = operations: e => e.metadata.subscriptionCreatedAuditLogSchema.operations, paymentMethodId: e => e.metadata.subscriptionCreatedAuditLogSchema.paymentMethodId, previousPlan: e => e.metadata.subscriptionCreatedAuditLogSchema.previousPlan, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts index a48a14fe68..6063fbc322 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SubscriptionUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SubscriptionUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -14,5 +14,5 @@ export const SubscriptionUpdatedAuditLog: SubscriptionUpdatedAuditLogResolvers = __isTypeOf: e => e.event_action === 'SUBSCRIPTION_UPDATED', eventTime: e => new Date(e.event_time).toISOString(), updatedFields: e => e.metadata.subscriptionUpdatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts index dde21cad26..b91875532e 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SupportTicketCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -17,5 +17,5 @@ export const SupportTicketCreatedAuditLog: SupportTicketCreatedAuditLogResolvers ticketId: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketId, ticketPriority: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketPriority, ticketSubject: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketSubject, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts index de415bdc74..8d5b8ac598 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/SupportTicketUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { SupportTicketUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const SupportTicketUpdatedAuditLog: SupportTicketUpdatedAuditLogResolvers eventTime: e => new Date(e.event_time).toISOString(), ticketId: e => e.metadata.supportTicketCreatedAuditLogSchema.ticketId, updatedFields: e => e.metadata.supportTicketCreatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts index 96fda6f5c5..2e044a32b0 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetCreatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { TargetCreatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const TargetCreatedAuditLog: TargetCreatedAuditLogResolvers = { projectId: e => e.metadata.targetCreatedAuditLogSchema.projectId, targetId: e => e.metadata.targetCreatedAuditLogSchema.targetId, targetName: e => e.metadata.targetCreatedAuditLogSchema.targetName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts index 72019baf3b..4cb694b578 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetDeletedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { TargetDeletedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const TargetDeletedAuditLog: TargetDeletedAuditLogResolvers = { projectId: e => e.metadata.targetCreatedAuditLogSchema.projectId, targetId: e => e.metadata.targetCreatedAuditLogSchema.targetId, targetName: e => e.metadata.targetCreatedAuditLogSchema.targetName, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts index cfa881ae9d..d31bdf2f3f 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/TargetSettingsUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { TargetSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -16,5 +16,5 @@ export const TargetSettingsUpdatedAuditLog: TargetSettingsUpdatedAuditLogResolve projectId: e => e.metadata.targetCreatedAuditLogSchema.projectId, targetId: e => e.metadata.targetCreatedAuditLogSchema.targetId, updatedFields: e => e.metadata.targetSettingsUpdatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts index 09b93e6115..66a48fd5fe 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserInvitedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { UserInvitedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const UserInvitedAuditLog: UserInvitedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), inviteeEmail: e => e.metadata.userInvitedAuditLogSchema.inviteeEmail, inviteeId: e => e.metadata.userInvitedAuditLogSchema.inviteeId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts index cf9d2fae5d..de7c8dc268 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserJoinedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { UserJoinedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const UserJoinedAuditLog: UserJoinedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), inviteeEmail: e => e.metadata.userInvitedAuditLogSchema.inviteeEmail, inviteeId: e => e.metadata.userInvitedAuditLogSchema.inviteeId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts index 5584bd59bc..2e6275a8a5 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserRemovedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { UserRemovedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -15,5 +15,5 @@ export const UserRemovedAuditLog: UserRemovedAuditLogResolvers = { eventTime: e => new Date(e.event_time).toISOString(), removedUserEmail: e => e.metadata.userRemovedAuditLogSchema.removedUserEmail, removedUserId: e => e.metadata.userRemovedAuditLogSchema.removedUserId, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts b/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts index 04e0462147..383573bb25 100644 --- a/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts +++ b/packages/services/api/src/modules/audit-logs/resolvers/UserSettingsUpdatedAuditLog.ts @@ -1,4 +1,4 @@ -import { resolveRecordAuditLog } from '../helpers'; +import { AuditLogManager } from '../providers/audit-logs-manager'; import type { UserSettingsUpdatedAuditLogResolvers } from './../../../__generated__/types.next'; /* @@ -14,5 +14,5 @@ export const UserSettingsUpdatedAuditLog: UserSettingsUpdatedAuditLogResolvers = __isTypeOf: e => e.event_action === 'USER_SETTINGS_UPDATED', eventTime: e => new Date(e.event_time).toISOString(), updatedFields: e => e.metadata.userSettingsUpdatedAuditLogSchema.updatedFields, - record: (e, _, { injector }) => resolveRecordAuditLog(e, injector), + record: (e, _, { injector }) => injector.get(AuditLogManager).resolveRecordAuditLog(e, injector), }; diff --git a/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts b/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts index 9981b203b6..57344b72c9 100644 --- a/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts +++ b/packages/services/api/src/modules/billing/resolvers/Mutation/downgradeToHobby.ts @@ -45,23 +45,6 @@ export const downgradeToHobby: NonNullable Date: Tue, 22 Oct 2024 12:01:00 +0300 Subject: [PATCH 16/17] ? --- .../modules/audit-logs/providers/audit-logs-manager.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts index 3e9c72e087..d80b9c1293 100644 --- a/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts +++ b/packages/services/api/src/modules/audit-logs/providers/audit-logs-manager.ts @@ -17,7 +17,9 @@ export const AUDIT_LOG_CLICKHOUSE_OBJECT = z.object({ user_email: z.string(), organization_id: z.string(), event_action: z.enum(auditLogEventTypes as [string, ...string[]]), - metadata: z.string().transform(x => JSON.parse(x)), + metadata: + z.string().transform(x => auditLogSchema.parse(JSON.parse(x))) && + z.string().transform(x => JSON.parse(x)), }); export type AuditLogType = z.infer; @@ -116,8 +118,7 @@ export class AuditLogManager { if (!props.selector.organization) { throw new Error('Organization ID is required'); } - console.log('props.pagination.limit', props.pagination.limit); - console.log('props.pagination.offset', props.pagination.offset); + const sqlLimit = sql.raw(props.pagination.limit.toString()); const sqlOffset = sql.raw(props.pagination.offset.toString()); @@ -169,6 +170,7 @@ export class AuditLogManager { const currentOrganization = await injector.get(OrganizationManager).getOrganization({ organization: event.organization_id, }); + console.log('event', event); return { userEmail: event.user_email, userId: event.user_id, From fb1f204510d460ec415285e5104bf1ffcab6b050 Mon Sep 17 00:00:00 2001 From: TuvalSimha Date: Tue, 22 Oct 2024 14:15:23 +0300 Subject: [PATCH 17/17] some --- .../billing/providers/billing.provider.ts | 28 +++- .../Mutation/generateStripePortalLink.ts | 20 --- .../resolvers/Mutation/updateOrgRateLimit.ts | 23 ---- .../providers/organization-manager.ts | 124 ++++++++++++++---- .../resolvers/Mutation/createMemberRole.ts | 22 ---- .../resolvers/Mutation/createOrganization.ts | 17 --- .../resolvers/Mutation/deleteOrganization.ts | 18 --- .../project/providers/project-manager.ts | 49 ++++++- .../resolvers/Mutation/createProject.ts | 18 --- .../resolvers/Mutation/deleteProject.ts | 16 --- 10 files changed, 167 insertions(+), 168 deletions(-) diff --git a/packages/services/api/src/modules/billing/providers/billing.provider.ts b/packages/services/api/src/modules/billing/providers/billing.provider.ts index cb7fdf16c9..7b00204280 100644 --- a/packages/services/api/src/modules/billing/providers/billing.provider.ts +++ b/packages/services/api/src/modules/billing/providers/billing.provider.ts @@ -7,27 +7,32 @@ import { Logger } from '../../shared/providers/logger'; import { Storage } from '../../shared/providers/storage'; import type { BillingConfig } from './tokens'; import { BILLING_CONFIG } from './tokens'; +import { AuthManager } from '../../auth/providers/auth-manager'; +import { AuditLogManager } from '../../audit-logs/providers/audit-logs-manager'; @Injectable({ global: true, - scope: Scope.Singleton, + scope: Scope.Operation, }) export class BillingProvider { private logger: Logger; private billingService; + enabled = false; constructor( logger: Logger, + private authManager: AuthManager, + private auditLogManager: AuditLogManager, private storage: Storage, @Inject(BILLING_CONFIG) billingConfig: BillingConfig, ) { this.logger = logger.child({ source: 'BillingProvider' }); this.billingService = billingConfig.endpoint ? createTRPCProxyClient({ - links: [httpLink({ url: `${billingConfig.endpoint}/trpc`, fetch })], - }) + links: [httpLink({ url: `${billingConfig.endpoint}/trpc`, fetch })], + }) : null; if (billingConfig.endpoint) { @@ -104,6 +109,23 @@ export class BillingProvider { throw new Error(`Billing service is not configured!`); } + const currentUser = await this.authManager.getCurrentUser(); + this.auditLogManager.createLogAuditEvent( + { + eventType: 'SUBSCRIPTION_CANCELED', + subscriptionCanceledAuditLogSchema: { + newPlan: 'HOBBY', + previousPlan: 'PRO', + } + }, + { + organizationId: input.organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return await this.billingService.cancelSubscriptionForOrganization.mutate(input); } diff --git a/packages/services/api/src/modules/billing/resolvers/Mutation/generateStripePortalLink.ts b/packages/services/api/src/modules/billing/resolvers/Mutation/generateStripePortalLink.ts index cbcbc73370..3703f7008c 100644 --- a/packages/services/api/src/modules/billing/resolvers/Mutation/generateStripePortalLink.ts +++ b/packages/services/api/src/modules/billing/resolvers/Mutation/generateStripePortalLink.ts @@ -1,5 +1,3 @@ -import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; -import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationAccessScope } from '../../../auth/providers/organization-access'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; @@ -21,23 +19,5 @@ export const generateStripePortalLink: NonNullable< const result = injector.get(BillingProvider).generateStripePortalLink(organization.id); - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'SUBSCRIPTION_UPDATED', - subscriptionUpdatedAuditLogSchema: { - updatedFields: JSON.stringify({ - generateStripePortalLink: true, - }), - }, - }, - { - organizationId: organizationId, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - return result; }; diff --git a/packages/services/api/src/modules/billing/resolvers/Mutation/updateOrgRateLimit.ts b/packages/services/api/src/modules/billing/resolvers/Mutation/updateOrgRateLimit.ts index 999c8573bf..8cc82d078f 100644 --- a/packages/services/api/src/modules/billing/resolvers/Mutation/updateOrgRateLimit.ts +++ b/packages/services/api/src/modules/billing/resolvers/Mutation/updateOrgRateLimit.ts @@ -1,5 +1,3 @@ -import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; -import { AuthManager } from '../../../auth/providers/auth-manager'; import { OrganizationManager } from '../../../organization/providers/organization-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { USAGE_DEFAULT_LIMITATIONS } from '../../constants'; @@ -22,26 +20,5 @@ export const updateOrgRateLimit: NonNullable { this.logger.debug('Leaving organization (organization=%s)', organizationId); const user = await this.authManager.getCurrentUser(); @@ -310,8 +312,26 @@ export class OrganizationManager { }, user, }); + + const currentUser = await this.authManager.getCurrentUser(); + this.auditLogManager.createLogAuditEvent( + { + eventType: 'ORGANIZATION_CREATED', + organizationCreatedAuditLogSchema: { + organizationId: result.organization.id, + organizationName: result.organization.name, + } + }, + { + organizationId: result.organization.id, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); } + return result; } @@ -335,6 +355,23 @@ export class OrganizationManager { // Because we checked the access before, it's stale by now this.authManager.resetAccessCache(); + + const currentUser = await this.authManager.getCurrentUser(); + this.auditLogManager.createLogAuditEvent( + { + eventType: 'ORGANIZATION_DELETED', + organizationDeletedAuditLogSchema: { + organizationId: organization.id, + }, + }, + { + organizationId: organization.id, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return deletedOrganization; } @@ -399,6 +436,27 @@ export class OrganizationManager { }); } + const currentUser = await this.authManager.getCurrentUser(); + this.auditLogManager.createLogAuditEvent( + { + eventType: 'SUBSCRIPTION_UPDATED', + subscriptionUpdatedAuditLogSchema: { + updatedFields: JSON.stringify({ + monthlyRateLimit: { + retentionInDays: monthlyRateLimit.retentionInDays, + operations: monthlyRateLimit.operations, + }, + }), + }, + }, + { + organizationId: organization.id, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return result; } @@ -491,12 +549,12 @@ export class OrganizationManager { const role = input.role ? await this.storage.getOrganizationMemberRole({ - organizationId: organization.id, - roleId: input.role, - }) + organizationId: organization.id, + roleId: input.role, + }) : await this.storage.getViewerOrganizationMemberRole({ - organizationId: organization.id, - }); + organizationId: organization.id, + }); if (!role) { throw new HiveError(`Role not found`); } @@ -947,6 +1005,22 @@ export class OrganizationManager { scopes, }); + this.auditLogManager.createLogAuditEvent( + { + eventType: 'ROLE_CREATED', + roleCreatedAuditLogSchema: { + roleId: role.id, + roleName: role.name, + }, + }, + { + organizationId: input.organizationId, + userEmail: currentUser.email, + userId: currentUser.id, + user: currentUser, + }, + ); + return { ok: { updatedOrganization: await this.storage.getOrganization({ @@ -1288,12 +1362,12 @@ export class OrganizationManager { )[], ): Promise< | { - ok: false; - message: string; - } + ok: false; + message: string; + } | { - ok: true; - } + ok: true; + } > { // Ensure role is not locked (can't be deleted) if (role.locked) { @@ -1354,12 +1428,12 @@ export class OrganizationManager { )[], ): | { - ok: false; - message: string; - } + ok: false; + message: string; + } | { - ok: true; - } { + ok: true; + } { // Ensure role is not locked (can't be updated) if (role.locked) { return { @@ -1397,12 +1471,12 @@ export class OrganizationManager { )[], ): | { - ok: false; - message: string; - } + ok: false; + message: string; + } | { - ok: true; - } { + ok: true; + } { // Ensure user has access to all scopes in the role const currentUserMissingScopes = role.scopes.filter( scope => !currentUserScopes.includes(scope), diff --git a/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts b/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts index 29fdaff775..b57188d337 100644 --- a/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts +++ b/packages/services/api/src/modules/organization/resolvers/Mutation/createMemberRole.ts @@ -1,5 +1,3 @@ -import { AuditLogManager } from '../../../audit-logs/providers/audit-logs-manager'; -import { AuthManager } from '../../../auth/providers/auth-manager'; import { IdTranslator } from '../../../shared/providers/id-translator'; import { OrganizationManager } from '../../providers/organization-manager'; import { createOrUpdateMemberRoleInputSchema } from '../../validation'; @@ -37,25 +35,5 @@ export const createMemberRole: NonNullable { const { slug, organization, project } = input; this.logger.info('Updating a project slug (input=%o)', input); diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts index 5078678200..da1bed8013 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/createProject.ts @@ -78,24 +78,6 @@ export const createProject: NonNullable = as }), ]); - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'PROJECT_CREATED', - projectCreatedAuditLogSchema: { - projectId: result.project.id, - projectType: result.project.type, - projectName: result.project.name, - }, - }, - { - organizationId: organization.id, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); - const logger = injector.get(Logger); const targets: Target[] = []; for (const result of targetResults) { diff --git a/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts b/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts index e3159f14f2..8308e7485d 100644 --- a/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts +++ b/packages/services/api/src/modules/project/resolvers/Mutation/deleteProject.ts @@ -24,22 +24,6 @@ export const deleteProject: NonNullable = as project: projectId, }); - const currentUser = await injector.get(AuthManager).getCurrentUser(); - injector.get(AuditLogManager).createLogAuditEvent( - { - eventType: 'PROJECT_DELETED', - projectDeletedAuditLogSchema: { - projectId: projectId, - projectName: deletedProject.name, - }, - }, - { - organizationId: organizationId, - userEmail: currentUser.email, - userId: currentUser.id, - user: currentUser, - }, - ); return { selector: {