mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 02:26:21 +08:00
c53457691d
#### PR Dependency Tree * **PR #14996** 👈 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 * **New Features** * Admin mutations to grant/revoke commercial entitlements. * New Doc comment-update permission. * Realtime user/workspace quota-state endpoints and live-update rooms. * **Bug Fixes** * More accurate readable-doc filtering and permission evaluation. * **Refactor** * Workspace feature management moved to entitlement-based model; permission and quota pipelines redesigned. * Admin workspace UI now edits flags only (feature toggles removed). * **Tests** * Extensive new and updated tests for permissions, entitlements, quota, projection, and backfills. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/14996?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
248 lines
6.8 KiB
TypeScript
248 lines
6.8 KiB
TypeScript
import {
|
|
Args,
|
|
GraphQLISODateTime,
|
|
Int,
|
|
Mutation,
|
|
Parent,
|
|
ResolveField,
|
|
Resolver,
|
|
} from '@nestjs/graphql';
|
|
|
|
import { ActionForbidden, AuthenticationRequired, Config } from '../../base';
|
|
import { CurrentUser } from '../../core/auth';
|
|
import { ServerConfigType } from '../../core/config/types';
|
|
import { PermissionAccess } from '../../core/permission';
|
|
import { UserType } from '../../core/user';
|
|
import { WorkspaceType } from '../../core/workspaces';
|
|
import { Models } from '../../models';
|
|
import { CalendarOAuthService } from './oauth';
|
|
import { CalendarProviderFactory, CalendarProviderName } from './providers';
|
|
import { CalendarService } from './service';
|
|
import {
|
|
CalendarAccountObjectType,
|
|
CalendarCalDAVProviderPresetObjectType,
|
|
CalendarEventObjectType,
|
|
CalendarSubscriptionObjectType,
|
|
LinkCalDAVAccountInput,
|
|
LinkCalendarAccountInput,
|
|
UpdateWorkspaceCalendarsInput,
|
|
WorkspaceCalendarObjectType,
|
|
} from './types';
|
|
|
|
@Resolver(() => ServerConfigType)
|
|
export class CalendarServerConfigResolver {
|
|
constructor(
|
|
private readonly providerFactory: CalendarProviderFactory,
|
|
private readonly config: Config,
|
|
private readonly calendar: CalendarService
|
|
) {}
|
|
|
|
@ResolveField(() => [CalendarProviderName])
|
|
async calendarProviders(@CurrentUser() user?: CurrentUser) {
|
|
const providers = [];
|
|
for (const provider of this.providerFactory.providers) {
|
|
if (!(await this.calendar.canLinkProvider(user?.id, provider))) {
|
|
continue;
|
|
}
|
|
providers.push(provider);
|
|
}
|
|
return providers;
|
|
}
|
|
|
|
@ResolveField(() => [CalendarCalDAVProviderPresetObjectType])
|
|
calendarCalDAVProviders() {
|
|
const caldavConfig = this.config.calendar.caldav;
|
|
if (!caldavConfig?.enabled) {
|
|
return [];
|
|
}
|
|
return caldavConfig.providers.map(provider => ({
|
|
id: provider.id,
|
|
label: provider.label,
|
|
requiresAppPassword: provider.requiresAppPassword ?? null,
|
|
docsUrl: provider.docsUrl ?? null,
|
|
}));
|
|
}
|
|
}
|
|
|
|
@Resolver(() => UserType)
|
|
export class UserCalendarResolver {
|
|
constructor(private readonly calendar: CalendarService) {}
|
|
|
|
@ResolveField(() => [CalendarAccountObjectType])
|
|
async calendarAccounts(
|
|
@CurrentUser() currentUser: CurrentUser,
|
|
@Parent() user: UserType
|
|
) {
|
|
if (!currentUser || currentUser.id !== user.id) {
|
|
throw new ActionForbidden();
|
|
}
|
|
return await this.calendar.listAccounts(user.id);
|
|
}
|
|
}
|
|
|
|
@Resolver(() => CalendarAccountObjectType)
|
|
export class CalendarAccountResolver {
|
|
constructor(private readonly calendar: CalendarService) {}
|
|
|
|
@ResolveField(() => Int)
|
|
async calendarsCount(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Parent() account: CalendarAccountObjectType
|
|
) {
|
|
if (typeof account.calendarsCount === 'number') {
|
|
return account.calendarsCount;
|
|
}
|
|
|
|
const calendars = await this.calendar.listAccountCalendars(
|
|
user.id,
|
|
account.id
|
|
);
|
|
return calendars.length;
|
|
}
|
|
|
|
@ResolveField(() => [CalendarSubscriptionObjectType])
|
|
async calendars(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Parent() account: CalendarAccountObjectType
|
|
) {
|
|
return await this.calendar.listAccountCalendars(user.id, account.id);
|
|
}
|
|
}
|
|
|
|
@Resolver(() => WorkspaceType)
|
|
export class WorkspaceCalendarResolver {
|
|
constructor(
|
|
private readonly calendar: CalendarService,
|
|
private readonly access: PermissionAccess
|
|
) {}
|
|
|
|
@ResolveField(() => [WorkspaceCalendarObjectType])
|
|
async calendars(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Parent() workspace: WorkspaceType
|
|
) {
|
|
await this.access
|
|
.user(user.id)
|
|
.workspace(workspace.id)
|
|
.assert('Workspace.Settings.Read');
|
|
return await this.calendar.getWorkspaceCalendars(workspace.id);
|
|
}
|
|
}
|
|
|
|
@Resolver(() => WorkspaceCalendarObjectType)
|
|
export class WorkspaceCalendarEventsResolver {
|
|
constructor(
|
|
private readonly calendar: CalendarService,
|
|
private readonly access: PermissionAccess
|
|
) {}
|
|
|
|
@ResolveField(() => [CalendarEventObjectType])
|
|
async events(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Parent() calendar: WorkspaceCalendarObjectType,
|
|
@Args({ name: 'from', type: () => GraphQLISODateTime }) from: Date,
|
|
@Args({ name: 'to', type: () => GraphQLISODateTime }) to: Date
|
|
) {
|
|
await this.access
|
|
.user(user.id)
|
|
.workspace(calendar.workspaceId)
|
|
.assert('Workspace.Settings.Read');
|
|
|
|
return await this.calendar.listWorkspaceEvents({
|
|
workspaceCalendarId: calendar.id,
|
|
from,
|
|
to,
|
|
});
|
|
}
|
|
}
|
|
|
|
@Resolver(() => CalendarAccountObjectType)
|
|
export class CalendarMutationResolver {
|
|
constructor(
|
|
private readonly calendar: CalendarService,
|
|
private readonly oauth: CalendarOAuthService,
|
|
private readonly models: Models,
|
|
private readonly access: PermissionAccess
|
|
) {}
|
|
|
|
@Mutation(() => String)
|
|
async linkCalendarAccount(
|
|
@CurrentUser() user: CurrentUser | null,
|
|
@Args('input') input: LinkCalendarAccountInput
|
|
) {
|
|
if (!user) {
|
|
throw new AuthenticationRequired();
|
|
}
|
|
|
|
await this.calendar.assertCanLinkProvider(user.id, input.provider);
|
|
|
|
const state = await this.oauth.saveOAuthState({
|
|
provider: input.provider,
|
|
userId: user.id,
|
|
redirectUri: input.redirectUri ?? undefined,
|
|
});
|
|
|
|
const callbackUrl = this.calendar.getCallbackUrl();
|
|
return this.calendar.getAuthUrl(input.provider, state, callbackUrl);
|
|
}
|
|
|
|
@Mutation(() => CalendarAccountObjectType)
|
|
async linkCalDAVAccount(
|
|
@CurrentUser() user: CurrentUser | null,
|
|
@Args('input') input: LinkCalDAVAccountInput
|
|
) {
|
|
if (!user) {
|
|
throw new AuthenticationRequired();
|
|
}
|
|
|
|
return await this.calendar.linkCalDAVAccount({
|
|
userId: user.id,
|
|
input,
|
|
});
|
|
}
|
|
|
|
@Mutation(() => CalendarAccountObjectType, { nullable: true })
|
|
async updateCalendarAccount(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Args('accountId') accountId: string,
|
|
@Args('refreshIntervalMinutes') refreshIntervalMinutes: number
|
|
) {
|
|
return await this.calendar.updateAccountRefreshInterval(
|
|
user.id,
|
|
accountId,
|
|
refreshIntervalMinutes
|
|
);
|
|
}
|
|
|
|
@Mutation(() => Boolean)
|
|
async unlinkCalendarAccount(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Args('accountId') accountId: string
|
|
) {
|
|
return await this.calendar.unlinkAccount(user.id, accountId);
|
|
}
|
|
|
|
@Mutation(() => WorkspaceCalendarObjectType)
|
|
async updateWorkspaceCalendars(
|
|
@CurrentUser() user: CurrentUser,
|
|
@Args('input') input: UpdateWorkspaceCalendarsInput
|
|
) {
|
|
await this.access
|
|
.user(user.id)
|
|
.workspace(input.workspaceId)
|
|
.assert('Workspace.Settings.Update');
|
|
|
|
const calendar = await this.calendar.updateWorkspaceCalendars({
|
|
workspaceId: input.workspaceId,
|
|
userId: user.id,
|
|
items: input.items,
|
|
});
|
|
|
|
const items = await this.models.workspaceCalendar.listItems(calendar.id);
|
|
return {
|
|
...calendar,
|
|
items,
|
|
};
|
|
}
|
|
}
|