Redesign B3 bordeaux + finish lab-page rework

- Lab page is now read-only with a settings screen for levels/variants;
  variant issuance moved to a large modal on the group page (assign-fragment).
- Header: bordeaux logo and 2px accent underline, bordeaux login/logout/theme
  buttons; smaller radii across the UI; filled secondary/danger buttons.
- Includes prior agent work (login backoff, no-cache pages, API timeouts,
  content tweaks) and fixes (variant usage Map.values, import order).
- Bump static asset version to v=9.
This commit is contained in:
2026-10-02 15:41:16 +03:00
parent c0cf11a5a3
commit 51d1b84297
67 changed files with 3019 additions and 653 deletions

View File

@@ -37,3 +37,8 @@ class UnauthorizedException(
message: String,
code: String? = null,
) : LmsException(HttpStatus.UNAUTHORIZED, message, code)
class TooManyRequestsException(
message: String,
code: String? = null,
) : LmsException(HttpStatus.TOO_MANY_REQUESTS, message, code)

View File

@@ -1,9 +1,11 @@
package ru.ysu.lms.common
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.multipart.MaxUploadSizeExceededException
@RestControllerAdvice
class LmsExceptionHandler {
@@ -34,4 +36,37 @@ class LmsExceptionHandler {
),
)
}
/** Некорректный UUID/аргумент в пути или теле — клиентская ошибка, а не 500. */
@ExceptionHandler(IllegalArgumentException::class)
fun handleIllegalArgument(): ResponseEntity<ApiError> =
ResponseEntity.badRequest().body(
ApiError(
title = "Bad Request",
status = 400,
detail = "Некорректный параметр запроса",
),
)
/** Нарушение уникальности/ссылок (гонки двойной отправки) — конфликт, а не 500. */
@ExceptionHandler(DataIntegrityViolationException::class)
fun handleDataIntegrity(): ResponseEntity<ApiError> =
ResponseEntity.status(409).body(
ApiError(
title = "Conflict",
status = 409,
detail = "Действие конфликтует с уже существующими данными",
),
)
/** Превышение лимита загрузки — вместо 500 понятный 413. */
@ExceptionHandler(MaxUploadSizeExceededException::class)
fun handleMaxUpload(): ResponseEntity<ApiError> =
ResponseEntity.status(413).body(
ApiError(
title = "Payload Too Large",
status = 413,
detail = "Файл слишком большой",
),
)
}