Initial commit
This commit is contained in:
147
lib/system-config.ts
Normal file
147
lib/system-config.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
export type SystemConfigData = {
|
||||
app?: { initialized?: boolean };
|
||||
db?: { databaseUrl?: string };
|
||||
auth?: { jwtSecret?: string };
|
||||
email?: {
|
||||
enabled?: boolean;
|
||||
provider?: "smtp";
|
||||
fromAddress?: string;
|
||||
from?: string;
|
||||
smtp?: { host?: string; port?: number; user?: string; pass?: string };
|
||||
};
|
||||
stripe?: {
|
||||
enabled?: boolean;
|
||||
secretKey?: string;
|
||||
webhookSecret?: string;
|
||||
publishableKey?: string;
|
||||
};
|
||||
oauth?: {
|
||||
google?: { enabled?: boolean; clientId?: string; clientSecret?: string };
|
||||
github?: { enabled?: boolean; clientId?: string; clientSecret?: string };
|
||||
facebook?: { enabled?: boolean; clientId?: string; clientSecret?: string };
|
||||
discord?: { enabled?: boolean; clientId?: string; clientSecret?: string };
|
||||
};
|
||||
googleAuth?: {
|
||||
enabled?: boolean;
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
};
|
||||
googleCalendar?: {
|
||||
enabled?: boolean;
|
||||
serviceAccountEmail?: string;
|
||||
serviceAccountKey?: string;
|
||||
calendarId?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const DATA_DIR = process.env.CONFIG_DIR || path.join(process.cwd(), "data");
|
||||
const FILE_PATH = path.join(DATA_DIR, "system-config.json");
|
||||
|
||||
const defaultConfig: SystemConfigData = {
|
||||
app: { initialized: false },
|
||||
db: { databaseUrl: process.env.DATABASE_URL },
|
||||
auth: { jwtSecret: process.env.JWT_SECRET || process.env.BETTER_AUTH_SECRET },
|
||||
email: {
|
||||
enabled: Boolean(process.env.EMAIL_FROM),
|
||||
provider: (process.env.EMAIL_PROVIDER as any) || undefined,
|
||||
fromAddress: process.env.EMAIL_FROM,
|
||||
from: process.env.EMAIL_FROM,
|
||||
smtp: {
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined,
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
},
|
||||
stripe: {
|
||||
enabled: Boolean(process.env.STRIPE_SECRET_KEY),
|
||||
secretKey: process.env.STRIPE_SECRET_KEY,
|
||||
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
||||
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
|
||||
},
|
||||
oauth: {
|
||||
google: {
|
||||
enabled: Boolean(process.env.GOOGLE_CLIENT_ID),
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
},
|
||||
github: {
|
||||
enabled: Boolean(process.env.GITHUB_CLIENT_ID),
|
||||
clientId: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
},
|
||||
facebook: {
|
||||
enabled: Boolean(process.env.FACEBOOK_CLIENT_ID),
|
||||
clientId: process.env.FACEBOOK_CLIENT_ID,
|
||||
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
|
||||
},
|
||||
discord: {
|
||||
enabled: Boolean(process.env.DISCORD_CLIENT_ID),
|
||||
clientId: process.env.DISCORD_CLIENT_ID,
|
||||
clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
||||
},
|
||||
},
|
||||
googleAuth: {
|
||||
enabled: Boolean(process.env.GOOGLE_CLIENT_ID),
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
},
|
||||
};
|
||||
|
||||
async function ensureDir() {
|
||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
export async function loadSystemConfig(): Promise<SystemConfigData> {
|
||||
await ensureDir();
|
||||
try {
|
||||
const raw = await fs.readFile(FILE_PATH, "utf-8");
|
||||
const fileCfg = JSON.parse(raw) as SystemConfigData;
|
||||
return deepMerge(defaultConfig, fileCfg);
|
||||
} catch {
|
||||
// create initial file config
|
||||
await fs.writeFile(FILE_PATH, JSON.stringify(defaultConfig, null, 2), "utf-8");
|
||||
return defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSystemConfig(partial: SystemConfigData, prisma?: PrismaClient | null) {
|
||||
const current = await loadSystemConfig();
|
||||
const merged = deepMerge(current, partial);
|
||||
await ensureDir();
|
||||
await fs.writeFile(FILE_PATH, JSON.stringify(merged, null, 2), "utf-8");
|
||||
|
||||
// Also persist to DB if available (best-effort)
|
||||
if (prisma) {
|
||||
try {
|
||||
await prisma.systemConfig.upsert({
|
||||
where: { id: 1 },
|
||||
update: { data: merged as any },
|
||||
create: { id: 1, data: merged as any },
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
function isObject(v: any) {
|
||||
return v && typeof v === "object" && !Array.isArray(v);
|
||||
}
|
||||
|
||||
function deepMerge<T>(base: T, patch: any): T {
|
||||
if (!isObject(base) || !isObject(patch)) return (patch ?? base) as T;
|
||||
const out: any = { ...base };
|
||||
for (const k of Object.keys(patch)) {
|
||||
const bv = (base as any)[k];
|
||||
const pv = patch[k];
|
||||
out[k] = isObject(bv) && isObject(pv) ? deepMerge(bv, pv) : pv;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user