Files
magistr/docs/DEVELOPMENT.md
2026-07-19 20:16:12 +03:00

12 KiB
Raw Blame History

🛠 Руководство для разработчиков

Локальный запуск

Предварительные требования

  • Docker и Docker Compose
  • Git
  • (Опционально) Java 17 + Maven 3.9+ для запуска backend вне Docker

Первый запуск

# Создать Docker-сеть
docker network create proxy

# Собрать и запустить
docker compose up -d --build

# Убедиться, что всё работает
docker compose logs -f

Приложение доступно: http://localhost:80

Обновление контейнерных артефактов

Базовые и служебные образы указываются как точный-tag@sha256:manifest-digest; сторонние Actions — полным 40-символьным commit SHA. При обновлении версии одновременно обновите tag, digest и комментарий версии, затем выполните:

bash scripts/test-artifact-pinning.sh
K8S_DIR=../k8s bash scripts/check-artifact-pinning.sh

Для скачиваемого исполняемого файла обязательны точная версия и проверка опубликованного SHA-256 до установки. Отключать SBOM, provenance или блокирующее HIGH/CRITICAL-сканирование ради прохождения pipeline запрещено.

Пересборка после изменений

# Пересобрать только backend
docker compose up -d --build backend

# Пересобрать только frontend
docker compose up -d --build frontend

Полный сброс данных

docker compose down -v     # Удаляет БД
docker compose up -d       # Пересоздаёт с нуля

Соглашения о коде

Java (Backend)

Именование

Категория Стиль Пример
Классы PascalCase ScheduleController, RenderedLessonDto
Методы и переменные camelCase buildScheduleForTeacher(), teacherId
Константы UPPER_SNAKE_CASE ROLE_REDIRECTS
Пакеты lowercase com.magistr.app.controller

Архитектурные правила

  • Constructor Injection — все зависимости через конструктор (не @Autowired на поля)
  • Controller → Repository — контроллеры работают напрямую с репозиториями (без слоя service)
  • Префикс /api/ — все REST-эндпоинты
  • ResponseEntity<?> — все мутирующие методы возвращают ResponseEntity с HTTP-статусом
  • Сообщения на русском — все ошибки и уведомления на русском языке

Логирование

Используйте SLF4J:

private static final Logger logger = LoggerFactory.getLogger(MyController.class);

// Информационные сообщения
logger.info("Запрос на получение всех занятий");

// Технические детали остаются только в журнале
logger.error("Ошибка при сохранении записи", e);

Валидация

  • Для сложных правил — отдельные сервисы или приватные методы в контроллере с понятным сообщением об ошибке
  • Для простых — inline-проверки в контроллере с ResponseEntity.badRequest()
  • Не формируйте HTTP-ответ из Exception.getMessage(): JDBC/SQL-исключения передаются в GlobalExceptionHandler, который возвращает безопасный русский 400/409/500

Импорты

// 1. Static imports
import static org.junit.Assert.*;

// 2. Java/Jakarta
import java.util.*;
import jakarta.persistence.*;

// 3. External libraries
import org.springframework.web.bind.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;

// 4. Internal packages (wildcard для того же модуля)
import com.magistr.app.model.*;
import com.magistr.app.repository.*;

Форматирование

  • Отступы: 4 пробела
  • Скобки: K&R style (открывающая на той же строке)
  • Длина строки: до 120 символов
  • Фигурные скобки обязательны для if/for/while

JavaScript (Frontend)

Именование

Категория Стиль Пример
Файлы kebab-case main.js, schedule-view.js
Функции и переменные camelCase loadUsers(), pageTitle
Константы UPPER_SNAKE_CASE API_BASE_URL

Модули

  • ES6 Modules с import/export
  • Всегда указывать расширение: import { api } from './api.js';

Лучшие практики

// ✅ Авторизация только через общий модуль; access JWT не хранится в Web Storage
const session = getSession();

// ✅ Async/await вместо .then()
async function loadData() {
    try {
        const data = await api.get('/api/users');
    } catch (e) {
        console.error('Ошибка:', e.message);
    }
}

// ✅ Template literals
const msg = `Найдено ${items.length} записей`;

// ✅ Деструктуризация
const { id, name, role } = user;

Форматирование

  • Отступы: 4 пробела
  • Кавычки: одинарные '
  • Точки с запятой: обязательны

