chore: implement audit action items and translate hardcoded German text

This commit is contained in:
ralksta
2026-03-15 07:32:42 +01:00
parent 3fa47f7031
commit 922302ce5c
16 changed files with 222 additions and 122 deletions

View File

@@ -93,8 +93,8 @@ describe('buildSubpageGrid', () => {
});
it('handles all fields together', () => {
expect(
buildSubpageGrid({ columns: 3, gap: 16, aspectRatio: '1', layout: 'masonry' }),
).toEqual({ grid: { columns: 3, gap: 16, aspectRatio: '1', layout: 'masonry' } });
expect(buildSubpageGrid({ columns: 3, gap: 16, aspectRatio: '1', layout: 'masonry' })).toEqual({
grid: { columns: 3, gap: 16, aspectRatio: '1', layout: 'masonry' },
});
});
});

View File

@@ -6,6 +6,7 @@ import {
AppConfig,
SubpageConfig,
SubpageSectionConfig,
SubpageObjectValue,
GalleryYaml,
SettingsYaml,
GridConfig,
@@ -17,9 +18,12 @@ export * from './theme';
let _config: AppConfig | null = null;
/** Converts raw YAML grid overrides into a typed partial GridConfig. */
export function buildSubpageGrid(
raw?: { columns?: number; gap?: number; aspectRatio?: string; layout?: string },
): { grid: Partial<GridConfig> } | Record<string, never> {
export function buildSubpageGrid(raw?: {
columns?: number;
gap?: number;
aspectRatio?: string;
layout?: string;
}): { grid: Partial<GridConfig> } | Record<string, never> {
if (!raw) return {};
return {
grid: {
@@ -43,13 +47,19 @@ export function getConfig(): AppConfig {
const apiUrl = env.IMMICH_API_URL;
const apiKey = env.IMMICH_API_KEY;
const authSecret = env.AUTH_SECRET || apiKey;
if (!env.AUTH_SECRET && process.env.NODE_ENV === 'production') {
let { AUTH_SECRET } = env;
if (!AUTH_SECRET) {
if (process.env.NODE_ENV === 'production') {
throw new Error(
'SECURITY ERROR: AUTH_SECRET is not set in production. Please set a long random string as AUTH_SECRET in your .env.',
);
}
console.warn(
'\n⚠️ SECURITY WARNING: AUTH_SECRET is not set. Falling back to IMMICH_API_KEY.\n Please set a long random string as AUTH_SECRET in your .env for better security.\n',
);
AUTH_SECRET = apiKey;
}
const authSecret = AUTH_SECRET;
const gallery = loadYaml<GalleryYaml>('gallery.yaml');
const settings = loadYaml<SettingsYaml>('settings.yaml') || {};
@@ -64,6 +74,7 @@ export function getConfig(): AppConfig {
subpages: [],
siteTitle: env.SITE_TITLE || 'Immich Folio',
siteSubtitle: env.SITE_SUBTITLE || 'Setup Required',
lang: 'en',
seo: {
title: 'Setup Required',
description: 'Please configure Immich Folio',
@@ -169,13 +180,6 @@ export function getConfig(): AppConfig {
albumIds: value.map((entry) => processAlbumEntry(entry, `subpage "${name}"`)),
};
}
interface SubpageObjectValue {
title?: string;
subtitle?: string;
albums?: Array<string | Record<string, string>>;
password?: string;
grid?: { columns?: number; gap?: number; aspectRatio?: string; layout?: string };
}
const sp = value as SubpageObjectValue;
const albumEntries = sp.albums || [];
return {
@@ -201,6 +205,7 @@ export function getConfig(): AppConfig {
subpages,
siteTitle: settings.title ?? env.SITE_TITLE,
siteSubtitle: settings.subtitle ?? env.SITE_SUBTITLE,
lang: settings.lang ?? 'en',
seo: {
title: settings.seo?.title || settings.title || env.SITE_TITLE || 'Gallery',
description:

View File

@@ -16,6 +16,19 @@ export interface SubpageConfig {
grid?: Partial<GridConfig>;
}
export interface SubpageObjectValue {
title?: string;
subtitle?: string;
password?: string;
grid?: Partial<GridConfig>;
albums?: Array<string | Record<string, string>>;
sections?: Array<{
title: string;
description?: string;
albums: Array<string | Record<string, string>>;
}>;
}
export interface FooterConfig {
name?: string;
instagram?: string;
@@ -62,6 +75,7 @@ export interface AppConfig {
subpages: SubpageConfig[];
siteTitle: string;
siteSubtitle: string;
lang: string;
seo: {
title: string;
description: string;
@@ -110,6 +124,7 @@ export interface GalleryYaml {
export interface SettingsYaml {
title?: string;
subtitle?: string;
lang?: string;
seo?: {
title?: string;
description?: string;

View File

@@ -66,13 +66,13 @@ export interface SubpageSummary {
coverAssetId: string | null;
}
// Module-level flag so the album summary is only logged once even if the
// ImmichClient singleton is replaced in hot-reload scenarios.
let _hasLoggedAlbums = false;
// Note: Album summary logging state is kept in the client instance.
// ── API Client ─────────────────────────────────────────────────
class ImmichClient {
private hasLoggedAlbums = false;
private get config() {
return getConfig();
}
@@ -168,8 +168,8 @@ class ImmichClient {
});
// Log album summary on first load so admins can see what's published
if (!_hasLoggedAlbums) {
_hasLoggedAlbums = true;
if (!this.hasLoggedAlbums) {
this.hasLoggedAlbums = true;
console.log('\n[Lightbox] Published albums:');
console.log('─'.repeat(80));

View File

@@ -21,6 +21,10 @@ function getKey(): Buffer {
/**
* Encode an asset ID into an opaque URL-safe token.
* Note: The AES-CBC deterministic IV causes the identical input asset ID
* to encrypt to the exact same cipher token. This provides URL obfuscation
* and caching consistency, but it does NOT provide k-anonymous cryptographic
* security guarantees against recognizing identical items if intercepted.
*/
export function encodeAssetId(assetId: string): string {
const key = getKey();