nitin
2025-01-21 19:00:59 +05:30
committed by GitHub
parent 86b0a7952b
commit 50f36e345e
31 changed files with 710 additions and 6 deletions

View File

@ -0,0 +1,16 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsBoolean, IsNotEmpty } from 'class-validator';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
@InputType()
export class UpdateLabPublicFeatureFlagInput {
@Field(() => String)
@IsNotEmpty()
publicFeatureFlag: FeatureFlagKey;
@Field(() => Boolean)
@IsBoolean()
value: boolean;
}

View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { LabResolver } from './lab.resolver';
import { LabService } from './services/lab.service';
@Module({
imports: [TypeOrmModule.forFeature([FeatureFlagEntity, Workspace], 'core')],
providers: [LabService, LabResolver],
exports: [LabService],
})
export class LabModule {}

View File

@ -0,0 +1,26 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { UpdateLabPublicFeatureFlagInput } from 'src/engine/core-modules/lab/dtos/update-lab-public-feature-flag.input';
import { LabService } from 'src/engine/core-modules/lab/services/lab.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@Resolver()
@UseFilters(AuthGraphqlApiExceptionFilter)
export class LabResolver {
constructor(private labService: LabService) {}
@UseGuards(WorkspaceAuthGuard)
@Mutation(() => Boolean)
async updateLabPublicFeatureFlag(
@Args('input') input: UpdateLabPublicFeatureFlagInput,
@AuthWorkspace() workspace: Workspace,
): Promise<boolean> {
await this.labService.updateLabPublicFeatureFlag(workspace.id, input);
return true;
}
}

View File

@ -0,0 +1,76 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import {
FeatureFlagException,
FeatureFlagExceptionCode,
} from 'src/engine/core-modules/feature-flag/feature-flag.exception';
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
import { publicFeatureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/is-public-feature-flag.validate';
import { UpdateLabPublicFeatureFlagInput } from 'src/engine/core-modules/lab/dtos/update-lab-public-feature-flag.input';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
@Injectable()
export class LabService {
constructor(
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
) {}
async updateLabPublicFeatureFlag(
workspaceId: string,
payload: UpdateLabPublicFeatureFlagInput,
): Promise<void> {
featureFlagValidator.assertIsFeatureFlagKey(
payload.publicFeatureFlag,
new FeatureFlagException(
'Invalid feature flag key',
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
),
);
publicFeatureFlagValidator.assertIsPublicFeatureFlag(
FeatureFlagKey[payload.publicFeatureFlag],
new FeatureFlagException(
'Feature flag is not public',
FeatureFlagExceptionCode.FEATURE_FLAG_IS_NOT_PUBLIC,
),
);
const workspace = await this.workspaceRepository.findOne({
where: { id: workspaceId },
relations: ['featureFlags'],
});
workspaceValidator.assertIsDefinedOrThrow(
workspace,
new AuthException('Workspace not found', AuthExceptionCode.INVALID_INPUT),
);
const existingFlag = workspace.featureFlags?.find(
(flag) => flag.key === FeatureFlagKey[payload.publicFeatureFlag],
);
if (!existingFlag) {
throw new FeatureFlagException(
'Public feature flag not found',
FeatureFlagExceptionCode.FEATURE_FLAG_NOT_FOUND,
);
}
await this.featureFlagRepository.update(existingFlag.id, {
value: payload.value,
});
}
}

View File

@ -0,0 +1,48 @@
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { isPublicFeatureFlag } from './is-public-feature-flag.util';
jest.mock(
'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum',
() => ({
FeatureFlagKey: {
mockKey1: 'MOCK_KEY_1',
mockKey2: 'MOCK_KEY_2',
},
}),
);
jest.mock(
'src/engine/core-modules/feature-flag/constants/public-feature-flag.const',
() => ({
PUBLIC_FEATURE_FLAGS: [
{
key: 'MOCK_KEY_1',
metadata: {
label: 'Mock Label 1',
description: 'Mock Description 1',
imagePath: 'mock/path/1',
},
},
],
}),
);
describe('isPublicFeatureFlag', () => {
it('should return true for public flags', () => {
const publicFlag = 'MOCK_KEY_1';
expect(isPublicFeatureFlag(publicFlag as FeatureFlagKey)).toBe(true);
});
it('should return false for non-public flags', () => {
const nonPublicFlag = 'MOCK_KEY_2';
expect(isPublicFeatureFlag(nonPublicFlag as FeatureFlagKey)).toBe(false);
});
it('should return false for undefined/null', () => {
expect(isPublicFeatureFlag(undefined as any)).toBe(false);
expect(isPublicFeatureFlag(null as any)).toBe(false);
});
});

View File

@ -0,0 +1,15 @@
import {
PUBLIC_FEATURE_FLAGS,
PublicFeatureFlag,
} from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
export const isPublicFeatureFlag = (
key: FeatureFlagKey,
): key is PublicFeatureFlag['key'] => {
if (!key) {
return false;
}
return PUBLIC_FEATURE_FLAGS.some((flag) => flag.key === key);
};