mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-13 08:06:24 +08:00
41b3b0e82e
#### PR Dependency Tree * **PR #14510** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved calendar sync reliability with exponential backoff for repeated failures. * Better handling of token refresh failures with automatic account invalidation and cleanup when needed. * Subscriptions are now automatically disabled and related events removed when the calendar provider reports missing resources. * **Tests** * Added comprehensive tests covering sync failures, backoff behavior, token refresh handling, skipping retries during backoff, and recovery. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
212 lines
5.4 KiB
TypeScript
212 lines
5.4 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { Transactional } from '@nestjs-cls/transactional';
|
|
import type { CalendarSubscription, Prisma } from '@prisma/client';
|
|
|
|
import { BaseModel } from './base';
|
|
|
|
export interface UpsertCalendarSubscriptionInput {
|
|
accountId: string;
|
|
provider: string;
|
|
externalCalendarId: string;
|
|
displayName?: string | null;
|
|
timezone?: string | null;
|
|
color?: string | null;
|
|
enabled?: boolean;
|
|
}
|
|
|
|
export interface UpdateCalendarSubscriptionSyncInput {
|
|
syncToken?: string | null;
|
|
lastSyncAt?: Date | null;
|
|
}
|
|
|
|
export interface UpdateCalendarSubscriptionChannelInput {
|
|
customChannelId?: string | null;
|
|
customResourceId?: string | null;
|
|
channelExpiration?: Date | null;
|
|
}
|
|
|
|
@Injectable()
|
|
export class CalendarSubscriptionModel extends BaseModel {
|
|
async listByAccount(accountId: string) {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: { accountId },
|
|
orderBy: { createdAt: 'asc' },
|
|
});
|
|
}
|
|
|
|
async listByAccountIds(accountIds: string[]) {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: { accountId: { in: accountIds } },
|
|
});
|
|
}
|
|
|
|
async get(id: string) {
|
|
return await this.db.calendarSubscription.findUnique({
|
|
where: { id },
|
|
});
|
|
}
|
|
|
|
async getByChannelId(customChannelId: string) {
|
|
return await this.db.calendarSubscription.findFirst({
|
|
where: { customChannelId },
|
|
});
|
|
}
|
|
|
|
async upsert(input: UpsertCalendarSubscriptionInput) {
|
|
const data: Prisma.CalendarSubscriptionUncheckedCreateInput = {
|
|
accountId: input.accountId,
|
|
provider: input.provider,
|
|
externalCalendarId: input.externalCalendarId,
|
|
displayName: input.displayName ?? null,
|
|
timezone: input.timezone ?? null,
|
|
color: input.color ?? null,
|
|
enabled: input.enabled ?? true,
|
|
};
|
|
|
|
return await this.db.calendarSubscription.upsert({
|
|
where: {
|
|
accountId_externalCalendarId: {
|
|
accountId: input.accountId,
|
|
externalCalendarId: input.externalCalendarId,
|
|
},
|
|
},
|
|
create: data,
|
|
update: {
|
|
displayName: data.displayName,
|
|
timezone: data.timezone,
|
|
color: data.color,
|
|
enabled: data.enabled,
|
|
},
|
|
});
|
|
}
|
|
|
|
async updateSync(id: string, input: UpdateCalendarSubscriptionSyncInput) {
|
|
return await this.db.calendarSubscription.update({
|
|
where: { id },
|
|
data: {
|
|
syncToken: input.syncToken ?? null,
|
|
lastSyncAt: input.lastSyncAt ?? null,
|
|
},
|
|
});
|
|
}
|
|
|
|
async updateChannel(
|
|
id: string,
|
|
input: UpdateCalendarSubscriptionChannelInput
|
|
) {
|
|
return await this.db.calendarSubscription.update({
|
|
where: { id },
|
|
data: {
|
|
customChannelId: input.customChannelId ?? null,
|
|
customResourceId: input.customResourceId ?? null,
|
|
channelExpiration: input.channelExpiration ?? null,
|
|
},
|
|
});
|
|
}
|
|
|
|
async updateEnabled(id: string, enabled: boolean) {
|
|
return await this.db.calendarSubscription.update({
|
|
where: { id },
|
|
data: { enabled },
|
|
});
|
|
}
|
|
|
|
async deleteByAccount(accountId: string) {
|
|
return await this.db.calendarSubscription.deleteMany({
|
|
where: { accountId },
|
|
});
|
|
}
|
|
|
|
async deleteByIds(ids: string[]) {
|
|
return await this.db.calendarSubscription.deleteMany({
|
|
where: { id: { in: ids } },
|
|
});
|
|
}
|
|
|
|
async listActiveByAccount(accountId: string) {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: { accountId, enabled: true },
|
|
});
|
|
}
|
|
|
|
async listWithAccount(id: string) {
|
|
return await this.db.calendarSubscription.findUnique({
|
|
where: { id },
|
|
include: { account: true },
|
|
});
|
|
}
|
|
|
|
async listWithAccounts(ids: string[]) {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: { id: { in: ids } },
|
|
include: { account: true },
|
|
});
|
|
}
|
|
|
|
async listAccountSubscriptions(
|
|
accountId: string,
|
|
subscriptionIds?: string[]
|
|
) {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: {
|
|
accountId,
|
|
...(subscriptionIds ? { id: { in: subscriptionIds } } : undefined),
|
|
},
|
|
});
|
|
}
|
|
|
|
async listAllWithAccountForSync() {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: { enabled: true },
|
|
include: { account: true },
|
|
});
|
|
}
|
|
|
|
async listByAccountForSync(accountId: string) {
|
|
return await this.db.calendarSubscription.findMany({
|
|
where: { accountId, enabled: true },
|
|
include: { account: true },
|
|
});
|
|
}
|
|
|
|
async updateLastSyncAt(id: string, lastSyncAt: Date) {
|
|
return await this.db.calendarSubscription.update({
|
|
where: { id },
|
|
data: { lastSyncAt },
|
|
});
|
|
}
|
|
|
|
async clearSyncTokensByAccount(accountId: string) {
|
|
return await this.db.calendarSubscription.updateMany({
|
|
where: { accountId },
|
|
data: { syncToken: null },
|
|
});
|
|
}
|
|
|
|
async updateManyStatus(
|
|
ids: string[],
|
|
data: Partial<Pick<CalendarSubscription, 'enabled'>>
|
|
) {
|
|
return await this.db.calendarSubscription.updateMany({
|
|
where: { id: { in: ids } },
|
|
data,
|
|
});
|
|
}
|
|
|
|
@Transactional()
|
|
async disableAndPurge(subscriptionId: string) {
|
|
await this.db.calendarSubscription.update({
|
|
where: { id: subscriptionId },
|
|
data: {
|
|
enabled: false,
|
|
syncToken: null,
|
|
customChannelId: null,
|
|
customResourceId: null,
|
|
channelExpiration: null,
|
|
},
|
|
});
|
|
|
|
await this.models.calendarEvent.deleteBySubscriptionIds([subscriptionId]);
|
|
}
|
|
}
|