update suggestion
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
TableColumn,
|
||||
TableForeignKey,
|
||||
TableIndex,
|
||||
TableUnique,
|
||||
} from 'typeorm';
|
||||
|
||||
export class MoveSuggestionsToServiceSkill1720900000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'MoveSuggestionsToServiceSkill1720900000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = await queryRunner.getTable('service_suggestions');
|
||||
|
||||
const serviceFk = table?.foreignKeys.find((fk) =>
|
||||
fk.columnNames.includes('serviceId'),
|
||||
);
|
||||
if (serviceFk) {
|
||||
await queryRunner.dropForeignKey('service_suggestions', serviceFk);
|
||||
}
|
||||
|
||||
const suggestedServiceFk = table?.foreignKeys.find((fk) =>
|
||||
fk.columnNames.includes('suggestedServiceId'),
|
||||
);
|
||||
if (suggestedServiceFk) {
|
||||
await queryRunner.dropForeignKey('service_suggestions', suggestedServiceFk);
|
||||
}
|
||||
|
||||
const uniqueConstraints = table?.uniques ?? [];
|
||||
for (const unique of uniqueConstraints) {
|
||||
await queryRunner.dropUniqueConstraint('service_suggestions', unique);
|
||||
}
|
||||
|
||||
const indexes = table?.indices ?? [];
|
||||
for (const index of indexes) {
|
||||
if (index.columnNames.includes('serviceId') || index.columnNames.includes('serviceSkillId')) {
|
||||
await queryRunner.dropIndex('service_suggestions', index);
|
||||
}
|
||||
}
|
||||
|
||||
if (table?.findColumnByName('serviceId')) {
|
||||
await queryRunner.dropColumn('service_suggestions', 'serviceId');
|
||||
}
|
||||
if (table?.findColumnByName('suggestedServiceId')) {
|
||||
await queryRunner.dropColumn('service_suggestions', 'suggestedServiceId');
|
||||
}
|
||||
|
||||
const refreshedTable = await queryRunner.getTable('service_suggestions');
|
||||
|
||||
if (!refreshedTable?.findColumnByName('serviceSkillId')) {
|
||||
await queryRunner.addColumn(
|
||||
'service_suggestions',
|
||||
new TableColumn({
|
||||
name: 'serviceSkillId',
|
||||
type: 'uuid',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (!refreshedTable?.findColumnByName('suggestedServiceSkillId')) {
|
||||
await queryRunner.addColumn(
|
||||
'service_suggestions',
|
||||
new TableColumn({
|
||||
name: 'suggestedServiceSkillId',
|
||||
type: 'uuid',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'service_suggestions',
|
||||
new TableForeignKey({
|
||||
columnNames: ['serviceSkillId'],
|
||||
referencedTableName: 'service_skills',
|
||||
referencedColumnNames: ['id'],
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'service_suggestions',
|
||||
new TableForeignKey({
|
||||
columnNames: ['suggestedServiceSkillId'],
|
||||
referencedTableName: 'service_skills',
|
||||
referencedColumnNames: ['id'],
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createUniqueConstraint(
|
||||
'service_suggestions',
|
||||
new TableUnique({
|
||||
name: 'UQ_service_suggestions_serviceSkillId_suggestedServiceSkillId',
|
||||
columnNames: ['serviceSkillId', 'suggestedServiceSkillId'],
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createIndex(
|
||||
'service_suggestions',
|
||||
new TableIndex({
|
||||
name: 'IDX_service_suggestions_serviceSkillId_isActive_displayPriority',
|
||||
columnNames: ['serviceSkillId', 'isActive', 'displayPriority'],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = await queryRunner.getTable('service_suggestions');
|
||||
|
||||
for (const fk of table?.foreignKeys ?? []) {
|
||||
if (
|
||||
fk.columnNames.includes('serviceSkillId') ||
|
||||
fk.columnNames.includes('suggestedServiceSkillId')
|
||||
) {
|
||||
await queryRunner.dropForeignKey('service_suggestions', fk);
|
||||
}
|
||||
}
|
||||
|
||||
await queryRunner.dropUniqueConstraint(
|
||||
'service_suggestions',
|
||||
'UQ_service_suggestions_serviceSkillId_suggestedServiceSkillId',
|
||||
);
|
||||
await queryRunner.dropIndex(
|
||||
'service_suggestions',
|
||||
'IDX_service_suggestions_serviceSkillId_isActive_displayPriority',
|
||||
);
|
||||
await queryRunner.dropColumn('service_suggestions', 'serviceSkillId');
|
||||
await queryRunner.dropColumn('service_suggestions', 'suggestedServiceSkillId');
|
||||
|
||||
await queryRunner.addColumn(
|
||||
'service_suggestions',
|
||||
new TableColumn({ name: 'serviceId', type: 'uuid' }),
|
||||
);
|
||||
await queryRunner.addColumn(
|
||||
'service_suggestions',
|
||||
new TableColumn({ name: 'suggestedServiceId', type: 'uuid' }),
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'service_suggestions',
|
||||
new TableForeignKey({
|
||||
columnNames: ['serviceId'],
|
||||
referencedTableName: 'services',
|
||||
referencedColumnNames: ['id'],
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'service_suggestions',
|
||||
new TableForeignKey({
|
||||
columnNames: ['suggestedServiceId'],
|
||||
referencedTableName: 'services',
|
||||
referencedColumnNames: ['id'],
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createUniqueConstraint(
|
||||
'service_suggestions',
|
||||
new TableUnique({
|
||||
name: 'UQ_service_suggestions_serviceId_suggestedServiceId',
|
||||
columnNames: ['serviceId', 'suggestedServiceId'],
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createIndex(
|
||||
'service_suggestions',
|
||||
new TableIndex({
|
||||
name: 'IDX_service_suggestions_serviceId_isActive_displayPriority',
|
||||
columnNames: ['serviceId', 'isActive', 'displayPriority'],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
MaxLength,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { SuggestionType } from '../enums/suggestion-type.enum';
|
||||
|
||||
export class CreateBulkServiceSuggestionItemDto {
|
||||
@IsUUID()
|
||||
suggestedServiceSkillId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(100)
|
||||
badgeTitle: string;
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
displayPriority: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
shortDescription?: string;
|
||||
|
||||
@IsEnum(SuggestionType)
|
||||
type: SuggestionType;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { CreateBulkServiceSuggestionItemDto } from './create-bulk-service-suggestion-item.dto';
|
||||
|
||||
export class CreateBulkServiceSuggestionsDto {
|
||||
@IsUUID()
|
||||
serviceSkillId: string;
|
||||
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateBulkServiceSuggestionItemDto)
|
||||
suggestions: CreateBulkServiceSuggestionItemDto[];
|
||||
}
|
||||
@@ -13,10 +13,10 @@ import { SuggestionType } from '../enums/suggestion-type.enum';
|
||||
|
||||
export class CreateServiceSuggestionDto {
|
||||
@IsUUID()
|
||||
serviceId: string;
|
||||
serviceSkillId: string;
|
||||
|
||||
@IsUUID()
|
||||
suggestedServiceId: string;
|
||||
suggestedServiceSkillId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IsBoolean, IsOptional, IsUUID } from 'class-validator';
|
||||
export class FindSuggestionsQueryDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
serviceId?: string;
|
||||
serviceSkillId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
|
||||
@@ -13,11 +13,11 @@ import { SuggestionType } from '../enums/suggestion-type.enum';
|
||||
export class UpdateServiceSuggestionDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
serviceId?: string;
|
||||
serviceSkillId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
suggestedServiceId?: string;
|
||||
suggestedServiceSkillId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
|
||||
@@ -9,29 +9,29 @@ import {
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { Service } from '../../services/entities/service.entity';
|
||||
import { ServiceSkill } from '../../services/entities/service-skill.entity';
|
||||
import { SuggestionType } from '../enums/suggestion-type.enum';
|
||||
|
||||
@Entity('service_suggestions')
|
||||
@Unique(['serviceId', 'suggestedServiceId'])
|
||||
@Index(['serviceId', 'isActive', 'displayPriority'])
|
||||
@Unique(['serviceSkillId', 'suggestedServiceSkillId'])
|
||||
@Index(['serviceSkillId', 'isActive', 'displayPriority'])
|
||||
export class ServiceSuggestion {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
serviceId: string;
|
||||
serviceSkillId: string;
|
||||
|
||||
@ManyToOne(() => Service, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'serviceId' })
|
||||
service: Service;
|
||||
@ManyToOne(() => ServiceSkill, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'serviceSkillId' })
|
||||
serviceSkill: ServiceSkill;
|
||||
|
||||
@Column()
|
||||
suggestedServiceId: string;
|
||||
suggestedServiceSkillId: string;
|
||||
|
||||
@ManyToOne(() => Service, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'suggestedServiceId' })
|
||||
suggestedService: Service;
|
||||
@ManyToOne(() => ServiceSkill, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'suggestedServiceSkillId' })
|
||||
suggestedServiceSkill: ServiceSkill;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@@ -20,6 +20,7 @@ import { RolesGuard } from '../auth/guards/roles.guard';
|
||||
import { AuthUser } from '../auth/interfaces/auth-user.interface';
|
||||
import { PermissionCode } from '../authorization/enums/permission-code.enum';
|
||||
import { UserRole } from '../users/enums/user-role.enum';
|
||||
import { CreateBulkServiceSuggestionsDto } from './dto/create-bulk-service-suggestions.dto';
|
||||
import { CreateServiceSuggestionDto } from './dto/create-service-suggestion.dto';
|
||||
import { FindSuggestionsQueryDto } from './dto/find-suggestions-query.dto';
|
||||
import { UpdateServiceSuggestionDto } from './dto/update-service-suggestion.dto';
|
||||
@@ -52,6 +53,17 @@ export class SuggestionsController {
|
||||
return this.suggestionsService.create(createDto, user);
|
||||
}
|
||||
|
||||
@Roles(UserRole.ADMIN)
|
||||
@Permissions(PermissionCode.SERVICES_WRITE)
|
||||
@ApiStandardOkResponse(ServiceSuggestion, { isArray: true })
|
||||
@Post('bulk')
|
||||
createBulk(
|
||||
@Body() createDto: CreateBulkServiceSuggestionsDto,
|
||||
@CurrentUser() user: AuthUser,
|
||||
) {
|
||||
return this.suggestionsService.createBulk(createDto, user);
|
||||
}
|
||||
|
||||
@Roles(UserRole.ADMIN)
|
||||
@Permissions(PermissionCode.SERVICES_READ)
|
||||
@ApiStandardOkResponse(ServiceSuggestion, { isArray: true })
|
||||
@@ -61,11 +73,13 @@ export class SuggestionsController {
|
||||
}
|
||||
|
||||
@Public()
|
||||
@ApiPublicEndpoint('Get suggestions for a service')
|
||||
@ApiPublicEndpoint('Get suggestions for a service skill')
|
||||
@ApiStandardOkResponse(ServiceSuggestion, { isArray: true })
|
||||
@Get('for-service/:serviceId')
|
||||
findForService(@Param('serviceId', ParseUUIDPipe) serviceId: string) {
|
||||
return this.suggestionsService.findForService(serviceId);
|
||||
@Get('for-service-skill/:serviceSkillId')
|
||||
findForServiceSkill(
|
||||
@Param('serviceSkillId', ParseUUIDPipe) serviceSkillId: string,
|
||||
) {
|
||||
return this.suggestionsService.findForServiceSkill(serviceSkillId);
|
||||
}
|
||||
|
||||
@Roles(UserRole.ADMIN)
|
||||
|
||||
@@ -2,14 +2,14 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuthGuardsModule } from '../auth/auth-guards.module';
|
||||
import { AuthorizationModule } from '../authorization/authorization.module';
|
||||
import { Service } from '../services/entities/service.entity';
|
||||
import { ServiceSkill } from '../services/entities/service-skill.entity';
|
||||
import { ServiceSuggestion } from './entities/service-suggestion.entity';
|
||||
import { SuggestionsController } from './suggestions.controller';
|
||||
import { SuggestionsService } from './suggestions.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ServiceSuggestion, Service]),
|
||||
TypeOrmModule.forFeature([ServiceSuggestion, ServiceSkill]),
|
||||
AuthorizationModule,
|
||||
AuthGuardsModule,
|
||||
],
|
||||
|
||||
@@ -5,22 +5,25 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { FindOptionsWhere, Repository } from 'typeorm';
|
||||
import { FindOptionsWhere, In, Repository } from 'typeorm';
|
||||
import { ServicePolicy } from '../authorization/policies/service.policy';
|
||||
import { AuthUser } from '../auth/interfaces/auth-user.interface';
|
||||
import { Service } from '../services/entities/service.entity';
|
||||
import { ServiceSkill } from '../services/entities/service-skill.entity';
|
||||
import { CreateBulkServiceSuggestionsDto } from './dto/create-bulk-service-suggestions.dto';
|
||||
import { CreateServiceSuggestionDto } from './dto/create-service-suggestion.dto';
|
||||
import { FindSuggestionsQueryDto } from './dto/find-suggestions-query.dto';
|
||||
import { UpdateServiceSuggestionDto } from './dto/update-service-suggestion.dto';
|
||||
import { ServiceSuggestion } from './entities/service-suggestion.entity';
|
||||
|
||||
const skillRelations = { service: true } as const;
|
||||
|
||||
@Injectable()
|
||||
export class SuggestionsService {
|
||||
constructor(
|
||||
@InjectRepository(ServiceSuggestion)
|
||||
private readonly suggestionsRepository: Repository<ServiceSuggestion>,
|
||||
@InjectRepository(Service)
|
||||
private readonly servicesRepository: Repository<Service>,
|
||||
@InjectRepository(ServiceSkill)
|
||||
private readonly serviceSkillsRepository: Repository<ServiceSkill>,
|
||||
private readonly servicePolicy: ServicePolicy,
|
||||
) {}
|
||||
|
||||
@@ -29,27 +32,73 @@ export class SuggestionsService {
|
||||
user: AuthUser,
|
||||
): Promise<ServiceSuggestion> {
|
||||
this.ensureCanManage(user);
|
||||
await this.validateServices(
|
||||
createDto.serviceId,
|
||||
createDto.suggestedServiceId,
|
||||
await this.validateSuggestionTargets(
|
||||
createDto.serviceSkillId,
|
||||
createDto.suggestedServiceSkillId,
|
||||
);
|
||||
const suggestion = this.suggestionsRepository.create(createDto);
|
||||
const saved = await this.suggestionsRepository.save(suggestion);
|
||||
return this.findOne(saved.id);
|
||||
}
|
||||
|
||||
findAll(query: FindSuggestionsQueryDto): Promise<ServiceSuggestion[]> {
|
||||
async createBulk(
|
||||
createDto: CreateBulkServiceSuggestionsDto,
|
||||
user: AuthUser,
|
||||
): Promise<ServiceSuggestion[]> {
|
||||
this.ensureCanManage(user);
|
||||
|
||||
const { serviceSkillId, suggestions } = createDto;
|
||||
const suggestedServiceSkillIds = suggestions.map(
|
||||
(item) => item.suggestedServiceSkillId,
|
||||
);
|
||||
|
||||
await this.validateBaseServiceSkill(serviceSkillId);
|
||||
this.ensureUniqueSuggestedSkills(suggestedServiceSkillIds);
|
||||
await this.validateSuggestedServiceSkills(
|
||||
serviceSkillId,
|
||||
suggestedServiceSkillIds,
|
||||
);
|
||||
await this.ensureNoExistingSuggestions(
|
||||
serviceSkillId,
|
||||
suggestedServiceSkillIds,
|
||||
);
|
||||
|
||||
const savedIds = await this.suggestionsRepository.manager.transaction(
|
||||
async (manager) => {
|
||||
const repository = manager.getRepository(ServiceSuggestion);
|
||||
const entities = suggestions.map((item) =>
|
||||
repository.create({ ...item, serviceSkillId }),
|
||||
);
|
||||
const saved = await repository.save(entities);
|
||||
return saved.map((suggestion) => suggestion.id);
|
||||
},
|
||||
);
|
||||
|
||||
return this.suggestionsRepository.find({
|
||||
where: this.buildWhere(query),
|
||||
relations: { service: true, suggestedService: true },
|
||||
where: { id: In(savedIds) },
|
||||
relations: {
|
||||
serviceSkill: skillRelations,
|
||||
suggestedServiceSkill: skillRelations,
|
||||
},
|
||||
order: { displayPriority: 'ASC', createdAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
findForService(serviceId: string): Promise<ServiceSuggestion[]> {
|
||||
findAll(query: FindSuggestionsQueryDto): Promise<ServiceSuggestion[]> {
|
||||
return this.suggestionsRepository.find({
|
||||
where: { serviceId, isActive: true },
|
||||
relations: { suggestedService: true },
|
||||
where: this.buildWhere(query),
|
||||
relations: {
|
||||
serviceSkill: skillRelations,
|
||||
suggestedServiceSkill: skillRelations,
|
||||
},
|
||||
order: { displayPriority: 'ASC', createdAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
findForServiceSkill(serviceSkillId: string): Promise<ServiceSuggestion[]> {
|
||||
return this.suggestionsRepository.find({
|
||||
where: { serviceSkillId, isActive: true },
|
||||
relations: { suggestedServiceSkill: skillRelations },
|
||||
order: { displayPriority: 'ASC', createdAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
@@ -57,7 +106,10 @@ export class SuggestionsService {
|
||||
async findOne(id: string): Promise<ServiceSuggestion> {
|
||||
const suggestion = await this.suggestionsRepository.findOne({
|
||||
where: { id },
|
||||
relations: { service: true, suggestedService: true },
|
||||
relations: {
|
||||
serviceSkill: skillRelations,
|
||||
suggestedServiceSkill: skillRelations,
|
||||
},
|
||||
});
|
||||
if (!suggestion) {
|
||||
throw new NotFoundException(`Suggestion #${id} not found`);
|
||||
@@ -72,11 +124,11 @@ export class SuggestionsService {
|
||||
): Promise<ServiceSuggestion> {
|
||||
this.ensureCanManage(user);
|
||||
const suggestion = await this.findOne(id);
|
||||
const serviceId = updateDto.serviceId ?? suggestion.serviceId;
|
||||
const suggestedServiceId =
|
||||
updateDto.suggestedServiceId ?? suggestion.suggestedServiceId;
|
||||
const serviceSkillId = updateDto.serviceSkillId ?? suggestion.serviceSkillId;
|
||||
const suggestedServiceSkillId =
|
||||
updateDto.suggestedServiceSkillId ?? suggestion.suggestedServiceSkillId;
|
||||
|
||||
await this.validateServices(serviceId, suggestedServiceId);
|
||||
await this.validateSuggestionTargets(serviceSkillId, suggestedServiceSkillId);
|
||||
Object.assign(suggestion, updateDto);
|
||||
await this.suggestionsRepository.save(suggestion);
|
||||
return this.findOne(id);
|
||||
@@ -93,8 +145,8 @@ export class SuggestionsService {
|
||||
): FindOptionsWhere<ServiceSuggestion> {
|
||||
const where: FindOptionsWhere<ServiceSuggestion> = {};
|
||||
|
||||
if (query.serviceId) {
|
||||
where.serviceId = query.serviceId;
|
||||
if (query.serviceSkillId) {
|
||||
where.serviceSkillId = query.serviceSkillId;
|
||||
}
|
||||
if (query.isActive !== undefined) {
|
||||
where.isActive = query.isActive;
|
||||
@@ -109,27 +161,71 @@ export class SuggestionsService {
|
||||
}
|
||||
}
|
||||
|
||||
private async validateServices(
|
||||
serviceId: string,
|
||||
suggestedServiceId: string,
|
||||
private async validateSuggestionTargets(
|
||||
serviceSkillId: string,
|
||||
suggestedServiceSkillId: string,
|
||||
): Promise<void> {
|
||||
if (serviceId === suggestedServiceId) {
|
||||
throw new BadRequestException(
|
||||
'A service cannot be suggested for itself',
|
||||
if (serviceSkillId === suggestedServiceSkillId) {
|
||||
throw new BadRequestException('A skill cannot be suggested for itself');
|
||||
}
|
||||
|
||||
await this.validateBaseServiceSkill(serviceSkillId);
|
||||
await this.validateSuggestedServiceSkills(serviceSkillId, [
|
||||
suggestedServiceSkillId,
|
||||
]);
|
||||
}
|
||||
|
||||
private async validateBaseServiceSkill(serviceSkillId: string): Promise<void> {
|
||||
const exists = await this.serviceSkillsRepository.existsBy({ id: serviceSkillId });
|
||||
if (!exists) {
|
||||
throw new NotFoundException(`Service skill #${serviceSkillId} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
private ensureUniqueSuggestedSkills(suggestedServiceSkillIds: string[]): void {
|
||||
const uniqueIds = new Set(suggestedServiceSkillIds);
|
||||
if (uniqueIds.size !== suggestedServiceSkillIds.length) {
|
||||
throw new BadRequestException('Duplicate suggested skills in batch');
|
||||
}
|
||||
}
|
||||
|
||||
private async validateSuggestedServiceSkills(
|
||||
serviceSkillId: string,
|
||||
suggestedServiceSkillIds: string[],
|
||||
): Promise<void> {
|
||||
if (suggestedServiceSkillIds.includes(serviceSkillId)) {
|
||||
throw new BadRequestException('A skill cannot be suggested for itself');
|
||||
}
|
||||
|
||||
const suggestedSkills = await this.serviceSkillsRepository.find({
|
||||
where: { id: In(suggestedServiceSkillIds) },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (suggestedSkills.length !== suggestedServiceSkillIds.length) {
|
||||
const foundIds = new Set(suggestedSkills.map((skill) => skill.id));
|
||||
const missingId = suggestedServiceSkillIds.find((id) => !foundIds.has(id));
|
||||
throw new NotFoundException(
|
||||
`Suggested service skill #${missingId} not found`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [serviceExists, suggestedExists] = await Promise.all([
|
||||
this.servicesRepository.existsBy({ id: serviceId }),
|
||||
this.servicesRepository.existsBy({ id: suggestedServiceId }),
|
||||
]);
|
||||
private async ensureNoExistingSuggestions(
|
||||
serviceSkillId: string,
|
||||
suggestedServiceSkillIds: string[],
|
||||
): Promise<void> {
|
||||
const existing = await this.suggestionsRepository.find({
|
||||
where: {
|
||||
serviceSkillId,
|
||||
suggestedServiceSkillId: In(suggestedServiceSkillIds),
|
||||
},
|
||||
select: { suggestedServiceSkillId: true },
|
||||
});
|
||||
|
||||
if (!serviceExists) {
|
||||
throw new NotFoundException(`Service #${serviceId} not found`);
|
||||
}
|
||||
if (!suggestedExists) {
|
||||
throw new NotFoundException(
|
||||
`Suggested service #${suggestedServiceId} not found`,
|
||||
if (existing.length > 0) {
|
||||
throw new BadRequestException(
|
||||
'Some suggestions already exist for this service skill',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user