mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
@@ -2,6 +2,7 @@ export * from './blobs';
|
||||
export * from './invite';
|
||||
export * from './notification';
|
||||
export * from './permission';
|
||||
export * from './settings';
|
||||
export * from './testing-app';
|
||||
export * from './testing-module';
|
||||
export * from './user';
|
||||
|
||||
33
packages/backend/server/src/__tests__/utils/settings.ts
Normal file
33
packages/backend/server/src/__tests__/utils/settings.ts
Normal file
@@ -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 { UserAvatarController } from './controller';
|
||||
import { UserEventsListener } from './event';
|
||||
import { UserManagementResolver, UserResolver } from './resolver';
|
||||
import {
|
||||
UserManagementResolver,
|
||||
UserResolver,
|
||||
UserSettingsResolver,
|
||||
} from './resolver';
|
||||
|
||||
@Module({
|
||||
imports: [StorageModule, PermissionModule],
|
||||
providers: [UserResolver, UserManagementResolver, UserEventsListener],
|
||||
providers: [
|
||||
UserResolver,
|
||||
UserManagementResolver,
|
||||
UserEventsListener,
|
||||
UserSettingsResolver,
|
||||
],
|
||||
controllers: [UserAvatarController],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Mutation,
|
||||
ObjectType,
|
||||
Query,
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
Throttle,
|
||||
UserNotFound,
|
||||
} from '../../base';
|
||||
import { Models } from '../../models';
|
||||
import { Models, SettingsSchema } from '../../models';
|
||||
import { Public } from '../auth/guard';
|
||||
import { sessionUser } from '../auth/service';
|
||||
import { CurrentUser } from '../auth/session';
|
||||
@@ -31,6 +32,8 @@ import {
|
||||
ManageUserInput,
|
||||
PublicUserType,
|
||||
RemoveAvatar,
|
||||
SettingsType,
|
||||
UpdateSettingsInput,
|
||||
UpdateUserInput,
|
||||
UserOrLimitedUser,
|
||||
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()
|
||||
class ListUserInput {
|
||||
@Field(() => Int, { nullable: true, defaultValue: 0 })
|
||||
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
} from '@nestjs/graphql';
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
import { PublicUser, WorkspaceUser } from '../../models';
|
||||
import {
|
||||
PublicUser,
|
||||
Settings,
|
||||
SettingsInput,
|
||||
WorkspaceUser,
|
||||
} from '../../models';
|
||||
import { type CurrentUser } from '../auth/session';
|
||||
|
||||
@ObjectType()
|
||||
@@ -109,6 +114,15 @@ export class RemoveAvatar {
|
||||
success!: boolean;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class SettingsType implements Settings {
|
||||
@Field({ description: 'Receive invitation email' })
|
||||
receiveInvitationEmail!: boolean;
|
||||
|
||||
@Field({ description: 'Receive mention email' })
|
||||
receiveMentionEmail!: boolean;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class UpdateUserInput implements Partial<User> {
|
||||
@Field({ description: 'User name', nullable: true })
|
||||
@@ -123,3 +137,12 @@ export class ManageUserInput {
|
||||
@Field({ description: 'User name', nullable: true })
|
||||
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"""
|
||||
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!
|
||||
|
||||
"""Update an user"""
|
||||
@@ -1278,6 +1281,14 @@ type ServerServiceConfig {
|
||||
name: String!
|
||||
}
|
||||
|
||||
type SettingsType {
|
||||
"""Receive invitation email"""
|
||||
receiveInvitationEmail: Boolean!
|
||||
|
||||
"""Receive mention email"""
|
||||
receiveMentionEmail: Boolean!
|
||||
}
|
||||
|
||||
type SpaceAccessDeniedDataType {
|
||||
spaceId: String!
|
||||
}
|
||||
@@ -1403,6 +1414,14 @@ input UpdateDocUserRoleInput {
|
||||
workspaceId: String!
|
||||
}
|
||||
|
||||
input UpdateSettingsInput {
|
||||
"""Receive invitation email"""
|
||||
receiveInvitationEmail: Boolean
|
||||
|
||||
"""Receive mention email"""
|
||||
receiveMentionEmail: Boolean
|
||||
}
|
||||
|
||||
input UpdateUserInput {
|
||||
"""User name"""
|
||||
name: String
|
||||
@@ -1495,6 +1514,9 @@ type UserType {
|
||||
notifications(pagination: PaginationInput!): PaginatedNotificationObjectType!
|
||||
quota: UserQuotaType!
|
||||
quotaUsage: UserQuotaUsageType!
|
||||
|
||||
"""Get user settings"""
|
||||
settings: SettingsType!
|
||||
subscriptions: [SubscriptionType!]!
|
||||
token: tokenType! @deprecated(reason: "use [/api/auth/sign-in?native=true] instead")
|
||||
}
|
||||
|
||||
@@ -1053,6 +1053,8 @@ export interface Mutation {
|
||||
updateRuntimeConfig: ServerRuntimeConfigType;
|
||||
/** update multiple server runtime configurable settings */
|
||||
updateRuntimeConfigs: Array<ServerRuntimeConfigType>;
|
||||
/** Update user settings */
|
||||
updateSettings: Scalars['Boolean']['output'];
|
||||
updateSubscriptionRecurring: SubscriptionType;
|
||||
/** Update an user */
|
||||
updateUser: UserType;
|
||||
@@ -1372,6 +1374,10 @@ export interface MutationUpdateRuntimeConfigsArgs {
|
||||
updates: Scalars['JSONObject']['input'];
|
||||
}
|
||||
|
||||
export interface MutationUpdateSettingsArgs {
|
||||
input: UpdateSettingsInput;
|
||||
}
|
||||
|
||||
export interface MutationUpdateSubscriptionRecurringArgs {
|
||||
idempotencyKey?: InputMaybe<Scalars['String']['input']>;
|
||||
plan?: InputMaybe<SubscriptionPlan>;
|
||||
@@ -1756,6 +1762,14 @@ export interface ServerServiceConfig {
|
||||
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 {
|
||||
__typename?: 'SpaceAccessDeniedDataType';
|
||||
spaceId: Scalars['String']['output'];
|
||||
@@ -1897,6 +1911,13 @@ export interface UpdateDocUserRoleInput {
|
||||
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 {
|
||||
/** User name */
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -1983,6 +2004,8 @@ export interface UserType {
|
||||
notifications: PaginatedNotificationObjectType;
|
||||
quota: UserQuotaType;
|
||||
quotaUsage: UserQuotaUsageType;
|
||||
/** Get user settings */
|
||||
settings: SettingsType;
|
||||
subscriptions: Array<SubscriptionType>;
|
||||
/** @deprecated use [/api/auth/sign-in?native=true] instead */
|
||||
token: TokenType;
|
||||
|
||||
Reference in New Issue
Block a user