mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-22 08:13:52 +03:00
feat(server): init nestjs server (#1997)
Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
parent
a92d0fff4a
commit
91c3040db7
@ -6,6 +6,7 @@
|
||||
"always",
|
||||
[
|
||||
"electron",
|
||||
"server",
|
||||
"web",
|
||||
"docs",
|
||||
"component",
|
||||
|
17
.eslintrc.js
17
.eslintrc.js
@ -1,8 +1,11 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* @type {import('eslint').Linter.Config}
|
||||
*/
|
||||
const config = {
|
||||
root: true,
|
||||
settings: {
|
||||
react: {
|
||||
version: '18',
|
||||
version: 'detect',
|
||||
},
|
||||
next: {
|
||||
rootDir: 'apps/web',
|
||||
@ -65,4 +68,14 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: 'apps/server/**/*.ts',
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
# check lockfile is up to date
|
||||
yarn install
|
||||
cd ./apps/eletron && yarn install
|
||||
|
||||
# lint staged files
|
||||
yarn exec lint-staged
|
||||
|
@ -5,10 +5,6 @@
|
||||
"author": "affine",
|
||||
"description": "AFFiNE App",
|
||||
"homepage": "https://github.com/toeverything/AFFiNE",
|
||||
"workspaces": [
|
||||
"../../packages/*",
|
||||
"../../tests/fixtures"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development node scripts/dev.mjs",
|
||||
"prod": "cross-env NODE_ENV=production node scripts/dev.mjs",
|
||||
|
17454
apps/electron/yarn.lock
17454
apps/electron/yarn.lock
File diff suppressed because it is too large
Load Diff
1
apps/server/.env.example
Normal file
1
apps/server/.env.example
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL="postgresql://affine@localhost:5432/affine"
|
2
apps/server/.gitignore
vendored
Normal file
2
apps/server/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.env
|
||||
src/schema.gql
|
79
apps/server/package.json
Normal file
79
apps/server/package.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "@affine/server",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "Affine Node.js server",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nodemon ./src/index.ts",
|
||||
"test": "ava"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.6.0",
|
||||
"@nestjs/apollo": "^11.0.5",
|
||||
"@nestjs/common": "^9.4.0",
|
||||
"@nestjs/core": "^9.4.0",
|
||||
"@nestjs/graphql": "^11.0.5",
|
||||
"@nestjs/platform-express": "^9.4.0",
|
||||
"@prisma/client": "^4.12.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"prisma": "^4.12.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/testing": "^9.4.0",
|
||||
"@types/lodash-es": "^4.14.194",
|
||||
"@types/node": "^18.15.11",
|
||||
"ava": "^5.2.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"exec": "node",
|
||||
"script": "./src/index.ts",
|
||||
"nodeArgs": [
|
||||
"--loader",
|
||||
"ts-node/esm.mjs",
|
||||
"--es-module-specifier-resolution",
|
||||
"node"
|
||||
],
|
||||
"ignore": [
|
||||
"**/__tests__/**",
|
||||
"**/dist/**"
|
||||
],
|
||||
"env": {
|
||||
"TS_NODE_PROJECT": "./tsconfig.json",
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG": "affine:*",
|
||||
"FORCE_COLOR": true,
|
||||
"DEBUG_COLORS": true
|
||||
},
|
||||
"delay": 1000
|
||||
},
|
||||
"ava": {
|
||||
"extensions": {
|
||||
"ts": "module"
|
||||
},
|
||||
"nodeArguments": [
|
||||
"--loader",
|
||||
"ts-node/esm.mjs",
|
||||
"--es-module-specifier-resolution",
|
||||
"node"
|
||||
],
|
||||
"files": [
|
||||
"src/**/*.spec.ts"
|
||||
],
|
||||
"require": [
|
||||
"./src/prelude.ts"
|
||||
],
|
||||
"environmentVariables": {
|
||||
"TS_NODE_PROJECT": "./tsconfig.json",
|
||||
"NODE_ENV": "test"
|
||||
}
|
||||
}
|
||||
}
|
52
apps/server/schema.prisma
Normal file
52
apps/server/schema.prisma
Normal file
@ -0,0 +1,52 @@
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model google_users {
|
||||
id String @id @db.VarChar
|
||||
user_id String @db.VarChar
|
||||
google_id String @unique @db.VarChar
|
||||
users users @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model permissions {
|
||||
id String @id @db.VarChar
|
||||
workspace_id String @db.VarChar
|
||||
user_id String? @db.VarChar
|
||||
user_email String?
|
||||
type Int @db.SmallInt
|
||||
accepted Boolean @default(false)
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
users users? @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||
workspaces workspaces @relation(fields: [workspace_id], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model seaql_migrations {
|
||||
version String @id @db.VarChar
|
||||
applied_at BigInt
|
||||
}
|
||||
|
||||
model users {
|
||||
id String @id @db.VarChar
|
||||
name String @db.VarChar
|
||||
email String @unique @db.VarChar
|
||||
avatar_url String? @db.VarChar
|
||||
token_nonce Int? @default(0) @db.SmallInt
|
||||
password String? @db.VarChar
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
google_users google_users[]
|
||||
permissions permissions[]
|
||||
}
|
||||
|
||||
model workspaces {
|
||||
id String @id @db.VarChar
|
||||
public Boolean
|
||||
type Int @db.SmallInt
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
permissions permissions[]
|
||||
}
|
16
apps/server/src/app.ts
Normal file
16
apps/server/src/app.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ConfigModule } from './config';
|
||||
import { GqlModule } from './graphql.module';
|
||||
import { BusinessModules } from './modules';
|
||||
import { PrismaModule } from './prisma';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule,
|
||||
GqlModule,
|
||||
ConfigModule.forRoot(),
|
||||
...BusinessModules,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
30
apps/server/src/config/__tests__/config.spec.ts
Normal file
30
apps/server/src/config/__tests__/config.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import test from 'ava';
|
||||
|
||||
import { Config, ConfigModule } from '..';
|
||||
|
||||
let config: Config;
|
||||
test.beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [ConfigModule.forRoot()],
|
||||
}).compile();
|
||||
config = module.get(Config);
|
||||
});
|
||||
|
||||
test('should be able to get config', t => {
|
||||
t.assert(typeof config.host === 'string');
|
||||
t.is(config.env, 'test');
|
||||
});
|
||||
|
||||
test('should be able to override config', async t => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
host: 'testing',
|
||||
}),
|
||||
],
|
||||
}).compile();
|
||||
const config = module.get(Config);
|
||||
|
||||
t.is(config.host, 'testing');
|
||||
});
|
202
apps/server/src/config/def.ts
Normal file
202
apps/server/src/config/def.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import type { ApolloDriverConfig } from '@nestjs/apollo';
|
||||
|
||||
import type { LeafPaths } from '../utils/types';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace globalThis {
|
||||
// eslint-disable-next-line no-var
|
||||
var AFFiNE: AFFiNEConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export const enum ExternalAccount {
|
||||
github = 'github',
|
||||
google = 'google',
|
||||
firebase = 'firebase',
|
||||
}
|
||||
|
||||
type EnvConfigType = 'string' | 'int' | 'float' | 'boolean';
|
||||
type ConfigPaths = LeafPaths<
|
||||
Omit<
|
||||
AFFiNEConfig,
|
||||
| 'ENV_MAP'
|
||||
| 'version'
|
||||
| 'baseUrl'
|
||||
| 'origin'
|
||||
| 'prod'
|
||||
| 'dev'
|
||||
| 'test'
|
||||
| 'deploy'
|
||||
>,
|
||||
'',
|
||||
'....'
|
||||
>;
|
||||
/**
|
||||
* parse number value from environment variables
|
||||
*/
|
||||
function int(value: string) {
|
||||
const n = parseInt(value);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
function float(value: string) {
|
||||
const n = parseFloat(value);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
function boolean(value: string) {
|
||||
return value === '1' || value.toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
export function parseEnvValue(value: string | undefined, type?: EnvConfigType) {
|
||||
if (typeof value === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
return type === 'int'
|
||||
? int(value)
|
||||
: type === 'float'
|
||||
? float(value)
|
||||
: type === 'boolean'
|
||||
? boolean(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
/**
|
||||
* All Configurations that would control AFFiNE server behaviors
|
||||
*
|
||||
*/
|
||||
export interface AFFiNEConfig {
|
||||
ENV_MAP: Record<string, ConfigPaths | [ConfigPaths, EnvConfigType?]>;
|
||||
/**
|
||||
* System version
|
||||
*/
|
||||
readonly version: string;
|
||||
/**
|
||||
* alias to `process.env.NODE_ENV`
|
||||
*
|
||||
* @default 'production'
|
||||
* @env NODE_ENV
|
||||
*/
|
||||
readonly env: string;
|
||||
/**
|
||||
* fast environment judge
|
||||
*/
|
||||
get prod(): boolean;
|
||||
get dev(): boolean;
|
||||
get test(): boolean;
|
||||
get deploy(): boolean;
|
||||
|
||||
/**
|
||||
* Whether the server is hosted on a ssl enabled domain
|
||||
*/
|
||||
https: boolean;
|
||||
/**
|
||||
* where the server get deployed.
|
||||
*
|
||||
* @default 'localhost'
|
||||
* @env AFFINE_SERVER_HOST
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* which port the server will listen on
|
||||
*
|
||||
* @default 3000
|
||||
* @env AFFINE_SERVER_PORT
|
||||
*/
|
||||
port: number;
|
||||
/**
|
||||
* subpath where the server get deployed if there is.
|
||||
*
|
||||
* @default '' // empty string
|
||||
* @env AFFINE_SERVER_SUB_PATH
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Readonly property `baseUrl` is the full url of the server consists of `https://HOST:PORT/PATH`.
|
||||
*
|
||||
* if `host` is not `localhost` then the port will be ignored
|
||||
*/
|
||||
get baseUrl(): string;
|
||||
|
||||
/**
|
||||
* Readonly property `origin` is domain origin in the form of `https://HOST:PORT` without subpath.
|
||||
*
|
||||
* if `host` is not `localhost` then the port will be ignored
|
||||
*/
|
||||
get origin(): string;
|
||||
|
||||
/**
|
||||
* the apollo driver config
|
||||
*/
|
||||
graphql: ApolloDriverConfig;
|
||||
/**
|
||||
* object storage Config
|
||||
*
|
||||
* all artifacts and logs will be stored on instance disk,
|
||||
* and can not shared between instances if not configured
|
||||
*/
|
||||
objectStorage: {
|
||||
/**
|
||||
* whether use remote object storage
|
||||
*/
|
||||
enable: boolean;
|
||||
/**
|
||||
* used to store all uploaded builds and analysis reports
|
||||
*
|
||||
* the concrete type definition is not given here because different storage providers introduce
|
||||
* significant differences in configuration
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* provider: 'aws',
|
||||
* region: 'eu-west-1',
|
||||
* aws_access_key_id: '',
|
||||
* aws_secret_access_key: '',
|
||||
* // other aws storage config...
|
||||
* }
|
||||
*/
|
||||
config: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* authentication config
|
||||
*/
|
||||
auth: {
|
||||
/**
|
||||
* whether allow user to signup with email directly
|
||||
*/
|
||||
enableSignup: boolean;
|
||||
/**
|
||||
* whether allow user to signup by oauth providers
|
||||
*/
|
||||
enableOauth: boolean;
|
||||
/**
|
||||
* all available oauth providers
|
||||
*/
|
||||
oauthProviders: Partial<
|
||||
Record<
|
||||
ExternalAccount,
|
||||
{
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
/**
|
||||
* uri to start oauth flow
|
||||
*/
|
||||
authorizationUri?: string;
|
||||
/**
|
||||
* uri to authenticate `access_token` when user is redirected back from oauth provider with `code`
|
||||
*/
|
||||
accessTokenUri?: string;
|
||||
/**
|
||||
* uri to get user info with authenticated `access_token`
|
||||
*/
|
||||
userInfoUri?: string;
|
||||
args?: Record<string, any>;
|
||||
}
|
||||
>
|
||||
>;
|
||||
};
|
||||
}
|
51
apps/server/src/config/default.ts
Normal file
51
apps/server/src/config/default.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import pkg from '../../package.json' assert { type: 'json' };
|
||||
import type { AFFiNEConfig } from './def';
|
||||
|
||||
export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => ({
|
||||
version: pkg.version,
|
||||
ENV_MAP: {},
|
||||
env: process.env.NODE_ENV ?? 'development',
|
||||
get prod() {
|
||||
return this.env === 'production';
|
||||
},
|
||||
get dev() {
|
||||
return this.env === 'development';
|
||||
},
|
||||
get test() {
|
||||
return this.env === 'test';
|
||||
},
|
||||
get deploy() {
|
||||
return !this.dev && !this.test;
|
||||
},
|
||||
https: false,
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
path: '',
|
||||
get origin() {
|
||||
return this.dev
|
||||
? 'http://localhost:8080'
|
||||
: `${this.https ? 'https' : 'http'}://${this.host}${
|
||||
this.host === 'localhost' ? `:${this.port}` : ''
|
||||
}`;
|
||||
},
|
||||
get baseUrl() {
|
||||
return `${this.origin}${this.path}`;
|
||||
},
|
||||
graphql: {
|
||||
buildSchemaOptions: {
|
||||
numberScalarMode: 'integer',
|
||||
},
|
||||
introspection: true,
|
||||
playground: true,
|
||||
debug: true,
|
||||
},
|
||||
auth: {
|
||||
enableSignup: true,
|
||||
enableOauth: false,
|
||||
oauthProviders: {},
|
||||
},
|
||||
objectStorage: {
|
||||
enable: false,
|
||||
config: {},
|
||||
},
|
||||
});
|
15
apps/server/src/config/env.ts
Normal file
15
apps/server/src/config/env.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { set } from 'lodash-es';
|
||||
|
||||
import { parseEnvValue } from './def';
|
||||
|
||||
for (const env in AFFiNE.ENV_MAP) {
|
||||
const config = AFFiNE.ENV_MAP[env];
|
||||
const [path, value] =
|
||||
typeof config === 'string'
|
||||
? [config, process.env[env]]
|
||||
: [config[0], parseEnvValue(process.env[env], config[1])];
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
set(globalThis.AFFiNE, path, process.env[env]);
|
||||
}
|
||||
}
|
69
apps/server/src/config/index.ts
Normal file
69
apps/server/src/config/index.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import type { DynamicModule, FactoryProvider } from '@nestjs/common';
|
||||
import { merge } from 'lodash-es';
|
||||
|
||||
import type { DeepPartial } from '../utils/types';
|
||||
import type { AFFiNEConfig } from './def';
|
||||
|
||||
type ConstructorOf<T> = {
|
||||
new (): T;
|
||||
};
|
||||
|
||||
function ApplyType<T>(): ConstructorOf<T> {
|
||||
// @ts-expect-error used to fake the type of config
|
||||
return class Inner implements T {
|
||||
constructor() {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* usage:
|
||||
* ```
|
||||
* import { Config } from '@affine/server'
|
||||
*
|
||||
* class TestConfig {
|
||||
* constructor(private readonly config: Config) {}
|
||||
* test() {
|
||||
* return this.config.env
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class Config extends ApplyType<AFFiNEConfig>() {}
|
||||
|
||||
function createConfigProvider(
|
||||
override?: DeepPartial<Config>
|
||||
): FactoryProvider<Config> {
|
||||
return {
|
||||
provide: Config,
|
||||
useFactory: () => {
|
||||
const wrapper = new Config();
|
||||
const config = merge({}, AFFiNE, override);
|
||||
|
||||
const proxy: Config = new Proxy(wrapper, {
|
||||
get: (_target, property: keyof Config) => {
|
||||
const desc = Object.getOwnPropertyDescriptor(AFFiNE, property);
|
||||
if (desc?.get) {
|
||||
return desc.get.call(proxy);
|
||||
}
|
||||
return config[property];
|
||||
},
|
||||
});
|
||||
return proxy;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class ConfigModule {
|
||||
static forRoot = (override?: DeepPartial<Config>): DynamicModule => {
|
||||
const provider = createConfigProvider(override);
|
||||
|
||||
return {
|
||||
global: true,
|
||||
module: ConfigModule,
|
||||
providers: [provider],
|
||||
exports: [provider],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export { AFFiNEConfig } from './def';
|
30
apps/server/src/graphql.module.ts
Normal file
30
apps/server/src/graphql.module.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import { ApolloDriver } from '@nestjs/apollo';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { Config } from './config';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
||||
driver: ApolloDriver,
|
||||
useFactory: (config: Config) => {
|
||||
return {
|
||||
...config.graphql,
|
||||
path: `${config.path}/graphql`,
|
||||
autoSchemaFile: join(
|
||||
fileURLToPath(import.meta.url),
|
||||
'..',
|
||||
'schema.gql'
|
||||
),
|
||||
};
|
||||
},
|
||||
inject: [Config],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class GqlModule {}
|
20
apps/server/src/index.ts
Normal file
20
apps/server/src/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import './prelude';
|
||||
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import type { NestExpressApplication } from '@nestjs/platform-express';
|
||||
|
||||
import { AppModule } from './app';
|
||||
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||
cors: {
|
||||
origin:
|
||||
process.env.AFFINE_ENV === 'preview'
|
||||
? ['https://affine-preview.vercel.app']
|
||||
: ['http://localhost:8080'],
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: '*',
|
||||
},
|
||||
bodyParser: true,
|
||||
});
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
3
apps/server/src/modules/index.ts
Normal file
3
apps/server/src/modules/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { WorkspaceModule } from './workspaces';
|
||||
|
||||
export const BusinessModules = [WorkspaceModule];
|
8
apps/server/src/modules/workspaces/index.ts
Normal file
8
apps/server/src/modules/workspaces/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceResolver } from './resolver';
|
||||
|
||||
@Module({
|
||||
providers: [WorkspaceResolver],
|
||||
})
|
||||
export class WorkspaceModule {}
|
47
apps/server/src/modules/workspaces/resolver.ts
Normal file
47
apps/server/src/modules/workspaces/resolver.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import {
|
||||
Args,
|
||||
Field,
|
||||
ObjectType,
|
||||
Query,
|
||||
registerEnumType,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
import type { workspaces } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from '../../prisma/service';
|
||||
|
||||
export enum WorkspaceType {
|
||||
Private = 0,
|
||||
Normal = 1,
|
||||
}
|
||||
|
||||
registerEnumType(WorkspaceType, {
|
||||
name: 'WorkspaceType',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
export class Workspace implements workspaces {
|
||||
@Field()
|
||||
id!: string;
|
||||
@Field({ description: 'is Public workspace' })
|
||||
public!: boolean;
|
||||
@Field(() => WorkspaceType, { description: 'Workspace type' })
|
||||
type!: WorkspaceType;
|
||||
@Field({ description: 'Workspace created date' })
|
||||
created_at!: Date;
|
||||
}
|
||||
|
||||
@Resolver(() => Workspace)
|
||||
export class WorkspaceResolver {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
@Query(() => Workspace, {
|
||||
name: 'workspace',
|
||||
description: 'Get workspace by id',
|
||||
})
|
||||
async workspace(@Args('id') id: string) {
|
||||
return this.prisma.workspaces.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
6
apps/server/src/prelude.ts
Normal file
6
apps/server/src/prelude.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import 'reflect-metadata';
|
||||
import 'dotenv/config';
|
||||
|
||||
import { getDefaultAFFiNEConfig } from './config/default';
|
||||
|
||||
globalThis.AFFiNE = getDefaultAFFiNEConfig();
|
10
apps/server/src/prisma/index.ts
Normal file
10
apps/server/src/prisma/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from './service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
16
apps/server/src/prisma/service.ts
Normal file
16
apps/server/src/prisma/service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { INestApplication, OnModuleInit } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
this.$on('beforeExit', async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
42
apps/server/src/utils/types.ts
Normal file
42
apps/server/src/utils/types.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export type DeepPartial<T> = T extends Array<infer U>
|
||||
? DeepPartial<U>[]
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends object
|
||||
? {
|
||||
[K in keyof T]?: DeepPartial<T[K]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
type Join<Prefix, Suffixes> = Prefix extends string | number
|
||||
? Suffixes extends string | number
|
||||
? Prefix extends ''
|
||||
? Suffixes
|
||||
: `${Prefix}.${Suffixes}`
|
||||
: never
|
||||
: never;
|
||||
|
||||
export type PrimitiveType =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| symbol
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type LeafPaths<
|
||||
T,
|
||||
Path extends string = '',
|
||||
MaxDepth extends string = '...',
|
||||
Depth extends string = ''
|
||||
> = Depth extends MaxDepth
|
||||
? never
|
||||
: T extends Record<string | number, any>
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? T[K] extends PrimitiveType
|
||||
? K
|
||||
: Join<K, LeafPaths<T[K], Path, MaxDepth, `${Depth}.`>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: never;
|
22
apps/server/tsconfig.json
Normal file
22
apps/server/tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": false,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"outDir": "dist",
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["src", "package.json"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"ts-node": {
|
||||
"esm": true,
|
||||
"experimentalSpecifierResolution": "node"
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
"author": "toeverything",
|
||||
"license": "MPL-2.0",
|
||||
"workspaces": [
|
||||
"apps/!(electron)",
|
||||
"apps/*",
|
||||
"packages/*",
|
||||
"tests/fixtures"
|
||||
],
|
||||
|
@ -15,6 +15,7 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@affine/component": ["./packages/component/src/index"],
|
||||
|
Loading…
Reference in New Issue
Block a user