|
|
@@ -46,25 +46,42 @@ export class TagService {
|
|
|
* Ensure category exists.
|
|
|
* NOTE: Categories are no longer tied to channels, so we only validate category existence.
|
|
|
*/
|
|
|
- private async assertCategoryExists(categoryId: string): Promise<void> {
|
|
|
- const category = await this.mongoPrismaService.category.findUnique({
|
|
|
+ async assertCategoryExists(categoryId?: string | null): Promise<void> {
|
|
|
+ if (!categoryId) {
|
|
|
+ // "All / no category" is valid
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // if categoryId == 'ALL', treat as no category
|
|
|
+ if (categoryId.toUpperCase() === 'ALL') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const exists = await this.mongoPrismaService.category.findUnique({
|
|
|
where: { id: categoryId },
|
|
|
select: { id: true },
|
|
|
});
|
|
|
|
|
|
- if (!category) {
|
|
|
- throw new NotFoundException('Category not found');
|
|
|
+ if (!exists) {
|
|
|
+ throw new BadRequestException(
|
|
|
+ `Category with id "${categoryId}" does not exist`,
|
|
|
+ );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async create(dto: CreateTagDto) {
|
|
|
- // Validate category exists (channelId no longer required)
|
|
|
- await this.assertCategoryExists(dto.categoryId);
|
|
|
+ const categoryId =
|
|
|
+ dto.categoryId.toUpperCase() === 'ALL' ? null : dto.categoryId;
|
|
|
+
|
|
|
+ // Validate category exists (only if provided)
|
|
|
+ if (categoryId) {
|
|
|
+ await this.assertCategoryExists(categoryId);
|
|
|
+ }
|
|
|
|
|
|
- // Check for duplicate tag name within the same category
|
|
|
+ // Check duplicate tag name within the same category scope
|
|
|
const existingTag = await this.mongoPrismaService.tag.findFirst({
|
|
|
where: {
|
|
|
- categoryId: dto.categoryId,
|
|
|
+ categoryId,
|
|
|
name: dto.name,
|
|
|
},
|
|
|
});
|
|
|
@@ -80,7 +97,7 @@ export class TagService {
|
|
|
const tag = await this.mongoPrismaService.tag.create({
|
|
|
data: {
|
|
|
name: dto.name,
|
|
|
- categoryId: dto.categoryId,
|
|
|
+ categoryId,
|
|
|
seq: dto.seq ?? 0,
|
|
|
status: dto.status ?? CommonStatus.enabled,
|
|
|
createAt: now,
|
|
|
@@ -88,18 +105,19 @@ export class TagService {
|
|
|
},
|
|
|
});
|
|
|
|
|
|
- await this.categoryService.refreshTagsMetadata(tag.categoryId);
|
|
|
- await this.scheduleCategoryCaches(tag.categoryId);
|
|
|
+ // Category-related cache refresh (only if categoryId exists)
|
|
|
+ if (categoryId) {
|
|
|
+ await this.categoryService.refreshTagsMetadata(categoryId);
|
|
|
+ await this.scheduleCategoryCaches(categoryId);
|
|
|
|
|
|
- await this.categoryService.refreshTagsMetadata(tag.categoryId);
|
|
|
- await this.scheduleCategoryCaches(tag.categoryId);
|
|
|
+ await this.cacheSyncService.scheduleAction({
|
|
|
+ entityType: CacheEntityType.TAG,
|
|
|
+ operation: CacheOperation.REFRESH,
|
|
|
+ payload: { categoryId },
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- // Schedule cache sync actions
|
|
|
- await this.cacheSyncService.scheduleAction({
|
|
|
- entityType: CacheEntityType.TAG,
|
|
|
- operation: CacheOperation.REFRESH,
|
|
|
- payload: { categoryId: tag.categoryId },
|
|
|
- });
|
|
|
+ // Global tag cache refresh
|
|
|
await this.cacheSyncService.scheduleAction({
|
|
|
entityType: CacheEntityType.TAG,
|
|
|
operation: CacheOperation.REFRESH_ALL,
|
|
|
@@ -109,10 +127,14 @@ export class TagService {
|
|
|
}
|
|
|
|
|
|
async update(dto: UpdateTagDto) {
|
|
|
- // Validate category exists (channelId no longer required)
|
|
|
- await this.assertCategoryExists(dto.categoryId);
|
|
|
+ const categoryId =
|
|
|
+ dto.categoryId.toUpperCase() === 'ALL' ? null : dto.categoryId;
|
|
|
+
|
|
|
+ // Validate category exists (only if provided)
|
|
|
+ if (categoryId) {
|
|
|
+ await this.assertCategoryExists(categoryId);
|
|
|
+ }
|
|
|
|
|
|
- // Load existing tag to capture old categoryId in case it changed
|
|
|
const existingTag = await this.mongoPrismaService.tag.findUnique({
|
|
|
where: { id: dto.id },
|
|
|
});
|
|
|
@@ -121,10 +143,10 @@ export class TagService {
|
|
|
throw new NotFoundException('Tag not found');
|
|
|
}
|
|
|
|
|
|
- // Check for duplicate tag name within the same category (excluding current tag)
|
|
|
+ // Check duplicate tag name within same category scope
|
|
|
const duplicateTag = await this.mongoPrismaService.tag.findFirst({
|
|
|
where: {
|
|
|
- categoryId: dto.categoryId,
|
|
|
+ categoryId,
|
|
|
name: dto.name,
|
|
|
id: { not: dto.id },
|
|
|
},
|
|
|
@@ -136,19 +158,16 @@ export class TagService {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- const oldCategoryId = existingTag.categoryId;
|
|
|
+ const oldCategoryId = existingTag.categoryId ?? null;
|
|
|
const now = this.now();
|
|
|
|
|
|
- // Build update data carefully to avoid accidentally changing fields
|
|
|
const data: any = {
|
|
|
name: dto.name,
|
|
|
- categoryId: dto.categoryId,
|
|
|
+ categoryId,
|
|
|
seq: dto.seq ?? 0,
|
|
|
updateAt: now,
|
|
|
};
|
|
|
|
|
|
- // Only update `status` if it is explicitly provided.
|
|
|
- // This avoids silently re-enabling disabled tags.
|
|
|
if (dto.status !== undefined) {
|
|
|
data.status = dto.status;
|
|
|
}
|
|
|
@@ -158,23 +177,31 @@ export class TagService {
|
|
|
data,
|
|
|
});
|
|
|
|
|
|
- if (oldCategoryId !== dto.categoryId) {
|
|
|
- await this.categoryService.ensureTagChildren(oldCategoryId);
|
|
|
+ // If category changed, refresh old category caches
|
|
|
+ if (oldCategoryId && oldCategoryId !== categoryId) {
|
|
|
+ await this.categoryService.refreshTagsMetadata(oldCategoryId);
|
|
|
await this.scheduleCategoryCaches(oldCategoryId);
|
|
|
+
|
|
|
await this.cacheSyncService.scheduleAction({
|
|
|
entityType: CacheEntityType.TAG,
|
|
|
operation: CacheOperation.REFRESH,
|
|
|
payload: { categoryId: oldCategoryId },
|
|
|
});
|
|
|
}
|
|
|
- await this.categoryService.refreshTagsMetadata(dto.categoryId);
|
|
|
- await this.scheduleCategoryCaches(dto.categoryId);
|
|
|
|
|
|
- await this.cacheSyncService.scheduleAction({
|
|
|
- entityType: CacheEntityType.TAG,
|
|
|
- operation: CacheOperation.REFRESH,
|
|
|
- payload: { categoryId: dto.categoryId },
|
|
|
- });
|
|
|
+ // Refresh new category caches
|
|
|
+ if (categoryId) {
|
|
|
+ await this.categoryService.refreshTagsMetadata(categoryId);
|
|
|
+ await this.scheduleCategoryCaches(categoryId);
|
|
|
+
|
|
|
+ await this.cacheSyncService.scheduleAction({
|
|
|
+ entityType: CacheEntityType.TAG,
|
|
|
+ operation: CacheOperation.REFRESH,
|
|
|
+ payload: { categoryId },
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Global tag cache refresh
|
|
|
await this.cacheSyncService.scheduleAction({
|
|
|
entityType: CacheEntityType.TAG,
|
|
|
operation: CacheOperation.REFRESH_ALL,
|