Files
pos-system/services/_template/src/modules/feature/feature.repository.ts

236 lines
7.3 KiB
TypeScript

import { logger } from '@goodgo/logger';
import { prisma } from '../../config/database.config';
import { ConflictError } from '../../errors/http-error';
import { BaseRepository, IRepository } from '../common/repository';
// EN: Feature entity type from Prisma
// VI: Feature entity type từ Prisma
type Feature = {
id: string;
name: string;
title: string | null;
description: string | null;
config: any;
enabled: boolean;
version: string | null;
tags: string[];
createdAt: Date;
updatedAt: Date;
};
// EN: Input types for create/update operations
// VI: Input types cho create/update operations
type CreateFeatureInput = {
name: string;
title?: string;
description?: string;
config?: any;
tags?: string[];
};
type UpdateFeatureInput = {
title?: string;
description?: string;
config?: any;
enabled?: boolean;
tags?: string[];
};
/**
* EN: Feature repository implementing repository pattern
* VI: Feature repository implement repository pattern
*/
export class FeatureRepository extends BaseRepository<Feature, CreateFeatureInput, UpdateFeatureInput>
implements IRepository<Feature, CreateFeatureInput, UpdateFeatureInput> {
constructor() {
super(prisma, 'feature');
}
/**
* EN: Find feature by name (unique field)
* VI: Tìm feature theo tên (field duy nhất)
*/
async findByName(name: string): Promise<Feature | null> {
return this.findByUnique('name', name);
}
/**
* EN: Find features by tags
* VI: Tìm features theo tags
*/
async findByTags(tags: string[]): Promise<Feature[]> {
try {
logger.debug('Finding features by tags / Tìm features theo tags', { tags });
const features = await (this.prisma as any).feature.findMany({
where: {
tags: {
hasSome: tags,
},
},
orderBy: { createdAt: 'desc' },
});
logger.debug(`Found ${features.length} features by tags / Đã tìm thấy ${features.length} features theo tags`, { tags });
return features;
} catch (error) {
logger.error('Failed to find features by tags / Không thể tìm features theo tags', { error, tags });
throw this.handleDatabaseError(error, { tags });
}
}
/**
* EN: Find enabled features only
* VI: Tìm chỉ features đã được bật
*/
async findEnabled(): Promise<Feature[]> {
try {
logger.debug('Finding enabled features / Tìm features đã được bật');
const features = await (this.prisma as any).feature.findMany({
where: { enabled: true },
orderBy: { createdAt: 'desc' },
});
logger.debug(`Found ${features.length} enabled features / Đã tìm thấy ${features.length} features đã được bật`);
return features;
} catch (error) {
logger.error('Failed to find enabled features / Không thể tìm features đã được bật', { error });
throw this.handleDatabaseError(error);
}
}
/**
* EN: Create feature with duplicate name check
* VI: Tạo feature với kiểm tra tên trùng lặp
*/
async create(data: CreateFeatureInput): Promise<Feature> {
try {
// EN: Check for duplicate name
// VI: Kiểm tra tên trùng lặp
const existingFeature = await this.findByName(data.name);
if (existingFeature) {
logger.warn('Feature with this name already exists / Feature với tên này đã tồn tại', { name: data.name });
throw new ConflictError('Feature with this name already exists / Feature với tên này đã tồn tại', { name: data.name });
}
return await super.create(data);
} catch (error) {
if (error instanceof ConflictError) {
throw error;
}
throw error;
}
}
/**
* EN: Toggle feature enabled/disabled status
* VI: Bật/tắt trạng thái feature
*/
async toggleEnabled(id: string): Promise<Feature> {
try {
logger.debug('Toggling feature enabled status / Chuyển đổi trạng thái feature', { id });
const feature = await this.findById(id);
if (!feature) {
throw new ConflictError('Feature not found / Feature không tìm thấy', { id });
}
const updatedFeature = await this.update(id, {
enabled: !feature.enabled,
});
logger.debug(`Feature ${updatedFeature.enabled ? 'enabled' : 'disabled'} / Feature đã được ${updatedFeature.enabled ? 'bật' : 'tắt'}`, { id });
return updatedFeature;
} catch (error) {
if (error instanceof ConflictError) {
throw error;
}
logger.error('Failed to toggle feature status / Không thể chuyển đổi trạng thái feature', { error, id });
throw this.handleDatabaseError(error, { id });
}
}
/**
* EN: Search features by name or description
* VI: Tìm kiếm features theo tên hoặc mô tả
*/
async search(query: string, limit: number = 10): Promise<Feature[]> {
try {
logger.debug('Searching features / Tìm kiếm features', { query, limit });
const features = await (this.prisma as any).feature.findMany({
where: {
OR: [
{ name: { contains: query, mode: 'insensitive' } },
{ title: { contains: query, mode: 'insensitive' } },
{ description: { contains: query, mode: 'insensitive' } },
],
},
take: limit,
orderBy: { createdAt: 'desc' },
});
logger.debug(`Found ${features.length} features matching search / Đã tìm thấy ${features.length} features khớp với tìm kiếm`, { query });
return features;
} catch (error) {
logger.error('Failed to search features / Không thể tìm kiếm features', { error, query });
throw this.handleDatabaseError(error, { query });
}
}
/**
* EN: Get feature statistics
* VI: Lấy thống kê feature
*/
async getStatistics(): Promise<{
total: number;
enabled: number;
disabled: number;
byTag: Record<string, number>;
}> {
try {
logger.debug('Getting feature statistics / Lấy thống kê feature');
const [total, enabled, disabled, features] = await Promise.all([
this.count(),
this.count({ enabled: true }),
this.count({ enabled: false }),
this.findAll(),
]);
// EN: Count by tags
// VI: Đếm theo tags
const byTag: Record<string, number> = {};
features.forEach(feature => {
feature.tags.forEach(tag => {
byTag[tag] = (byTag[tag] || 0) + 1;
});
});
const statistics = { total, enabled, disabled, byTag };
logger.debug('Feature statistics retrieved / Thống kê feature đã được lấy', statistics);
return statistics;
} catch (error) {
logger.error('Failed to get feature statistics / Không thể lấy thống kê feature', { error });
throw this.handleDatabaseError(error);
}
}
/**
* EN: Handle database-specific errors
* VI: Xử lý lỗi database-specific
*/
private handleDatabaseError(error: any, context?: any) {
if (error.code === 'P2002') {
return new ConflictError('Feature with this name already exists / Feature với tên này đã tồn tại', context);
}
return error;
}
}
// EN: Singleton instance
// VI: Singleton instance
export const featureRepository = new FeatureRepository();