Publication per student: checkbox in lab column, partial state in eye, cursor on Variant

This commit is contained in:
2026-10-02 00:33:16 +03:00
parent d5fbb804c3
commit fba4e013e5
14 changed files with 329 additions and 117 deletions

View File

@@ -168,3 +168,15 @@ data class SubmissionDto(
val createdAt: String,
val files: List<String>,
)
/** Публикация оценок по (группа, лаба): либо всей группе, либо списку студентов. */
data class PublicationState(
val all: Boolean = false,
val studentIds: List<String> = emptyList(),
)
data class PublicationSetRequest(
val groupId: String,
val labId: String,
val studentIds: List<String> = emptyList(),
)

View File

@@ -2,34 +2,45 @@ package ru.ysu.lms.progress
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import ru.ysu.lms.progress.domain.GradePublication
import ru.ysu.lms.progress.domain.GradePublicationStudent
import ru.ysu.lms.progress.repo.GradePublicationRepository
import ru.ysu.lms.progress.repo.GradePublicationStudentRepository
import java.util.UUID
@Service
class GradePublicationService(
private val repo: GradePublicationRepository,
private val studentRepo: GradePublicationStudentRepository,
) {
@Transactional
fun setPublished(
/** Текущее состояние публикации по (группа, лаба): группа целиком или список студентов. */
@Transactional(readOnly = true)
fun state(
groupId: UUID,
labId: UUID,
published: Boolean,
): PublicationState {
val all = repo.findByLabIdAndGroupId(labId, groupId) != null
val students = studentRepo.findByGroupIdAndLabId(groupId, labId).map { it.studentId.toString() }
return PublicationState(all, students)
}
/** Устанавливает ровно указанный набор студентов; публикация «всей группе» при этом снимается. */
@Transactional
fun setStudents(
groupId: UUID,
labId: UUID,
studentIds: Collection<UUID>,
) {
val existing = repo.findByLabIdAndGroupId(labId, groupId)
if (published && existing == null) repo.save(GradePublication(groupId, labId))
if (!published && existing != null) repo.delete(existing)
repo.findByLabIdAndGroupId(labId, groupId)?.let { repo.delete(it) }
studentRepo.deleteByGroupIdAndLabId(groupId, labId)
studentIds.distinct().forEach { studentRepo.save(GradePublicationStudent(groupId, labId, it)) }
}
@Transactional(readOnly = true)
fun isPublished(
groupId: UUID,
labId: UUID,
): Boolean = repo.findByLabIdAndGroupId(labId, groupId) != null
@Transactional(readOnly = true)
fun publishedGroupIdsForLab(labId: UUID): List<String> = repo.findByLabId(labId).map { it.groupId.toString() }
@Transactional(readOnly = true)
fun publishedLabIdsForGroup(groupId: UUID): List<String> = repo.findByGroupId(groupId).map { it.labId.toString() }
studentId: UUID,
): Boolean =
repo.findByLabIdAndGroupId(labId, groupId) != null ||
studentRepo.existsByGroupIdAndLabIdAndStudentId(groupId, labId, studentId)
}

View File

@@ -85,28 +85,24 @@ class ProgressController(
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
fun listGrades(): List<GradeResponse> = grades.listGrades()
@PostMapping("/grades/publish")
@GetMapping("/grades/publication")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
fun publishGrades(
fun publicationState(
@RequestParam groupId: java.util.UUID,
@RequestParam labId: java.util.UUID,
@RequestParam groupId: java.util.UUID,
@RequestParam published: Boolean,
) {
publications.setPublished(groupId, labId, published)
}
): PublicationState = publications.state(groupId, labId)
@GetMapping("/grades/published")
@PostMapping("/grades/publication")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
fun gradesPublished(
@RequestParam("labId") labId: java.util.UUID,
@RequestParam groupId: java.util.UUID,
): Boolean = publications.isPublished(groupId, labId)
@GetMapping("/grades/publications")
@PreAuthorize("isAuthenticated()")
fun groupPublications(
@RequestParam groupId: java.util.UUID,
): List<String> = publications.publishedLabIdsForGroup(groupId)
fun setPublication(
@RequestBody body: PublicationSetRequest,
) {
publications.setStudents(
java.util.UUID.fromString(body.groupId),
java.util.UUID.fromString(body.labId),
body.studentIds.map { java.util.UUID.fromString(it) },
)
}
@GetMapping("/me/grades")
fun myGrades(
@@ -115,7 +111,8 @@ class ProgressController(
): List<GradeResponse> {
val all = grades.listGrades().filter { it.studentId == jwt.subject }
if (groupId == null) return emptyList()
return all.filter { publications.isPublished(groupId, java.util.UUID.fromString(it.labId)) }
val studentId = java.util.UUID.fromString(jwt.subject)
return all.filter { publications.isPublished(groupId, java.util.UUID.fromString(it.labId), studentId) }
}
@GetMapping("/comment-templates")

View File

@@ -0,0 +1,30 @@
package ru.ysu.lms.progress.domain
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.IdClass
import jakarta.persistence.Table
import java.io.Serializable
import java.time.Instant
import java.util.UUID
@Entity
@Table(name = "grade_publication_students")
@IdClass(GradePublicationStudentKey::class)
class GradePublicationStudent(
@Id @Column(name = "group_id", nullable = false) var groupId: UUID = UUID(0, 0),
@Id @Column(name = "lab_id", nullable = false) var labId: UUID = UUID(0, 0),
@Id @Column(name = "student_id", nullable = false) var studentId: UUID = UUID(0, 0),
@Column(name = "published_at", nullable = false) var publishedAt: Instant = Instant.now(),
)
data class GradePublicationStudentKey(
val groupId: UUID = UUID(0, 0),
val labId: UUID = UUID(0, 0),
val studentId: UUID = UUID(0, 0),
) : Serializable {
companion object {
private const val serialVersionUID: Long = 1L
}
}

View File

@@ -10,8 +10,4 @@ interface GradePublicationRepository : JpaRepository<GradePublication, GradePubl
labId: UUID,
groupId: UUID,
): GradePublication?
fun findByLabId(labId: UUID): List<GradePublication>
fun findByGroupId(groupId: UUID): List<GradePublication>
}

View File

@@ -0,0 +1,24 @@
package ru.ysu.lms.progress.repo
import org.springframework.data.jpa.repository.JpaRepository
import ru.ysu.lms.progress.domain.GradePublicationStudent
import ru.ysu.lms.progress.domain.GradePublicationStudentKey
import java.util.UUID
interface GradePublicationStudentRepository : JpaRepository<GradePublicationStudent, GradePublicationStudentKey> {
fun findByGroupIdAndLabId(
groupId: UUID,
labId: UUID,
): List<GradePublicationStudent>
fun deleteByGroupIdAndLabId(
groupId: UUID,
labId: UUID,
)
fun existsByGroupIdAndLabIdAndStudentId(
groupId: UUID,
labId: UUID,
studentId: UUID,
): Boolean
}

View File

@@ -0,0 +1,10 @@
-- Per-student grade publication: which students see the grade and comments for a lab.
-- The older group-wide rows in grade_publications keep meaning "visible to everyone in the group".
CREATE TABLE grade_publication_students (
group_id uuid NOT NULL,
lab_id uuid NOT NULL,
student_id uuid NOT NULL,
published_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (group_id, lab_id, student_id)
);