remove nestjs-query from app-token and drop unused createOneAppToken mutation (#22765)

## What

Migrates `app-token` off `@ptc-org/nestjs-query` and removes the
auto-generated `createOneAppToken` mutation, which was unused dead API
surface.

## Why

The `createOneAppToken` mutation was reachable only from the schema — no
frontend query, SDK caller, or test used it. It was also non-functional
(its input couldn't set the token `value`), a leftover from
nestjs-query's default `create.one` being left enabled. Real app tokens
(refresh, password-reset, email-verification, invitation, OAuth,
enterprise) are all created directly via the repository in ~14 services,
none of which touched this mutation.

## Notes

- ⚠️ This removes `AppToken`, `createOneAppToken`,
`CreateAppTokenInput`, and `CreateOneAppTokenInput` from the `/metadata`
schema, so the **api-breaking-changes check will flag it**


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22765?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Abdul Rahman
2026-07-10 16:14:13 +05:30
committed by GitHub
parent 9e20e2222a
commit 4ab36a630b
13 changed files with 701 additions and 980 deletions
@@ -952,14 +952,6 @@ enum WorkspaceActivationStatus {
SUSPENDED
}
type AppToken {
id: UUID!
type: String!
expiresAt: DateTime!
createdAt: DateTime!
updatedAt: DateTime!
}
type User {
id: UUID!
firstName: String!
@@ -3512,7 +3504,6 @@ type Mutation {
createPublicDomain(domain: String!, applicationId: String!): PublicDomain!
deletePublicDomain(domain: String!): Boolean!
checkPublicDomainValidRecords(domain: String!): DomainValidRecords
createOneAppToken(input: CreateOneAppTokenInput!): AppToken!
createDevelopmentApplication(universalIdentifier: String!, name: String!): DevelopmentApplication!
syncApplication(manifest: JSON!, dryRun: Boolean): WorkspaceMigration!
uploadApplicationFile(file: Upload!, applicationUniversalIdentifier: String!, fileFolder: FileFolder!, filePath: String!): File!
@@ -4682,15 +4673,6 @@ input UpdateLabPublicFeatureFlagInput {
value: Boolean!
}
input CreateOneAppTokenInput {
"""The record to create"""
appToken: CreateAppTokenInput!
}
input CreateAppTokenInput {
expiresAt: DateTime!
}
type Subscription {
onEventSubscription(eventStreamId: String!): EventSubscription
logicFunctionLogs(input: LogicFunctionLogsInput!): LogicFunctionLogs!
@@ -669,15 +669,6 @@ export type WorkspaceDiscoverability = 'PUBLIC' | 'MEMBERS_AND_INVITEES' | 'HIDD
export type WorkspaceActivationStatus = 'ONGOING_CREATION' | 'PENDING_CREATION' | 'ACTIVE' | 'INACTIVE' | 'SUSPENDED'
export interface AppToken {
id: Scalars['UUID']
type: Scalars['String']
expiresAt: Scalars['DateTime']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
__typename: 'AppToken'
}
export interface User {
id: Scalars['UUID']
firstName: Scalars['String']
@@ -3048,7 +3039,6 @@ export interface Mutation {
createPublicDomain: PublicDomain
deletePublicDomain: Scalars['Boolean']
checkPublicDomainValidRecords?: DomainValidRecords
createOneAppToken: AppToken
createDevelopmentApplication: DevelopmentApplication
syncApplication: WorkspaceMigration
uploadApplicationFile: File
@@ -3751,16 +3741,6 @@ export interface WorkspaceGenqlSelection{
__scalar?: boolean | number
}
export interface AppTokenGenqlSelection{
id?: boolean | number
type?: boolean | number
expiresAt?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface UserGenqlSelection{
id?: boolean | number
firstName?: boolean | number
@@ -6300,7 +6280,6 @@ export interface MutationGenqlSelection{
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId: Scalars['String']} })
deletePublicDomain?: { __args: {domain: Scalars['String']} }
checkPublicDomainValidRecords?: (DomainValidRecordsGenqlSelection & { __args: {domain: Scalars['String']} })
createOneAppToken?: (AppTokenGenqlSelection & { __args: {input: CreateOneAppTokenInput} })
createDevelopmentApplication?: (DevelopmentApplicationGenqlSelection & { __args: {universalIdentifier: Scalars['String'], name: Scalars['String']} })
syncApplication?: (WorkspaceMigrationGenqlSelection & { __args: {manifest: Scalars['JSON'], dryRun?: (Scalars['Boolean'] | null)} })
uploadApplicationFile?: (FileGenqlSelection & { __args: {file: Scalars['Upload'], applicationUniversalIdentifier: Scalars['String'], fileFolder: FileFolder, filePath: Scalars['String']} })
@@ -6689,12 +6668,6 @@ export interface ConnectionParametersInput {host: Scalars['String'],port: Scalar
export interface UpdateLabPublicFeatureFlagInput {publicFeatureFlag: Scalars['String'],value: Scalars['Boolean']}
export interface CreateOneAppTokenInput {
/** The record to create */
appToken: CreateAppTokenInput}
export interface CreateAppTokenInput {expiresAt: Scalars['DateTime']}
export interface SubscriptionGenqlSelection{
onEventSubscription?: (EventSubscriptionGenqlSelection & { __args: {eventStreamId: Scalars['String']} })
logicFunctionLogs?: (LogicFunctionLogsGenqlSelection & { __args: {input: LogicFunctionLogsInput} })
@@ -7011,14 +6984,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const AppToken_possibleTypes: string[] = ['AppToken']
export const isAppToken = (obj?: { __typename?: any } | null): obj is AppToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAppToken"')
return AppToken_possibleTypes.includes(obj.__typename)
}
const User_possibleTypes: string[] = ['User']
export const isUser = (obj?: { __typename?: any } | null): obj is User => {
if (!obj?.__typename) throw new Error('__typename is missing in "isUser"')
File diff suppressed because it is too large Load Diff
@@ -279,15 +279,6 @@ export type AppConnection = {
visibility: Scalars['String']['output'];
};
export type AppToken = {
__typename?: 'AppToken';
createdAt: Scalars['DateTime']['output'];
expiresAt: Scalars['DateTime']['output'];
id: Scalars['UUID']['output'];
type: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
};
export type Application = {
__typename?: 'Application';
agents: Array<Agent>;
@@ -1052,10 +1043,6 @@ export type CreateApiKeyInput = {
roleId: Scalars['UUID']['input'];
};
export type CreateAppTokenInput = {
expiresAt: Scalars['DateTime']['input'];
};
export type CreateApplicationRegistration = {
__typename?: 'CreateApplicationRegistration';
applicationRegistration: ApplicationRegistration;
@@ -1225,11 +1212,6 @@ export type CreateObjectInput = {
skipNameField?: InputMaybe<Scalars['Boolean']['input']>;
};
export type CreateOneAppTokenInput = {
/** The record to create */
appToken: CreateAppTokenInput;
};
export type CreateOneFieldMetadataInput = {
/** The record to create */
field: CreateFieldInput;
@@ -2557,7 +2539,6 @@ export type Mutation = {
createOIDCIdentityProvider: SetupSso;
createObjectEvent: Analytics;
createOneAgent: Agent;
createOneAppToken: AppToken;
createOneField: Field;
createOneIndex: Index;
createOneLogicFunction: LogicFunction;
@@ -2925,11 +2906,6 @@ export type MutationCreateOneAgentArgs = {
};
export type MutationCreateOneAppTokenArgs = {
input: CreateOneAppTokenInput;
};
export type MutationCreateOneFieldArgs = {
input: CreateOneFieldMetadataInput;
};
@@ -1,42 +0,0 @@
import {
type AutoResolverOpts,
PagingStrategies,
type ReadResolverOpts,
} from '@ptc-org/nestjs-query-graphql';
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { CreateAppTokenInput } from 'src/engine/core-modules/app-token/dtos/create-app-token.input';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
export const appTokenAutoResolverOpts: AutoResolverOpts<
// oxlint-disable-next-line typescript/no-explicit-any
any,
// oxlint-disable-next-line typescript/no-explicit-any
any,
unknown,
unknown,
// oxlint-disable-next-line typescript/no-explicit-any
ReadResolverOpts<any>,
PagingStrategies
>[] = [
{
EntityClass: AppTokenEntity,
DTOClass: AppTokenEntity,
CreateDTOClass: CreateAppTokenInput,
enableTotalCount: true,
pagingStrategy: PagingStrategies.CURSOR,
read: {
many: { disabled: true },
one: { disabled: true },
},
create: {
many: { disabled: true },
},
update: {
many: { disabled: true },
one: { disabled: true },
},
delete: { many: { disabled: true }, one: { disabled: true } },
guards: [WorkspaceAuthGuard],
},
];
@@ -1,6 +1,3 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { BeforeCreateOne, IDField } from '@ptc-org/nestjs-query-graphql';
import { isDefined } from 'twenty-shared/utils';
import {
BeforeInsert,
@@ -15,8 +12,6 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { BeforeCreateOneAppToken } from 'src/engine/core-modules/app-token/hooks/before-create-one-app-token.hook';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -32,10 +27,7 @@ export enum AppTokenType {
}
@Entity({ name: 'appToken', schema: 'core' })
@ObjectType('AppToken')
@BeforeCreateOne(BeforeCreateOneAppToken)
export class AppTokenEntity {
@IDField(() => UUIDScalarType)
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -57,14 +49,12 @@ export class AppTokenEntity {
@Column({ nullable: true, type: 'uuid' })
workspaceId: string | null;
@Field()
@Column({ nullable: false, type: 'text', default: AppTokenType.RefreshToken })
type: AppTokenType;
@Column({ nullable: true, type: 'text' })
value: string;
@Field()
@Column({ type: 'timestamptz' })
expiresAt: Date;
@@ -74,11 +64,9 @@ export class AppTokenEntity {
@Column({ nullable: true, type: 'timestamptz' })
revokedAt: Date | null;
@Field()
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@Field()
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@@ -1,19 +0,0 @@
import { Module } from '@nestjs/common';
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { appTokenAutoResolverOpts } from 'src/engine/core-modules/app-token/app-token.auto-resolver-opts';
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([AppTokenEntity])],
services: [AppTokenService],
resolvers: appTokenAutoResolverOpts,
}),
],
})
export class AppTokenModule {}
@@ -1,11 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsDate, IsNotEmpty } from 'class-validator';
@InputType()
export class CreateAppTokenInput {
@IsDate()
@IsNotEmpty()
@Field()
expiresAt: Date;
}
@@ -1,22 +0,0 @@
import {
type BeforeCreateOneHook,
type CreateOneInputType,
} from '@ptc-org/nestjs-query-graphql';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
export class BeforeCreateOneAppToken<
T extends AppTokenEntity,
> implements BeforeCreateOneHook<T> {
async run(
instance: CreateOneInputType<T>,
// oxlint-disable-next-line typescript/no-explicit-any
context: any,
): Promise<CreateOneInputType<T>> {
const userId = context?.req?.user?.id;
instance.input.userId = userId;
return instance;
}
}
@@ -1,32 +0,0 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
describe('AppTokenService', () => {
let service: AppTokenService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AppTokenService,
{
provide: getRepositoryToken(AppTokenEntity),
useValue: {
manager: {
connection: { driver: { options: { type: 'postgres' } } },
},
metadata: { columns: [] },
},
},
],
}).compile();
service = module.get<AppTokenService>(AppTokenService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
@@ -1,15 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { Repository } from 'typeorm';
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
export class AppTokenService extends TypeOrmQueryService<AppTokenEntity> {
constructor(
@InjectRepository(AppTokenEntity)
private readonly appTokenRepository: Repository<AppTokenEntity>,
) {
super(appTokenRepository);
}
}
@@ -5,7 +5,6 @@ import { CoreEntityCacheModule } from 'src/engine/core-entity-cache/core-entity-
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { ConnectionProviderOAuthController } from 'src/engine/core-modules/application/connection-provider/connection-provider-oauth.controller';
@@ -151,7 +150,6 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
GoogleAPIScopesService,
GoogleApisServiceAvailabilityService,
MicrosoftAPIsService,
AppTokenService,
AccessTokenService,
RefreshTokenService,
LoginTokenService,
@@ -6,7 +6,6 @@ import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-que
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
import { AdminPanelModule } from 'src/engine/core-modules/admin-panel/admin-panel.module';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { AppTokenModule } from 'src/engine/core-modules/app-token/app-token.module';
import { ApplicationDevelopmentModule } from 'src/engine/core-modules/application/application-development/application-development.module';
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
@@ -107,7 +106,6 @@ import { FileModule } from './file/file.module';
ApplicationUpgradeModule,
ApplicationDevelopmentModule,
MarketplaceModule,
AppTokenModule,
TimelineMessagingModule,
TimelineCalendarEventModule,
UserModule,