Initial commit

This commit is contained in:
Developer
2026-02-06 21:44:04 -06:00
commit f85e93c7a6
151 changed files with 22916 additions and 0 deletions

147
lib/system-config.ts Normal file
View 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;
}