Files
photo-live/services/api/src/albums/support.ts
T

58 lines
2.0 KiB
TypeScript
Raw Normal View History

// services/api/src/albums/support.ts —— 相册域服务共用小工具(Task 15 起自
// albums.service.ts 抽出,Task 17 起 members/categories 服务同用)。
import { HttpStatus } from '@nestjs/common';
import { ErrorCodes } from '@photo/contracts';
import { ApiException } from '../api-exception';
/** 统一 404:不可见(含不存在)同码防 id 枚举(契约 albums/categories 404 语义)。 */
export function albumNotFound(): ApiException {
return new ApiException(
ErrorCodes.ALBUM_NOT_FOUND,
'相册不存在或不可见',
HttpStatus.NOT_FOUND,
);
}
/** 统一 403:可见但无管理权限(协作者视角)。 */
export function albumForbidden(message = '仅创建者或管理员可操作'): ApiException {
return new ApiException(ErrorCodes.AUTH_FORBIDDEN, message, HttpStatus.FORBIDDEN);
}
/** 统一 400(契约 VALIDATION_FAILED)。 */
export function validationFailed(message: string): ApiException {
return new ApiException(
ErrorCodes.VALIDATION_FAILED,
message,
HttpStatus.BAD_REQUEST,
);
}
/**
* P2002 是否命中指定唯一索引。uniqueName 用 Prisma 唯一选择器名(单列
* 'publicToken' / 复合 'albumId_userId')。meta.target 两种形态都兼容:
* 字段数组(部分引擎/版本)或 DB 约束名字符串(MySQL 实测形如
* 'albums_publicToken_key' / 'album_members_albumId_userId_key'——
* 表名前缀 + 选择器名 + _key 后缀,两种后缀都匹配)。
*/
export function isUniqueViolationOn(err: unknown, uniqueName: string): boolean {
if (typeof err !== 'object' || err === null) {
return false;
}
const { code, meta } = err as { code?: unknown; meta?: { target?: unknown } };
if (code !== 'P2002') {
return false;
}
const target = meta?.target;
if (Array.isArray(target)) {
return target.map(String).includes(uniqueName);
}
if (typeof target === 'string') {
return (
target === uniqueName ||
target.endsWith(`${uniqueName}_key`) ||
target.endsWith(`_${uniqueName}_key`)
);
}
return false;
}