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>
67 lines
2.7 KiB
Docker
67 lines
2.7 KiB
Docker
# 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
|
||
WORKDIR /app
|
||
|
||
# Сначала 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
|
||
|
||
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
|
||
|
||
# Раскладываем jar по слоям: зависимости меняются редко и кэшируются отдельно от кода
|
||
RUN java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted
|
||
|
||
|
||
# 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 --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
|
||
|
||
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"]
|