modules 050219
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
AttributeDefinitionEntity,
|
||||
AttributeType,
|
||||
} from '../../entities/attribute-definition.entity';
|
||||
import { BusinessAttributeValueEntity } from '../../entities/business-attribute-value.entity';
|
||||
import { BusinessEntity } from '../../entities/business.entity';
|
||||
import { CreateBusinessAttributeValueDto } from './dto/create-business-attribute-value.dto';
|
||||
import { UpdateBusinessAttributeValueDto } from './dto/update-business-attribute-value.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BusinessAttributeValuesService {
|
||||
constructor(
|
||||
@InjectRepository(BusinessAttributeValueEntity)
|
||||
private readonly repo: Repository<BusinessAttributeValueEntity>,
|
||||
@InjectRepository(BusinessEntity)
|
||||
private readonly businessRepo: Repository<BusinessEntity>,
|
||||
@InjectRepository(AttributeDefinitionEntity)
|
||||
private readonly defRepo: Repository<AttributeDefinitionEntity>,
|
||||
) {}
|
||||
|
||||
async findAll(businessId?: string): Promise<BusinessAttributeValueEntity[]> {
|
||||
const qb = this.repo
|
||||
.createQueryBuilder('v')
|
||||
.leftJoinAndSelect('v.business', 'business')
|
||||
.leftJoinAndSelect('v.attributeDefinition', 'def')
|
||||
.orderBy('v.createdAt', 'DESC');
|
||||
|
||||
if (businessId) {
|
||||
qb.andWhere('v.businessId = :businessId', { businessId });
|
||||
}
|
||||
|
||||
return qb.getMany();
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<BusinessAttributeValueEntity> {
|
||||
const row = await this.repo.findOne({
|
||||
where: { id },
|
||||
relations: { business: true, attributeDefinition: true },
|
||||
});
|
||||
if (!row) {
|
||||
throw new NotFoundException('Business attribute value not found');
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
async create(
|
||||
dto: CreateBusinessAttributeValueDto,
|
||||
): Promise<BusinessAttributeValueEntity> {
|
||||
const business = await this.businessRepo.findOne({
|
||||
where: { id: dto.businessId },
|
||||
});
|
||||
if (!business) {
|
||||
throw new NotFoundException('Business not found');
|
||||
}
|
||||
|
||||
const def = await this.defRepo.findOne({
|
||||
where: { id: dto.attributeDefinitionId },
|
||||
});
|
||||
if (!def) {
|
||||
throw new NotFoundException('Attribute definition not found');
|
||||
}
|
||||
|
||||
if (def.categoryId !== business.categoryId) {
|
||||
throw new BadRequestException(
|
||||
'Attribute definition must belong to the same category as the business',
|
||||
);
|
||||
}
|
||||
|
||||
const dup = await this.repo.findOne({
|
||||
where: {
|
||||
businessId: dto.businessId,
|
||||
attributeDefinitionId: dto.attributeDefinitionId,
|
||||
},
|
||||
});
|
||||
if (dup) {
|
||||
throw new ConflictException(
|
||||
'This attribute is already set for this business; use PATCH instead',
|
||||
);
|
||||
}
|
||||
|
||||
const normalized = this.normalizeAndValidateValue(def, dto.value);
|
||||
|
||||
const entity = this.repo.create({
|
||||
businessId: dto.businessId,
|
||||
attributeDefinitionId: dto.attributeDefinitionId,
|
||||
value: normalized,
|
||||
});
|
||||
|
||||
return this.repo.save(entity);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
dto: UpdateBusinessAttributeValueDto,
|
||||
): Promise<BusinessAttributeValueEntity> {
|
||||
const row = await this.findOne(id);
|
||||
|
||||
const def =
|
||||
row.attributeDefinition ??
|
||||
(await this.defRepo.findOne({
|
||||
where: { id: row.attributeDefinitionId },
|
||||
}));
|
||||
|
||||
if (!def) {
|
||||
throw new NotFoundException('Attribute definition not found');
|
||||
}
|
||||
|
||||
if (dto.value !== undefined) {
|
||||
row.value = this.normalizeAndValidateValue(def, dto.value);
|
||||
}
|
||||
|
||||
return this.repo.save(row);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const result = await this.repo.delete(id);
|
||||
if (!result.affected) {
|
||||
throw new NotFoundException('Business attribute value not found');
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeAndValidateValue(
|
||||
def: AttributeDefinitionEntity,
|
||||
raw: string | null | undefined,
|
||||
): string | null {
|
||||
const empty =
|
||||
raw === undefined || raw === null || String(raw).trim() === '';
|
||||
|
||||
if (empty) {
|
||||
if (def.required) {
|
||||
throw new BadRequestException(`value required for attribute "${def.key}"`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const s = String(raw).trim();
|
||||
|
||||
switch (def.type) {
|
||||
case AttributeType.BOOLEAN: {
|
||||
const low = s.toLowerCase();
|
||||
if (!['true', 'false', '1', '0'].includes(low)) {
|
||||
throw new BadRequestException(
|
||||
`invalid boolean for "${def.key}" (use true/false)`,
|
||||
);
|
||||
}
|
||||
return low === 'true' || low === '1' ? 'true' : 'false';
|
||||
}
|
||||
case AttributeType.NUMBER: {
|
||||
const n = Number(s);
|
||||
if (!Number.isFinite(n)) {
|
||||
throw new BadRequestException(`invalid number for "${def.key}"`);
|
||||
}
|
||||
return String(n);
|
||||
}
|
||||
case AttributeType.SELECT: {
|
||||
const opts = def.options ?? [];
|
||||
if (!opts.includes(s)) {
|
||||
throw new BadRequestException(
|
||||
`value must be one of options for "${def.key}"`,
|
||||
);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
case AttributeType.TEXT:
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user