feat: finish demo — Angular 22 + Material UI, role management, schema and Docker cleanup

Frontend
- Upgrade Angular 19 -> 22 (TypeScript 6, new control flow), drop unused
  packages (bootstrap, jquery, popper, ng-bootstrap, webpack)
- Move the UI to Angular Material: toolbar with user menu, tutorials list,
  add/edit forms, admin users table with role editing dialog, profile,
  home page with published tutorials, registration form, login dialog
- Guard routes: login required for add/edit/profile, admin role for /admin
- Remove the /login, Moderator and User pages, event bus, auth interceptor
  and other dead code

Backend
- Add PUT /api/users/{id}/roles (admin only) that keeps at least one admin
  and does not let an admin drop their own admin role
- Tighten security: read tutorials publicly, change them only when logged
  in, users/roles API for admins only; hide password hashes in JSON
- Remove unused Settings and Test endpoints, DTO mappers, commented code
- Flyway V2 aligns the schema with the entities (bigint ids, tutorials PK,
  identity ids); Hibernate now runs with ddl-auto: validate

Docker
- Three-stage Dockerfile (Node build, Gradle build, JRE runtime) with
  layer caching, non-root user and healthcheck; add .dockerignore
- docker-compose waits for a healthy Postgres, always rebuilds the image,
  no longer mounts the Docker socket

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 19:03:58 +02:00
co-authored by Claude Opus 5.5
parent fd51342a0c
commit 3f5a6c26bb
119 changed files with 18172 additions and 9070 deletions
+54 -25
View File
@@ -1,37 +1,66 @@
# 1. Этап сборки (Build stage)
# syntax=docker/dockerfile:1
# 1. Сборка Angular
FROM node:24-alpine AS ui
WORKDIR /ui
# Сначала только манифесты — слой с зависимостями пересобирается лишь при их изменении.
# npm install, а не npm ci: сборка не должна падать, если package-lock.json разошёлся с package.json
COPY spring-angular-ui/package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm install --legacy-peer-deps --no-audit --no-fund
COPY spring-angular-ui/ ./
RUN npx ng build
# 2. Сборка jar
FROM eclipse-temurin:26-jdk-jammy AS build
# Устанавливаем только Node.js v20 для Angular
RUN apt-get update && apt-get install -y curl \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Копируем всё содержимое проекта
COPY . .
# Даем права на запуск Gradle
# Сначала Gradle-обвязка — зависимости скачиваются заново лишь при изменении build.gradle
COPY gradlew settings.gradle build.gradle ./
COPY gradle ./gradle
RUN chmod +x gradlew
RUN --mount=type=cache,target=/root/.gradle \
./gradlew dependencies --no-daemon -q > /dev/null
# Устанавливаем зависимости фронтенда
RUN cd spring-angular-ui && npm install --legacy-peer-deps
COPY src ./src
# Свежесобранный фронт кладём туда же, куда его копирует задача copyAngularBuild
COPY --from=ui /ui/static/browser ./src/main/resources/static
RUN --mount=type=cache,target=/root/.gradle \
./gradlew bootJar -x test --no-daemon \
&& cp "$(ls build/libs/*.jar | grep -v -- '-plain.jar' | head -n 1)" app.jar
# Запускаем сборку через Gradle
RUN ./gradlew copyAngularBuild --no-daemon --info
RUN ./gradlew bootJar -x test --no-daemon
# Раскладываем jar по слоям: зависимости меняются редко и кэшируются отдельно от кода
RUN java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted
# 2. Финальный образ (Runtime stage)
FROM eclipse-temurin:26-jdk-jammy
# Здесь оставляем все утилиты управления контейнерами
RUN apt-get update && \
apt-get install -y docker.io docker-compose-v2 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# 3. Запуск
FROM eclipse-temurin:26-jre-jammy
# curl нужен для healthcheck
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# Непривилегированный пользователь; InitDataService пишет начальные данные в /jambotron_data
RUN groupadd --system app \
&& useradd --system --gid app --home-dir /app app \
&& mkdir -p /jambotron_data \
&& chown app:app /jambotron_data
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
COPY --from=build --chown=app:app /app/extracted/dependencies/ ./
COPY --from=build --chown=app:app /app/extracted/spring-boot-loader/ ./
COPY --from=build --chown=app:app /app/extracted/snapshot-dependencies/ ./
COPY --from=build --chown=app:app /app/extracted/application/ ./
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"]
HEALTHCHECK --interval=15s --timeout=5s --start-period=60s --retries=5 \
CMD curl -fs http://localhost:8080/actuator/health || exit 1
# JVM ориентируется на лимит памяти контейнера, а не на память хоста
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "org.springframework.boot.loader.launch.JarLauncher"]