From 3dca10b56fba00c69095cbe33d9ab28b75d63a25 Mon Sep 17 00:00:00 2001 From: Nessar Date: Thu, 16 Jul 2026 21:30:51 +0200 Subject: [PATCH] feat: update Dockerfiles to use explicit image paths and enhance localStorage handling in ThemeService --- backend/Dockerfile | 4 +-- frontend/Dockerfile | 6 ++-- frontend/angular.json | 2 +- frontend/e2e/app.spec.ts | 14 +++++++-- frontend/eslint.config.js | 33 +++++++++++++++++++- frontend/src/app/services/browser-storage.ts | 7 +++++ frontend/src/app/services/theme.service.ts | 3 +- 7 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 frontend/src/app/services/browser-storage.ts diff --git a/backend/Dockerfile b/backend/Dockerfile index 9e4ec32..162952c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM maven:3.9-eclipse-temurin-25-alpine AS build +FROM docker.io/library/maven:3.9-eclipse-temurin-25-alpine AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline @@ -7,7 +7,7 @@ COPY src ./src RUN mvn clean package -DskipTests # Run stage -FROM eclipse-temurin:25-jre +FROM docker.io/library/eclipse-temurin:25-jre WORKDIR /app COPY --from=build /app/target/backend-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index bb1f947..c83f5e0 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,7 +1,7 @@ # ========================================== # Stage 1: Build using Node.js + Bun # ========================================== -FROM node:26-alpine AS build +FROM docker.io/library/node:26-alpine AS build WORKDIR /app RUN npm install -g bun @@ -19,10 +19,10 @@ RUN bun run build # ========================================== # Stage 2: Serve the app using Nginx # ========================================== -FROM nginx:alpine +FROM docker.io/library/nginx:alpine RUN rm -rf /usr/share/nginx/html/* COPY --from=build /app/dist/lmnp-frontend/browser /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/angular.json b/frontend/angular.json index 175e64c..b00c5e6 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -83,7 +83,7 @@ "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] + "lintFilePatterns": ["src/**/*.ts", "src/**/*.html", "e2e/**/*.ts"] } } } diff --git a/frontend/e2e/app.spec.ts b/frontend/e2e/app.spec.ts index ba824ea..7abd052 100644 --- a/frontend/e2e/app.spec.ts +++ b/frontend/e2e/app.spec.ts @@ -58,7 +58,13 @@ test('persists a dark appearance preference from Settings', async ({ page }) => await page.getByRole('radio', { name: 'Sombre' }).check(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); - expect(await page.evaluate(() => localStorage.getItem('lmnp-theme'))).toBe('dark'); + expect( + await page.evaluate( + // This callback runs in the browser page, where localStorage is guaranteed by the test. + // eslint-disable-next-line no-restricted-globals + () => localStorage.getItem('lmnp-theme'), + ), + ).toBe('dark'); await page.reload(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); @@ -192,7 +198,11 @@ test('keeps the secured API check and logout available', async ({ page }) => { }); test('follows operating-system changes for the System theme', async ({ page }) => { - await page.addInitScript(() => localStorage.setItem('lmnp-theme', 'system')); + await page.addInitScript( + // This callback runs in the browser page, where localStorage is guaranteed by the test. + // eslint-disable-next-line no-restricted-globals + () => localStorage.setItem('lmnp-theme', 'system'), + ); await page.emulateMedia({ colorScheme: 'dark' }); await mockUser(page, true); await mockEmptyApartmentList(page); diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 80eaa26..3e26df0 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -16,11 +16,36 @@ module.exports = defineConfig([ processor: angular.processInlineTemplates, languageOptions: { parserOptions: { - project: ['./tsconfig.json', './tsconfig.app.json', './tsconfig.spec.json'], + project: [ + './tsconfig.json', + './tsconfig.app.json', + './tsconfig.spec.json', + './tsconfig.e2e.json', + ], tsconfigRootDir: __dirname, }, }, rules: { + 'no-restricted-globals': [ + 'error', + { + name: 'localStorage', + message: 'Use getBrowserStorage() for storage that works outside a browser.', + }, + ], + 'no-restricted-properties': [ + 'error', + { + object: 'window', + property: 'localStorage', + message: 'Use getBrowserStorage() for storage that works outside a browser.', + }, + { + object: 'globalThis', + property: 'localStorage', + message: 'Use getBrowserStorage() for storage that works outside a browser.', + }, + ], '@typescript-eslint/no-deprecated': 'error', '@angular-eslint/directive-selector': [ 'error', @@ -40,6 +65,12 @@ module.exports = defineConfig([ ], }, }, + { + files: ['**/services/browser-storage.ts'], + rules: { + 'no-restricted-properties': 'off', + }, + }, { files: ['**/*.html'], extends: [angular.configs.templateRecommended, angular.configs.templateAccessibility], diff --git a/frontend/src/app/services/browser-storage.ts b/frontend/src/app/services/browser-storage.ts new file mode 100644 index 0000000..9ee74a1 --- /dev/null +++ b/frontend/src/app/services/browser-storage.ts @@ -0,0 +1,7 @@ +export function getBrowserStorage(): Storage | null { + try { + return typeof globalThis.localStorage === 'undefined' ? null : globalThis.localStorage; + } catch { + return null; + } +} diff --git a/frontend/src/app/services/theme.service.ts b/frontend/src/app/services/theme.service.ts index 2df7b5b..f70ea25 100644 --- a/frontend/src/app/services/theme.service.ts +++ b/frontend/src/app/services/theme.service.ts @@ -1,5 +1,6 @@ import { DOCUMENT } from '@angular/common'; import { effect, inject, Injectable, signal } from '@angular/core'; +import { getBrowserStorage } from './browser-storage'; export type ThemePreference = 'light' | 'dark' | 'system'; @@ -8,7 +9,7 @@ const THEME_STORAGE_KEY = 'lmnp-theme'; @Injectable({ providedIn: 'root' }) export class ThemeService { private readonly document = inject(DOCUMENT); - private readonly storage = typeof localStorage === 'undefined' ? null : localStorage; + private readonly storage = getBrowserStorage(); private readonly colorSchemeQuery = typeof window.matchMedia === 'function' ? window.matchMedia('(prefers-color-scheme: dark)')