Создание нового эндпоинта (пошагово)

1. Модель (если нужна новая таблица)

Создайте Flyway миграцию V{N}__{description}.sql:

-- backend/src/main/resources/db/migration/V3__add_absences.sql
CREATE TABLE IF NOT EXISTS absences (
    id BIGSERIAL PRIMARY KEY,
    teacher_id BIGINT NOT NULL REFERENCES users(id),
    reason VARCHAR(255) NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL
);

Создайте JPA-сущность:

@Entity
@Table(name = "absences")
public class Absence {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // ...
}

2. Репозиторий

public interface AbsenceRepository extends JpaRepository<Absence, Long> {
    List<Absence> findByTeacherId(Long teacherId);
}

3. DTO (опционально)

public record AbsenceResponse(Long id, String teacherName, String reason) {}

4. Контроллер

@RestController
@RequestMapping("/api/absences")
public class AbsenceController {
    private final AbsenceRepository absenceRepository;

    public AbsenceController(AbsenceRepository absenceRepository) {
        this.absenceRepository = absenceRepository;
    }

    @GetMapping
    public List<Absence> getAll() {
        return absenceRepository.findAll();
    }
}

Работа со временем

  • Для абсолютного момента используйте Instant; в БД ему соответствует TIMESTAMPTZ.
  • Для календарной даты без времени используйте LocalDate; текущую бизнес-дату получайте через инъекцию BusinessTimeService, а не через LocalDate.now().
  • Новую бизнес-логику, зависящую от текущего времени, проверяйте фиксированным Clock на границе московской полуночи.
  • Во frontend не формируйте date-only через Date.toISOString().slice(0, 10): используйте formatLocalDate() или исходную строку YYYY-MM-DD.

Работа с миграциями Flyway

Правила

  1. Никогда не изменяйте уже закоммиченные файлы миграций без прямой просьбы пользователя
  2. Имя файла: V{номер}__{описание}.sql (два подчёркивания!)
  3. Нумерация строго инкрементальная: V1, V2, V3, ...
  4. После добавления — перезапустите backend для применения

Изменение V1__init.sql допустимо только как осознанное исключение на этапе разработки. Для уже применённой V1 требуется полный сброс tenant-схем или удаление истории Flyway перед запуском backend, иначе будет checksum mismatch.

Текущее состояние проекта — единый baseline V1__init.sql: по прямому решению владельца содержимое прежних V2V7 объединено в V1, клиентских tenant-БД нет. До отдельного решения о фиксации baseline новые DB-инварианты добавляются в V1 и проверяются на полностью чистой БД.

Применение

# Локально — сброс и повтор всех миграций
docker compose down -v && docker compose up -d

# Продакшн — применить к существующим тенантам
kubectl rollout restart deployment backend -n magistr

Структура пакетов (Backend)

com.magistr.app/
├── Application.java           # Точка входа
├── config/
│   ├── AppConfig.java         # Бины (BCryptPasswordEncoder)
│   ├── DataInitializer.java   # Инициализация данных
│   └── tenant/                # Мультитенантность
│       ├── TenantConfig.java          # POJO конфигурации тенанта
│       ├── TenantContext.java         # ThreadLocal текущего тенанта
│       ├── TenantInterceptor.java     # Определение тенанта из Host
│       ├── TenantRoutingDataSource.java  # Маршрутизация к БД
│       ├── TenantDataSourceConfig.java   # Конфигурация DataSource/JPA
│       ├── TenantWebMvcConfig.java       # Регистрация MVC-интерцепторов
│       ├── TenantConfigWatcher.java      # Периодическая синхронизация
│       └── KubernetesTenantSecretUpdater.java # Обновление tenant Secret с проверкой TLS
├── controller/                # REST-контроллеры
│   ├── AuthController.java
│   ├── GlobalExceptionHandler.java
│   ├── ScheduleController.java
│   ├── ClassroomController.java
│   ├── DatabaseController.java
│   ├── UserController.java
│   ├── GroupController.java
│   ├── SubjectController.java
│   ├── EquipmentController.java
│   ├── EducationFormController.java
│   └── TeacherSubjectController.java
├── dto/                       # Data Transfer Objects
├── model/                     # JPA-сущности
├── repository/                # Spring Data JPA
└── utils/                     # Общие утилиты