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

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

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

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

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

```bash
# Создать 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
bash scripts/test-artifact-pinning.sh
K8S_DIR=../k8s bash scripts/check-artifact-pinning.sh
```

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

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

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

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

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

```bash
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:

```java
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`

#### Импорты

```java
// 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';`

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

```javascript
// ✅ Авторизация только через общий модуль; 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`:

```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-сущность:

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

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

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

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

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

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

```java
@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`: по прямому решению владельца
содержимое прежних V2–V7 объединено в V1, клиентских tenant-БД нет. До отдельного решения о
фиксации baseline новые DB-инварианты добавляются в V1 и проверяются на полностью чистой БД.

### Применение

```bash
# Локально — сброс и повтор всех миграций
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/                     # Общие утилиты
```
