Add queue management dashboard (#15202)

Adds a comprehensive queue management interface to the admin panel for
viewing and managing background jobs.

**Features:**
- Queue detail pages showing paginated job lists (50 per page)
- Filter jobs by state: completed, failed, active, waiting, delayed,
paused
- Checkbox selection with bulk actions (delete jobs, retry failed jobs)
- Per-job dropdown menu for individual retry/delete
- Expandable rows showing error messages, stack traces, and job data
- Relative timestamps with hover tooltips
- Display attempt counts on failed jobs
- Dynamic retention policy info from backend

**Changes:**
- Backend: New AdminPanelQueueService with GraphQL endpoints for job
listing, retry, and delete
- Frontend: Queue detail page with QueueJobsTable component
- Updated retention policy: completed jobs kept 4 hours, failed jobs
kept 7 days (max 1000 each)
- Added JobState enum for type safety

<img width="634" height="696" alt="Screenshot_2025-10-20_at_11 45 25"
src="https://github.com/user-attachments/assets/c67bcd27-26cf-47f5-9575-3cd5684d006b"
/>
<img width="484" height="680" alt="Screenshot_2025-10-20_at_11 45 14"
src="https://github.com/user-attachments/assets/68725cc6-b3ec-4098-99ca-f9a717d6f8f1"
/>
<img width="490" height="643" alt="Screenshot_2025-10-20_at_11 45 05"
src="https://github.com/user-attachments/assets/b68a5809-33ff-4452-b48b-741aff7f1dd6"
/>



<img width="685" height="662" alt="Screenshot 2025-10-20 at 13 15 01"
src="https://github.com/user-attachments/assets/eeb5207b-de5c-4b18-bdde-392892053dab"
/>
This commit is contained in:
Félix Malfait
2025-10-21 16:02:50 +02:00
committed by GitHub
parent 27f50c4f4e
commit f7421c5fc0
144 changed files with 2914 additions and 955 deletions
@@ -5,7 +5,7 @@ import { type Redis } from 'ioredis';
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
import { HEALTH_INDICATORS } from 'src/engine/core-modules/admin-panel/constants/health-indicators.constants';
import { type SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
import { type SystemHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
import { HEALTH_ERROR_MESSAGES } from 'src/engine/core-modules/health/constants/health-error-messages.constants';
@@ -40,6 +40,7 @@ describe('AdminPanelHealthService', () => {
appHealth = { isHealthy: jest.fn() } as any;
redisClient = {
getClient: jest.fn().mockReturnValue({} as Redis),
getQueueClient: jest.fn().mockReturnValue({} as Redis),
} as any;
twentyConfigService = { get: jest.fn() } as any;
@@ -150,7 +151,7 @@ describe('AdminPanelHealthService', () => {
const result = await service.getSystemHealthStatus();
const expected: SystemHealth = {
const expected: SystemHealthDTO = {
services: [
{
...HEALTH_INDICATORS[HealthIndicatorId.database],
@@ -372,6 +373,7 @@ describe('AdminPanelHealthService', () => {
beforeEach(() => {
jest.clearAllMocks();
redisClient.getClient.mockReturnValue({} as Redis);
redisClient.getQueueClient.mockReturnValue({} as Redis);
(Queue as unknown as jest.Mock).mockImplementation(() => mockQueue);
});
@@ -7,9 +7,9 @@ import {
import { Queue } from 'bullmq';
import { HEALTH_INDICATORS } from 'src/engine/core-modules/admin-panel/constants/health-indicators.constants';
import { type AdminPanelHealthServiceData } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-health-service-data.dto';
import { type QueueMetricsData } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data.dto';
import { type SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
import { type AdminPanelHealthServiceDataDTO } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-health-service-data.dto';
import { type QueueMetricsDataDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data.dto';
import { type SystemHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
@@ -117,7 +117,7 @@ export class AdminPanelHealthService {
async getIndicatorHealthStatus(
indicatorId: HealthIndicatorId,
): Promise<AdminPanelHealthServiceData> {
): Promise<AdminPanelHealthServiceDataDTO> {
const healthIndicator = this.healthIndicators[indicatorId];
if (!healthIndicator) {
@@ -145,7 +145,7 @@ export class AdminPanelHealthService {
return indicatorStatus;
}
async getSystemHealthStatus(): Promise<SystemHealth> {
async getSystemHealthStatus(): Promise<SystemHealthDTO> {
const [
databaseResult,
redisResult,
@@ -198,8 +198,8 @@ export class AdminPanelHealthService {
async getQueueMetrics(
queueName: MessageQueue,
timeRange: QueueMetricsTimeRange = QueueMetricsTimeRange.OneDay,
): Promise<QueueMetricsData> {
const redis = this.redisClient.getClient();
): Promise<QueueMetricsDataDTO> {
const redis = this.redisClient.getQueueClient();
const queue = new Queue(queueName, { connection: redis });
try {
@@ -325,7 +325,7 @@ export class AdminPanelHealthService {
timeRange: QueueMetricsTimeRange,
queueName: MessageQueue,
queueDetails: WorkerQueueHealth | null,
): QueueMetricsData {
): QueueMetricsDataDTO {
try {
return {
queueName,
@@ -0,0 +1,246 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { Queue } from 'bullmq';
import { type JobState as BullMQJobState } from 'bullmq/dist/esm/types';
import {
bullMQToJobStateEnum,
JobStateEnum,
jobStateEnumToBullMQ,
} from 'src/engine/core-modules/admin-panel/enums/job-state.enum';
import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { QUEUE_RETENTION } from 'src/engine/core-modules/message-queue/constants/queue-retention.constants';
import { type MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { RedisClientService } from 'src/engine/core-modules/redis-client/redis-client.service';
type JobOperationResult = {
jobId: string;
success: boolean;
error?: string;
};
@Injectable()
export class AdminPanelQueueService {
constructor(private readonly redisClient: RedisClientService) {}
async getQueueJobs(
queueName: MessageQueue,
state: JobStateEnum,
limit = 50,
offset = 0,
) {
const redis = this.redisClient.getQueueClient();
const queue = new Queue(queueName, { connection: redis });
try {
const validLimit = Math.min(Math.max(1, limit), 200);
const validOffset = Math.max(0, offset);
const start = validOffset;
const end = validOffset + validLimit - 1;
// Convert GraphQL enum to BullMQ state
const bullMQState = jobStateEnumToBullMQ[state];
const jobs = await queue.getJobs([bullMQState], start, end, false);
const transformedJobs = await Promise.all(
jobs.map(async (job) => {
const jobBullMQState = (await job.getState()) as BullMQJobState;
return {
id: job.id!,
name: job.name,
data: job.data,
state: bullMQToJobStateEnum[jobBullMQState],
timestamp: job.timestamp,
failedReason: job.failedReason,
processedOn: job.processedOn,
finishedOn: job.finishedOn,
attemptsMade: job.attemptsMade,
returnValue: job.returnValue,
logs: undefined,
stackTrace: job.stackTrace,
};
}),
);
const hasMore = jobs.length === validLimit;
const jobCounts = await queue.getJobCounts(
'completed',
'failed',
'active',
'waiting',
'delayed',
'prioritized',
'waiting-children',
);
const totalCountForState = (() => {
switch (state) {
case JobStateEnum.COMPLETED:
return jobCounts.completed ?? 0;
case JobStateEnum.FAILED:
return jobCounts.failed ?? 0;
case JobStateEnum.ACTIVE:
return jobCounts.active ?? 0;
case JobStateEnum.WAITING:
return jobCounts.waiting ?? 0;
case JobStateEnum.DELAYED:
return jobCounts.delayed ?? 0;
case JobStateEnum.PRIORITIZED:
return jobCounts.prioritized ?? 0;
case JobStateEnum.WAITING_CHILDREN:
return jobCounts['waiting-children'] ?? 0;
default:
return jobs.length;
}
})();
return {
jobs: transformedJobs,
count: jobs.length,
totalCount: totalCountForState,
hasMore,
retentionConfig: { ...QUEUE_RETENTION },
};
} catch (error) {
throw new InternalServerError(
`Failed to fetch jobs from queue ${queueName}: ${error instanceof Error ? error.message : String(error)}`,
{
userFriendlyMessage: msg`Failed to load queue jobs. Please try again later.`,
},
);
} finally {
await queue.close();
}
}
async retryJobs(
queueName: MessageQueue,
jobIds: string[],
): Promise<{
retriedCount: number;
results: JobOperationResult[];
}> {
const redis = this.redisClient.getQueueClient();
const queue = new Queue(queueName, { connection: redis });
try {
if (jobIds.length === 0) {
await queue.retryJobs({ state: 'failed' });
return { retriedCount: -1, results: [] };
}
const results: JobOperationResult[] = [];
let retriedCount = 0;
for (const jobId of jobIds) {
const job = await queue.getJob(jobId);
if (!job) {
results.push({
jobId,
success: false,
error: 'Job not found',
});
continue;
}
const state = await job.getState();
if (state !== 'failed') {
results.push({
jobId,
success: false,
error: `Job is not in failed state (current state: ${state})`,
});
continue;
}
try {
await job.retry();
retriedCount++;
results.push({
jobId,
success: true,
});
} catch (error) {
results.push({
jobId,
success: false,
error: error instanceof Error ? error.message : String(error),
});
}
}
return { retriedCount, results };
} catch (error) {
throw new InternalServerError(
`Failed to retry jobs in queue ${queueName}: ${error instanceof Error ? error.message : String(error)}`,
{
userFriendlyMessage: msg`Failed to retry jobs. Please try again later.`,
},
);
} finally {
await queue.close();
}
}
async deleteJobs(
queueName: MessageQueue,
jobIds: string[],
): Promise<{
deletedCount: number;
results: JobOperationResult[];
}> {
const redis = this.redisClient.getQueueClient();
const queue = new Queue(queueName, { connection: redis });
try {
const results: JobOperationResult[] = [];
let deletedCount = 0;
for (const jobId of jobIds) {
const job = await queue.getJob(jobId);
if (!job) {
results.push({
jobId,
success: false,
error: 'Job not found',
});
continue;
}
try {
await job.remove();
deletedCount++;
results.push({
jobId,
success: true,
});
} catch (error) {
results.push({
jobId,
success: false,
error: error instanceof Error ? error.message : String(error),
});
}
}
return { deletedCount, results };
} catch (error) {
throw new InternalServerError(
`Failed to delete jobs in queue ${queueName}: ${error instanceof Error ? error.message : String(error)}`,
{
userFriendlyMessage: msg`Failed to delete jobs. Please try again later.`,
},
);
} finally {
await queue.close();
}
}
}
@@ -3,6 +3,7 @@ import { TerminusModule } from '@nestjs/terminus';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
import { AdminPanelResolver } from 'src/engine/core-modules/admin-panel/admin-panel.resolver';
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
@@ -32,7 +33,12 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
ImpersonationModule,
PermissionsModule,
],
providers: [AdminPanelResolver, AdminPanelService, AdminPanelHealthService],
providers: [
AdminPanelResolver,
AdminPanelService,
AdminPanelHealthService,
AdminPanelQueueService,
],
exports: [AdminPanelService],
})
export class AdminPanelModule {}
@@ -1,17 +1,22 @@
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Args, Int, Mutation, Query, Resolver } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
import { ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { ConfigVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/config-variables.output';
import { SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
import { DeleteJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/delete-jobs-response.dto';
import { QueueJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-jobs-response.dto';
import { RetryJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/retry-jobs-response.dto';
import { SystemHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
import { VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
import { VersionInfoDTO } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
import { JobStateEnum } from 'src/engine/core-modules/admin-panel/enums/job-state.enum';
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { FeatureFlagException } from 'src/engine/core-modules/feature-flag/feature-flag.exception';
@@ -29,8 +34,8 @@ import { ServerLevelImpersonateGuard } from 'src/engine/guards/server-level-impe
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { AdminPanelHealthServiceData } from './dtos/admin-panel-health-service-data.dto';
import { QueueMetricsData } from './dtos/queue-metrics-data.dto';
import { AdminPanelHealthServiceDataDTO } from './dtos/admin-panel-health-service-data.dto';
import { QueueMetricsDataDTO } from './dtos/queue-metrics-data.dto';
@UsePipes(ResolverValidationPipe)
@Resolver()
@@ -43,6 +48,7 @@ export class AdminPanelResolver {
constructor(
private adminService: AdminPanelService,
private adminPanelHealthService: AdminPanelHealthService,
private adminPanelQueueService: AdminPanelQueueService,
private featureFlagService: FeatureFlagService,
private readonly twentyConfigService: TwentyConfigService,
) {}
@@ -84,34 +90,34 @@ export class AdminPanelResolver {
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Query(() => SystemHealth)
async getSystemHealthStatus(): Promise<SystemHealth> {
@Query(() => SystemHealthDTO)
async getSystemHealthStatus(): Promise<SystemHealthDTO> {
return this.adminPanelHealthService.getSystemHealthStatus();
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Query(() => AdminPanelHealthServiceData)
@Query(() => AdminPanelHealthServiceDataDTO)
async getIndicatorHealthStatus(
@Args('indicatorId', {
type: () => HealthIndicatorId,
})
indicatorId: HealthIndicatorId,
): Promise<AdminPanelHealthServiceData> {
): Promise<AdminPanelHealthServiceDataDTO> {
return this.adminPanelHealthService.getIndicatorHealthStatus(indicatorId);
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Query(() => QueueMetricsData)
@Query(() => QueueMetricsDataDTO)
async getQueueMetrics(
@Args('queueName', { type: () => String })
queueName: string,
@Args('timeRange', {
nullable: true,
defaultValue: QueueMetricsTimeRange.OneDay,
defaultValue: QueueMetricsTimeRange.OneHour,
type: () => QueueMetricsTimeRange,
})
timeRange: QueueMetricsTimeRange = QueueMetricsTimeRange.OneHour,
): Promise<QueueMetricsData> {
): Promise<QueueMetricsDataDTO> {
return await this.adminPanelHealthService.getQueueMetrics(
queueName as MessageQueue,
timeRange,
@@ -119,16 +125,16 @@ export class AdminPanelResolver {
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Query(() => VersionInfo)
async versionInfo(): Promise<VersionInfo> {
@Query(() => VersionInfoDTO)
async versionInfo(): Promise<VersionInfoDTO> {
return this.adminService.getVersionInfo();
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Query(() => ConfigVariable)
@Query(() => ConfigVariableDTO)
async getDatabaseConfigVariable(
@Args('key', { type: () => String }) key: keyof ConfigVariables,
): Promise<ConfigVariable> {
): Promise<ConfigVariableDTO> {
this.twentyConfigService.validateConfigVariableExists(key as string);
return this.adminService.getConfigVariable(key);
@@ -167,4 +173,52 @@ export class AdminPanelResolver {
return true;
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Query(() => QueueJobsResponseDTO)
async getQueueJobs(
@Args('queueName', { type: () => String })
queueName: string,
@Args('state', { type: () => JobStateEnum })
state: JobStateEnum,
@Args('limit', { type: () => Int, nullable: true, defaultValue: 50 })
limit?: number,
@Args('offset', { type: () => Int, nullable: true, defaultValue: 0 })
offset?: number,
): Promise<QueueJobsResponseDTO> {
return await this.adminPanelQueueService.getQueueJobs(
queueName as MessageQueue,
state,
limit,
offset,
);
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Mutation(() => RetryJobsResponseDTO)
async retryJobs(
@Args('queueName', { type: () => String })
queueName: string,
@Args('jobIds', { type: () => [String] })
jobIds: string[],
): Promise<RetryJobsResponseDTO> {
return await this.adminPanelQueueService.retryJobs(
queueName as MessageQueue,
jobIds,
);
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
@Mutation(() => DeleteJobsResponseDTO)
async deleteJobs(
@Args('queueName', { type: () => String })
queueName: string,
@Args('jobIds', { type: () => [String] })
jobIds: string[],
): Promise<DeleteJobsResponseDTO> {
return await this.adminPanelQueueService.deleteJobs(
queueName as MessageQueue,
jobIds,
);
}
}
@@ -7,11 +7,11 @@ import semver from 'semver';
import { Repository } from 'typeorm';
import * as z from 'zod';
import { type ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { type ConfigVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
import { type ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { type ConfigVariablesGroupDataDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
import { type ConfigVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/config-variables.output';
import { type UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
import { type VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
import { type VersionInfoDTO } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
import {
AuthException,
@@ -114,14 +114,14 @@ export class AdminPanelService {
getConfigVariablesGrouped(): ConfigVariablesOutput {
const rawEnvVars = this.twentyConfigService.getAll();
const groupedData = new Map<ConfigVariablesGroup, ConfigVariable[]>();
const groupedData = new Map<ConfigVariablesGroup, ConfigVariableDTO[]>();
for (const [varName, { value, metadata, source }] of Object.entries(
rawEnvVars,
)) {
const { group, description } = metadata;
const envVar: ConfigVariable = {
const envVar: ConfigVariableDTO = {
name: varName,
description,
value: value ?? null,
@@ -139,7 +139,9 @@ export class AdminPanelService {
groupedData.get(group)?.push(envVar);
}
const groups: ConfigVariablesGroupData[] = Array.from(groupedData.entries())
const groups: ConfigVariablesGroupDataDTO[] = Array.from(
groupedData.entries(),
)
.sort((a, b) => {
const positionA = CONFIG_VARIABLES_GROUP_METADATA[a[0]].position;
const positionB = CONFIG_VARIABLES_GROUP_METADATA[b[0]].position;
@@ -156,7 +158,7 @@ export class AdminPanelService {
return { groups };
}
getConfigVariable(key: string): ConfigVariable {
getConfigVariable(key: string): ConfigVariableDTO {
const variableWithMetadata =
this.twentyConfigService.getVariableWithMetadata(
key as keyof ConfigVariables,
@@ -180,7 +182,7 @@ export class AdminPanelService {
};
}
async getVersionInfo(): Promise<VersionInfo> {
async getVersionInfo(): Promise<VersionInfoDTO> {
const currentVersion = this.twentyConfigService.get('APP_VERSION');
try {
@@ -1,11 +1,11 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { AdminPanelWorkerQueueHealth } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-worker-queue-health.dto';
import { AdminPanelWorkerQueueHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-worker-queue-health.dto';
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
@ObjectType()
export class AdminPanelHealthServiceData {
@ObjectType('AdminPanelHealthServiceData')
export class AdminPanelHealthServiceDataDTO {
@Field(() => HealthIndicatorId)
id: HealthIndicatorId;
@@ -23,6 +23,6 @@ export class AdminPanelHealthServiceData {
@Field(() => String, { nullable: true })
details?: string;
@Field(() => [AdminPanelWorkerQueueHealth], { nullable: true })
queues?: AdminPanelWorkerQueueHealth[];
@Field(() => [AdminPanelWorkerQueueHealthDTO], { nullable: true })
queues?: AdminPanelWorkerQueueHealthDTO[];
}
@@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
@ObjectType()
export class AdminPanelWorkerQueueHealth {
@ObjectType('AdminPanelWorkerQueueHealth')
export class AdminPanelWorkerQueueHealthDTO {
@Field(() => String)
id: string;
@@ -15,8 +15,8 @@ registerEnumType(ConfigVariableType, {
name: 'ConfigVariableType',
});
@ObjectType()
export class ConfigVariable {
@ObjectType('ConfigVariable')
export class ConfigVariableDTO {
@Field()
name: string;
@@ -1,16 +1,16 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
import { ConfigVariablesGroup } from 'src/engine/core-modules/twenty-config/enums/config-variables-group.enum';
registerEnumType(ConfigVariablesGroup, {
name: 'ConfigVariablesGroup',
});
@ObjectType()
export class ConfigVariablesGroupData {
@Field(() => [ConfigVariable])
variables: ConfigVariable[];
@ObjectType('ConfigVariablesGroupData')
export class ConfigVariablesGroupDataDTO {
@Field(() => [ConfigVariableDTO])
variables: ConfigVariableDTO[];
@Field(() => ConfigVariablesGroup)
name: ConfigVariablesGroup;
@@ -1,9 +1,9 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { ConfigVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
import { ConfigVariablesGroupDataDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
@ObjectType()
@ObjectType('ConfigVariablesOutput')
export class ConfigVariablesOutput {
@Field(() => [ConfigVariablesGroupData])
groups: ConfigVariablesGroupData[];
@Field(() => [ConfigVariablesGroupDataDTO])
groups: ConfigVariablesGroupDataDTO[];
}
@@ -0,0 +1,12 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { JobOperationResultDTO } from 'src/engine/core-modules/admin-panel/dtos/job-operation-result.dto';
@ObjectType('DeleteJobsResponse')
export class DeleteJobsResponseDTO {
@Field(() => Int)
deletedCount: number;
@Field(() => [JobOperationResultDTO])
results: JobOperationResultDTO[];
}
@@ -1,13 +1,13 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { AuthToken } from 'src/engine/core-modules/auth/dto/token.entity';
import { WorkspaceUrlsAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
import { WorkspaceUrlsAndIdDTO } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
@ObjectType()
@ObjectType('ImpersonateOutput')
export class ImpersonateOutput {
@Field(() => AuthToken)
loginToken: AuthToken;
@Field(() => WorkspaceUrlsAndId)
workspace: WorkspaceUrlsAndId;
@Field(() => WorkspaceUrlsAndIdDTO)
workspace: WorkspaceUrlsAndIdDTO;
}
@@ -0,0 +1,13 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType('JobOperationResult')
export class JobOperationResultDTO {
@Field(() => String)
jobId: string;
@Field(() => Boolean)
success: boolean;
@Field(() => String, { nullable: true })
error?: string;
}
@@ -0,0 +1,44 @@
import { Field, ObjectType } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { JobStateEnum } from 'src/engine/core-modules/admin-panel/enums/job-state.enum';
@ObjectType('QueueJob')
export class QueueJobDTO {
@Field(() => String)
id: string;
@Field(() => String)
name: string;
@Field(() => GraphQLJSON, { nullable: true })
data?: object;
@Field(() => JobStateEnum)
state: JobStateEnum;
@Field(() => Number, { nullable: true })
timestamp?: number;
@Field(() => String, { nullable: true })
failedReason?: string;
@Field(() => Number, { nullable: true })
processedOn?: number;
@Field(() => Number, { nullable: true })
finishedOn?: number;
@Field(() => Number)
attemptsMade: number;
@Field(() => GraphQLJSON, { nullable: true })
returnValue?: object;
@Field(() => [String], { nullable: true })
logs?: string[];
@Field(() => [String], { nullable: true })
stackTrace?: string[];
}
@@ -0,0 +1,22 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { QueueJobDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-job.dto';
import { QueueRetentionConfigDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-retention-config.dto';
@ObjectType('QueueJobsResponse')
export class QueueJobsResponseDTO {
@Field(() => [QueueJobDTO])
jobs: QueueJobDTO[];
@Field(() => Number)
count: number;
@Field(() => Number)
totalCount: number;
@Field(() => Boolean)
hasMore: boolean;
@Field(() => QueueRetentionConfigDTO)
retentionConfig: QueueRetentionConfigDTO;
}
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class QueueMetricsDataPoint {
@ObjectType('QueueMetricsDataPoint')
export class QueueMetricsDataPointDTO {
@Field(() => Number)
x: number;
@@ -1,11 +1,11 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { QueueMetricsSeries } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-series.dto';
import { QueueMetricsSeriesDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-series.dto';
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
import { WorkerQueueMetrics } from 'src/engine/core-modules/health/types/worker-queue-metrics.type';
@ObjectType()
export class QueueMetricsData {
@ObjectType('QueueMetricsData')
export class QueueMetricsDataDTO {
@Field(() => String)
queueName: string;
@@ -18,6 +18,6 @@ export class QueueMetricsData {
@Field(() => WorkerQueueMetrics, { nullable: true })
details: WorkerQueueMetrics | null;
@Field(() => [QueueMetricsSeries])
data: QueueMetricsSeries[];
@Field(() => [QueueMetricsSeriesDTO])
data: QueueMetricsSeriesDTO[];
}
@@ -1,12 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { QueueMetricsDataPoint } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data-point.dto';
import { QueueMetricsDataPointDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data-point.dto';
@ObjectType()
export class QueueMetricsSeries {
@ObjectType('QueueMetricsSeries')
export class QueueMetricsSeriesDTO {
@Field(() => String)
id: string;
@Field(() => [QueueMetricsDataPoint])
data: QueueMetricsDataPoint[];
@Field(() => [QueueMetricsDataPointDTO])
data: QueueMetricsDataPointDTO[];
}
@@ -0,0 +1,16 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType('QueueRetentionConfig')
export class QueueRetentionConfigDTO {
@Field(() => Number)
completedMaxAge: number;
@Field(() => Number)
completedMaxCount: number;
@Field(() => Number)
failedMaxAge: number;
@Field(() => Number)
failedMaxCount: number;
}
@@ -0,0 +1,12 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { JobOperationResultDTO } from 'src/engine/core-modules/admin-panel/dtos/job-operation-result.dto';
@ObjectType('RetryJobsResponse')
export class RetryJobsResponseDTO {
@Field(() => Int)
retriedCount: number;
@Field(() => [JobOperationResultDTO])
results: JobOperationResultDTO[];
}
@@ -3,8 +3,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
@ObjectType()
export class SystemHealthService {
@ObjectType('SystemHealthService')
export class SystemHealthServiceDTO {
@Field(() => HealthIndicatorId)
id: HealthIndicatorId;
@@ -15,8 +15,8 @@ export class SystemHealthService {
status: AdminPanelHealthServiceStatus;
}
@ObjectType()
export class SystemHealth {
@Field(() => [SystemHealthService])
services: SystemHealthService[];
@ObjectType('SystemHealth')
export class SystemHealthDTO {
@Field(() => [SystemHealthServiceDTO])
services: SystemHealthServiceDTO[];
}
@@ -2,10 +2,10 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
@ObjectType()
class UserInfo {
@ObjectType('UserInfo')
class UserInfoDTO {
@Field(() => UUIDScalarType)
id: string;
@@ -19,8 +19,8 @@ class UserInfo {
lastName?: string;
}
@ObjectType()
class WorkspaceInfo {
@ObjectType('WorkspaceInfo')
class WorkspaceInfoDTO {
@Field(() => UUIDScalarType)
id: string;
@@ -36,21 +36,21 @@ class WorkspaceInfo {
@Field(() => Number)
totalUsers: number;
@Field(() => WorkspaceUrls)
workspaceUrls: WorkspaceUrls;
@Field(() => WorkspaceUrlsDTO)
workspaceUrls: WorkspaceUrlsDTO;
@Field(() => [UserInfo])
users: UserInfo[];
@Field(() => [UserInfoDTO])
users: UserInfoDTO[];
@Field(() => [FeatureFlag])
featureFlags: FeatureFlag[];
}
@ObjectType()
@ObjectType('UserLookup')
export class UserLookup {
@Field(() => UserInfo)
user: UserInfo;
@Field(() => UserInfoDTO)
user: UserInfoDTO;
@Field(() => [WorkspaceInfo])
workspaces: WorkspaceInfo[];
@Field(() => [WorkspaceInfoDTO])
workspaces: WorkspaceInfoDTO[];
}
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class VersionInfo {
@ObjectType('VersionInfo')
export class VersionInfoDTO {
@Field(() => String, { nullable: true })
currentVersion?: string;
@@ -0,0 +1,40 @@
import { registerEnumType } from '@nestjs/graphql';
import { type JobState as BullMQJobState } from 'bullmq/dist/esm/types';
export enum JobStateEnum {
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
ACTIVE = 'ACTIVE',
WAITING = 'WAITING',
DELAYED = 'DELAYED',
PRIORITIZED = 'PRIORITIZED',
WAITING_CHILDREN = 'WAITING_CHILDREN',
}
registerEnumType(JobStateEnum, {
name: 'JobState',
description: 'Job state in the queue',
});
// Mapping from GraphQL enum to BullMQ state values
export const jobStateEnumToBullMQ: Record<JobStateEnum, BullMQJobState> = {
[JobStateEnum.COMPLETED]: 'completed',
[JobStateEnum.FAILED]: 'failed',
[JobStateEnum.ACTIVE]: 'active',
[JobStateEnum.WAITING]: 'waiting',
[JobStateEnum.DELAYED]: 'delayed',
[JobStateEnum.PRIORITIZED]: 'prioritized',
[JobStateEnum.WAITING_CHILDREN]: 'waiting-children',
};
// Mapping from BullMQ state values to GraphQL enum
export const bullMQToJobStateEnum: Record<BullMQJobState, JobStateEnum> = {
completed: JobStateEnum.COMPLETED,
failed: JobStateEnum.FAILED,
active: JobStateEnum.ACTIVE,
waiting: JobStateEnum.WAITING,
delayed: JobStateEnum.DELAYED,
prioritized: JobStateEnum.PRIORITIZED,
'waiting-children': JobStateEnum.WAITING_CHILDREN,
};
@@ -8,10 +8,10 @@ import {
IdentityProviderType,
SSOIdentityProviderStatus,
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
@ObjectType()
class SSOConnection {
@ObjectType('SSOConnection')
class SSOConnectionDTO {
@Field(() => IdentityProviderType)
type: SSOConfiguration['type'];
@@ -28,7 +28,7 @@ class SSOConnection {
status: SSOConfiguration['status'];
}
@ObjectType()
@ObjectType('AvailableWorkspace')
export class AvailableWorkspace {
@Field(() => UUIDScalarType)
id: string;
@@ -45,17 +45,17 @@ export class AvailableWorkspace {
@Field(() => String, { nullable: true })
inviteHash?: string;
@Field(() => WorkspaceUrls)
workspaceUrls: WorkspaceUrls;
@Field(() => WorkspaceUrlsDTO)
workspaceUrls: WorkspaceUrlsDTO;
@Field(() => String, { nullable: true })
logo?: string;
@Field(() => [SSOConnection])
sso: SSOConnection[];
@Field(() => [SSOConnectionDTO])
sso: SSOConnectionDTO[];
}
@ObjectType()
@ObjectType('AvailableWorkspaces')
export class AvailableWorkspaces {
@Field(() => [AvailableWorkspace])
availableWorkspacesForSignIn: Array<AvailableWorkspace>;
@@ -1,14 +1,14 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { AuthToken } from './token.entity';
@ObjectType()
@ObjectType('GetLoginTokenFromEmailVerificationTokenOutput')
export class GetLoginTokenFromEmailVerificationTokenOutput {
@Field(() => AuthToken)
loginToken: AuthToken;
@Field(() => WorkspaceUrls)
workspaceUrls: WorkspaceUrls;
@Field(() => WorkspaceUrlsDTO)
workspaceUrls: WorkspaceUrlsDTO;
}
@@ -1,14 +1,14 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { WorkspaceUrlsAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
import { WorkspaceUrlsAndIdDTO } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
import { AuthToken } from './token.entity';
@ObjectType()
@ObjectType('SignUpOutput')
export class SignUpOutput {
@Field(() => AuthToken)
loginToken: AuthToken;
@Field(() => WorkspaceUrlsAndId)
workspace: WorkspaceUrlsAndId;
@Field(() => WorkspaceUrlsAndIdDTO)
workspace: WorkspaceUrlsAndIdDTO;
}
@@ -4,11 +4,11 @@ import { isDefined } from 'twenty-shared/utils';
import type Stripe from 'stripe';
import { type BillingSubscriptionSchedulePhase } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
import { type BillingSubscriptionSchedulePhaseDTO } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
export function transformStripeSubscriptionScheduleEventToDatabaseSubscriptionPhase(
schedule: Stripe.SubscriptionSchedule,
): Array<BillingSubscriptionSchedulePhase> {
): Array<BillingSubscriptionSchedulePhaseDTO> {
return schedule.phases.slice(-2).map((phase) => ({
start_date: phase.start_date,
end_date: phase.end_date,
@@ -5,7 +5,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
@ObjectType()
@ObjectType('BillingPriceLicensed')
export class BillingPriceLicensedDTO {
@Field(() => SubscriptionInterval)
recurringInterval: SubscriptionInterval;
@@ -6,7 +6,7 @@ import { BillingPriceTierDTO } from 'src/engine/core-modules/billing/dtos/billin
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
@ObjectType()
@ObjectType('BillingPriceMetered')
export class BillingPriceMeteredDTO {
@Field(() => [BillingPriceTierDTO])
tiers: BillingPriceTierDTO[];
@@ -2,7 +2,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
@ObjectType('BillingPriceTier')
export class BillingPriceTierDTO {
@Field(() => Number, { nullable: true })
upTo: number | null;
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class BillingSubscriptionSchedulePhaseItem {
@ObjectType('BillingSubscriptionSchedulePhaseItem')
export class BillingSubscriptionSchedulePhaseItemDTO {
@Field(() => String)
price: string;
@@ -9,14 +9,14 @@ export class BillingSubscriptionSchedulePhaseItem {
quantity?: number;
}
@ObjectType()
export class BillingSubscriptionSchedulePhase {
@ObjectType('BillingSubscriptionSchedulePhase')
export class BillingSubscriptionSchedulePhaseDTO {
@Field(() => Number)
start_date: number;
@Field(() => Number)
end_date: number;
@Field(() => [BillingSubscriptionSchedulePhaseItem])
items: Array<BillingSubscriptionSchedulePhaseItem>;
@Field(() => [BillingSubscriptionSchedulePhaseItemDTO])
items: Array<BillingSubscriptionSchedulePhaseItemDTO>;
}
@@ -4,7 +4,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { Min } from 'class-validator';
@ObjectType()
@ObjectType('BillingTrialPeriod')
export class BillingTrialPeriodDTO {
@Field(() => Number)
@Min(0)
@@ -3,6 +3,7 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import graphqlTypeJson from 'graphql-type-json';
import Stripe from 'stripe';
import {
Column,
@@ -16,16 +17,15 @@ import {
Relation,
UpdateDateColumn,
} from 'typeorm';
import graphqlTypeJson from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { BillingSubscriptionSchedulePhaseDTO } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
import { BillingSubscriptionItemDTO } from 'src/engine/core-modules/billing/dtos/outputs/billing-subscription-item.output';
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { BillingSubscriptionCollectionMethod } from 'src/engine/core-modules/billing/enums/billing-subscription-collection-method.enum';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
import { BillingSubscriptionSchedulePhase } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
registerEnumType(SubscriptionStatus, { name: 'SubscriptionStatus' });
registerEnumType(SubscriptionInterval, { name: 'SubscriptionInterval' });
@@ -122,9 +122,9 @@ export class BillingSubscription {
@Column({ nullable: false, type: 'jsonb', default: {} })
metadata: Stripe.Metadata;
@Field(() => [BillingSubscriptionSchedulePhase])
@Field(() => [BillingSubscriptionSchedulePhaseDTO])
@Column({ nullable: false, type: 'jsonb', default: [] })
phases: Array<BillingSubscriptionSchedulePhase>;
phases: Array<BillingSubscriptionSchedulePhaseDTO>;
@Column({ nullable: true, type: 'timestamptz' })
cancelAt: Date | null;
@@ -3,21 +3,21 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import Stripe from 'stripe';
import {
assertIsDefinedOrThrow,
findOrThrow,
isDefined,
} from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import Stripe from 'stripe';
import { BillingSubscriptionSchedulePhase } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
import { BillingSubscriptionSchedulePhaseDTO } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity';
import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
import { normalizePriceRef } from 'src/engine/core-modules/billing/utils/normalize-price-ref.utils';
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
import { BillingPriceService } from 'src/engine/core-modules/billing/services/billing-price.service';
import { normalizePriceRef } from 'src/engine/core-modules/billing/utils/normalize-price-ref.utils';
@Injectable()
export class BillingSubscriptionPhaseService {
@@ -28,7 +28,7 @@ export class BillingSubscriptionPhaseService {
private readonly billingPriceService: BillingPriceService,
) {}
async getDetailsFromPhase(phase: BillingSubscriptionSchedulePhase) {
async getDetailsFromPhase(phase: BillingSubscriptionSchedulePhaseDTO) {
const meteredPrice = await this.billingPriceRepository.findOneOrFail({
where: {
stripePriceId: findOrThrow(
@@ -3,56 +3,56 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Not, Repository } from 'typeorm';
import { differenceInDays } from 'date-fns';
import {
assertIsDefinedOrThrow,
findOrThrow,
isDefined,
} from 'twenty-shared/utils';
import { differenceInDays } from 'date-fns';
import { Not, Repository } from 'typeorm';
import type Stripe from 'stripe';
import { transformStripeSubscriptionEventToDatabaseCustomer } from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-customer.util';
import { transformStripeSubscriptionEventToDatabaseSubscriptionItem } from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription-item.util';
import {
getSubscriptionStatus,
transformStripeSubscriptionEventToDatabaseSubscription,
} from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription.util';
import {
BillingException,
BillingExceptionCode,
} from 'src/engine/core-modules/billing/billing.exception';
import { billingValidator } from 'src/engine/core-modules/billing/billing.validate';
import { BillingSubscriptionSchedulePhaseDTO } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity';
import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity';
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { type BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
import { BillingPriceService } from 'src/engine/core-modules/billing/services/billing-price.service';
import { BillingProductService } from 'src/engine/core-modules/billing/services/billing-product.service';
import { BillingSubscriptionPhaseService } from 'src/engine/core-modules/billing/services/billing-subscription-phase.service';
import { StripeCustomerService } from 'src/engine/core-modules/billing/stripe/services/stripe-customer.service';
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
import { StripeSubscriptionScheduleService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-schedule.service';
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
import { BillingMeterPrice } from 'src/engine/core-modules/billing/types/billing-meter-price.type';
import { LicensedBillingSubscriptionItem } from 'src/engine/core-modules/billing/types/billing-subscription-item.type';
import { SubscriptionWithSchedule } from 'src/engine/core-modules/billing/types/billing-subscription-with-schedule.type';
import { MeterBillingPriceTiers } from 'src/engine/core-modules/billing/types/meter-billing-price-tier.type';
import { ensureFutureStartDate } from 'src/engine/core-modules/billing/utils/ensure-future-start-date.util';
import { getOppositeInterval } from 'src/engine/core-modules/billing/utils/get-opposite-interval';
import { getOppositePlan } from 'src/engine/core-modules/billing/utils/get-opposite-plan';
import { getPlanKeyFromSubscription } from 'src/engine/core-modules/billing/utils/get-plan-key-from-subscription.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { BillingUsageType } from 'src/engine/core-modules/billing/enums/billing-usage-type.enum';
import { LicensedBillingSubscriptionItem } from 'src/engine/core-modules/billing/types/billing-subscription-item.type';
import { BillingSubscriptionPhaseService } from 'src/engine/core-modules/billing/services/billing-subscription-phase.service';
import { BillingSubscriptionSchedulePhase } from 'src/engine/core-modules/billing/dtos/billing-subscription-schedule-phase.dto';
import { getOppositeInterval } from 'src/engine/core-modules/billing/utils/get-opposite-interval';
import { billingValidator } from 'src/engine/core-modules/billing/billing.validate';
import { MeterBillingPriceTiers } from 'src/engine/core-modules/billing/types/meter-billing-price-tier.type';
import { BillingMeterPrice } from 'src/engine/core-modules/billing/types/billing-meter-price.type';
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { getOppositePlan } from 'src/engine/core-modules/billing/utils/get-opposite-plan';
import {
getSubscriptionStatus,
transformStripeSubscriptionEventToDatabaseSubscription,
} from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription.util';
import { transformStripeSubscriptionEventToDatabaseSubscriptionItem } from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-subscription-item.util';
import { transformStripeSubscriptionEventToDatabaseCustomer } from 'src/engine/core-modules/billing-webhook/utils/transform-stripe-subscription-event-to-database-customer.util';
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
import { SubscriptionWithSchedule } from 'src/engine/core-modules/billing/types/billing-subscription-with-schedule.type';
import { ensureFutureStartDate } from 'src/engine/core-modules/billing/utils/ensure-future-start-date.util';
import { BillingPriceService } from 'src/engine/core-modules/billing/services/billing-price.service';
@Injectable()
export class BillingSubscriptionService {
@@ -287,7 +287,7 @@ export class BillingSubscriptionService {
const currentPhaseDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
currentEditable as BillingSubscriptionSchedulePhase,
currentEditable as BillingSubscriptionSchedulePhaseDTO,
);
await this.changeMeteredPrice(
@@ -634,12 +634,12 @@ export class BillingSubscriptionService {
}
const currentPhaseDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
currentEditable as BillingSubscriptionSchedulePhase,
currentEditable as BillingSubscriptionSchedulePhaseDTO,
);
const hasNextInitially = !!nextEditable;
const nextPhaseDetailsInitial = hasNextInitially
? await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
)
: undefined;
const currentCap = (currentPhaseDetails.meteredPrice as BillingMeterPrice)
@@ -734,7 +734,7 @@ export class BillingSubscriptionService {
const hasNext = !!nextEditable;
const nextPhaseDetails = hasNext
? await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
)
: undefined;
const currentLicensedId = currentSnap
@@ -906,7 +906,7 @@ export class BillingSubscriptionService {
const currentDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
currentEditable as BillingSubscriptionSchedulePhase,
currentEditable as BillingSubscriptionSchedulePhaseDTO,
);
const { nextEditable } = await this.loadScheduleEditable(
billingSubscription.stripeSubscriptionId,
@@ -925,7 +925,7 @@ export class BillingSubscriptionService {
const nextDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
);
if (nextDetails.interval !== targetInterval) {
@@ -1000,7 +1000,7 @@ export class BillingSubscriptionService {
if (nextEditable && currentEditable) {
const reloadedNextDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
);
const mappedNext = await this.resolvePrices({
@@ -1048,7 +1048,7 @@ export class BillingSubscriptionService {
const nextDetails = hasNext
? await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
)
: undefined;
@@ -1099,7 +1099,7 @@ export class BillingSubscriptionService {
const currentDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
currentEditable as BillingSubscriptionSchedulePhase,
currentEditable as BillingSubscriptionSchedulePhaseDTO,
);
const currentPlan = currentDetails.plan.planKey;
@@ -1115,7 +1115,7 @@ export class BillingSubscriptionService {
const nextDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
);
if (nextDetails.plan.planKey !== targetPlanKey) {
@@ -1181,7 +1181,7 @@ export class BillingSubscriptionService {
if (nextEditable && currentEditable) {
const nextDetails =
await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
);
const preservedNextInterval = nextDetails?.interval ?? interval;
@@ -1233,7 +1233,7 @@ export class BillingSubscriptionService {
const nextDetails = hasNext
? await this.billingSubscriptionPhaseService.getDetailsFromPhase(
nextEditable as BillingSubscriptionSchedulePhase,
nextEditable as BillingSubscriptionSchedulePhaseDTO,
)
: undefined;
@@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType()
export class TimelineCalendarEventParticipant {
@ObjectType('TimelineCalendarEventParticipant')
export class TimelineCalendarEventParticipantDTO {
@Field(() => UUIDScalarType, { nullable: true })
personId: string | null;
@@ -1,11 +1,11 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TimelineCalendarEventParticipant } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto';
import { TimelineCalendarEventParticipantDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto';
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
@ObjectType()
class LinkMetadata {
@ObjectType('LinkMetadata')
class LinkMetadataDTO {
@Field()
label: string;
@@ -13,20 +13,20 @@ class LinkMetadata {
url: string;
}
@ObjectType()
export class LinksMetadata {
@ObjectType('LinksMetadata')
export class LinksMetadataDTO {
@Field()
primaryLinkLabel: string;
@Field()
primaryLinkUrl: string;
@Field(() => [LinkMetadata], { nullable: true })
secondaryLinks: LinkMetadata[] | null;
@Field(() => [LinkMetadataDTO], { nullable: true })
secondaryLinks: LinkMetadataDTO[] | null;
}
@ObjectType()
export class TimelineCalendarEvent {
@ObjectType('TimelineCalendarEvent')
export class TimelineCalendarEventDTO {
@Field(() => UUIDScalarType)
id: string;
@@ -54,11 +54,11 @@ export class TimelineCalendarEvent {
@Field()
conferenceSolution: string;
@Field(() => LinksMetadata)
conferenceLink: LinksMetadata;
@Field(() => LinksMetadataDTO)
conferenceLink: LinksMetadataDTO;
@Field(() => [TimelineCalendarEventParticipant])
participants: TimelineCalendarEventParticipant[];
@Field(() => [TimelineCalendarEventParticipantDTO])
participants: TimelineCalendarEventParticipantDTO[];
@Field(() => CalendarChannelVisibility)
visibility: CalendarChannelVisibility;
@@ -1,12 +1,12 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { TimelineCalendarEvent } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto';
import { TimelineCalendarEventDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto';
@ObjectType()
export class TimelineCalendarEventsWithTotal {
@ObjectType('TimelineCalendarEventsWithTotal')
export class TimelineCalendarEventsWithTotalDTO {
@Field(() => Int)
totalNumberOfCalendarEvents: number;
@Field(() => [TimelineCalendarEvent])
timelineCalendarEvents: TimelineCalendarEvent[];
@Field(() => [TimelineCalendarEventDTO])
timelineCalendarEvents: TimelineCalendarEventDTO[];
}
@@ -5,7 +5,7 @@ import { Max } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
import { TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service';
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@@ -50,13 +50,13 @@ class GetTimelineCalendarEventsFromOpportunityIdArgs {
}
@UseGuards(WorkspaceAuthGuard)
@Resolver(() => TimelineCalendarEventsWithTotal)
@Resolver(() => TimelineCalendarEventsWithTotalDTO)
export class TimelineCalendarEventResolver {
constructor(
private readonly timelineCalendarEventService: TimelineCalendarEventService,
) {}
@Query(() => TimelineCalendarEventsWithTotal)
@Query(() => TimelineCalendarEventsWithTotalDTO)
async getTimelineCalendarEventsFromPersonId(
@Args()
{ personId, page, pageSize }: GetTimelineCalendarEventsFromPersonIdArgs,
@@ -73,7 +73,7 @@ export class TimelineCalendarEventResolver {
return timelineCalendarEvents;
}
@Query(() => TimelineCalendarEventsWithTotal)
@Query(() => TimelineCalendarEventsWithTotalDTO)
async getTimelineCalendarEventsFromCompanyId(
@Args()
{ companyId, page, pageSize }: GetTimelineCalendarEventsFromCompanyIdArgs,
@@ -90,7 +90,7 @@ export class TimelineCalendarEventResolver {
return timelineCalendarEvents;
}
@Query(() => TimelineCalendarEventsWithTotal)
@Query(() => TimelineCalendarEventsWithTotalDTO)
async getTimelineCalendarEventsFromOpportunityId(
@Args()
{
@@ -5,7 +5,7 @@ import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/
import { Any } from 'typeorm';
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
import { type TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { type TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
@@ -27,7 +27,7 @@ export class TimelineCalendarEventService {
personIds: string[];
page: number;
pageSize: number;
}): Promise<TimelineCalendarEventsWithTotal> {
}): Promise<TimelineCalendarEventsWithTotalDTO> {
const offset = (page - 1) * pageSize;
const calendarEventRepository =
@@ -167,7 +167,7 @@ export class TimelineCalendarEventService {
companyId: string;
page: number;
pageSize: number;
}): Promise<TimelineCalendarEventsWithTotal> {
}): Promise<TimelineCalendarEventsWithTotalDTO> {
const personRepository =
await this.twentyORMManager.getRepository<PersonWorkspaceEntity>(
'person',
@@ -211,7 +211,7 @@ export class TimelineCalendarEventService {
opportunityId: string;
page: number;
pageSize: number;
}): Promise<TimelineCalendarEventsWithTotal> {
}): Promise<TimelineCalendarEventsWithTotalDTO> {
const opportunityRepository =
await this.twentyORMManager.getRepository<OpportunityWorkspaceEntity>(
'opportunity',
@@ -9,7 +9,7 @@ import {
import { BillingTrialPeriodDTO } from 'src/engine/core-modules/billing/dtos/billing-trial-period.dto';
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
import { AuthProvidersDTO } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
registerEnumType(FeatureFlagKey, {
name: 'FeatureFlagKey',
@@ -123,8 +123,8 @@ export class ClientConfig {
@Field(() => String, { nullable: true })
appVersion?: string;
@Field(() => AuthProviders, { nullable: false })
authProviders: AuthProviders;
@Field(() => AuthProvidersDTO, { nullable: false })
authProviders: AuthProvidersDTO;
@Field(() => Billing, { nullable: false })
billing: Billing;
@@ -24,7 +24,7 @@ export class EmailDriverFactory extends DriverFactoryBase<EmailDriverInterface>
if (driver === EmailDriver.SMTP) {
const emailConfigHash = this.getConfigGroupHash(
ConfigVariablesGroup.EmailSettings,
ConfigVariablesGroup.EMAIL_SETTINGS,
);
return `smtp|${emailConfigHash}`;
@@ -19,7 +19,7 @@ import {
import { type AwsSesClientProvider } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/providers/aws-ses-client.provider';
import { type AwsSesHandleErrorService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-handle-error.service';
import { EmailingDomainStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain';
import { type VerificationRecord } from 'src/engine/core-modules/emailing-domain/dtos/verification-record.dto';
import { type VerificationRecordDTO } from 'src/engine/core-modules/emailing-domain/dtos/verification-record.dto';
export class AwsSesDriver implements EmailingDomainDriverInterface {
private readonly logger = new Logger(AwsSesDriver.name);
@@ -127,7 +127,7 @@ export class AwsSesDriver implements EmailingDomainDriverInterface {
tenantName: string,
): Promise<{
isVerified: boolean;
verificationRecords: VerificationRecord[];
verificationRecords: VerificationRecordDTO[];
}> {
const sesClient = this.awsSesClientProvider.getSESClient();
@@ -161,7 +161,7 @@ export class AwsSesDriver implements EmailingDomainDriverInterface {
tenantName: string,
): Promise<{
isVerified: boolean;
verificationRecords: VerificationRecord[];
verificationRecords: VerificationRecordDTO[];
}> {
const sesClient = this.awsSesClientProvider.getSESClient();
@@ -227,7 +227,7 @@ export class AwsSesDriver implements EmailingDomainDriverInterface {
private buildVerificationRecords(
domain: string,
dkimTokens: string[],
): VerificationRecord[] {
): VerificationRecordDTO[] {
return dkimTokens.map((token) => ({
type: 'CNAME' as const,
key: `${token}._domainkey.${domain}`,
@@ -26,7 +26,7 @@ export class EmailingDomainDriverFactory extends DriverFactoryBase<EmailingDomai
if (driver === EmailingDomainDriver.AWS_SES) {
const awsConfigHash = this.getConfigGroupHash(
ConfigVariablesGroup.AwsSesSettings,
ConfigVariablesGroup.AWS_SES_SETTINGS,
);
return `aws-ses|${awsConfigHash}`;
@@ -7,7 +7,7 @@ import {
EmailingDomainDriver,
EmailingDomainStatus,
} from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain';
import { VerificationRecord } from 'src/engine/core-modules/emailing-domain/dtos/verification-record.dto';
import { VerificationRecordDTO } from 'src/engine/core-modules/emailing-domain/dtos/verification-record.dto';
registerEnumType(EmailingDomainDriver, {
name: 'EmailingDomainDriver',
@@ -37,8 +37,8 @@ export class EmailingDomainDto {
@Field(() => EmailingDomainStatus)
status: EmailingDomainStatus;
@Field(() => [VerificationRecord], { nullable: true })
verificationRecords: VerificationRecord[] | null;
@Field(() => [VerificationRecordDTO], { nullable: true })
verificationRecords: VerificationRecordDTO[] | null;
@Field(() => Date, { nullable: true })
verifiedAt: Date | null;
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class VerificationRecord {
@ObjectType('VerificationRecord')
export class VerificationRecordDTO {
@Field(() => String)
type: 'TXT' | 'CNAME' | 'MX';
@@ -29,7 +29,7 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
if (storageType === StorageDriverType.S_3) {
const storageConfigHash = this.getConfigGroupHash(
ConfigVariablesGroup.StorageConfig,
ConfigVariablesGroup.STORAGE_CONFIG,
);
return `s3|${storageConfigHash}`;
@@ -1,6 +1,6 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
@ObjectType('SignedFile')
export class SignedFileDTO {
@Field(() => String)
path: string;
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class AutocompleteResultDto {
@ObjectType('AutocompleteResult')
export class AutocompleteResultDTO {
@Field()
text: string;
@@ -1,7 +1,7 @@
import { Field, Float, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class LocationDto {
@ObjectType('Location')
export class LocationDTO {
@Field(() => Float, { nullable: true })
lat?: number;
@@ -9,8 +9,8 @@ export class LocationDto {
lng?: number;
}
@ObjectType()
export class PlaceDetailsResultDto {
@ObjectType('PlaceDetailsResult')
export class PlaceDetailsResultDTO {
@Field({ nullable: true })
state?: string;
@@ -23,6 +23,6 @@ export class PlaceDetailsResultDto {
@Field({ nullable: true })
country?: string;
@Field(() => LocationDto, { nullable: true })
location?: LocationDto;
@Field(() => LocationDTO, { nullable: true })
location?: LocationDTO;
}
@@ -1,8 +1,8 @@
import { UseGuards } from '@nestjs/common';
import { Args, Query, Resolver } from '@nestjs/graphql';
import { AutocompleteResultDto } from 'src/engine/core-modules/geo-map/dtos/autocomplete-result.dto';
import { PlaceDetailsResultDto } from 'src/engine/core-modules/geo-map/dtos/place-details-result.dto';
import { AutocompleteResultDTO } from 'src/engine/core-modules/geo-map/dtos/autocomplete-result.dto';
import { PlaceDetailsResultDTO } from 'src/engine/core-modules/geo-map/dtos/place-details-result.dto';
import { GeoMapService } from 'src/engine/core-modules/geo-map/services/geo-map.service';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@@ -11,7 +11,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
export class GeoMapResolver {
constructor(private readonly geoMapService: GeoMapService) {}
@Query(() => [AutocompleteResultDto])
@Query(() => [AutocompleteResultDTO])
async getAutoCompleteAddress(
@Args('address') address: string,
@Args('token') token: string,
@@ -26,7 +26,7 @@ export class GeoMapResolver {
);
}
@Query(() => PlaceDetailsResultDto)
@Query(() => PlaceDetailsResultDTO)
async getAddressDetails(
@Args('placeId') placeId: string,
@Args('token') token: string,
@@ -13,7 +13,7 @@ declare module 'graphql' {
export interface GraphQLErrorExtensions {
exception?: {
code?: string;
stacktrace?: ReadonlyArray<string>;
stackTrace?: ReadonlyArray<string>;
};
}
}
@@ -3,10 +3,10 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ImapSmtpCaldavConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
import { ImapSmtpCaldavConnectionParametersDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
@ObjectType()
export class ConnectedImapSmtpCaldavAccount {
@ObjectType('ConnectedImapSmtpCaldavAccount')
export class ConnectedImapSmtpCaldavAccountDTO {
@Field(() => UUIDScalarType)
id: string;
@@ -19,6 +19,6 @@ export class ConnectedImapSmtpCaldavAccount {
@Field(() => UUIDScalarType)
accountOwnerId: string;
@Field(() => ImapSmtpCaldavConnectionParameters, { nullable: true })
connectionParameters: ImapSmtpCaldavConnectionParameters | null;
@Field(() => ImapSmtpCaldavConnectionParametersDTO, { nullable: true })
connectionParameters: ImapSmtpCaldavConnectionParametersDTO | null;
}
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class ImapSmtpCaldavConnectionSuccess {
@ObjectType('ImapSmtpCaldavConnectionSuccess')
export class ImapSmtpCaldavConnectionSuccessDTO {
@Field(() => Boolean)
success: boolean;
@@ -41,8 +41,8 @@ export class EmailAccountConnectionParameters {
CALDAV?: ConnectionParameters;
}
@ObjectType()
export class ConnectionParametersOutput {
@ObjectType('ConnectionParametersOutput')
export class ConnectionParametersOutputDTO {
@Field(() => String)
host: string;
@@ -59,14 +59,14 @@ export class ConnectionParametersOutput {
secure?: boolean;
}
@ObjectType()
export class ImapSmtpCaldavConnectionParameters {
@Field(() => ConnectionParametersOutput, { nullable: true })
IMAP?: ConnectionParametersOutput;
@ObjectType('ImapSmtpCaldavConnectionParameters')
export class ImapSmtpCaldavConnectionParametersDTO {
@Field(() => ConnectionParametersOutputDTO, { nullable: true })
IMAP?: ConnectionParametersOutputDTO;
@Field(() => ConnectionParametersOutput, { nullable: true })
SMTP?: ConnectionParametersOutput;
@Field(() => ConnectionParametersOutputDTO, { nullable: true })
SMTP?: ConnectionParametersOutputDTO;
@Field(() => ConnectionParametersOutput, { nullable: true })
CALDAV?: ConnectionParametersOutput;
@Field(() => ConnectionParametersOutputDTO, { nullable: true })
CALDAV?: ConnectionParametersOutputDTO;
}
@@ -15,8 +15,8 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { ConnectedImapSmtpCaldavAccount } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto';
import { ImapSmtpCaldavConnectionSuccess } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection-success.dto';
import { ConnectedImapSmtpCaldavAccountDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto';
import { ImapSmtpCaldavConnectionSuccessDTO } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection-success.dto';
import { EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
import { ImapSmtpCaldavValidatorService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.service';
import { ImapSmtpCaldavService } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service';
@@ -43,12 +43,12 @@ export class ImapSmtpCaldavResolver {
private readonly mailConnectionValidatorService: ImapSmtpCaldavValidatorService,
) {}
@Query(() => ConnectedImapSmtpCaldavAccount)
@Query(() => ConnectedImapSmtpCaldavAccountDTO)
@UseGuards(WorkspaceAuthGuard)
async getConnectedImapSmtpCaldavAccount(
@Args('id', { type: () => UUIDScalarType }) id: string,
@AuthWorkspace() workspace: Workspace,
): Promise<ConnectedImapSmtpCaldavAccount> {
): Promise<ConnectedImapSmtpCaldavAccountDTO> {
const connectedAccountRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
workspace.id,
@@ -74,7 +74,7 @@ export class ImapSmtpCaldavResolver {
};
}
@Mutation(() => ImapSmtpCaldavConnectionSuccess)
@Mutation(() => ImapSmtpCaldavConnectionSuccessDTO)
@UseGuards(WorkspaceAuthGuard)
async saveImapSmtpCaldavAccount(
@Args('accountOwnerId', { type: () => UUIDScalarType })
@@ -84,7 +84,7 @@ export class ImapSmtpCaldavResolver {
connectionParameters: EmailAccountConnectionParameters,
@AuthWorkspace() workspace: Workspace,
@Args('id', { type: () => UUIDScalarType, nullable: true }) id?: string,
): Promise<ImapSmtpCaldavConnectionSuccess> {
): Promise<ImapSmtpCaldavConnectionSuccessDTO> {
const isImapSmtpCaldavFeatureFlagEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_IMAP_SMTP_CALDAV_ENABLED,
@@ -0,0 +1,6 @@
export const QUEUE_RETENTION = {
completedMaxAge: 14400, // 4 hours (4*3600s)
completedMaxCount: 1000,
failedMaxAge: 604800, // 7 days (7*24*3600s)
failedMaxCount: 1000,
};
@@ -18,6 +18,7 @@ import { type MessageQueueDriver } from 'src/engine/core-modules/message-queue/d
import { type MessageQueueJob } from 'src/engine/core-modules/message-queue/interfaces/message-queue-job.interface';
import { type MessageQueueWorkerOptions } from 'src/engine/core-modules/message-queue/interfaces/message-queue-worker-options.interface';
import { QUEUE_RETENTION } from 'src/engine/core-modules/message-queue/constants/queue-retention.constants';
import { type MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getJobKey } from 'src/engine/core-modules/message-queue/utils/get-job-key.util';
import { MESSAGE_QUEUE_PRIORITY } from 'src/engine/core-modules/message-queue/message-queue-priority.constant';
@@ -112,8 +113,14 @@ export class BullMQDriver implements MessageQueueDriver, OnModuleDestroy {
const queueOptions: JobsOptions = {
priority: options?.priority,
repeat: options?.repeat,
removeOnComplete: true,
removeOnFail: 100,
removeOnComplete: {
age: QUEUE_RETENTION.completedMaxAge,
count: QUEUE_RETENTION.completedMaxCount,
},
removeOnFail: {
age: QUEUE_RETENTION.failedMaxAge,
count: QUEUE_RETENTION.failedMaxCount,
},
};
await this.queueMap[queueName].upsertJobScheduler(
@@ -170,8 +177,14 @@ export class BullMQDriver implements MessageQueueDriver, OnModuleDestroy {
jobId: options?.id ? `${options.id}-${v4()}` : undefined, // We add V4() to id to make sure ids are uniques so we can add a waiting job when a job related with the same option.id is running
priority: options?.priority ?? MESSAGE_QUEUE_PRIORITY[queueName],
attempts: 1 + (options?.retryLimit || 0),
removeOnComplete: true,
removeOnFail: 100,
removeOnComplete: {
age: QUEUE_RETENTION.completedMaxAge,
count: QUEUE_RETENTION.completedMaxCount,
},
removeOnFail: {
age: QUEUE_RETENTION.failedMaxAge,
count: QUEUE_RETENTION.failedMaxCount,
},
delay: options?.delay,
};
@@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType()
export class TimelineThreadParticipant {
@ObjectType('TimelineThreadParticipant')
export class TimelineThreadParticipantDTO {
@Field(() => UUIDScalarType, { nullable: true })
personId: string | null;
@@ -1,11 +1,11 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TimelineThreadParticipant } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { TimelineThreadParticipantDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
@ObjectType()
export class TimelineThread {
@ObjectType('TimelineThread')
export class TimelineThreadDTO {
@Field(() => UUIDScalarType)
id: string;
@@ -16,10 +16,10 @@ export class TimelineThread {
visibility: MessageChannelVisibility;
@Field()
firstParticipant: TimelineThreadParticipant;
firstParticipant: TimelineThreadParticipantDTO;
@Field(() => [TimelineThreadParticipant])
lastTwoParticipants: TimelineThreadParticipant[];
@Field(() => [TimelineThreadParticipantDTO])
lastTwoParticipants: TimelineThreadParticipantDTO[];
@Field()
lastMessageReceivedAt: Date;
@@ -1,12 +1,12 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { TimelineThread } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
import { TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
@ObjectType()
export class TimelineThreadsWithTotal {
@ObjectType('TimelineThreadsWithTotal')
export class TimelineThreadsWithTotalDTO {
@Field(() => Int)
totalNumberOfThreads: number;
@Field(() => [TimelineThread])
timelineThreads: TimelineThread[];
@Field(() => [TimelineThreadDTO])
timelineThreads: TimelineThreadDTO[];
}
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/messaging/constants/messaging.constants';
import { type TimelineThreadsWithTotal } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto';
import { type TimelineThreadsWithTotalDTO } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto';
import { TimelineMessagingService } from 'src/engine/core-modules/messaging/services/timeline-messaging.service';
import { formatThreads } from 'src/engine/core-modules/messaging/utils/format-threads.util';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
@@ -20,7 +20,7 @@ export class GetMessagesService {
personIds: string[],
page = 1,
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
): Promise<TimelineThreadsWithTotal> {
): Promise<TimelineThreadsWithTotalDTO> {
const offset = (page - 1) * pageSize;
const { messageThreads, totalNumberOfThreads } =
@@ -67,7 +67,7 @@ export class GetMessagesService {
companyId: string,
page = 1,
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
): Promise<TimelineThreadsWithTotal> {
): Promise<TimelineThreadsWithTotalDTO> {
const personRepository =
await this.twentyORMManager.getRepository<PersonWorkspaceEntity>(
'person',
@@ -105,7 +105,7 @@ export class GetMessagesService {
opportunityId: string,
page = 1,
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
): Promise<TimelineThreadsWithTotal> {
): Promise<TimelineThreadsWithTotalDTO> {
const opportunityRepository =
await this.twentyORMManager.getRepository<OpportunityWorkspaceEntity>(
'opportunity',
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { type TimelineThread } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
import { type TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
@@ -18,7 +18,7 @@ export class TimelineMessagingService {
pageSize: number,
): Promise<{
messageThreads: Omit<
TimelineThread,
TimelineThreadDTO,
| 'firstParticipant'
| 'lastTwoParticipants'
| 'participantCount'
@@ -5,7 +5,7 @@ import { Max } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TIMELINE_THREADS_MAX_PAGE_SIZE } from 'src/engine/core-modules/messaging/constants/messaging.constants';
import { TimelineThreadsWithTotal } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto';
import { TimelineThreadsWithTotalDTO } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto';
import { GetMessagesService } from 'src/engine/core-modules/messaging/services/get-messages.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity';
@@ -55,14 +55,14 @@ class GetTimelineThreadsFromOpportunityIdArgs {
}
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
@Resolver(() => TimelineThreadsWithTotal)
@Resolver(() => TimelineThreadsWithTotalDTO)
export class TimelineMessagingResolver {
constructor(
private readonly getMessagesFromPersonIdsService: GetMessagesService,
private readonly userService: UserService,
) {}
@Query(() => TimelineThreadsWithTotal)
@Query(() => TimelineThreadsWithTotalDTO)
async getTimelineThreadsFromPersonId(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
@@ -88,7 +88,7 @@ export class TimelineMessagingResolver {
return timelineThreads;
}
@Query(() => TimelineThreadsWithTotal)
@Query(() => TimelineThreadsWithTotalDTO)
async getTimelineThreadsFromCompanyId(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
@@ -114,7 +114,7 @@ export class TimelineMessagingResolver {
return timelineThreads;
}
@Query(() => TimelineThreadsWithTotal)
@Query(() => TimelineThreadsWithTotalDTO)
async getTimelineThreadsFromOpportunityId(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
@@ -1,4 +1,4 @@
import { type TimelineThreadParticipant } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { type TimelineThreadParticipantDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { filterActiveParticipants } from 'src/engine/core-modules/messaging/utils/filter-active-participants.util';
import { formatThreadParticipant } from 'src/engine/core-modules/messaging/utils/format-thread-participant.util';
import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
@@ -6,8 +6,8 @@ import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/co
export const extractParticipantSummary = (
messageParticipants: MessageParticipantWorkspaceEntity[],
): {
firstParticipant: TimelineThreadParticipant;
lastTwoParticipants: TimelineThreadParticipant[];
firstParticipant: TimelineThreadParticipantDTO;
lastTwoParticipants: TimelineThreadParticipantDTO[];
participantCount: number;
} => {
const activeMessageParticipants =
@@ -23,7 +23,7 @@ export const extractParticipantSummary = (
threadParticipant.handle !== firstParticipant.handle,
);
const lastTwoParticipants: TimelineThreadParticipant[] = [];
const lastTwoParticipants: TimelineThreadParticipantDTO[] = [];
const lastParticipant =
activeMessageParticipantsWithoutFirstParticipant.slice(-1)[0];
@@ -1,9 +1,9 @@
import { type TimelineThreadParticipant } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { type TimelineThreadParticipantDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
export const formatThreadParticipant = (
threadParticipant: MessageParticipantWorkspaceEntity,
): TimelineThreadParticipant => ({
): TimelineThreadParticipantDTO => ({
personId: threadParticipant.personId,
workspaceMemberId: threadParticipant.workspaceMemberId,
firstName:
@@ -1,13 +1,13 @@
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
import { type TimelineThread } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
import { type TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto';
import { extractParticipantSummary } from 'src/engine/core-modules/messaging/utils/extract-participant-summary.util';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
export const formatThreads = (
threads: Omit<
TimelineThread,
TimelineThreadDTO,
| 'firstParticipant'
| 'lastTwoParticipants'
| 'participantCount'
@@ -20,7 +20,7 @@ export const formatThreads = (
threadVisibilityByThreadId: {
[key: string]: MessageChannelVisibility;
},
): TimelineThread[] => {
): TimelineThreadDTO[] => {
return threads.map((thread) => {
const visibility = threadVisibilityByThreadId[thread.id];
@@ -1,7 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class OnboardingStepSuccess {
@ObjectType('OnboardingStepSuccess')
export class OnboardingStepSuccessDTO {
@Field(() => Boolean, {
description: 'Boolean that confirms query was dispatched',
})
@@ -3,7 +3,7 @@ import { Mutation, Resolver } from '@nestjs/graphql';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { OnboardingStepSuccess } from 'src/engine/core-modules/onboarding/dtos/onboarding-step-success.dto';
import { OnboardingStepSuccessDTO } from 'src/engine/core-modules/onboarding/dtos/onboarding-step-success.dto';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -19,11 +19,11 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
export class OnboardingResolver {
constructor(private readonly onboardingService: OnboardingService) {}
@Mutation(() => OnboardingStepSuccess)
@Mutation(() => OnboardingStepSuccessDTO)
async skipSyncEmailOnboardingStep(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
): Promise<OnboardingStepSuccess> {
): Promise<OnboardingStepSuccessDTO> {
await this.onboardingService.setOnboardingConnectAccountPending({
userId: user.id,
workspaceId: workspace.id,
@@ -33,10 +33,10 @@ export class OnboardingResolver {
return { success: true };
}
@Mutation(() => OnboardingStepSuccess)
@Mutation(() => OnboardingStepSuccessDTO)
async skipBookOnboardingStep(
@AuthWorkspace() workspace: Workspace,
): Promise<OnboardingStepSuccess> {
): Promise<OnboardingStepSuccessDTO> {
await this.onboardingService.setOnboardingBookOnboardingPending({
workspaceId: workspace.id,
value: false,
File diff suppressed because it is too large Load Diff
@@ -10,75 +10,75 @@ export const CONFIG_VARIABLES_GROUP_METADATA: Record<
ConfigVariablesGroup,
GroupMetadata
> = {
[ConfigVariablesGroup.ServerConfig]: {
[ConfigVariablesGroup.SERVER_CONFIG]: {
position: 100,
description: '',
isHiddenOnLoad: false,
},
[ConfigVariablesGroup.RateLimiting]: {
[ConfigVariablesGroup.RATE_LIMITING]: {
position: 200,
description:
'We use this to limit the number of requests to the server. This is useful to prevent abuse.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.StorageConfig]: {
[ConfigVariablesGroup.STORAGE_CONFIG]: {
position: 300,
description:
'By default, file uploads are stored on the local filesystem, which is suitable for traditional servers. However, for ephemeral deployment servers, it is essential to configure the variables here to set up an S3-compatible file system. This ensures that files remain unaffected by server redeploys.',
isHiddenOnLoad: false,
},
[ConfigVariablesGroup.GoogleAuth]: {
[ConfigVariablesGroup.GOOGLE_AUTH]: {
position: 400,
description: 'Configure Google integration (login, calendar, email)',
isHiddenOnLoad: false,
},
[ConfigVariablesGroup.MicrosoftAuth]: {
[ConfigVariablesGroup.MICROSOFT_AUTH]: {
position: 500,
description: 'Configure Microsoft integration (login, calendar, email)',
isHiddenOnLoad: false,
},
[ConfigVariablesGroup.EmailSettings]: {
[ConfigVariablesGroup.EMAIL_SETTINGS]: {
position: 600,
description:
'This is used for emails that are sent by the app such as invitations to join a workspace. This is not used to email CRM contacts.',
isHiddenOnLoad: false,
},
[ConfigVariablesGroup.Logging]: {
[ConfigVariablesGroup.LOGGING]: {
position: 700,
description: '',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.ExceptionHandler]: {
[ConfigVariablesGroup.EXCEPTION_HANDLER]: {
position: 800,
description:
'By default, exceptions are sent to the logs. This should be enough for most self-hosting use-cases. For our cloud app we use Sentry.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.Metering]: {
[ConfigVariablesGroup.METERING]: {
position: 900,
description:
'By default, metrics are sent to the console. OpenTelemetry collector can be set up for self-hosting use-cases.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.Other]: {
[ConfigVariablesGroup.OTHER]: {
position: 1000,
description:
"The variables in this section are mostly used for internal purposes (running our Cloud offering), but shouldn't usually be required for a simple self-hosted instance",
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.BillingConfig]: {
[ConfigVariablesGroup.BILLING_CONFIG]: {
position: 1100,
description:
'We use Stripe in our Cloud app to charge customers. Not relevant to Self-hosters.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.CaptchaConfig]: {
[ConfigVariablesGroup.CAPTCHA_CONFIG]: {
position: 1200,
description:
'This protects critical endpoints like login and signup with a captcha to prevent bot attacks. Likely unnecessary for self-hosting scenarios.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.CloudflareConfig]: {
[ConfigVariablesGroup.CLOUDFLARE_CONFIG]: {
position: 1300,
description: '',
isHiddenOnLoad: true,
@@ -89,7 +89,7 @@ export const CONFIG_VARIABLES_GROUP_METADATA: Record<
'Configure the LLM provider and model to use for the app. This is experimental and not linked to any public feature.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.ServerlessConfig]: {
[ConfigVariablesGroup.SERVERLESS_CONFIG]: {
position: 1500,
description:
'In our multi-tenant cloud app, we offload untrusted custom code from workflows to a serverless system (Lambda) for enhanced security and scalability. Self-hosters with a single tenant can typically ignore this configuration.',
@@ -101,31 +101,31 @@ export const CONFIG_VARIABLES_GROUP_METADATA: Record<
'Configure this if you want to setup SSL on your server or full end-to-end encryption. If you just want basic HTTPS, a simple setup like Cloudflare in flexible mode might be easier.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.SupportChatConfig]: {
[ConfigVariablesGroup.SUPPORT_CHAT_CONFIG]: {
position: 1700,
description:
'We use this to setup a small support chat on the bottom left. Currently powered by Front.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.AnalyticsConfig]: {
[ConfigVariablesGroup.ANALYTICS_CONFIG]: {
position: 1800,
description:
'Were running a test to perform analytics within the app. This will evolve.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.TokensDuration]: {
[ConfigVariablesGroup.TOKENS_DURATION]: {
position: 1900,
description:
'These have been set to sensible default so you probably dont need to change them unless you have a specific use-case.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.TwoFactorAuthentication]: {
[ConfigVariablesGroup.TWO_FACTOR_AUTHENTICATION]: {
position: 2000,
description:
'These have been set to sensible default so you probably dont need to change them unless you have a specific use-case.',
isHiddenOnLoad: true,
},
[ConfigVariablesGroup.AwsSesSettings]: {
[ConfigVariablesGroup.AWS_SES_SETTINGS]: {
position: 2100,
description: 'Configure AWS SES settings for emailing domains',
isHiddenOnLoad: true,
@@ -101,7 +101,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
AUTH_PASSWORD_ENABLED: {
type: ConfigVariableType.BOOLEAN,
group: ConfigVariablesGroup.Other,
group: ConfigVariablesGroup.OTHER,
description: 'Enable or disable password authentication for users',
},
});
@@ -124,7 +124,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
CACHE_STORAGE_TTL: {
type: ConfigVariableType.NUMBER,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Time-to-live for cache storage in seconds',
},
});
@@ -147,7 +147,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
FRONTEND_URL: {
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Frontend URL',
},
});
@@ -172,7 +172,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
LOG_LEVELS: {
type: ConfigVariableType.ARRAY,
group: ConfigVariablesGroup.Logging,
group: ConfigVariablesGroup.LOGGING,
description: 'Levels of logging to be captured',
options: ['log', 'error', 'warn', 'debug', 'verbose'],
},
@@ -198,7 +198,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
NODE_ENV: {
type: ConfigVariableType.ENUM,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Node environment',
options: ['development', 'production', 'test'],
},
@@ -254,7 +254,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
CACHE_STORAGE_TTL: {
type: ConfigVariableType.NUMBER,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Time-to-live for cache storage in seconds',
},
});
@@ -281,7 +281,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
AUTH_PASSWORD_ENABLED: {
type: ConfigVariableType.BOOLEAN,
group: ConfigVariablesGroup.Other,
group: ConfigVariablesGroup.OTHER,
description: 'Enable or disable password authentication for users',
},
});
@@ -304,7 +304,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
CACHE_STORAGE_TTL: {
type: ConfigVariableType.NUMBER,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Time-to-live for cache storage in seconds',
},
});
@@ -327,7 +327,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
FRONTEND_URL: {
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Frontend URL',
},
});
@@ -352,7 +352,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
LOG_LEVELS: {
type: ConfigVariableType.ARRAY,
group: ConfigVariablesGroup.Logging,
group: ConfigVariablesGroup.LOGGING,
description: 'Levels of logging to be captured',
options: ['log', 'error', 'warn', 'debug', 'verbose'],
},
@@ -379,7 +379,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
NODE_ENV: {
type: ConfigVariableType.ENUM,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Node environment',
options: ['development', 'production', 'test'],
},
@@ -434,7 +434,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
CUSTOM_OBJECT: {
type: 'unknown-type' as ConfigVariableType,
group: ConfigVariablesGroup.Other,
group: ConfigVariablesGroup.OTHER,
description: 'Custom object',
},
});
@@ -453,7 +453,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
CACHE_STORAGE_TTL: {
type: ConfigVariableType.NUMBER,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Time-to-live for cache storage in seconds',
},
});
@@ -478,7 +478,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
AUTH_PASSWORD_ENABLED: {
type: ConfigVariableType.BOOLEAN,
group: ConfigVariablesGroup.Other,
group: ConfigVariablesGroup.OTHER,
description: 'Enable or disable password authentication for users',
},
});
@@ -501,7 +501,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
CACHE_STORAGE_TTL: {
type: ConfigVariableType.NUMBER,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Time-to-live for cache storage in seconds',
},
});
@@ -524,7 +524,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
FRONTEND_URL: {
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Frontend URL',
},
});
@@ -547,7 +547,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
LOG_LEVELS: {
type: ConfigVariableType.ARRAY,
group: ConfigVariablesGroup.Logging,
group: ConfigVariablesGroup.LOGGING,
description: 'Levels of logging to be captured',
options: ['log', 'error', 'warn', 'debug', 'verbose'],
},
@@ -571,7 +571,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
NODE_ENV: {
type: ConfigVariableType.ENUM,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Node environment',
options: ['development', 'production', 'test'],
},
@@ -595,7 +595,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
NODE_ENV: {
type: ConfigVariableType.ENUM,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Node environment',
options: ['development', 'production', 'test'],
},
@@ -630,7 +630,7 @@ describe('ConfigValueConverterService', () => {
jest.spyOn(TypedReflect, 'getMetadata').mockReturnValueOnce({
LOG_LEVELS: {
type: ConfigVariableType.ARRAY,
group: ConfigVariablesGroup.Logging,
group: ConfigVariablesGroup.LOGGING,
description: 'Levels of logging to be captured',
options: ['log', 'error', 'warn', 'debug', 'verbose'],
},
@@ -1,23 +1,23 @@
export enum ConfigVariablesGroup {
ServerConfig = 'server-config',
RateLimiting = 'rate-limiting',
StorageConfig = 'storage-config',
GoogleAuth = 'google-auth',
MicrosoftAuth = 'microsoft-auth',
EmailSettings = 'email-settings',
Logging = 'logging',
Metering = 'metering',
ExceptionHandler = 'exception-handler',
Other = 'other',
BillingConfig = 'billing-config',
CaptchaConfig = 'captcha-config',
CloudflareConfig = 'cloudflare-config',
LLM = 'llm',
ServerlessConfig = 'serverless-config',
SSL = 'ssl',
SupportChatConfig = 'support-chat-config',
AnalyticsConfig = 'audit-config',
TokensDuration = 'tokens-duration',
TwoFactorAuthentication = 'two-factor-authentication',
AwsSesSettings = 'aws-ses-settings',
SERVER_CONFIG = 'SERVER_CONFIG',
RATE_LIMITING = 'RATE_LIMITING',
STORAGE_CONFIG = 'STORAGE_CONFIG',
GOOGLE_AUTH = 'GOOGLE_AUTH',
MICROSOFT_AUTH = 'MICROSOFT_AUTH',
EMAIL_SETTINGS = 'EMAIL_SETTINGS',
LOGGING = 'LOGGING',
METERING = 'METERING',
EXCEPTION_HANDLER = 'EXCEPTION_HANDLER',
OTHER = 'OTHER',
BILLING_CONFIG = 'BILLING_CONFIG',
CAPTCHA_CONFIG = 'CAPTCHA_CONFIG',
CLOUDFLARE_CONFIG = 'CLOUDFLARE_CONFIG',
LLM = 'LLM',
SERVERLESS_CONFIG = 'SERVERLESS_CONFIG',
SSL = 'SSL',
SUPPORT_CHAT_CONFIG = 'SUPPORT_CHAT_CONFIG',
ANALYTICS_CONFIG = 'ANALYTICS_CONFIG',
TOKENS_DURATION = 'TOKENS_DURATION',
TWO_FACTOR_AUTHENTICATION = 'TWO_FACTOR_AUTHENTICATION',
AWS_SES_SETTINGS = 'AWS_SES_SETTINGS',
}
@@ -179,7 +179,7 @@ describe('ConfigStorageService', () => {
[key]: {
isSensitive: true,
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test sensitive config',
},
});
@@ -213,7 +213,7 @@ describe('ConfigStorageService', () => {
[key]: {
isSensitive: true,
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test sensitive config',
},
});
@@ -241,7 +241,7 @@ describe('ConfigStorageService', () => {
[key]: {
isSensitive: true,
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test sensitive config',
},
});
@@ -413,7 +413,7 @@ describe('ConfigStorageService', () => {
[key]: {
isSensitive: true,
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test sensitive config',
},
});
@@ -449,7 +449,7 @@ describe('ConfigStorageService', () => {
[key]: {
isSensitive: true,
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test sensitive config',
},
});
@@ -617,12 +617,12 @@ describe('ConfigStorageService', () => {
SENSITIVE_CONFIG: {
isSensitive: true,
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test sensitive config',
},
NORMAL_CONFIG: {
type: ConfigVariableType.STRING,
group: ConfigVariablesGroup.ServerConfig,
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Test normal config',
},
});
@@ -43,17 +43,17 @@ type TwentyConfigServicePrivateProps = {
const mockConfigVarMetadata = {
TEST_VAR: {
group: ConfigVariablesGroup.GoogleAuth,
group: ConfigVariablesGroup.GOOGLE_AUTH,
description: 'Test variable',
isEnvOnly: false,
},
ENV_ONLY_VAR: {
group: ConfigVariablesGroup.StorageConfig,
group: ConfigVariablesGroup.STORAGE_CONFIG,
description: 'Environment only variable',
isEnvOnly: true,
},
SENSITIVE_VAR: {
group: ConfigVariablesGroup.Logging,
group: ConfigVariablesGroup.LOGGING,
description: 'Sensitive variable',
isSensitive: true,
},
@@ -3,15 +3,15 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { FullName } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { FullNameDTO } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
@ObjectType()
export class DeletedWorkspaceMember {
@ObjectType('DeletedWorkspaceMember')
export class DeletedWorkspaceMemberDTO {
@IDField(() => UUIDScalarType)
id: string;
@Field(() => FullName)
name: FullName;
@Field(() => FullNameDTO)
name: FullNameDTO;
@Field({ nullable: false })
userEmail: string;
@@ -11,8 +11,8 @@ import {
WorkspaceMemberTimeFormatEnum,
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@ObjectType()
export class FullName {
@ObjectType('FullName')
export class FullNameDTO {
@Field({ nullable: false })
firstName: string;
@@ -20,13 +20,13 @@ export class FullName {
lastName: string;
}
@ObjectType()
export class WorkspaceMember {
@ObjectType('WorkspaceMember')
export class WorkspaceMemberDTO {
@IDField(() => UUIDScalarType)
id: string;
@Field(() => FullName)
name: FullName;
@Field(() => FullNameDTO)
name: FullNameDTO;
@Field({ nullable: false })
userEmail: string;
@@ -5,13 +5,13 @@ import { isDefined } from 'twenty-shared/utils';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { type UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { type DeletedWorkspaceMember } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
import { type WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { type DeletedWorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
import { type WorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { fromRoleEntitiesToRoleDtos } from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util';
import {
type WorkspaceMemberNumberFormatEnum,
type WorkspaceMemberDateFormatEnum,
type WorkspaceMemberNumberFormatEnum,
type WorkspaceMemberTimeFormatEnum,
type WorkspaceMemberWorkspaceEntity,
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@@ -50,7 +50,7 @@ export class WorkspaceMemberTranspiler {
userWorkspace,
workspaceMemberEntity,
userWorkspaceRoles,
}: ToWorkspaceMemberDtoArgs): WorkspaceMember {
}: ToWorkspaceMemberDtoArgs): WorkspaceMemberDTO {
const {
avatarUrl: avatarUrlFromEntity,
id,
@@ -89,7 +89,7 @@ export class WorkspaceMemberTranspiler {
roles,
calendarStartDay,
numberFormat: numberFormat as WorkspaceMemberNumberFormatEnum,
} satisfies WorkspaceMember;
} satisfies WorkspaceMemberDTO;
}
toWorkspaceMemberDtos(
@@ -103,7 +103,7 @@ export class WorkspaceMemberTranspiler {
toDeletedWorkspaceMemberDto(
workspaceMember: WorkspaceMemberWorkspaceEntity,
userWorkspaceId?: string,
): DeletedWorkspaceMember {
): DeletedWorkspaceMemberDTO {
const {
avatarUrl: avatarUrlFromEntity,
id,
@@ -127,13 +127,13 @@ export class WorkspaceMemberTranspiler {
userEmail,
avatarUrl,
userWorkspaceId: userWorkspaceId ?? null,
} satisfies DeletedWorkspaceMember;
} satisfies DeletedWorkspaceMemberDTO;
}
toDeletedWorkspaceMemberDtos(
workspaceMembers: WorkspaceMemberWorkspaceEntity[],
userWorkspaceId?: string,
): DeletedWorkspaceMember[] {
): DeletedWorkspaceMemberDTO[] {
return workspaceMembers.map((workspaceMember) =>
this.toDeletedWorkspaceMemberDto(workspaceMember, userWorkspaceId),
);
@@ -1,6 +1,7 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
import {
BeforeInsert,
BeforeUpdate,
@@ -14,14 +15,13 @@ import {
Relation,
UpdateDateColumn,
} from 'typeorm';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { WorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
registerEnumType(OnboardingStatus, {
@@ -108,8 +108,8 @@ export class User {
})
keyValuePairs: Relation<KeyValuePair[]>;
@Field(() => WorkspaceMember, { nullable: true })
workspaceMember: Relation<WorkspaceMember>;
@Field(() => WorkspaceMemberDTO, { nullable: true })
workspaceMember: Relation<WorkspaceMemberDTO>;
@Field(() => [UserWorkspace])
@OneToMany(() => UserWorkspace, (userWorkspace) => userWorkspace.user)
@@ -36,8 +36,8 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
import { buildTwoFactorAuthenticationMethodSummary } from 'src/engine/core-modules/two-factor-authentication/utils/two-factor-authentication-method.presenter';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { DeletedWorkspaceMember } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { DeletedWorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
import { WorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import {
type ToWorkspaceMemberDtoArgs,
@@ -195,13 +195,13 @@ export class UserResolver {
return Object.fromEntries(filteredMap);
}
@ResolveField(() => WorkspaceMember, {
@ResolveField(() => WorkspaceMemberDTO, {
nullable: true,
})
async workspaceMember(
@Parent() user: User,
@AuthWorkspace({ allowUndefined: true }) workspace: Workspace | undefined,
): Promise<WorkspaceMember | null> {
): Promise<WorkspaceMemberDTO | null> {
if (!workspace) return null;
const workspaceMemberEntity = await this.userService.loadWorkspaceMember(
@@ -239,13 +239,13 @@ export class UserResolver {
});
}
@ResolveField(() => [WorkspaceMember], {
@ResolveField(() => [WorkspaceMemberDTO], {
nullable: true,
})
async workspaceMembers(
@Parent() _user: User,
@AuthWorkspace({ allowUndefined: true }) workspace: Workspace | undefined,
): Promise<WorkspaceMember[]> {
): Promise<WorkspaceMemberDTO[]> {
if (!workspace) return [];
const workspaceMemberEntities = await this.userService.loadWorkspaceMembers(
@@ -307,13 +307,13 @@ export class UserResolver {
);
}
@ResolveField(() => [DeletedWorkspaceMember], {
@ResolveField(() => [DeletedWorkspaceMemberDTO], {
nullable: true,
})
async deletedWorkspaceMembers(
@Parent() _user: User,
@AuthWorkspace({ allowUndefined: true }) workspace: Workspace | undefined,
): Promise<DeletedWorkspaceMember[]> {
): Promise<DeletedWorkspaceMemberDTO[]> {
if (!workspace) return [];
const workspaceMemberEntities =
@@ -5,11 +5,11 @@ import {
IdentityProviderType,
SSOIdentityProviderStatus,
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ObjectType()
export class SSOIdentityProvider {
@ObjectType('SSOIdentityProvider')
export class SSOIdentityProviderDTO {
@Field(() => UUIDScalarType)
id: string;
@@ -26,10 +26,10 @@ export class SSOIdentityProvider {
issuer: string;
}
@ObjectType()
export class AuthProviders {
@Field(() => [SSOIdentityProvider])
sso: Array<SSOIdentityProvider>;
@ObjectType('AuthProviders')
export class AuthProvidersDTO {
@Field(() => [SSOIdentityProviderDTO])
sso: Array<SSOIdentityProviderDTO>;
@Field(() => Boolean)
google: boolean;
@@ -44,13 +44,13 @@ export class AuthProviders {
microsoft: boolean;
}
@ObjectType()
@ObjectType('PublicWorkspaceDataOutput')
export class PublicWorkspaceDataOutput {
@Field(() => UUIDScalarType)
id: string;
@Field(() => AuthProviders)
authProviders: AuthProviders;
@Field(() => AuthProvidersDTO)
authProviders: AuthProvidersDTO;
@Field(() => String, { nullable: true })
logo: Workspace['logo'];
@@ -58,6 +58,6 @@ export class PublicWorkspaceDataOutput {
@Field(() => String, { nullable: true })
displayName: Workspace['displayName'];
@Field(() => WorkspaceUrls)
workspaceUrls: WorkspaceUrls;
@Field(() => WorkspaceUrlsDTO)
workspaceUrls: WorkspaceUrlsDTO;
}
@@ -1,12 +1,12 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
@ObjectType()
export class WorkspaceUrlsAndId {
@Field(() => WorkspaceUrls)
workspaceUrls: WorkspaceUrls;
@ObjectType('WorkspaceUrlsAndId')
export class WorkspaceUrlsAndIdDTO {
@Field(() => WorkspaceUrlsDTO)
workspaceUrls: WorkspaceUrlsDTO;
@Field(() => UUIDScalarType)
id: string;
@@ -1,7 +1,7 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class WorkspaceUrls {
@ObjectType('WorkspaceUrls')
export class WorkspaceUrlsDTO {
@Field(() => String, { nullable: true })
customUrl?: string;
@@ -1,7 +1,7 @@
import { isDefined } from 'twenty-shared/utils';
import { SSOIdentityProviderStatus } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
import { type AuthProviders } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
import { type AuthProvidersDTO } from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
export const getAuthProvidersByWorkspace = ({
@@ -15,7 +15,7 @@ export const getAuthProvidersByWorkspace = ({
| 'isMicrosoftAuthEnabled'
| 'workspaceSSOIdentityProviders'
>;
systemEnabledProviders: AuthProviders;
systemEnabledProviders: AuthProvidersDTO;
}) => {
return {
google: workspace.isGoogleAuthEnabled && systemEnabledProviders.google,
@@ -39,11 +39,11 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { User } from 'src/engine/core-modules/user/user.entity';
import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-input';
import {
type AuthProviders,
type AuthProvidersDTO,
PublicWorkspaceDataOutput,
} from 'src/engine/core-modules/workspace/dtos/public-workspace-data-output';
import { UpdateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/update-workspace-input';
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
import { getAuthProvidersByWorkspace } from 'src/engine/core-modules/workspace/utils/get-auth-providers-by-workspace.util';
import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util';
@@ -293,7 +293,7 @@ export class WorkspaceResolver {
return isDefined(this.twentyConfigService.get('ENTERPRISE_KEY'));
}
@ResolveField(() => WorkspaceUrls)
@ResolveField(() => WorkspaceUrlsDTO)
workspaceUrls(@Parent() workspace: Workspace) {
return this.domainManagerService.getWorkspaceUrls(workspace);
}
@@ -334,7 +334,7 @@ export class WorkspaceResolver {
@Args('origin', { nullable: true }) origin?: string,
): Promise<PublicWorkspaceDataOutput | undefined> {
try {
const systemEnabledProviders: AuthProviders = {
const systemEnabledProviders: AuthProvidersDTO = {
google: this.twentyConfigService.get('AUTH_GOOGLE_ENABLED'),
magicLink: false,
password: this.twentyConfigService.get('AUTH_PASSWORD_ENABLED'),