Repository: twentyhq/twenty
File: packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts (TypeScript)

Code before:
    this.inactiveDaysBeforeWarn = this.twentyConfigService.get(
      'WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION',
    );
    this.maxNumberOfWorkspacesDeletedPerExecution =
      this.twentyConfigService.get(
        'MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION',
      );
  }

  async computeDaysSinceSubscriptionUnpaid(
    workspace: WorkspaceEntity,
  ): Promise<number> {
    try {
      const lastSubscription =
        await this.billingSubscriptionRepository.findOneOrFail({
          where: {
            workspaceId: workspace.id,
          },
          order: { updatedAt: 'DESC' },
        });

      if (
        lastSubscription.status !== SubscriptionStatus.Unpaid &&
        lastSubscription.status !== SubscriptionStatus.Canceled
      ) {
        throw new Error();
      }

      const daysSinceSubscriptionUnpaid = differenceInDays(
        new Date(),
        lastSubscription.currentPeriodStart,
      );

      return daysSinceSubscriptionUnpaid;
    } catch {
      throw new WorkspaceCleanerException(
        `No cancelled or unpaid billing subscription found for workspace ${workspace.id} ${workspace.displayName}`,
        WorkspaceCleanerExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND,
      );
    }
  }

  async checkIfAtLeastOneWorkspaceMemberWarned(
    workspaceMembers: WorkspaceMemberWorkspaceEntity[],
    workspaceId: string,
  ) {
    for (const workspaceMember of workspaceMembers) {
      const workspaceMemberWarned = await this.userVarsService.get({
        userId: workspaceMember.userId,
        workspaceId: workspaceId,
        key: USER_WORKSPACE_DELETION_WARNING_SENT_KEY,

Review comment (suggestion):
Could we throw a custom billing error? (even if it's not user facing)

Code after:
    this.inactiveDaysBeforeWarn = this.twentyConfigService.get(
      'WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION',
    );
    this.maxNumberOfWorkspacesDeletedPerExecution =
      this.twentyConfigService.get(
        'MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION',
      );
  }

  async computeDaysSinceSubscriptionUnpaidOrThrow(
    workspace: WorkspaceEntity,
  ): Promise<number> {
    try {
      const lastSubscription =
        await this.billingSubscriptionRepository.findOneOrFail({
          where: {
            workspaceId: workspace.id,
          },
          order: { updatedAt: 'DESC' },
        });

      if (
        lastSubscription.status !== SubscriptionStatus.Unpaid &&
        lastSubscription.status !== SubscriptionStatus.Canceled
      ) {
        throw new Error(
          'No cancelled or unpaid billing subscription found for workspace',
        );
      }

      const daysSinceSubscriptionUnpaid = differenceInDays(
        new Date(),
        lastSubscription.currentPeriodStart,
      );

      return daysSinceSubscriptionUnpaid;
    } catch {
      throw new WorkspaceCleanerException(
        `No cancelled or unpaid billing subscription found for workspace ${workspace.id} ${workspace.displayName}`,
        WorkspaceCleanerExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND,
      );
    }
  }

  async checkIfAtLeastOneWorkspaceMemberWarned(
    workspaceMembers: WorkspaceMemberWorkspaceEntity[],
    workspaceId: string,
  ) {
    for (const workspaceMember of workspaceMembers) {
      const workspaceMemberWarned = await this.userVarsService.get({
        userId: workspaceMember.userId,