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)')