feat(server): add model base (#9734)
This commit is contained in:
@@ -13,9 +13,7 @@ interface Context {
|
|||||||
const test = ava as TestFn<Context>;
|
const test = ava as TestFn<Context>;
|
||||||
|
|
||||||
test.before(async t => {
|
test.before(async t => {
|
||||||
const module = await createTestingModule({
|
const module = await createTestingModule({});
|
||||||
providers: [FeatureModel],
|
|
||||||
});
|
|
||||||
|
|
||||||
t.context.feature = module.get(FeatureModel);
|
t.context.feature = module.get(FeatureModel);
|
||||||
t.context.module = module;
|
t.context.module = module;
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ interface Context {
|
|||||||
const test = ava as TestFn<Context>;
|
const test = ava as TestFn<Context>;
|
||||||
|
|
||||||
test.before(async t => {
|
test.before(async t => {
|
||||||
const module = await createTestingModule({
|
const module = await createTestingModule({});
|
||||||
providers: [SessionModel],
|
|
||||||
});
|
|
||||||
|
|
||||||
t.context.session = module.get(SessionModel);
|
t.context.session = module.get(SessionModel);
|
||||||
t.context.user = module.get(UserModel);
|
t.context.user = module.get(UserModel);
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ interface Context {
|
|||||||
const test = ava as TestFn<Context>;
|
const test = ava as TestFn<Context>;
|
||||||
|
|
||||||
test.before(async t => {
|
test.before(async t => {
|
||||||
const module = await createTestingModule({
|
const module = await createTestingModule({});
|
||||||
providers: [UserModel],
|
|
||||||
});
|
|
||||||
|
|
||||||
t.context.user = module.get(UserModel);
|
t.context.user = module.get(UserModel);
|
||||||
t.context.module = module;
|
t.context.module = module;
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ interface Context {
|
|||||||
const test = ava as TestFn<Context>;
|
const test = ava as TestFn<Context>;
|
||||||
|
|
||||||
test.before(async t => {
|
test.before(async t => {
|
||||||
const module = await createTestingModule({
|
const module = await createTestingModule({});
|
||||||
providers: [VerificationTokenModel],
|
|
||||||
});
|
|
||||||
|
|
||||||
t.context.verificationToken = module.get(VerificationTokenModel);
|
t.context.verificationToken = module.get(VerificationTokenModel);
|
||||||
t.context.db = module.get(PrismaClient);
|
t.context.db = module.get(PrismaClient);
|
||||||
|
|||||||
@@ -41,4 +41,4 @@ export {
|
|||||||
getRequestResponseFromHost,
|
getRequestResponseFromHost,
|
||||||
parseCookies,
|
parseCookies,
|
||||||
} from './utils/request';
|
} from './utils/request';
|
||||||
export type * from './utils/types';
|
export * from './utils/types';
|
||||||
|
|||||||
19
packages/backend/server/src/models/base.ts
Normal file
19
packages/backend/server/src/models/base.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inject, Logger } from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
import { Config } from '../base';
|
||||||
|
import type { Models } from '.';
|
||||||
|
import { MODELS_SYMBOL } from './provider';
|
||||||
|
|
||||||
|
export class BaseModel {
|
||||||
|
protected readonly logger = new Logger(this.constructor.name);
|
||||||
|
|
||||||
|
@Inject(MODELS_SYMBOL)
|
||||||
|
protected readonly models!: Models;
|
||||||
|
|
||||||
|
@Inject(Config)
|
||||||
|
protected readonly config!: Config;
|
||||||
|
|
||||||
|
@Inject(PrismaClient)
|
||||||
|
protected readonly db!: PrismaClient;
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Feature, PrismaClient } from '@prisma/client';
|
import { Feature } from '@prisma/client';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { PrismaTransaction } from '../base';
|
import { PrismaTransaction } from '../base';
|
||||||
|
import { BaseModel } from './base';
|
||||||
import { Features, FeatureType } from './common';
|
import { Features, FeatureType } from './common';
|
||||||
|
|
||||||
type FeatureNames = keyof typeof Features;
|
type FeatureNames = keyof typeof Features;
|
||||||
@@ -17,11 +18,7 @@ type FeatureConfigs<T extends FeatureNames> = z.infer<
|
|||||||
// We have to manually update all the users and workspaces binding to the latest version, which are thousands of handreds.
|
// We have to manually update all the users and workspaces binding to the latest version, which are thousands of handreds.
|
||||||
// This is a huge burden for us and we should remove it.
|
// This is a huge burden for us and we should remove it.
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FeatureModel {
|
export class FeatureModel extends BaseModel {
|
||||||
private readonly logger = new Logger(FeatureModel.name);
|
|
||||||
|
|
||||||
constructor(private readonly db: PrismaClient) {}
|
|
||||||
|
|
||||||
async get<T extends FeatureNames>(name: T) {
|
async get<T extends FeatureNames>(name: T) {
|
||||||
const feature = await this.getLatest(this.db, name);
|
const feature = await this.getLatest(this.db, name);
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,71 @@
|
|||||||
import { Global, Injectable, Module } from '@nestjs/common';
|
import {
|
||||||
|
ExistingProvider,
|
||||||
|
FactoryProvider,
|
||||||
|
Global,
|
||||||
|
Module,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ModuleRef } from '@nestjs/core';
|
||||||
|
|
||||||
|
import { ApplyType } from '../base';
|
||||||
import { FeatureModel } from './feature';
|
import { FeatureModel } from './feature';
|
||||||
|
import { MODELS_SYMBOL } from './provider';
|
||||||
import { SessionModel } from './session';
|
import { SessionModel } from './session';
|
||||||
import { UserModel } from './user';
|
import { UserModel } from './user';
|
||||||
import { VerificationTokenModel } from './verification-token';
|
import { VerificationTokenModel } from './verification-token';
|
||||||
|
|
||||||
const models = [
|
const MODELS = {
|
||||||
UserModel,
|
user: UserModel,
|
||||||
SessionModel,
|
session: SessionModel,
|
||||||
VerificationTokenModel,
|
verificationToken: VerificationTokenModel,
|
||||||
FeatureModel,
|
feature: FeatureModel,
|
||||||
] as const;
|
};
|
||||||
|
|
||||||
@Injectable()
|
type ModelsType = {
|
||||||
export class Models {
|
[K in keyof typeof MODELS]: InstanceType<(typeof MODELS)[K]>;
|
||||||
constructor(
|
};
|
||||||
public readonly user: UserModel,
|
|
||||||
public readonly session: SessionModel,
|
export class Models extends ApplyType<ModelsType>() {}
|
||||||
public readonly verificationToken: VerificationTokenModel,
|
|
||||||
public readonly feature: FeatureModel
|
const ModelsProvider: FactoryProvider = {
|
||||||
) {}
|
provide: Models,
|
||||||
|
useFactory: (ref: ModuleRef) => {
|
||||||
|
return new Proxy({} as any, {
|
||||||
|
get: (target, prop) => {
|
||||||
|
// cache
|
||||||
|
if (prop in target) {
|
||||||
|
return target[prop];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find the model instance
|
||||||
|
// @ts-expect-error null detection happens right after
|
||||||
|
const Model = MODELS[prop];
|
||||||
|
if (!Model) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = ref.get(Model);
|
||||||
|
|
||||||
|
if (!model) {
|
||||||
|
throw new Error(`Failed to initialize model ${Model.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
target[prop] = model;
|
||||||
|
return model;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
inject: [ModuleRef],
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModelsSymbolProvider: ExistingProvider = {
|
||||||
|
provide: MODELS_SYMBOL,
|
||||||
|
useExisting: Models,
|
||||||
|
};
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
providers: [...models, Models],
|
providers: [...Object.values(MODELS), ModelsProvider, ModelsSymbolProvider],
|
||||||
exports: [Models],
|
exports: [ModelsProvider],
|
||||||
})
|
})
|
||||||
export class ModelModules {}
|
export class ModelModules {}
|
||||||
|
|
||||||
|
|||||||
1
packages/backend/server/src/models/provider.ts
Normal file
1
packages/backend/server/src/models/provider.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const MODELS_SYMBOL = Symbol('AFFINE_MODELS');
|
||||||
@@ -1,25 +1,18 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Prisma,
|
Prisma,
|
||||||
PrismaClient,
|
|
||||||
type Session,
|
type Session,
|
||||||
type User,
|
type User,
|
||||||
type UserSession,
|
type UserSession,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
|
|
||||||
import { Config } from '../base';
|
import { BaseModel } from './base';
|
||||||
|
|
||||||
export type { Session, UserSession };
|
export type { Session, UserSession };
|
||||||
export type UserSessionWithUser = UserSession & { user: User };
|
export type UserSessionWithUser = UserSession & { user: User };
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionModel {
|
export class SessionModel extends BaseModel {
|
||||||
private readonly logger = new Logger(SessionModel.name);
|
|
||||||
constructor(
|
|
||||||
private readonly db: PrismaClient,
|
|
||||||
private readonly config: Config
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async createSession() {
|
async createSession() {
|
||||||
return await this.db.session.create({
|
return await this.db.session.create({
|
||||||
data: {},
|
data: {},
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Prisma, PrismaClient, type User, Workspace } from '@prisma/client';
|
import { Prisma, type User, Workspace } from '@prisma/client';
|
||||||
import { pick } from 'lodash-es';
|
import { pick } from 'lodash-es';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Config,
|
|
||||||
CryptoHelper,
|
CryptoHelper,
|
||||||
EmailAlreadyUsed,
|
EmailAlreadyUsed,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@@ -15,6 +14,7 @@ import {
|
|||||||
import type { Payload } from '../base/event/def';
|
import type { Payload } from '../base/event/def';
|
||||||
import { Permission } from '../core/permission';
|
import { Permission } from '../core/permission';
|
||||||
import { Quota_FreePlanV1_1 } from '../core/quota/schema';
|
import { Quota_FreePlanV1_1 } from '../core/quota/schema';
|
||||||
|
import { BaseModel } from './base';
|
||||||
|
|
||||||
const publicUserSelect = {
|
const publicUserSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -64,14 +64,13 @@ export type PublicUser = Pick<User, keyof typeof publicUserSelect>;
|
|||||||
export type { User };
|
export type { User };
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserModel {
|
export class UserModel extends BaseModel {
|
||||||
private readonly logger = new Logger(UserModel.name);
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly db: PrismaClient,
|
|
||||||
private readonly crypto: CryptoHelper,
|
private readonly crypto: CryptoHelper,
|
||||||
private readonly event: EventEmitter,
|
private readonly event: EventEmitter
|
||||||
private readonly config: Config
|
) {
|
||||||
) {}
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
async get(id: string) {
|
async get(id: string) {
|
||||||
return this.db.user.findUnique({
|
return this.db.user.findUnique({
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaClient, type VerificationToken } from '@prisma/client';
|
import { type VerificationToken } from '@prisma/client';
|
||||||
|
|
||||||
import { CryptoHelper } from '../base/helpers';
|
import { CryptoHelper } from '../base/helpers';
|
||||||
|
import { BaseModel } from './base';
|
||||||
|
|
||||||
export type { VerificationToken };
|
export type { VerificationToken };
|
||||||
|
|
||||||
@@ -16,12 +17,10 @@ export enum TokenType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VerificationTokenModel {
|
export class VerificationTokenModel extends BaseModel {
|
||||||
private readonly logger = new Logger(VerificationTokenModel.name);
|
constructor(private readonly crypto: CryptoHelper) {
|
||||||
constructor(
|
super();
|
||||||
private readonly db: PrismaClient,
|
}
|
||||||
private readonly crypto: CryptoHelper
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create token by type and credential (optional) with ttl in seconds (default 30 minutes)
|
* create token by type and credential (optional) with ttl in seconds (default 30 minutes)
|
||||||
|
|||||||
Reference in New Issue
Block a user