mirror of
https://github.com/FJurmanovic/sugarr.git
synced 2026-11-11 15:51:09 +00:00
Add app config schema and shared models
This commit is contained in:
53
src/app/config/AppConfig.ts
Normal file
53
src/app/config/AppConfig.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { loadEnv } from '../../core/config/env';
|
||||||
|
|
||||||
|
const boolFromEnv = (defaultValue: 'true' | 'false') =>
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default(defaultValue)
|
||||||
|
.transform((v) => v === 'true');
|
||||||
|
|
||||||
|
export const AppConfigSchema = z.object({
|
||||||
|
JELLYFIN_URL: z.string(),
|
||||||
|
JELLYFIN_API_KEY: z.string(),
|
||||||
|
JELLYFIN_USER_ID: z.string(),
|
||||||
|
TMDB_API_KEY: z.string(),
|
||||||
|
|
||||||
|
LLM_ENABLED: boolFromEnv('false'),
|
||||||
|
LLM_BASE_URL: z.string().default('http://localhost:11434/v1'),
|
||||||
|
LLM_API_KEY: z.string().optional().default(''),
|
||||||
|
LLM_MODEL: z.string().default('llama3'),
|
||||||
|
|
||||||
|
RADARR_ENABLED: boolFromEnv('false'),
|
||||||
|
RADARR_URL: z.string().optional().default(''),
|
||||||
|
RADARR_API_KEY: z.string().optional().default(''),
|
||||||
|
RADARR_QUALITY_PROFILE_ID: z.coerce.number().default(1),
|
||||||
|
RADARR_ROOT_FOLDER: z.string().default('/movies'),
|
||||||
|
|
||||||
|
CRON_ENABLED: boolFromEnv('false'),
|
||||||
|
CRON_SCHEDULE: z.string().default('0 3 * * *'),
|
||||||
|
|
||||||
|
API_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default('true')
|
||||||
|
.transform((v) => v !== 'false'),
|
||||||
|
API_PORT: z.coerce.number().default(8080),
|
||||||
|
SECRET_KEY: z.string().optional().default(''),
|
||||||
|
|
||||||
|
RECOMMEND_COUNT: z.coerce.number().default(10),
|
||||||
|
RECOMMEND_MAX_RETRY: z.coerce.number().default(3),
|
||||||
|
MIN_RATED_MOVIES: z.coerce.number().default(5),
|
||||||
|
|
||||||
|
LANGFUSE_PUBLIC_KEY: z.string(),
|
||||||
|
LANGFUSE_SECRET_KEY: z.string(),
|
||||||
|
LANGFUSE_BASE_URL: z.string(),
|
||||||
|
LANGFUSE_PROMPT_CACHE_ENABLED: boolFromEnv('true'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppConfig = z.infer<typeof AppConfigSchema>;
|
||||||
|
|
||||||
|
export function loadAppConfig(source: NodeJS.ProcessEnv = process.env): AppConfig {
|
||||||
|
return loadEnv(AppConfigSchema, source);
|
||||||
|
}
|
||||||
29
src/app/models.ts
Normal file
29
src/app/models.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export interface Movie {
|
||||||
|
name: string;
|
||||||
|
year: number;
|
||||||
|
imdbId: string;
|
||||||
|
tmdbId: string;
|
||||||
|
genres: string[];
|
||||||
|
communityRating: number;
|
||||||
|
isFavorite: boolean;
|
||||||
|
playCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TMDbMovie {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MovieSuggestion {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Recommendation {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
tmdbId: number;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
45
test/app/config/AppConfig.test.ts
Normal file
45
test/app/config/AppConfig.test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { loadAppConfig } from '../../../src/app/config/AppConfig';
|
||||||
|
|
||||||
|
const REQUIRED_ENV = {
|
||||||
|
JELLYFIN_URL: 'http://jellyfin',
|
||||||
|
JELLYFIN_API_KEY: 'jf-key',
|
||||||
|
JELLYFIN_USER_ID: 'user-1',
|
||||||
|
TMDB_API_KEY: 'tmdb-key',
|
||||||
|
LANGFUSE_PUBLIC_KEY: 'lf-pub',
|
||||||
|
LANGFUSE_SECRET_KEY: 'lf-secret',
|
||||||
|
LANGFUSE_BASE_URL: 'http://langfuse',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('loadAppConfig', () => {
|
||||||
|
it('applies defaults for optional settings', () => {
|
||||||
|
const config = loadAppConfig(REQUIRED_ENV);
|
||||||
|
expect(config.LLM_ENABLED).toBe(false);
|
||||||
|
expect(config.RADARR_ENABLED).toBe(false);
|
||||||
|
expect(config.API_PORT).toBe(8080);
|
||||||
|
expect(config.RECOMMEND_COUNT).toBe(10);
|
||||||
|
expect(config.MIN_RATED_MOVIES).toBe(5);
|
||||||
|
expect(config.LANGFUSE_PROMPT_CACHE_ENABLED).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses boolean and numeric overrides from env strings', () => {
|
||||||
|
const config = loadAppConfig({
|
||||||
|
...REQUIRED_ENV,
|
||||||
|
LLM_ENABLED: 'true',
|
||||||
|
RADARR_ENABLED: 'true',
|
||||||
|
API_PORT: '9090',
|
||||||
|
RECOMMEND_COUNT: '20',
|
||||||
|
LANGFUSE_PROMPT_CACHE_ENABLED: 'false',
|
||||||
|
});
|
||||||
|
expect(config.LLM_ENABLED).toBe(true);
|
||||||
|
expect(config.RADARR_ENABLED).toBe(true);
|
||||||
|
expect(config.API_PORT).toBe(9090);
|
||||||
|
expect(config.RECOMMEND_COUNT).toBe(20);
|
||||||
|
expect(config.LANGFUSE_PROMPT_CACHE_ENABLED).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when a required var is missing', () => {
|
||||||
|
const { JELLYFIN_URL, ...rest } = REQUIRED_ENV;
|
||||||
|
expect(() => loadAppConfig(rest)).toThrow(/Invalid environment configuration/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user