Files
Cibello-app/apps/api/src/config.ts
T
Sven (AAMOS AI) 050c958285 feat(gemini): Skiva 1 – Gemini 2.5 Flash som lärar-tier för kylskåpsskanning
- Gemini-adapter bakom AamosClient-interface (AAMOS_MODE=gemini)
- Serversida/worker: hämtar bild, anropar Gemini, mappar mot canonical_ingredients
- Kostnad/tokens bokförs i ai_usage_counters; global dagsbudget via BudgetStore
- Redis-backed budget i worker, in-memory i tester
- Migration 0019: ai_cost_usd_microcents
- Hermetiska tester med inspelad fixture; separat pnpm eval:scan
- docs/09 uppdaterad ärligt: AAMOS-status, Gemini-flöde, säkerhet/kostnad
- REQUIRE_REAL=1 stödjer AAMOS_MODE=gemini; deploy-grind uppdaterad
2026-08-08 03:30:59 +07:00

104 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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", "gemini"]).default("http"),
AAMOS_API_URL: z.string().optional(),
AAMOS_API_KEY: z.string().optional(),
AAMOS_TIMEOUT_MS: z.coerce.number().int().default(60_000),
GEMINI_API_KEY: z.string().optional(),
GEMINI_MODEL: z.string().default("gemini-2.5-flash"),
GEMINI_TIMEOUT_MS: z.coerce.number().int().default(60_000),
GEMINI_DAILY_BUDGET_USD: z.coerce.number().default(0),
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);
}
if (cfg.AAMOS_MODE === "gemini" && !cfg.GEMINI_API_KEY) {
console.error("SÄKERHETSSTOPP: AAMOS_MODE=gemini kräver GEMINI_API_KEY i produktion.");
process.exit(1);
}
}
return cfg;
}