feat(server): add settings model (#10755)

close CLOUD-166
This commit is contained in:
fengmk2
2025-03-18 00:41:21 +00:00
parent 3de7d85eea
commit 5dcbae6f86
5 changed files with 176 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import { randomUUID } from 'node:crypto';
import { mock } from 'node:test';
import ava, { TestFn } from 'ava';
import { ZodError } from 'zod';
import { createTestingModule, type TestingModule } from '../../__tests__/utils';
import { Config } from '../../base/config';
import { Models, User } from '..';
interface Context {
config: Config;
module: TestingModule;
models: Models;
}
const test = ava as TestFn<Context>;
test.before(async t => {
const module = await createTestingModule();
t.context.models = module.get(Models);
t.context.config = module.get(Config);
t.context.module = module;
await t.context.module.initTestingDB();
});
let user: User;
test.beforeEach(async t => {
user = await t.context.models.user.create({
email: `test-${randomUUID()}@affine.pro`,
});
});
test.afterEach.always(() => {
mock.reset();
mock.timers.reset();
});
test.after(async t => {
await t.context.module.close();
});
test('should get a user settings with default value', async t => {
const settings = await t.context.models.settings.get(user.id);
t.deepEqual(settings, {
receiveInvitationEmail: true,
receiveMentionEmail: true,
});
});
test('should update a user settings', async t => {
const settings = await t.context.models.settings.set(user.id, {
receiveInvitationEmail: false,
});
t.deepEqual(settings, {
receiveInvitationEmail: false,
receiveMentionEmail: true,
});
const settings2 = await t.context.models.settings.get(user.id);
t.deepEqual(settings2, settings);
// update existing setting
const setting3 = await t.context.models.settings.set(user.id, {
receiveInvitationEmail: true,
});
t.deepEqual(setting3, {
receiveInvitationEmail: true,
receiveMentionEmail: true,
});
const setting4 = await t.context.models.settings.get(user.id);
t.deepEqual(setting4, setting3);
const setting5 = await t.context.models.settings.set(user.id, {
receiveMentionEmail: false,
receiveInvitationEmail: false,
});
t.deepEqual(setting5, {
receiveInvitationEmail: false,
receiveMentionEmail: false,
});
const setting6 = await t.context.models.settings.get(user.id);
t.deepEqual(setting6, setting5);
});
test('should throw error when update settings with invalid payload', async t => {
await t.throwsAsync(
t.context.models.settings.set(user.id, {
// @ts-expect-error invalid setting input types
receiveInvitationEmail: 1,
}),
{
instanceOf: ZodError,
message: /Expected boolean, received number/,
}
);
});

View File

@@ -14,6 +14,7 @@ import { HistoryModel } from './history';
import { NotificationModel } from './notification';
import { MODELS_SYMBOL } from './provider';
import { SessionModel } from './session';
import { SettingsModel } from './settings';
import { UserModel } from './user';
import { UserDocModel } from './user-doc';
import { UserFeatureModel } from './user-feature';
@@ -36,6 +37,7 @@ const MODELS = {
docUser: DocUserModel,
history: HistoryModel,
notification: NotificationModel,
settings: SettingsModel,
};
type ModelsType = {
@@ -94,6 +96,7 @@ export * from './feature';
export * from './history';
export * from './notification';
export * from './session';
export * from './settings';
export * from './user';
export * from './user-doc';
export * from './user-feature';

View File

@@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import { Transactional } from '@nestjs-cls/transactional';
import z from 'zod';
import { BaseModel } from './base';
export const SettingsSchema = z.object({
receiveInvitationEmail: z.boolean().default(true),
receiveMentionEmail: z.boolean().default(true),
});
export type SettingsInput = z.input<typeof SettingsSchema>;
export type Settings = z.infer<typeof SettingsSchema>;
/**
* Settings Model
*/
@Injectable()
export class SettingsModel extends BaseModel {
@Transactional()
async set(userId: string, setting: SettingsInput) {
const existsSetting = await this.get(userId);
const payload = SettingsSchema.parse({
...existsSetting,
...setting,
});
await this.db.settings.upsert({
where: {
userId,
},
update: {
payload,
},
create: {
userId,
payload,
},
});
this.logger.log(`Settings updated for user ${userId}`);
return payload;
}
async get(userId: string): Promise<Settings> {
const row = await this.db.settings.findUnique({
where: {
userId,
},
});
return SettingsSchema.parse(row?.payload ?? {});
}
}