chore: implement audit action items and translate hardcoded German text
This commit is contained in:
@@ -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' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user