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:
DarkSky
2026-01-14 00:01:07 +08:00
committed by GitHub
parent 7c440686ad
commit 7c24b2521a
41 changed files with 978 additions and 503 deletions
@@ -8,8 +8,9 @@ import { Store } from '@toeverything/infra';
import type { GraphQLService } from '../services/graphql';
export type AccessToken =
ListUserAccessTokensQuery['revealedAccessTokens'][number];
export type AccessToken = NonNullable<
ListUserAccessTokensQuery['currentUser']
>['revealedAccessTokens'][number];
export class AccessTokenStore extends Store {
constructor(private readonly gqlService: GraphQLService) {
@@ -22,7 +23,7 @@ export class AccessTokenStore extends Store {
context: { signal },
});
return data.revealedAccessTokens;
return data.currentUser?.revealedAccessTokens ?? [];
}
async generateUserAccessToken(
@@ -1,4 +1,4 @@
import { copilotQuotaQuery } from '@affine/graphql';
import { getCurrentUserProfileQuery } from '@affine/graphql';
import { Store } from '@toeverything/infra';
import type { GraphQLService } from '../services/graphql';
@@ -10,7 +10,7 @@ export class UserCopilotQuotaStore extends Store {
async fetchUserCopilotQuota(abortSignal?: AbortSignal) {
const data = await this.graphqlService.gql({
query: copilotQuotaQuery,
query: getCurrentUserProfileQuery,
context: {
signal: abortSignal,
},
@@ -1,4 +1,4 @@
import { getUserFeaturesQuery } from '@affine/graphql';
import { getCurrentUserProfileQuery } from '@affine/graphql';
import { Store } from '@toeverything/infra';
import type { GraphQLService } from '../services/graphql';
@@ -10,7 +10,7 @@ export class UserFeatureStore extends Store {
async getUserFeatures(signal: AbortSignal) {
const data = await this.gqlService.gql({
query: getUserFeaturesQuery,
query: getCurrentUserProfileQuery,
context: {
signal,
},
@@ -1,4 +1,4 @@
import { quotaQuery } from '@affine/graphql';
import { getCurrentUserProfileQuery } from '@affine/graphql';
import { Store } from '@toeverything/infra';
import type { GraphQLService } from '../services/graphql';
@@ -10,7 +10,7 @@ export class UserQuotaStore extends Store {
async fetchUserQuota(abortSignal?: AbortSignal) {
const data = await this.graphqlService.gql({
query: quotaQuery,
query: getCurrentUserProfileQuery,
context: {
signal: abortSignal,
},
@@ -1,6 +1,6 @@
import {
type GetUserSettingsQuery,
getUserSettingsQuery,
type GetCurrentUserProfileQuery,
getCurrentUserProfileQuery,
type UpdateUserSettingsInput,
updateUserSettingsMutation,
} from '@affine/graphql';
@@ -9,7 +9,7 @@ import { Store } from '@toeverything/infra';
import type { GraphQLService } from '../services/graphql';
export type UserSettings = NonNullable<
GetUserSettingsQuery['currentUser']
GetCurrentUserProfileQuery['currentUser']
>['settings'];
export type { UpdateUserSettingsInput };
@@ -21,7 +21,7 @@ export class UserSettingsStore extends Store {
async getUserSettings(): Promise<UserSettings | undefined> {
const result = await this.gqlService.gql({
query: getUserSettingsQuery,
query: getCurrentUserProfileQuery,
});
return result.currentUser?.settings;
}
@@ -1,5 +1,4 @@
import type {
CalendarAccountCalendarsQuery,
CalendarAccountsQuery,
CalendarEventsQuery,
WorkspaceCalendarItemInput,
@@ -16,20 +15,29 @@ export class CalendarIntegration extends Entity {
super();
}
accounts$ = new LiveData<CalendarAccountsQuery['calendarAccounts'][number][]>(
[]
);
accounts$ = new LiveData<
NonNullable<
CalendarAccountsQuery['currentUser']
>['calendarAccounts'][number][]
>([]);
accountCalendars$ = new LiveData<
Map<
string,
CalendarAccountCalendarsQuery['calendarAccountCalendars'][number][]
NonNullable<
NonNullable<
CalendarAccountsQuery['currentUser']
>['calendarAccounts'][number]
>['calendars']
>
>(new Map());
workspaceCalendars$ = new LiveData<
WorkspaceCalendarsQuery['workspaceCalendars'][number][]
WorkspaceCalendarsQuery['workspace']['calendars'][number][]
>([]);
readonly eventsByDateMap$ = new LiveData<
Map<string, CalendarEventsQuery['calendarEvents'][number][]>
Map<
string,
CalendarEventsQuery['workspace']['calendars'][number]['events'][number][]
>
>(new Map());
readonly eventDates$ = LiveData.computed(get => {
const eventsByDateMap = get(this.eventsByDateMap$);
@@ -48,7 +56,11 @@ export class CalendarIntegration extends Entity {
const subscriptionInfo = new Map<
string,
{
subscription: CalendarAccountCalendarsQuery['calendarAccountCalendars'][number];
subscription: NonNullable<
NonNullable<
CalendarAccountsQuery['currentUser']
>['calendarAccounts'][number]
>['calendars'][number];
colorOverride?: string | null;
}
>();
@@ -113,23 +125,16 @@ export class CalendarIntegration extends Entity {
const calendarsByAccount = new Map<
string,
CalendarAccountCalendarsQuery['calendarAccountCalendars'][number][]
NonNullable<
NonNullable<
CalendarAccountsQuery['currentUser']
>['calendarAccounts'][number]
>['calendars']
>();
await Promise.all(
accounts.map(async account => {
try {
const calendars = await this.store.fetchAccountCalendars(
account.id,
signal
);
calendarsByAccount.set(account.id, calendars);
} catch (error) {
console.error('Failed to load calendar subscriptions', error);
calendarsByAccount.set(account.id, []);
}
})
);
accounts.forEach(account => {
calendarsByAccount.set(account.id, account.calendars ?? []);
});
this.accountCalendars$.setValue(calendarsByAccount);
return calendarsByAccount;
@@ -1,6 +1,4 @@
import {
type CalendarAccountCalendarsQuery,
calendarAccountCalendarsQuery,
type CalendarAccountsQuery,
calendarAccountsQuery,
type CalendarEventsQuery,
@@ -34,38 +32,27 @@ export class CalendarStore extends Store {
async fetchAccounts(signal?: AbortSignal) {
const gql = this.gql;
if (!gql) return [] satisfies CalendarAccountsQuery['calendarAccounts'];
if (!gql)
return [] satisfies NonNullable<
CalendarAccountsQuery['currentUser']
>['calendarAccounts'];
const data = await gql({
query: calendarAccountsQuery,
context: { signal },
});
return data.calendarAccounts;
}
async fetchAccountCalendars(accountId: string, signal?: AbortSignal) {
const gql = this.gql;
if (!gql) {
return [] satisfies CalendarAccountCalendarsQuery['calendarAccountCalendars'];
}
const data = await gql({
query: calendarAccountCalendarsQuery,
variables: { accountId },
context: { signal },
});
return data.calendarAccountCalendars;
return data.currentUser?.calendarAccounts ?? [];
}
async fetchWorkspaceCalendars(signal?: AbortSignal) {
const gql = this.gql;
if (!gql) {
return [] satisfies WorkspaceCalendarsQuery['workspaceCalendars'];
}
if (!gql)
return [] satisfies WorkspaceCalendarsQuery['workspace']['calendars'];
const data = await gql({
query: workspaceCalendarsQuery,
variables: { workspaceId: this.workspaceId },
context: { signal },
});
return data.workspaceCalendars;
return data.workspace.calendars;
}
async updateWorkspaceCalendars(items: WorkspaceCalendarItemInput[]) {
@@ -92,16 +79,19 @@ export class CalendarStore extends Store {
signal?: AbortSignal
) {
const gql = this.gql;
if (!gql) return [] satisfies CalendarEventsQuery['calendarEvents'];
if (!gql)
return [] satisfies CalendarEventsQuery['workspace']['calendars'][number]['events'];
const data = await gql({
query: calendarEventsQuery,
variables: {
workspaceCalendarId,
workspaceId: this.workspaceId,
from,
to,
},
context: { signal },
});
return data.calendarEvents;
const calendars = data.workspace.calendars;
const calendar = calendars.find(item => item.id === workspaceCalendarId);
return calendar?.events ?? [];
}
}
@@ -60,7 +60,7 @@ export class NotificationStore extends Store {
},
});
return result.currentUser?.notificationCount;
return result.currentUser?.notifications.totalCount;
}
async listNotification(pagination: PaginationInput, signal?: AbortSignal) {
@@ -1,8 +1,8 @@
import {
type GetDocRolePermissionsQuery,
getDocRolePermissionsQuery,
type GetWorkspaceRolePermissionsQuery,
getWorkspaceRolePermissionsQuery,
type GetWorkspaceInfoQuery,
getWorkspaceInfoQuery,
} from '@affine/graphql';
import { Store } from '@toeverything/infra';
@@ -10,7 +10,7 @@ import type { WorkspaceServerService } from '../../cloud';
import type { WorkspaceService } from '../../workspace';
export type WorkspacePermissionActions = keyof Omit<
GetWorkspaceRolePermissionsQuery['workspaceRolePermissions']['permissions'],
GetWorkspaceInfoQuery['workspace']['permissions'],
'__typename'
>;
@@ -34,12 +34,12 @@ export class GuardStore extends Store {
throw new Error('No server');
}
const data = await this.workspaceServerService.server.gql({
query: getWorkspaceRolePermissionsQuery,
query: getWorkspaceInfoQuery,
variables: {
id: this.workspaceService.workspace.id,
workspaceId: this.workspaceService.workspace.id,
},
});
return data.workspaceRolePermissions.permissions;
return data.workspace.permissions;
}
async getDocPermissions(