Refactor: split ApiClient into domain extension files

This commit is contained in:
2026-09-27 22:29:57 +03:00
parent d96369c7e3
commit ab382ed178
12 changed files with 1281 additions and 1185 deletions

View File

@@ -27,6 +27,43 @@ import ru.ysu.lms.web.api.LevelPayload
import ru.ysu.lms.web.api.LevelSavePayload import ru.ysu.lms.web.api.LevelSavePayload
import ru.ysu.lms.web.api.StudentRowDto import ru.ysu.lms.web.api.StudentRowDto
import ru.ysu.lms.web.api.VariantSavePayload import ru.ysu.lms.web.api.VariantSavePayload
import ru.ysu.lms.web.api.addLevel
import ru.ysu.lms.web.api.assignDefense
import ru.ysu.lms.web.api.assignVariant
import ru.ysu.lms.web.api.createGroup
import ru.ysu.lms.web.api.createLab
import ru.ysu.lms.web.api.createStudent
import ru.ysu.lms.web.api.credentials
import ru.ysu.lms.web.api.deleteLab
import ru.ysu.lms.web.api.deleteLevel
import ru.ysu.lms.web.api.deleteVariant
import ru.ysu.lms.web.api.distribute
import ru.ysu.lms.web.api.downloadSubmissionAsAdmin
import ru.ysu.lms.web.api.enrollStudent
import ru.ysu.lms.web.api.getLabAssignments
import ru.ysu.lms.web.api.getLevels
import ru.ysu.lms.web.api.groupCandidates
import ru.ysu.lms.web.api.groupDetail
import ru.ysu.lms.web.api.groupPublications
import ru.ysu.lms.web.api.importGroup
import ru.ysu.lms.web.api.importLab
import ru.ysu.lms.web.api.issueDefenseCode
import ru.ysu.lms.web.api.labDefenses
import ru.ysu.lms.web.api.labDetail
import ru.ysu.lms.web.api.labSubmissions
import ru.ysu.lms.web.api.listGrades
import ru.ysu.lms.web.api.listGroups
import ru.ysu.lms.web.api.listLabs
import ru.ysu.lms.web.api.publishGrades
import ru.ysu.lms.web.api.renameLevel
import ru.ysu.lms.web.api.resetDefense
import ru.ysu.lms.web.api.resetPassword
import ru.ysu.lms.web.api.saveVariant
import ru.ysu.lms.web.api.setGrade
import ru.ysu.lms.web.api.setLevel
import ru.ysu.lms.web.api.toggleLabStatus
import ru.ysu.lms.web.api.unenrollStudent
import ru.ysu.lms.web.api.updateLab
import ru.ysu.lms.web.session.SessionKeys import ru.ysu.lms.web.session.SessionKeys
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream

View File

