mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 13:29:02 +08:00
feat: reduce backend (#14251)
#### PR Dependency Tree * **PR #14251** 👈 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** * Current user profile now exposes access tokens, revealed tokens, and detailed calendar accounts/subscriptions. * Workspace now exposes permissions, calendars, calendar events, and a workspace-scoped blob upload part URL. * New document-update mutation for applying doc updates. * **API Changes** * validateAppConfig is now a query (mutation deprecated). * Several legacy top-level calendar/blob endpoints deprecated in favor of user/workspace fields. * **Refactor** * Calendar, blob-upload and access-token surfaces reorganized to use user/workspace-centric fields. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -9,7 +9,14 @@ import { CalendarController } from './controller';
|
||||
import { CalendarCronJobs } from './cron';
|
||||
import { CalendarOAuthService } from './oauth';
|
||||
import { CalendarProviderFactory, CalendarProviders } from './providers';
|
||||
import { CalendarResolver } from './resolver';
|
||||
import {
|
||||
CalendarAccountResolver,
|
||||
CalendarMutationResolver,
|
||||
CalendarServerConfigResolver,
|
||||
UserCalendarResolver,
|
||||
WorkspaceCalendarEventsResolver,
|
||||
WorkspaceCalendarResolver,
|
||||
} from './resolver';
|
||||
import { CalendarService } from './service';
|
||||
|
||||
@Module({
|
||||
@@ -20,7 +27,12 @@ import { CalendarService } from './service';
|
||||
CalendarService,
|
||||
CalendarOAuthService,
|
||||
CalendarCronJobs,
|
||||
CalendarResolver,
|
||||
CalendarServerConfigResolver,
|
||||
UserCalendarResolver,
|
||||
CalendarAccountResolver,
|
||||
WorkspaceCalendarResolver,
|
||||
WorkspaceCalendarEventsResolver,
|
||||
CalendarMutationResolver,
|
||||
],
|
||||
controllers: [CalendarController],
|
||||
})
|
||||
|
||||
@@ -2,13 +2,17 @@ import {
|
||||
Args,
|
||||
GraphQLISODateTime,
|
||||
Mutation,
|
||||
Query,
|
||||
Parent,
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { AuthenticationRequired } from '../../base';
|
||||
import { ActionForbidden, AuthenticationRequired } from '../../base';
|
||||
import { CurrentUser } from '../../core/auth';
|
||||
import { ServerConfigType } from '../../core/config/types';
|
||||
import { AccessController } 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';
|
||||
@@ -22,70 +26,100 @@ import {
|
||||
WorkspaceCalendarObjectType,
|
||||
} from './types';
|
||||
|
||||
@Resolver(() => CalendarAccountObjectType)
|
||||
export class CalendarResolver {
|
||||
constructor(
|
||||
private readonly calendar: CalendarService,
|
||||
private readonly oauth: CalendarOAuthService,
|
||||
private readonly models: Models,
|
||||
private readonly access: AccessController,
|
||||
private readonly providerFactory: CalendarProviderFactory
|
||||
) {}
|
||||
@Resolver(() => ServerConfigType)
|
||||
export class CalendarServerConfigResolver {
|
||||
constructor(private readonly providerFactory: CalendarProviderFactory) {}
|
||||
|
||||
@Query(() => [CalendarAccountObjectType])
|
||||
async calendarAccounts(@CurrentUser() user: CurrentUser) {
|
||||
@ResolveField(() => [CalendarProviderName])
|
||||
calendarProviders() {
|
||||
return this.providerFactory.providers;
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => [CalendarSubscriptionObjectType])
|
||||
async calendarAccountCalendars(
|
||||
@Resolver(() => CalendarAccountObjectType)
|
||||
export class CalendarAccountResolver {
|
||||
constructor(private readonly calendar: CalendarService) {}
|
||||
|
||||
@ResolveField(() => [CalendarSubscriptionObjectType])
|
||||
async calendars(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('accountId') accountId: string
|
||||
@Parent() account: CalendarAccountObjectType
|
||||
) {
|
||||
return await this.calendar.listAccountCalendars(user.id, accountId);
|
||||
return await this.calendar.listAccountCalendars(user.id, account.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => [WorkspaceCalendarObjectType])
|
||||
async workspaceCalendars(
|
||||
@Resolver(() => WorkspaceType)
|
||||
export class WorkspaceCalendarResolver {
|
||||
constructor(
|
||||
private readonly calendar: CalendarService,
|
||||
private readonly access: AccessController
|
||||
) {}
|
||||
|
||||
@ResolveField(() => [WorkspaceCalendarObjectType])
|
||||
async calendars(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceId') workspaceId: string
|
||||
@Parent() workspace: WorkspaceType
|
||||
) {
|
||||
await this.access
|
||||
.user(user.id)
|
||||
.workspace(workspaceId)
|
||||
.assert('Workspace.CreateDoc');
|
||||
return await this.calendar.getWorkspaceCalendars(workspaceId);
|
||||
.workspace(workspace.id)
|
||||
.assert('Workspace.Settings.Read');
|
||||
return await this.calendar.getWorkspaceCalendars(workspace.id);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => [CalendarEventObjectType])
|
||||
async calendarEvents(
|
||||
@Resolver(() => WorkspaceCalendarObjectType)
|
||||
export class WorkspaceCalendarEventsResolver {
|
||||
constructor(
|
||||
private readonly calendar: CalendarService,
|
||||
private readonly access: AccessController
|
||||
) {}
|
||||
|
||||
@ResolveField(() => [CalendarEventObjectType])
|
||||
async events(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceCalendarId') workspaceCalendarId: string,
|
||||
@Parent() calendar: WorkspaceCalendarObjectType,
|
||||
@Args({ name: 'from', type: () => GraphQLISODateTime }) from: Date,
|
||||
@Args({ name: 'to', type: () => GraphQLISODateTime }) to: Date
|
||||
) {
|
||||
const workspaceCalendar =
|
||||
await this.models.workspaceCalendar.get(workspaceCalendarId);
|
||||
if (!workspaceCalendar) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await this.access
|
||||
.user(user.id)
|
||||
.workspace(workspaceCalendar.workspaceId)
|
||||
.assert('Workspace.CreateDoc');
|
||||
.workspace(calendar.workspaceId)
|
||||
.assert('Workspace.Settings.Read');
|
||||
|
||||
return await this.calendar.listWorkspaceEvents({
|
||||
workspaceCalendarId,
|
||||
workspaceCalendarId: calendar.id,
|
||||
from,
|
||||
to,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => [CalendarProviderName])
|
||||
async calendarProviders() {
|
||||
return this.providerFactory.providers;
|
||||
}
|
||||
@Resolver(() => CalendarAccountObjectType)
|
||||
export class CalendarMutationResolver {
|
||||
constructor(
|
||||
private readonly calendar: CalendarService,
|
||||
private readonly oauth: CalendarOAuthService,
|
||||
private readonly models: Models,
|
||||
private readonly access: AccessController
|
||||
) {}
|
||||
|
||||
@Mutation(() => String)
|
||||
async linkCalendarAccount(
|
||||
|
||||
@@ -825,6 +825,7 @@ export class CopilotResolver {
|
||||
@Query(() => String, {
|
||||
description:
|
||||
'Apply updates to a doc using LLM and return the merged markdown.',
|
||||
deprecationReason: 'use Mutation.applyDocUpdates',
|
||||
})
|
||||
async applyDocUpdates(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@@ -836,6 +837,35 @@ export class CopilotResolver {
|
||||
op: string,
|
||||
@Args({ name: 'updates', type: () => String })
|
||||
updates: string
|
||||
): Promise<string> {
|
||||
return this.applyDocUpdatesInternal(user, workspaceId, docId, op, updates);
|
||||
}
|
||||
|
||||
@Mutation(() => String, {
|
||||
description:
|
||||
'Apply updates to a doc using LLM and return the merged markdown.',
|
||||
name: 'applyDocUpdates',
|
||||
})
|
||||
async applyDocUpdatesMutation(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args({ name: 'workspaceId', type: () => String })
|
||||
workspaceId: string,
|
||||
@Args({ name: 'docId', type: () => String })
|
||||
docId: string,
|
||||
@Args({ name: 'op', type: () => String })
|
||||
op: string,
|
||||
@Args({ name: 'updates', type: () => String })
|
||||
updates: string
|
||||
): Promise<string> {
|
||||
return this.applyDocUpdatesInternal(user, workspaceId, docId, op, updates);
|
||||
}
|
||||
|
||||
private async applyDocUpdatesInternal(
|
||||
user: CurrentUser,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
op: string,
|
||||
updates: string
|
||||
): Promise<string> {
|
||||
await this.assertPermission(user, { workspaceId, docId });
|
||||
|
||||
|
||||
@@ -230,64 +230,57 @@ export class IndexerService {
|
||||
docId,
|
||||
docSnapshot.blob
|
||||
);
|
||||
if (result) {
|
||||
await this.write(
|
||||
SearchTable.doc,
|
||||
[
|
||||
{
|
||||
workspaceId,
|
||||
docId,
|
||||
title: result.title,
|
||||
summary: result.summary,
|
||||
// NOTE(@fengmk): journal is not supported yet
|
||||
// journal: result.journal,
|
||||
createdByUserId: docSnapshot.createdBy ?? '',
|
||||
updatedByUserId: docSnapshot.updatedBy ?? '',
|
||||
createdAt: docSnapshot.createdAt,
|
||||
updatedAt: docSnapshot.updatedAt,
|
||||
},
|
||||
],
|
||||
options
|
||||
);
|
||||
await this.deleteBlocksByDocId(workspaceId, docId, options);
|
||||
await this.write(
|
||||
SearchTable.block,
|
||||
result.blocks.map(block => ({
|
||||
await this.write(
|
||||
SearchTable.doc,
|
||||
[
|
||||
{
|
||||
workspaceId,
|
||||
docId,
|
||||
blockId: block.blockId,
|
||||
content: block.content ?? '',
|
||||
flavour: block.flavour,
|
||||
blob: block.blob,
|
||||
refDocId: block.refDocId,
|
||||
ref: block.ref,
|
||||
parentFlavour: block.parentFlavour,
|
||||
parentBlockId: block.parentBlockId,
|
||||
additional: block.additional
|
||||
? JSON.stringify(block.additional)
|
||||
: undefined,
|
||||
markdownPreview: undefined,
|
||||
title: result.title,
|
||||
summary: result.summary,
|
||||
// NOTE(@fengmk): journal is not supported yet
|
||||
// journal: result.journal,
|
||||
createdByUserId: docSnapshot.createdBy ?? '',
|
||||
updatedByUserId: docSnapshot.updatedBy ?? '',
|
||||
createdAt: docSnapshot.createdAt,
|
||||
updatedAt: docSnapshot.updatedAt,
|
||||
})),
|
||||
options
|
||||
);
|
||||
|
||||
await this.queue.add('copilot.embedding.updateDoc', {
|
||||
},
|
||||
],
|
||||
options
|
||||
);
|
||||
await this.deleteBlocksByDocId(workspaceId, docId, options);
|
||||
await this.write(
|
||||
SearchTable.block,
|
||||
result.blocks.map(block => ({
|
||||
workspaceId,
|
||||
docId,
|
||||
});
|
||||
this.logger.verbose(
|
||||
`synced doc ${workspaceId}/${docId} with ${result.blocks.length} blocks`
|
||||
);
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`failed to parse ${workspaceId}/${docId}, no result returned`,
|
||||
metadata
|
||||
);
|
||||
}
|
||||
blockId: block.blockId,
|
||||
content: block.content ?? '',
|
||||
flavour: block.flavour,
|
||||
blob: block.blob,
|
||||
refDocId: block.refDocId,
|
||||
ref: block.ref,
|
||||
parentFlavour: block.parentFlavour,
|
||||
parentBlockId: block.parentBlockId,
|
||||
additional: block.additional
|
||||
? JSON.stringify(block.additional)
|
||||
: undefined,
|
||||
markdownPreview: undefined,
|
||||
createdByUserId: docSnapshot.createdBy ?? '',
|
||||
updatedByUserId: docSnapshot.updatedBy ?? '',
|
||||
createdAt: docSnapshot.createdAt,
|
||||
updatedAt: docSnapshot.updatedAt,
|
||||
})),
|
||||
options
|
||||
);
|
||||
|
||||
await this.queue.add('copilot.embedding.updateDoc', {
|
||||
workspaceId,
|
||||
docId,
|
||||
});
|
||||
this.logger.verbose(
|
||||
`synced doc ${workspaceId}/${docId} with ${result.blocks.length} blocks`
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`failed to parse ${workspaceId}/${docId}: ${err}`,
|
||||
|
||||
Reference in New Issue
Block a user