Skip to content

Extracted services and controllers into separate files #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/main/kotlin/ch/uzh/ifi/access/config/CacheConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ class CacheConfig {
"PointsService.getMaxPoints",
"PointsService.calculateAvgTaskPoints",
"PointsService.calculateAssignmentMaxPoints",
"EvaluationService.getEvaluation",
"EvaluationService.getEvaluationSummary",
"CourseService.getStudents",
)
}
}
Expand Down
123 changes: 3 additions & 120 deletions src/main/kotlin/ch/uzh/ifi/access/controller/CourseController.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package ch.uzh.ifi.access.controller

import ch.uzh.ifi.access.model.constants.Role
import ch.uzh.ifi.access.model.constants.TaskStatus
import ch.uzh.ifi.access.model.dto.*
import ch.uzh.ifi.access.projections.*
import ch.uzh.ifi.access.service.CourseService
import ch.uzh.ifi.access.service.EmitterService
import ch.uzh.ifi.access.service.RoleService
import ch.uzh.ifi.access.service.SubmissionService
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
Expand Down Expand Up @@ -75,6 +75,7 @@ class CourseController(
private val courseService: CourseService,
private val roleService: RoleService,
private val emitterService: EmitterService,
private val submissionService: SubmissionService
) {
private val logger = KotlinLogging.logger {}

Expand All @@ -90,7 +91,6 @@ class CourseController(
}

@GetMapping("/{course}")
//@PreAuthorize("hasRole(#course) or hasRole(#course+'-supervisor')")
@PreAuthorize("hasRole(#course)")
fun getCourseWorkspace(@PathVariable course: String?): CourseWorkspace {
return courseService.getCourseWorkspaceBySlug(course!!)
Expand Down Expand Up @@ -127,125 +127,8 @@ class CourseController(
) {
val userId = roleService.getUserId(authentication.name)
submission.userId = userId
courseService.createSubmission(course, assignment, task!!, submission)
}

@PostMapping("/{course}/examples/{example}/submit")
@PreAuthorize("hasRole(#course) and (#submission.restricted or hasRole(#course + '-assistant'))")
fun evaluateExampleSubmission(
@PathVariable course: String,
@PathVariable example: String,
@RequestBody submission: SubmissionDTO,
authentication: Authentication
) {
submission.userId = authentication.name
// Is there a better way than passing null to assignmentSlug?
courseService.createSubmission(course, null, example, submission)
}


@GetMapping("/{course}/examples/{example}/users/{user}")
@PreAuthorize("hasRole(#course+'-assistant') or (#user == authentication.name)")
fun getExample(
@PathVariable course: String,
@PathVariable example: String,
@PathVariable user: String
): TaskWorkspace {
return courseService.getExample(course, example, user)
}

@GetMapping("/{course}/examples/{example}/information")
@PreAuthorize("hasRole(#course+'-assistant')")
fun getGeneralInformation(
@PathVariable course: String,
@PathVariable example: String,
authentication: Authentication
): ExampleInformationDTO {
val participantsOnline = roleService.getOnlineCount(course)
val totalParticipants = courseService.getCourseBySlug(course).participantCount
val numberOfStudentsWhoSubmitted = courseService.countStudentsWhoSubmittedExample(course, example)
return ExampleInformationDTO(
participantsOnline,
totalParticipants,
numberOfStudentsWhoSubmitted,
/*TODO: Replace following mock code with actual test pass rate and test names*/
passRatePerTestCase = mapOf(
"First Test" to 0.85,
"Second Test" to 0.5,
"Some Other Test" to 0.7,
"One More Test" to 0.1,
"Edge Case" to 0.2
)
)
}

// TODO: Change this to returning TaskOverview, as we don't need more information. However, when changing it, an error occurs in the courses/{course} endpoint.
@GetMapping("/{course}/examples")
@PreAuthorize("hasRole(#course)")
fun getExamples(
@PathVariable course: String,
authentication: Authentication
): List<TaskWorkspace> {
return courseService.getExamples(course)
}

// Invoked by the teacher when publishing an example to inform the students
@PostMapping("/{course}/examples/{example}/publish")
@PreAuthorize("hasRole(#course+'-supervisor')")
fun publishExample(
@PathVariable course: String,
@PathVariable example: String,
@RequestBody body: ExampleDurationDTO,
) {
val activeExample = courseService.getExamples(course).firstOrNull {
it.status == TaskStatus.Interactive
}

if (activeExample != null) {
throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"An interactive example already exists."
)
}

val updatedExample = courseService.publishExampleBySlug(course, example, body.duration)

emitterService.sendMessage(course, "redirect", "/courses/$course/examples/$example")
emitterService.sendMessage(course, "timer-update", "${updatedExample.start}/${updatedExample.end}")
}

// Invoked by the teacher when want to extend the time of an active example by a certain amount of seconds
@PutMapping("/{course}/examples/{example}/extend")
@PreAuthorize("hasRole(#course+'-supervisor')")
fun extendExampleDeadline(
@PathVariable course: String,
@PathVariable example: String,
@RequestBody body: ExampleDurationDTO,
) {
val updatedExample = courseService.extendExampleDeadlineBySlug(course, example, body.duration)

emitterService.sendMessage(
course,
"message",
"Submission time extended by the lecturer by ${body.duration} seconds."
)
emitterService.sendMessage(course, "timer-update", "${updatedExample.start}/${updatedExample.end}")
}

// Invoked by the teacher when want to terminate the active example
@PutMapping("/{course}/examples/{example}/terminate")
@PreAuthorize("hasRole(#course+'-supervisor')")
fun terminateExample(
@PathVariable course: String,
@PathVariable example: String
) {
val updatedExample = courseService.terminateExampleBySlug(course, example)
emitterService.sendMessage(
course,
"message",
"The example has been terminated by the lecturer."
)
emitterService.sendMessage(course, "timer-update", "${updatedExample.start}/${updatedExample.end}")
submissionService.createTaskSubmission(course, assignment, task!!, submission)
}

// A text event endpoint to publish events to clients
Expand Down
141 changes: 141 additions & 0 deletions src/main/kotlin/ch/uzh/ifi/access/controller/ExampleController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package ch.uzh.ifi.access.controller

import ch.uzh.ifi.access.model.constants.TaskStatus
import ch.uzh.ifi.access.model.dto.ExampleDurationDTO
import ch.uzh.ifi.access.model.dto.ExampleInformationDTO
import ch.uzh.ifi.access.model.dto.SubmissionDTO
import ch.uzh.ifi.access.projections.TaskWorkspace
import ch.uzh.ifi.access.service.EmitterService
import ch.uzh.ifi.access.service.ExampleService
import ch.uzh.ifi.access.service.RoleService
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.http.HttpStatus
import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.Authentication
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException


@RestController
@RequestMapping("/courses/{course}/examples")
@EnableAsync
class ExampleController(
private val exampleService: ExampleService,
private val roleService: RoleService,
private val emitterService: EmitterService,
) {
private val logger = KotlinLogging.logger {}

// TODO: Change this to returning TaskOverview, as we don't need more information. However, when changing it, an error occurs in the courses/{course} endpoint.
@GetMapping("")
@PreAuthorize("hasRole(#course)")
fun getExamples(
@PathVariable course: String,
authentication: Authentication
): List<TaskWorkspace> {
return exampleService.getExamples(course)
}

@GetMapping("/{example}/information")
@PreAuthorize("hasRole(#course+'-assistant')")
fun getGeneralInformation(
@PathVariable course: String,
@PathVariable example: String,
authentication: Authentication
): ExampleInformationDTO {
val participantsOnline = roleService.getOnlineCount(course)
val totalParticipants = exampleService.getCourseBySlug(course).participantCount
val numberOfStudentsWhoSubmitted = exampleService.countStudentsWhoSubmittedExample(course, example)
val passRatePerTestCase = exampleService.getExamplePassRatePerTestCase(course, example)

return ExampleInformationDTO(
participantsOnline,
totalParticipants,
numberOfStudentsWhoSubmitted,
passRatePerTestCase
)
}

@PostMapping("/{example}/submit")
@PreAuthorize("hasRole(#course) and (#submission.restricted or hasRole(#course + '-assistant'))")
fun evaluateExampleSubmission(
@PathVariable course: String,
@PathVariable example: String,
@RequestBody submission: SubmissionDTO,
authentication: Authentication
) {
submission.userId = authentication.name
// Is there a better way than passing null to assignmentSlug?
exampleService.createExampleSubmission(course, example, submission)
}

@GetMapping("/{example}/users/{user}")
@PreAuthorize("hasRole(#course+'-assistant') or (#user == authentication.name)")
fun getExample(
@PathVariable course: String,
@PathVariable example: String,
@PathVariable user: String
): TaskWorkspace {
return exampleService.getExample(course, example, user)
}

// Invoked by the teacher when publishing an example to inform the students
@PostMapping("/{example}/publish")
@PreAuthorize("hasRole(#course+'-supervisor')")
fun publishExample(
@PathVariable course: String,
@PathVariable example: String,
@RequestBody body: ExampleDurationDTO,
) {
val activeExample = exampleService.getExamples(course).firstOrNull {
it.status == TaskStatus.Interactive
}

if (activeExample != null) {
throw ResponseStatusException(
HttpStatus.NOT_FOUND,
"An interactive example already exists."
)
}

val updatedExample = exampleService.publishExampleBySlug(course, example, body.duration)

emitterService.sendMessage(course, "redirect", "/courses/$course/examples/$example")
emitterService.sendMessage(course, "timer-update", "${updatedExample.start}/${updatedExample.end}")
}

// Invoked by the teacher when want to extend the time of an active example by a certain amount of seconds
@PutMapping("/{example}/extend")
@PreAuthorize("hasRole(#course+'-supervisor')")
fun extendExampleDeadline(
@PathVariable course: String,
@PathVariable example: String,
@RequestBody body: ExampleDurationDTO,
) {
val updatedExample = exampleService.extendExampleDeadlineBySlug(course, example, body.duration)

emitterService.sendMessage(
course,
"message",
"Submission time extended by the lecturer by ${body.duration} seconds."
)
emitterService.sendMessage(course, "timer-update", "${updatedExample.start}/${updatedExample.end}")
}

// Invoked by the teacher when want to terminate the active example
@PutMapping("/{example}/terminate")
@PreAuthorize("hasRole(#course+'-supervisor')")
fun terminateExample(
@PathVariable course: String,
@PathVariable example: String
) {
val updatedExample = exampleService.terminateExampleBySlug(course, example)
emitterService.sendMessage(
course,
"message",
"The example has been terminated by the lecturer."
)
emitterService.sendMessage(course, "timer-update", "${updatedExample.start}/${updatedExample.end}")
}
}
4 changes: 4 additions & 0 deletions src/main/kotlin/ch/uzh/ifi/access/model/Submission.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class Submission {
@OneToMany(mappedBy = "submission", cascade = [CascadeType.ALL])
var persistentResultFiles: MutableList<ResultFile> = ArrayList()

@Column(name = "test_scores")
@JdbcTypeCode(SqlTypes.JSON)
var testScores: List<Double> = ArrayList()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here:)


@Column(name = "embedding")
@JdbcTypeCode(SqlTypes.JSON)
var embedding: List<Double> = ArrayList()
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/ch/uzh/ifi/access/model/dao/Results.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import lombok.Data
class Results(
var points: Double? = null,
var hints: MutableList<String> = mutableListOf(),
var tests: MutableList<String> = mutableListOf()
var tests: MutableList<String> = mutableListOf(),
var testScores: MutableList<Double> = mutableListOf()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needed, as discussed.

)
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface AssignmentOverview {
val isPastDue: Boolean
val isActive: Boolean

@get:Value("#{@pointsService.calculateAssignmentMaxPoints(target.tasks}")
@get:Value("#{@pointsService.calculateAssignmentMaxPoints(target.tasks)}")
val maxPoints: Double?

@get:Value("#{@courseService.calculateAssignmentPoints(target.tasks)}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ interface CourseWorkspace : CourseOverview {
@get:Value("#{@courseService.getAssignments(target.slug)}")
val assignments: List<AssignmentWorkspace?>?

@get:Value("#{@courseService.getExamples(target.slug)}")
@get:Value("#{@exampleService.getExamples(target.slug)}")
val examples: List<TaskWorkspace?>?
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface TaskWorkspace : TaskOverview {
@get:Value("#{@courseService.getTaskFiles(target.id)}")
val files: List<TaskFile?>?

@get:Value("#{@courseService.getSubmissions(target.id, target.userId)}")
@get:Value("#{@submissionService.getSubmissions(target.id, target.userId)}")
val submissions: List<Submission?>?

@get:Value("#{@courseService.getNextAttemptAt(target.id, target.userId)}")
Expand Down
Loading