| 1 | import { InjectModel } from '@nestjs/mongoose' |
| 2 | import { Pagination, UserType } from '@yikart/common' |
| 3 | import { FilterQuery, Model } from 'mongoose' |
| 4 | import { AssetStatus, AssetType } from '../enums' |
| 5 | import { Asset } from '../schemas' |
| 6 | import { BaseRepository } from './base.repository' |
| 7 | |
| 8 | export interface ListAssetsParams extends Pagination { |
| 9 | userId: string |
| 10 | userType?: UserType |
| 11 | type?: AssetType |
| 12 | types?: AssetType[] |
| 13 | status?: AssetStatus |
| 14 | statuses?: AssetStatus[] |
| 15 | } |
| 16 | |
| 17 | export class AssetRepository extends BaseRepository<Asset> { |
| 18 | constructor( |
| 19 | @InjectModel(Asset.name) assetModel: Model<Asset>, |
| 20 | ) { |
| 21 | super(assetModel) |
| 22 | } |
| 23 | |
| 24 | async getByPath(path: string) { |
| 25 | return await this.findOne({ |
| 26 | path, |
| 27 | deletedAt: { $exists: false }, |
| 28 | }) |
| 29 | } |
| 30 | |
| 31 | async getByIdAndUserId(id: string, userId: string, userType?: UserType) { |
| 32 | return await this.findOne({ |
| 33 | _id: id, |
| 34 | userId, |
| 35 | ...(userType ? { userType } : {}), |
| 36 | deletedAt: { $exists: false }, |
| 37 | }) |
| 38 | } |
| 39 | |
| 40 | async listWithPagination(params: ListAssetsParams) { |
| 41 | const { page, pageSize, userId, userType, type, types, status, statuses } = params |
| 42 | |
| 43 | const filter: FilterQuery<Asset> = { |
| 44 | userId, |
| 45 | ...(userType ? { userType } : {}), |
| 46 | deletedAt: { $exists: false }, |
| 47 | ...(types ? { type: { $in: types } } : type && { type }), |
| 48 | ...(statuses ? { status: { $in: statuses } } : status && { status }), |
| 49 | } |
| 50 | |
| 51 | const [list, total] = await this.findWithPagination({ |
| 52 | page, |
| 53 | pageSize, |
| 54 | filter, |
| 55 | options: { sort: { createdAt: -1 } }, |
| 56 | }) |
| 57 | |
| 58 | return { list, total } |
| 59 | } |
| 60 | |
| 61 | async updateStatus(id: string, status: AssetStatus, additionalData?: Partial<Asset>) { |
| 62 | return await this.updateById(id, { |
| 63 | status, |
| 64 | ...additionalData, |
| 65 | }) |
| 66 | } |
| 67 | |
| 68 | async softDelete(id: string) { |
| 69 | return await this.updateById(id, { |
| 70 | deletedAt: new Date(), |
| 71 | }) |
| 72 | } |
| 73 | |
| 74 | async softDeleteByUserId(userId: string, assetIds: string[]) { |
| 75 | const result = await this.model.updateMany( |
| 76 | { |
| 77 | _id: { $in: assetIds }, |
| 78 | userId, |
| 79 | deletedAt: { $exists: false }, |
| 80 | }, |
| 81 | { |
| 82 | $set: { deletedAt: new Date() }, |
| 83 | }, |
| 84 | ) |
| 85 | return { affectedCount: result.modifiedCount } |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * 查找待确认的 assets(PENDING 状态且创建时间超过指定阈值) |
| 90 | * @param olderThanSeconds 创建时间超过多少秒的 asset |
| 91 | * @param limit 最大返回数量 |
| 92 | */ |
| 93 | async findPendingAssets(olderThanSeconds: number, limit = 100) { |
| 94 | const threshold = new Date(Date.now() - olderThanSeconds * 1000) |
| 95 | return await this.model.find({ |
| 96 | status: AssetStatus.Pending, |
| 97 | createdAt: { $lt: threshold }, |
| 98 | deletedAt: { $exists: false }, |
| 99 | }).limit(limit).lean({ virtuals: true }).exec() |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * 将过期的 PENDING assets 标记为失败 |
| 104 | * @param olderThanSeconds 创建时间超过多少秒视为过期 |
| 105 | */ |
| 106 | async markExpiredPendingAsFailed(olderThanSeconds: number) { |
| 107 | const threshold = new Date(Date.now() - olderThanSeconds * 1000) |
| 108 | const result = await this.model.updateMany( |
| 109 | { |
| 110 | status: AssetStatus.Pending, |
| 111 | createdAt: { $lt: threshold }, |
| 112 | deletedAt: { $exists: false }, |
| 113 | }, |
| 114 | { |
| 115 | $set: { status: AssetStatus.Failed }, |
| 116 | }, |
| 117 | ) |
| 118 | return { affectedCount: result.modifiedCount } |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * 根据状态和创建时间获取资源列表 |
| 123 | * 只查找创建时间超过 minAgeSeconds 的记录 |
| 124 | */ |
| 125 | async listByStatusAndAge(status: AssetStatus, minAgeSeconds: number, limit = 50) { |
| 126 | const threshold = new Date(Date.now() - minAgeSeconds * 1000) |
| 127 | return await this.model.find({ |
| 128 | status, |
| 129 | createdAt: { $lt: threshold }, |
| 130 | deletedAt: { $exists: false }, |
| 131 | }).limit(limit).lean({ virtuals: true }).exec() |
| 132 | } |
| 133 | } |
| 134 |