1635 extensibilitytwenty cli app vars (#15143)
- Update twenty-cli to support application env variable definition - Update twenty-server to create a new `core.applicationVariable` entity to store env variables and provide env var when executing serverless function - Update twenty-front to support application environment variable value setting <img width="1044" height="660" alt="image" src="https://github.com/user-attachments/assets/24c3d323-5370-4a80-8174-fc4653cc3c22" /> <img width="1178" height="662" alt="image" src="https://github.com/user-attachments/assets/c124f423-8ed8-4246-ae5b-a9bd6672c7dc" /> <img width="1163" height="823" alt="image" src="https://github.com/user-attachments/assets/fb7425a3-facc-4895-a5eb-8a8e278e0951" /> <img width="1087" height="696" alt="image" src="https://github.com/user-attachments/assets/113da8a2-5590-433c-b1b3-5ed3137f24ca" /> <img width="1512" height="715" alt="image" src="https://github.com/user-attachments/assets/1d2110b7-301d-4f21-a45c-ddd54d6e3391" /> <img width="1287" height="581" alt="image" src="https://github.com/user-attachments/assets/353b16c6-0527-444c-87d6-51447a96cbc7" />
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
import { Catch, ExceptionFilter } from '@nestjs/common';
|
||||
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
ApplicationVariableException,
|
||||
ApplicationVariableExceptionCode,
|
||||
} from 'src/engine/core-modules/applicationVariable/application-variable.exception';
|
||||
import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
@Catch(ApplicationVariableException)
|
||||
export class ApplicationVariableExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: ApplicationVariableException) {
|
||||
switch (exception.code) {
|
||||
case ApplicationVariableExceptionCode.APPLICATION_VARIABLE_NOT_FOUND:
|
||||
throw new NotFoundError(exception);
|
||||
default:
|
||||
assertUnreachable(exception.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
|
||||
@Entity({
|
||||
name: 'applicationVariable',
|
||||
schema: 'core',
|
||||
})
|
||||
@ObjectType()
|
||||
@Unique('IDX_APPLICATION_VARIABLE_KEY_APPLICATION_ID_UNIQUE', [
|
||||
'key',
|
||||
'applicationId',
|
||||
])
|
||||
export class ApplicationVariable {
|
||||
@IDField(() => UUIDScalarType)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false, type: 'text' })
|
||||
key: string;
|
||||
|
||||
@Column({ nullable: false, type: 'text', default: '' })
|
||||
value: string;
|
||||
|
||||
@Column({ nullable: false, type: 'text', default: '' })
|
||||
description: string;
|
||||
|
||||
@Column({ nullable: false, type: 'boolean', default: false })
|
||||
isSecret: boolean;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
applicationId?: string;
|
||||
|
||||
@ManyToOne(
|
||||
() => ApplicationEntity,
|
||||
(application) => application.applicationVariables,
|
||||
{
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: 'applicationId' })
|
||||
application: Relation<ApplicationEntity> | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class ApplicationVariableException extends CustomException<ApplicationVariableExceptionCode> {}
|
||||
|
||||
export enum ApplicationVariableExceptionCode {
|
||||
APPLICATION_VARIABLE_NOT_FOUND = 'APPLICATION_VARIABLE_NOT_FOUND',
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity';
|
||||
import { ApplicationVariableService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
|
||||
import { ApplicationVariableResolver } from 'src/engine/core-modules/applicationVariable/application-variable.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [NestjsQueryTypeOrmModule.forFeature([ApplicationVariable])],
|
||||
providers: [ApplicationVariableService, ApplicationVariableResolver],
|
||||
exports: [ApplicationVariableService],
|
||||
})
|
||||
export class ApplicationVariableModule {}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { ApplicationVariableService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
|
||||
import { UpdateApplicationVariableInput } from 'src/engine/core-modules/applicationVariable/dtos/update-application-variable.input';
|
||||
import { ApplicationVariableExceptionFilter } from 'src/engine/core-modules/applicationVariable/application-variable-exception-filter';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver()
|
||||
@UseFilters(ApplicationVariableExceptionFilter)
|
||||
export class ApplicationVariableResolver {
|
||||
constructor(
|
||||
private readonly applicationVariableService: ApplicationVariableService,
|
||||
) {}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async updateOneApplicationVariable(
|
||||
@Args() { key, value, applicationId }: UpdateApplicationVariableInput,
|
||||
) {
|
||||
await this.applicationVariableService.update({ key, value, applicationId });
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { In, Not, Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity';
|
||||
|
||||
export class ApplicationVariableService {
|
||||
constructor(
|
||||
@InjectRepository(ApplicationVariable)
|
||||
private readonly applicationVariableRepository: Repository<ApplicationVariable>,
|
||||
) {}
|
||||
|
||||
async update({
|
||||
key,
|
||||
value,
|
||||
applicationId,
|
||||
}: Pick<ApplicationVariable, 'key' | 'value'> & { applicationId: string }) {
|
||||
await this.applicationVariableRepository.update(
|
||||
{ key, applicationId },
|
||||
{
|
||||
value,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async upsertManyApplicationVariables({
|
||||
env,
|
||||
applicationId,
|
||||
}: {
|
||||
env: Record<
|
||||
string,
|
||||
{
|
||||
value?: string;
|
||||
description?: string;
|
||||
isSecret: boolean;
|
||||
}
|
||||
>;
|
||||
applicationId: string;
|
||||
}) {
|
||||
for (const [key, { value, description, isSecret }] of Object.entries(env)) {
|
||||
await this.applicationVariableRepository.upsert(
|
||||
{
|
||||
key,
|
||||
value,
|
||||
description,
|
||||
isSecret,
|
||||
applicationId,
|
||||
},
|
||||
{ conflictPaths: ['key', 'applicationId'] },
|
||||
);
|
||||
}
|
||||
|
||||
await this.applicationVariableRepository.delete({
|
||||
applicationId,
|
||||
key: Not(In(Object.keys(env))),
|
||||
});
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsBoolean, IsString } from 'class-validator';
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('ApplicationVariable')
|
||||
export class ApplicationVariableDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
key: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
value: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
description: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isSecret: boolean;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ArgsType()
|
||||
export class UpdateApplicationVariableInput {
|
||||
@Field(() => String, { nullable: false })
|
||||
key: string;
|
||||
|
||||
@Field(() => String, { nullable: false })
|
||||
value: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
applicationId: string;
|
||||
}
|
||||
Reference in New Issue
Block a user