Files
lislgosms/api/src/dictionaries/dictionaries.service.ts
T

297 lines
9.7 KiB
TypeScript

import { BadRequestException, Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
export interface CreatePhoneSegmentDto {
prefix: string;
carrier: string;
province?: string;
city?: string;
}
export interface PhoneSegmentListQuery {
keyword?: string;
page?: number;
pageSize?: number;
}
export interface CreatePhoneCarrierRuleDto {
carrier: string;
pattern: string;
priority?: number;
status?: string;
remark?: string;
}
export interface PageQuery {
keyword?: string;
page?: number;
pageSize?: number;
}
export interface CreateSensitiveWordDto {
word: string;
level?: string;
status?: string;
}
export interface CreateBlacklistDto {
tenantId?: string;
applicationId?: string;
phoneNumber: string;
reason?: string;
status?: string;
operatorId?: string;
}
export interface CreateDrainageFieldDto {
code: string;
name: string;
fieldType: 'string' | 'image' | 'file';
required?: boolean;
status?: string;
description?: string;
}
export interface DictionaryStatusDto {
status?: string;
operatorId?: string;
reason?: string;
}
export interface DictionaryListQuery {
tenantId?: string;
applicationId?: string;
keyword?: string;
status?: string;
enterpriseKeyword?: string;
applicationKeyword?: string;
phoneNumber?: string;
reasonKeyword?: string;
}
@Injectable()
export class DictionariesService {
constructor(private readonly prisma: PrismaService) {}
async listPhoneSegments(query: PhoneSegmentListQuery = {}) {
const page = Math.max(1, Number(query.page ?? 1));
const pageSize = Math.min(100, Math.max(1, Number(query.pageSize ?? 25)));
const keyword = query.keyword?.trim();
const where = keyword ? {
OR: [
{ prefix: { startsWith: keyword } },
{ carrier: { contains: keyword } },
{ province: { contains: keyword } },
{ city: { contains: keyword } },
],
} : undefined;
const [items, total] = await Promise.all([
this.prisma.phoneSegment.findMany({ where, orderBy: { prefix: 'asc' }, skip: (page - 1) * pageSize, take: pageSize }),
this.prisma.phoneSegment.count({ where }),
]);
return { items, total, page, pageSize };
}
createPhoneSegment(data: CreatePhoneSegmentDto) {
return this.prisma.phoneSegment.create({ data });
}
async listPhoneCarrierRules(query: PageQuery = {}) {
const page = Math.max(1, Number(query.page ?? 1));
const pageSize = Math.min(100, Math.max(1, Number(query.pageSize ?? 25)));
const where = query.keyword?.trim() ? {
OR: [
{ carrier: { contains: query.keyword.trim() } },
{ pattern: { contains: query.keyword.trim() } },
{ remark: { contains: query.keyword.trim() } },
],
} : undefined;
const [items, total] = await Promise.all([
this.prisma.phoneCarrierRule.findMany({ where, orderBy: [{ priority: 'asc' }, { createdAt: 'desc' }], skip: (page - 1) * pageSize, take: pageSize }),
this.prisma.phoneCarrierRule.count({ where }),
]);
return { items, total, page, pageSize };
}
createPhoneCarrierRule(data: CreatePhoneCarrierRuleDto) {
if (!data.carrier || !data.pattern) {
throw new BadRequestException('carrier and pattern are required');
}
try {
new RegExp(data.pattern);
} catch {
throw new BadRequestException('pattern must be a valid regular expression');
}
return this.prisma.phoneCarrierRule.create({
data: {
carrier: data.carrier,
pattern: data.pattern,
priority: data.priority ?? 100,
status: data.status ?? 'active',
remark: data.remark,
},
});
}
listSensitiveWords(query: DictionaryListQuery = {}) {
return this.prisma.sensitiveWord.findMany({
where: {
status: query.status && query.status !== 'all' ? query.status : undefined,
OR: query.keyword ? [
{ word: { contains: query.keyword } },
{ level: { contains: query.keyword } },
] : undefined,
},
orderBy: { createdAt: 'desc' },
});
}
async createSensitiveWord(data: CreateSensitiveWordDto) {
const created = await this.prisma.sensitiveWord.create({
data: {
word: data.word,
level: data.level ?? 'block',
status: data.status ?? 'active',
},
});
await this.writeOperationLog(undefined, 'sensitive_word.create', 'sensitive_word', created.id, { word: data.word });
return created;
}
async changeSensitiveWordStatus(id: string, data: DictionaryStatusDto) {
const status = data.status ?? 'active';
const updated = await this.prisma.sensitiveWord.update({ where: { id }, data: { status } });
await this.writeOperationLog(data.operatorId, `sensitive_word.${status}`, 'sensitive_word', id, { reason: data.reason });
return updated;
}
listGlobalBlacklist(query: DictionaryListQuery = {}) {
return this.prisma.globalBlacklist.findMany({
where: {
status: query.status && query.status !== 'all' ? query.status : undefined,
OR: query.keyword ? [
{ phoneNumber: { contains: query.keyword } },
{ reason: { contains: query.keyword } },
] : undefined,
},
orderBy: { createdAt: 'desc' },
});
}
async createGlobalBlacklist(data: CreateBlacklistDto) {
const created = await this.prisma.globalBlacklist.create({
data: {
phoneNumber: data.phoneNumber,
reason: data.reason,
status: data.status ?? 'active',
},
});
await this.writeOperationLog(data.operatorId, 'global_blacklist.create', 'global_blacklist', created.id, {
phoneNumber: data.phoneNumber,
reason: data.reason,
});
return created;
}
async changeGlobalBlacklistStatus(id: string, data: DictionaryStatusDto) {
const status = data.status ?? 'active';
const updated = await this.prisma.globalBlacklist.update({ where: { id }, data: { status } });
await this.writeOperationLog(data.operatorId, `global_blacklist.${status}`, 'global_blacklist', id, { reason: data.reason });
return updated;
}
listEnterpriseBlacklist(query: DictionaryListQuery = {}) {
return this.prisma.enterpriseBlacklist.findMany({
where: {
tenantId: query.tenantId,
applicationId: query.applicationId,
status: query.status && query.status !== 'all' ? query.status : undefined,
tenant: query.enterpriseKeyword ? { name: { contains: query.enterpriseKeyword } } : undefined,
application: query.applicationKeyword ? { name: { contains: query.applicationKeyword } } : undefined,
phoneNumber: query.phoneNumber ? { contains: query.phoneNumber } : undefined,
reason: query.reasonKeyword ? { contains: query.reasonKeyword } : undefined,
OR: query.keyword ? [
{ phoneNumber: { contains: query.keyword } },
{ reason: { contains: query.keyword } },
{ tenant: { name: { contains: query.keyword } } },
{ application: { name: { contains: query.keyword } } },
] : undefined,
},
include: { tenant: true, application: true },
orderBy: { createdAt: 'desc' },
});
}
async createEnterpriseBlacklist(data: CreateBlacklistDto) {
if (!data.tenantId || !data.applicationId) {
throw new BadRequestException('tenantId and applicationId are required for application blacklist');
}
const application = await this.prisma.smsApplication.findUnique({
where: { id: data.applicationId },
select: { tenantId: true },
});
if (!application || application.tenantId !== data.tenantId) {
throw new BadRequestException('applicationId does not belong to tenantId');
}
const createData: Prisma.EnterpriseBlacklistUncheckedCreateInput = {
tenantId: data.tenantId,
applicationId: data.applicationId,
phoneNumber: data.phoneNumber,
reason: data.reason,
status: data.status ?? 'active',
};
const created = await this.prisma.enterpriseBlacklist.create({ data: createData });
await this.writeOperationLog(data.operatorId, 'enterprise_blacklist.create', 'enterprise_blacklist', created.id, {
tenantId: data.tenantId,
applicationId: data.applicationId,
phoneNumber: data.phoneNumber,
reason: data.reason,
});
return created;
}
async changeEnterpriseBlacklistStatus(id: string, data: DictionaryStatusDto) {
const status = data.status ?? 'active';
const updated = await this.prisma.enterpriseBlacklist.update({ where: { id }, data: { status } });
await this.writeOperationLog(data.operatorId, `enterprise_blacklist.${status}`, 'enterprise_blacklist', id, { reason: data.reason });
return updated;
}
listDrainageFields() {
return this.prisma.drainageField.findMany({ orderBy: { createdAt: 'desc' } });
}
createDrainageField(data: CreateDrainageFieldDto) {
const code = data.code?.trim();
if (!code || !/^[A-Za-z0-9]+$/.test(code)) {
throw new BadRequestException('code must contain only Arabic numerals and English letters');
}
if (!['string', 'image', 'file'].includes(data.fieldType)) {
throw new BadRequestException('fieldType must be string, image or file');
}
return this.prisma.drainageField.create({
data: {
code,
name: data.name,
fieldType: data.fieldType,
required: data.required ?? false,
status: data.status ?? 'active',
description: data.description,
},
});
}
private writeOperationLog(userId: string | undefined, action: string, resource: string, resourceId: string, detail: Record<string, unknown>) {
return this.prisma.operationLog.create({
data: {
userId,
action,
resource,
resourceId,
detail: detail as Prisma.InputJsonValue,
},
});
}
}