@@ -17,6 +17,22 @@ import ru.ysu.lms.web.api.ApiException
import ru.ysu.lms.web.api.ArticlesReorderDto import ru.ysu.lms.web.api.ArticlesReorderDto
import ru.ysu.lms.web.api.ContentReorderDto import ru.ysu.lms.web.api.ContentReorderDto
import ru.ysu.lms.web.api.SectionsReorderDto import ru.ysu.lms.web.api.SectionsReorderDto
import ru.ysu.lms.web.api.createArticle
import ru.ysu.lms.web.api.createChapter
import ru.ysu.lms.web.api.createSection
import ru.ysu.lms.web.api.deleteChapter
import ru.ysu.lms.web.api.deleteContentArticle
import ru.ysu.lms.web.api.deleteSection
import ru.ysu.lms.web.api.deleteSectionWithMove
import ru.ysu.lms.web.api.importContent
import ru.ysu.lms.web.api.moveArticle
import ru.ysu.lms.web.api.renameChapter
import ru.ysu.lms.web.api.renameSection
import ru.ysu.lms.web.api.reorderArticles
import ru.ysu.lms.web.api.reorderContent
import ru.ysu.lms.web.api.reorderSections
import ru.ysu.lms.web.api.setContentPublished
import ru.ysu.lms.web.api.updateContentArticle
import ru.ysu.lms.web.session.SessionKeys import ru.ysu.lms.web.session.SessionKeys
@Controller @Controller

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
package ru.ysu.lms.web.api
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
fun ApiClient.adminExists(): Boolean =
call {
client
.get()
.uri("/api/v1/auth/health")
.retrieve()
.body(HealthDto::class.java)
}.adminExists
fun ApiClient.setup(
login: String,
password: String,
password2: String,
): AuthResponseDto =
call {
client
.post()
.uri("/api/v1/auth/setup")
.contentType(MediaType.APPLICATION_JSON)
.body(SetupPayload(login, password, password2))
.retrieve()
.body(AuthResponseDto::class.java)
}
fun ApiClient.login(
login: String,
password: String,
): AuthResponseDto =
call {
client
.post()
.uri("/api/v1/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.body(LoginPayload(login, password))
.retrieve()
.body(AuthResponseDto::class.java)
}
fun ApiClient.changePassword(
token: String,
old: String,
new: String,
new2: String,
) {
call {
client
.post()
.uri("/api/v1/auth/change-password")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(ChangePasswordPayload(old, new, new2))
.retrieve()
.toBodilessEntity()
}
}

View File

@@ -0,0 +1,351 @@
package ru.ysu.lms.web.api
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.io.ByteArrayResource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.util.LinkedMultiValueMap
fun ApiClient.contentTree(
token: String?,
all: Boolean = false,
): ContentTreeNodeDto =
call {
val uri = if (all) "/api/v1/content/tree/all" else "/api/v1/content/tree"
val spec = client.get().uri(uri)
(if (token != null) spec.header(HttpHeaders.AUTHORIZATION, bearer(token)) else spec)
.retrieve()
.body(ContentTreeNodeDto::class.java)
}
fun ApiClient.contentArticle(
token: String?,
chapter: String,
section: String,
article: String,
all: Boolean = false,
): ContentArticleDto =
call {
val uri = if (all) "/api/v1/content/all/{c}/{s}/{a}" else "/api/v1/content/{c}/{s}/{a}"
val spec = client.get().uri(uri, chapter, section, article)
(if (token != null) spec.header(HttpHeaders.AUTHORIZATION, bearer(token)) else spec)
.retrieve()
.body(ContentArticleDto::class.java)
}
fun ApiClient.setContentPublished(
token: String,
kind: String,
id: String,
published: Boolean,
) {
val path =
when (kind) {
"chapters" -> "/api/v1/content/chapters/{id}/published"
"sections" -> "/api/v1/content/sections/{id}/published"
"articles" -> "/api/v1/content/articles/{id}/published"
else -> throw IllegalArgumentException("Неизвестный тип материала: $kind")
}
call {
client
.put()
.uri(path, id)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("published" to published))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.contentTreeList(token: String?): List<ContentArticleDto> {
val type = object : ParameterizedTypeReference<List<ContentArticleDto>>() {}
return call {
val spec = client.get().uri("/api/v1/content/articles")
(if (token != null) spec.header(HttpHeaders.AUTHORIZATION, bearer(token)) else spec)
.retrieve()
.body(type)
}
}
fun ApiClient.reorderContent(
token: String,
tree: ContentReorderDto,
) {
call {
client
.post()
.uri("/api/v1/content/reorder")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(tree)
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.reorderSections(
token: String,
chapterId: String,
body: SectionsReorderDto,
) {
call {
client
.post()
.uri("/api/v1/content/chapters/{id}/sections/reorder", chapterId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(body)
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.reorderArticles(
token: String,
sectionId: String,
body: ArticlesReorderDto,
) {
call {
client
.post()
.uri("/api/v1/content/sections/{id}/articles/reorder", sectionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(body)
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.importContent(
token: String,
filename: String,
bytes: ByteArray,
sync: Boolean = true,
): ContentImportResultDto {
val parts = LinkedMultiValueMap<String, Any>()
parts.add("sync", sync.toString())
parts.add(
"file",
object : ByteArrayResource(bytes) {
override fun getFilename(): String = filename
},
)
return call {
client
.post()
.uri("/api/v1/content/import")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(parts)
.retrieve()
.body(ContentImportResultDto::class.java)
}
}
fun ApiClient.updateContentArticle(
token: String,
articleId: String,
title: String,
bodyMd: String,
) {
call {
client
.put()
.uri("/api/v1/content/articles/{id}", articleId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("title" to title, "bodyMd" to bodyMd))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.deleteContentArticle(
token: String,
articleId: String,
) {
call {
client
.delete()
.uri("/api/v1/content/articles/{id}", articleId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.contentAsset(name: String): DownloadDto {
val entity =
call {
client
.get()
.uri("/api/v1/content/assets/{name}", name)
.retrieve()
.toEntity(ByteArray::class.java)
}
return DownloadDto(
bytes = entity.body ?: ByteArray(0),
filename = name,
contentType = entity.headers.contentType?.toString() ?: "application/octet-stream",
)
}
fun ApiClient.createChapter(
token: String,
title: String,
slug: String?,
): Map<String, Any> {
val type = object : ParameterizedTypeReference<Map<String, Any>>() {}
return call {
client
.post()
.uri("/api/v1/content/chapters")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("title" to title, "slug" to slug))
.retrieve()
.body(type)
}
}
fun ApiClient.renameChapter(
token: String,
chapterId: String,
title: String,
) {
call {
client
.put()
.uri("/api/v1/content/chapters/{id}", chapterId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("title" to title))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.deleteChapter(
token: String,
chapterId: String,
) {
call {
client
.delete()
.uri("/api/v1/content/chapters/{id}", chapterId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.createSection(
token: String,
chapterId: String,
title: String,
slug: String?,
): Map<String, Any> {
val type = object : ParameterizedTypeReference<Map<String, Any>>() {}
return call {
client
.post()
.uri("/api/v1/content/sections?chapterId={cid}", chapterId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("title" to title, "slug" to slug))
.retrieve()
.body(type)
}
}
fun ApiClient.renameSection(
token: String,
sectionId: String,
title: String,
) {
call {
client
.put()
.uri("/api/v1/content/sections/{id}", sectionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("title" to title))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.deleteSection(
token: String,
sectionId: String,
) {
call {
client
.delete()
.uri("/api/v1/content/sections/{id}", sectionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.deleteSectionWithMove(
token: String,
sectionId: String,
moveTo: String,
) {
call {
client
.delete()
.uri { b -> b.path("/api/v1/content/sections/{id}").queryParam("moveTo", moveTo).build(sectionId) }
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.createArticle(
token: String,
chapterId: String?,
sectionId: String?,
newChapterTitle: String?,
newSectionTitle: String?,
title: String,
bodyMd: String,
): ContentArticleDto =
call {
client
.post()
.uri("/api/v1/content/articles")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(
mapOf(
"chapterId" to chapterId,
"sectionId" to sectionId,
"newChapterTitle" to newChapterTitle,
"newSectionTitle" to newSectionTitle,
"title" to title,
"bodyMd" to bodyMd,
),
).retrieve()
.body(ContentArticleDto::class.java)
}
fun ApiClient.moveArticle(
token: String,
articleId: String,
newSectionId: String,
): ContentArticleDto =
call {
client
.post()
.uri("/api/v1/content/articles/{id}/move", articleId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(ArticleMovePayload(newSectionId))
.retrieve()
.body(ContentArticleDto::class.java)
}

View File

@@ -0,0 +1,173 @@
package ru.ysu.lms.web.api
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.io.ByteArrayResource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.util.LinkedMultiValueMap
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
fun ApiClient.listGroups(token: String): List<GroupDto> {
val type = object : ParameterizedTypeReference<List<GroupDto>>() {}
return call {
client
.get()
.uri("/api/v1/groups")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.createGroup(
token: String,
name: String,
): GroupDto =
call {
client
.post()
.uri("/api/v1/groups")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(CreateGroupPayload(name))
.retrieve()
.body(GroupDto::class.java)
}
fun ApiClient.importGroup(
token: String,
name: String?,
filename: String,
bytes: ByteArray,
): ImportResultDto {
val parts = LinkedMultiValueMap<String, Any>()
parts.add(
"file",
object : ByteArrayResource(bytes) {
override fun getFilename(): String = filename
},
)
if (!name.isNullOrBlank()) {
parts.add("name", name)
}
return call {
client
.post()
.uri("/api/v1/groups/import")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(parts)
.retrieve()
.body(ImportResultDto::class.java)
}
}
fun ApiClient.groupDetail(
token: String,
groupId: String,
): GroupDetailDto =
call {
client
.get()
.uri("/api/v1/groups/{id}", groupId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(GroupDetailDto::class.java)
}
fun ApiClient.groupCandidates(
token: String,
groupId: String,
): List<StudentCandidateDto> {
val type = object : ParameterizedTypeReference<List<StudentCandidateDto>>() {}
return call {
client
.get()
.uri("/api/v1/groups/{id}/candidates", groupId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.enrollStudent(
token: String,
groupId: String,
studentId: String,
) {
call {
client
.post()
.uri("/api/v1/groups/{id}/students/{sid}/enroll", groupId, studentId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.unenrollStudent(
token: String,
groupId: String,
studentId: String,
) {
call {
client
.delete()
.uri("/api/v1/groups/{id}/students/{sid}", groupId, studentId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.resetPassword(
token: String,
studentId: String,
): ResetPasswordDto =
call {
client
.post()
.uri("/api/v1/students/{id}/reset-password", studentId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(ResetPasswordDto::class.java)
}
fun ApiClient.credentials(
token: String,
groupId: String,
format: String,
): DownloadDto {
val entity =
call {
client
.get()
.uri { builder ->
builder.path("/api/v1/groups/{id}/credentials").queryParam("format", format).build(groupId)
}.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toEntity(ByteArray::class.java)
}
val disposition = entity.headers.getFirst(HttpHeaders.CONTENT_DISPOSITION).orEmpty()
val rawName = disposition.substringAfter("UTF-8''", "").substringBefore(';').ifBlank { "credentials.$format" }
val filename = URLDecoder.decode(rawName, StandardCharsets.UTF_8)
val contentType = entity.headers.contentType?.toString() ?: "application/octet-stream"
return DownloadDto(entity.body ?: ByteArray(0), filename, contentType)
}
fun ApiClient.createStudent(
token: String,
groupId: String,
payload: CreateStudentRequestDto,
): CredentialDto =
call {
client
.post()
.uri("/api/v1/groups/{id}/students", groupId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(CredentialDto::class.java)
}

View File

@@ -0,0 +1,246 @@
package ru.ysu.lms.web.api
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.io.ByteArrayResource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.util.LinkedMultiValueMap
fun ApiClient.listLabs(token: String): List<LabSummaryDto> {
val type = object : ParameterizedTypeReference<List<LabSummaryDto>>() {}
return call {
client
.get()
.uri("/api/v1/labs")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.labDetail(
token: String,
labId: String,
): LabDetailDto =
call {
client
.get()
.uri("/api/v1/labs/{id}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(LabDetailDto::class.java)
}
fun ApiClient.importLab(
token: String,
filename: String,
bytes: ByteArray,
): LabImportResultDto {
val parts = LinkedMultiValueMap<String, Any>()
parts.add(
"file",
object : ByteArrayResource(bytes) {
override fun getFilename(): String = filename
},
)
return call {
client
.post()
.uri("/api/v1/labs/import")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(parts)
.retrieve()
.body(LabImportResultDto::class.java)
}
}
fun ApiClient.deleteLab(
token: String,
labId: String,
) {
call {
client
.delete()
.uri("/api/v1/labs/{id}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.toggleLabStatus(
token: String,
labId: String,
) {
call {
client
.post()
.uri("/api/v1/labs/{id}/toggle-status", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.getLevels(
token: String,
labId: String,
): List<LevelAssignmentResultDto> {
val type = object : ParameterizedTypeReference<List<LevelAssignmentResultDto>>() {}
return call {
client
.get()
.uri("/api/v1/levels?labId={labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.setLevel(
token: String,
payload: LevelAssignPayload,
): LevelAssignmentResultDto =
call {
client
.post()
.uri("/api/v1/levels")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(LevelAssignmentResultDto::class.java)
}
fun ApiClient.updateLab(
token: String,
labId: String,
payload: LabUpdatePayload,
): LabDetailDto =
call {
client
.put()
.uri("/api/v1/labs/{id}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(LabDetailDto::class.java)
}
fun ApiClient.saveVariant(
token: String,
labId: String,
variantId: String?,
payload: VariantSavePayload,
): LabDetailDto =
if (variantId.isNullOrBlank()) {
addVariant(token, labId, payload)
} else {
call {
client
.put()
.uri("/api/v1/labs/{id}/variants/{vid}", labId, variantId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(LabDetailDto::class.java)
}
}
fun ApiClient.addVariant(
token: String,
labId: String,
payload: VariantSavePayload,
): LabDetailDto =
call {
client
.post()
.uri("/api/v1/labs/{id}/variants", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(LabDetailDto::class.java)
}
fun ApiClient.deleteVariant(
token: String,
labId: String,
variantId: String,
): LabDetailDto =
call {
client
.delete()
.uri("/api/v1/labs/{id}/variants/{vid}", labId, variantId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(LabDetailDto::class.java)
}
fun ApiClient.createLab(
token: String,
payload: LabCreatePayload,
): LabDetailDto =
call {
client
.post()
.uri("/api/v1/labs")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(LabDetailDto::class.java)
}
fun ApiClient.addLevel(
token: String,
labId: String,
payload: LevelSavePayload,
) {
call {
client
.post()
.uri("/api/v1/labs/{id}/levels", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.renameLevel(
token: String,
labId: String,
levelId: String,
title: String,
) {
call {
client
.put()
.uri("/api/v1/labs/{id}/levels/{lid}", labId, levelId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("title" to title))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.deleteLevel(
token: String,
labId: String,
levelId: String,
) {
call {
client
.delete()
.uri("/api/v1/labs/{id}/levels/{lid}", labId, levelId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}

View File

@@ -0,0 +1,373 @@
package ru.ysu.lms.web.api
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.io.ByteArrayResource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.util.LinkedMultiValueMap
fun ApiClient.distribute(
token: String,
payload: DistributeRequestPayload,
): DistributeResultDto =
call {
client
.post()
.uri("/api/v1/distribute")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(DistributeResultDto::class.java)
}
fun ApiClient.assignVariant(
token: String,
payload: AssignVariantPayload,
): VariantAssignmentResultDto =
call {
client
.post()
.uri("/api/v1/assign")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(VariantAssignmentResultDto::class.java)
}
fun ApiClient.getLabAssignments(
token: String,
labId: String,
): List<VariantAssignmentResultDto> {
val type = object : ParameterizedTypeReference<List<VariantAssignmentResultDto>>() {}
return call {
client
.get()
.uri("/api/v1/assignments?labId={labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.meAssignments(token: String): List<MeAssignmentDto> {
val type = object : ParameterizedTypeReference<List<MeAssignmentDto>>() {}
return call {
client
.get()
.uri("/api/v1/me/assignments")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.listGrades(token: String): List<GradeDto> {
val type = object : ParameterizedTypeReference<List<GradeDto>>() {}
return call {
client
.get()
.uri("/api/v1/grades")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.setGrade(
token: String,
payload: GradeSetPayload,
): GradeDto =
call {
client
.post()
.uri("/api/v1/grades")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(payload)
.retrieve()
.body(GradeDto::class.java)
}
fun ApiClient.suggestComments(
token: String,
query: String,
): List<CommentDto> {
val type = object : ParameterizedTypeReference<List<CommentDto>>() {}
return call {
client
.get()
.uri("/api/v1/comment-templates?q={q}", query)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.myGrades(token: String): List<GradeDto> = myGrades(token, null)
fun ApiClient.myGrades(
token: String,
groupId: String?,
): List<GradeDto> {
val type = object : ParameterizedTypeReference<List<GradeDto>>() {}
return call {
val uri =
if (groupId.isNullOrBlank()) {
"/api/v1/me/grades"
} else {
"/api/v1/me/grades?groupId={gid}"
}
val spec =
client
.get()
.uri(if (groupId.isNullOrBlank()) uri else uri, *if (groupId.isNullOrBlank()) emptyArray() else arrayOf(groupId))
spec.header(HttpHeaders.AUTHORIZATION, bearer(token)).retrieve().body(type)
}
}
fun ApiClient.groupPublications(
token: String,
groupId: String,
): List<String> {
val type = object : ParameterizedTypeReference<List<String>>() {}
return call {
client
.get()
.uri("/api/v1/grades/publications?groupId={gid}", groupId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.publishGrades(
token: String,
labId: String,
groupId: String,
published: Boolean,
) {
call {
client
.post()
.uri("/api/v1/grades/publish?labId={lab}&groupId={g}&published={p}", labId, groupId, published)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.myGroup(token: String): List<GroupDto> {
val type = object : ParameterizedTypeReference<List<GroupDto>>() {}
return call {
client
.get()
.uri("/api/v1/groups/me")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.submitLab(
token: String,
labId: String,
files: List<Pair<String, ByteArray>>,
): List<SubmissionDto> {
val parts = LinkedMultiValueMap<String, Any>()
for ((name, bytes) in files) {
parts.add(
"files",
object : ByteArrayResource(bytes) {
override fun getFilename(): String = name
},
)
}
val type = object : ParameterizedTypeReference<List<SubmissionDto>>() {}
return call {
client
.post()
.uri("/api/v1/me/submissions/{labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(parts)
.retrieve()
.body(type)
}
}
fun ApiClient.mySubmissions(
token: String,
labId: String,
): List<SubmissionDto> {
val type = object : ParameterizedTypeReference<List<SubmissionDto>>() {}
return call {
client
.get()
.uri("/api/v1/me/submissions?labId={labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.labSubmissions(
token: String,
labId: String,
): List<SubmissionDto> {
val type = object : ParameterizedTypeReference<List<SubmissionDto>>() {}
return call {
client
.get()
.uri("/api/v1/submissions?labId={labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.downloadMySubmission(
token: String,
submissionId: String,
): DownloadDto {
val entity =
call {
client
.get()
.uri("/api/v1/me/submissions/{id}/download", submissionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toEntity(ByteArray::class.java)
}
return downloadOf(entity)
}
fun ApiClient.downloadSubmissionAsAdmin(
token: String,
submissionId: String,
): DownloadDto {
val entity =
call {
client
.get()
.uri("/api/v1/submissions/{id}/download", submissionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toEntity(ByteArray::class.java)
}
return downloadOf(entity)
}
fun ApiClient.labDefenses(
token: String,
labId: String,
): List<DefenseDto> {
val type = object : ParameterizedTypeReference<List<DefenseDto>>() {}
return call {
client
.get()
.uri("/api/v1/defenses?labId={labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.assignDefense(
token: String,
studentId: String,
labId: String,
assignmentMd: String,
): DefenseDto =
call {
client
.post()
.uri("/api/v1/defenses/assign")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("studentId" to studentId, "labId" to labId, "assignmentMd" to assignmentMd))
.retrieve()
.body(DefenseDto::class.java)
}
fun ApiClient.issueDefenseCode(
token: String,
sessionId: String,
): String =
call {
client
.post()
.uri("/api/v1/defenses/{id}/issue-code", sessionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(Map::class.java)
}.get("code").toString()
fun ApiClient.resetDefense(
token: String,
sessionId: String,
) {
call {
client
.post()
.uri("/api/v1/defenses/{id}/reset", sessionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.toBodilessEntity()
}
}
fun ApiClient.myDefenses(token: String): List<DefenseDto> {
val type = object : ParameterizedTypeReference<List<DefenseDto>>() {}
return call {
client
.get()
.uri("/api/v1/me/defenses")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(type)
}
}
fun ApiClient.myDefense(
token: String,
labId: String,
): DefenseDto? =
callOrNull {
client
.get()
.uri("/api/v1/me/defenses/{labId}", labId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.retrieve()
.body(DefenseDto::class.java)
}
fun ApiClient.startDefense(
token: String,
code: String,
): DefenseDto =
call {
client
.post()
.uri("/api/v1/me/defenses/start")
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.body(mapOf("code" to code))
.retrieve()
.body(DefenseDto::class.java)
}
fun ApiClient.finishDefense(
token: String,
sessionId: String,
): DefenseDto =
call {
client
.post()
.uri("/api/v1/me/defenses/{id}/finish", sessionId)
.header(HttpHeaders.AUTHORIZATION, bearer(token))
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.body(DefenseDto::class.java)
}

View File

@@ -10,6 +10,10 @@ import org.springframework.web.bind.annotation.RequestParam
import ru.ysu.lms.web.api.ApiClient import ru.ysu.lms.web.api.ApiClient
import ru.ysu.lms.web.api.ApiException import ru.ysu.lms.web.api.ApiException
import ru.ysu.lms.web.api.AuthResponseDto import ru.ysu.lms.web.api.AuthResponseDto
import ru.ysu.lms.web.api.adminExists
import ru.ysu.lms.web.api.changePassword
import ru.ysu.lms.web.api.login
import ru.ysu.lms.web.api.setup
import ru.ysu.lms.web.session.SessionKeys import ru.ysu.lms.web.session.SessionKeys
import ru.ysu.lms.web.session.SessionUser import ru.ysu.lms.web.session.SessionUser

View File

@@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import ru.ysu.lms.web.api.ApiClient import ru.ysu.lms.web.api.ApiClient
import ru.ysu.lms.web.api.contentAsset
/** /**
* Проксирует картинки статей (public path) через BFF: страницы /learn/... * Проксирует картинки статей (public path) через BFF: страницы /learn/...

View File

@@ -10,6 +10,9 @@ import ru.ysu.lms.web.api.ApiClient
import ru.ysu.lms.web.api.ApiException import ru.ysu.lms.web.api.ApiException
import ru.ysu.lms.web.api.ContentArticleDto import ru.ysu.lms.web.api.ContentArticleDto
import ru.ysu.lms.web.api.ContentTreeNodeDto import ru.ysu.lms.web.api.ContentTreeNodeDto
import ru.ysu.lms.web.api.contentArticle
import ru.ysu.lms.web.api.contentTree
import ru.ysu.lms.web.api.contentTreeList
import ru.ysu.lms.web.session.SessionKeys import ru.ysu.lms.web.session.SessionKeys
import ru.ysu.lms.web.session.SessionUser import ru.ysu.lms.web.session.SessionUser

View File

@@ -18,6 +18,17 @@ import ru.ysu.lms.web.DateUtils
import ru.ysu.lms.web.api.ApiClient import ru.ysu.lms.web.api.ApiClient
import ru.ysu.lms.web.api.ApiException import ru.ysu.lms.web.api.ApiException
import ru.ysu.lms.web.api.SubmissionDto import ru.ysu.lms.web.api.SubmissionDto
import ru.ysu.lms.web.api.downloadMySubmission
import ru.ysu.lms.web.api.finishDefense
import ru.ysu.lms.web.api.labDetail
import ru.ysu.lms.web.api.listLabs
import ru.ysu.lms.web.api.meAssignments
import ru.ysu.lms.web.api.myDefense
import ru.ysu.lms.web.api.myGrades
import ru.ysu.lms.web.api.myGroup
import ru.ysu.lms.web.api.mySubmissions
import ru.ysu.lms.web.api.startDefense
import ru.ysu.lms.web.api.submitLab
import ru.ysu.lms.web.session.SessionKeys import ru.ysu.lms.web.session.SessionKeys
import java.math.BigDecimal import java.math.BigDecimal
import java.net.URLEncoder import java.net.URLEncoder