164 lines
4.4 KiB
TypeScript
164 lines
4.4 KiB
TypeScript
import {
|
|
Injectable,
|
|
NotFoundException,
|
|
ServiceUnavailableException,
|
|
} from '@nestjs/common';
|
|
import { InjectModel } from '@nestjs/mongoose';
|
|
import { Form, FormDocument, Status } from './schemas/form.schema';
|
|
import { Model } from 'mongoose';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { UpdateFieldDto } from './dto/update-field.dto';
|
|
import { QueryFormDto } from './dto/query-form.dto';
|
|
import { FormQueryBuilder } from './helpers/form-query.builder';
|
|
import { PaginatedResponse } from 'src/interfaces/paginated-response.interface';
|
|
import { LlmService } from 'src/common/services/llm.service';
|
|
import { CreateFormDto } from './dto/create-form.dto';
|
|
|
|
// Reusable projections
|
|
const LIST_PROJECTION = {
|
|
_id: 0,
|
|
id: 1,
|
|
name: 1,
|
|
status: 1,
|
|
deletedAt: 1,
|
|
createdAt: 1,
|
|
};
|
|
const DETAIL_PROJECTION = { _id: 0, __v: 0 };
|
|
|
|
@Injectable()
|
|
export class FormService {
|
|
constructor(
|
|
@InjectModel(Form.name) private formModel: Model<FormDocument>,
|
|
private llmService: LlmService,
|
|
) {}
|
|
|
|
async createForm(formDto: CreateFormDto): Promise<Form> {
|
|
const form = await this.formModel.create({
|
|
id: uuidv4(),
|
|
...formDto,
|
|
});
|
|
return form;
|
|
}
|
|
|
|
async findAll(query: QueryFormDto): Promise<PaginatedResponse<Form>> {
|
|
const filter = FormQueryBuilder.buildFilter(query);
|
|
const sort = FormQueryBuilder.buildSort(query);
|
|
const { skip, limit } = FormQueryBuilder.buildPagination(query);
|
|
|
|
// Run count and data fetch in parallel
|
|
const [total, data] = await Promise.all([
|
|
this.formModel.countDocuments(filter),
|
|
this.formModel
|
|
.find(filter)
|
|
.select(DETAIL_PROJECTION)
|
|
.sort(sort)
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.exec(),
|
|
]);
|
|
|
|
const page = query.page ?? 1;
|
|
|
|
return {
|
|
data,
|
|
meta: {
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(total / limit),
|
|
hasNext: page * limit < total,
|
|
hasPrev: page > 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
async find(formId: string): Promise<Form> {
|
|
const form = await this.formModel
|
|
.findOne({ id: formId, deletedAt: null })
|
|
.select(DETAIL_PROJECTION)
|
|
.exec();
|
|
if (!form) throw new NotFoundException(`Form ${formId} not found`);
|
|
|
|
return form;
|
|
}
|
|
|
|
async findInterface(formId: string): Promise<string> {
|
|
const form = await this.formModel
|
|
.findOne({ id: formId, deletedAt: null })
|
|
.select(DETAIL_PROJECTION)
|
|
.exec();
|
|
|
|
if (!form) throw new NotFoundException(`Form ${formId} not found`);
|
|
|
|
try {
|
|
let tsInference = await this.llmService.generateFormInterface(form);
|
|
return tsInference;
|
|
} catch (err) {
|
|
throw new ServiceUnavailableException('llm down');
|
|
}
|
|
}
|
|
|
|
async updateField(
|
|
formId: string,
|
|
fieldId: string,
|
|
updateFieldDto: UpdateFieldDto,
|
|
): Promise<Form> {
|
|
const setPayload = Object.fromEntries(
|
|
Object.entries(updateFieldDto).map(([k, v]) => [
|
|
`fields.$[elem].${k}`,
|
|
v,
|
|
]),
|
|
);
|
|
const form = await this.formModel.findOneAndUpdate(
|
|
{ id: formId },
|
|
{ $set: setPayload },
|
|
{
|
|
new: true,
|
|
arrayFilters: [{ 'elem.id': fieldId }],
|
|
projection: DETAIL_PROJECTION,
|
|
},
|
|
);
|
|
if (!form) throw new NotFoundException(`Form ${formId} not found`);
|
|
return form;
|
|
}
|
|
|
|
async deleteField(formId: string, fieldId: string): Promise<Form> {
|
|
const form = await this.formModel.findOneAndUpdate(
|
|
{ id: formId },
|
|
{ $pull: { fields: { id: fieldId } } },
|
|
{ new: true, projection: DETAIL_PROJECTION },
|
|
);
|
|
if (!form) throw new NotFoundException(`Form ${formId} not found`);
|
|
return form;
|
|
}
|
|
|
|
async softDelete(FormId: string): Promise<Form> {
|
|
const form = await this.formModel.findOneAndUpdate(
|
|
{ id: FormId, deletedAt: null }, // prevent double deletion
|
|
{
|
|
$set: {
|
|
deletedAt: new Date(),
|
|
},
|
|
},
|
|
{ new: true, projection: DETAIL_PROJECTION },
|
|
);
|
|
|
|
if (!form) throw new NotFoundException(`Form ${FormId} not found`);
|
|
return form;
|
|
}
|
|
|
|
async restore(FormId: string): Promise<Form> {
|
|
const form = await this.formModel.findOneAndUpdate(
|
|
{ id: FormId, deletedAt: { $ne: null } }, // only restore if actually deleted
|
|
{
|
|
$set: {
|
|
deletedAt: null,
|
|
},
|
|
},
|
|
{ new: true, projection: DETAIL_PROJECTION },
|
|
);
|
|
if (!form) throw new NotFoundException(`Form ${FormId} not found`);
|
|
return form;
|
|
}
|
|
}
|