mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-29 00:06:09 +08:00
@@ -2,6 +2,7 @@ export * from './blobs';
|
|||||||
export * from './invite';
|
export * from './invite';
|
||||||
export * from './notification';
|
export * from './notification';
|
||||||
export * from './permission';
|
export * from './permission';
|
||||||
|
export * from './settings';
|
||||||
export * from './testing-app';
|
export * from './testing-app';
|
||||||
export * from './testing-module';
|
export * from './testing-module';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { SettingsType, UpdateSettingsInput } from '../../core/user/types';
|
||||||
|
import type { TestingApp } from './testing-app';
|
||||||
|
|
||||||
|
export async function getSettings(app: TestingApp): Promise<SettingsType> {
|
||||||
|
const res = await app.gql(
|
||||||
|
`
|
||||||
|
query settings {
|
||||||
|
currentUser {
|
||||||
|
settings {
|
||||||
|
receiveInvitationEmail
|
||||||
|
receiveMentionEmail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
return res.currentUser.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSettings(
|
||||||
|
app: TestingApp,
|
||||||
|
input: UpdateSettingsInput
|
||||||
|
): Promise<boolean> {
|
||||||
|
const res = await app.gql(
|
||||||
|
`
|
||||||
|
mutation updateSettings($input: UpdateSettingsInput!) {
|
||||||
|
updateSettings(input: $input)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ input }
|
||||||
|
);
|
||||||
|
return res.updateSettings;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createTestingApp,
|
||||||
|
getSettings,
|
||||||
|
TestingApp,
|
||||||
|
updateSettings,
|
||||||
|
} from '../../../__tests__/utils';
|
||||||
|
|
||||||
|
let app: TestingApp;
|
||||||
|
|
||||||
|
test.before(async () => {
|
||||||
|
app = await createTestingApp();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.after.always(async () => {
|
||||||
|
await app.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get user settings', async t => {
|
||||||
|
await app.signup();
|
||||||
|
const settings = await getSettings(app);
|
||||||
|
t.deepEqual(settings, {
|
||||||
|
receiveInvitationEmail: true,
|
||||||
|
receiveMentionEmail: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update user settings', async t => {
|
||||||
|
await app.signup();
|
||||||
|
await updateSettings(app, {
|
||||||
|
receiveInvitationEmail: false,
|
||||||
|
receiveMentionEmail: false,
|
||||||
|
});
|
||||||
|
const settings = await getSettings(app);
|
||||||
|
t.deepEqual(settings, {
|
||||||
|
receiveInvitationEmail: false,
|
||||||
|
receiveMentionEmail: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateSettings(app, {
|
||||||
|
receiveMentionEmail: true,
|
||||||
|
});
|
||||||
|
const settings2 = await getSettings(app);
|
||||||
|
t.deepEqual(settings2, {
|
||||||
|
receiveInvitationEmail: false,
|
||||||
|
receiveMentionEmail: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateSettings(app, {
|
||||||
|
// ignore undefined value
|
||||||
|
receiveInvitationEmail: undefined,
|
||||||
|
});
|
||||||
|
const settings3 = await getSettings(app);
|
||||||
|
t.deepEqual(settings3, {
|
||||||
|
receiveInvitationEmail: false,
|
||||||
|
receiveMentionEmail: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw error when update user settings with invalid input', async t => {
|
||||||
|
await app.signup();
|
||||||
|
await t.throwsAsync(
|
||||||
|
updateSettings(app, {
|
||||||
|
receiveInvitationEmail: false,
|
||||||
|
// @ts-expect-error invalid value
|
||||||
|
receiveMentionEmail: null,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
message: /Expected boolean, received null/,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not update user settings when not logged in', async t => {
|
||||||
|
await app.logout();
|
||||||
|
await t.throwsAsync(
|
||||||
|
updateSettings(app, {
|
||||||
|
receiveInvitationEmail: false,
|
||||||
|
receiveMentionEmail: false,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
message: 'You must sign in first to access this resource.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -4,11 +4,20 @@ import { PermissionModule } from '../permission';
|
|||||||
import { StorageModule } from '../storage';
|
import { StorageModule } from '../storage';
|
||||||
import { UserAvatarController } from './controller';
|
import { UserAvatarController } from './controller';
|
||||||
import { UserEventsListener } from './event';
|
import { UserEventsListener } from './event';
|
||||||
import { UserManagementResolver, UserResolver } from './resolver';
|
import {
|
||||||
|
UserManagementResolver,
|
||||||
|
UserResolver,
|
||||||
|
UserSettingsResolver,
|
||||||
|
} from './resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [StorageModule, PermissionModule],
|
imports: [StorageModule, PermissionModule],
|
||||||
providers: [UserResolver, UserManagementResolver, UserEventsListener],
|
providers: [
|
||||||
|
UserResolver,
|
||||||
|
UserManagementResolver,
|
||||||
|
UserEventsListener,
|
||||||
|
UserSettingsResolver,
|
||||||
|
],
|
||||||
controllers: [UserAvatarController],
|
controllers: [UserAvatarController],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Mutation,
|
Mutation,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Query,
|
Query,
|
||||||
|
ResolveField,
|
||||||
Resolver,
|
Resolver,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
Throttle,
|
Throttle,
|
||||||
UserNotFound,
|
UserNotFound,
|
||||||
} from '../../base';
|
} from '../../base';
|
||||||
import { Models } from '../../models';
|
import { Models, SettingsSchema } from '../../models';
|
||||||
import { Public } from '../auth/guard';
|
import { Public } from '../auth/guard';
|
||||||
import { sessionUser } from '../auth/service';
|
import { sessionUser } from '../auth/service';
|
||||||
import { CurrentUser } from '../auth/session';
|
import { CurrentUser } from '../auth/session';
|
||||||
@@ -31,6 +32,8 @@ import {
|
|||||||
ManageUserInput,
|
ManageUserInput,
|
||||||
PublicUserType,
|
PublicUserType,
|
||||||
RemoveAvatar,
|
RemoveAvatar,
|
||||||
|
SettingsType,
|
||||||
|
UpdateSettingsInput,
|
||||||
UpdateUserInput,
|
UpdateUserInput,
|
||||||
UserOrLimitedUser,
|
UserOrLimitedUser,
|
||||||
UserType,
|
UserType,
|
||||||
@@ -155,6 +158,33 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Resolver(() => UserType)
|
||||||
|
export class UserSettingsResolver {
|
||||||
|
constructor(private readonly models: Models) {}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean, {
|
||||||
|
name: 'updateSettings',
|
||||||
|
description: 'Update user settings',
|
||||||
|
})
|
||||||
|
async updateSettings(
|
||||||
|
@CurrentUser() user: CurrentUser,
|
||||||
|
@Args('input', { type: () => UpdateSettingsInput })
|
||||||
|
input: UpdateSettingsInput
|
||||||
|
) {
|
||||||
|
SettingsSchema.parse(input);
|
||||||
|
await this.models.settings.set(user.id, input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => SettingsType, {
|
||||||
|
name: 'settings',
|
||||||
|
description: 'Get user settings',
|
||||||
|
})
|
||||||
|
async getSettings(@CurrentUser() me: CurrentUser): Promise<SettingsType> {
|
||||||
|
return await this.models.settings.get(me.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
class ListUserInput {
|
class ListUserInput {
|
||||||
@Field(() => Int, { nullable: true, defaultValue: 0 })
|
@Field(() => Int, { nullable: true, defaultValue: 0 })
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import {
|
|||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import type { User } from '@prisma/client';
|
import type { User } from '@prisma/client';
|
||||||
|
|
||||||
import { PublicUser, WorkspaceUser } from '../../models';
|
import {
|
||||||
|
PublicUser,
|
||||||
|
Settings,
|
||||||
|
SettingsInput,
|
||||||
|
WorkspaceUser,
|
||||||
|
} from '../../models';
|
||||||
import { type CurrentUser } from '../auth/session';
|
import { type CurrentUser } from '../auth/session';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
@@ -109,6 +114,15 @@ export class RemoveAvatar {
|
|||||||
success!: boolean;
|
success!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class SettingsType implements Settings {
|
||||||
|
@Field({ description: 'Receive invitation email' })
|
||||||
|
receiveInvitationEmail!: boolean;
|
||||||
|
|
||||||
|
@Field({ description: 'Receive mention email' })
|
||||||
|
receiveMentionEmail!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
export class UpdateUserInput implements Partial<User> {
|
export class UpdateUserInput implements Partial<User> {
|
||||||
@Field({ description: 'User name', nullable: true })
|
@Field({ description: 'User name', nullable: true })
|
||||||
@@ -123,3 +137,12 @@ export class ManageUserInput {
|
|||||||
@Field({ description: 'User name', nullable: true })
|
@Field({ description: 'User name', nullable: true })
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class UpdateSettingsInput implements SettingsInput {
|
||||||
|
@Field({ description: 'Receive invitation email', nullable: true })
|
||||||
|
receiveInvitationEmail?: boolean;
|
||||||
|
|
||||||
|
@Field({ description: 'Receive mention email', nullable: true })
|
||||||
|
receiveMentionEmail?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -943,6 +943,9 @@ type Mutation {
|
|||||||
|
|
||||||
"""update multiple server runtime configurable settings"""
|
"""update multiple server runtime configurable settings"""
|
||||||
updateRuntimeConfigs(updates: JSONObject!): [ServerRuntimeConfigType!]!
|
updateRuntimeConfigs(updates: JSONObject!): [ServerRuntimeConfigType!]!
|
||||||
|
|
||||||
|
"""Update user settings"""
|
||||||
|
updateSettings(input: UpdateSettingsInput!): Boolean!
|
||||||
updateSubscriptionRecurring(idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`"), plan: SubscriptionPlan = Pro, recurring: SubscriptionRecurring!, workspaceId: String): SubscriptionType!
|
updateSubscriptionRecurring(idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`"), plan: SubscriptionPlan = Pro, recurring: SubscriptionRecurring!, workspaceId: String): SubscriptionType!
|
||||||
|
|
||||||
"""Update an user"""
|
"""Update an user"""
|
||||||
@@ -1278,6 +1281,14 @@ type ServerServiceConfig {
|
|||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SettingsType {
|
||||||
|
"""Receive invitation email"""
|
||||||
|
receiveInvitationEmail: Boolean!
|
||||||
|
|
||||||
|
"""Receive mention email"""
|
||||||
|
receiveMentionEmail: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type SpaceAccessDeniedDataType {
|
type SpaceAccessDeniedDataType {
|
||||||
spaceId: String!
|
spaceId: String!
|
||||||
}
|
}
|
||||||
@@ -1403,6 +1414,14 @@ input UpdateDocUserRoleInput {
|
|||||||
workspaceId: String!
|
workspaceId: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UpdateSettingsInput {
|
||||||
|
"""Receive invitation email"""
|
||||||
|
receiveInvitationEmail: Boolean
|
||||||
|
|
||||||
|
"""Receive mention email"""
|
||||||
|
receiveMentionEmail: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
input UpdateUserInput {
|
input UpdateUserInput {
|
||||||
"""User name"""
|
"""User name"""
|
||||||
name: String
|
name: String
|
||||||
@@ -1495,6 +1514,9 @@ type UserType {
|
|||||||
notifications(pagination: PaginationInput!): PaginatedNotificationObjectType!
|
notifications(pagination: PaginationInput!): PaginatedNotificationObjectType!
|
||||||
quota: UserQuotaType!
|
quota: UserQuotaType!
|
||||||
quotaUsage: UserQuotaUsageType!
|
quotaUsage: UserQuotaUsageType!
|
||||||
|
|
||||||
|
"""Get user settings"""
|
||||||
|
settings: SettingsType!
|
||||||
subscriptions: [SubscriptionType!]!
|
subscriptions: [SubscriptionType!]!
|
||||||
token: tokenType! @deprecated(reason: "use [/api/auth/sign-in?native=true] instead")
|
token: tokenType! @deprecated(reason: "use [/api/auth/sign-in?native=true] instead")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1053,6 +1053,8 @@ export interface Mutation {
|
|||||||
updateRuntimeConfig: ServerRuntimeConfigType;
|
updateRuntimeConfig: ServerRuntimeConfigType;
|
||||||
/** update multiple server runtime configurable settings */
|
/** update multiple server runtime configurable settings */
|
||||||
updateRuntimeConfigs: Array<ServerRuntimeConfigType>;
|
updateRuntimeConfigs: Array<ServerRuntimeConfigType>;
|
||||||
|
/** Update user settings */
|
||||||
|
updateSettings: Scalars['Boolean']['output'];
|
||||||
updateSubscriptionRecurring: SubscriptionType;
|
updateSubscriptionRecurring: SubscriptionType;
|
||||||
/** Update an user */
|
/** Update an user */
|
||||||
updateUser: UserType;
|
updateUser: UserType;
|
||||||
@@ -1372,6 +1374,10 @@ export interface MutationUpdateRuntimeConfigsArgs {
|
|||||||
updates: Scalars['JSONObject']['input'];
|
updates: Scalars['JSONObject']['input'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MutationUpdateSettingsArgs {
|
||||||
|
input: UpdateSettingsInput;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MutationUpdateSubscriptionRecurringArgs {
|
export interface MutationUpdateSubscriptionRecurringArgs {
|
||||||
idempotencyKey?: InputMaybe<Scalars['String']['input']>;
|
idempotencyKey?: InputMaybe<Scalars['String']['input']>;
|
||||||
plan?: InputMaybe<SubscriptionPlan>;
|
plan?: InputMaybe<SubscriptionPlan>;
|
||||||
@@ -1756,6 +1762,14 @@ export interface ServerServiceConfig {
|
|||||||
name: Scalars['String']['output'];
|
name: Scalars['String']['output'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SettingsType {
|
||||||
|
__typename?: 'SettingsType';
|
||||||
|
/** Receive invitation email */
|
||||||
|
receiveInvitationEmail: Scalars['Boolean']['output'];
|
||||||
|
/** Receive mention email */
|
||||||
|
receiveMentionEmail: Scalars['Boolean']['output'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface SpaceAccessDeniedDataType {
|
export interface SpaceAccessDeniedDataType {
|
||||||
__typename?: 'SpaceAccessDeniedDataType';
|
__typename?: 'SpaceAccessDeniedDataType';
|
||||||
spaceId: Scalars['String']['output'];
|
spaceId: Scalars['String']['output'];
|
||||||
@@ -1897,6 +1911,13 @@ export interface UpdateDocUserRoleInput {
|
|||||||
workspaceId: Scalars['String']['input'];
|
workspaceId: Scalars['String']['input'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateSettingsInput {
|
||||||
|
/** Receive invitation email */
|
||||||
|
receiveInvitationEmail?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
|
/** Receive mention email */
|
||||||
|
receiveMentionEmail?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateUserInput {
|
export interface UpdateUserInput {
|
||||||
/** User name */
|
/** User name */
|
||||||
name?: InputMaybe<Scalars['String']['input']>;
|
name?: InputMaybe<Scalars['String']['input']>;
|
||||||
@@ -1983,6 +2004,8 @@ export interface UserType {
|
|||||||
notifications: PaginatedNotificationObjectType;
|
notifications: PaginatedNotificationObjectType;
|
||||||
quota: UserQuotaType;
|
quota: UserQuotaType;
|
||||||
quotaUsage: UserQuotaUsageType;
|
quotaUsage: UserQuotaUsageType;
|
||||||
|
/** Get user settings */
|
||||||
|
settings: SettingsType;
|
||||||
subscriptions: Array<SubscriptionType>;
|
subscriptions: Array<SubscriptionType>;
|
||||||
/** @deprecated use [/api/auth/sign-in?native=true] instead */
|
/** @deprecated use [/api/auth/sign-in?native=true] instead */
|
||||||
token: TokenType;
|
token: TokenType;
|
||||||
|
|||||||
Reference in New Issue
Block a user