Skip to content

Commit

Permalink
feat(server): runtime setting support
Browse files Browse the repository at this point in the history
  • Loading branch information
forehalo committed Mar 14, 2024
1 parent c1f5e84 commit 52e4b59
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- CreateTable
CREATE TABLE "application_settings" (
"key" VARCHAR(36) NOT NULL,
"value" TEXT NOT NULL,

CONSTRAINT "application_settings_pkey" PRIMARY KEY ("key")
);
8 changes: 8 additions & 0 deletions packages/backend/server/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,11 @@ model DataMigration {
@@map("_data_migrations")
}

model ApplicationSetting {
key String @id @db.VarChar(36)
// JSON strinfied value
value String @db.Text
@@map("application_settings")
}
2 changes: 2 additions & 0 deletions packages/backend/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { HelpersModule } from './fundamentals/helpers';
import { MailModule } from './fundamentals/mailer';
import { MetricsModule } from './fundamentals/metrics';
import { PrismaModule } from './fundamentals/prisma';
import { RuntimeSettingModule } from './fundamentals/runtime';
import { StorageProviderModule } from './fundamentals/storage';
import { RateLimiterModule } from './fundamentals/throttler';
import { WebSocketModule } from './fundamentals/websocket';
Expand All @@ -45,6 +46,7 @@ export const FunctionalityModules = [
MailModule,
StorageProviderModule,
HelpersModule,
RuntimeSettingModule,
];

export class AppModuleBuilder {
Expand Down
1 change: 1 addition & 0 deletions packages/backend/server/src/fundamentals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export {
GlobalExceptionFilter,
OptionalModule,
} from './nestjs';
export { Runtime } from './runtime';
export * from './storage';
export { type StorageProvider, StorageProviderFactory } from './storage';
export { AuthThrottlerGuard, CloudThrottlerGuard, Throttle } from './throttler';
Expand Down
36 changes: 36 additions & 0 deletions packages/backend/server/src/fundamentals/runtime/def.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable, Provider } from '@nestjs/common';

import { Config } from '../config';

export interface RuntimeConfigs {
enablePayment: boolean;
defaultPasswordLogin: boolean;
}

export type RuntimeConfigKey = keyof RuntimeConfigs;
export type RuntimeConfig<T extends RuntimeConfigKey> = RuntimeConfigs[T];

@Injectable()
export class DefaultRuntimeConfigs implements RuntimeConfigs {
enablePayment = true;
enableQuota = true;
defaultPasswordLogin = false;
}

export class SelfHostDefaultRuntimeConfigs extends DefaultRuntimeConfigs {
override enablePayment = false;
override enableQuota = false;
override defaultPasswordLogin = true;
}

export const DefaultRuntimeConfigsProvider: Provider = {
provide: DefaultRuntimeConfigs,
useFactory: (config: Config) => {
if (config.flavor === 'selfhosted') {

Check failure on line 29 in packages/backend/server/src/fundamentals/runtime/def.ts

View workflow job for this annotation

GitHub Actions / Lint

This comparison appears to be unintentional because the types '{ type: string; graphql: boolean; sync: boolean; }' and 'string' have no overlap.
return new SelfHostDefaultRuntimeConfigs();
} else {
return new DefaultRuntimeConfigs();
}
},
inject: [Config],
};
12 changes: 12 additions & 0 deletions packages/backend/server/src/fundamentals/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Global, Module } from '@nestjs/common';

import { DefaultRuntimeConfigsProvider } from './def';
import { Runtime } from './service';

@Global()
@Module({
providers: [Runtime, DefaultRuntimeConfigsProvider],
exports: [Runtime],
})
export class RuntimeSettingModule {}
export { Runtime };
107 changes: 107 additions & 0 deletions packages/backend/server/src/fundamentals/runtime/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { PrismaClient } from '@prisma/client';

import { Cache } from '../cache';
import { Config } from '../config';
import {
DefaultRuntimeConfigs,
type RuntimeConfig,
type RuntimeConfigKey,
} from './def';

/**
* runtime.get(k)
* runtime.set(k, v)
* runtime.update(k, (v) => {
*
* })
*/
export class Runtime {
constructor(
public readonly config: Config,
private readonly db: PrismaClient,
private readonly cache: Cache,
private readonly defaultConfigs: DefaultRuntimeConfigs
) {}

async get<K extends RuntimeConfigKey, V extends RuntimeConfig<K>>(
k: K
): Promise<V> {
const cached = await this.loadCache<K, V>(k);

if (cached) {
return cached;
}

const dbValue = await this.loadDb<K, V>(k);

if (!dbValue) {
throw new Error(`Runtime config ${k} not found`);
}

await this.setCache(k, dbValue);

return dbValue;
}

async set<K extends RuntimeConfigKey, V extends RuntimeConfig<K>>(
k: K,
v: V
) {
await this.db.applicationSetting.upsert({
where: {
key: k,
},
create: {
key: k,
value: JSON.stringify(v),
},
update: {
value: JSON.stringify(v),
},
});

await this.setCache(k, v);
}

async update<K extends RuntimeConfigKey, V extends RuntimeConfig<K>>(
k: K,
modifier: (v: V) => V | Promise<V>
) {
const data = await this.loadDb<K, V>(k);

const updated = await modifier(data);

await this.set(k, updated);

return updated;
}

async loadDb<K extends RuntimeConfigKey, V extends RuntimeConfig<K>>(
k: K
): Promise<V> {
const v = await this.db.applicationSetting.findUnique({
where: {
key: k,
},
});

if (v) {
return JSON.parse(v.value);
} else {
return this.defaultConfigs[k] as V;
}
}

async loadCache<K extends RuntimeConfigKey, V extends RuntimeConfig<K>>(
k: K
): Promise<V | undefined> {
return this.cache.get<V>(`runtime:${k}`);
}

async setCache<K extends RuntimeConfigKey, V extends RuntimeConfig<K>>(
k: K,
v: V
): Promise<boolean> {
return this.cache.set<V>(k, v, { ttl: 60 * 1000 });
}
}

0 comments on commit 52e4b59

Please sign in to comment.