返回 AiToEarn
account.repository.ts
根目录 / project / aitoearn-backend / libs / mongodb / src / repositories / account.repository.ts
1 import { Logger } from '@nestjs/common'
2 import { InjectModel } from '@nestjs/mongoose'
3 import { AccountType, TableDto } from '@yikart/common'
4 import { Model, RootFilterQuery } from 'mongoose'
5 import { Account, AccountStatus } from '../schemas'
6 import { BaseRepository } from './base.repository'
7
8 export interface AccountIdentity {
9 type: AccountType
10 uid: string
11 account?: string
12 clientType?: Account['clientType']
13 }
14
15 export class AccountRepository extends BaseRepository<Account> {
16 logger = new Logger(AccountRepository.name)
17 constructor(
18 @InjectModel(Account.name)
19 private readonly accountModel: Model<Account>,
20 ) { super(accountModel) }
21
22 async getByIdentity(identity: AccountIdentity): Promise<Account | null> {
23 return this.accountModel.findOne(this.getIdentityFilter(identity)).lean({ virtuals: true }).exec()
24 }
25
26 async createByIdentity(identity: AccountIdentity, accountData: Partial<Account>): Promise<Account> {
27 const account = new this.accountModel({
28 _id: this.getIdentityId(identity),
29 ...this.toAccountData(identity, accountData),
30 })
31 const saved = await account.save()
32 return saved.toObject() as Account
33 }
34
35 async updateByIdentity(
36 identity: AccountIdentity,
37 accountData: Partial<Account>,
38 ): Promise<Account | null> {
39 return this.accountModel.findOneAndUpdate(
40 this.getIdentityFilter(identity),
41 { $set: this.toAccountData(identity, accountData) },
42 { new: true },
43 ).lean({ virtuals: true }).exec()
44 }
45
46 async update(id: string, updateDto: Partial<Account>): Promise<Account | null> {
47 return await this.accountModel
48 .findByIdAndUpdate(id, updateDto)
49 .lean({ virtuals: true })
50 .exec()
51 }
52
53 async delete(id: string) {
54 await this.accountModel.deleteOne({ _id: id }).exec()
55 }
56
57 async getUserAccountList(userId: string) {
58 return await this.accountModel.find({ userId }).lean({ virtuals: true }).exec()
59 }
60
61 async listRelayAccountsByUserId(userId: string) {
62 return await this.accountModel.find({ userId, relayAccountRef: { $ne: null } }).lean({ virtuals: true }).exec()
63 }
64
65 async getList(
66 page: {
67 pageNo: number
68 pageSize: number
69 },
70 filter: {
71 name?: string
72 type?: AccountType
73 },
74 ) {
75 const { pageNo, pageSize } = page
76 const queryFilter: RootFilterQuery<Account> = {
77 ...(filter.name && { name: filter.name }),
78 ...(filter.type && { type: filter.type }),
79 }
80
81 const [total, list] = await Promise.all([
82 this.accountModel.countDocuments(queryFilter),
83 this.accountModel
84 .find(queryFilter)
85 .skip((pageNo - 1) * pageSize)
86 .limit(pageSize)
87 .sort({ createdAt: -1 })
88 .lean({ virtuals: true })
89 .exec(),
90 ])
91
92 return {
93 list,
94 total,
95 }
96 }
97
98 /**
99 * Switch accounts under user group to default group
100 * @param userId
101 * @param groupId
102 * @param defaultGroupId
103 */
104 async updateManyToDefaultGroup(
105 userId: string,
106 groupId: string,
107 defaultGroupId: string,
108 ) {
109 return this.accountModel.updateMany(
110 { userId, groupId },
111 { groupId: defaultGroupId },
112 )
113 }
114
115 /**
116 * Update account information
117 * @param id
118 * @param account
119 * @returns
120 */
121 override async updateById(
122 id: string,
123 account: Partial<Account>,
124 ) {
125 return await this.accountModel.findByIdAndUpdate(
126 id,
127 { $set: account },
128 { new: true },
129 ).lean({ virtuals: true }).exec()
130 }
131
132 /**
133 * Get account by user ID
134 */
135 async getAccountById(id: string) {
136 return this.accountModel.findOne({ _id: id }).lean({ virtuals: true }).exec()
137 }
138
139 async getByIdAndUserId(id: string, userId: string) {
140 return this.accountModel.findOne({ _id: id, userId }).lean({ virtuals: true }).exec()
141 }
142
143 /**
144 * Get account by user uid
145 */
146 async getAccountByUid(uid: string, type: AccountType) {
147 return this.accountModel.findOne({ uid, type }).lean({ virtuals: true }).exec()
148 }
149
150 async getAccountByUidAndAccount(uid: string, type: AccountType, account: string) {
151 return this.accountModel.findOne({ uid, type, account }).lean({ virtuals: true }).exec()
152 }
153
154 private getIdentityId(identity: AccountIdentity): string {
155 let id = `${identity.type}_${identity.uid}`
156 if (identity.account) {
157 id += `_${identity.account}`
158 }
159 if (identity.type === AccountType.RedNote) {
160 id += `_${identity.clientType}`
161 }
162 return id
163 }
164
165 private getIdentityFilter(identity: AccountIdentity): RootFilterQuery<Account> {
166 return {
167 $or: [
168 { _id: this.getIdentityId(identity) },
169 {
170 type: identity.type,
171 uid: identity.uid,
172 account: identity.account ?? null,
173 ...(identity.type === AccountType.RedNote ? { clientType: identity.clientType } : {}),
174 },
175 ],
176 }
177 }
178
179 private toAccountData(identity: AccountIdentity, accountData: Partial<Account>): Partial<Account> {
180 const data = {
181 ...accountData,
182 ...(identity.type === AccountType.RedNote ? { clientType: identity.clientType } : {}),
183 }
184 return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined)) as Partial<Account>
185 }
186
187 /**
188 * Get all accounts
189 * @param userId
190 * @returns
191 */
192 async getUserAccounts(userId: string) {
193 const accounts = await this.accountModel.find({
194 userId,
195 }).lean({ virtuals: true })
196 if (!accounts || accounts.length === 0) {
197 return []
198 }
199 return accounts
200 }
201
202 async getAccounts(filterDto: {
203 userId?: string
204 types?: string[]
205 }, pageInfo: TableDto) {
206 const { pageNo, pageSize } = pageInfo
207 const filter: RootFilterQuery<Account> = {
208 }
209 if (filterDto.userId) {
210 filter.userId = filterDto.userId
211 }
212
213 if (filterDto.types) {
214 filter.type = {
215 $in: filterDto.types,
216 }
217 }
218
219 const total = await this.accountModel.countDocuments(filter)
220 const list = await this.accountModel
221 .find(filter)
222 .sort({ createdAt: -1 })
223 .skip((pageNo! - 1) * pageSize)
224 .limit(pageSize)
225 .lean({ virtuals: true })
226
227 return {
228 total,
229 list,
230 }
231 }
232
233 async listByUserIdAndIds(userId: string, ids: string[]) {
234 return this.accountModel.find({
235 userId,
236 _id: { $in: ids },
237 }).lean({ virtuals: true })
238 }
239
240 async getAccountListByIds(ids: string[]) {
241 return this.accountModel.find({
242 id: { $in: ids },
243 }).lean({ virtuals: true })
244 }
245
246 async getAccountListByGroupId(groupId: string) {
247 return this.accountModel.find({ groupId }).lean({ virtuals: true })
248 }
249
250 async getAccountStatistics(
251 userId: string,
252 type?: AccountType,
253 ): Promise<{
254 accountTotal: number
255 list: Account[]
256 fansCount?: number
257 readCount?: number
258 likeCount?: number
259 collectCount?: number
260 commentCount?: number
261 income?: number
262 }> {
263 const accountList = await this.accountModel.find({
264 userId,
265 ...(type && { type }),
266 }).lean({ virtuals: true })
267
268 const res = {
269 accountTotal: accountList.length,
270 list: accountList,
271 fansCount: 0,
272 }
273
274 return res
275 }
276
277 async getUserAccountCount(userId: string) {
278 return await this.accountModel.countDocuments({ userId })
279 }
280
281 async getByUserIdTotalFansCount(userId: string): Promise<number> {
282 const result = await this.accountModel.aggregate([
283 { $match: { userId, status: AccountStatus.NORMAL } },
284 {
285 $group: {
286 _id: '$type',
287 fansCount: { $max: { $ifNull: ['$fansCount', 0] } },
288 },
289 },
290 { $group: { _id: null, total: { $sum: '$fansCount' } } },
291 ]).exec()
292 return result[0]?.total ?? 0
293 }
294
295 async sumFansCountByUserId(userId: string): Promise<number> {
296 const result = await this.accountModel.aggregate([
297 { $match: { userId } },
298 { $group: { _id: null, total: { $sum: '$fansCount' } } },
299 ]).exec()
300 return result[0]?.total ?? 0
301 }
302
303 /**
304 * Get account information by multiple account IDs
305 * @param ids
306 * @returns
307 */
308 async getAccountsByIds(ids: string[]) {
309 return await this.accountModel.find({
310 id: { $in: ids },
311 }).lean({ virtuals: true })
312 }
313
314 /**
315 * Delete
316 * @param id
317 * @param userId
318 * @returns
319 */
320 async deleteByIdAndUserId(id: string, userId: string): Promise<boolean> {
321 const res = await this.accountModel.deleteOne({
322 _id: id,
323 userId,
324 })
325
326 return res.deletedCount > 0
327 }
328
329 async deleteByUserIdAndIds(userId: string, ids: string[]) {
330 const res = await this.accountModel.deleteMany({
331 _id: { $in: ids },
332 userId,
333 })
334 return res.deletedCount > 0
335 }
336
337 /**
338 * Update user status
339 * @param id
340 * @param status
341 * @returns
342 */
343 async updateAccountStatus(id: string, status: AccountStatus) {
344 const res = await this.accountModel.updateOne({ _id: id }, { status })
345 return res
346 }
347
348 async updateAccountStatistics(
349 id: string,
350 data: {
351 fansCount?: number
352 followingCount?: number
353 readCount?: number
354 likeCount?: number
355 collectCount?: number
356 commentCount?: number
357 income?: number
358 workCount?: number
359 },
360 ) {
361 const res = await this.accountModel.updateOne(
362 { _id: id },
363 {
364 $set: data,
365 },
366 )
367 return res.matchedCount > 0 || res.modifiedCount > 0
368 }
369
370 async getAccountByParam(param: { [key: string]: string }) {
371 this.logger.log(`getAccountByParam query param: ${JSON.stringify(param)}`)
372 const result = await this.accountModel.findOne(param).lean({ virtuals: true })
373 return result
374 }
375
376 /**
377 * Get account list array by ID array ids
378 * @param ids
379 * @returns
380 */
381 async listByIds(ids: string[]) {
382 return this.accountModel.find({
383 _id: { $in: ids },
384 }).lean({ virtuals: true })
385 }
386
387 /**
388 * Get account list array by space ID array spaceIds
389 * @param userId
390 * @param spaceIds
391 * @returns
392 */
393 async listBySpaceIds(userId: string, spaceIds: string[]) {
394 return this.accountModel.find({
395 userId,
396 groupId: { $in: spaceIds },
397 }).lean({ virtuals: true })
398 }
399
400 /**
401 * Get all accounts by type array
402 * @param types
403 * @param status
404 * @returns
405 */
406 async getAccountsByTypes(types: string[], status?: number) {
407 const filter: RootFilterQuery<Account> = {}
408 filter.type = {
409 $in: types,
410 }
411 if (status) {
412 filter.status = status
413 }
414
415 const accounts = await this.accountModel
416 .find(filter)
417 .lean({ virtuals: true })
418
419 return accounts
420 }
421
422 async updateManyRankByIds(userId: string, groupId: string, list: { id: string, rank: number }[]) {
423 if (list.length === 0) {
424 return true
425 }
426 const result = await this.accountModel.bulkWrite(
427 list.map(element => ({
428 updateOne: {
429 filter: { userId, groupId, _id: element.id },
430 update: { $set: { rank: element.rank } },
431 },
432 })),
433 )
434 return result.matchedCount === list.length
435 }
436
437 // Get account cursor for iteration operations
438 async getAccountCursor(filter: { groupId?: string, userId?: string }) {
439 const cursor = this.accountModel.find({ ...(filter.groupId && { groupId: filter.groupId }), ...(filter.userId && { userId: filter.userId }) }).lean({ virtuals: true }).cursor()
440 return cursor
441 }
442
443 async listByUserIdAndGroupId(userId: string, groupId: string) {
444 return this.accountModel.find({ userId, groupId }).lean({ virtuals: true })
445 }
446
447 async listByFilterWithPagination(
448 pageInfo: { pageNo: number, pageSize: number },
449 filter: {
450 userId?: string
451 status?: AccountStatus
452 types?: AccountType[]
453 groupIds?: string[]
454 },
455 ) {
456 const { pageNo, pageSize } = pageInfo
457 const queryFilter: RootFilterQuery<Account> = {
458 ...(filter.userId && { userId: filter.userId }),
459 ...(filter.status !== undefined && { status: filter.status }),
460 ...(filter.types && { type: { $in: filter.types } }),
461 ...(filter.groupIds && { groupId: { $in: filter.groupIds } }),
462 }
463 const [total, list] = await Promise.all([
464 this.accountModel.countDocuments(queryFilter),
465 this.accountModel
466 .find(queryFilter)
467 .sort({ createdAt: -1 })
468 .skip((pageNo - 1) * pageSize)
469 .limit(pageSize)
470 .lean({ virtuals: true }),
471 ])
472 return { total, list }
473 }
474
475 /**
476 * Batch update userId for accounts that have no userId or empty userId
477 * @param accountIds - List of account IDs to update
478 * @param userId - User ID to set
479 * @returns Number of updated accounts
480 */
481 async updateManyUserIdByIds(accountIds: string[], userId: string): Promise<number> {
482 if (accountIds.length === 0) {
483 return 0
484 }
485 const result = await this.accountModel.updateMany(
486 {
487 _id: { $in: accountIds },
488 $or: [
489 { userId: { $exists: false } },
490 { userId: '' },
491 { userId: null },
492 ],
493 },
494 { $set: { userId } },
495 )
496 return result.modifiedCount
497 }
498 }
499
499 lines TYPESCRIPT