Initial commit (unpacked platform)

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 19:21:11 +07:00
commit ac5340195a
314 changed files with 57584 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
import { config as loadDotenv } from "dotenv";
import { existsSync } from "node:fs";
import path from "node:path";
// Ladda .env från paketet ELLER monorepo-roten (pnpm --filter sätter cwd till paketet).
for (const candidate of [".env", "../.env", "../../.env"]) {
const p = path.resolve(process.cwd(), candidate);
if (existsSync(p)) {
loadDotenv({ path: p });
break;
}
}
import { z } from "zod";
import { BRAND } from "@app/shared-types";
/** All konfiguration valideras vid start tydliga fel i stället för mystiska krascher. */
const configSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
API_PORT: z.coerce.number().int().default(4000),
API_BASE_URL: z.string().default("http://localhost:4000"),
CORS_ORIGINS: z.string().default("http://localhost:5173"),
LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
DATABASE_URL: z.string().default("postgres://app_user:app_dev_password@localhost:5432/app"),
REDIS_URL: z.string().default("redis://localhost:6379"),
JWT_ACCESS_SECRET: z.string().min(10).default("dev-only-change-me"),
JWT_REFRESH_SECRET: z.string().min(10).default("dev-only-change-me-too"),
JWT_ACCESS_TTL_SECONDS: z.coerce.number().int().default(900),
JWT_REFRESH_TTL_SECONDS: z.coerce.number().int().default(2_592_000),
ENTITLEMENT_SIGNING_SECRET: z.string().min(10).default("dev-only-change-me-three"),
ENTITLEMENT_TTL_HOURS: z.coerce.number().int().default(24),
ENTITLEMENT_GRACE_DAYS: z.coerce.number().int().default(7),
S3_MODE: z.enum(["mock", "aws"]).default("mock"),
S3_BUCKET: z.string().default(""),
S3_REGION: z.string().default("eu-north-1"),
/** Tom = AWS S3; sätt för S3-kompatibel lagring (MinIO/R2/Hetzner). */
S3_ENDPOINT: z.string().default(""),
/** Tomma = IAM-roll/instansprofil används (rekommenderat i AWS). */
S3_ACCESS_KEY_ID: z.string().default(""),
S3_SECRET_ACCESS_KEY: z.string().default(""),
AAMOS_MODE: z.enum(["http", "mock"]).default("http"),
AAMOS_API_URL: z.string().optional(),
AAMOS_API_KEY: z.string().optional(),
AAMOS_TIMEOUT_MS: z.coerce.number().int().default(60_000),
APP_STORE_MODE: z.enum(["production", "sandbox"]).default("sandbox"),
EMAIL_MODE: z.enum(["log", "smtp"]).default("log"),
SMTP_HOST: z.string().default(""),
SMTP_PORT: z.coerce.number().int().default(587),
SMTP_SECURE: z
.enum(["true", "false"])
.default("false")
.transform((v) => v === "true"),
SMTP_USER: z.string().default(""),
SMTP_PASS: z.string().default(""),
MAIL_FROM: z.string().default(""),
APPLE_BUNDLE_ID: z.string().default(""),
GOOGLE_PACKAGE_NAME: z.string().default(""),
});
export type AppConfig = z.infer<typeof configSchema>;
export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
const parsed = configSchema.safeParse(env);
if (!parsed.success) {
console.error("Ogiltig konfiguration:", parsed.error.flatten().fieldErrors);
process.exit(1);
}
const cfg = parsed.data;
// Varumärkesberoende defaults ur brand.config.json (i18n-spec §28)
if (!cfg.S3_BUCKET) cfg.S3_BUCKET = `${BRAND.slug}-production`;
if (!cfg.APPLE_BUNDLE_ID) cfg.APPLE_BUNDLE_ID = BRAND.iosBundleId;
if (!cfg.GOOGLE_PACKAGE_NAME) cfg.GOOGLE_PACKAGE_NAME = BRAND.androidPackage;
if (cfg.NODE_ENV === "production") {
const insecure = [
cfg.JWT_ACCESS_SECRET,
cfg.JWT_REFRESH_SECRET,
cfg.ENTITLEMENT_SIGNING_SECRET,
].some((s) => s.startsWith("dev-only"));
if (insecure) {
console.error("SÄKERHETSSTOPP: dev-hemligheter i produktion (spec §56).");
process.exit(1);
}
if (cfg.AAMOS_MODE === "mock") {
console.error("SÄKERHETSSTOPP: AAMOS_MODE=mock är inte tillåtet i produktion.");
process.exit(1);
}
}
return cfg;
}