Refactor: reduce detekt findings (unused params, throws, long methods, config)
This commit is contained in:
@@ -37,6 +37,16 @@ style:
|
|||||||
active: true
|
active: true
|
||||||
LoopWithTooManyJumpStatements:
|
LoopWithTooManyJumpStatements:
|
||||||
maxJumpCount: 2
|
maxJumpCount: 2
|
||||||
|
# MVC-контроллеры возвращают имя шаблона — это не «магическая константа».
|
||||||
|
FunctionOnlyReturningConstant:
|
||||||
|
ignoreAnnotated:
|
||||||
|
- 'GetMapping'
|
||||||
|
- 'PostMapping'
|
||||||
|
|
||||||
|
performance:
|
||||||
|
# runApplication<App>(*args) — идиоматичная точка входа Spring Boot.
|
||||||
|
SpreadOperator:
|
||||||
|
active: false
|
||||||
|
|
||||||
naming:
|
naming:
|
||||||
FunctionNaming:
|
FunctionNaming:
|
||||||
@@ -44,6 +54,9 @@ naming:
|
|||||||
- 'Test'
|
- 'Test'
|
||||||
MatchingDeclarationName:
|
MatchingDeclarationName:
|
||||||
active: false
|
active: false
|
||||||
|
# Enum-значения хранятся в БД и API строками в lowerCamelCase.
|
||||||
|
EnumNaming:
|
||||||
|
active: false
|
||||||
|
|
||||||
empty-blocks:
|
empty-blocks:
|
||||||
EmptyCatchBlock:
|
EmptyCatchBlock:
|
||||||
|
|||||||
@@ -134,7 +134,35 @@ class MarkdownRenderer(
|
|||||||
|
|
||||||
private fun buildSanitizer(): PolicyFactory =
|
private fun buildSanitizer(): PolicyFactory =
|
||||||
HtmlPolicyBuilder()
|
HtmlPolicyBuilder()
|
||||||
.allowElements(
|
.allowElements(*ALLOWED_ELEMENTS)
|
||||||
|
.allowAttributes("href")
|
||||||
|
.onElements("a")
|
||||||
|
.allowAttributes("title")
|
||||||
|
.onElements("a", "img")
|
||||||
|
.allowAttributes("src", "alt")
|
||||||
|
.onElements("img")
|
||||||
|
.allowAttributes("src", "poster", "controls", "preload", "width", "height", "loop", "muted", "playsinline")
|
||||||
|
.onElements("video")
|
||||||
|
.allowAttributes("src", "type")
|
||||||
|
.onElements("source")
|
||||||
|
.allowAttributes("src", "kind", "label", "srclang", "default")
|
||||||
|
.onElements("track")
|
||||||
|
.allowAttributes("class")
|
||||||
|
.onElements(*CLASS_ATTR_ELEMENTS)
|
||||||
|
.allowAttributes("id")
|
||||||
|
.onElements("h1", "h2", "h3", "h4", "h5", "h6")
|
||||||
|
.allowAttributes("colspan", "rowspan")
|
||||||
|
.onElements("td", "th")
|
||||||
|
.allowUrlProtocols("http", "https", "mailto")
|
||||||
|
.requireRelNofollowOnLinks()
|
||||||
|
.toFactory()
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val MAX_DEPTH = 5
|
||||||
|
|
||||||
|
/** Теги, разрешённые в отрендеренном HTML (allow-list санитайзера). */
|
||||||
|
val ALLOWED_ELEMENTS =
|
||||||
|
arrayOf(
|
||||||
"p",
|
"p",
|
||||||
"br",
|
"br",
|
||||||
"hr",
|
"hr",
|
||||||
@@ -177,20 +205,11 @@ class MarkdownRenderer(
|
|||||||
"source",
|
"source",
|
||||||
"track",
|
"track",
|
||||||
"audio",
|
"audio",
|
||||||
).allowAttributes("href")
|
)
|
||||||
.onElements("a")
|
|
||||||
.allowAttributes("title")
|
/** Элементы, которым разрешён атрибут class (подсветка кода, фигуры и т.п.). */
|
||||||
.onElements("a", "img")
|
val CLASS_ATTR_ELEMENTS =
|
||||||
.allowAttributes("src", "alt")
|
arrayOf(
|
||||||
.onElements("img")
|
|
||||||
.allowAttributes("src", "poster", "controls", "preload", "width", "height", "loop", "muted", "playsinline")
|
|
||||||
.onElements("video")
|
|
||||||
.allowAttributes("src", "type")
|
|
||||||
.onElements("source")
|
|
||||||
.allowAttributes("src", "kind", "label", "srclang", "default")
|
|
||||||
.onElements("track")
|
|
||||||
.allowAttributes("class")
|
|
||||||
.onElements(
|
|
||||||
"code",
|
"code",
|
||||||
"pre",
|
"pre",
|
||||||
"div",
|
"div",
|
||||||
@@ -204,16 +223,7 @@ class MarkdownRenderer(
|
|||||||
"figcaption",
|
"figcaption",
|
||||||
"video",
|
"video",
|
||||||
"img",
|
"img",
|
||||||
).allowAttributes("id")
|
)
|
||||||
.onElements("h1", "h2", "h3", "h4", "h5", "h6")
|
|
||||||
.allowAttributes("colspan", "rowspan")
|
|
||||||
.onElements("td", "th")
|
|
||||||
.allowUrlProtocols("http", "https", "mailto")
|
|
||||||
.requireRelNofollowOnLinks()
|
|
||||||
.toFactory()
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val MAX_DEPTH = 5
|
|
||||||
val MEDIA =
|
val MEDIA =
|
||||||
Regex(
|
Regex(
|
||||||
pattern = "(?m)^[ \\t]*:::[ \\t]*(figure|video)[ \\t]+(\\S+)[ \\t]*(?:\\|[ \\t]*(.*?))?[ \\t]*$",
|
pattern = "(?m)^[ \\t]*:::[ \\t]*(figure|video)[ \\t]+(\\S+)[ \\t]*(?:\\|[ \\t]*(.*?))?[ \\t]*$",
|
||||||
|
|||||||
@@ -33,92 +33,112 @@ class LabImportParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseZip(bytes: ByteArray): LabImportBundle {
|
private fun parseZip(bytes: ByteArray): LabImportBundle {
|
||||||
|
val entries = readEntries(bytes)
|
||||||
|
val meta = readMeta(entries)
|
||||||
|
val readmeMd = readReadme(entries)
|
||||||
|
|
||||||
|
val variants = mutableListOf<VariantMeta>()
|
||||||
|
val assets = mutableMapOf<String, ByteArray>()
|
||||||
|
for ((path, data) in entries) {
|
||||||
|
val normalized = path.replace('\\', '/')
|
||||||
|
when {
|
||||||
|
isInDir(normalized, "variants") ->
|
||||||
|
parseVariantFile(normalized, String(data, Charsets.UTF_8), meta.levels)?.let(variants::add)
|
||||||
|
isInDir(normalized, "assets") -> assets[normalized] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (variants.isEmpty()) {
|
||||||
|
throw BadRequestException("В архиве не найдены варианты (папка variants/)")
|
||||||
|
}
|
||||||
|
return LabImportBundle(meta, readmeMd, variants, assets)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readEntries(bytes: ByteArray): Map<String, ByteArray> {
|
||||||
val entries = mutableMapOf<String, ByteArray>()
|
val entries = mutableMapOf<String, ByteArray>()
|
||||||
ZipInputStream(ByteArrayInputStream(bytes)).use { zis ->
|
ZipInputStream(ByteArrayInputStream(bytes)).use { zis ->
|
||||||
var entry = zis.nextEntry
|
var entry = zis.nextEntry
|
||||||
while (entry != null) {
|
while (entry != null) {
|
||||||
if (!entry.isDirectory) {
|
if (!entry.isDirectory) entries[entry.name] = zis.readAllBytes()
|
||||||
entries[entry.name] = zis.readAllBytes()
|
|
||||||
}
|
|
||||||
entry = zis.nextEntry
|
entry = zis.nextEntry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readMeta(entries: Map<String, ByteArray>): LabMeta {
|
||||||
val metaEntry =
|
val metaEntry =
|
||||||
entries.entries.firstOrNull { it.key.endsWith("lab.yaml") || it.key.endsWith("lab.yml") }
|
entries.entries.firstOrNull { it.key.endsWith("lab.yaml") || it.key.endsWith("lab.yml") }
|
||||||
?: throw BadRequestException("В архиве не найден lab.yaml")
|
?: throw BadRequestException("В архиве не найден lab.yaml")
|
||||||
val meta = yaml.readValue(metaEntry.value, LabMeta::class.java)
|
return yaml.readValue(metaEntry.value, LabMeta::class.java)
|
||||||
|
|
||||||
val readmeEntry = entries.entries.firstOrNull { it.key.endsWith("/README.md") || it.key == "README.md" }
|
|
||||||
val readmeMd = readmeEntry?.let { String(it.value, Charsets.UTF_8) } ?: ""
|
|
||||||
|
|
||||||
val variants = mutableListOf<VariantMeta>()
|
|
||||||
val assets = mutableMapOf<String, ByteArray>()
|
|
||||||
|
|
||||||
for ((path, data) in entries) {
|
|
||||||
val p = path.replace('\\', '/')
|
|
||||||
when {
|
|
||||||
p.contains("/variants/") || p.startsWith("variants/") -> {
|
|
||||||
val content = String(data, Charsets.UTF_8)
|
|
||||||
val variant = parseVariantFile(p, content, meta.levels)
|
|
||||||
if (variant != null) variants.add(variant)
|
|
||||||
}
|
|
||||||
p.contains("/assets/") || p.startsWith("assets/") -> {
|
|
||||||
assets[p] = data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variants.isEmpty()) {
|
|
||||||
throw BadRequestException("В архиве не найдены варианты (папка variants/)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return LabImportBundle(meta, readmeMd, variants, assets)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun readReadme(entries: Map<String, ByteArray>): String =
|
||||||
|
entries.entries
|
||||||
|
.firstOrNull { it.key.endsWith("/README.md") || it.key == "README.md" }
|
||||||
|
?.let { String(it.value, Charsets.UTF_8) }
|
||||||
|
?: ""
|
||||||
|
|
||||||
|
private fun isInDir(
|
||||||
|
path: String,
|
||||||
|
dir: String,
|
||||||
|
): Boolean = path.contains("/$dir/") || path.startsWith("$dir/")
|
||||||
|
|
||||||
private fun parseVariantFile(
|
private fun parseVariantFile(
|
||||||
path: String,
|
path: String,
|
||||||
content: String,
|
content: String,
|
||||||
levels: List<LabLevelMeta>,
|
levels: List<LabLevelMeta>,
|
||||||
): VariantMeta? {
|
): VariantMeta? {
|
||||||
val levelMap = levels.associateBy { it.code }
|
|
||||||
val lines = content.lines()
|
val lines = content.lines()
|
||||||
val firstSep = lines.indexOfFirst { it.trim() == "---" }
|
val frontMatter =
|
||||||
if (firstSep != 0) {
|
readFrontMatter(lines)
|
||||||
return VariantMeta(
|
?: return VariantMeta(
|
||||||
level = "basic",
|
level = "basic",
|
||||||
number = path.substringAfterLast("/").removeSuffix(".md"),
|
number = path.substringAfterLast("/").removeSuffix(".md"),
|
||||||
title = "",
|
title = "",
|
||||||
statement = content.trim(),
|
statement = content.trim(),
|
||||||
)
|
)
|
||||||
}
|
val levelCode = normalizeLevel(frontMatter.values["level"] ?: "basic")
|
||||||
val secondSep = lines.withIndex().firstOrNull { it.index != firstSep && it.value.trim() == "---" }?.index ?: -1
|
val number = frontMatter.values["number"]?.takeIf { it.isNotBlank() }
|
||||||
if (secondSep < 0) return null
|
if (levels.none { it.code == levelCode } || number == null) return null
|
||||||
val fmLines = lines.subList(1, secondSep)
|
val body = lines.drop(frontMatter.bodyStart).joinToString("\n").trim()
|
||||||
val fm = mutableMapOf<String, String>()
|
|
||||||
for (line in fmLines) {
|
|
||||||
val parts = line.split(":", limit = 2)
|
|
||||||
if (parts.size == 2) fm[parts[0].trim()] = unquote(parts[1].trim())
|
|
||||||
}
|
|
||||||
val levelCode = normalizeLevel(fm["level"] ?: "basic")
|
|
||||||
if (levelMap[levelCode] == null) return null
|
|
||||||
val body = lines.drop(secondSep + 1).joinToString("\n").trim()
|
|
||||||
return VariantMeta(
|
return VariantMeta(
|
||||||
level = levelCode,
|
level = levelCode,
|
||||||
number = fm["number"]?.takeIf { it.isNotBlank() } ?: return null,
|
number = number,
|
||||||
title = fm["title"] ?: "",
|
title = frontMatter.values["title"] ?: "",
|
||||||
statement = body.ifBlank { "—" },
|
statement = body.ifBlank { "—" },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unquote(value: String): String =
|
private data class FrontMatter(
|
||||||
if (value.length >= 2 &&
|
val values: Map<String, String>,
|
||||||
((value.first() == '"' && value.last() == '"') || (value.first() == '\'' && value.last() == '\''))
|
val bodyStart: Int,
|
||||||
) {
|
)
|
||||||
value.substring(1, value.length - 1)
|
|
||||||
} else {
|
/** Читает YAML-front-matter (между двумя `---`), иначе возвращает null. */
|
||||||
value
|
private fun readFrontMatter(lines: List<String>): FrontMatter? {
|
||||||
|
if (lines.firstOrNull()?.trim() != "---") return null
|
||||||
|
val end =
|
||||||
|
lines
|
||||||
|
.withIndex()
|
||||||
|
.drop(1)
|
||||||
|
.firstOrNull { it.value.trim() == "---" }
|
||||||
|
?.index ?: return null
|
||||||
|
val values = mutableMapOf<String, String>()
|
||||||
|
for (line in lines.subList(1, end)) {
|
||||||
|
val parts = line.split(":", limit = 2)
|
||||||
|
if (parts.size == 2) values[parts[0].trim()] = unquote(parts[1].trim())
|
||||||
}
|
}
|
||||||
|
return FrontMatter(values, end + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unquote(value: String): String = if (isQuoted(value)) value.substring(1, value.length - 1) else value
|
||||||
|
|
||||||
|
private fun isQuoted(value: String): Boolean {
|
||||||
|
if (value.length < 2) return false
|
||||||
|
val first = value.first()
|
||||||
|
val last = value.last()
|
||||||
|
return (first == '"' && last == '"') || (first == '\'' && last == '\'')
|
||||||
|
}
|
||||||
|
|
||||||
private fun normalizeLevel(s: String): String =
|
private fun normalizeLevel(s: String): String =
|
||||||
when (s.lowercase().trim()) {
|
when (s.lowercase().trim()) {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class ProgressController(
|
|||||||
private val service: ProgressService,
|
private val service: ProgressService,
|
||||||
private val publications: GradePublicationService,
|
private val publications: GradePublicationService,
|
||||||
private val submissions: SubmissionService,
|
private val submissions: SubmissionService,
|
||||||
private val academic: AcademicClient,
|
|
||||||
) {
|
) {
|
||||||
@GetMapping("/me/assignments")
|
@GetMapping("/me/assignments")
|
||||||
fun meAssignments(
|
fun meAssignments(
|
||||||
@@ -39,9 +38,7 @@ class ProgressController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/labs")
|
@GetMapping("/me/labs")
|
||||||
fun meLabs(
|
fun meLabs(): List<Map<String, Any>> = listOf()
|
||||||
@AuthenticationPrincipal jwt: Jwt,
|
|
||||||
): List<Map<String, Any>> = listOf()
|
|
||||||
|
|
||||||
@PostMapping("/levels")
|
@PostMapping("/levels")
|
||||||
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
|
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
|
||||||
|
|||||||
@@ -25,32 +25,54 @@ class SubmissionService(
|
|||||||
files: List<MultipartFile>,
|
files: List<MultipartFile>,
|
||||||
token: String,
|
token: String,
|
||||||
): SubmissionDto {
|
): SubmissionDto {
|
||||||
if (files.isEmpty() || files.all { it.isEmpty }) {
|
val payload = validatedPayload(files)
|
||||||
throw BadRequestException("Приложите файлы или архив")
|
ensureDeadline(requireLab(labId, token))
|
||||||
}
|
val seq = nextSeq(studentId, labId)
|
||||||
val lab =
|
|
||||||
runCatching { academic.getLabSummary(labId.toString(), token) }.getOrNull()
|
|
||||||
?: throw NotFoundException("Лабораторная не найдена")
|
|
||||||
val deadline = lab.deadlineAt?.let { runCatching { Instant.parse(it) }.getOrNull() }
|
|
||||||
if (deadline != null && Instant.now().isAfter(deadline)) {
|
|
||||||
throw BadRequestException("Срок сдачи истёк")
|
|
||||||
}
|
|
||||||
val last = submissions.findFirstByStudentIdAndLabIdOrderBySeqDesc(studentId, labId)
|
|
||||||
val seq = (last?.seq ?: 0) + 1
|
|
||||||
val payload = files.mapIndexed { i, f -> (f.originalFilename ?: "file$i") to f.bytes }
|
|
||||||
payload.forEach { (name, bytes) ->
|
|
||||||
if (looksLikeProjectFile(name, bytes)) {
|
|
||||||
throw BadRequestException(
|
|
||||||
"Файл «$name» похож на файл проекта (XML), а не на исходник. " +
|
|
||||||
"Приложите файл .c/.h или zip с проектом.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val stored = storage.store(studentId, labId, seq, payload)
|
val stored = storage.store(studentId, labId, seq, payload)
|
||||||
val entity = submissions.save(Submission(studentId = studentId, labId = labId, seq = seq))
|
val entity = submissions.save(Submission(studentId = studentId, labId = labId, seq = seq))
|
||||||
return toDto(entity, stored.files)
|
return toDto(entity, stored.files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun validatedPayload(files: List<MultipartFile>): List<Pair<String, ByteArray>> {
|
||||||
|
if (files.isEmpty() || files.all { it.isEmpty }) {
|
||||||
|
throw BadRequestException("Приложите файлы или архив")
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
.mapIndexed { i, f -> (f.originalFilename ?: "file$i") to f.bytes }
|
||||||
|
.onEach { (name, bytes) -> rejectProjectFile(name, bytes) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rejectProjectFile(
|
||||||
|
name: String,
|
||||||
|
bytes: ByteArray,
|
||||||
|
) {
|
||||||
|
if (looksLikeProjectFile(name, bytes)) {
|
||||||
|
throw BadRequestException(
|
||||||
|
"Файл «$name» похож на файл проекта (XML), а не на исходник. " +
|
||||||
|
"Приложите файл .c/.h или zip с проектом.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requireLab(
|
||||||
|
labId: UUID,
|
||||||
|
token: String,
|
||||||
|
): LabInfo =
|
||||||
|
runCatching { academic.getLabSummary(labId.toString(), token) }.getOrNull()
|
||||||
|
?: throw NotFoundException("Лабораторная не найдена")
|
||||||
|
|
||||||
|
private fun ensureDeadline(lab: LabInfo) {
|
||||||
|
val deadline = lab.deadlineAt?.let { runCatching { Instant.parse(it) }.getOrNull() }
|
||||||
|
if (deadline != null && Instant.now().isAfter(deadline)) {
|
||||||
|
throw BadRequestException("Срок сдачи истёк")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nextSeq(
|
||||||
|
studentId: UUID,
|
||||||
|
labId: UUID,
|
||||||
|
): Int = (submissions.findFirstByStudentIdAndLabIdOrderBySeqDesc(studentId, labId)?.seq ?: 0) + 1
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun mySubmissions(
|
fun mySubmissions(
|
||||||
studentId: UUID,
|
studentId: UUID,
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ class GradePublication(
|
|||||||
data class GradePublicationKey(
|
data class GradePublicationKey(
|
||||||
val groupId: UUID = UUID(0, 0),
|
val groupId: UUID = UUID(0, 0),
|
||||||
val labId: UUID = UUID(0, 0),
|
val labId: UUID = UUID(0, 0),
|
||||||
) : Serializable
|
) : Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID: Long = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,4 +23,8 @@ class LabGrade(
|
|||||||
data class LabGradeKey(
|
data class LabGradeKey(
|
||||||
val studentId: UUID = UUID(0, 0),
|
val studentId: UUID = UUID(0, 0),
|
||||||
val labId: UUID = UUID(0, 0),
|
val labId: UUID = UUID(0, 0),
|
||||||
) : Serializable
|
) : Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID: Long = 1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.springframework.mock.web.MockMultipartFile
|
|||||||
import ru.ysu.lms.common.BadRequestException
|
import ru.ysu.lms.common.BadRequestException
|
||||||
import ru.ysu.lms.progress.domain.Submission
|
import ru.ysu.lms.progress.domain.Submission
|
||||||
import ru.ysu.lms.progress.repo.SubmissionRepository
|
import ru.ysu.lms.progress.repo.SubmissionRepository
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class ContentAdminController(
|
|||||||
private val api: ApiClient,
|
private val api: ApiClient,
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun index(session: HttpSession): String = "redirect:/learn"
|
fun index(): String = "redirect:/learn"
|
||||||
|
|
||||||
@PostMapping("/import")
|
@PostMapping("/import")
|
||||||
fun import(
|
fun import(
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ class ApiClient(
|
|||||||
private fun <T> callOrNull(block: () -> T?): T? =
|
private fun <T> callOrNull(block: () -> T?): T? =
|
||||||
try {
|
try {
|
||||||
block()
|
block()
|
||||||
} catch (ex: RestClientResponseException) {
|
} catch (ignore: RestClientResponseException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ class ApiClient(
|
|||||||
throw ex
|
throw ex
|
||||||
} catch (ex: RestClientResponseException) {
|
} catch (ex: RestClientResponseException) {
|
||||||
throw ApiException(ex.statusCode.value(), detail(ex))
|
throw ApiException(ex.statusCode.value(), detail(ex))
|
||||||
} catch (ex: RestClientException) {
|
} catch (ignore: RestClientException) {
|
||||||
throw ApiException(502, "Сервис недоступен. Проверьте, что gateway запущен.")
|
throw ApiException(502, "Сервис недоступен. Проверьте, что gateway запущен.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class AuthPageController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/setup")
|
@GetMapping("/setup")
|
||||||
fun setupPage(model: Model): String {
|
fun setupPage(): String {
|
||||||
if (api.adminExists()) {
|
if (api.adminExists()) {
|
||||||
return "redirect:/login"
|
return "redirect:/login"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class LearnController(
|
|||||||
val a: ContentArticleDto =
|
val a: ContentArticleDto =
|
||||||
try {
|
try {
|
||||||
api.contentArticle(token(session), chapter, section, article, all = admin)
|
api.contentArticle(token(session), chapter, section, article, all = admin)
|
||||||
} catch (ex: ApiException) {
|
} catch (ignore: ApiException) {
|
||||||
return "redirect:/learn"
|
return "redirect:/learn"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class StudentController(
|
|||||||
val submittedOrDefending = defense != null && (defense.status == "waiting" || defense.assignmentMd.isNotBlank())
|
val submittedOrDefending = defense != null && (defense.status == "waiting" || defense.assignmentMd.isNotBlank())
|
||||||
model.addAttribute("canSubmit", canSubmit(lab.deadlineAt) && !submittedOrDefending)
|
model.addAttribute("canSubmit", canSubmit(lab.deadlineAt) && !submittedOrDefending)
|
||||||
"student/lab"
|
"student/lab"
|
||||||
} catch (ex: ApiException) {
|
} catch (ignore: ApiException) {
|
||||||
"redirect:/student/labs"
|
"redirect:/student/labs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user