返回 AiToEarn
oauth2-credential.repository.ts
根目录 / project / aitoearn-backend / libs / mongodb / src / repositories / oauth2-credential.repository.ts
1 import { Injectable } from '@nestjs/common'
2 import { InjectModel } from '@nestjs/mongoose'
3 import { AccountType } from '@yikart/common'
4 import { Model } from 'mongoose'
5 import { AccountStatus, OAuth2Credential } from '../schemas'
6 import { BaseRepository } from './base.repository'
7
8 export interface OAuth2CredentialExpiryCursor {
9 accessTokenExpiresAt: number
10 cursorId: unknown
11 }
12
13 export type OAuth2CredentialExpiryRecord = OAuth2Credential & OAuth2CredentialExpiryCursor
14
15 @Injectable()
16 export class OAuth2CredentialRepository extends BaseRepository<OAuth2Credential> {
17 constructor(
18 @InjectModel(OAuth2Credential.name) oauth2CredentialModel: Model<OAuth2Credential>,
19 ) {
20 super(oauth2CredentialModel)
21 }
22
23 async getByAccountId(accountId: string) {
24 return await this.findOne({ accountId })
25 }
26
27 async getByAccountIdAndPlatform(accountId: string, platform: AccountType) {
28 return await this.findOne({ accountId, platform })
29 }
30
31 async listByAccountIds(accountIds: string[]) {
32 return await this.find({ accountId: { $in: accountIds } })
33 }
34
35 async listByAccessTokenExpiresAt(beforeTimestamp: number, limit: number) {
36 return await this.find({
37 accessTokenExpiresAt: { $type: 'number', $lte: beforeTimestamp },
38 refreshToken: { $type: 'string', $ne: '' },
39 }, {
40 sort: { accessTokenExpiresAt: 1, _id: 1 },
41 limit,
42 })
43 }
44
45 async listByAccessTokenExpiresAtAndNormalAccount(
46 beforeTimestamp: number,
47 limit: number,
48 cursor?: OAuth2CredentialExpiryCursor,
49 ) {
50 const match: Record<string, unknown> = {
51 accessTokenExpiresAt: { $type: 'number', $lte: beforeTimestamp },
52 refreshToken: { $type: 'string', $ne: '' },
53 }
54 if (cursor) {
55 match['$or'] = [
56 { accessTokenExpiresAt: { $gt: cursor.accessTokenExpiresAt } },
57 {
58 accessTokenExpiresAt: cursor.accessTokenExpiresAt,
59 _id: { $gt: cursor.cursorId },
60 },
61 ]
62 }
63
64 return await this.model.aggregate<OAuth2CredentialExpiryRecord>([
65 {
66 $match: match,
67 },
68 { $sort: { accessTokenExpiresAt: 1, _id: 1 } },
69 {
70 $lookup: {
71 from: 'account',
72 localField: 'accountId',
73 foreignField: '_id',
74 as: 'account',
75 },
76 },
77 { $unwind: '$account' },
78 { $match: { 'account.status': AccountStatus.NORMAL } },
79 { $limit: limit },
80 { $addFields: { cursorId: '$_id' } },
81 { $project: { account: 0 } },
82 ]).exec()
83 }
84
85 async createOrUpdateByAccountId(
86 accountId: string,
87 platform: AccountType,
88 credentialData: Partial<OAuth2Credential>,
89 ) {
90 const setData = Object.fromEntries(
91 Object.entries(credentialData).filter(([, value]) => value !== undefined),
92 )
93 const unsetData = Object.fromEntries(
94 Object.entries(credentialData)
95 .filter(([, value]) => value === undefined)
96 .map(([key]) => [key, '']),
97 )
98
99 return await this.updateOne(
100 { accountId },
101 {
102 $set: {
103 platform,
104 ...setData,
105 },
106 ...(Object.keys(unsetData).length > 0 && { $unset: unsetData }),
107 $setOnInsert: { accountId },
108 },
109 { upsert: true },
110 )
111 }
112
113 async deleteByAccountId(accountId: string): Promise<boolean> {
114 const result = await this.deleteOne({ accountId })
115 return result.deletedCount > 0
116 }
117 }
118
118 lines TYPESCRIPT