From 59ab194c39639b5801a42e7230767f3ce69d37e1 Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Thu, 26 Feb 2026 12:19:35 +0200 Subject: [PATCH 1/7] EZ-1615 update Spring Boot to 4.0.3, exposed to 1.1.0 java version to 25 and other dependencies. Migrate security conf, JSON mapping etc --- build.gradle.kts | 4 +- core/build.gradle | 100 +++++++------- core/gradle.properties | 1 - .../kotlin/core/aas/AutoGradeScheduler.kt | 42 +++--- .../core/aas/AutoGradeStatusObserver.kt | 2 +- .../main/kotlin/core/aas/FunctionScheduler.kt | 11 +- .../main/kotlin/core/aas/executor_utils.kt | 122 +++++++++--------- .../kotlin/core/aas/insert_auto_exercise.kt | 11 +- .../kotlin/core/aas/select_auto_exercise.kt | 7 +- .../kotlin/core/aas/service/CreateExecutor.kt | 20 +-- .../kotlin/core/aas/service/CreateImage.kt | 18 +-- .../kotlin/core/aas/service/DeleteExecutor.kt | 6 +- .../kotlin/core/aas/service/ReadExecutor.kt | 19 +-- .../kotlin/core/aas/service/ReadImages.kt | 8 +- .../kotlin/core/aas/service/UpdateExecutor.kt | 33 ++--- .../src/main/kotlin/core/conf/DatabaseConf.kt | 8 +- core/src/main/kotlin/core/conf/SysConf.kt | 10 +- .../core/conf/security/DummyZeroAuthFilter.kt | 6 +- .../conf/security/EasyUserAuthProvider.kt | 16 +-- .../core/conf/security/PreAuthHeaderFilter.kt | 8 +- .../kotlin/core/conf/security/SecurityConf.kt | 79 ++++++------ core/src/main/kotlin/core/db/Tables.kt | 15 ++- .../main/kotlin/core/db/exposed_extensions.kt | 53 +------- .../core/ems/cron/delete_inactive_users.kt | 22 ++-- .../main/kotlin/core/ems/cron/norm_ce_idx.kt | 4 +- .../core/ems/service/ExportPersonalData.kt | 54 ++++---- .../core/ems/service/access_control/course.kt | 8 +- .../access_control/library_exercise.kt | 9 +- .../main/kotlin/core/ems/service/accounts.kt | 21 ++- .../kotlin/core/ems/service/adoc_preview.kt | 6 +- .../kotlin/core/ems/service/adoc_service.kt | 30 ++--- .../ems/service/article/AdminCreateArticle.kt | 27 ++-- .../article/AdminCreateArticleAlias.kt | 19 +-- .../article/AdminDeleteArticleAlias.kt | 10 +- .../article/AdminReadArticleAliases.kt | 29 +++-- .../ems/service/article/AdminUpdateArticle.kt | 30 +++-- .../ems/service/article/ReadArticleDetails.kt | 46 +++---- .../main/kotlin/core/ems/service/articles.kt | 45 ++++--- .../kotlin/core/ems/service/assessments.kt | 30 +++-- .../ems/service/cache/AdminEvictAllCache.kt | 2 +- .../core/ems/service/cache/CachingService.kt | 11 +- .../kotlin/core/ems/service/ce_idx_norm.kt | 9 +- .../service/code/TeacherCheckSimilarity.kt | 54 ++++---- .../ems/service/course/AddTeachersToCourse.kt | 22 ++-- .../core/ems/service/course/ArchiveCourse.kt | 13 +- .../core/ems/service/course/CreateCourse.kt | 20 +-- .../ExportPersonalCourseLatestSubmissions.kt | 44 ++++--- .../ems/service/course/ReadBasicCourseInfo.kt | 16 ++- .../course/ReadParticipantsOnCourse.kt | 40 +++--- .../ems/service/course/ReadStudentCourses.kt | 34 +++-- .../ems/service/course/ReadTeacherCourses.kt | 75 +++++++---- .../course/RemoveStudentsFromCourse.kt | 24 ++-- .../course/RemoveTeachersFromCourse.kt | 54 ++++---- .../ems/service/course/SendCourseInvites.kt | 24 ++-- .../service/course/SetStudentLastAccess.kt | 9 +- .../service/course/SetTeacherLastAccess.kt | 9 +- .../ems/service/course/TeacherReadGrades.kt | 2 +- .../core/ems/service/course/UpdateCourse.kt | 21 +-- .../course/group/AddStudentsToCourseGroup.kt | 29 +++-- .../service/course/group/CreateCourseGroup.kt | 18 +-- .../service/course/group/DeleteCourseGroup.kt | 19 +-- .../service/course/group/ReadCourseGroups.kt | 11 +- .../group/RemoveStudentsFromCourseGroup.kt | 29 +++-- .../course/invite/DeleteCourseInvite.kt | 8 +- .../course/invite/GenerateCourseInvite.kt | 28 ++-- .../course/invite/GetCourseInfoByInvite.kt | 14 +- .../course/invite/JoinCourseByInvite.kt | 16 +-- .../course/invite/ReadCourseInviteDetails.kt | 20 +-- .../GetMoodleLinkedCourseInfoByInvite.kt | 12 +- .../moodle/JoinMoodleLinkedCourseByInvite.kt | 19 ++- .../invite/moodle/SendMoodleCourseInvites.kt | 13 +- .../kotlin/core/ems/service/course_groups.kt | 7 +- .../main/kotlin/core/ems/service/courses.kt | 104 +++++++-------- core/src/main/kotlin/core/ems/service/dirs.kt | 16 ++- .../service/exercise/AddExerciseToCourse.kt | 50 +++---- .../exercise/AnonymousReadExerciseDetails.kt | 17 ++- .../ems/service/exercise/AnonymousSubmit.kt | 22 ++-- .../core/ems/service/exercise/CompileTSL.kt | 32 ++--- .../ems/service/exercise/CreateExercise.kt | 58 +++++---- .../ems/service/exercise/DeleteExercise.kt | 16 +-- .../exercise/ReadAllSubmissionsByStudent.kt | 24 ++-- .../exercise/ReadAnonymousSubmissions.kt | 25 ++-- .../core/ems/service/exercise/ReadExercise.kt | 84 ++++++------ .../exercise/ReadLatestTeacherSubmissions.kt | 32 ++--- .../service/exercise/ReadSubmissionDetails.kt | 28 ++-- .../exercise/RemoveExerciseFromCourse.kt | 9 +- .../ems/service/exercise/SetSubmissionSeen.kt | 17 ++- .../exercise/StudentAwaitLatestSubmission.kt | 10 +- ...StudentReadAllExerciseTeacherActivities.kt | 2 +- .../ems/service/exercise/StudentReadDraft.kt | 19 +-- .../exercise/StudentReadExerciseDetails.kt | 33 ++--- .../service/exercise/StudentReadExercises.kt | 30 ++--- .../exercise/StudentReadSubmissions.kt | 39 +++--- .../ems/service/exercise/StudentSubmit.kt | 10 +- .../service/exercise/StudentSubmitDraft.kt | 24 ++-- .../ems/service/exercise/TeacherAutoassess.kt | 27 ++-- ...eacherDownloadCourseExerciseSubmissions.kt | 20 +-- .../exercise/TeacherDownloadSubmissions.kt | 29 +++-- .../service/exercise/TeacherEditFeedback.kt | 43 +++--- .../service/exercise/TeacherPostFeedback.kt | 40 +++--- .../ems/service/exercise/TeacherPostGrade.kt | 26 ++-- .../exercise/TeacherReadExerciseDetails.kt | 97 +++++++------- .../service/exercise/TeacherReadExercises.kt | 5 +- .../TeacherReadSubmissionSummaries.kt | 2 +- .../exercise/TeacherReorderExercise.kt | 14 +- .../exercise/TeacherRetryAutoassess.kt | 8 +- .../exercise/TeacherSubmitBehalfStudent.kt | 12 +- .../TecherReadStudentAllExerciseActivities.kt | 2 +- .../service/exercise/UpdateCourseExercise.kt | 49 +++---- .../ems/service/exercise/UpdateExercise.kt | 53 ++++---- .../service/exercise/UpdateExercisePatch.kt | 13 +- .../ems/service/exercise/dir/CreateDir.kt | 20 +-- .../ems/service/exercise/dir/DeleteDir.kt | 10 +- .../ems/service/exercise/dir/PutDirAccess.kt | 12 +- .../core/ems/service/exercise/dir/ReadDir.kt | 64 ++++----- .../service/exercise/dir/ReadDirAccesses.kt | 50 +++---- .../service/exercise/dir/ReadDirParents.kt | 12 +- .../ems/service/exercise/dir/UpdateDir.kt | 17 +-- .../exceptions/PutCourseExerciseExceptions.kt | 52 ++++---- .../RemoveCourseExerciseExceptions.kt | 18 +-- .../ems/service/file/AdminReadFileMetadata.kt | 28 ++-- .../ems/service/file/CommonReadStoredFile.kt | 8 +- .../ems/service/file/TeacherUploadFile.kt | 18 +-- .../core/ems/service/group/CreateGroup.kt | 22 ++-- .../main/kotlin/core/ems/service/groups.kt | 17 ++- .../AdminCreateManagementNotification.kt | 14 +- .../AdminDeleteManagementNotification.kt | 10 +- .../AdminReadManagementNotification.kt | 33 ++--- .../service/management/AdminReadSystemConf.kt | 23 ++-- .../AdminUpdateManagementNotification.kt | 19 +-- .../CommonReadManagementNotification.kt | 14 +- .../service/management/report_client_log.kt | 20 +-- .../core/ems/service/markdown_preview.kt | 6 +- .../ems/service/moodle/LinkCourseMoodle.kt | 25 ++-- .../service/moodle/ReadCourseMoodleProps.kt | 19 +-- .../ems/service/moodle/SyncMoodleAllGrades.kt | 4 +- .../service/moodle/SyncMoodleAllStudents.kt | 4 +- .../core/ems/service/moodle/moodle_grades.kt | 31 +++-- .../ems/service/moodle/moodle_students.kt | 36 +++--- .../core/ems/service/moodle/moodle_utils.kt | 8 +- .../ems/service/register/account_checkin.kt | 33 ++--- .../kotlin/core/ems/service/request_logger.kt | 14 +- .../kotlin/core/ems/service/request_util.kt | 2 +- .../snippet/TeacherCreateFeedbackSnippet.kt | 24 ++-- .../snippet/TeacherReadFeedbackSnippets.kt | 48 +++---- .../snippet/TeacherUpdateFeedbackSnippet.kt | 18 +-- .../kotlin/core/ems/service/statistics.kt | 10 +- .../kotlin/core/ems/service/submissions.kt | 21 +-- .../kotlin/core/exception/error_response.kt | 8 +- .../core/exception/exception_handlers.kt | 52 ++++---- .../src/main/kotlin/core/util/DBBackedLock.kt | 11 +- .../kotlin/core/util/DateTimeSerializers.kt | 23 ++-- .../main/kotlin/core/util/SendMailService.kt | 6 +- .../test/kotlin/core/conf/TestDatabaseConf.kt | 8 +- ...lectAllCourseExercisesLatestSubmissions.kt | 16 +-- ...lectAllCourseExercisesLatestSubmissions.kt | 16 +-- gradle/wrapper/gradle-wrapper.properties | 2 +- tsl/build.gradle.kts | 2 +- 158 files changed, 1961 insertions(+), 1823 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2eafa87c..b518804c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,6 @@ plugins { kotlin("multiplatform") version "1.8.0" apply false kotlin("js") version "1.7.20" apply false - kotlin("jvm") version "1.5.31" apply false - kotlin("plugin.serialization") version "1.5.31" apply false + kotlin("jvm") version "2.3.10" apply false + kotlin("plugin.serialization") version "2.3.10" apply false } diff --git a/core/build.gradle b/core/build.gradle index 6376e08e..ada6ea02 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,45 +1,32 @@ -Properties liquibaseProperties = new Properties() -liquibaseProperties.load(new FileInputStream( - file("src/main/resources/db/database.properties").exists() ? file("src/main/resources/db/database.properties") : file("src/main/resources/db/database.properties.sample"))) - - buildscript { - ext { - springBootVersion = '2.7.17' - liquibaseVersion = '2.0.4' - } repositories { mavenCentral() } dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") - classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") - classpath("org.liquibase:liquibase-gradle-plugin:${liquibaseVersion}") + classpath 'org.liquibase:liquibase-core:4.31.1' } } -apply plugin: 'kotlin' -apply plugin: 'kotlin-spring' -apply plugin: 'eclipse' -apply plugin: 'org.springframework.boot' -apply plugin: 'io.spring.dependency-management' -apply plugin: 'org.liquibase.gradle' - +plugins { + id 'org.jetbrains.kotlin.jvm' version '2.3.10' + id 'org.jetbrains.kotlin.plugin.spring' version '2.3.10' + id 'org.springframework.boot' version '4.0.3' + id 'io.spring.dependency-management' version '1.1.7' + id 'org.liquibase.gradle' version '3.1.0' +} group = 'ee.urgas' version = '1' -sourceCompatibility = 11 -compileKotlin { - kotlinOptions { - freeCompilerArgs = ["-Xjsr305=strict"] - jvmTarget = "11" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) } } -compileTestKotlin { - kotlinOptions { - freeCompilerArgs = ["-Xjsr305=strict"] - jvmTarget = "11" + +kotlin { + compilerOptions { + freeCompilerArgs.addAll '-Xjsr305=strict', '-Xannotation-default-target=param-property' } } @@ -54,43 +41,46 @@ test { dependencies { // Spring Boot - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webmvc' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-cache' + // restTemplate: + implementation 'org.springframework.boot:spring-boot-starter-restclient' implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-validation' // Cache - implementation 'com.github.ben-manes.caffeine:caffeine:3.0.1' + implementation 'com.github.ben-manes.caffeine:caffeine' // Logging - implementation 'io.github.microutils:kotlin-logging:1.7.10' + implementation 'io.github.oshai:kotlin-logging:8.0.01' // Database - implementation 'org.jetbrains.exposed:exposed-core:0.48.0' - implementation 'org.jetbrains.exposed:exposed-java-time:0.48.0' - implementation 'org.jetbrains.exposed:exposed-dao:0.48.0' - implementation 'org.jetbrains.exposed:exposed-jodatime:0.48.0' - implementation 'org.jetbrains.exposed:exposed-jdbc:0.48.0' - implementation 'org.jetbrains.exposed:exposed-json:0.48.0' - - implementation 'com.zaxxer:HikariCP:3.4.5' - implementation 'org.postgresql:postgresql:42.6.0' - implementation 'org.liquibase:liquibase-core:3.6.3' - liquibaseRuntime 'org.liquibase:liquibase-core:3.6.3' - liquibaseRuntime 'org.postgresql:postgresql:42.6.0' + implementation("org.jetbrains.exposed:exposed-core:1.1.0") + implementation("org.jetbrains.exposed:exposed-jdbc:1.1.0") + implementation("org.jetbrains.exposed:exposed-dao:1.1.0") + implementation("org.jetbrains.exposed:exposed-jodatime:1.1.0") + implementation("org.jetbrains.exposed:exposed-json:1.1.0") + + + implementation 'org.postgresql:postgresql' + implementation 'org.liquibase:liquibase-core:4.31.1' + liquibaseRuntime 'org.liquibase:liquibase-core:4.31.1' + liquibaseRuntime 'org.postgresql:postgresql' + liquibaseRuntime 'info.picocli:picocli:4.7.6' + liquibaseRuntime 'org.apache.commons:commons-lang3:3.20.0' + liquibaseRuntime files('src/main/resources') // Kotlin support - implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + implementation 'tools.jackson.module:jackson-module-kotlin' implementation 'org.jetbrains.kotlin:kotlin-reflect' // Testing testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.1' - testImplementation 'org.liquibase:liquibase-core:3.7.0' // Asciidoc - implementation 'org.asciidoctor:asciidoctorj:2.4.3' + implementation 'org.asciidoctor:asciidoctorj:3.0.1' // Markdown (CommonMark with GFM extensions) implementation 'org.commonmark:commonmark:0.24.0' @@ -98,10 +88,10 @@ dependencies { implementation 'org.commonmark:commonmark-ext-gfm-strikethrough:0.24.0' // Jsoup for Asciidoctor HTML output customisation - implementation 'org.jsoup:jsoup:1.13.1' + implementation 'org.jsoup:jsoup:1.22.1' // StoredFile type detection - implementation 'org.apache.tika:tika-core:1.25' + implementation 'org.apache.tika:tika-core:3.2.3' // Source code similarity implementation 'me.xdrop:fuzzywuzzy:1.2.0' @@ -113,11 +103,17 @@ dependencies { implementation project(":tsl") } +Properties liquibaseProperties = new Properties() +liquibaseProperties.load(new FileInputStream( + file("src/main/resources/db/database.properties").exists() + ? file("src/main/resources/db/database.properties") + : file("src/main/resources/db/database.properties.sample"))) + liquibase { activities { main { - changeLogFile 'src/main/resources/db/changelog.xml' + changelogFile 'db/changelog.xml' url liquibaseProperties.url username liquibaseProperties.username password liquibaseProperties.password diff --git a/core/gradle.properties b/core/gradle.properties index 9ae6f9d9..190e4456 100644 --- a/core/gradle.properties +++ b/core/gradle.properties @@ -1,3 +1,2 @@ -kotlinVersion=1.8.22 org.gradle.warning.mode=all \ No newline at end of file diff --git a/core/src/main/kotlin/core/aas/AutoGradeScheduler.kt b/core/src/main/kotlin/core/aas/AutoGradeScheduler.kt index bb5d223d..5b9537d4 100644 --- a/core/src/main/kotlin/core/aas/AutoGradeScheduler.kt +++ b/core/src/main/kotlin/core/aas/AutoGradeScheduler.kt @@ -2,11 +2,11 @@ package core.aas import core.db.* import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.context.ApplicationListener import org.springframework.context.event.ContextRefreshedEvent import org.springframework.scheduling.annotation.Scheduled @@ -60,11 +60,7 @@ class AutoGradeScheduler : ApplicationListener { } - suspend fun submitAndAwait( - autoExerciseId: Long, - submission: String, - priority: PriorityLevel - ): AutoAssessment { + suspend fun submitAndAwait(autoExerciseId: Long, submission: String, priority: PriorityLevel): AutoAssessment { val autoExercise = getAutoExerciseDetails(autoExerciseId) val request = autoExercise.mapToExecutorRequest(submission) @@ -83,25 +79,23 @@ class AutoGradeScheduler : ApplicationListener { @Synchronized - fun deleteExecutor(executorId: Long, force: Boolean) { - return transaction { + fun deleteExecutor(executorId: Long, force: Boolean): Unit = transaction { - // The load of the all the (priority) queues in the executor map of this executor. - val load = executors[executorId]?.values?.sumOf { it.size() } ?: 0 + // The load of the all the (priority) queues in the executor map of this executor. + val load = executors[executorId]?.values?.sumOf { it.size() } ?: 0 - // If not force removal and there is any load, deny removal - if (!force && load > 0) { - throw InvalidRequestException("Executor load != 0 (is $load). Set 'force'=true for forced removal.") - } + // If not force removal and there is any load, deny removal + if (!force && load > 0) { + throw InvalidRequestException("Executor load != 0 (is $load). Set 'force'=true for forced removal.") + } - ExecutorContainerImage.deleteWhere { ExecutorContainerImage.executor eq executorId } - Executor.deleteWhere { Executor.id eq executorId } + ExecutorContainerImage.deleteWhere { ExecutorContainerImage.executor eq executorId } + Executor.deleteWhere { Executor.id eq executorId } - executors[executorId]?.forEach { it.value.killScheduler() } - executors.remove(executorId) + executors[executorId]?.forEach { it.value.killScheduler() } + executors.remove(executorId) - log.info { "Executor '$executorId' deleted" } - } + log.info { "Executor '$executorId' deleted" } } @Synchronized diff --git a/core/src/main/kotlin/core/aas/AutoGradeStatusObserver.kt b/core/src/main/kotlin/core/aas/AutoGradeStatusObserver.kt index ccc15449..f6ba3764 100644 --- a/core/src/main/kotlin/core/aas/AutoGradeStatusObserver.kt +++ b/core/src/main/kotlin/core/aas/AutoGradeStatusObserver.kt @@ -1,7 +1,7 @@ package core.aas import kotlinx.coroutines.Job -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import java.util.concurrent.ConcurrentHashMap diff --git a/core/src/main/kotlin/core/aas/FunctionScheduler.kt b/core/src/main/kotlin/core/aas/FunctionScheduler.kt index 8a3fa004..ba33ee50 100644 --- a/core/src/main/kotlin/core/aas/FunctionScheduler.kt +++ b/core/src/main/kotlin/core/aas/FunctionScheduler.kt @@ -1,19 +1,15 @@ package core.aas -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.onClosed import kotlinx.coroutines.channels.onFailure -import mu.KotlinLogging import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicBoolean import kotlin.reflect.KFunction -private val log = KotlinLogging.logger {} /** * @@ -29,6 +25,8 @@ private val log = KotlinLogging.logger {} * */ class FunctionScheduler(private val function: KFunction) { + private val log = KotlinLogging.logger {} + private val jobs = ConcurrentLinkedQueue>() private val closed = AtomicBoolean(false) @@ -62,6 +60,7 @@ class FunctionScheduler(private val function: KFunction) { * @param arguments to be passed to [function] * @return [function] output */ + @OptIn(DelicateCoroutinesApi::class) suspend fun scheduleAndAwait(vararg arguments: Any?): T { val job = synchronized(this) { if (closed.get()) throw ExecutorException("Scheduler is killed") diff --git a/core/src/main/kotlin/core/aas/executor_utils.kt b/core/src/main/kotlin/core/aas/executor_utils.kt index 6e85a4d7..cba7b3b6 100644 --- a/core/src/main/kotlin/core/aas/executor_utils.kt +++ b/core/src/main/kotlin/core/aas/executor_utils.kt @@ -3,10 +3,12 @@ package core.aas import com.fasterxml.jackson.annotation.JsonProperty import core.conf.SysConf import core.db.* -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.springframework.boot.web.client.RestTemplateBuilder +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.springframework.boot.restclient.RestTemplateBuilder import org.springframework.web.client.RestTemplate import java.time.Duration @@ -18,22 +20,22 @@ private val log = KotlinLogging.logger {} data class ExecutorResponse( - @JsonProperty("grade") val grade: Int, - @JsonProperty("feedback") val feedback: String + @get:JsonProperty("grade") val grade: Int, + @get:JsonProperty("feedback") val feedback: String ) data class ExecutorRequest( - @JsonProperty("submission") val submission: String, - @JsonProperty("grading_script") val gradingScript: String, - @JsonProperty("assets") val assets: List, - @JsonProperty("image_name") val imageName: String, - @JsonProperty("max_time_sec") val maxTime: Int, - @JsonProperty("max_mem_mb") val maxMem: Int + @param:JsonProperty("submission") val submission: String, + @param:JsonProperty("grading_script") val gradingScript: String, + @param:JsonProperty("assets") val assets: List, + @param:JsonProperty("image_name") val imageName: String, + @param:JsonProperty("max_time_sec") val maxTime: Int, + @param:JsonProperty("max_mem_mb") val maxMem: Int ) data class ExecutorRequestAsset( - @JsonProperty("file_name") val fileName: String, - @JsonProperty("file_content") val fileContent: String + @param:JsonProperty("file_name") val fileName: String, + @param:JsonProperty("file_content") val fileContent: String ) data class CapableExecutor( @@ -57,13 +59,11 @@ internal data class AutoAssessExerciseDetails( val assets: List ) -fun getExecutorMaxLoad(executorId: Long): Int { - return transaction { - Executor.select(Executor.maxLoad) - .where { Executor.id eq executorId } - .map { it[Executor.maxLoad] } - .first() - } +fun getExecutorMaxLoad(executorId: Long): Int = transaction { + Executor.select(Executor.maxLoad) + .where { Executor.id eq executorId } + .map { it[Executor.maxLoad] } + .first() } fun getAvailableExecutorIds(): List = transaction { Executor.select(Executor.id).map { it[Executor.id].value } } @@ -82,54 +82,50 @@ internal fun AutoAssessExerciseDetails.mapToExecutorRequest(submission: String): maxMem ) -internal fun getAutoExerciseDetails(autoExerciseId: Long): AutoAssessExerciseDetails { - return transaction { - val assets = Asset - .selectAll().where { Asset.autoExercise eq autoExerciseId } - .map { - AutoAssessExerciseAsset( - it[Asset.fileName], - it[Asset.fileContent] - ) - } - - AutoExercise.selectAll().where { AutoExercise.id eq autoExerciseId } - .map { - AutoAssessExerciseDetails( - it[AutoExercise.gradingScript], - it[AutoExercise.containerImage].value, - it[AutoExercise.maxTime], - it[AutoExercise.maxMem], - assets - ) - } - .first() - } +internal fun getAutoExerciseDetails(autoExerciseId: Long): AutoAssessExerciseDetails = transaction { + val assets = Asset + .selectAll().where { Asset.autoExercise eq autoExerciseId } + .map { + AutoAssessExerciseAsset( + it[Asset.fileName], + it[Asset.fileContent] + ) + } + + AutoExercise.selectAll().where { AutoExercise.id eq autoExerciseId } + .map { + AutoAssessExerciseDetails( + it[AutoExercise.gradingScript], + it[AutoExercise.containerImage].value, + it[AutoExercise.maxTime], + it[AutoExercise.maxMem], + assets + ) + } + .first() } -internal fun getCapableExecutors(autoExerciseId: Long): Set { - return transaction { - (AutoExercise innerJoin ContainerImage innerJoin ExecutorContainerImage innerJoin Executor) - .selectAll().where { AutoExercise.id eq autoExerciseId } - .map { - CapableExecutor( - it[Executor.id].value, - it[Executor.name], - it[Executor.baseUrl], - it[Executor.maxLoad], - it[Executor.drain] - ) - } - .toSet() - } +internal fun getCapableExecutors(autoExerciseId: Long): Set = transaction { + (AutoExercise innerJoin ContainerImage innerJoin ExecutorContainerImage innerJoin Executor) + .selectAll().where { AutoExercise.id eq autoExerciseId } + .map { + CapableExecutor( + it[Executor.id].value, + it[Executor.name], + it[Executor.baseUrl], + it[Executor.maxLoad], + it[Executor.drain] + ) + } + .toSet() } private fun timeoutRestTemplate(): RestTemplate { val timeout = SysConf.getProp(EXECUTOR_REQUEST_TIMEOUT_SECONDS_KEY)?.toLong() ?: 3600L return RestTemplateBuilder() - .setConnectTimeout(Duration.ofSeconds(timeout)) - .setReadTimeout(Duration.ofSeconds(timeout)) + .connectTimeout(Duration.ofSeconds(timeout)) + .readTimeout(Duration.ofSeconds(timeout)) .build() } @@ -141,8 +137,8 @@ internal fun callExecutor(executor: CapableExecutor, request: ExecutorRequest): ) if (responseEntity.statusCode.isError) { - log.error { "Executor error ${responseEntity.statusCodeValue} with request $request" } - throw ExecutorException("Executor error (${responseEntity.statusCodeValue})") + log.error { "Executor error ${responseEntity.statusCode.value()} with request $request" } + throw ExecutorException("Executor error (${responseEntity.statusCode.value()})") } val response = responseEntity.body diff --git a/core/src/main/kotlin/core/aas/insert_auto_exercise.kt b/core/src/main/kotlin/core/aas/insert_auto_exercise.kt index 70472735..23379807 100644 --- a/core/src/main/kotlin/core/aas/insert_auto_exercise.kt +++ b/core/src/main/kotlin/core/aas/insert_auto_exercise.kt @@ -5,11 +5,12 @@ import core.db.AutoExercise import core.db.ContainerImage import core.exception.InvalidRequestException import core.exception.ReqError -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.batchInsert -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.batchInsert +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction /** diff --git a/core/src/main/kotlin/core/aas/select_auto_exercise.kt b/core/src/main/kotlin/core/aas/select_auto_exercise.kt index cfe9e0a9..de7333c1 100644 --- a/core/src/main/kotlin/core/aas/select_auto_exercise.kt +++ b/core/src/main/kotlin/core/aas/select_auto_exercise.kt @@ -1,9 +1,10 @@ package core.aas import core.db.* -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction data class AutoExerciseDetails( diff --git a/core/src/main/kotlin/core/aas/service/CreateExecutor.kt b/core/src/main/kotlin/core/aas/service/CreateExecutor.kt index 60d534f0..7028db51 100644 --- a/core/src/main/kotlin/core/aas/service/CreateExecutor.kt +++ b/core/src/main/kotlin/core/aas/service/CreateExecutor.kt @@ -4,17 +4,17 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.aas.AutoGradeScheduler import core.conf.security.EasyUser import core.db.Executor -import mu.KotlinLogging -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @RequestMapping("/v2") @@ -22,12 +22,12 @@ class CreateExecutorController(private val autoGradeScheduler: AutoGradeSchedule private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("name", required = true) @field:NotBlank @field:Size(max = 100) val name: String, - @JsonProperty("base_url", required = true) @field:NotBlank @field:Size(max = 2000) val baseUrl: String, - @JsonProperty("max_load", required = true) val maxLoad: Int + @param:JsonProperty("name", required = true) @field:NotBlank @field:Size(max = 100) val name: String, + @param:JsonProperty("base_url", required = true) @field:NotBlank @field:Size(max = 2000) val baseUrl: String, + @param:JsonProperty("max_load", required = true) val maxLoad: Int ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_ADMIN") @PostMapping("/executors") diff --git a/core/src/main/kotlin/core/aas/service/CreateImage.kt b/core/src/main/kotlin/core/aas/service/CreateImage.kt index 4736143a..348dcefd 100644 --- a/core/src/main/kotlin/core/aas/service/CreateImage.kt +++ b/core/src/main/kotlin/core/aas/service/CreateImage.kt @@ -5,26 +5,27 @@ import core.conf.security.EasyUser import core.db.ContainerImage import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank @RestController @RequestMapping("/v2") class CreateImageController { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("id", required = true) @field:NotBlank val id: String) + data class Req(@param:JsonProperty("id", required = true) @field:NotBlank val id: String) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_ADMIN") @PostMapping("/container-images") @@ -46,6 +47,5 @@ class CreateImageController { ) } } - } diff --git a/core/src/main/kotlin/core/aas/service/DeleteExecutor.kt b/core/src/main/kotlin/core/aas/service/DeleteExecutor.kt index 63a43f00..a1335ff1 100644 --- a/core/src/main/kotlin/core/aas/service/DeleteExecutor.kt +++ b/core/src/main/kotlin/core/aas/service/DeleteExecutor.kt @@ -4,17 +4,17 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.aas.AutoGradeScheduler import core.conf.security.EasyUser import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid @RestController @RequestMapping("/v2") class DeleteExecutorController(private val autoGradeScheduler: AutoGradeScheduler) { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("force") val force: Boolean) + data class Req(@param:JsonProperty("force") val force: Boolean) @Secured("ROLE_ADMIN") @DeleteMapping("/executors/{executorId}") diff --git a/core/src/main/kotlin/core/aas/service/ReadExecutor.kt b/core/src/main/kotlin/core/aas/service/ReadExecutor.kt index 1d819ecb..df918fea 100644 --- a/core/src/main/kotlin/core/aas/service/ReadExecutor.kt +++ b/core/src/main/kotlin/core/aas/service/ReadExecutor.kt @@ -4,9 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.Executor import core.db.ExecutorContainerImage -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -19,12 +20,12 @@ class ReadExecutorController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String, - @JsonProperty("base_url") val baseUrl: String, - @JsonProperty("max_load") val maxLoad: Int, - @JsonProperty("drain") val drain: Boolean, - @JsonProperty("containers") val containers: List + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String, + @get:JsonProperty("base_url") val baseUrl: String, + @get:JsonProperty("max_load") val maxLoad: Int, + @get:JsonProperty("drain") val drain: Boolean, + @get:JsonProperty("containers") val containers: List ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/aas/service/ReadImages.kt b/core/src/main/kotlin/core/aas/service/ReadImages.kt index 4a908a45..b0b635f7 100644 --- a/core/src/main/kotlin/core/aas/service/ReadImages.kt +++ b/core/src/main/kotlin/core/aas/service/ReadImages.kt @@ -3,9 +3,9 @@ package core.aas.service import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.ContainerImage -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController class ReadImagesController { private val log = KotlinLogging.logger {} - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @GetMapping("/container-images") diff --git a/core/src/main/kotlin/core/aas/service/UpdateExecutor.kt b/core/src/main/kotlin/core/aas/service/UpdateExecutor.kt index 79ba78cb..df7b21dc 100644 --- a/core/src/main/kotlin/core/aas/service/UpdateExecutor.kt +++ b/core/src/main/kotlin/core/aas/service/UpdateExecutor.kt @@ -8,18 +8,18 @@ import core.db.ExecutorContainerImage import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.batchInsert -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.batchInsert +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -28,11 +28,11 @@ class UpdateExecutorController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("name", required = true) @field:NotBlank @field:Size(max = 100) val name: String, - @JsonProperty("base_url", required = true) @field:NotBlank @field:Size(max = 2000) val baseUrl: String, - @JsonProperty("max_load", required = true) val maxLoad: Int, - @JsonProperty("drain", required = true) val drain: Boolean, - @JsonProperty("containers", required = true) val containers: List + @param:JsonProperty("name", required = true) @field:NotBlank @field:Size(max = 100) val name: String, + @param:JsonProperty("base_url", required = true) @field:NotBlank @field:Size(max = 2000) val baseUrl: String, + @param:JsonProperty("max_load", required = true) val maxLoad: Int, + @param:JsonProperty("drain", required = true) val drain: Boolean, + @param:JsonProperty("containers", required = true) val containers: List ) @Secured("ROLE_ADMIN") @@ -52,7 +52,8 @@ class UpdateExecutorController { // Check that containers exists and if not, exception. body.containers.forEach { - if (ContainerImage.selectAll().where { ContainerImage.id eq it }.count() != 1L) throw InvalidRequestException( + if (ContainerImage.selectAll().where { ContainerImage.id eq it } + .count() != 1L) throw InvalidRequestException( "Container image '$it' not found!", ReqError.ENTITY_WITH_ID_NOT_FOUND, "id" to it diff --git a/core/src/main/kotlin/core/conf/DatabaseConf.kt b/core/src/main/kotlin/core/conf/DatabaseConf.kt index af94085d..22a70005 100644 --- a/core/src/main/kotlin/core/conf/DatabaseConf.kt +++ b/core/src/main/kotlin/core/conf/DatabaseConf.kt @@ -1,14 +1,14 @@ package core.conf import liquibase.integration.spring.SpringLiquibase -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.TransactionManager import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.jdbc.DataSourceBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.DependsOn -import javax.annotation.PostConstruct +import jakarta.annotation.PostConstruct +import org.jetbrains.exposed.v1.jdbc.Database +import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager import javax.sql.DataSource @Configuration @@ -34,6 +34,6 @@ class DatabaseInit(val dataSource: DataSource) { @PostConstruct fun init() { Database.connect(dataSource) - TransactionManager.manager.defaultRepetitionAttempts = 6 + TransactionManager.manager.defaultMaxAttempts = 6 } } diff --git a/core/src/main/kotlin/core/conf/SysConf.kt b/core/src/main/kotlin/core/conf/SysConf.kt index 7641de54..d027c38f 100644 --- a/core/src/main/kotlin/core/conf/SysConf.kt +++ b/core/src/main/kotlin/core/conf/SysConf.kt @@ -1,9 +1,10 @@ package core.conf import core.db.SystemConfiguration -import core.db.insertOrUpdate -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.upsert object SysConf { @@ -14,8 +15,9 @@ object SysConf { }.firstOrNull() } + fun putProp(key: String, value: String) = transaction { - SystemConfiguration.insertOrUpdate(SystemConfiguration.id, listOf(SystemConfiguration.id)) { + SystemConfiguration.upsert(SystemConfiguration.id) { it[SystemConfiguration.id] = key it[SystemConfiguration.value] = value } diff --git a/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt b/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt index 12e754c4..cce601fa 100644 --- a/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt +++ b/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt @@ -1,11 +1,11 @@ package core.conf.security import core.ems.service.getOptionalHeader +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.security.core.context.SecurityContextHolder import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse class DummyZeroAuthFilter : OncePerRequestFilter() { diff --git a/core/src/main/kotlin/core/conf/security/EasyUserAuthProvider.kt b/core/src/main/kotlin/core/conf/security/EasyUserAuthProvider.kt index ab0383b8..951f5574 100644 --- a/core/src/main/kotlin/core/conf/security/EasyUserAuthProvider.kt +++ b/core/src/main/kotlin/core/conf/security/EasyUserAuthProvider.kt @@ -1,22 +1,18 @@ package core.conf.security -import org.springframework.context.annotation.Configuration import org.springframework.security.authentication.AuthenticationProvider import org.springframework.security.core.Authentication +import org.springframework.stereotype.Component -@Configuration +@Component class EasyUserAuthProvider : AuthenticationProvider { - override fun authenticate(authentication: Authentication?): Authentication? { - authentication?.isAuthenticated = authentication?.authorities?.isNotEmpty() ?: false + override fun authenticate(authentication: Authentication): Authentication? { + authentication.isAuthenticated = authentication.authorities?.isNotEmpty() ?: false return authentication } - override fun supports(authentication: Class<*>?): Boolean { - return if (authentication == null) { - false - } else { - EasyUser::class.java.isAssignableFrom(authentication) - } + override fun supports(authentication: Class<*>): Boolean { + return EasyUser::class.java.isAssignableFrom(authentication) } } diff --git a/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt b/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt index 2596e657..0791e270 100644 --- a/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt +++ b/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt @@ -2,12 +2,12 @@ package core.conf.security import com.auth0.jwt.JWT import core.ems.service.getOptionalHeader -import mu.KotlinLogging +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.core.context.SecurityContextHolder import org.springframework.web.filter.OncePerRequestFilter -import javax.servlet.FilterChain -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse private val log = KotlinLogging.logger {} diff --git a/core/src/main/kotlin/core/conf/security/SecurityConf.kt b/core/src/main/kotlin/core/conf/security/SecurityConf.kt index a0d1aee8..894a0f5c 100644 --- a/core/src/main/kotlin/core/conf/security/SecurityConf.kt +++ b/core/src/main/kotlin/core/conf/security/SecurityConf.kt @@ -1,67 +1,66 @@ package core.conf.security -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter import org.springframework.security.web.firewall.HttpFirewall import org.springframework.security.web.firewall.StrictHttpFirewall import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(securedEnabled = true) -class SecurityConf : WebSecurityConfigurerAdapter() { +@EnableMethodSecurity(securedEnabled = true) +class SecurityConf { private val log = KotlinLogging.logger {} @Value("\${easy.core.auth-enabled}") private var authEnabled: Boolean = true - override fun configure(http: HttpSecurity) { - http.authorizeRequests() - .antMatchers( - // Allow unauthenticated access to anonymous auto-assess services - "/*/unauth/exercises/*/anonymous/autoassess", - "/*/unauth/exercises/*/anonymous/details" - ).permitAll() - // All other services require auth == any role by default - .anyRequest().authenticated() - - http.addFilterAfter( - if (authEnabled) PreAuthHeaderFilter() else DummyZeroAuthFilter(), - RequestHeaderAuthenticationFilter::class.java - ) - - http.exceptionHandling() - .accessDeniedHandler { request, response, _ -> - log.info { "Forbidden for ${makeRequestLogMsg(request)}" } - response.sendError(HttpServletResponse.SC_FORBIDDEN) - } - .authenticationEntryPoint { request, response, _ -> - log.info { "Unauthorized for ${makeRequestLogMsg(request)}" } - response.sendError(HttpServletResponse.SC_UNAUTHORIZED) - } - - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - - http.cors().configurationSource(getCorsConfiguration()) - http.csrf().disable() - } + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain = + http + .authorizeHttpRequests { + it.requestMatchers( + // Allow unauthenticated access to anonymous auto-assess services + "/*/unauth/exercises/*/anonymous/autoassess", + "/*/unauth/exercises/*/anonymous/details" + ).permitAll() + // All other services require auth == any role by default + .anyRequest().authenticated() + }.addFilterAfter( + if (authEnabled) PreAuthHeaderFilter() else DummyZeroAuthFilter(), + RequestHeaderAuthenticationFilter::class.java + ).exceptionHandling { + it.accessDeniedHandler { request, response, _ -> + log.info { "Forbidden for ${makeRequestLogMsg(request)}" } + response.sendError(HttpServletResponse.SC_FORBIDDEN) + } + it.authenticationEntryPoint { request, response, _ -> + log.info { "Unauthorized for ${makeRequestLogMsg(request)}" } + response.sendError(HttpServletResponse.SC_UNAUTHORIZED) + } + }.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } + .cors { it.configurationSource(getCorsConfiguration()) } + .csrf { it.disable() }.build() - override fun configure(auth: AuthenticationManagerBuilder) { - auth.authenticationProvider(EasyUserAuthProvider()) + @Bean + @Throws(Exception::class) + fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? { + return authenticationConfiguration.getAuthenticationManager() } // Temporary workaround for EZ-1434 diff --git a/core/src/main/kotlin/core/db/Tables.kt b/core/src/main/kotlin/core/db/Tables.kt index 8c1582b5..26f881bc 100644 --- a/core/src/main/kotlin/core/db/Tables.kt +++ b/core/src/main/kotlin/core/db/Tables.kt @@ -1,12 +1,13 @@ package core.db -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.IdTable -import org.jetbrains.exposed.dao.id.LongIdTable -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Table -import org.jetbrains.exposed.sql.jodatime.datetime -import org.jetbrains.exposed.sql.json.jsonb + +import org.jetbrains.exposed.v1.core.Column +import org.jetbrains.exposed.v1.core.Table +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.dao.id.IdTable +import org.jetbrains.exposed.v1.core.dao.id.LongIdTable +import org.jetbrains.exposed.v1.jodatime.datetime +import org.jetbrains.exposed.v1.json.jsonb import org.joda.time.DateTime diff --git a/core/src/main/kotlin/core/db/exposed_extensions.kt b/core/src/main/kotlin/core/db/exposed_extensions.kt index e348d3bd..30dcf6e8 100644 --- a/core/src/main/kotlin/core/db/exposed_extensions.kt +++ b/core/src/main/kotlin/core/db/exposed_extensions.kt @@ -1,47 +1,11 @@ package core.db -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.Function -import org.jetbrains.exposed.sql.statements.InsertStatement -import org.jetbrains.exposed.sql.transactions.TransactionManager - - -// Waiting for exposed upsert: https://github.com/JetBrains/Exposed/issues/167 -fun T.insertOrUpdate(key: Column<*>, excluded: List>, body: T.(InsertStatement) -> Unit) = - InsertOrUpdate(this, keys = listOf(key), excluded = excluded).apply { - body(this) - execute(TransactionManager.current()) - } - -fun T.insertOrUpdate( - keys: List>, - excluded: List>, - body: T.(InsertStatement) -> Unit -) = - InsertOrUpdate(this, keys = keys, excluded = excluded).apply { - body(this) - execute(TransactionManager.current()) - } - -class InsertOrUpdate( - table: Table, - isIgnore: Boolean = false, - private val keys: List>, - private val excluded: List> -) : InsertStatement(table, isIgnore) { - override fun prepareSQL(transaction: Transaction, prepared: Boolean): String { - val tm = TransactionManager.current() - val updateCols = table.columns.minus(excluded) - val updateSetter = updateCols.joinToString { "${tm.identity(it)} = EXCLUDED.${tm.identity(it)}" } - val keyColsStr = keys.joinToString { tm.identity(it) } - val onConflict = "ON CONFLICT ($keyColsStr) DO UPDATE SET $updateSetter" - return "${super.prepareSQL(transaction, prepared)} $onConflict" - } -} +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.core.Function // Waiting for exposed DISTINCT ON. Extension inspired by https://github.com/JetBrains/Exposed/issues/500 -class DistinctOn(columns: List>) : Function(columns.first().columnType) { +class DistinctOn(columns: List>) : Function(columns.first().columnType as IColumnType) { private val distinctNames = columns.joinToString(", ") { "${it.table.tableName}.${it.name}" } @@ -55,17 +19,6 @@ class DistinctOn(columns: List>) : Function(columns.first().colu } } -// Waiting for NULLS FIRST/LAST support (https://github.com/JetBrains/Exposed/issues/478) -// Workaround: use ORDER BY IS NULL ASC to force nulls last - -// IS NULL clause that can be used in ORDER BY -fun Expression.isNull() = IsNullExp(this) - -class IsNullExp(val expr: Expression<*>) : Op() { - override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append(expr, " IS NULL") } -} - - // Shortcut for finding the complement/negation of SortOrder fun SortOrder.complement() = when (this) { SortOrder.ASC -> SortOrder.DESC diff --git a/core/src/main/kotlin/core/ems/cron/delete_inactive_users.kt b/core/src/main/kotlin/core/ems/cron/delete_inactive_users.kt index adcbd199..5c350f3b 100644 --- a/core/src/main/kotlin/core/ems/cron/delete_inactive_users.kt +++ b/core/src/main/kotlin/core/ems/cron/delete_inactive_users.kt @@ -4,13 +4,15 @@ package core.ems.cron import com.fasterxml.jackson.annotation.JsonProperty import core.db.* import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.insertIgnoreAndGetId +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.web.client.RestTemplateBuilder import org.springframework.core.ParameterizedTypeReference import org.springframework.http.* import org.springframework.scheduling.annotation.Scheduled @@ -18,7 +20,7 @@ import org.springframework.stereotype.Component import org.springframework.util.LinkedMultiValueMap import java.time.Duration import java.util.* - +import org.springframework.boot.restclient.RestTemplateBuilder @Component class DeleteInactiveUsers(val sendMailService: SendMailService) { @@ -43,11 +45,11 @@ class DeleteInactiveUsers(val sendMailService: SendMailService) { private var ignoreMissingKeycloakUsers: Boolean = false private data class TokenResponse( - @JsonProperty("access_token") val accessToken: String, + @param:JsonProperty("access_token") val accessToken: String, ) private data class KeycloakUser( - @JsonProperty("id") val id: String, + @param:JsonProperty("id") val id: String, ) @Scheduled(cron = "\${easy.core.keycloak.cron}") @@ -273,7 +275,7 @@ class DeleteInactiveUsers(val sendMailService: SendMailService) { } private fun restTemplate() = RestTemplateBuilder() - .setConnectTimeout(Duration.ofSeconds(60)) - .setReadTimeout(Duration.ofSeconds(60)) + .connectTimeout(Duration.ofSeconds(60)) + .readTimeout(Duration.ofSeconds(60)) .build() } diff --git a/core/src/main/kotlin/core/ems/cron/norm_ce_idx.kt b/core/src/main/kotlin/core/ems/cron/norm_ce_idx.kt index 10be1bd6..815bf77b 100644 --- a/core/src/main/kotlin/core/ems/cron/norm_ce_idx.kt +++ b/core/src/main/kotlin/core/ems/cron/norm_ce_idx.kt @@ -9,7 +9,5 @@ import org.springframework.stereotype.Component @Component class ExerciseIndexNormalisationCron { @Scheduled(cron = "\${easy.core.exercise-index-normalisation.cron}") - fun cron() { - normaliseAllCourseExIndices() - } + fun cron() = normaliseAllCourseExIndices() } diff --git a/core/src/main/kotlin/core/ems/service/ExportPersonalData.kt b/core/src/main/kotlin/core/ems/service/ExportPersonalData.kt index dc46c3d7..0bcdd95e 100644 --- a/core/src/main/kotlin/core/ems/service/ExportPersonalData.kt +++ b/core/src/main/kotlin/core/ems/service/ExportPersonalData.kt @@ -1,15 +1,15 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import core.conf.security.EasyUser import core.db.* import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.core.io.ByteArrayResource import org.springframework.http.HttpHeaders.CONTENT_DISPOSITION @@ -18,6 +18,8 @@ import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import tools.jackson.databind.annotation.JsonSerialize +import tools.jackson.module.kotlin.jacksonObjectMapper import java.io.ByteArrayOutputStream import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream @@ -48,11 +50,11 @@ class ExportPersonalData { private fun selectAccountJSON(userId: String): String { data class AccountDataJSON( - @JsonProperty("id") val username: String, - @JsonProperty("created_at") @JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime, - @JsonProperty("email") val email: String, - @JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") val familyName: String + @get:JsonProperty("id") val username: String, + @get:JsonProperty("created_at") @get:JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime, + @get:JsonProperty("email") val email: String, + @get:JsonProperty("given_name") val givenName: String, + @get:JsonProperty("family_name") val familyName: String ) return transaction { @@ -79,11 +81,11 @@ class ExportPersonalData { private fun selectLogReportsJSON(userId: String): String { data class LogReportDataJSON( - @JsonProperty("id") val id: Long, - @JsonProperty("log_time") @JsonSerialize(using = DateTimeSerializer::class) val logTime: DateTime, - @JsonProperty("level") val level: String, - @JsonProperty("message") val message: String, - @JsonProperty("client_id") val clientId: String + @get:JsonProperty("id") val id: Long, + @get:JsonProperty("log_time") @get:JsonSerialize(using = DateTimeSerializer::class) val logTime: DateTime, + @get:JsonProperty("level") val level: String, + @get:JsonProperty("message") val message: String, + @get:JsonProperty("client_id") val clientId: String ) return transaction { @@ -109,13 +111,13 @@ class ExportPersonalData { private fun selectSubmissionsJSON(studentId: String): String { data class SubmissionDataJSON( - @JsonProperty("id") val submissionId: String, - @JsonProperty("solution") val solution: String, - @JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("grade_auto") val gradeAuto: Int?, - @JsonProperty("feedback_auto") val feedbackAuto: String?, - @JsonProperty("grade_teacher") val gradeTeacher: Int?, - @JsonProperty("feedback_teacher") val feedbackTeacher: String? + @get:JsonProperty("id") val submissionId: String, + @get:JsonProperty("solution") val solution: String, + @get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("grade_auto") val gradeAuto: Int?, + @get:JsonProperty("feedback_auto") val feedbackAuto: String?, + @get:JsonProperty("grade_teacher") val gradeTeacher: Int?, + @get:JsonProperty("feedback_teacher") val feedbackTeacher: String? ) return transaction { @@ -145,9 +147,9 @@ class ExportPersonalData { private fun selectSubmissionsDraftJSON(studentId: String): String { data class SubmissionDraftJSON( - @JsonProperty("course_exercise_id") val courseExerciseId: Long, - @JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("draft") val draft: String + @get:JsonProperty("course_exercise_id") val courseExerciseId: Long, + @get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("draft") val draft: String ) return transaction { diff --git a/core/src/main/kotlin/core/ems/service/access_control/course.kt b/core/src/main/kotlin/core/ems/service/access_control/course.kt index ee815beb..675bc78d 100644 --- a/core/src/main/kotlin/core/ems/service/access_control/course.kt +++ b/core/src/main/kotlin/core/ems/service/access_control/course.kt @@ -10,9 +10,11 @@ import core.ems.service.selectCourseExerciseExceptions import core.exception.ForbiddenException import core.exception.InvalidRequestException import core.exception.ReqError -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction /** diff --git a/core/src/main/kotlin/core/ems/service/access_control/library_exercise.kt b/core/src/main/kotlin/core/ems/service/access_control/library_exercise.kt index 7e4b9f0e..bfbac508 100644 --- a/core/src/main/kotlin/core/ems/service/access_control/library_exercise.kt +++ b/core/src/main/kotlin/core/ems/service/access_control/library_exercise.kt @@ -10,9 +10,12 @@ import core.ems.service.hasAccountDirAccess import core.exception.ForbiddenException import core.exception.InvalidRequestException import core.exception.ReqError -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction /** * Has the given access level to this exercise in the exercise library. diff --git a/core/src/main/kotlin/core/ems/service/accounts.kt b/core/src/main/kotlin/core/ems/service/accounts.kt index 940b2332..03d18463 100644 --- a/core/src/main/kotlin/core/ems/service/accounts.kt +++ b/core/src/main/kotlin/core/ems/service/accounts.kt @@ -2,10 +2,12 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty import core.db.Account -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update fun getUsernameByEmail(email: String): String? = transaction { @@ -21,11 +23,11 @@ fun teacherExists(username: String): Boolean = transaction { } data class TeacherResp( - @JsonProperty("id") + @get:JsonProperty("id") val id: String, - @JsonProperty("given_name") + @get:JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") + @get:JsonProperty("family_name") val familyName: String ) @@ -38,11 +40,6 @@ fun selectTeacher(teacherId: String) = transaction { .singleOrInvalidRequest() } - -fun accountExists(username: String): Boolean = transaction { - Account.selectAll().where { Account.id eq username }.count() > 0 -} - fun selectPseudonym(username: String): String = transaction { Account .select(Account.pseudonym) diff --git a/core/src/main/kotlin/core/ems/service/adoc_preview.kt b/core/src/main/kotlin/core/ems/service/adoc_preview.kt index 68977436..0b3fa1aa 100644 --- a/core/src/main/kotlin/core/ems/service/adoc_preview.kt +++ b/core/src/main/kotlin/core/ems/service/adoc_preview.kt @@ -2,13 +2,13 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser -import mu.KotlinLogging +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @@ -16,7 +16,7 @@ import javax.validation.Valid class AdocPreviewController(private val adocService: AdocService) { private val log = KotlinLogging.logger {} - data class ReqResp(@JsonProperty("content") val content: String) + data class ReqResp(@get:JsonProperty("content") val content: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN", "ROLE_STUDENT") @PostMapping("/preview/adoc") diff --git a/core/src/main/kotlin/core/ems/service/adoc_service.kt b/core/src/main/kotlin/core/ems/service/adoc_service.kt index 44b48442..6cf5ec36 100644 --- a/core/src/main/kotlin/core/ems/service/adoc_service.kt +++ b/core/src/main/kotlin/core/ems/service/adoc_service.kt @@ -1,6 +1,6 @@ package core.ems.service -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.asciidoctor.Asciidoctor import org.asciidoctor.Attributes import org.asciidoctor.Options @@ -16,20 +16,20 @@ private val log = KotlinLogging.logger {} @Service class AdocService { private val doctor = Asciidoctor.Factory.create() - private val options = Options() + private val options: Options init { - val attributes = Attributes() - attributes.setSourceHighlighter("highlightjs") - // TODO: remove after all exercises have migrated to EasyCode - attributes.setAttribute("run", "") - attributes.setAttribute("nur", "") - attributes.setAttribute("in", "") - attributes.setAttribute("ni", "") - attributes.setAttribute("nohl", "") - attributes.setAttribute("lhon", "") - options.setAttributes(attributes) + val attributes = Attributes.builder().sourceHighlighter("highlightjs") + .attribute("run", "") + .attribute("nur", "") + .attribute("in", "") + .attribute("ni", "") + .attribute("nohl", "") + .attribute("lhon", "") + .build() + + options = Options.builder().attributes(attributes).build() doctor.javaExtensionRegistry() .postprocessor(LinkExternaliserProcessor()) @@ -42,7 +42,7 @@ class AdocService { } class LinkExternaliserProcessor : Postprocessor() { - override fun process(document: Document?, output: String?): String { + override fun process(document: Document?, output: String): String { val jdoc = Jsoup.parse(output, "UTF-8") jdoc.getElementsByTag("a").forEach { it.attr("target", "_blank") @@ -62,7 +62,7 @@ class EasyCodeProcessor : Postprocessor() { private val regex: Regex; init { - val attrStr = CodeAttr.values().joinToString("|") { it.id } + val attrStr = CodeAttr.entries.joinToString("|") { it.id } regex = Regex("\\\$($attrStr)\\[(.+?)(? + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("articles") val articles: List ) data class ArticleResp( - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("aliases") val aliases: List? + @get:JsonProperty("id") val id: String, + @get:JsonProperty("title") val title: String, + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("aliases") val aliases: List? ) data class RespAlias( - @JsonProperty("id") val id: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("created_by") val createdBy: String + @get:JsonProperty("id") val id: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("created_by") val createdBy: String ) @Secured("ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/article/AdminUpdateArticle.kt b/core/src/main/kotlin/core/ems/service/article/AdminUpdateArticle.kt index 04fecf12..af230cd4 100644 --- a/core/src/main/kotlin/core/ems/service/article/AdminUpdateArticle.kt +++ b/core/src/main/kotlin/core/ems/service/article/AdminUpdateArticle.kt @@ -11,19 +11,23 @@ import core.ems.service.assertArticleExists import core.ems.service.cache.CachingService import core.ems.service.cache.articleCache import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -32,9 +36,9 @@ class UpdateArticleController(private val adocService: AdocService, private val private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("title", required = true) @field:NotBlank @field:Size(max = 100) val title: String, - @JsonProperty("text_adoc", required = false) @field:Size(max = 300000) val textAdoc: String?, - @JsonProperty("public", required = true) val public: Boolean + @param:JsonProperty("title", required = true) @field:NotBlank @field:Size(max = 100) val title: String, + @param:JsonProperty("text_adoc", required = false) @field:Size(max = 300000) val textAdoc: String?, + @param:JsonProperty("public", required = true) val public: Boolean ) diff --git a/core/src/main/kotlin/core/ems/service/article/ReadArticleDetails.kt b/core/src/main/kotlin/core/ems/service/article/ReadArticleDetails.kt index 98ca6f3e..4f0ea837 100644 --- a/core/src/main/kotlin/core/ems/service/article/ReadArticleDetails.kt +++ b/core/src/main/kotlin/core/ems/service/article/ReadArticleDetails.kt @@ -2,17 +2,17 @@ package core.ems.service.article import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.ems.service.cache.CachingService import core.util.DateTimeSerializer -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import tools.jackson.databind.annotation.JsonSerialize import java.io.Serializable @@ -22,33 +22,33 @@ class ReadArticleDetailsController(private val cachingService: CachingService) { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("last_modified") val lastModified: DateTime, - @JsonProperty("owner") val owner: RespUser, - @JsonProperty("author") val author: RespUser, - @JsonProperty("text_html") val textHtml: String?, - @JsonProperty("text_adoc") val textAdoc: String?, - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("public") val public: Boolean?, - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("aliases") val assets: List? + @get:JsonProperty("id") val id: String, + @get:JsonProperty("title") val title: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("last_modified") val lastModified: DateTime, + @get:JsonProperty("owner") val owner: RespUser, + @get:JsonProperty("author") val author: RespUser, + @get:JsonProperty("text_html") val textHtml: String?, + @get:JsonProperty("text_adoc") val textAdoc: String?, + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("public") val public: Boolean?, + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("aliases") val assets: List? ) : Serializable data class RespAlias( - @JsonProperty("id") val id: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("created_by") val createdBy: String + @get:JsonProperty("id") val id: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("created_by") val createdBy: String ) : Serializable data class RespUser( - @JsonProperty("id") val id: String, - @JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") val familyName: String + @get:JsonProperty("id") val id: String, + @get:JsonProperty("given_name") val givenName: String, + @get:JsonProperty("family_name") val familyName: String ) : Serializable diff --git a/core/src/main/kotlin/core/ems/service/articles.kt b/core/src/main/kotlin/core/ems/service/articles.kt index 776b581a..5bf0e2b1 100644 --- a/core/src/main/kotlin/core/ems/service/articles.kt +++ b/core/src/main/kotlin/core/ems/service/articles.kt @@ -5,9 +5,11 @@ import core.db.ArticleAlias import core.ems.service.article.ReadArticleDetailsController import core.exception.InvalidRequestException import core.exception.ReqError -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction fun assertArticleExists(articleId: Long) { if (!articleExists(articleId)) { @@ -26,28 +28,25 @@ fun assertArticleAliasExists(articleId: Long, alias: String) { } -private fun articleExists(articleId: Long): Boolean { - return transaction { - Article.selectAll().where { Article.id eq articleId }.count() == 1L - } +private fun articleExists(articleId: Long): Boolean = transaction { + Article.selectAll().where { Article.id eq articleId }.count() == 1L } -private fun articleAliasExists(articleId: Long, alias: String): Boolean { - return transaction { - ArticleAlias.selectAll().where { (ArticleAlias.id eq alias) and (ArticleAlias.article eq articleId) } - .count() == 1L - } +private fun articleAliasExists(articleId: Long, alias: String): Boolean = transaction { + ArticleAlias + .selectAll() + .where { (ArticleAlias.id eq alias) and (ArticleAlias.article eq articleId) } + .count() == 1L } -fun selectArticleAliases(articleId: Long): List { - return transaction { - ArticleAlias.select(ArticleAlias.id, ArticleAlias.createdAt, ArticleAlias.owner) - .where { ArticleAlias.article eq articleId }.map { - ReadArticleDetailsController.RespAlias( - it[ArticleAlias.id].value, - it[ArticleAlias.createdAt], - it[ArticleAlias.owner].value - ) - } - } +fun selectArticleAliases(articleId: Long): List = transaction { + ArticleAlias.select(ArticleAlias.id, ArticleAlias.createdAt, ArticleAlias.owner) + .where { ArticleAlias.article eq articleId } + .map { + ReadArticleDetailsController.RespAlias( + it[ArticleAlias.id].value, + it[ArticleAlias.createdAt], + it[ArticleAlias.owner].value + ) + } } diff --git a/core/src/main/kotlin/core/ems/service/assessments.kt b/core/src/main/kotlin/core/ems/service/assessments.kt index e8cbf535..8935146a 100644 --- a/core/src/main/kotlin/core/ems/service/assessments.kt +++ b/core/src/main/kotlin/core/ems/service/assessments.kt @@ -2,8 +2,6 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonRawValue -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import core.conf.security.EasyUser import core.db.* import core.ems.service.access_control.assertAccess @@ -11,9 +9,15 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.cache.CachingService import core.ems.service.cache.countSubmissionsInAutoAssessmentCache import core.util.DateTimeSerializer -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime +import tools.jackson.databind.annotation.JsonSerialize +import tools.jackson.module.kotlin.jacksonObjectMapper data class InlineComment( val line_start: Int, @@ -47,19 +51,19 @@ fun buildFeedbackJson( } data class TeacherActivityResp( - @JsonProperty("id") val id: String, - @JsonProperty("submission_id") val submissionId: String, - @JsonProperty("submission_number") val submissionNumber: Int, - @JsonProperty("created_at") @JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime, - @JsonProperty("grade") val grade: Int?, - @JsonProperty("edited_at") @JsonSerialize(using = DateTimeSerializer::class) val editedAt: DateTime?, - @JsonProperty("feedback") @JsonRawValue val feedback: String?, - @JsonProperty("teacher") val teacher: TeacherResp + @get:JsonProperty("id") val id: String, + @get:JsonProperty("submission_id") val submissionId: String, + @get:JsonProperty("submission_number") val submissionNumber: Int, + @get:JsonProperty("created_at") @get:JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime, + @get:JsonProperty("grade") val grade: Int?, + @get:JsonProperty("edited_at") @get:JsonSerialize(using = DateTimeSerializer::class) val editedAt: DateTime?, + @get:JsonProperty("feedback") @JsonRawValue val feedback: String?, + @get:JsonProperty("teacher") val teacher: TeacherResp ) data class ActivityResp( - @JsonProperty("teacher_activities") val teacherActivities: List, + @get:JsonProperty("teacher_activities") val teacherActivities: List, ) fun selectStudentAllExerciseActivities(courseExId: Long, studentId: String): ActivityResp = transaction { diff --git a/core/src/main/kotlin/core/ems/service/cache/AdminEvictAllCache.kt b/core/src/main/kotlin/core/ems/service/cache/AdminEvictAllCache.kt index f2f7fee2..3a7b021f 100644 --- a/core/src/main/kotlin/core/ems/service/cache/AdminEvictAllCache.kt +++ b/core/src/main/kotlin/core/ems/service/cache/AdminEvictAllCache.kt @@ -1,7 +1,7 @@ package core.ems.service.cache import core.conf.security.EasyUser -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping diff --git a/core/src/main/kotlin/core/ems/service/cache/CachingService.kt b/core/src/main/kotlin/core/ems/service/cache/CachingService.kt index ec6f3de7..8bd931d1 100644 --- a/core/src/main/kotlin/core/ems/service/cache/CachingService.kt +++ b/core/src/main/kotlin/core/ems/service/cache/CachingService.kt @@ -5,12 +5,11 @@ import core.ems.service.article.ReadArticleDetailsController import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectArticleAliases import core.ems.service.singleOrInvalidRequest -import mu.KotlinLogging -import org.jetbrains.exposed.sql.alias -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.innerJoin -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.cache.CacheManager import org.springframework.cache.annotation.Cacheable diff --git a/core/src/main/kotlin/core/ems/service/ce_idx_norm.kt b/core/src/main/kotlin/core/ems/service/ce_idx_norm.kt index ec44ca81..5315a17d 100644 --- a/core/src/main/kotlin/core/ems/service/ce_idx_norm.kt +++ b/core/src/main/kotlin/core/ems/service/ce_idx_norm.kt @@ -2,9 +2,12 @@ package core.ems.service import core.db.Course import core.db.CourseExercise -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update private val log = KotlinLogging.logger {} diff --git a/core/src/main/kotlin/core/ems/service/code/TeacherCheckSimilarity.kt b/core/src/main/kotlin/core/ems/service/code/TeacherCheckSimilarity.kt index c8cc57bd..b05ec3ae 100644 --- a/core/src/main/kotlin/core/ems/service/code/TeacherCheckSimilarity.kt +++ b/core/src/main/kotlin/core/ems/service/code/TeacherCheckSimilarity.kt @@ -1,7 +1,7 @@ package core.ems.service.code import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.Account import core.db.Course @@ -14,18 +14,21 @@ import core.exception.InvalidRequestException import core.exception.ReqError import core.util.DateTimeSerializer import core.util.forEachCombination +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size import me.xdrop.fuzzywuzzy.FuzzySearch -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.andWhere +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* import java.util.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size import kotlin.math.roundToInt @@ -41,46 +44,45 @@ class TeacherCheckSimilarityController { * 3) exerciseId, courseId(s), submissionIds - compare over all given submissions (must be on given courses) */ data class Req( - @JsonProperty("courses", required = false) val courses: List, - @JsonProperty("submissions", required = false) val submissions: List? + @param:JsonProperty("courses", required = false) val courses: List, + @param:JsonProperty("submissions", required = false) val submissions: List? ) data class ReqCourse( - @JsonProperty("id", required = true) + @param:JsonProperty("id", required = true) @field:NotBlank @field:Size(max = 100) val id: String ) data class ReqSubmission( - @JsonProperty("id", required = true) + @param:JsonProperty("id", required = true) @field:NotBlank @field:Size(max = 100) val id: String ) - data class Resp( - @JsonProperty("submissions") val submissions: List, - @JsonProperty("scores") val scores: List + @get:JsonProperty("submissions") val submissions: List, + @get:JsonProperty("scores") val scores: List ) data class RespSubmission( - @JsonProperty("id") val id: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("id") val id: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, // TODO: number - @JsonProperty("solution") val solution: String, - @JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") val familyName: String, - @JsonProperty("course_title") val courseTitle: String + @get:JsonProperty("solution") val solution: String, + @get:JsonProperty("given_name") val givenName: String, + @get:JsonProperty("family_name") val familyName: String, + @get:JsonProperty("course_title") val courseTitle: String ) data class RespScore( - @JsonProperty("sub_1") val sub1: String, - @JsonProperty("sub_2") val sub2: String, - @JsonProperty("score_a") val scoreA: Int, - @JsonProperty("score_b") val scoreB: Int + @get:JsonProperty("sub_1") val sub1: String, + @get:JsonProperty("sub_2") val sub2: String, + @get:JsonProperty("score_a") val scoreA: Int, + @get:JsonProperty("score_b") val scoreB: Int ) diff --git a/core/src/main/kotlin/core/ems/service/course/AddTeachersToCourse.kt b/core/src/main/kotlin/core/ems/service/course/AddTeachersToCourse.kt index 3cd92e6e..3a6c30e6 100644 --- a/core/src/main/kotlin/core/ems/service/course/AddTeachersToCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/AddTeachersToCourse.kt @@ -9,15 +9,15 @@ import core.ems.service.access_control.canTeacherAccessCourse import core.ems.service.access_control.teacherOnCourse import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.sql.batchInsert -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.jdbc.batchInsert +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -26,20 +26,20 @@ class AddTeachersToCourse { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("teachers") @field:Valid val teachers: List + @param:JsonProperty("teachers") @field:Valid val teachers: List ) data class TeacherReq( - @JsonProperty("email") @field:NotBlank @field:Size(max = 100) val email: String, - @JsonProperty("groups") @field:Valid val groups: List = emptyList() + @param:JsonProperty("email") @field:NotBlank @field:Size(max = 100) val email: String, + @param:JsonProperty("groups") @field:Valid val groups: List = emptyList() ) data class GroupReq( - @JsonProperty("id") @field:NotBlank @field:Size(max = 100) val groupId: String + @param:JsonProperty("id") @field:NotBlank @field:Size(max = 100) val groupId: String ) data class Resp( - @JsonProperty("accesses_added") val accessesAdded: Int + @get:JsonProperty("accesses_added") val accessesAdded: Int ) private data class TeacherNewAccess(val id: String, val email: String, val groups: Set) diff --git a/core/src/main/kotlin/core/ems/service/course/ArchiveCourse.kt b/core/src/main/kotlin/core/ems/service/course/ArchiveCourse.kt index 6460b40b..8adad1ba 100644 --- a/core/src/main/kotlin/core/ems/service/course/ArchiveCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/ArchiveCourse.kt @@ -8,13 +8,14 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank @RestController @@ -22,7 +23,7 @@ import javax.validation.constraints.NotBlank class ArchiveCourse { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("archived") @field:NotBlank val archivedStr: String) + data class Req(@param:JsonProperty("archived") @field:NotBlank val archivedStr: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/courses/{courseId}/archive") diff --git a/core/src/main/kotlin/core/ems/service/course/CreateCourse.kt b/core/src/main/kotlin/core/ems/service/course/CreateCourse.kt index 2966531d..5a1e75cf 100644 --- a/core/src/main/kotlin/core/ems/service/course/CreateCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/CreateCourse.kt @@ -3,18 +3,18 @@ package core.ems.service.course import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.Course -import mu.KotlinLogging -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -23,12 +23,12 @@ class CreateCourse { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("title") @field:NotBlank @field:Size(max = 100) val title: String, - @JsonProperty("color") @field:NotBlank @field:Size(max = 20) val color: String, - @JsonProperty("course_code") @field:Size(max = 100) val courseCode: String?, + @param:JsonProperty("title") @field:NotBlank @field:Size(max = 100) val title: String, + @param:JsonProperty("color") @field:NotBlank @field:Size(max = 20) val color: String, + @param:JsonProperty("course_code") @field:Size(max = 100) val courseCode: String?, ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_ADMIN") @PostMapping("/admin/courses") diff --git a/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt b/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt index 889d39a7..c5c974f9 100644 --- a/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt @@ -5,10 +5,13 @@ import core.db.* import core.ems.service.access_control.assertAccess import core.ems.service.access_control.studentOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.core.io.ByteArrayResource import org.springframework.http.HttpHeaders.CONTENT_DISPOSITION @@ -54,23 +57,24 @@ class ExportPersonalCourseLatestSubmissions { .map { it[Course.alias] ?: it[Course.title] } .single() - CourseSolution(courseName, (CourseExercise innerJoin Exercise innerJoin ExerciseVer innerJoin Submission) - .select( - CourseExercise.titleAlias, - ExerciseVer.title, - Submission.solution, - Submission.createdAt - ) - .where { CourseExercise.course eq courseId and (Submission.student eq studentId) and (ExerciseVer.validTo.isNull()) } - .orderBy(Submission.createdAt, SortOrder.DESC) - .distinctBy { it[Submission.solution] } - .map { - Solution( - it[CourseExercise.titleAlias] ?: it[ExerciseVer.title], - it[Submission.solution], - it[Submission.createdAt] + CourseSolution( + courseName, (CourseExercise innerJoin Exercise innerJoin ExerciseVer innerJoin Submission) + .select( + CourseExercise.titleAlias, + ExerciseVer.title, + Submission.solution, + Submission.createdAt ) - }) + .where { CourseExercise.course eq courseId and (Submission.student eq studentId) and (ExerciseVer.validTo.isNull()) } + .orderBy(Submission.createdAt, SortOrder.DESC) + .distinctBy { it[Submission.solution] } + .map { + Solution( + it[CourseExercise.titleAlias] ?: it[ExerciseVer.title], + it[Submission.solution], + it[Submission.createdAt] + ) + }) } private fun zip(courseSolution: CourseSolution): ByteArrayResource { diff --git a/core/src/main/kotlin/core/ems/service/course/ReadBasicCourseInfo.kt b/core/src/main/kotlin/core/ems/service/course/ReadBasicCourseInfo.kt index 5120c460..7c91a356 100644 --- a/core/src/main/kotlin/core/ems/service/course/ReadBasicCourseInfo.kt +++ b/core/src/main/kotlin/core/ems/service/course/ReadBasicCourseInfo.kt @@ -6,8 +6,10 @@ import core.db.Course import core.ems.service.access_control.assertAccess import core.ems.service.access_control.userOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -21,11 +23,11 @@ class ReadBasicCourseInfo { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("title") val title: String, - @JsonProperty("alias") val alias: String?, - @JsonProperty("archived") val archived: Boolean, - @JsonProperty("color") val color: String, - @JsonProperty("course_code") val courseCode: String?, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("alias") val alias: String?, + @get:JsonProperty("archived") val archived: Boolean, + @get:JsonProperty("color") val color: String, + @get:JsonProperty("course_code") val courseCode: String?, ) @Secured("ROLE_STUDENT", "ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt b/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt index a4934bc0..319e2ced 100644 --- a/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt @@ -3,7 +3,7 @@ package core.ems.service.course import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.* import core.ems.service.GroupResp @@ -14,10 +14,12 @@ import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectStudentsOnCourse import core.exception.InvalidRequestException import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.andWhere +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* @@ -29,26 +31,26 @@ class ReadParticipantsOnCourseController { private val log = KotlinLogging.logger {} data class TeachersResp( - @JsonProperty("id") val id: String, - @JsonProperty("email") val email: String, - @JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") val familyName: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime? + @get:JsonProperty("id") val id: String, + @get:JsonProperty("email") val email: String, + @get:JsonProperty("given_name") val givenName: String, + @get:JsonProperty("family_name") val familyName: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime? ) data class StudentMoodlePendingResp( - @JsonProperty("moodle_username") val moodleUsername: String, - @JsonProperty("email") val email: String, - @JsonProperty("invite_id") val inviteId: String, - @JsonProperty("groups") val groups: List + @get:JsonProperty("moodle_username") val moodleUsername: String, + @get:JsonProperty("email") val email: String, + @get:JsonProperty("invite_id") val inviteId: String, + @get:JsonProperty("groups") val groups: List ) data class Resp( - @JsonProperty("students") @JsonInclude(Include.NON_NULL) val students: List?, - @JsonProperty("teachers") @JsonInclude(Include.NON_NULL) val teachers: List?, - @JsonProperty("students_moodle_pending") @JsonInclude(Include.NON_NULL) val studentMoodlePendingAccess: List?, - @JsonProperty("moodle_linked") val moodleLinked: Boolean + @get:JsonProperty("students") @get:JsonInclude(Include.NON_NULL) val students: List?, + @get:JsonProperty("teachers") @get:JsonInclude(Include.NON_NULL) val teachers: List?, + @get:JsonProperty("students_moodle_pending") @get:JsonInclude(Include.NON_NULL) val studentMoodlePendingAccess: List?, + @get:JsonProperty("moodle_linked") val moodleLinked: Boolean ) enum class Role(val paramValue: String) { diff --git a/core/src/main/kotlin/core/ems/service/course/ReadStudentCourses.kt b/core/src/main/kotlin/core/ems/service/course/ReadStudentCourses.kt index cd62eebd..1e3d8f83 100644 --- a/core/src/main/kotlin/core/ems/service/course/ReadStudentCourses.kt +++ b/core/src/main/kotlin/core/ems/service/course/ReadStudentCourses.kt @@ -1,13 +1,15 @@ package core.ems.service.course import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.Course import core.db.StudentCourseAccess import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -21,18 +23,18 @@ class ReadStudentCourses { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("courses") val courses: List + @get:JsonProperty("courses") val courses: List ) data class CourseResp( - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonProperty("alias") val alias: String?, - @JsonProperty("course_code") val courseCode: String?, - @JsonProperty("archived") val archived: Boolean, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("last_accessed") val lastAccessed: DateTime, - @JsonProperty("color") val color: String, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("alias") val alias: String?, + @get:JsonProperty("course_code") val courseCode: String?, + @get:JsonProperty("archived") val archived: Boolean, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("last_accessed") val lastAccessed: DateTime, + @get:JsonProperty("color") val color: String, ) @Secured("ROLE_STUDENT") @@ -46,7 +48,13 @@ class ReadStudentCourses { private fun selectCoursesForStudent(studentId: String): Resp = transaction { Resp( (StudentCourseAccess innerJoin Course).select( - Course.id, Course.title, Course.alias, Course.courseCode, Course.archived, Course.color, StudentCourseAccess.lastAccessed + Course.id, + Course.title, + Course.alias, + Course.courseCode, + Course.archived, + Course.color, + StudentCourseAccess.lastAccessed ).where { StudentCourseAccess.student eq studentId }.map { diff --git a/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt b/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt index 89aa5fc6..03b62a29 100644 --- a/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt +++ b/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt @@ -1,22 +1,24 @@ package core.ems.service.course import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.Course import core.db.StudentCourseAccess import core.db.StudentCourseGroup import core.db.TeacherCourseAccess import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.alias -import org.jetbrains.exposed.sql.count -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.alias +import org.jetbrains.exposed.v1.core.count +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import tools.jackson.databind.annotation.JsonSerialize @RestController @@ -24,23 +26,21 @@ import org.springframework.web.bind.annotation.RestController class ReadTeacherCourses { private val log = KotlinLogging.logger {} - data class Resp( - @JsonProperty("courses") val courses: List - ) + data class Resp(@get:JsonProperty("courses") val courses: List) data class CourseResp( - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonProperty("alias") val alias: String?, - @JsonProperty("course_code") val courseCode: String?, - @JsonProperty("archived") val archived: Boolean, - @JsonProperty("student_count") val studentCount: Long, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("last_accessed") val lastAccessed: DateTime, - @JsonProperty("moodle_short_name") val moodleShortName: String?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("last_submission_at") val lastSubmissionAt: DateTime?, - @JsonProperty("color") val color: String, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("alias") val alias: String?, + @get:JsonProperty("course_code") val courseCode: String?, + @get:JsonProperty("archived") val archived: Boolean, + @get:JsonProperty("student_count") val studentCount: Long, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("last_accessed") val lastAccessed: DateTime, + @get:JsonProperty("moodle_short_name") val moodleShortName: String?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("last_submission_at") val lastSubmissionAt: DateTime?, + @get:JsonProperty("color") val color: String, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -64,8 +64,27 @@ class ReadTeacherCourses { val studentCount = StudentCourseAccess.student.count().alias("student_count") (Course leftJoin StudentCourseAccess) - .select(Course.id, Course.title, Course.alias, Course.courseCode, Course.archived, Course.moodleShortName, Course.lastSubmissionAt, Course.color, studentCount) - .groupBy(Course.id, Course.title, Course.alias, Course.courseCode, Course.archived, Course.moodleShortName, Course.lastSubmissionAt, Course.color) + .select( + Course.id, + Course.title, + Course.alias, + Course.courseCode, + Course.archived, + Course.moodleShortName, + Course.lastSubmissionAt, + Course.color, + studentCount + ) + .groupBy( + Course.id, + Course.title, + Course.alias, + Course.courseCode, + Course.archived, + Course.moodleShortName, + Course.lastSubmissionAt, + Course.color + ) .map { CourseResp( it[Course.id].value.toString(), @@ -85,7 +104,17 @@ class ReadTeacherCourses { private fun selectCoursesForTeacher(teacherId: String): List = transaction { // get teacher course accesses with groups (Course innerJoin TeacherCourseAccess) - .select(Course.id, Course.title, Course.alias, Course.courseCode, Course.archived, Course.moodleShortName, Course.lastSubmissionAt, Course.color, TeacherCourseAccess.lastAccessed) + .select( + Course.id, + Course.title, + Course.alias, + Course.courseCode, + Course.archived, + Course.moodleShortName, + Course.lastSubmissionAt, + Course.color, + TeacherCourseAccess.lastAccessed + ) .where { TeacherCourseAccess.teacher eq teacherId } .map { // Get student count for each course diff --git a/core/src/main/kotlin/core/ems/service/course/RemoveStudentsFromCourse.kt b/core/src/main/kotlin/core/ems/service/course/RemoveStudentsFromCourse.kt index 14688d23..9ce5055d 100644 --- a/core/src/main/kotlin/core/ems/service/course/RemoveStudentsFromCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/RemoveStudentsFromCourse.kt @@ -7,17 +7,17 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.cache.CachingService import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @RequestMapping("/v2") @@ -25,17 +25,17 @@ class RemoveStudentsFromCourseController(val cachingService: CachingService) { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("active_students") @field:Valid + @param:JsonProperty("active_students") @field:Valid val activeStudents: List = emptyList(), ) data class ActiveStudentReq( - @JsonProperty("id") @field:NotBlank @field:Size(max = 100) + @param:JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String, ) data class Resp( - @JsonProperty("removed_active_count") + @get:JsonProperty("removed_active_count") val removedActiveCount: Int, ) diff --git a/core/src/main/kotlin/core/ems/service/course/RemoveTeachersFromCourse.kt b/core/src/main/kotlin/core/ems/service/course/RemoveTeachersFromCourse.kt index c6a293c0..9e09b00c 100644 --- a/core/src/main/kotlin/core/ems/service/course/RemoveTeachersFromCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/RemoveTeachersFromCourse.kt @@ -8,17 +8,17 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.teacherExists import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @RequestMapping("/v2") @@ -26,11 +26,11 @@ class RemoveTeachersFromCourseController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("teachers") @field:Valid val teachers: List + @param:JsonProperty("teachers") @field:Valid val teachers: List ) data class TeacherIdReq( - @JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String + @param:JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -52,29 +52,27 @@ class RemoveTeachersFromCourseController { deleteTeachersFromCourse(teachers, courseId) } - private fun deleteTeachersFromCourse(teachers: Req, courseId: Long) { - transaction { - teachers.teachers.forEach { teacher -> - if (!teacherExists(teacher.id)) { - throw InvalidRequestException("Teacher not found: $teacher") - } + private fun deleteTeachersFromCourse(teachers: Req, courseId: Long) = transaction { + teachers.teachers.forEach { teacher -> + if (!teacherExists(teacher.id)) { + throw InvalidRequestException("Teacher not found: $teacher") } + } - val teachersWithAccess = teachers.teachers.filter { - TeacherCourseAccess.selectAll() - .where { TeacherCourseAccess.teacher eq it.id and (TeacherCourseAccess.course eq courseId) } - .count() > 0 - } + val teachersWithAccess = teachers.teachers.filter { + TeacherCourseAccess.selectAll() + .where { TeacherCourseAccess.teacher eq it.id and (TeacherCourseAccess.course eq courseId) } + .count() > 0 + } - teachersWithAccess.forEach { teacher -> - TeacherCourseAccess.deleteWhere { - TeacherCourseAccess.teacher eq teacher.id and (TeacherCourseAccess.course eq courseId) - } + teachersWithAccess.forEach { teacher -> + TeacherCourseAccess.deleteWhere { + TeacherCourseAccess.teacher eq teacher.id and (TeacherCourseAccess.course eq courseId) } + } - log.info { "Removing access from teachers (the rest already have no access): $teachersWithAccess" } + log.info { "Removing access from teachers (the rest already have no access): $teachersWithAccess" } - } } } \ No newline at end of file diff --git a/core/src/main/kotlin/core/ems/service/course/SendCourseInvites.kt b/core/src/main/kotlin/core/ems/service/course/SendCourseInvites.kt index 3d072084..f78754b1 100644 --- a/core/src/main/kotlin/core/ems/service/course/SendCourseInvites.kt +++ b/core/src/main/kotlin/core/ems/service/course/SendCourseInvites.kt @@ -2,20 +2,24 @@ package core.ems.service.course import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser -import core.db.* +import core.db.Account +import core.db.Course +import core.db.StudentCourseAccess +import core.db.StudentMoodlePendingAccess import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -23,10 +27,10 @@ import javax.validation.constraints.Size class SendCourseInvites(val sendMailService: SendMailService) { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("emails") @field:Valid val students: List) + data class Req(@param:JsonProperty("emails") @field:Valid val students: List) data class StudentEmailReq( - @JsonProperty("email") @field:NotBlank @field:Size(max = 100) val email: String, + @param:JsonProperty("email") @field:NotBlank @field:Size(max = 100) val email: String, ) diff --git a/core/src/main/kotlin/core/ems/service/course/SetStudentLastAccess.kt b/core/src/main/kotlin/core/ems/service/course/SetStudentLastAccess.kt index 2ad803e4..750f38ba 100644 --- a/core/src/main/kotlin/core/ems/service/course/SetStudentLastAccess.kt +++ b/core/src/main/kotlin/core/ems/service/course/SetStudentLastAccess.kt @@ -5,10 +5,11 @@ import core.db.StudentCourseAccess import core.ems.service.access_control.assertAccess import core.ems.service.access_control.userOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/course/SetTeacherLastAccess.kt b/core/src/main/kotlin/core/ems/service/course/SetTeacherLastAccess.kt index e140e95d..5fc0237a 100644 --- a/core/src/main/kotlin/core/ems/service/course/SetTeacherLastAccess.kt +++ b/core/src/main/kotlin/core/ems/service/course/SetTeacherLastAccess.kt @@ -5,10 +5,11 @@ import core.db.TeacherCourseAccess import core.ems.service.access_control.assertAccess import core.ems.service.access_control.userOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/course/TeacherReadGrades.kt b/core/src/main/kotlin/core/ems/service/course/TeacherReadGrades.kt index 7cfb2b29..08ec4277 100644 --- a/core/src/main/kotlin/core/ems/service/course/TeacherReadGrades.kt +++ b/core/src/main/kotlin/core/ems/service/course/TeacherReadGrades.kt @@ -6,7 +6,7 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectAllCourseExercisesLatestSubmissions -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* diff --git a/core/src/main/kotlin/core/ems/service/course/UpdateCourse.kt b/core/src/main/kotlin/core/ems/service/course/UpdateCourse.kt index acfb1ae3..217d5123 100644 --- a/core/src/main/kotlin/core/ems/service/course/UpdateCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/UpdateCourse.kt @@ -6,14 +6,15 @@ import core.db.Course import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -22,10 +23,10 @@ class UpdateCourse { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("title") @field:NotBlank @field:Size(max = 100) val title: String, - @JsonProperty("alias") @field:Size(max = 100) val alias: String?, - @JsonProperty("color") @field:NotBlank @field:Size(max = 20) val color: String, - @JsonProperty("course_code") @field:Size(max = 100) val courseCode: String?, + @param:JsonProperty("title") @field:NotBlank @field:Size(max = 100) val title: String, + @param:JsonProperty("alias") @field:Size(max = 100) val alias: String?, + @param:JsonProperty("color") @field:NotBlank @field:Size(max = 20) val color: String, + @param:JsonProperty("course_code") @field:Size(max = 100) val courseCode: String?, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/course/group/AddStudentsToCourseGroup.kt b/core/src/main/kotlin/core/ems/service/course/group/AddStudentsToCourseGroup.kt index 4d4f12ba..5c653d16 100644 --- a/core/src/main/kotlin/core/ems/service/course/group/AddStudentsToCourseGroup.kt +++ b/core/src/main/kotlin/core/ems/service/course/group/AddStudentsToCourseGroup.kt @@ -2,7 +2,9 @@ package core.ems.service.course.group import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser -import core.db.* +import core.db.StudentCourseGroup +import core.db.StudentMoodlePendingAccess +import core.db.StudentMoodlePendingCourseGroup import core.ems.service.access_control.assertAccess import core.ems.service.access_control.canStudentAccessCourse import core.ems.service.access_control.teacherOnCourse @@ -10,16 +12,17 @@ import core.ems.service.assertGroupExistsOnCourse import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.batchInsert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.batchInsert +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -28,16 +31,16 @@ class AddStudentsToCourseGroupController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("active_students") @field:Valid val activeStudents: List = emptyList(), - @JsonProperty("moodle_pending_students") @field:Valid val moodlePendingStudents: List = emptyList(), + @param:JsonProperty("active_students") @field:Valid val activeStudents: List = emptyList(), + @param:JsonProperty("moodle_pending_students") @field:Valid val moodlePendingStudents: List = emptyList(), ) data class ActiveStudentReq( - @JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String, + @param:JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String, ) data class MoodlePendingStudentReq( - @JsonProperty("moodle_username") @field:NotBlank @field:Size(max = 100) val moodleUsername: String, + @param:JsonProperty("moodle_username") @field:NotBlank @field:Size(max = 100) val moodleUsername: String, ) diff --git a/core/src/main/kotlin/core/ems/service/course/group/CreateCourseGroup.kt b/core/src/main/kotlin/core/ems/service/course/group/CreateCourseGroup.kt index 4bb6a804..3d34ac6e 100644 --- a/core/src/main/kotlin/core/ems/service/course/group/CreateCourseGroup.kt +++ b/core/src/main/kotlin/core/ems/service/course/group/CreateCourseGroup.kt @@ -7,15 +7,15 @@ import core.db.CourseGroup import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -24,13 +24,13 @@ class CreateCourseGroupController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("name", required = true) + @param:JsonProperty("name", required = true) @field:NotBlank @field:Size(max = 200) val name: String ) data class Resp( - @JsonProperty("id") val id: String + @get:JsonProperty("id") val id: String ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/course/group/DeleteCourseGroup.kt b/core/src/main/kotlin/core/ems/service/course/group/DeleteCourseGroup.kt index e10e6fda..1d893070 100644 --- a/core/src/main/kotlin/core/ems/service/course/group/DeleteCourseGroup.kt +++ b/core/src/main/kotlin/core/ems/service/course/group/DeleteCourseGroup.kt @@ -1,20 +1,23 @@ package core.ems.service.course.group import core.conf.security.EasyUser -import core.db.* +import core.db.Course +import core.db.CourseGroup +import core.db.StudentCourseGroup +import core.db.StudentMoodlePendingCourseGroup import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.assertGroupExistsOnCourse import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/course/group/ReadCourseGroups.kt b/core/src/main/kotlin/core/ems/service/course/group/ReadCourseGroups.kt index 8f947e85..f593e597 100644 --- a/core/src/main/kotlin/core/ems/service/course/group/ReadCourseGroups.kt +++ b/core/src/main/kotlin/core/ems/service/course/group/ReadCourseGroups.kt @@ -6,9 +6,10 @@ import core.db.CourseGroup import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -21,10 +22,10 @@ import org.springframework.web.bind.annotation.RestController class ReadCourseGroupsController { private val log = KotlinLogging.logger {} - data class Resp(@JsonProperty("groups") val groups: List) + data class Resp(@get:JsonProperty("groups") val groups: List) data class GroupResp( - @JsonProperty("id") val id: String, @JsonProperty("name") val name: String + @get:JsonProperty("id") val id: String, @get:JsonProperty("name") val name: String ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/course/group/RemoveStudentsFromCourseGroup.kt b/core/src/main/kotlin/core/ems/service/course/group/RemoveStudentsFromCourseGroup.kt index 59b4ab71..dcb35d4e 100644 --- a/core/src/main/kotlin/core/ems/service/course/group/RemoveStudentsFromCourseGroup.kt +++ b/core/src/main/kotlin/core/ems/service/course/group/RemoveStudentsFromCourseGroup.kt @@ -8,17 +8,18 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.assertGroupExistsOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction + import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @RequestMapping("/v2") @@ -26,20 +27,20 @@ class RemoveStudentsFromCourseGroupController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("active_students") @field:Valid val activeStudents: List = emptyList(), - @JsonProperty("moodle_pending_students") @field:Valid val moodlePendingStudents: List = emptyList(), + @param:JsonProperty("active_students") @field:Valid val activeStudents: List = emptyList(), + @param:JsonProperty("moodle_pending_students") @field:Valid val moodlePendingStudents: List = emptyList(), ) data class ActiveStudentReq( - @JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String, + @param:JsonProperty("id") @field:NotBlank @field:Size(max = 100) val id: String, ) data class MoodlePendingStudentReq( - @JsonProperty("moodle_username") @field:NotBlank @field:Size(max = 100) val moodleUsername: String, + @param:JsonProperty("moodle_username") @field:NotBlank @field:Size(max = 100) val moodleUsername: String, ) data class Resp( - @JsonProperty("deleted_count") val deletedCount: Int + @get:JsonProperty("deleted_count") val deletedCount: Int ) diff --git a/core/src/main/kotlin/core/ems/service/course/invite/DeleteCourseInvite.kt b/core/src/main/kotlin/core/ems/service/course/invite/DeleteCourseInvite.kt index 5cb9fb44..2871c886 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/DeleteCourseInvite.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/DeleteCourseInvite.kt @@ -5,10 +5,10 @@ import core.db.CourseInviteLink import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/course/invite/GenerateCourseInvite.kt b/core/src/main/kotlin/core/ems/service/course/invite/GenerateCourseInvite.kt index eb238bd0..98758abc 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/GenerateCourseInvite.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/GenerateCourseInvite.kt @@ -1,25 +1,27 @@ package core.ems.service.course.invite import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize import core.conf.security.EasyUser import core.db.Course import core.db.CourseInviteLink -import core.db.insertOrUpdate import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.generateInviteId import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.util.DateTimeDeserializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.upsert import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Max -import javax.validation.constraints.Min +import tools.jackson.databind.annotation.JsonDeserialize @RestController @@ -27,11 +29,11 @@ import javax.validation.constraints.Min class GenerateCourseInvite { private val log = KotlinLogging.logger {} - data class Resp(@JsonProperty("invite_id") val inviteId: String) + data class Resp(@get:JsonProperty("invite_id") val inviteId: String) data class Req( - @JsonDeserialize(using = DateTimeDeserializer::class) @JsonProperty("expires_at") val expiresAt: DateTime, - @JsonProperty("allowed_uses") @field:Min(0) @field:Max(1000000) val allowedUses: Int + @param:JsonDeserialize(using = DateTimeDeserializer::class) @param:JsonProperty("expires_at") val expiresAt: DateTime, + @param:JsonProperty("allowed_uses") @field:Min(0) @field:Max(1000000) val allowedUses: Int ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -86,7 +88,10 @@ class GenerateCourseInvite { 0 ) - CourseInviteLink.insertOrUpdate(listOf(CourseInviteLink.course), listOf(CourseInviteLink.course)) { + CourseInviteLink.upsert( + CourseInviteLink.course, + onUpdateExclude = listOf(CourseInviteLink.course) + ) { it[course] = courseId it[createdAt] = d.createdAt it[expiresAt] = req.expiresAt @@ -94,6 +99,7 @@ class GenerateCourseInvite { it[inviteId] = d.inviteId it[usedCount] = d.usedCount } + log.debug { "Invite '${d.inviteId}' created for course $courseId with expiry date of ${req.expiresAt} and ${req.allowedUses} uses." } d.inviteId diff --git a/core/src/main/kotlin/core/ems/service/course/invite/GetCourseInfoByInvite.kt b/core/src/main/kotlin/core/ems/service/course/invite/GetCourseInfoByInvite.kt index c7f3109d..650f2e73 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/GetCourseInfoByInvite.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/GetCourseInfoByInvite.kt @@ -4,11 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.Course import core.db.CourseInviteLink -import core.ems.service.singleOrInvalidRequest import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -23,8 +23,8 @@ class GetCourseInfoByInvite { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("course_id") val courseId: String, - @JsonProperty("course_title") val courseTitle: String, + @get:JsonProperty("course_id") val courseId: String, + @get:JsonProperty("course_title") val courseTitle: String, ) @Secured("ROLE_STUDENT") @@ -50,7 +50,7 @@ class GetCourseInfoByInvite { Resp( row[Course.id].value.toString(), - row[Course.alias] ?: row[Course.title].toString() + row[Course.alias] ?: row[Course.title] ) } } diff --git a/core/src/main/kotlin/core/ems/service/course/invite/JoinCourseByInvite.kt b/core/src/main/kotlin/core/ems/service/course/invite/JoinCourseByInvite.kt index 9d6a00e1..aef5fd72 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/JoinCourseByInvite.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/JoinCourseByInvite.kt @@ -5,11 +5,13 @@ import core.conf.security.EasyUser import core.db.Course import core.db.CourseInviteLink import core.db.StudentCourseAccess -import core.ems.service.singleOrInvalidRequest import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.insertIgnore +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable @@ -24,7 +26,7 @@ class JoinCourseByInvite { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("course_id") val courseId: String + @get:JsonProperty("course_id") val courseId: String ) @Secured("ROLE_STUDENT") @@ -60,9 +62,7 @@ class JoinCourseByInvite { if (accessesAdded > 0) { CourseInviteLink.update({ CourseInviteLink.course eq courseId }) { - with(SqlExpressionBuilder) { - it.update(usedCount, usedCount + 1) - } + it.update(usedCount, usedCount + 1) } } diff --git a/core/src/main/kotlin/core/ems/service/course/invite/ReadCourseInviteDetails.kt b/core/src/main/kotlin/core/ems/service/course/invite/ReadCourseInviteDetails.kt index d16fbd66..a1ab0d6e 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/ReadCourseInviteDetails.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/ReadCourseInviteDetails.kt @@ -1,15 +1,17 @@ package core.ems.service.course.invite import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.CourseInviteLink import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -25,12 +27,12 @@ class ReadCourseInviteDetails { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("invite_id") val inviteId: String, - @JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("expires_at") val expiry: DateTime, - @JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("allowed_uses") val allowedUses: Int, - @JsonProperty("used_count") val usedCount: Int, - @JsonProperty("uses_remaining") val usesRemaining: Int + @get:JsonProperty("invite_id") val inviteId: String, + @get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("expires_at") val expiry: DateTime, + @get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("allowed_uses") val allowedUses: Int, + @get:JsonProperty("used_count") val usedCount: Int, + @get:JsonProperty("uses_remaining") val usesRemaining: Int ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/course/invite/moodle/GetMoodleLinkedCourseInfoByInvite.kt b/core/src/main/kotlin/core/ems/service/course/invite/moodle/GetMoodleLinkedCourseInfoByInvite.kt index 0783b2f8..b12ce8d8 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/moodle/GetMoodleLinkedCourseInfoByInvite.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/moodle/GetMoodleLinkedCourseInfoByInvite.kt @@ -5,8 +5,10 @@ import core.conf.security.EasyUser import core.db.Course import core.db.StudentMoodlePendingAccess import core.ems.service.singleOrInvalidRequest -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -20,8 +22,8 @@ class GetMoodleLinkedCourseInfoByInvite { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("course_id") val courseId: String, - @JsonProperty("course_title") val courseTitle: String, + @get:JsonProperty("course_id") val courseId: String, + @get:JsonProperty("course_title") val courseTitle: String, ) @Secured("ROLE_STUDENT") @@ -40,7 +42,7 @@ class GetMoodleLinkedCourseInfoByInvite { }.map { Resp( it[Course.id].value.toString(), - it[Course.alias] ?: it[Course.title].toString() + it[Course.alias] ?: it[Course.title] ) } .singleOrInvalidRequest(false) diff --git a/core/src/main/kotlin/core/ems/service/course/invite/moodle/JoinMoodleLinkedCourseByInvite.kt b/core/src/main/kotlin/core/ems/service/course/invite/moodle/JoinMoodleLinkedCourseByInvite.kt index 4e52fdd0..da7e2b19 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/moodle/JoinMoodleLinkedCourseByInvite.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/moodle/JoinMoodleLinkedCourseByInvite.kt @@ -4,13 +4,14 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.* import core.ems.service.singleOrInvalidRequest -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.insertIgnore -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.insertIgnore +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable @@ -24,9 +25,7 @@ import org.springframework.web.bind.annotation.RestController class JoinMoodleLinkedCourseByInvite { private val log = KotlinLogging.logger {} - data class Resp( - @JsonProperty("course_id") val courseId: String - ) + data class Resp(@get:JsonProperty("course_id") val courseId: String) @Secured("ROLE_STUDENT") @PostMapping("/courses/moodle/join/{inviteId}") diff --git a/core/src/main/kotlin/core/ems/service/course/invite/moodle/SendMoodleCourseInvites.kt b/core/src/main/kotlin/core/ems/service/course/invite/moodle/SendMoodleCourseInvites.kt index 3d5d49d8..9dd43dec 100644 --- a/core/src/main/kotlin/core/ems/service/course/invite/moodle/SendMoodleCourseInvites.kt +++ b/core/src/main/kotlin/core/ems/service/course/invite/moodle/SendMoodleCourseInvites.kt @@ -10,12 +10,15 @@ import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid @RestController @@ -23,7 +26,7 @@ import javax.validation.Valid class SendMoodleCourseInvites(val mailService: SendMailService) { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("students") val students: List) + data class Req(@param:JsonProperty("students") val students: List) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/courses/moodle/{courseId}/students/invite") diff --git a/core/src/main/kotlin/core/ems/service/course_groups.kt b/core/src/main/kotlin/core/ems/service/course_groups.kt index d5f44ac4..4dcb832b 100644 --- a/core/src/main/kotlin/core/ems/service/course_groups.kt +++ b/core/src/main/kotlin/core/ems/service/course_groups.kt @@ -2,9 +2,10 @@ package core.ems.service import core.db.CourseGroup import core.exception.InvalidRequestException -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction fun assertGroupExistsOnCourse(groupId: Long, courseId: Long) { diff --git a/core/src/main/kotlin/core/ems/service/courses.kt b/core/src/main/kotlin/core/ems/service/courses.kt index 50f9bb06..bcaf2baa 100644 --- a/core/src/main/kotlin/core/ems/service/courses.kt +++ b/core/src/main/kotlin/core/ems/service/courses.kt @@ -1,20 +1,20 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.db.* import core.ems.service.exercise.getStudentExerciseStatus import core.exception.InvalidRequestException import core.exception.ReqError import core.util.DateTimeSerializer import core.util.notNullAndInPast -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.andWhere +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import java.io.Serializable import java.security.SecureRandom @@ -22,36 +22,36 @@ import java.security.SecureRandom private val log = KotlinLogging.logger {} data class SubmissionRow( - @JsonProperty("submission") val latestSubmission: LatestSubmissionResp?, - @JsonProperty("status") val status: StudentExerciseStatus, - @JsonProperty("student_id") val accountId: String, - @JsonProperty("given_name") val accountGivenName: String, - @JsonProperty("family_name") val accountFamilyName: String, - @JsonProperty("groups") val groups: List + @get:JsonProperty("submission") val latestSubmission: LatestSubmissionResp?, + @get:JsonProperty("status") val status: StudentExerciseStatus, + @get:JsonProperty("student_id") val accountId: String, + @get:JsonProperty("given_name") val accountGivenName: String, + @get:JsonProperty("family_name") val accountFamilyName: String, + @get:JsonProperty("groups") val groups: List ) : Serializable data class LatestSubmissionResp( - @JsonProperty("id") val submissionId: String, - @JsonProperty("submission_number") val submissionNumber: Int, - @JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("time") val time: DateTime, - @JsonProperty("grade") val grade: GradeResp?, - @JsonProperty("seen") val seen: Boolean, + @get:JsonProperty("id") val submissionId: String, + @get:JsonProperty("submission_number") val submissionNumber: Int, + @get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("time") val time: DateTime, + @get:JsonProperty("grade") val grade: GradeResp?, + @get:JsonProperty("seen") val seen: Boolean, ) data class GroupResp( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String ) data class StudentsResp( - @JsonProperty("id") val id: String, - @JsonProperty("email") val email: String, - @JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") val familyName: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime?, - @JsonProperty("groups") val groups: List, - @JsonProperty("moodle_username") val moodleUsername: String?, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("email") val email: String, + @get:JsonProperty("given_name") val givenName: String, + @get:JsonProperty("family_name") val familyName: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime?, + @get:JsonProperty("groups") val groups: List, + @get:JsonProperty("moodle_username") val moodleUsername: String?, ) data class CourseDTO( @@ -150,26 +150,26 @@ fun toGradeRespOrNull(grade: Int?, isAuto: Boolean?, isGradedDirectly: Boolean?) data class ExercisesResp( - @JsonProperty("course_exercise_id") val courseExerciseId: String, - @JsonProperty("exercise_id") val exerciseId: String, - @JsonProperty("library_title") val libraryTitle: String, - @JsonProperty("title_alias") val titleAlias: String?, - @JsonProperty("effective_title") val effectiveTitle: String, - @JsonProperty("grade_threshold") val gradeThreshold: Int, - @JsonProperty("student_visible") val studentVisible: Boolean, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("student_visible_from") val studentVisibleFrom: DateTime?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("soft_deadline") val softDeadline: DateTime?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("hard_deadline") val hardDeadline: DateTime?, - @JsonProperty("grader_type") val graderType: GraderType, - @JsonProperty("ordering_idx") val orderingIndex: Int, - @JsonProperty("unstarted_count") val unstartedCount: Int, - @JsonProperty("ungraded_count") val ungradedCount: Int, - @JsonProperty("started_count") val startedCount: Int, - @JsonProperty("completed_count") val completedCount: Int, - @JsonProperty("latest_submissions") val latestSubmissions: List, + @get:JsonProperty("course_exercise_id") val courseExerciseId: String, + @get:JsonProperty("exercise_id") val exerciseId: String, + @get:JsonProperty("library_title") val libraryTitle: String, + @get:JsonProperty("title_alias") val titleAlias: String?, + @get:JsonProperty("effective_title") val effectiveTitle: String, + @get:JsonProperty("grade_threshold") val gradeThreshold: Int, + @get:JsonProperty("student_visible") val studentVisible: Boolean, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("student_visible_from") val studentVisibleFrom: DateTime?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("soft_deadline") val softDeadline: DateTime?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("hard_deadline") val hardDeadline: DateTime?, + @get:JsonProperty("grader_type") val graderType: GraderType, + @get:JsonProperty("ordering_idx") val orderingIndex: Int, + @get:JsonProperty("unstarted_count") val unstartedCount: Int, + @get:JsonProperty("ungraded_count") val ungradedCount: Int, + @get:JsonProperty("started_count") val startedCount: Int, + @get:JsonProperty("completed_count") val completedCount: Int, + @get:JsonProperty("latest_submissions") val latestSubmissions: List, ) @@ -523,7 +523,8 @@ private fun CourseExerciseException.extractGroups(courseExId: Long): List?.farthestValueInFutureOrNull(): DateTime? = this?.maxByOrNull { it.value ?: DateTime(0) }?.value +private fun List?.farthestValueInFutureOrNull(): DateTime? = + this?.maxByOrNull { it.value ?: DateTime(0) }?.value /** * Determines the soft deadline for a specific course exercise, prioritizing student and group exceptions @@ -543,7 +544,8 @@ fun determineSoftDeadline( defaultSoftDeadline: DateTime? ): DateTime? { val studentException: ExceptionValue? = exceptions.extractStudentException(courseExId, studentId)?.softDeadline - val groupException: List? = exceptions.extractGroups(courseExId)?.mapNotNull { it.softDeadline }?.ifEmpty { null } + val groupException: List? = + exceptions.extractGroups(courseExId)?.mapNotNull { it.softDeadline }?.ifEmpty { null } return when { studentException != null -> studentException.value diff --git a/core/src/main/kotlin/core/ems/service/dirs.kt b/core/src/main/kotlin/core/ems/service/dirs.kt index 3348d8e2..cd92bcb3 100644 --- a/core/src/main/kotlin/core/ems/service/dirs.kt +++ b/core/src/main/kotlin/core/ems/service/dirs.kt @@ -7,10 +7,12 @@ import core.exception.ReqError import core.util.component1 import core.util.component2 import core.util.maxOfOrNull -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNotNull +import org.jetbrains.exposed.v1.jdbc.* +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime private val log = KotlinLogging.logger {} @@ -286,9 +288,9 @@ fun getAccountDirectDirAccessLevel(userId: String, dirId: Long): DirAccessLevel? } fun upsertGroupDirAccess(groupId: Long, dirId: Long, level: DirAccessLevel) = transaction { - GroupDirAccess.insertOrUpdate( - listOf(GroupDirAccess.group, GroupDirAccess.dir), - listOf(GroupDirAccess.group, GroupDirAccess.dir) + GroupDirAccess.upsert( + GroupDirAccess.group, GroupDirAccess.dir, + onUpdateExclude = listOf(GroupDirAccess.group, GroupDirAccess.dir) ) { it[group] = groupId it[dir] = dirId diff --git a/core/src/main/kotlin/core/ems/service/exercise/AddExerciseToCourse.kt b/core/src/main/kotlin/core/ems/service/exercise/AddExerciseToCourse.kt index fb0bc457..2e406219 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/AddExerciseToCourse.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/AddExerciseToCourse.kt @@ -1,7 +1,6 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize import core.conf.security.EasyUser import core.db.* import core.ems.service.AdocService @@ -14,20 +13,25 @@ import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError import core.util.DateTimeDeserializer -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.max -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.Size +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.core.max +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Max -import javax.validation.constraints.Min -import javax.validation.constraints.Size +import tools.jackson.databind.annotation.JsonDeserialize + @RestController @RequestMapping("/v2") @@ -35,27 +39,27 @@ class AddExerciseToCourseCont(private val adocService: AdocService) { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("exercise_id") @field:Size(max = 100) + @param:JsonProperty("exercise_id") @field:Size(max = 100) val exerciseId: String, - @JsonProperty("threshold") @field:Min(0) @field:Max(100) + @param:JsonProperty("threshold") @field:Min(0) @field:Max(100) val threshold: Int, - @JsonProperty("student_visible") + @param:JsonProperty("student_visible") val isStudentVisible: Boolean, - @JsonDeserialize(using = DateTimeDeserializer::class) - @JsonProperty("soft_deadline") + @param:JsonDeserialize(using = DateTimeDeserializer::class) + @param:JsonProperty("soft_deadline") val softDeadline: DateTime?, - @JsonDeserialize(using = DateTimeDeserializer::class) - @JsonProperty("hard_deadline") + @param:JsonDeserialize(using = DateTimeDeserializer::class) + @param:JsonProperty("hard_deadline") val hardDeadline: DateTime?, - @JsonProperty("assessments_student_visible") + @param:JsonProperty("assessments_student_visible") val assStudentVisible: Boolean, - @JsonProperty("instructions_adoc") @field:Size(max = 300000) + @param:JsonProperty("instructions_adoc") @field:Size(max = 300000) val instructionsAdoc: String?, - @JsonProperty("title_alias") @field:Size(max = 100) + @param:JsonProperty("title_alias") @field:Size(max = 100) val titleAlias: String? ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/teacher/courses/{courseId}/exercises") diff --git a/core/src/main/kotlin/core/ems/service/exercise/AnonymousReadExerciseDetails.kt b/core/src/main/kotlin/core/ems/service/exercise/AnonymousReadExerciseDetails.kt index e8288bc3..6c2c334a 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/AnonymousReadExerciseDetails.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/AnonymousReadExerciseDetails.kt @@ -8,9 +8,12 @@ import core.ems.service.access_control.assertExerciseHasTextEditorSubmission import core.ems.service.access_control.assertUnauthAccessToExercise import core.ems.service.idToLongOrInvalidReq import core.ems.service.singleOrInvalidRequest -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping @@ -22,10 +25,10 @@ class AnonymousReadExerciseDetails { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("title") val title: String, - @JsonProperty("text_html") val textHtml: String?, - @JsonProperty("anonymous_autoassess_template") val anonymousAutoassessTemplate: String?, - @JsonProperty("submit_allowed") val allowSubmit: Boolean, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("text_html") val textHtml: String?, + @get:JsonProperty("anonymous_autoassess_template") val anonymousAutoassessTemplate: String?, + @get:JsonProperty("submit_allowed") val allowSubmit: Boolean, ) @GetMapping("/unauth/exercises/{exerciseId}/anonymous/details") diff --git a/core/src/main/kotlin/core/ems/service/exercise/AnonymousSubmit.kt b/core/src/main/kotlin/core/ems/service/exercise/AnonymousSubmit.kt index 9d7ca625..4c9c22ce 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/AnonymousSubmit.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/AnonymousSubmit.kt @@ -7,19 +7,21 @@ import core.ems.service.access_control.assertExerciseHasTextEditorSubmission import core.ems.service.access_control.assertUnauthAccessToExercise import core.ems.service.assertExerciseIsAutoGradable import core.ems.service.idToLongOrInvalidReq +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.Size import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.less -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.beans.factory.annotation.Value import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size import kotlin.coroutines.EmptyCoroutineContext @RestController @@ -32,12 +34,12 @@ class AnonymousSubmitCont(private val autoGradeScheduler: AutoGradeScheduler) { // TODO should be required = true? data class Req( - @JsonProperty("solution") @field:Size(max = 300000) val solution: String + @param:JsonProperty("solution") @field:Size(max = 300000) val solution: String ) data class Resp( - @JsonProperty("grade") val grade: Int, - @JsonProperty("feedback") val feedback: String + @get:JsonProperty("grade") val grade: Int, + @get:JsonProperty("feedback") val feedback: String ) diff --git a/core/src/main/kotlin/core/ems/service/exercise/CompileTSL.kt b/core/src/main/kotlin/core/ems/service/exercise/CompileTSL.kt index a90484d7..72a3f1d8 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/CompileTSL.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/CompileTSL.kt @@ -3,18 +3,18 @@ package core.ems.service.exercise import com.example.demo.TSLSpecFormat import com.example.demo.compileTSL import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.util.DateTimeSerializer -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.Size import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.Size @RestController @RequestMapping("/v2") @@ -22,27 +22,27 @@ class CompileTSL { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("tsl_spec") @field:Size(max = 100_000) val tslSpec: String, - @JsonProperty("format") val format: TSLSpecFormat = TSLSpecFormat.JSON, + @param:JsonProperty("tsl_spec") @field:Size(max = 100_000) val tslSpec: String, + @param:JsonProperty("format") val format: TSLSpecFormat = TSLSpecFormat.JSON, ) data class Resp( - @JsonProperty("scripts") val scripts: List?, - @JsonProperty("feedback") val feedback: String?, - @JsonProperty("meta") val meta: MetaResp?, + @get:JsonProperty("scripts") val scripts: List?, + @get:JsonProperty("feedback") val feedback: String?, + @get:JsonProperty("meta") val meta: MetaResp?, ) data class ScriptResp( - @JsonProperty("name") val name: String, - @JsonProperty("value") val value: String, + @get:JsonProperty("name") val name: String, + @get:JsonProperty("value") val value: String, ) data class MetaResp( - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("timestamp") val timestamp: DateTime, - @JsonProperty("compiler_version") val compilerVersion: String, - @JsonProperty("backend_id") val backendId: String, - @JsonProperty("backend_version") val backendVersion: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("timestamp") val timestamp: DateTime, + @get:JsonProperty("compiler_version") val compilerVersion: String, + @get:JsonProperty("backend_id") val backendId: String, + @get:JsonProperty("backend_version") val backendVersion: String, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/CreateExercise.kt b/core/src/main/kotlin/core/ems/service/exercise/CreateExercise.kt index caf4065b..0178668e 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/CreateExercise.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/CreateExercise.kt @@ -10,21 +10,24 @@ import core.ems.service.access_control.libraryDir import core.ems.service.getImplicitGroupFromAccount import core.ems.service.idToLongOrInvalidReq import core.ems.service.upsertGroupDirAccess -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -33,28 +36,28 @@ class CreateExercise(private val adocService: AdocService) { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("parent_dir_id", required = false) @field:Size(max = 100) val parentDirIdStr: String?, - @JsonProperty("title", required = true) @field:NotBlank @field:Size(max = 100) val title: String, - @JsonProperty("text_html", required = false) @field:Size(max = 300000) val textHtml: String?, - @JsonProperty("text_adoc", required = false) @field:Size(max = 300000) val textAdoc: String?, - @JsonProperty("public", required = true) val public: Boolean, - @JsonProperty("anonymous_autoassess_enabled", required = true) val anonymousAutoassessEnabled: Boolean, - @JsonProperty("grader_type", required = true) val graderType: GraderType, - @JsonProperty("solution_file_name", required = true) val solutionFileName: String, - @JsonProperty("solution_file_type", required = true) val solutionFileType: SolutionFileType, - @JsonProperty("grading_script", required = false) val gradingScript: String?, - @JsonProperty("container_image", required = false) @field:Size(max = 2000) val containerImage: String?, - @JsonProperty("max_time_sec", required = false) val maxTime: Int?, - @JsonProperty("max_mem_mb", required = false) val maxMem: Int?, - @JsonProperty("assets", required = false) val assets: List?, + @param:JsonProperty("parent_dir_id", required = false) @field:Size(max = 100) val parentDirIdStr: String?, + @param:JsonProperty("title", required = true) @field:NotBlank @field:Size(max = 100) val title: String, + @param:JsonProperty("text_html", required = false) @field:Size(max = 300000) val textHtml: String?, + @param:JsonProperty("text_adoc", required = false) @field:Size(max = 300000) val textAdoc: String?, + @param:JsonProperty("public", required = true) val public: Boolean, + @param:JsonProperty("anonymous_autoassess_enabled", required = true) val anonymousAutoassessEnabled: Boolean, + @param:JsonProperty("grader_type", required = true) val graderType: GraderType, + @param:JsonProperty("solution_file_name", required = true) val solutionFileName: String, + @param:JsonProperty("solution_file_type", required = true) val solutionFileType: SolutionFileType, + @param:JsonProperty("grading_script", required = false) val gradingScript: String?, + @param:JsonProperty("container_image", required = false) @field:Size(max = 2000) val containerImage: String?, + @param:JsonProperty("max_time_sec", required = false) val maxTime: Int?, + @param:JsonProperty("max_mem_mb", required = false) val maxMem: Int?, + @param:JsonProperty("assets", required = false) val assets: List?, ) data class ReqAsset( - @JsonProperty("file_name", required = true) @field:Size(max = 100) val fileName: String, - @JsonProperty("file_content", required = true) @field:Size(max = 300000) val fileContent: String + @param:JsonProperty("file_name", required = true) @field:Size(max = 100) val fileName: String, + @param:JsonProperty("file_content", required = true) @field:Size(max = 300000) val fileContent: String ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -82,7 +85,8 @@ class CreateExercise(private val adocService: AdocService) { val newAutoExerciseId = if (req.graderType == GraderType.AUTO) { - insertAutoExercise(req.gradingScript, req.containerImage, req.maxTime, req.maxMem, + insertAutoExercise( + req.gradingScript, req.containerImage, req.maxTime, req.maxMem, req.assets?.map { it.fileName to it.fileContent }) } else null diff --git a/core/src/main/kotlin/core/ems/service/exercise/DeleteExercise.kt b/core/src/main/kotlin/core/ems/service/exercise/DeleteExercise.kt index 8de584a4..f5d6501b 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/DeleteExercise.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/DeleteExercise.kt @@ -9,24 +9,24 @@ import core.ems.service.getDirectGroupDirAccesses import core.ems.service.getImplicitDirFromExercise import core.ems.service.idToLongOrInvalidReq import core.ems.service.libraryDirRemoveAccess -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -private val log = KotlinLogging.logger {} @RestController @RequestMapping("/v2") class DeleteExercise { + private val log = KotlinLogging.logger {} @Secured("ROLE_TEACHER", "ROLE_ADMIN") @DeleteMapping("/exercises/{exerciseId}") diff --git a/core/src/main/kotlin/core/ems/service/exercise/ReadAllSubmissionsByStudent.kt b/core/src/main/kotlin/core/ems/service/exercise/ReadAllSubmissionsByStudent.kt index a88cee5b..8dd98cf7 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/ReadAllSubmissionsByStudent.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/ReadAllSubmissionsByStudent.kt @@ -1,7 +1,6 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.CourseExercise import core.db.StudentExerciseStatus @@ -12,16 +11,19 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.toGradeRespOrNull import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import tools.jackson.databind.annotation.JsonSerialize @RestController @@ -30,14 +32,14 @@ class ReadAllSubmissionsByStudent { private val log = KotlinLogging.logger {} data class SubmissionResp( - @JsonProperty("id") val submissionId: String, - @JsonProperty("submission_number") val submissionNumber: Int, - @JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("status") val status: StudentExerciseStatus, - @JsonProperty("grade") val grade: GradeResp? + @get:JsonProperty("id") val submissionId: String, + @get:JsonProperty("submission_number") val submissionNumber: Int, + @get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("status") val status: StudentExerciseStatus, + @get:JsonProperty("grade") val grade: GradeResp? ) - data class Resp(@JsonProperty("submissions") val submissions: List) + data class Resp(@get:JsonProperty("submissions") val submissions: List) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @GetMapping("/teacher/courses/{courseId}/exercises/{courseExerciseId}/submissions/all/students/{studentId}") diff --git a/core/src/main/kotlin/core/ems/service/exercise/ReadAnonymousSubmissions.kt b/core/src/main/kotlin/core/ems/service/exercise/ReadAnonymousSubmissions.kt index 06e5d120..cfed6943 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/ReadAnonymousSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/ReadAnonymousSubmissions.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.AnonymousSubmission import core.db.DirAccessLevel @@ -9,10 +9,11 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.libraryExercise import core.ems.service.idToLongOrInvalidReq import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -26,15 +27,15 @@ class ReadAnonymousSubmissions { private val log = KotlinLogging.logger {} data class SubmissionResp( - @JsonProperty("id") val submissionId: String, - @JsonProperty("solution") val solution: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("grade") val grade: Int, - @JsonProperty("feedback") val feedback: String? + @get:JsonProperty("id") val submissionId: String, + @get:JsonProperty("solution") val solution: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("grade") val grade: Int, + @get:JsonProperty("feedback") val feedback: String? ) - data class Resp(@JsonProperty("submissions") val submissions: List) + data class Resp(@get:JsonProperty("submissions") val submissions: List) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/ReadExercise.kt b/core/src/main/kotlin/core/ems/service/exercise/ReadExercise.kt index c4099d55..01b599c6 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/ReadExercise.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/ReadExercise.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.aas.selectAutoExercise import core.conf.security.EasyUser import core.db.* @@ -12,10 +12,13 @@ import core.ems.service.getImplicitDirFromExercise import core.ems.service.idToLongOrInvalidReq import core.ems.service.singleOrInvalidRequest import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.leftJoin -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.core.leftJoin +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -30,49 +33,49 @@ class ReadExercise { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("dir_id") val implicitDirId: String, - @JsonProperty("effective_access") val effectiveAccess: DirAccessLevel, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val created_at: DateTime, - @JsonProperty("is_public") val public: Boolean, - @JsonProperty("is_anonymous_autoassess_enabled") val anonymousAutoassessEnabled: Boolean, - @JsonProperty("owner_id") val ownerUsername: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("last_modified") val lastModified: DateTime, - @JsonProperty("last_modified_by_id") val lastModifiedBy: String, - @JsonProperty("grader_type") val grader: GraderType, - @JsonProperty("solution_file_name") val solutionFileName: String, - @JsonProperty("solution_file_type") val solutionFileType: SolutionFileType, - @JsonProperty("title") val title: String, - @JsonProperty("text_html") val textHtml: String?, - @JsonProperty("text_adoc") val textAdoc: String?, - @JsonProperty("anonymous_autoassess_template") val anonymousAutoassessTemplate: String?, - @JsonProperty("grading_script") val gradingScript: String?, - @JsonProperty("container_image") val containerImage: String?, - @JsonProperty("max_time_sec") val maxTime: Int?, - @JsonProperty("max_mem_mb") val maxMem: Int?, - @JsonProperty("assets") val assets: List?, - @JsonProperty("executors") val executors: List?, - @JsonProperty("on_courses") val courses: List, - @JsonProperty("on_courses_no_access") val coursesNoAccessCount: Int + @get:JsonProperty("dir_id") val implicitDirId: String, + @get:JsonProperty("effective_access") val effectiveAccess: DirAccessLevel, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val created_at: DateTime, + @get:JsonProperty("is_public") val public: Boolean, + @get:JsonProperty("is_anonymous_autoassess_enabled") val anonymousAutoassessEnabled: Boolean, + @get:JsonProperty("owner_id") val ownerUsername: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("last_modified") val lastModified: DateTime, + @get:JsonProperty("last_modified_by_id") val lastModifiedBy: String, + @get:JsonProperty("grader_type") val grader: GraderType, + @get:JsonProperty("solution_file_name") val solutionFileName: String, + @get:JsonProperty("solution_file_type") val solutionFileType: SolutionFileType, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("text_html") val textHtml: String?, + @get:JsonProperty("text_adoc") val textAdoc: String?, + @get:JsonProperty("anonymous_autoassess_template") val anonymousAutoassessTemplate: String?, + @get:JsonProperty("grading_script") val gradingScript: String?, + @get:JsonProperty("container_image") val containerImage: String?, + @get:JsonProperty("max_time_sec") val maxTime: Int?, + @get:JsonProperty("max_mem_mb") val maxMem: Int?, + @get:JsonProperty("assets") val assets: List?, + @get:JsonProperty("executors") val executors: List?, + @get:JsonProperty("on_courses") val courses: List, + @get:JsonProperty("on_courses_no_access") val coursesNoAccessCount: Int ) data class RespAsset( - @JsonProperty("file_name") val fileName: String, - @JsonProperty("file_content") val fileContent: String + @get:JsonProperty("file_name") val fileName: String, + @get:JsonProperty("file_content") val fileContent: String ) data class RespExecutor( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String ) data class RespCourse( - @JsonProperty("id") val id: String, - @JsonProperty("title") val title: String, - @JsonProperty("alias") val alias: String?, - @JsonProperty("course_exercise_id") val courseExId: String, - @JsonProperty("course_exercise_title_alias") val titleAlias: String? + @get:JsonProperty("id") val id: String, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("alias") val alias: String?, + @get:JsonProperty("course_exercise_id") val courseExId: String, + @get:JsonProperty("course_exercise_title_alias") val titleAlias: String? ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -95,7 +98,8 @@ class ReadExercise { ) val usedOnCourses = - (CourseExercise innerJoin Course).leftJoin(TeacherCourseAccess, + (CourseExercise innerJoin Course).leftJoin( + TeacherCourseAccess, onColumn = { Course.id }, otherColumn = { TeacherCourseAccess.course }, additionalConstraint = { TeacherCourseAccess.teacher eq caller.id }).select( Course.id, Course.title, Course.alias, CourseExercise.id, CourseExercise.titleAlias, diff --git a/core/src/main/kotlin/core/ems/service/exercise/ReadLatestTeacherSubmissions.kt b/core/src/main/kotlin/core/ems/service/exercise/ReadLatestTeacherSubmissions.kt index 2a659ec6..32fbcc89 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/ReadLatestTeacherSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/ReadLatestTeacherSubmissions.kt @@ -1,20 +1,20 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.TeacherSubmission import core.ems.service.idToLongOrInvalidReq import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.servlet.http.HttpServletResponse @RestController @@ -23,15 +23,15 @@ class ReadLatestTeacherSubmissions { private val log = KotlinLogging.logger {} data class RespSubmission( - @JsonProperty("id") val id: String, - @JsonProperty("solution") val solution: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val submissionTime: DateTime + @get:JsonProperty("id") val id: String, + @get:JsonProperty("solution") val solution: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val submissionTime: DateTime ) data class Resp( - @JsonProperty("count") val count: Int, - @JsonProperty("submissions") val submissions: List + @get:JsonProperty("count") val count: Int, + @get:JsonProperty("submissions") val submissions: List ) @@ -39,11 +39,10 @@ class ReadLatestTeacherSubmissions { @GetMapping("/exercises/{exerciseId}/testing/autoassess/submissions") fun controller( @PathVariable("exerciseId") exerciseIdStr: String, - response: HttpServletResponse, @RequestParam("offset", required = false) offsetStr: String?, @RequestParam("limit", required = false) limitStr: String?, caller: EasyUser - ): Resp? { + ): Resp { log.info { "Getting latest teacher submissions for ${caller.id} on exercise $exerciseIdStr" } val exerciseId = exerciseIdStr.idToLongOrInvalidReq() @@ -62,7 +61,8 @@ class ReadLatestTeacherSubmissions { val totalSubmissions = selectQuery.count() val submissions = selectQuery - .limit(limit ?: totalSubmissions.toInt(), offset ?: 0) + .limit(limit ?: totalSubmissions.toInt()) + .offset(offset ?: 0) .map { RespSubmission( it[TeacherSubmission.id].value.toString(), diff --git a/core/src/main/kotlin/core/ems/service/exercise/ReadSubmissionDetails.kt b/core/src/main/kotlin/core/ems/service/exercise/ReadSubmissionDetails.kt index 77c91d55..c89bff6d 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/ReadSubmissionDetails.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/ReadSubmissionDetails.kt @@ -1,15 +1,17 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.AutoGradeStatus import core.db.Submission import core.ems.service.* import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -24,15 +26,15 @@ class ReadSubmissionDetails { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("id") val id: String, - @JsonProperty("submission_number") val submissionNumber: Int, - @JsonProperty("solution") val solution: String, - @JsonProperty("seen") val seen: Boolean, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("autograde_status") val autoGradeStatus: AutoGradeStatus, - @JsonProperty("grade") val grade: GradeResp?, - @JsonProperty("auto_assessment") val autoAssessments: AutomaticAssessmentResp? + @get:JsonProperty("id") val id: String, + @get:JsonProperty("submission_number") val submissionNumber: Int, + @get:JsonProperty("solution") val solution: String, + @get:JsonProperty("seen") val seen: Boolean, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("autograde_status") val autoGradeStatus: AutoGradeStatus, + @get:JsonProperty("grade") val grade: GradeResp?, + @get:JsonProperty("auto_assessment") val autoAssessments: AutomaticAssessmentResp? ) diff --git a/core/src/main/kotlin/core/ems/service/exercise/RemoveExerciseFromCourse.kt b/core/src/main/kotlin/core/ems/service/exercise/RemoveExerciseFromCourse.kt index 2cafef59..2e9c7c6f 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/RemoveExerciseFromCourse.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/RemoveExerciseFromCourse.kt @@ -2,15 +2,14 @@ package core.ems.service.exercise import core.conf.security.EasyUser import core.db.CourseExercise -import core.ems.service.access_control.RequireStudentVisible import core.ems.service.access_control.assertAccess import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/exercise/SetSubmissionSeen.kt b/core/src/main/kotlin/core/ems/service/exercise/SetSubmissionSeen.kt index c5a494b9..f6d9b675 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/SetSubmissionSeen.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/SetSubmissionSeen.kt @@ -4,12 +4,13 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.Submission import core.ems.service.assertAssessmentControllerChecks -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid @RestController @@ -18,13 +19,11 @@ class SetSubmissionSeen { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("submissions") val submissions: List, - @JsonProperty("seen") val seen: Boolean + @param:JsonProperty("submissions") val submissions: List, + @param:JsonProperty("seen") val seen: Boolean ) - data class SubmissionReq( - @JsonProperty("id") val id: String - ) + data class SubmissionReq(@param:JsonProperty("id") val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/teacher/courses/{courseId}/exercises/{courseExerciseId}/submissions/seen") diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentAwaitLatestSubmission.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentAwaitLatestSubmission.kt index f1a251ac..77f59e25 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentAwaitLatestSubmission.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentAwaitLatestSubmission.kt @@ -11,10 +11,12 @@ import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.studentOnCourse import core.ems.service.idToLongOrInvalidReq import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentReadAllExerciseTeacherActivities.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentReadAllExerciseTeacherActivities.kt index fef48d32..31b0274a 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentReadAllExerciseTeacherActivities.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentReadAllExerciseTeacherActivities.kt @@ -8,7 +8,7 @@ import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.studentOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectStudentAllExerciseActivities -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentReadDraft.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentReadDraft.kt index ab3d583f..2c0e7b0a 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentReadDraft.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentReadDraft.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.SubmissionDraft import core.ems.service.access_control.RequireStudentVisible @@ -10,10 +10,12 @@ import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.studentOnCourse import core.ems.service.idToLongOrInvalidReq import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.servlet.http.HttpServletResponse +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.http.HttpStatus import org.springframework.security.access.annotation.Secured @@ -21,7 +23,6 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletResponse @RestController @@ -30,9 +31,9 @@ class StudentReadLatestSubmissionDraftController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("solution") val solution: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val submissionTime: DateTime + @get:JsonProperty("solution") val solution: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val submissionTime: DateTime ) @Secured("ROLE_STUDENT") diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentReadExerciseDetails.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentReadExerciseDetails.kt index d49bf16d..d0a81b9f 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentReadExerciseDetails.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentReadExerciseDetails.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.* import core.ems.service.* @@ -10,9 +10,12 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.studentOnCourse import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -27,16 +30,16 @@ class StudentReadExerciseDetailsController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("effective_title") val title: String, - @JsonProperty("text_html") val textHtml: String?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("deadline") val softDeadline: DateTime?, - @JsonProperty("grader_type") val graderType: GraderType, - @JsonProperty("threshold") val threshold: Int, - @JsonProperty("instructions_html") val instructionsHtml: String?, - @JsonProperty("is_open") val isOpenForSubmissions: Boolean, - @JsonProperty("solution_file_name") val solutionFileName: String, - @JsonProperty("solution_file_type") val solutionFileType: SolutionFileType, + @get:JsonProperty("effective_title") val title: String, + @get:JsonProperty("text_html") val textHtml: String?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("deadline") val softDeadline: DateTime?, + @get:JsonProperty("grader_type") val graderType: GraderType, + @get:JsonProperty("threshold") val threshold: Int, + @get:JsonProperty("instructions_html") val instructionsHtml: String?, + @get:JsonProperty("is_open") val isOpenForSubmissions: Boolean, + @get:JsonProperty("solution_file_name") val solutionFileName: String, + @get:JsonProperty("solution_file_type") val solutionFileType: SolutionFileType, ) @Secured("ROLE_STUDENT") @@ -45,7 +48,7 @@ class StudentReadExerciseDetailsController { @PathVariable("courseId") courseIdStr: String, @PathVariable("courseExerciseId") courseExIdStr: String, caller: EasyUser - ): Resp? { + ): Resp { log.info { "Getting exercise details for student ${caller.id} on course exercise $courseExIdStr" } val courseId = courseIdStr.idToLongOrInvalidReq() diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentReadExercises.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentReadExercises.kt index d4bd9cc1..61cf56fa 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentReadExercises.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentReadExercises.kt @@ -1,17 +1,17 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.* import core.ems.service.* import core.ems.service.access_control.assertAccess import core.ems.service.access_control.studentOnCourse import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -26,18 +26,18 @@ class StudentReadExercisesController { private val log = KotlinLogging.logger {} data class ExerciseResp( - @JsonProperty("id") val courseExId: String, - @JsonProperty("effective_title") val title: String, - @JsonProperty("grader_type") val graderType: GraderType, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("deadline") val softDeadline: DateTime?, - @JsonProperty("is_open") val isOpenForSubmissions: Boolean, - @JsonProperty("status") val status: StudentExerciseStatus, - @JsonProperty("grade") val grade: GradeResp?, - @JsonProperty("ordering_idx") val orderingIndex: Int + @get:JsonProperty("id") val courseExId: String, + @get:JsonProperty("effective_title") val title: String, + @get:JsonProperty("grader_type") val graderType: GraderType, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("deadline") val softDeadline: DateTime?, + @get:JsonProperty("is_open") val isOpenForSubmissions: Boolean, + @get:JsonProperty("status") val status: StudentExerciseStatus, + @get:JsonProperty("grade") val grade: GradeResp?, + @get:JsonProperty("ordering_idx") val orderingIndex: Int ) - data class Resp(@JsonProperty("exercises") val exercises: List) + data class Resp(@get:JsonProperty("exercises") val exercises: List) @Secured("ROLE_STUDENT") @GetMapping("/student/courses/{courseId}/exercises") diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentReadSubmissions.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentReadSubmissions.kt index 129b242b..940d1f81 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentReadSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentReadSubmissions.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.AutoGradeStatus import core.db.CourseExercise @@ -13,10 +13,12 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.studentOnCourse import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.web.bind.annotation.* @@ -27,20 +29,18 @@ class StudentReadSubmissionsController { private val log = KotlinLogging.logger {} data class SubmissionResp( - @JsonProperty("id") val submissionId: String, - @JsonProperty("number") val number: Int, - @JsonProperty("solution") val solution: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("submission_time") val submissionTime: DateTime, - @JsonProperty("autograde_status") val autoGradeStatus: AutoGradeStatus, - @JsonProperty("grade") val grade: GradeResp?, - @JsonProperty("submission_status") val status: StudentExerciseStatus, - @JsonProperty("auto_assessment") val autoAssessments: AutomaticAssessmentResp? + @get:JsonProperty("id") val submissionId: String, + @get:JsonProperty("number") val number: Int, + @get:JsonProperty("solution") val solution: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("submission_time") val submissionTime: DateTime, + @get:JsonProperty("autograde_status") val autoGradeStatus: AutoGradeStatus, + @get:JsonProperty("grade") val grade: GradeResp?, + @get:JsonProperty("submission_status") val status: StudentExerciseStatus, + @get:JsonProperty("auto_assessment") val autoAssessments: AutomaticAssessmentResp? ) - data class Resp( - @JsonProperty("submissions") val submissions: List, - ) + data class Resp(@get:JsonProperty("submissions") val submissions: List) @GetMapping("/student/courses/{courseId}/exercises/{courseExerciseId}/submissions/all") fun controller( @@ -91,8 +91,9 @@ class StudentReadSubmissionsController { (Submission.student eq studentId) } .orderBy(Submission.createdAt to SortOrder.DESC) - .limit(limit ?: Int.MAX_VALUE, offset ?: 0) - .mapIndexed { i, it -> + .limit(limit ?: Int.MAX_VALUE) + .offset(offset ?: 0) + .mapIndexed { _, it -> val submissionId = it[Submission.id].value SubmissionResp( diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentSubmit.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentSubmit.kt index 5208851b..e1737e25 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentSubmit.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentSubmit.kt @@ -17,14 +17,15 @@ import core.ems.service.moodle.MoodleGradesSyncService import core.exception.InvalidRequestException import core.exception.ReqError import core.util.SendMailService +import jakarta.validation.Valid +import jakarta.validation.constraints.Size import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.DelicateCoroutinesApi import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size @RestController @@ -38,7 +39,7 @@ class StudentSubmitCont( ) { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("solution", required = true) @field:Size(max = 500000) val solution: String) + data class Req(@param:JsonProperty("solution", required = true) @field:Size(max = 500000) val solution: String) @Secured("ROLE_STUDENT") @PostMapping("/student/courses/{courseId}/exercises/{courseExerciseId}/submissions") @@ -58,6 +59,7 @@ class StudentSubmitCont( submitSolution(courseExId, solutionBody.solution, caller.id) } + @OptIn(DelicateCoroutinesApi::class) private fun submitSolution(courseExId: Long, solution: String, studentId: String) { if (!isCourseExerciseOpenForSubmit(courseExId, studentId)) throw InvalidRequestException("Exercise is not open for submissions", ReqError.COURSE_EXERCISE_CLOSED) diff --git a/core/src/main/kotlin/core/ems/service/exercise/StudentSubmitDraft.kt b/core/src/main/kotlin/core/ems/service/exercise/StudentSubmitDraft.kt index a2e9e69b..ce1b3a1e 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/StudentSubmitDraft.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/StudentSubmitDraft.kt @@ -4,20 +4,20 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.CourseExercise import core.db.SubmissionDraft -import core.db.insertOrUpdate import core.ems.service.access_control.RequireStudentVisible import core.ems.service.access_control.assertAccess import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.studentOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.Size +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.upsert import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size @RestController @@ -25,7 +25,7 @@ import javax.validation.constraints.Size class StudentSubmitDraftController { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("solution", required = true) @field:Size(max = 300000) val solution: String) + data class Req(@param:JsonProperty("solution", required = true) @field:Size(max = 300000) val solution: String) @Secured("ROLE_STUDENT") @PostMapping("/student/courses/{courseId}/exercises/{courseExerciseId}/draft") @@ -46,9 +46,13 @@ class StudentSubmitDraftController { } private fun insertOrUpdateSubmissionDraft(courseExId: Long, submission: String, studentId: String) = transaction { - SubmissionDraft.insertOrUpdate( - listOf(SubmissionDraft.courseExercise, SubmissionDraft.student), - listOf(SubmissionDraft.courseExercise, SubmissionDraft.student) + SubmissionDraft.upsert( + SubmissionDraft.courseExercise, + SubmissionDraft.student, + onUpdateExclude = listOf( + SubmissionDraft.courseExercise, + SubmissionDraft.student + ) ) { it[courseExercise] = EntityID(courseExId, CourseExercise) it[student] = studentId diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherAutoassess.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherAutoassess.kt index af4d71b7..6e1def44 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherAutoassess.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherAutoassess.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.aas.AutoGradeScheduler import core.conf.security.EasyUser import core.db.* @@ -12,16 +12,19 @@ import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError import core.util.DateTimeSerializer +import jakarta.validation.Valid +import jakarta.validation.constraints.Size import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size @RestController @@ -30,14 +33,14 @@ class TeacherAutoassess(val autoGradeScheduler: AutoGradeScheduler) { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("solution") @field:Size(max = 300000) val solution: String + @param:JsonProperty("solution") @field:Size(max = 300000) val solution: String ) data class Resp( - @JsonProperty("grade") val grade: Int, - @JsonProperty("feedback") val feedback: String?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("timestamp") val timestamp: DateTime, + @get:JsonProperty("grade") val grade: Int, + @get:JsonProperty("feedback") val feedback: String?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("timestamp") val timestamp: DateTime, ) diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadCourseExerciseSubmissions.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadCourseExerciseSubmissions.kt index 6ce12617..91c74ca7 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadCourseExerciseSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadCourseExerciseSubmissions.kt @@ -8,16 +8,16 @@ import core.ems.service.idToLongOrInvalidReq import core.ems.service.singleOrInvalidRequest import core.util.Zip import core.util.writeZipFile -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.servlet.http.HttpServletResponse +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.servlet.http.HttpServletResponse -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotEmpty @RestController @@ -25,9 +25,9 @@ import javax.validation.constraints.NotEmpty class TeacherDownloadCourseExerciseSubmissionsController { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("submissions") @field:NotEmpty val submissions: Set) + data class Req(@param:JsonProperty("submissions") @field:NotEmpty val submissions: Set) - data class SubmissionReq(@JsonProperty("id") @field:NotBlank val id: String) + data class SubmissionReq(@param:JsonProperty("id") @field:NotBlank val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/export/courses/{courseId}/exercises/{courseExerciseId}/submissions") diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt index 04d7b4f7..4e6d5045 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt @@ -8,17 +8,16 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.util.Zip import core.util.writeZipFile -import mu.KotlinLogging -import org.jetbrains.exposed.sql.Join -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.servlet.http.HttpServletResponse +import jakarta.validation.Valid +import jakarta.validation.constraints.NotEmpty +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.andWhere +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.servlet.http.HttpServletResponse -import javax.validation.Valid -import javax.validation.constraints.NotEmpty @RestController @@ -26,11 +25,14 @@ import javax.validation.constraints.NotEmpty class TeacherDownloadSubmissionsController { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("courses") @field:NotEmpty val courses: List) + data class Req(@param:JsonProperty("courses") @field:NotEmpty val courses: List) - data class CourseReq(@JsonProperty("id") val id: String, @JsonProperty("groups") val groups: List?) + data class CourseReq( + @param:JsonProperty("id") val id: String, + @param:JsonProperty("groups") val groups: List? + ) - data class GroupReq(@JsonProperty("id") val id: String) + data class GroupReq(@param:JsonProperty("id") val id: String) private data class Course(val id: Long, val groups: List?) @@ -48,7 +50,8 @@ class TeacherDownloadSubmissionsController { val exerciseId = exerciseIdStr.idToLongOrInvalidReq() val courses = req.courses.map { - Course(it.id.idToLongOrInvalidReq(), + Course( + it.id.idToLongOrInvalidReq(), it.groups?.map { group -> group.id.idToLongOrInvalidReq() } ) } diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherEditFeedback.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherEditFeedback.kt index 0a3da04f..6365d265 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherEditFeedback.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherEditFeedback.kt @@ -3,23 +3,23 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.TeacherActivity -import core.db.isNull import core.ems.service.* import core.exception.InvalidRequestException import core.exception.ReqError import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size @RestController @@ -28,19 +28,22 @@ class TeacherEditFeedbackController(val markdownService: MarkdownService, val ma private val log = KotlinLogging.logger {} data class InlineCommentReq( - @JsonProperty("line_start", required = true) val lineStart: Int, - @JsonProperty("line_end", required = true) val lineEnd: Int, - @JsonProperty("code", required = true) val code: String, - @JsonProperty("text_md", required = true) val textMd: String, - @JsonProperty("type", required = true) val type: String, - @JsonProperty("suggested_code", required = false) val suggestedCode: String? = null, + @param:JsonProperty("line_start", required = true) val lineStart: Int, + @param:JsonProperty("line_end", required = true) val lineEnd: Int, + @param:JsonProperty("code", required = true) val code: String, + @param:JsonProperty("text_md", required = true) val textMd: String, + @param:JsonProperty("type", required = true) val type: String, + @param:JsonProperty("suggested_code", required = false) val suggestedCode: String? = null, ) data class Req( - @JsonProperty("teacher_activity_id", required = true) @field:Size(max = 100) val teacherActivityId: String, - @JsonProperty("feedback_md", required = false) @field:Size(max = 300000) val feedbackMd: String?, - @JsonProperty("inline_comments", required = false) val inlineComments: List? = null, - @JsonProperty("notify_student", required = true) @field:NotNull val notifyStudent: Boolean + @param:JsonProperty( + "teacher_activity_id", + required = true + ) @field:Size(max = 100) val teacherActivityId: String, + @param:JsonProperty("feedback_md", required = false) @field:Size(max = 300000) val feedbackMd: String?, + @param:JsonProperty("inline_comments", required = false) val inlineComments: List? = null, + @param:JsonProperty("notify_student", required = true) @field:NotNull val notifyStudent: Boolean ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherPostFeedback.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherPostFeedback.kt index 598d9afd..87465c01 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherPostFeedback.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherPostFeedback.kt @@ -6,20 +6,22 @@ import core.db.StatsSubmission import core.db.TeacherActivity import core.ems.service.* import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.beans.factory.annotation.Value import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotNull -import javax.validation.constraints.Size @RestController @@ -31,22 +33,22 @@ class TeacherPostFeedbackController(val markdownService: MarkdownService, val ma private lateinit var mergeWindowInSeconds: String data class InlineCommentReq( - @JsonProperty("line_start", required = true) val lineStart: Int, - @JsonProperty("line_end", required = true) val lineEnd: Int, - @JsonProperty("code", required = true) val code: String, - @JsonProperty("text_md", required = true) @field:NotBlank val textMd: String, - @JsonProperty("type", required = true) val type: String, - @JsonProperty("suggested_code", required = false) val suggestedCode: String? = null, + @param:JsonProperty("line_start", required = true) val lineStart: Int, + @param:JsonProperty("line_end", required = true) val lineEnd: Int, + @param:JsonProperty("code", required = true) val code: String, + @param:JsonProperty("text_md", required = true) @field:NotBlank val textMd: String, + @param:JsonProperty("type", required = true) val type: String, + @param:JsonProperty("suggested_code", required = false) val suggestedCode: String? = null, ) data class Req( - @JsonProperty("feedback_md", required = true) + @param:JsonProperty("feedback_md", required = true) @field:Size(max = 300000) @field:NotBlank val feedbackMd: String, - @JsonProperty("inline_comments", required = false) + @param:JsonProperty("inline_comments", required = false) val inlineComments: List? = null, - @JsonProperty("notify_student", required = true) + @param:JsonProperty("notify_student", required = true) @field:NotNull val notifyStudent: Boolean ) diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherPostGrade.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherPostGrade.kt index dab7b14f..4eeac5fd 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherPostGrade.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherPostGrade.kt @@ -8,20 +8,22 @@ import core.db.TeacherActivity import core.ems.service.* import core.ems.service.moodle.MoodleGradesSyncService import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.beans.factory.annotation.Value import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Max -import javax.validation.constraints.Min -import javax.validation.constraints.NotNull @RestController @@ -33,8 +35,8 @@ class TeacherGradeController(val moodleGradesSyncService: MoodleGradesSyncServic private lateinit var mergeWindowInSeconds: String data class Req( - @JsonProperty("grade", required = true) @field:Min(0) @field:Max(100) val grade: Int, - @JsonProperty("notify_student", required = true) @field:NotNull val notifyStudent: Boolean + @param:JsonProperty("grade", required = true) @field:Min(0) @field:Max(100) val grade: Int, + @param:JsonProperty("notify_student", required = true) @field:NotNull val notifyStudent: Boolean ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExerciseDetails.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExerciseDetails.kt index 0ff54960..3dca2f1d 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExerciseDetails.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExerciseDetails.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.aas.selectAutoExercise import core.conf.security.EasyUser import core.db.* @@ -10,9 +10,12 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.util.DateTimeSerializer import core.util.notNullAndInPast -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -27,62 +30,62 @@ class TeacherReadExDetailsCont { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("exercise_id") val exerciseId: String, - @JsonProperty("title") val title: String, - @JsonProperty("title_alias") val titleAlias: String?, - @JsonProperty("instructions_html") val instructionsHtml: String?, - @JsonProperty("instructions_adoc") val instructionsAdoc: String?, - @JsonProperty("text_html") val textHtml: String?, - @JsonProperty("text_adoc") val textAdoc: String?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("soft_deadline") val softDeadline: DateTime?, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("hard_deadline") val hardDeadline: DateTime?, - @JsonProperty("grader_type") val grader: GraderType, - @JsonProperty("solution_file_name") val solutionFileName: String, - @JsonProperty("solution_file_type") val solutionFileType: SolutionFileType, - @JsonProperty("threshold") val threshold: Int, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("last_modified") val lastModified: DateTime, - @JsonProperty("student_visible") val studentVisible: Boolean, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("student_visible_from") val studentVisibleFrom: DateTime?, - @JsonProperty("assessments_student_visible") val assStudentVisible: Boolean, - @JsonProperty("grading_script") val gradingScript: String?, - @JsonProperty("container_image") val containerImage: String?, - @JsonProperty("max_time_sec") val maxTime: Int?, - @JsonProperty("max_mem_mb") val maxMem: Int?, - @JsonProperty("assets") val assets: List?, - @JsonProperty("executors") val executors: List?, - @JsonProperty("has_lib_access") val hasLibAccess: Boolean, - @JsonProperty("exception_students") val exceptionStudents: List?, - @JsonProperty("exception_groups") val exceptionGroups: List?, + @get:JsonProperty("exercise_id") val exerciseId: String, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("title_alias") val titleAlias: String?, + @get:JsonProperty("instructions_html") val instructionsHtml: String?, + @get:JsonProperty("instructions_adoc") val instructionsAdoc: String?, + @get:JsonProperty("text_html") val textHtml: String?, + @get:JsonProperty("text_adoc") val textAdoc: String?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("soft_deadline") val softDeadline: DateTime?, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("hard_deadline") val hardDeadline: DateTime?, + @get:JsonProperty("grader_type") val grader: GraderType, + @get:JsonProperty("solution_file_name") val solutionFileName: String, + @get:JsonProperty("solution_file_type") val solutionFileType: SolutionFileType, + @get:JsonProperty("threshold") val threshold: Int, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("last_modified") val lastModified: DateTime, + @get:JsonProperty("student_visible") val studentVisible: Boolean, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("student_visible_from") val studentVisibleFrom: DateTime?, + @get:JsonProperty("assessments_student_visible") val assStudentVisible: Boolean, + @get:JsonProperty("grading_script") val gradingScript: String?, + @get:JsonProperty("container_image") val containerImage: String?, + @get:JsonProperty("max_time_sec") val maxTime: Int?, + @get:JsonProperty("max_mem_mb") val maxMem: Int?, + @get:JsonProperty("assets") val assets: List?, + @get:JsonProperty("executors") val executors: List?, + @get:JsonProperty("has_lib_access") val hasLibAccess: Boolean, + @get:JsonProperty("exception_students") val exceptionStudents: List?, + @get:JsonProperty("exception_groups") val exceptionGroups: List?, ) data class RespAsset( - @JsonProperty("file_name") val fileName: String, - @JsonProperty("file_content") val fileContent: String + @get:JsonProperty("file_name") val fileName: String, + @get:JsonProperty("file_content") val fileContent: String ) data class RespExecutor( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String ) - data class ExceptionValueResp(@JsonSerialize(using = DateTimeSerializer::class) @JsonProperty("value") val value: DateTime?) + data class ExceptionValueResp(@get:JsonSerialize(using = DateTimeSerializer::class) @get:JsonProperty("value") val value: DateTime?) data class RespExceptionStudent( - @JsonProperty("student_id") val studentId: String, - @JsonProperty("soft_deadline") val softDeadline: ExceptionValueResp?, - @JsonProperty("hard_deadline") val hardDeadline: ExceptionValueResp?, - @JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueResp?, + @get:JsonProperty("student_id") val studentId: String, + @get:JsonProperty("soft_deadline") val softDeadline: ExceptionValueResp?, + @get:JsonProperty("hard_deadline") val hardDeadline: ExceptionValueResp?, + @get:JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueResp?, ) data class RespExceptionGroup( - @JsonProperty("group_id") val groupId: Long, - @JsonProperty("soft_deadline") val softDeadline: ExceptionValueResp?, - @JsonProperty("hard_deadline") val hardDeadline: ExceptionValueResp?, - @JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueResp?, + @get:JsonProperty("group_id") val groupId: Long, + @get:JsonProperty("soft_deadline") val softDeadline: ExceptionValueResp?, + @get:JsonProperty("hard_deadline") val hardDeadline: ExceptionValueResp?, + @get:JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueResp?, ) diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExercises.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExercises.kt index 57e24ca6..69ca1534 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExercises.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherReadExercises.kt @@ -7,7 +7,7 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectAllCourseExercisesLatestSubmissions -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* @@ -17,8 +17,7 @@ import org.springframework.web.bind.annotation.* class TeacherReadCourseExercisesController { private val log = KotlinLogging.logger {} - - data class Resp(@JsonProperty("exercises") val courseExercises: List) + data class Resp(@get:JsonProperty("exercises") val courseExercises: List) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @GetMapping("/teacher/courses/{courseId}/exercises") diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherReadSubmissionSummaries.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherReadSubmissionSummaries.kt index 0e49f9ac..07a0a78e 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherReadSubmissionSummaries.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherReadSubmissionSummaries.kt @@ -7,7 +7,7 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectAllCourseExercisesLatestSubmissions import core.ems.service.singleOrInvalidRequest -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherReorderExercise.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherReorderExercise.kt index 7dcec77e..8ef9707c 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherReorderExercise.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherReorderExercise.kt @@ -9,13 +9,15 @@ import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.normaliseCourseExIndices import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -26,7 +28,7 @@ import kotlin.math.min class TeacherReorderExerciseController { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("new_index", required = true) val newIndex: Int) + data class Req(@param:JsonProperty("new_index", required = true) val newIndex: Int) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/courses/{courseId}/exercises/{courseExerciseId}/reorder") diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherRetryAutoassess.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherRetryAutoassess.kt index 97e2067e..7329fc17 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherRetryAutoassess.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherRetryAutoassess.kt @@ -11,9 +11,11 @@ import core.ems.service.moodle.MoodleGradesSyncService import core.exception.InvalidRequestException import core.exception.ReqError import core.util.SendMailService +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.runBlocking -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping @@ -87,7 +89,7 @@ class TeacherRetryAutoassessCont( ) } catch (e: Exception) { - log.error("Autoassessment failed", e) + log.error { "Autoassessment failed $e" } insertAutoAssFailed(submissionId, cachingService) val notification = """ Autoassessment retry by teacher failed diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherSubmitBehalfStudent.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherSubmitBehalfStudent.kt index ca8dfb54..52bcd226 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherSubmitBehalfStudent.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherSubmitBehalfStudent.kt @@ -17,14 +17,15 @@ import core.ems.service.selectGraderType import core.exception.InvalidRequestException import core.exception.ReqError import core.util.SendMailService +import jakarta.validation.Valid +import jakarta.validation.constraints.Size import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.DelicateCoroutinesApi import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size @RestController @@ -39,8 +40,8 @@ class TeacherSubmitBehalfStudent( private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("solution", required = true) @field:Size(max = 500000) val solution: String, - @JsonProperty("student_id", required = true) @field:Size(max = 100) val studentId: String + @param:JsonProperty("solution", required = true) @field:Size(max = 500000) val solution: String, + @param:JsonProperty("student_id", required = true) @field:Size(max = 100) val studentId: String ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -64,6 +65,7 @@ class TeacherSubmitBehalfStudent( submitOnBehalfOf(courseExId, req.solution, req.studentId, caller.id) } + @OptIn(DelicateCoroutinesApi::class) private fun submitOnBehalfOf(courseExId: Long, solution: String, studentId: String, teacherId: String) { if (selectGraderType(courseExId) == GraderType.TEACHER) { log.debug { "Creating new submission to teacher-graded exercise $courseExId for $studentId by $teacherId" } diff --git a/core/src/main/kotlin/core/ems/service/exercise/TecherReadStudentAllExerciseActivities.kt b/core/src/main/kotlin/core/ems/service/exercise/TecherReadStudentAllExerciseActivities.kt index 98091b40..e4e12b36 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TecherReadStudentAllExerciseActivities.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TecherReadStudentAllExerciseActivities.kt @@ -6,7 +6,7 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.ems.service.selectStudentAllExerciseActivities -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/exercise/UpdateCourseExercise.kt b/core/src/main/kotlin/core/ems/service/exercise/UpdateCourseExercise.kt index 32fc9fc2..d0030ba0 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/UpdateCourseExercise.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/UpdateCourseExercise.kt @@ -1,7 +1,6 @@ package core.ems.service.exercise import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize import core.conf.security.EasyUser import core.db.CourseExercise import core.ems.service.AdocService @@ -10,16 +9,18 @@ import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.util.DateTimeDeserializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.Size +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Max -import javax.validation.constraints.Min -import javax.validation.constraints.Size +import tools.jackson.databind.annotation.JsonDeserialize @RestController @@ -28,36 +29,36 @@ class UpdateCourseExercise(private val adocService: AdocService) { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("replace") @field:Valid + @param:JsonProperty("replace") @field:Valid val replace: ReplaceReq?, - @JsonProperty("delete") @field:Valid + @param:JsonProperty("delete") @field:Valid val delete: Set?, ) data class ReplaceReq( - @JsonProperty("title_alias") @field:Size(max = 100) + @param:JsonProperty("title_alias") @field:Size(max = 100) val titleAlias: String?, - @JsonProperty("instructions_adoc") @field:Size(max = 300000) + @param:JsonProperty("instructions_adoc") @field:Size(max = 300000) val instructionsAdoc: String?, - @JsonProperty("threshold") @field:Min(0) @field:Max(100) + @param:JsonProperty("threshold") @field:Min(0) @field:Max(100) val threshold: Int?, - @JsonProperty("soft_deadline") - @JsonDeserialize(using = DateTimeDeserializer::class) + @param:JsonProperty("soft_deadline") + @param:JsonDeserialize(using = DateTimeDeserializer::class) val softDeadline: DateTime?, - @JsonProperty("hard_deadline") - @JsonDeserialize(using = DateTimeDeserializer::class) + @param:JsonProperty("hard_deadline") + @param:JsonDeserialize(using = DateTimeDeserializer::class) val hardDeadline: DateTime?, - @JsonProperty("assessments_student_visible") + @param:JsonProperty("assessments_student_visible") val assessmentsStudentVisible: Boolean?, // Functionality duplicated by two fields, so we don't have to assume that clients know the current time // isStudentVisible overrides studentVisibleFrom - @JsonProperty("student_visible") + @param:JsonProperty("student_visible") val isStudentVisible: Boolean?, // null here is still interpreted as "do-not-change" rather than "hide" - @JsonProperty("student_visible_from") - @JsonDeserialize(using = DateTimeDeserializer::class) + @param:JsonProperty("student_visible_from") + @param:JsonDeserialize(using = DateTimeDeserializer::class) val studentVisibleFrom: DateTime?, - @JsonProperty("moodle_exercise_id") @field:Size(max = 100) + @param:JsonProperty("moodle_exercise_id") @field:Size(max = 100) val moodleExerciseId: String?, ) @@ -127,14 +128,18 @@ class UpdateCourseExercise(private val adocService: AdocService) { when (deleteField) { DeleteFieldReq.TITLE_ALIAS -> it[titleAlias] = null + DeleteFieldReq.INSTRUCTIONS_ADOC -> { it[instructionsAdoc] = null it[instructionsHtml] = null } + DeleteFieldReq.SOFT_DEADLINE -> it[softDeadline] = null + DeleteFieldReq.HARD_DEADLINE -> it[hardDeadline] = null + DeleteFieldReq.MOODLE_EXERCISE_ID -> it[moodleExId] = null } diff --git a/core/src/main/kotlin/core/ems/service/exercise/UpdateExercise.kt b/core/src/main/kotlin/core/ems/service/exercise/UpdateExercise.kt index 5cc002c9..71719249 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/UpdateExercise.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/UpdateExercise.kt @@ -9,19 +9,23 @@ import core.ems.service.AdocService import core.ems.service.access_control.assertAccess import core.ems.service.access_control.libraryExercise import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -30,22 +34,22 @@ class UpdateExercise(private val adocService: AdocService) { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("title", required = true) @field:NotBlank @field:Size(max = 100) val title: String, - @JsonProperty("text_html", required = false) @field:Size(max = 300000) val textHtml: String?, - @JsonProperty("text_adoc", required = false) @field:Size(max = 300000) val textAdoc: String?, - @JsonProperty("grader_type", required = true) val graderType: GraderType, - @JsonProperty("solution_file_name", required = true) val solutionFileName: String, - @JsonProperty("solution_file_type", required = true) val solutionFileType: SolutionFileType, - @JsonProperty("grading_script", required = false) val gradingScript: String?, - @JsonProperty("container_image", required = false) @field:Size(max = 2000) val containerImage: String?, - @JsonProperty("max_time_sec", required = false) val maxTime: Int?, - @JsonProperty("max_mem_mb", required = false) val maxMem: Int?, - @JsonProperty("assets", required = false) val assets: List? + @param:JsonProperty("title", required = true) @field:NotBlank @field:Size(max = 100) val title: String, + @param:JsonProperty("text_html", required = false) @field:Size(max = 300000) val textHtml: String?, + @param:JsonProperty("text_adoc", required = false) @field:Size(max = 300000) val textAdoc: String?, + @param:JsonProperty("grader_type", required = true) val graderType: GraderType, + @param:JsonProperty("solution_file_name", required = true) val solutionFileName: String, + @param:JsonProperty("solution_file_type", required = true) val solutionFileType: SolutionFileType, + @param:JsonProperty("grading_script", required = false) val gradingScript: String?, + @param:JsonProperty("container_image", required = false) @field:Size(max = 2000) val containerImage: String?, + @param:JsonProperty("max_time_sec", required = false) val maxTime: Int?, + @param:JsonProperty("max_mem_mb", required = false) val maxMem: Int?, + @param:JsonProperty("assets", required = false) val assets: List? ) data class ReqAsset( - @JsonProperty("file_name", required = true) @field:Size(max = 100) val fileName: String, - @JsonProperty("file_content", required = true) @field:Size(max = 300000) val fileContent: String + @param:JsonProperty("file_name", required = true) @field:Size(max = 100) val fileName: String, + @param:JsonProperty("file_content", required = true) @field:Size(max = 300000) val fileContent: String ) @@ -92,7 +96,8 @@ class UpdateExercise(private val adocService: AdocService) { val newAutoExerciseId = if (req.graderType == GraderType.AUTO) { - insertAutoExercise(req.gradingScript, req.containerImage, req.maxTime, req.maxMem, + insertAutoExercise( + req.gradingScript, req.containerImage, req.maxTime, req.maxMem, req.assets?.map { it.fileName to it.fileContent }) } else null diff --git a/core/src/main/kotlin/core/ems/service/exercise/UpdateExercisePatch.kt b/core/src/main/kotlin/core/ems/service/exercise/UpdateExercisePatch.kt index 7b038f3a..c9db6260 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/UpdateExercisePatch.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/UpdateExercisePatch.kt @@ -7,12 +7,13 @@ import core.db.Exercise import core.ems.service.access_control.assertAccess import core.ems.service.access_control.libraryExercise import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid @RestController @@ -21,8 +22,8 @@ class UpdateExercisePatch { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("anonymous_autoassess_enabled") val anonymousAutoassessEnabled: Boolean?, - @JsonProperty("anonymous_autoassess_template") val anonymousAutoassessTemplate: String?, + @param:JsonProperty("anonymous_autoassess_enabled") val anonymousAutoassessEnabled: Boolean?, + @param:JsonProperty("anonymous_autoassess_template") val anonymousAutoassessTemplate: String?, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/CreateDir.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/CreateDir.kt index fdef8944..1d64c5b9 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/CreateDir.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/CreateDir.kt @@ -9,19 +9,19 @@ import core.ems.service.access_control.libraryDir import core.ems.service.getImplicitGroupFromAccount import core.ems.service.idToLongOrInvalidReq import core.ems.service.upsertGroupDirAccess -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -30,11 +30,11 @@ class CreateDirController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("name") @field:NotBlank @field:Size(max = 100) val name: String, - @JsonProperty("parent_dir_id") @field:Size(max = 100) val parentId: String?, + @param:JsonProperty("name") @field:NotBlank @field:Size(max = 100) val name: String, + @param:JsonProperty("parent_dir_id") @field:Size(max = 100) val parentId: String?, ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/lib/dirs") diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/DeleteDir.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/DeleteDir.kt index ce709ff1..3c59c45a 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/DeleteDir.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/DeleteDir.kt @@ -6,11 +6,11 @@ import core.db.DirAccessLevel import core.ems.service.* import core.ems.service.access_control.assertAccess import core.ems.service.access_control.libraryDir -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/PutDirAccess.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/PutDirAccess.kt index f10549ca..d856bfc6 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/PutDirAccess.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/PutDirAccess.kt @@ -9,10 +9,10 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.libraryDir import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid @RestController @@ -21,10 +21,10 @@ class PutDirAccess { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("group_id") val groupId: String? = null, - @JsonProperty("email") val email: String? = null, - @JsonProperty("any_access") val anyAccess: Boolean = false, - @JsonProperty("access_level") val level: DirAccessLevel?, + @param:JsonProperty("group_id") val groupId: String? = null, + @param:JsonProperty("email") val email: String? = null, + @param:JsonProperty("any_access") val anyAccess: Boolean = false, + @param:JsonProperty("access_level") val level: DirAccessLevel?, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDir.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDir.kt index dd579b0a..67325b48 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDir.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDir.kt @@ -1,7 +1,7 @@ package core.ems.service.exercise.dir import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.* import core.ems.service.access_control.assertAccess @@ -11,10 +11,10 @@ import core.ems.service.getDir import core.ems.service.idToLongOrInvalidReq import core.util.DateTimeSerializer import core.util.maxOfOrNull -import mu.KotlinLogging -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.leftJoin -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -29,36 +29,36 @@ class ReadDirController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("current_dir") val currentDir: DirResp?, // null for root dir - @JsonProperty("child_dirs") val childDirs: List, - @JsonProperty("child_exercises") val childExercises: List, + @get:JsonProperty("current_dir") val currentDir: DirResp?, // null for root dir + @get:JsonProperty("child_dirs") val childDirs: List, + @get:JsonProperty("child_exercises") val childExercises: List, ) data class ExerciseResp( - @JsonProperty("exercise_id") val exerciseId: String, - @JsonProperty("dir_id") val implicitDirId: String, - @JsonProperty("title") val title: String, - @JsonProperty("effective_access") val effectiveAccess: DirAccessLevel, - @JsonProperty("is_shared") val isShared: Boolean, - @JsonProperty("grader_type") val graderType: GraderType, - @JsonProperty("courses_count") val coursesCount: Int, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("created_by") val createdBy: String, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("modified_at") val modifiedAt: DateTime, - @JsonProperty("modified_by") val modifiedBy: String, + @get:JsonProperty("exercise_id") val exerciseId: String, + @get:JsonProperty("dir_id") val implicitDirId: String, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("effective_access") val effectiveAccess: DirAccessLevel, + @get:JsonProperty("is_shared") val isShared: Boolean, + @get:JsonProperty("grader_type") val graderType: GraderType, + @get:JsonProperty("courses_count") val coursesCount: Int, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("created_by") val createdBy: String, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("modified_at") val modifiedAt: DateTime, + @get:JsonProperty("modified_by") val modifiedBy: String, ) data class DirResp( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String, - @JsonProperty("effective_access") val effectiveAccess: DirAccessLevel, - @JsonProperty("is_shared") val isShared: Boolean, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("modified_at") val modifiedAt: DateTime, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String, + @get:JsonProperty("effective_access") val effectiveAccess: DirAccessLevel, + @get:JsonProperty("is_shared") val isShared: Boolean, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("modified_at") val modifiedAt: DateTime, ) @@ -204,7 +204,8 @@ class ReadDirController { } private fun getPotentialDirs(dirId: Long?, caller: EasyUser): List { - return Dir.leftJoin((GroupDirAccess innerJoin Group innerJoin AccountGroup), + return Dir.leftJoin( + (GroupDirAccess innerJoin Group innerJoin AccountGroup), onColumn = { Dir.id }, otherColumn = { GroupDirAccess.dir }, additionalConstraint = { AccountGroup.account.eq(caller.id) } @@ -246,7 +247,8 @@ class ReadDirController { // Get direct accesses (>= PR) and anyAccess for this dir in one query // leftJoin because there might be 0 accesses on it - val directAccesses = Dir.leftJoin((GroupDirAccess innerJoin Group), { Dir.id }, { GroupDirAccess.dir }, + val directAccesses = Dir.leftJoin( + (GroupDirAccess innerJoin Group), { Dir.id }, { GroupDirAccess.dir }, { GroupDirAccess.level.greaterEq(DirAccessLevel.PR) }) .select(Dir.anyAccess, Group.isImplicit) .where { Dir.id.eq(dirId) } diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirAccesses.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirAccesses.kt index 412ec841..28566d2b 100755 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirAccesses.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirAccesses.kt @@ -10,7 +10,7 @@ import core.ems.service.assertDirExists import core.ems.service.getAccountFromImplicitGroup import core.ems.service.getDirectGroupDirAccesses import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -26,44 +26,44 @@ class ReadDirAccesses { // Include all direct accesses and only effective inherited accesses data class Resp( - @JsonProperty("direct_any") val directAnyAccess: AnyAccessResp?, - @JsonProperty("direct_accounts") val directAccountAccesses: List, - @JsonProperty("direct_groups") val directGroupAccesses: List, - @JsonProperty("inherited_any") val inheritedAnyAccess: AnyAccessResp?, - @JsonProperty("inherited_accounts") val inheritedAccountAccesses: List, - @JsonProperty("inherited_groups") val inheritedGroupAccesses: List, + @get:JsonProperty("direct_any") val directAnyAccess: AnyAccessResp?, + @get:JsonProperty("direct_accounts") val directAccountAccesses: List, + @get:JsonProperty("direct_groups") val directGroupAccesses: List, + @get:JsonProperty("inherited_any") val inheritedAnyAccess: AnyAccessResp?, + @get:JsonProperty("inherited_accounts") val inheritedAccountAccesses: List, + @get:JsonProperty("inherited_groups") val inheritedGroupAccesses: List, ) data class AnyAccessResp( - @JsonProperty("access") val access: DirAccessLevel, - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("inherited_from") val inheritedFrom: InheritingDirResp?, + @get:JsonProperty("access") val access: DirAccessLevel, + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("inherited_from") val inheritedFrom: InheritingDirResp?, ) data class AccountAccessResp( - @JsonProperty("username") val username: String, - @JsonProperty("given_name") val givenName: String, - @JsonProperty("family_name") val familyName: String, - @JsonProperty("email") val email: String?, - @JsonProperty("group_id") val implicitGroupId: String, - @JsonProperty("access") val access: DirAccessLevel, + @get:JsonProperty("username") val username: String, + @get:JsonProperty("given_name") val givenName: String, + @get:JsonProperty("family_name") val familyName: String, + @get:JsonProperty("email") val email: String?, + @get:JsonProperty("group_id") val implicitGroupId: String, + @get:JsonProperty("access") val access: DirAccessLevel, // TODO: modified_at - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("inherited_from") val inheritedFrom: InheritingDirResp?, + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("inherited_from") val inheritedFrom: InheritingDirResp?, ) data class GroupAccessResp( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String, - @JsonProperty("access") val access: DirAccessLevel, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String, + @get:JsonProperty("access") val access: DirAccessLevel, // TODO: modified_at - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("inherited_from") val inheritedFrom: InheritingDirResp?, + @get:JsonInclude(JsonInclude.Include.NON_NULL) + @get:JsonProperty("inherited_from") val inheritedFrom: InheritingDirResp?, ) data class InheritingDirResp( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirParents.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirParents.kt index 634be9c2..6266a398 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirParents.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/ReadDirParents.kt @@ -7,8 +7,10 @@ import core.db.DirAccessLevel import core.ems.service.access_control.assertAccess import core.ems.service.access_control.libraryDir import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -22,12 +24,12 @@ class ReadDirParentsController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("parents") val parents: List, + @get:JsonProperty("parents") val parents: List, ) data class ParentDirResp( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String, + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/dir/UpdateDir.kt b/core/src/main/kotlin/core/ems/service/exercise/dir/UpdateDir.kt index 57b18f96..b2f8dbf0 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/dir/UpdateDir.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/dir/UpdateDir.kt @@ -10,23 +10,24 @@ import core.ems.service.assertDirExists import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size -private val log = KotlinLogging.logger {} - @RestController @RequestMapping("/v2") class UpdateDir { + private val log = KotlinLogging.logger {} + data class Req( - @JsonProperty("name") @field:Size(max = 100) val name: String?, + @param:JsonProperty("name") @field:Size(max = 100) val name: String?, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/exercise/exceptions/PutCourseExerciseExceptions.kt b/core/src/main/kotlin/core/ems/service/exercise/exceptions/PutCourseExerciseExceptions.kt index 0e34fe5c..09f3e9cd 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/exceptions/PutCourseExerciseExceptions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/exceptions/PutCourseExerciseExceptions.kt @@ -1,11 +1,9 @@ package core.ems.service.exercise.exceptions import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize import core.conf.security.EasyUser import core.db.CourseExerciseExceptionGroup import core.db.CourseExerciseExceptionStudent -import core.db.insertOrUpdate import core.ems.service.access_control.assertAccess import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.canStudentAccessCourse @@ -15,12 +13,14 @@ import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError import core.util.DateTimeDeserializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.upsert import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid +import tools.jackson.databind.annotation.JsonDeserialize @RestController @@ -28,25 +28,25 @@ import javax.validation.Valid class PutCourseExerciseExceptions { private val log = KotlinLogging.logger {} - data class ExceptionValueReq(@JsonDeserialize(using = DateTimeDeserializer::class) @JsonProperty("value") val value: DateTime?) + data class ExceptionValueReq(@param:JsonDeserialize(using = DateTimeDeserializer::class) @param:JsonProperty("value") val value: DateTime?) data class ReqExceptionStudent( - @JsonProperty("student_id") val studentId: String, - @JsonProperty("soft_deadline") val softDeadline: ExceptionValueReq?, - @JsonProperty("hard_deadline") val hardDeadline: ExceptionValueReq?, - @JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueReq?, + @param:JsonProperty("student_id") val studentId: String, + @param:JsonProperty("soft_deadline") val softDeadline: ExceptionValueReq?, + @param:JsonProperty("hard_deadline") val hardDeadline: ExceptionValueReq?, + @param:JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueReq?, ) data class ReqExceptionGroup( - @JsonProperty("group_id") val groupId: Long, - @JsonProperty("soft_deadline") val softDeadline: ExceptionValueReq?, - @JsonProperty("hard_deadline") val hardDeadline: ExceptionValueReq?, - @JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueReq?, + @param:JsonProperty("group_id") val groupId: Long, + @param:JsonProperty("soft_deadline") val softDeadline: ExceptionValueReq?, + @param:JsonProperty("hard_deadline") val hardDeadline: ExceptionValueReq?, + @param:JsonProperty("student_visible_from") val studentVisibleFrom: ExceptionValueReq?, ) data class Req( - @JsonProperty("exception_students") @field:Valid val exceptionStudents: List?, - @JsonProperty("exception_groups") @field:Valid val exceptionGroups: List? + @param:JsonProperty("exception_students") @field:Valid val exceptionStudents: List?, + @param:JsonProperty("exception_groups") @field:Valid val exceptionGroups: List? ) @@ -84,9 +84,13 @@ class PutCourseExerciseExceptions { ReqError.STUDENT_NOT_ON_COURSE ) - CourseExerciseExceptionStudent.insertOrUpdate( - listOf(CourseExerciseExceptionStudent.courseExercise, CourseExerciseExceptionStudent.student), - listOf(CourseExerciseExceptionStudent.courseExercise, CourseExerciseExceptionStudent.student), + CourseExerciseExceptionStudent.upsert( + CourseExerciseExceptionStudent.courseExercise, + CourseExerciseExceptionStudent.student, + onUpdateExclude = listOf( + CourseExerciseExceptionStudent.courseExercise, + CourseExerciseExceptionStudent.student + ) ) { it[CourseExerciseExceptionStudent.courseExercise] = courseExId it[CourseExerciseExceptionStudent.student] = ex.studentId @@ -102,9 +106,13 @@ class PutCourseExerciseExceptions { exceptionGroups?.forEach { ex -> assertGroupExistsOnCourse(ex.groupId, courseId) - CourseExerciseExceptionGroup.insertOrUpdate( - listOf(CourseExerciseExceptionGroup.courseExercise, CourseExerciseExceptionGroup.courseGroup), - listOf(CourseExerciseExceptionGroup.courseExercise, CourseExerciseExceptionGroup.courseGroup), + CourseExerciseExceptionGroup.upsert( + CourseExerciseExceptionGroup.courseExercise, + CourseExerciseExceptionGroup.courseGroup, + onUpdateExclude = listOf( + CourseExerciseExceptionGroup.courseExercise, + CourseExerciseExceptionGroup.courseGroup + ) ) { it[CourseExerciseExceptionGroup.courseExercise] = courseExId it[CourseExerciseExceptionGroup.courseGroup] = ex.groupId diff --git a/core/src/main/kotlin/core/ems/service/exercise/exceptions/RemoveCourseExerciseExceptions.kt b/core/src/main/kotlin/core/ems/service/exercise/exceptions/RemoveCourseExerciseExceptions.kt index fa4a5577..301d7a4e 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/exceptions/RemoveCourseExerciseExceptions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/exceptions/RemoveCourseExerciseExceptions.kt @@ -8,15 +8,15 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.assertCourseExerciseIsOnCourse import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid @RestController @@ -25,8 +25,8 @@ class RemoveCourseExerciseExceptions { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("exception_students") @field:Valid val exceptionStudents: List?, - @JsonProperty("exception_groups") @field:Valid val exceptionGroups: List? + @param:JsonProperty("exception_students") @field:Valid val exceptionStudents: List?, + @param:JsonProperty("exception_groups") @field:Valid val exceptionGroups: List? ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/file/AdminReadFileMetadata.kt b/core/src/main/kotlin/core/ems/service/file/AdminReadFileMetadata.kt index c60b7ae7..13d62285 100644 --- a/core/src/main/kotlin/core/ems/service/file/AdminReadFileMetadata.kt +++ b/core/src/main/kotlin/core/ems/service/file/AdminReadFileMetadata.kt @@ -1,7 +1,7 @@ package core.ems.service.file import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.StoredFile import core.db.StoredFile.article @@ -12,9 +12,9 @@ import core.db.StoredFile.owner import core.db.StoredFile.sizeBytes import core.db.StoredFile.type import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -27,18 +27,18 @@ import org.springframework.web.bind.annotation.RestController class ReadFileMetadataController { private val log = KotlinLogging.logger {} - data class Resp(@JsonProperty("files") val files: List) + data class Resp(@get:JsonProperty("files") val files: List) data class RespFile( - @JsonProperty("id") val id: String, - @JsonProperty("article_id") val articleId: String, - @JsonProperty("exercise_id") val exerciseId: String, - @JsonProperty("filename") val filename: String, - @JsonProperty("type") val type: String, - @JsonProperty("size_bytes") val sizeBytes: Long, - @JsonSerialize(using = DateTimeSerializer::class) - @JsonProperty("created_at") val createdAt: DateTime, - @JsonProperty("created_by") val createdBy: String + @get:JsonProperty("id") val id: String, + @get:JsonProperty("article_id") val articleId: String, + @get:JsonProperty("exercise_id") val exerciseId: String, + @get:JsonProperty("filename") val filename: String, + @get:JsonProperty("type") val type: String, + @get:JsonProperty("size_bytes") val sizeBytes: Long, + @get:JsonSerialize(using = DateTimeSerializer::class) + @get:JsonProperty("created_at") val createdAt: DateTime, + @get:JsonProperty("created_by") val createdBy: String ) @Secured("ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/file/CommonReadStoredFile.kt b/core/src/main/kotlin/core/ems/service/file/CommonReadStoredFile.kt index a5fa1fbb..2c373bce 100644 --- a/core/src/main/kotlin/core/ems/service/file/CommonReadStoredFile.kt +++ b/core/src/main/kotlin/core/ems/service/file/CommonReadStoredFile.kt @@ -5,14 +5,16 @@ import core.db.StoredFile import core.db.StoredFile.data import core.db.StoredFile.filename import core.db.StoredFile.type -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.servlet.http.HttpServletResponse +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.servlet.http.HttpServletResponse @RestController diff --git a/core/src/main/kotlin/core/ems/service/file/TeacherUploadFile.kt b/core/src/main/kotlin/core/ems/service/file/TeacherUploadFile.kt index 73f9a86a..982c5f4d 100644 --- a/core/src/main/kotlin/core/ems/service/file/TeacherUploadFile.kt +++ b/core/src/main/kotlin/core/ems/service/file/TeacherUploadFile.kt @@ -3,10 +3,13 @@ package core.ems.service.file import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.StoredFile -import mu.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging import org.apache.tika.Tika -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping @@ -15,9 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import java.security.MessageDigest import java.util.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -26,16 +26,16 @@ class UploadStoredFiledController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("filename", required = true) + @param:JsonProperty("filename", required = true) @field:NotBlank @field:Size(max = 259) val filename: String, @field:NotBlank @field:Size(max = 134640000) // Approx 100,98 MB - @JsonProperty("data", required = true) val data: String + @param:JsonProperty("data", required = true) val data: String ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@param:JsonProperty("id") val id: String) @Secured("ROLE_ADMIN", "ROLE_TEACHER") @PostMapping("/files") diff --git a/core/src/main/kotlin/core/ems/service/group/CreateGroup.kt b/core/src/main/kotlin/core/ems/service/group/CreateGroup.kt index 11dc5474..d0c4f990 100644 --- a/core/src/main/kotlin/core/ems/service/group/CreateGroup.kt +++ b/core/src/main/kotlin/core/ems/service/group/CreateGroup.kt @@ -5,20 +5,20 @@ import core.conf.security.EasyUser import core.db.Account import core.db.AccountGroup import core.db.Group -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.insertAndGetId -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -27,11 +27,11 @@ class CreateGroupController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("name") @field:NotBlank @field:Size(max = 100) val name: String, - @JsonProperty("color") @field:Size(min = 1, max = 100) val color: String?, + @param:JsonProperty("name") @field:NotBlank @field:Size(max = 100) val name: String, + @param:JsonProperty("color") @field:Size(min = 1, max = 100) val color: String?, ) - data class Resp(@JsonProperty("id") val id: String) + data class Resp(@get:JsonProperty("id") val id: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PostMapping("/groups") diff --git a/core/src/main/kotlin/core/ems/service/groups.kt b/core/src/main/kotlin/core/ems/service/groups.kt index 7d4ed6e2..b6614e7b 100644 --- a/core/src/main/kotlin/core/ems/service/groups.kt +++ b/core/src/main/kotlin/core/ems/service/groups.kt @@ -4,10 +4,12 @@ import core.conf.security.EasyUser import core.db.Account import core.db.AccountGroup import core.db.Group -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.andWhere +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime @@ -39,7 +41,12 @@ fun getImplicitGroupFromAccount(accountId: String): Long = transaction { } data class AccountFromImplicitGroup( - val id: String, val givenName: String, val familyName: String, val email: String, val createdAt: DateTime, val lastSeen: DateTime, + val id: String, + val givenName: String, + val familyName: String, + val email: String, + val createdAt: DateTime, + val lastSeen: DateTime, ) fun getAccountFromImplicitGroup(implicitGroupId: Long): AccountFromImplicitGroup = transaction { diff --git a/core/src/main/kotlin/core/ems/service/management/AdminCreateManagementNotification.kt b/core/src/main/kotlin/core/ems/service/management/AdminCreateManagementNotification.kt index 1c3474cd..37d0c818 100644 --- a/core/src/main/kotlin/core/ems/service/management/AdminCreateManagementNotification.kt +++ b/core/src/main/kotlin/core/ems/service/management/AdminCreateManagementNotification.kt @@ -3,17 +3,17 @@ package core.ems.service.management import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.ManagementNotification -import mu.KotlinLogging -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -22,7 +22,7 @@ class AdminCreateManagementNotificationsController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("message", required = true) + @param:JsonProperty("message", required = true) @field:NotBlank @field:Size(max = 1000) val message: String ) diff --git a/core/src/main/kotlin/core/ems/service/management/AdminDeleteManagementNotification.kt b/core/src/main/kotlin/core/ems/service/management/AdminDeleteManagementNotification.kt index d4081115..8d5ad407 100644 --- a/core/src/main/kotlin/core/ems/service/management/AdminDeleteManagementNotification.kt +++ b/core/src/main/kotlin/core/ems/service/management/AdminDeleteManagementNotification.kt @@ -4,11 +4,11 @@ import core.conf.security.EasyUser import core.db.ManagementNotification import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/core/src/main/kotlin/core/ems/service/management/AdminReadManagementNotification.kt b/core/src/main/kotlin/core/ems/service/management/AdminReadManagementNotification.kt index 62fd057f..ec8efe7d 100644 --- a/core/src/main/kotlin/core/ems/service/management/AdminReadManagementNotification.kt +++ b/core/src/main/kotlin/core/ems/service/management/AdminReadManagementNotification.kt @@ -4,10 +4,10 @@ import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.ManagementNotification -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -20,13 +20,13 @@ class AdminReadManagementNotificationsController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("messages") - @JsonInclude(JsonInclude.Include.NON_NULL) val messages: List + @get:JsonProperty("messages") + @get:JsonInclude(JsonInclude.Include.NON_NULL) val messages: List ) data class MessageResp( - @JsonProperty("id") val messageId: String, - @JsonProperty("message") val message: String + @get:JsonProperty("id") val messageId: String, + @get:JsonProperty("message") val message: String ) @Secured("ROLE_ADMIN") @@ -39,14 +39,15 @@ class AdminReadManagementNotificationsController { } private fun selectMessages(): Resp = transaction { - Resp(ManagementNotification - .selectAll() - .orderBy(ManagementNotification.id, SortOrder.DESC) - .map { - MessageResp( - it[ManagementNotification.id].value.toString(), it[ManagementNotification.message] - ) - }) + Resp( + ManagementNotification + .selectAll() + .orderBy(ManagementNotification.id, SortOrder.DESC) + .map { + MessageResp( + it[ManagementNotification.id].value.toString(), it[ManagementNotification.message] + ) + }) } } diff --git a/core/src/main/kotlin/core/ems/service/management/AdminReadSystemConf.kt b/core/src/main/kotlin/core/ems/service/management/AdminReadSystemConf.kt index fc585732..4c0006e3 100644 --- a/core/src/main/kotlin/core/ems/service/management/AdminReadSystemConf.kt +++ b/core/src/main/kotlin/core/ems/service/management/AdminReadSystemConf.kt @@ -3,10 +3,10 @@ package core.ems.service.management import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.SystemConfiguration -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -18,11 +18,11 @@ import org.springframework.web.bind.annotation.RestController class AdminReadSystemConf { private val log = KotlinLogging.logger {} - data class Resp(@JsonProperty("properties") val properties: List) + data class Resp(@get:JsonProperty("properties") val properties: List) data class PropertyResp( - @JsonProperty("key") val key: String, - @JsonProperty("value") val value: String? + @get:JsonProperty("key") val key: String, + @get:JsonProperty("value") val value: String? ) @Secured("ROLE_ADMIN") @@ -33,10 +33,11 @@ class AdminReadSystemConf { } private fun selectProperties(): Resp = transaction { - Resp(SystemConfiguration - .selectAll() - .orderBy(SystemConfiguration.id, SortOrder.ASC) - .map { PropertyResp(it[SystemConfiguration.id].toString(), it[SystemConfiguration.value]) }) + Resp( + SystemConfiguration + .selectAll() + .orderBy(SystemConfiguration.id, SortOrder.ASC) + .map { PropertyResp(it[SystemConfiguration.id].toString(), it[SystemConfiguration.value]) }) } } diff --git a/core/src/main/kotlin/core/ems/service/management/AdminUpdateManagementNotification.kt b/core/src/main/kotlin/core/ems/service/management/AdminUpdateManagementNotification.kt index 0dde91d1..85ef9324 100644 --- a/core/src/main/kotlin/core/ems/service/management/AdminUpdateManagementNotification.kt +++ b/core/src/main/kotlin/core/ems/service/management/AdminUpdateManagementNotification.kt @@ -5,15 +5,16 @@ import core.conf.security.EasyUser import core.db.ManagementNotification import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -22,7 +23,7 @@ class AdminUpdateManagementNotificationsController { private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("message", required = true) + @param:JsonProperty("message", required = true) @field:NotBlank @field:Size(max = 1000) val message: String ) @@ -40,7 +41,7 @@ class AdminUpdateManagementNotificationsController { updateMessage(dto, notificationId) } - private fun updateMessage(dto: AdminUpdateManagementNotificationsController.Req, notificationId: Long) { + private fun updateMessage(dto: Req, notificationId: Long) { transaction { val messageExists = diff --git a/core/src/main/kotlin/core/ems/service/management/CommonReadManagementNotification.kt b/core/src/main/kotlin/core/ems/service/management/CommonReadManagementNotification.kt index 97a75469..1cd3740c 100644 --- a/core/src/main/kotlin/core/ems/service/management/CommonReadManagementNotification.kt +++ b/core/src/main/kotlin/core/ems/service/management/CommonReadManagementNotification.kt @@ -4,10 +4,10 @@ import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.ManagementNotification -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping @@ -20,11 +20,11 @@ class CommonReadManagementNotificationsController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("messages") - @JsonInclude(JsonInclude.Include.NON_NULL) val messages: List + @get:JsonProperty("messages") + @get:JsonInclude(JsonInclude.Include.NON_NULL) val messages: List ) - data class MessageResp(@JsonProperty("message") val message: String) + data class MessageResp(@get:JsonProperty("message") val message: String) @Secured("ROLE_ADMIN", "ROLE_TEACHER", "ROLE_STUDENT") @GetMapping("/management/common/notifications") diff --git a/core/src/main/kotlin/core/ems/service/management/report_client_log.kt b/core/src/main/kotlin/core/ems/service/management/report_client_log.kt index d9064ba7..cb10692e 100644 --- a/core/src/main/kotlin/core/ems/service/management/report_client_log.kt +++ b/core/src/main/kotlin/core/ems/service/management/report_client_log.kt @@ -12,10 +12,13 @@ import core.ems.service.management.ReportLogController.ReportClientSysProp.NO_MA import core.ems.service.management.ReportLogController.Req import core.exception.InvalidRequestException import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.scheduling.annotation.Async import org.springframework.web.bind.annotation.PostMapping @@ -23,9 +26,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import java.util.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size private val log = KotlinLogging.logger {} @@ -34,13 +34,13 @@ private val log = KotlinLogging.logger {} class ReportLogController(private val mailService: SendMailService) { data class Req( - @JsonProperty("log_level", required = true) + @param:JsonProperty("log_level", required = true) @field:NotBlank @field:Size(max = 5) val logLevel: String, - @JsonProperty("log_message", required = true) + @param:JsonProperty("log_message", required = true) @field:NotBlank @field:Size(max = 10000) val logMessage: String, - @JsonProperty("client_id", required = true) + @param:JsonProperty("client_id", required = true) @field:NotBlank @field:Size(max = 100) val clientId: String ) diff --git a/core/src/main/kotlin/core/ems/service/markdown_preview.kt b/core/src/main/kotlin/core/ems/service/markdown_preview.kt index 36dc3b1d..006fcb4e 100644 --- a/core/src/main/kotlin/core/ems/service/markdown_preview.kt +++ b/core/src/main/kotlin/core/ems/service/markdown_preview.kt @@ -2,13 +2,13 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser -import mu.KotlinLogging +import jakarta.validation.Valid +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid @RestController @@ -16,7 +16,7 @@ import javax.validation.Valid class MarkdownPreviewController(private val markdownService: MarkdownService) { private val log = KotlinLogging.logger {} - data class ReqResp(@JsonProperty("content") val content: String) + data class ReqResp(@param:JsonProperty("content") @get:JsonProperty("content") val content: String) @Secured("ROLE_TEACHER", "ROLE_ADMIN", "ROLE_STUDENT") @PostMapping("/preview/markdown") diff --git a/core/src/main/kotlin/core/ems/service/moodle/LinkCourseMoodle.kt b/core/src/main/kotlin/core/ems/service/moodle/LinkCourseMoodle.kt index acca408a..53050a26 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/LinkCourseMoodle.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/LinkCourseMoodle.kt @@ -8,14 +8,15 @@ import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException import core.exception.ReqError import core.exception.ResourceLockedException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -27,14 +28,14 @@ class MoodleLinkCourseController( private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("moodle_props") val moodleProps: MoodleReq?, - @JsonProperty("force") val force: Boolean = false, + @param:JsonProperty("moodle_props") val moodleProps: MoodleReq?, + @param:JsonProperty("force") val force: Boolean = false, ) data class MoodleReq( - @JsonProperty("moodle_short_name") @field:NotBlank @field:Size(max = 500) val moodleShortName: String, - @JsonProperty("sync_students") val syncStudents: Boolean, - @JsonProperty("sync_grades") val syncGrades: Boolean, + @param:JsonProperty("moodle_short_name") @field:NotBlank @field:Size(max = 500) val moodleShortName: String, + @param:JsonProperty("sync_students") val syncStudents: Boolean, + @param:JsonProperty("sync_grades") val syncGrades: Boolean, ) @Secured("ROLE_ADMIN") @@ -69,7 +70,7 @@ class MoodleLinkCourseController( linkCourse(courseId, body.moodleProps) } } - } catch (e: ResourceLockedException) { + } catch (_: ResourceLockedException) { log.info { "Cannot change Moodle link, sync is in progress for course $courseId" } throw InvalidRequestException( "Moodle sync is in progress", ReqError.MOODLE_SYNC_IN_PROGRESS, notify = false diff --git a/core/src/main/kotlin/core/ems/service/moodle/ReadCourseMoodleProps.kt b/core/src/main/kotlin/core/ems/service/moodle/ReadCourseMoodleProps.kt index e7097605..1116b81e 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/ReadCourseMoodleProps.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/ReadCourseMoodleProps.kt @@ -6,9 +6,10 @@ import core.db.Course import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq -import mu.KotlinLogging -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -21,14 +22,14 @@ import org.springframework.web.bind.annotation.RestController class ReadCourseMoodlePropsController { private val log = KotlinLogging.logger {} - data class Resp(@JsonProperty("moodle_props") val moodleProps: MoodleResp?) + data class Resp(@get:JsonProperty("moodle_props") val moodleProps: MoodleResp?) data class MoodleResp( - @JsonProperty("moodle_short_name") val shortName: String, - @JsonProperty("students_synced") val studentsSynced: Boolean, - @JsonProperty("sync_students_in_progress") val studentsSyncInProgress: Boolean, - @JsonProperty("grades_synced") val gradesSynced: Boolean, - @JsonProperty("sync_grades_in_progress") val gradesSyncInProgress: Boolean, + @get:JsonProperty("moodle_short_name") val shortName: String, + @get:JsonProperty("students_synced") val studentsSynced: Boolean, + @get:JsonProperty("sync_students_in_progress") val studentsSyncInProgress: Boolean, + @get:JsonProperty("grades_synced") val gradesSynced: Boolean, + @get:JsonProperty("sync_grades_in_progress") val gradesSyncInProgress: Boolean, ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") diff --git a/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllGrades.kt b/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllGrades.kt index 41569494..4f3aead1 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllGrades.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllGrades.kt @@ -5,7 +5,7 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.exception.ResourceLockedException -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping @@ -34,7 +34,7 @@ class MoodleAllGradesSyncController(val moodleGradesSyncService: MoodleGradesSyn return try { moodleGradesSyncService.syncCourseGradesToMoodle(courseId) MoodleSyncedOperationResponse(MoodleSyncStatus.FINISHED) - } catch (e: ResourceLockedException) { + } catch (_: ResourceLockedException) { log.info { "Moodle sync grades already in progress for course $courseId" } MoodleSyncedOperationResponse(MoodleSyncStatus.IN_PROGRESS) } diff --git a/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllStudents.kt b/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllStudents.kt index 27ce3ca2..23466032 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllStudents.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/SyncMoodleAllStudents.kt @@ -5,7 +5,7 @@ import core.ems.service.access_control.assertAccess import core.ems.service.access_control.teacherOnCourse import core.ems.service.idToLongOrInvalidReq import core.exception.ResourceLockedException -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping @@ -34,7 +34,7 @@ class MoodleAllStudentsSyncController(val moodleStudentsSyncService: MoodleStude return try { moodleStudentsSyncService.syncStudents(courseId) MoodleSyncedOperationResponse(MoodleSyncStatus.FINISHED) - } catch (e: ResourceLockedException) { + } catch (_: ResourceLockedException) { log.info { "Moodle sync students already in progress for course $courseId" } MoodleSyncedOperationResponse(MoodleSyncStatus.IN_PROGRESS) } diff --git a/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt b/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt index b4ff205d..4a96a89b 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt @@ -1,17 +1,19 @@ package core.ems.service.moodle import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import core.db.* import core.ems.service.selectLatestSubmissionsForExercise import core.exception.InvalidRequestException import core.exception.ReqError import core.exception.ResourceLockedException import core.util.DBBackedLock -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.isNull +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders @@ -22,6 +24,7 @@ import org.springframework.stereotype.Service import org.springframework.util.LinkedMultiValueMap import org.springframework.util.MultiValueMap import org.springframework.web.client.RestTemplate +import tools.jackson.module.kotlin.jacksonObjectMapper @Service @@ -35,20 +38,20 @@ class MoodleGradesSyncService { data class MoodleReq( - @JsonProperty("shortname") val shortname: String, - @JsonProperty("exercises") val exercises: List + @param:JsonProperty("shortname") val shortname: String, + @param:JsonProperty("exercises") val exercises: List ) data class MoodleReqExercise( - @JsonProperty("idnumber") val idnumber: String, - @JsonProperty("title") val title: String, - @JsonProperty("grades") val grades: List + @param:JsonProperty("idnumber") val idnumber: String, + @param:JsonProperty("title") val title: String, + @param:JsonProperty("grades") val grades: List ) data class MoodleReqGrade( - @JsonProperty("username") val username: String, - @JsonProperty("grade") val grade: Int + @param:JsonProperty("username") val username: String, + @param:JsonProperty("grade") val grade: Int ) @@ -126,7 +129,7 @@ class MoodleGradesSyncService { RestTemplate().postForEntity(moodleGradeUrl, request, String::class.java) if (responseEntity.statusCode.value() != 200) { - log.error { "Moodle grade syncing error ${responseEntity.statusCodeValue} with data $req" } + log.error { "Moodle grade syncing error ${responseEntity.statusCode.value()} with data $req" } throw InvalidRequestException( "Grade syncing with Moodle failed due to error code in response.", ReqError.MOODLE_GRADE_SYNC_ERROR, @@ -210,7 +213,7 @@ class MoodleGradesSyncService { private fun selectLatestGradeForSubmission(submissionId: Long, courseId: Long): MoodleReqGrade? = (Submission innerJoin Account innerJoin StudentCourseAccess) .select(StudentCourseAccess.moodleUsername, Account.id, Submission.grade) - .where { (Submission.id eq submissionId) and (StudentCourseAccess.course eq courseId)} + .where { (Submission.id eq submissionId) and (StudentCourseAccess.course eq courseId) } .map { val moodleUsername = it[StudentCourseAccess.moodleUsername] val grade = it[Submission.grade] diff --git a/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt b/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt index a4b5120d..e72f18c8 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt @@ -9,12 +9,11 @@ import core.exception.ReqError import core.exception.ResourceLockedException import core.util.DBBackedLock import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.notInList -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.* +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpEntity @@ -26,13 +25,12 @@ import org.springframework.util.LinkedMultiValueMap import org.springframework.util.MultiValueMap import org.springframework.web.client.RestTemplate -private val log = KotlinLogging.logger {} - data class MoodleSyncedStudents(val syncedPendingStudents: Int) @Service class MoodleStudentsSyncService(val mailService: SendMailService) { + private val log = KotlinLogging.logger {} @Value("\${easy.core.moodle-sync.users.url}") private lateinit var moodleSyncUrl: String @@ -59,20 +57,20 @@ class MoodleStudentsSyncService(val mailService: SendMailService) { private data class MoodleRespStudent( - @JsonProperty("username") val username: String, - @JsonProperty("firstname") val firstname: String, - @JsonProperty("lastname") val lastname: String, - @JsonProperty("email") val email: String, - @JsonProperty("groups", required = false) val groups: List? + @get:JsonProperty("username") val username: String, + @get:JsonProperty("firstname") val firstname: String, + @get:JsonProperty("lastname") val lastname: String, + @get:JsonProperty("email") val email: String, + @get:JsonProperty("groups", required = false) val groups: List? ) private data class MoodleRespGroup( - @JsonProperty("id") val id: String, - @JsonProperty("name") val name: String + @get:JsonProperty("id") val id: String, + @get:JsonProperty("name") val name: String ) private data class MoodleResponse( - @JsonProperty("students") val students: List + @get:JsonProperty("students") val students: List ) private fun queryStudents(moodleShortName: String): MoodleResponse { @@ -88,11 +86,11 @@ class MoodleStudentsSyncService(val mailService: SendMailService) { val responseEntity = RestTemplate().postForEntity(moodleSyncUrl, request, MoodleResponse::class.java) if (responseEntity.statusCode.value() != 200) { - log.error { "Moodle linking error ${responseEntity.statusCodeValue} with request $request" } + log.error { "Moodle linking error ${responseEntity.statusCode.value()} with request $request" } throw InvalidRequestException( "Course linking with Moodle failed due to error code in response.", ReqError.MOODLE_LINKING_ERROR, - "Moodle response" to responseEntity.statusCodeValue.toString(), + "Moodle response" to responseEntity.statusCode.value().toString(), notify = true ) } @@ -249,7 +247,7 @@ class MoodleStudentsSyncService(val mailService: SendMailService) { try { syncStudents(courseId) - } catch (e: ResourceLockedException) { + } catch (_: ResourceLockedException) { log.warn { "Cannot Moodle sync students on course $courseId because it's locked" } } } diff --git a/core/src/main/kotlin/core/ems/service/moodle/moodle_utils.kt b/core/src/main/kotlin/core/ems/service/moodle/moodle_utils.kt index 1ad01748..1f7a9671 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/moodle_utils.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/moodle_utils.kt @@ -3,12 +3,12 @@ package core.ems.service.moodle import com.fasterxml.jackson.annotation.JsonProperty import core.db.Course import core.exception.InvalidRequestException -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.transactions.transaction -data class MoodleSyncedOperationResponse( - @JsonProperty("status") val status: MoodleSyncStatus, -) +data class MoodleSyncedOperationResponse(@get:JsonProperty("status") val status: MoodleSyncStatus) enum class MoodleSyncStatus { FINISHED, IN_PROGRESS } diff --git a/core/src/main/kotlin/core/ems/service/register/account_checkin.kt b/core/src/main/kotlin/core/ems/service/register/account_checkin.kt index fc7115de..78ffbda7 100644 --- a/core/src/main/kotlin/core/ems/service/register/account_checkin.kt +++ b/core/src/main/kotlin/core/ems/service/register/account_checkin.kt @@ -2,17 +2,21 @@ package core.ems.service.register import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser -import core.db.* +import core.db.Account +import core.db.AccountGroup +import core.db.Group import core.ems.service.cache.CachingService import core.ems.service.cache.countTotalUsersCache -import core.exception.InvalidRequestException -import core.exception.ReqError -import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.insertAndGetId +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping @@ -20,20 +24,17 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import java.util.* -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size -private val log = KotlinLogging.logger {} @RestController @RequestMapping("/v2") -class UpdateAccountController(val cachingService: CachingService, private val mailService: SendMailService) { +class UpdateAccountController(val cachingService: CachingService) { + private val log = KotlinLogging.logger {} data class PersonalDataBody( - @JsonProperty("first_name", required = true) + @param:JsonProperty("first_name", required = true) @field:NotBlank @field:Size(max = 100) val firstName: String, - @JsonProperty("last_name", required = true) + @param:JsonProperty("last_name", required = true) @field:NotBlank @field:Size(max = 100) val lastName: String ) diff --git a/core/src/main/kotlin/core/ems/service/request_logger.kt b/core/src/main/kotlin/core/ems/service/request_logger.kt index c567b352..7f305085 100644 --- a/core/src/main/kotlin/core/ems/service/request_logger.kt +++ b/core/src/main/kotlin/core/ems/service/request_logger.kt @@ -1,6 +1,6 @@ package core.ems.service -//import mu.KotlinLogging +//import io.github.oshai.kotlinlogging.KotlinLogging //import org.springframework.context.annotation.Configuration //import org.springframework.lang.Nullable //import org.springframework.stereotype.Component @@ -12,12 +12,12 @@ package core.ems.service //import org.springframework.web.util.ContentCachingResponseWrapper //import org.springframework.web.util.WebUtils //import java.io.IOException -//import javax.servlet.FilterChain -//import javax.servlet.ServletException -//import javax.servlet.ServletRequest -//import javax.servlet.ServletResponse -//import javax.servlet.http.HttpServletRequest -//import javax.servlet.http.HttpServletResponse +//import jakarta.servlet.FilterChain +//import jakarta.servlet.ServletException +//import jakarta.servlet.ServletRequest +//import jakarta.servlet.ServletResponse +//import jakarta.servlet.http.HttpServletRequest +//import jakarta.servlet.http.HttpServletResponse // //private val log = KotlinLogging.logger {} // diff --git a/core/src/main/kotlin/core/ems/service/request_util.kt b/core/src/main/kotlin/core/ems/service/request_util.kt index 0c78cabc..1ac7f5c6 100644 --- a/core/src/main/kotlin/core/ems/service/request_util.kt +++ b/core/src/main/kotlin/core/ems/service/request_util.kt @@ -2,7 +2,7 @@ package core.ems.service import core.exception.InvalidRequestException import core.exception.ReqError -import javax.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletRequest fun String.idToLongOrInvalidReq(): Long = this.toLongOrNull() ?: throw InvalidRequestException( diff --git a/core/src/main/kotlin/core/ems/service/snippet/TeacherCreateFeedbackSnippet.kt b/core/src/main/kotlin/core/ems/service/snippet/TeacherCreateFeedbackSnippet.kt index 93e521a5..0be30e08 100644 --- a/core/src/main/kotlin/core/ems/service/snippet/TeacherCreateFeedbackSnippet.kt +++ b/core/src/main/kotlin/core/ems/service/snippet/TeacherCreateFeedbackSnippet.kt @@ -4,22 +4,24 @@ import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.db.FeedbackSnippet import core.ems.service.MarkdownService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.core.inList +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.select +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.Size @RestController @@ -28,7 +30,7 @@ class TeacherCreateFeedbackSnippetController(val markdownService: MarkdownServic private val log = KotlinLogging.logger {} data class Req( - @JsonProperty("snippet_md", required = true) + @param:JsonProperty("snippet_md", required = true) @field:Size(max = 300000) @field:NotBlank val snippetMd: String diff --git a/core/src/main/kotlin/core/ems/service/snippet/TeacherReadFeedbackSnippets.kt b/core/src/main/kotlin/core/ems/service/snippet/TeacherReadFeedbackSnippets.kt index e4987df9..f5dd55c1 100644 --- a/core/src/main/kotlin/core/ems/service/snippet/TeacherReadFeedbackSnippets.kt +++ b/core/src/main/kotlin/core/ems/service/snippet/TeacherReadFeedbackSnippets.kt @@ -2,14 +2,15 @@ package core.ems.service.snippet import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import core.conf.security.EasyUser import core.db.FeedbackSnippet import core.util.DateTimeSerializer -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SortOrder -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.SortOrder +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.GetMapping @@ -23,15 +24,15 @@ class TeacherReadFeedbackSnippetsController { private val log = KotlinLogging.logger {} data class Resp( - @JsonProperty("snippets") - @JsonInclude(JsonInclude.Include.NON_NULL) val snippets: List + @get:JsonProperty("snippets") + @get:JsonInclude(JsonInclude.Include.NON_NULL) val snippets: List ) data class SnippetResp( - @JsonProperty("id") val id: String, - @JsonProperty("snippet_md") val snippetMd: String, - @JsonProperty("snippet_html") val snippetHtml: String, - @JsonProperty("created_at") @JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime + @get:JsonProperty("id") val id: String, + @get:JsonProperty("snippet_md") val snippetMd: String, + @get:JsonProperty("snippet_html") val snippetHtml: String, + @get:JsonProperty("created_at") @get:JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime ) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @@ -44,17 +45,18 @@ class TeacherReadFeedbackSnippetsController { } private fun selectSnippets(teacherId: String): Resp = transaction { - Resp(FeedbackSnippet - .selectAll() - .where { FeedbackSnippet.teacher eq teacherId } - .orderBy(FeedbackSnippet.createdAt, SortOrder.DESC) - .map { - SnippetResp( - it[FeedbackSnippet.id].value.toString(), - it[FeedbackSnippet.snippetMd], - it[FeedbackSnippet.snippetHtml], - it[FeedbackSnippet.createdAt], - ) - }) + Resp( + FeedbackSnippet + .selectAll() + .where { FeedbackSnippet.teacher eq teacherId } + .orderBy(FeedbackSnippet.createdAt, SortOrder.DESC) + .map { + SnippetResp( + it[FeedbackSnippet.id].value.toString(), + it[FeedbackSnippet.snippetMd], + it[FeedbackSnippet.snippetHtml], + it[FeedbackSnippet.createdAt], + ) + }) } } diff --git a/core/src/main/kotlin/core/ems/service/snippet/TeacherUpdateFeedbackSnippet.kt b/core/src/main/kotlin/core/ems/service/snippet/TeacherUpdateFeedbackSnippet.kt index c2a72725..9de0e9e0 100644 --- a/core/src/main/kotlin/core/ems/service/snippet/TeacherUpdateFeedbackSnippet.kt +++ b/core/src/main/kotlin/core/ems/service/snippet/TeacherUpdateFeedbackSnippet.kt @@ -6,17 +6,17 @@ import core.db.FeedbackSnippet import core.ems.service.MarkdownService import core.ems.service.idToLongOrInvalidReq import core.exception.InvalidRequestException -import mu.KotlinLogging -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.deleteWhere -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import jakarta.validation.Valid +import jakarta.validation.constraints.Size +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.and +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.deleteWhere +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import org.joda.time.DateTime import org.springframework.security.access.annotation.Secured import org.springframework.web.bind.annotation.* -import javax.validation.Valid -import javax.validation.constraints.Size @RestController @@ -24,7 +24,7 @@ import javax.validation.constraints.Size class TeacherUpdateFeedbackSnippetController(val markdownService: MarkdownService) { private val log = KotlinLogging.logger {} - data class Req(@JsonProperty("snippet_md", required = false) @field:Size(max = 300000) val snippetMd: String?) + data class Req(@param:JsonProperty("snippet_md", required = false) @field:Size(max = 300000) val snippetMd: String?) @Secured("ROLE_TEACHER", "ROLE_ADMIN") @PutMapping("/snippets/{snippetId}") diff --git a/core/src/main/kotlin/core/ems/service/statistics.kt b/core/src/main/kotlin/core/ems/service/statistics.kt index bdf87f7f..4e9f8540 100644 --- a/core/src/main/kotlin/core/ems/service/statistics.kt +++ b/core/src/main/kotlin/core/ems/service/statistics.kt @@ -3,9 +3,10 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.ems.service.cache.CachingService +import jakarta.validation.Valid import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.runBlocking -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Scheduled import org.springframework.security.access.annotation.Secured import org.springframework.stereotype.Service @@ -14,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import java.util.concurrent.ConcurrentLinkedQueue -import javax.validation.Valid private val log = KotlinLogging.logger {} @@ -31,9 +31,9 @@ class StatisticsController(private val statisticsService: StatisticsService) { } data class StatResp( - @JsonProperty("in_auto_assessing") val inAutoAssessing: Long, - @JsonProperty("total_submissions") val totalSubmissions: Long, - @JsonProperty("total_users") val totalUsers: Long + @get:JsonProperty("in_auto_assessing") val inAutoAssessing: Long, + @get:JsonProperty("total_submissions") val totalSubmissions: Long, + @get:JsonProperty("total_users") val totalUsers: Long ) diff --git a/core/src/main/kotlin/core/ems/service/submissions.kt b/core/src/main/kotlin/core/ems/service/submissions.kt index d2116f05..b935167a 100644 --- a/core/src/main/kotlin/core/ems/service/submissions.kt +++ b/core/src/main/kotlin/core/ems/service/submissions.kt @@ -10,9 +10,10 @@ import core.ems.service.moodle.MoodleGradesSyncService import core.exception.InvalidRequestException import core.exception.ReqError import core.util.SendMailService -import mu.KotlinLogging -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction +import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.* +import org.jetbrains.exposed.v1.jdbc.* +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime private val log = KotlinLogging.logger {} @@ -52,9 +53,9 @@ fun selectStudentEmailBySubmissionId(submissionId: Long) = transaction { data class GradeResp( - @JsonProperty("grade") val grade: Int, - @JsonProperty("is_autograde") val isAutograde: Boolean, - @JsonProperty("is_graded_directly") val isGradedDirectly: Boolean + @get:JsonProperty("grade") val grade: Int, + @get:JsonProperty("is_autograde") val isAutograde: Boolean, + @get:JsonProperty("is_graded_directly") val isGradedDirectly: Boolean ) @@ -114,14 +115,14 @@ suspend fun autoAssessAsync( autoGradeScheduler.submitAndAwait(autoExerciseId, solution, PriorityLevel.AUTHENTICATED) } catch (e: Exception) { // EZ-1214, retry autoassessment automatically once if it fails - log.error("Autoassessment failed, retrying once more...", e) + log.error { "Autoassessment failed, retrying once more... ${e.message}" } autoGradeScheduler.submitAndAwait(autoExerciseId, solution, PriorityLevel.AUTHENTICATED) } log.debug { "Finished autoassessment" } insertAutogradeActivity(autoAss.grade, autoAss.feedback, submissionId, caching, courseExId, studentId) } catch (e: Exception) { - log.error("Autoassessment failed", e) + log.error { "Autoassessment failed ${e.message}" } insertAutoAssFailed(submissionId, caching) val notification = """ Autoassessment failed @@ -220,8 +221,8 @@ fun insertSubmission( } data class AutomaticAssessmentResp( - @JsonProperty("grade") val grade: Int, - @JsonProperty("feedback") val feedback: String? + @get:JsonProperty("grade") val grade: Int, + @get:JsonProperty("feedback") val feedback: String? ) fun getLatestAutomaticAssessmentRespOrNull(submissionId: Long) = transaction { diff --git a/core/src/main/kotlin/core/exception/error_response.kt b/core/src/main/kotlin/core/exception/error_response.kt index 79fe36a1..0c95f934 100644 --- a/core/src/main/kotlin/core/exception/error_response.kt +++ b/core/src/main/kotlin/core/exception/error_response.kt @@ -38,8 +38,8 @@ enum class ReqError(val errorCodeStr: String) { data class RequestErrorResponse( - @JsonProperty("id", required = true) val id: String, - @JsonProperty("code", required = true) val code: String?, - @JsonProperty("attrs", required = true) val attrs: Map, - @JsonProperty("log_msg", required = true) val logMsg: String + @get:JsonProperty("id", required = true) val id: String, + @get:JsonProperty("code", required = true) val code: String?, + @get:JsonProperty("attrs", required = true) val attrs: Map, + @get:JsonProperty("log_msg", required = true) val logMsg: String ) diff --git a/core/src/main/kotlin/core/exception/exception_handlers.kt b/core/src/main/kotlin/core/exception/exception_handlers.kt index 1020e2b2..490b01ee 100644 --- a/core/src/main/kotlin/core/exception/exception_handlers.kt +++ b/core/src/main/kotlin/core/exception/exception_handlers.kt @@ -1,13 +1,12 @@ package core.exception -import com.fasterxml.jackson.core.JsonParseException -import com.fasterxml.jackson.databind.exc.MismatchedInputException -import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException import core.aas.ExecutorOverloadException import core.util.SendMailService -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.boot.json.JsonParseException import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatusCode import org.springframework.http.ResponseEntity import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.security.access.AccessDeniedException @@ -17,19 +16,20 @@ import org.springframework.web.bind.annotation.ControllerAdvice import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.context.request.WebRequest import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler +import tools.jackson.databind.exc.InvalidNullException +import tools.jackson.databind.exc.MismatchedInputException import java.util.* -private val log = KotlinLogging.logger {} - @ControllerAdvice class EasyExceptionHandler(private val mailService: SendMailService) : ResponseEntityExceptionHandler() { + private val log = KotlinLogging.logger {} @ExceptionHandler(value = [ExecutorOverloadException::class]) fun handleExecutorOverloadException(ex: ExecutorOverloadException, request: WebRequest): ResponseEntity { val id = UUID.randomUUID().toString() - log.warn("ExecutorOverloadException", ex) - log.warn("Request info: ${request.getDescription(true)}") + log.warn { "ExecutorOverloadException $ex" } + log.warn { "Request info: ${request.getDescription(true)}" } mailService.sendSystemNotification(ex.stackTraceString, id) // TODO: also use code return ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE) @@ -39,8 +39,8 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE fun handleForbiddenException(ex: ForbiddenException, request: WebRequest): ResponseEntity { val id = UUID.randomUUID().toString() - log.warn("Forbidden error: ${ex.message}") - log.warn("Request info: ${request.getDescription(true)}") + log.warn { "Forbidden error: ${ex.message}" } + log.warn { "Request info: ${request.getDescription(true)}" } mailService.sendSystemNotification(ex.stackTraceString, id) @@ -52,8 +52,8 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE fun handleInvalidReqException(ex: InvalidRequestException, request: WebRequest): ResponseEntity { val id = UUID.randomUUID().toString() - log.warn("Invalid request, message: ${ex.message}, id: $id") - log.warn("Request info: ${request.getDescription(true)}") + log.warn { "Invalid request, message: ${ex.message}, id: $id" } + log.warn { "Request info: ${request.getDescription(true)}" } if (ex.notify) { mailService.sendSystemNotification(ex.stackTraceString, id) @@ -66,8 +66,8 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE @ExceptionHandler(value = [Exception::class]) fun handleGenericException(ex: Exception, request: WebRequest): ResponseEntity { val id = UUID.randomUUID().toString() - log.error("Caught a big one", ex) - log.error("Request info: ${request.getDescription(true)}") + log.error { "Caught a big one $ex" } + log.error { "Request info: ${request.getDescription(true)}" } mailService.sendSystemNotification(ex.stackTraceString, id) return ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } @@ -76,8 +76,8 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE fun handleAccessDeniedException(ex: AccessDeniedException, request: WebRequest): ResponseEntity { val id = UUID.randomUUID().toString() - log.warn("Access denied error: ${ex.message}") - log.warn("Request info: ${request.getDescription(true)}") + log.warn { "Access denied error: ${ex.message}" } + log.warn { "Request info: ${request.getDescription(true)}" } mailService.sendSystemNotification(ex.stackTraceString, id) @@ -96,12 +96,12 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE override fun handleMethodArgumentNotValid( ex: MethodArgumentNotValidException, headers: HttpHeaders, - status: HttpStatus, + status: HttpStatusCode, request: WebRequest ): ResponseEntity { val id = UUID.randomUUID().toString() - log.info("MethodArgumentNotValidException: ${ex.message}") - log.info("Request info: ${request.getDescription(true)}") + log.info { "MethodArgumentNotValidException: ${ex.message}" } + log.info { "Request info: ${request.getDescription(true)}" } val msg = ex.bindingResult.allErrors .joinToString(separator = ";") { "'${(it as FieldError).field}': ${it.getDefaultMessage()};" } @@ -114,18 +114,18 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE override fun handleHttpMessageNotReadable( ex: HttpMessageNotReadableException, headers: HttpHeaders, - status: HttpStatus, + status: HttpStatusCode, request: WebRequest ): ResponseEntity { val id = UUID.randomUUID().toString() - log.info("HttpMessageNotReadableException: ${ex.message}") - log.info("Request info: ${request.getDescription(true)}") + log.info { "HttpMessageNotReadableException: ${ex.message}" } + log.info { "Request info: ${request.getDescription(true)}" } val msg = when (ex.cause) { - is MissingKotlinParameterException -> { - val cause = ex.cause as MissingKotlinParameterException - "Missing parameter '${cause.parameter.name}' of type '${ - cause.parameter.type.toString().replace("kotlin.", "") + is InvalidNullException -> { + val cause = ex.cause as InvalidNullException + "Missing parameter '${cause.propertyName.simpleName}' of type '${ + cause.targetType.toString().replace("kotlin.", "") }'" } diff --git a/core/src/main/kotlin/core/util/DBBackedLock.kt b/core/src/main/kotlin/core/util/DBBackedLock.kt index 48267a17..1efab2f2 100644 --- a/core/src/main/kotlin/core/util/DBBackedLock.kt +++ b/core/src/main/kotlin/core/util/DBBackedLock.kt @@ -1,11 +1,12 @@ package core.util import core.exception.ResourceLockedException -import org.jetbrains.exposed.dao.id.IdTable -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.update +import org.jetbrains.exposed.v1.core.Column +import org.jetbrains.exposed.v1.core.dao.id.IdTable +import org.jetbrains.exposed.v1.core.eq +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.jetbrains.exposed.v1.jdbc.update import java.util.* /** diff --git a/core/src/main/kotlin/core/util/DateTimeSerializers.kt b/core/src/main/kotlin/core/util/DateTimeSerializers.kt index 5899a919..e1068cfe 100644 --- a/core/src/main/kotlin/core/util/DateTimeSerializers.kt +++ b/core/src/main/kotlin/core/util/DateTimeSerializers.kt @@ -1,27 +1,28 @@ package core.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider + import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat import org.joda.time.format.ISODateTimeFormat +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.ValueSerializer private val ENCODE_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") private val DECODE_FORMATTER = ISODateTimeFormat.dateTimeParser().withZoneUTC() -class DateTimeSerializer : JsonSerializer() { - override fun serialize(value: DateTime, gen: JsonGenerator, serializerProvider: SerializerProvider) { +class DateTimeSerializer : ValueSerializer() { + override fun serialize(value: DateTime, gen: JsonGenerator, ctxt: SerializationContext) { gen.writeString(ENCODE_FORMATTER.print(value)) } } -class DateTimeDeserializer : JsonDeserializer() { - override fun deserialize(parser: JsonParser, ctxt: DeserializationContext?): DateTime { - return DECODE_FORMATTER.parseDateTime(parser.text) +class DateTimeDeserializer : ValueDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): DateTime { + return DECODE_FORMATTER.parseDateTime(p.getString()) } } \ No newline at end of file diff --git a/core/src/main/kotlin/core/util/SendMailService.kt b/core/src/main/kotlin/core/util/SendMailService.kt index ac72307b..70b9195f 100644 --- a/core/src/main/kotlin/core/util/SendMailService.kt +++ b/core/src/main/kotlin/core/util/SendMailService.kt @@ -1,6 +1,6 @@ package core.util -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.beans.factory.annotation.Value import org.springframework.mail.SimpleMailMessage import org.springframework.mail.javamail.JavaMailSender @@ -275,7 +275,7 @@ class SendMailService(private val mailSender: JavaMailSender) { } val time = Instant.now().toString() - log.info("Sending notification $id ($time) from $fromAddress to $toSysAddress") + log.info { "Sending notification $id ($time) from $fromAddress to $toSysAddress" } val bodyText = "$message\n\n$id\n$time" @@ -286,6 +286,6 @@ class SendMailService(private val mailSender: JavaMailSender) { mailMessage.setFrom(fromAddress) mailSender.send(mailMessage) - log.info("Sent notification $id") + log.info { "Sent notification $id" } } } \ No newline at end of file diff --git a/core/src/test/kotlin/core/conf/TestDatabaseConf.kt b/core/src/test/kotlin/core/conf/TestDatabaseConf.kt index d8315e89..7f785ba5 100644 --- a/core/src/test/kotlin/core/conf/TestDatabaseConf.kt +++ b/core/src/test/kotlin/core/conf/TestDatabaseConf.kt @@ -1,13 +1,13 @@ package core.conf +import jakarta.annotation.PostConstruct import liquibase.Liquibase import liquibase.database.jvm.JdbcConnection import liquibase.resource.FileSystemResourceAccessor -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.jetbrains.exposed.v1.jdbc.Database +import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Configuration -import javax.annotation.PostConstruct import javax.sql.DataSource @@ -25,7 +25,7 @@ class InitTestDatabase(val dataSource: DataSource) { @PostConstruct fun init() { Database.connect(dataSource) - TransactionManager.manager.defaultRepetitionAttempts = 6 + TransactionManager.manager.defaultMaxAttempts = 6 dropAndUpdateSchema(changelogFile, JdbcConnection(dataSource.connection)) } diff --git a/core/src/test/kotlin/core/ems/service/PerformanceTestSelectAllCourseExercisesLatestSubmissions.kt b/core/src/test/kotlin/core/ems/service/PerformanceTestSelectAllCourseExercisesLatestSubmissions.kt index 95745061..c4c9c91f 100644 --- a/core/src/test/kotlin/core/ems/service/PerformanceTestSelectAllCourseExercisesLatestSubmissions.kt +++ b/core/src/test/kotlin/core/ems/service/PerformanceTestSelectAllCourseExercisesLatestSubmissions.kt @@ -4,20 +4,20 @@ import core.EasyCoreApp import core.conf.DatabaseInit import core.conf.dropAll import core.db.* +import io.github.oshai.kotlinlogging.KotlinLogging import liquibase.database.jvm.JdbcConnection -import mu.KotlinLogging -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.batchInsert -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.batchInsert +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.junit.jupiter.api.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.bean.override.mockito.MockitoBean import java.time.Duration import java.util.* import javax.sql.DataSource @@ -40,7 +40,7 @@ class PerformanceTestSelectAllCourseExercisesLatestSubmissions(@Autowired privat // Disable DatabaseInit and use DatabaseInitTest - @MockBean + @MockitoBean private val databaseInit: DatabaseInit? = null @Test diff --git a/core/src/test/kotlin/core/ems/service/ValidateSelectAllCourseExercisesLatestSubmissions.kt b/core/src/test/kotlin/core/ems/service/ValidateSelectAllCourseExercisesLatestSubmissions.kt index ff0ed3c2..c085ba4d 100644 --- a/core/src/test/kotlin/core/ems/service/ValidateSelectAllCourseExercisesLatestSubmissions.kt +++ b/core/src/test/kotlin/core/ems/service/ValidateSelectAllCourseExercisesLatestSubmissions.kt @@ -5,9 +5,9 @@ import core.conf.DatabaseInit import core.conf.dropAll import core.db.* import liquibase.database.jvm.JdbcConnection -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.joda.time.DateTime import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Assertions.assertEquals @@ -18,8 +18,8 @@ import org.junit.jupiter.api.TestInstance import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.bean.override.mockito.MockitoBean import java.util.* import javax.sql.DataSource import kotlin.random.Random @@ -33,7 +33,7 @@ class ValidateSelectAllCourseExercisesLatestSubmissions(@Autowired private val d private lateinit var changelogFile: String // Disable DatabaseInit and use DatabaseInitTest - @MockBean + @MockitoBean private val databaseInit: DatabaseInit? = null private val random = Random(0) @@ -51,7 +51,7 @@ class ValidateSelectAllCourseExercisesLatestSubmissions(@Autowired private val d @Test fun `selectAllCourseExercisesLatestSubmissions should return 4 submissions from 2 students (1 null, 3 graded)`() { val latestSubmissions: List = selectAllCourseExercisesLatestSubmissions(course1Id) - val submissions = latestSubmissions.map { it.latestSubmissions }.flatten().map { it.latestSubmission } + val submissions = latestSubmissions.flatMap { it.latestSubmissions }.map { it.latestSubmission } assertEquals(4, submissions.size) } @@ -74,11 +74,11 @@ class ValidateSelectAllCourseExercisesLatestSubmissions(@Autowired private val d val stud1Ex1Sub = ex1Submissions.latestSubmissions.single { it.accountId == student1Id } assertEquals(81, stud1Ex1Sub.latestSubmission!!.grade!!.grade) - assertEquals(false, stud1Ex1Sub.latestSubmission!!.grade!!.isAutograde) + assertEquals(false, stud1Ex1Sub.latestSubmission.grade.isAutograde) val stud1Ex2Sub = ex2Submissions.latestSubmissions.single { it.accountId == student1Id } assertEquals(99, stud1Ex2Sub.latestSubmission!!.grade!!.grade) - assertEquals(false, stud1Ex2Sub.latestSubmission!!.grade!!.isAutograde) + assertEquals(false, stud1Ex2Sub.latestSubmission.grade.isAutograde) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f742dcee..5afdf6c5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Tue Jun 30 10:22:20 EEST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/tsl/build.gradle.kts b/tsl/build.gradle.kts index 46a42eb7..b89bfac2 100644 --- a/tsl/build.gradle.kts +++ b/tsl/build.gradle.kts @@ -6,7 +6,7 @@ plugins { group = "com.example" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_11 +java.sourceCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() From 9eacef200cf1ce3e863a5cb44ab7cae4202ce425 Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Thu, 26 Feb 2026 12:53:49 +0200 Subject: [PATCH 2/7] EZ-1615 fix Liquibase changeset issues from the past by allowing any checksum and remove issue causing trailing whitespaces and illegal sequences. --- core/src/main/resources/db/changesets/v2.xml | 17 +++++++++++------ core/src/main/resources/db/changesets/v3.xml | 16 +++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/core/src/main/resources/db/changesets/v2.xml b/core/src/main/resources/db/changesets/v2.xml index 807cd207..7f46a39b 100644 --- a/core/src/main/resources/db/changesets/v2.xml +++ b/core/src/main/resources/db/changesets/v2.xml @@ -312,6 +312,10 @@ + any + + + @@ -334,9 +338,6 @@ - - @@ -840,8 +841,12 @@ + any + + + + oldTableName="testing_draft"/> @@ -1481,7 +1486,7 @@ - + any @@ -1522,7 +1527,7 @@ - + + any + + + + - + @@ -189,18 +194,15 @@ - - - + - + - + From 0adfa38103177e390551817f0754d143b9b3fcec Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Thu, 26 Feb 2026 12:56:10 +0200 Subject: [PATCH 3/7] EZ-1615 migrate PreAuthHeaderFilter and DummyZeroAuthFilter --- .../core/conf/security/DummyZeroAuthFilter.kt | 16 +++++++++------- .../core/conf/security/PreAuthHeaderFilter.kt | 10 +++++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt b/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt index cce601fa..1aa238ea 100644 --- a/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt +++ b/core/src/main/kotlin/core/conf/security/DummyZeroAuthFilter.kt @@ -5,10 +5,12 @@ import jakarta.servlet.FilterChain import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.web.context.RequestAttributeSecurityContextRepository +import org.springframework.security.web.context.SecurityContextRepository import org.springframework.web.filter.OncePerRequestFilter -class DummyZeroAuthFilter : OncePerRequestFilter() { +class DummyZeroAuthFilter(private val securityContextRepository: SecurityContextRepository = RequestAttributeSecurityContextRepository()) : OncePerRequestFilter() { override fun doFilterInternal( request: HttpServletRequest, @@ -21,15 +23,15 @@ class DummyZeroAuthFilter : OncePerRequestFilter() { val familyName = request.getOptionalHeader("oidc_claim_family_name") val roles = request.getOptionalHeader("oidc_claim_easy_role") - if (username != null && - email != null && - roles != null - ) { - SecurityContextHolder.getContext().authentication = EasyUser( + if (username != null && email != null && roles != null) { + val context = SecurityContextHolder.createEmptyContext() + context.authentication = EasyUser( username, username, email, givenName, familyName, mapHeaderToRoles(roles) ) + SecurityContextHolder.setContext(context) + securityContextRepository.saveContext(context, request, response) } filterChain.doFilter(request, response) } -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt b/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt index 0791e270..f87b5605 100644 --- a/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt +++ b/core/src/main/kotlin/core/conf/security/PreAuthHeaderFilter.kt @@ -7,12 +7,14 @@ import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.web.context.RequestAttributeSecurityContextRepository +import org.springframework.security.web.context.SecurityContextRepository import org.springframework.web.filter.OncePerRequestFilter private val log = KotlinLogging.logger {} -class PreAuthHeaderFilter : OncePerRequestFilter() { +class PreAuthHeaderFilter(private val securityContextRepository: SecurityContextRepository = RequestAttributeSecurityContextRepository()) : OncePerRequestFilter() { override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, @@ -57,15 +59,17 @@ class PreAuthHeaderFilter : OncePerRequestFilter() { oldId } - SecurityContextHolder.getContext().authentication = EasyUser( + val context = SecurityContextHolder.createEmptyContext() + context.authentication = EasyUser( oldId, id, newEmail, newGivenName, newFamilyName, newRoles ) + SecurityContextHolder.setContext(context) + securityContextRepository.saveContext(context, request, response) } filterChain.doFilter(request, response) } } - fun mapHeaderToRoles(rolesHeader: String): Set = mapRoleStringsToRoles(rolesHeader.split(",")) From 39a4541874af3312d6fe1e78df9e32b4235aef42 Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Thu, 26 Feb 2026 13:05:46 +0200 Subject: [PATCH 4/7] EZ-1615 update EasyRoles.kt --- core/src/main/kotlin/core/conf/security/EasyRoles.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/kotlin/core/conf/security/EasyRoles.kt b/core/src/main/kotlin/core/conf/security/EasyRoles.kt index d8b03bd6..e23fa7be 100644 --- a/core/src/main/kotlin/core/conf/security/EasyRoles.kt +++ b/core/src/main/kotlin/core/conf/security/EasyRoles.kt @@ -13,6 +13,12 @@ class EasyUser( val roles: Set, ) : AbstractAuthenticationToken(roles) { + // Safe to set: authentication is guaranteed by the filter logic that constructs + // this object only after validating required claims (username, email, roles). + // Required by Spring Security 7 which strictly checks isAuthenticated() == true. + init { + isAuthenticated = true + } // We have no credentials override fun getCredentials(): Any? = null From ce7c0aea302f337cef882f912d67f900deeacefe Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Mon, 2 Mar 2026 08:44:29 +0200 Subject: [PATCH 5/7] EZ-1615 fix Jackson 3.x serialization bugs and rewrite Postman collection Fix annotation targets for Jackson 3.x compatibility: - @get:JsonRawValue on feedback field in assessments.kt - @get:JsonProperty on executor request DTOs (executor_utils.kt) - @get:JsonProperty on Moodle sync DTOs (moodle_grades.kt) - Inject RestTemplateBuilder for Spring Boot auto-configured ObjectMapper in moodle_grades.kt and moodle_students.kt Rewrite EMS.postman_collection.json to match current codebase: - Remove deleted endpoints (AddStudentsToCourse, teacher group, old assessments) - Add new endpoints (course invites, separate grade/feedback, markdown preview) - Update request bodies (color, solution_file_name/type, etc.) - Self-contained test data creation (85 sequential requests) Co-Authored-By: Claude Opus 4.6 --- .../main/kotlin/core/aas/executor_utils.kt | 16 +- .../kotlin/core/ems/service/assessments.kt | 2 +- .../core/ems/service/moodle/moodle_grades.kt | 21 +- .../ems/service/moodle/moodle_students.kt | 7 +- .../resources/EMS.postman_collection.json | 6872 ++--------------- 5 files changed, 497 insertions(+), 6421 deletions(-) diff --git a/core/src/main/kotlin/core/aas/executor_utils.kt b/core/src/main/kotlin/core/aas/executor_utils.kt index cba7b3b6..fefea324 100644 --- a/core/src/main/kotlin/core/aas/executor_utils.kt +++ b/core/src/main/kotlin/core/aas/executor_utils.kt @@ -25,17 +25,17 @@ data class ExecutorResponse( ) data class ExecutorRequest( - @param:JsonProperty("submission") val submission: String, - @param:JsonProperty("grading_script") val gradingScript: String, - @param:JsonProperty("assets") val assets: List, - @param:JsonProperty("image_name") val imageName: String, - @param:JsonProperty("max_time_sec") val maxTime: Int, - @param:JsonProperty("max_mem_mb") val maxMem: Int + @get:JsonProperty("submission") val submission: String, + @get:JsonProperty("grading_script") val gradingScript: String, + @get:JsonProperty("assets") val assets: List, + @get:JsonProperty("image_name") val imageName: String, + @get:JsonProperty("max_time_sec") val maxTime: Int, + @get:JsonProperty("max_mem_mb") val maxMem: Int ) data class ExecutorRequestAsset( - @param:JsonProperty("file_name") val fileName: String, - @param:JsonProperty("file_content") val fileContent: String + @get:JsonProperty("file_name") val fileName: String, + @get:JsonProperty("file_content") val fileContent: String ) data class CapableExecutor( diff --git a/core/src/main/kotlin/core/ems/service/assessments.kt b/core/src/main/kotlin/core/ems/service/assessments.kt index 8935146a..fdafd361 100644 --- a/core/src/main/kotlin/core/ems/service/assessments.kt +++ b/core/src/main/kotlin/core/ems/service/assessments.kt @@ -57,7 +57,7 @@ data class TeacherActivityResp( @get:JsonProperty("created_at") @get:JsonSerialize(using = DateTimeSerializer::class) val createdAt: DateTime, @get:JsonProperty("grade") val grade: Int?, @get:JsonProperty("edited_at") @get:JsonSerialize(using = DateTimeSerializer::class) val editedAt: DateTime?, - @get:JsonProperty("feedback") @JsonRawValue val feedback: String?, + @get:JsonProperty("feedback") @get:JsonRawValue val feedback: String?, @get:JsonProperty("teacher") val teacher: TeacherResp ) diff --git a/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt b/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt index 4a96a89b..c66561e0 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/moodle_grades.kt @@ -23,13 +23,14 @@ import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import org.springframework.util.LinkedMultiValueMap import org.springframework.util.MultiValueMap -import org.springframework.web.client.RestTemplate +import org.springframework.boot.restclient.RestTemplateBuilder import tools.jackson.module.kotlin.jacksonObjectMapper @Service -class MoodleGradesSyncService { +class MoodleGradesSyncService(restTemplateBuilder: RestTemplateBuilder) { private val log = KotlinLogging.logger {} + private val restTemplate = restTemplateBuilder.build() @Value("\${easy.core.moodle-sync.grades.url}") private lateinit var moodleGradeUrl: String @@ -38,20 +39,20 @@ class MoodleGradesSyncService { data class MoodleReq( - @param:JsonProperty("shortname") val shortname: String, - @param:JsonProperty("exercises") val exercises: List + @get:JsonProperty("shortname") val shortname: String, + @get:JsonProperty("exercises") val exercises: List ) data class MoodleReqExercise( - @param:JsonProperty("idnumber") val idnumber: String, - @param:JsonProperty("title") val title: String, - @param:JsonProperty("grades") val grades: List + @get:JsonProperty("idnumber") val idnumber: String, + @get:JsonProperty("title") val title: String, + @get:JsonProperty("grades") val grades: List ) data class MoodleReqGrade( - @param:JsonProperty("username") val username: String, - @param:JsonProperty("grade") val grade: Int + @get:JsonProperty("username") val username: String, + @get:JsonProperty("grade") val grade: Int ) @@ -126,7 +127,7 @@ class MoodleGradesSyncService { val request = HttpEntity(map, headers) val responseEntity: ResponseEntity = - RestTemplate().postForEntity(moodleGradeUrl, request, String::class.java) + restTemplate.postForEntity(moodleGradeUrl, request, String::class.java) if (responseEntity.statusCode.value() != 200) { log.error { "Moodle grade syncing error ${responseEntity.statusCode.value()} with data $req" } diff --git a/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt b/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt index e72f18c8..20638fbb 100644 --- a/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt +++ b/core/src/main/kotlin/core/ems/service/moodle/moodle_students.kt @@ -23,14 +23,15 @@ import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import org.springframework.util.LinkedMultiValueMap import org.springframework.util.MultiValueMap -import org.springframework.web.client.RestTemplate +import org.springframework.boot.restclient.RestTemplateBuilder data class MoodleSyncedStudents(val syncedPendingStudents: Int) @Service -class MoodleStudentsSyncService(val mailService: SendMailService) { +class MoodleStudentsSyncService(val mailService: SendMailService, restTemplateBuilder: RestTemplateBuilder) { private val log = KotlinLogging.logger {} + private val restTemplate = restTemplateBuilder.build() @Value("\${easy.core.moodle-sync.users.url}") private lateinit var moodleSyncUrl: String @@ -83,7 +84,7 @@ class MoodleStudentsSyncService(val mailService: SendMailService) { map.add("shortname", moodleShortName) val request = HttpEntity>(map, headers) - val responseEntity = RestTemplate().postForEntity(moodleSyncUrl, request, MoodleResponse::class.java) + val responseEntity = restTemplate.postForEntity(moodleSyncUrl, request, MoodleResponse::class.java) if (responseEntity.statusCode.value() != 200) { log.error { "Moodle linking error ${responseEntity.statusCode.value()} with request $request" } diff --git a/core/src/test/resources/EMS.postman_collection.json b/core/src/test/resources/EMS.postman_collection.json index 4b11f52c..7878bfa3 100644 --- a/core/src/test/resources/EMS.postman_collection.json +++ b/core/src/test/resources/EMS.postman_collection.json @@ -2,6823 +2,897 @@ "info": { "_postman_id": "af0266c7-020e-43e8-bc80-aaeab9e6f6bc", "name": "EMS", - "description": "This collection implements tests for the API of easy:ems", + "description": "This collection implements tests for the API of easy:ems. Tests must run sequentially. Set EMSBASE variable to http://localhost:8080/v2", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { - "name": "EMS: admin: post: update account admin: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Checkin admin (fp)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"email\": \"foo@bar.com\",\r\n \"first_name\": \"foo\",\r\n \"last_name\": \"bar\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/account/checkin", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "account", - "checkin" - ] - } + "body": {"mode": "raw", "raw": "{\"first_name\": \"Fred\", \"last_name\": \"Pansen\"}"}, + "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} }, "response": [] }, { - "name": "EMS: student: post: update account student: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Checkin student (fp)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - }, - { - "key": "oidc_claim_ut_username", - "value": "", - "type": "text" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"email\": \"foo@bar.com\",\r\n \"first_name\": \"foo\",\r\n \"last_name\": \"bar\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/account/checkin", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "account", - "checkin" - ] - } + "body": {"mode": "raw", "raw": "{\"first_name\": \"Fred\", \"last_name\": \"Pansen\"}"}, + "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} }, "response": [] }, { - "name": "EMS: admin: post: update account teacher: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Checkin teacher (pf)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"email\": \"foo@bar.com\",\r\n \"first_name\": \"foo\",\r\n \"last_name\": \"bar\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/account/checkin", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "account", - "checkin" - ] - } + "body": {"mode": "raw", "raw": "{\"first_name\": \"Pera\", \"last_name\": \"Fansen\"}"}, + "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} }, "response": [] }, { - "name": "EMS: admin: post: update account 2nd teacher", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Checkin teacher as admin (pf)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "bar@foo.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "pf" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"email\": \"bar@foo.com\",\r\n \"first_name\": \"bar\",\r\n \"last_name\": \"foo\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/account/checkin", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "account", - "checkin" - ] - } + "body": {"mode": "raw", "raw": "{\"first_name\": \"Pera\", \"last_name\": \"Fansen\"}"}, + "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} }, "response": [] }, { - "name": "AAS: admin: post: create new container image: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create container image", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"id\": \"sample-container-image\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/container-images", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "container-images" - ] - } + "body": {"mode": "raw", "raw": "{\"id\": \"sample-container-image\"}"}, + "url": {"raw": "{{EMSBASE}}/container-images", "host": ["{{EMSBASE}}"], "path": ["container-images"]} }, "response": [] }, { - "name": "AAS: admin: post: create new executor: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create executor 1", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('executor1_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"string\",\r\n \"base_url\": \"http://localhost:8080/\",\r\n \"max_load\": \"100\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/executors", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Test executor 1\", \"base_url\": \"http://executor:8080\", \"max_load\": 10}"}, + "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} }, "response": [] }, { - "name": "AAS: admin: post: create new executor: success-repeat", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create executor 2", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('executor2_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"string\",\r\n \"base_url\": \"http://localhost:8080/\",\r\n \"max_load\": 100\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/executors", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Test executor 2\", \"base_url\": \"http://executor2:8080\", \"max_load\": 5}"}, + "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} }, "response": [] }, { - "name": "AAS: wrong role: post: create new executor: fail", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(403);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Create executor: fail wrong role", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 403', function () { pm.response.to.have.status(403); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"string\",\r\n \"base_url\": \"string\",\r\n \"max_load\": 0\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/executors", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Fail executor\", \"base_url\": \"http://fail:8080\", \"max_load\": 1}"}, + "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} }, "response": [] }, { - "name": "AAS: admin: put: update existing executor: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Update executor", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "PUT", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"string2\",\r\n \"base_url\": \"http://localhost:8080/\",\r\n \"max_load\": 100,\r\n \"drain\": false,\r\n \"containers\": [\"sample-container-image\"]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/executors/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors", - "1" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Test executor 1 updated\", \"base_url\": \"http://executor:8080\", \"max_load\": 20, \"drain\": false, \"containers\": [\"sample-container-image\"]}"}, + "url": {"raw": "{{EMSBASE}}/executors/{{executor1_id}}", "host": ["{{EMSBASE}}"], "path": ["executors", "{{executor1_id}}"]} }, "response": [] }, { - "name": "AAS: teacher: get: get all executors", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Read all executors", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "GET", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} ], - "url": { - "raw": "{{EMSBASE}}/executors", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors" - ] - } + "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} }, "response": [] }, { - "name": "AAS: teacher: get: get all images", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Read all container images", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "GET", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} ], - "url": { - "raw": "{{EMSBASE}}/container-images", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "container-images" - ] - } + "url": {"raw": "{{EMSBASE}}/container-images", "host": ["{{EMSBASE}}"], "path": ["container-images"]} }, "response": [] }, { - "name": "AAS: admin: post: delete executor: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Delete executor 2", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "DELETE", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"force\": false\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/executors/2", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors", - "2" - ] - } + "url": {"raw": "{{EMSBASE}}/executors/{{executor2_id}}", "host": ["{{EMSBASE}}"], "path": ["executors", "{{executor2_id}}"]} }, "response": [] }, { - "name": "AAS: admin: post: delete executor: delete-executor-not-present", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Delete executor 2 again: expect error", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "DELETE", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"force\": false\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/executors/2", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "executors", - "2" - ] - } + "url": {"raw": "{{EMSBASE}}/executors/{{executor2_id}}", "host": ["{{EMSBASE}}"], "path": ["executors", "{{executor2_id}}"]} }, "response": [] }, { - "name": "EMS: admin: post: create new course: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('course_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"title\": \"test\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/admin/courses", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "admin", - "courses" - ] - } + "body": {"mode": "raw", "raw": "{\"title\": \"Test Course\", \"color\": \"#7986cb\"}"}, + "url": {"raw": "{{EMSBASE}}/admin/courses", "host": ["{{EMSBASE}}"], "path": ["admin", "courses"]} }, "response": [] }, { - "name": "EMS: admin: post: add teacher to course: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Add teacher to course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"teachers\": [\r\n {\r\n \"email\": \"foo@bar.com\"\r\n },\r\n {\r\n \"email\": \"bar@foo.com\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/teachers", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "teachers" - ] - } + "body": {"mode": "raw", "raw": "{\"teachers\": [{\"email\": \"bar@foo.com\"}]}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "teachers"]} }, "response": [] }, { - "name": "EMS: admin: delete: rm teacher from course: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Remove teacher from course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "DELETE", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"teachers\": [\r\n {\r\n \"id\": \"pf\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/teachers", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "teachers" - ] - } + "body": {"mode": "raw", "raw": "{\"teachers\": [{\"id\": \"pf\"}]}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "teachers"]} }, "response": [] }, { - "name": "EMS: teacher: post: add teacher to course: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Re-add teacher to course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"teachers\": [\r\n {\r\n \"email\": \"bar@foo.com\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/teachers", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "teachers" - ] - } + "body": {"mode": "raw", "raw": "{\"teachers\": [{\"email\": \"bar@foo.com\"}]}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "teachers"]} }, "response": [] }, { - "name": "EMS: teacher: get: basic course info success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"title\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Read basic course info", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "value": "admin", - "type": "text" - }, - { - "key": "oidc_claim_email", - "value": "foo@bar.com", - "type": "text" - }, - { - "key": "oidc_claim_given_name", - "value": "Foo", - "type": "text" - }, - { - "key": "oidc_claim_family_name", - "value": "Bar", - "type": "text" - }, - { - "key": "oidc_claim_preferred_username", - "value": "fp", - "type": "text" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} ], - "url": { - "raw": "{{EMSBASE}}/courses/1/basic", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "basic" - ] - } + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/basic", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "basic"]} }, "response": [] }, { - "name": "EMS: teacher: get: course summaries: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Read teacher courses", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} ], - "url": { - "raw": "{{EMSBASE}}/teacher/courses", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses" - ] - } + "url": {"raw": "{{EMSBASE}}/teacher/courses", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new exercise: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create exercise TEACHER type", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('exercise1_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"title\": \"string\",\r\n \"text_html\": \"string\",\r\n \"text_adoc\": \"string\",\r\n \"public\": true,\r\n \"anonymous_autoassess_enabled\": true,\r\n \"grader_type\": \"AUTO\",\r\n \"grading_script\": \"string\",\r\n \"container_image\": \"sample-container-image\",\r\n \"max_time_sec\": 0,\r\n \"max_mem_mb\": 0,\r\n \"assets\": [\r\n {\r\n \"file_name\": \"name\",\r\n \"file_content\": \"content\"\r\n }\r\n ],\r\n \"executors\": [\r\n {\r\n \"executor_id\": \"1\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"title\": \"Test exercise TEACHER\", \"public\": true, \"anonymous_autoassess_enabled\": false, \"grader_type\": \"TEACHER\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\"}"}, + "url": {"raw": "{{EMSBASE}}/exercises", "host": ["{{EMSBASE}}"], "path": ["exercises"]} }, "response": [] }, { - "name": "EMS: teacher: post: create another new exercise: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create exercise AUTO type", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('exercise2_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"title\": \"string2\",\r\n \"text_html\": \"string\",\r\n \"text_adoc\": \"string\",\r\n \"public\": true,\r\n \"anonymous_autoassess_enabled\": true,\r\n \"grader_type\": \"AUTO\",\r\n \"grading_script\": \"string\",\r\n \"container_image\": \"sample-container-image\",\r\n \"max_time_sec\": 0,\r\n \"max_mem_mb\": 0,\r\n \"assets\": [\r\n {\r\n \"file_name\": \"name\",\r\n \"file_content\": \"content\"\r\n }\r\n ],\r\n \"executors\": [\r\n {\r\n \"executor_id\": \"1\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"title\": \"Test exercise AUTO\", \"public\": true, \"anonymous_autoassess_enabled\": true, \"grader_type\": \"AUTO\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\", \"grading_script\": \"#!/bin/bash\\necho 100\", \"container_image\": \"sample-container-image\", \"max_time_sec\": 60, \"max_mem_mb\": 256, \"assets\": [{\"file_name\": \"test.sh\", \"file_content\": \"echo test\"}]}"}, + "url": {"raw": "{{EMSBASE}}/exercises", "host": ["{{EMSBASE}}"], "path": ["exercises"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new exercise: fail-no-executors", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Create exercise: fail missing title", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"title\": \"string\",\r\n \"text_html\": \"string\",\r\n \"public\": true,\r\n \"grader_type\": \"AUTO\",\r\n \"grading_script\": \"string\",\r\n \"container_image\": \"string\",\r\n \"max_time_sec\": 0,\r\n \"max_mem_mb\": 0,\r\n \"assets\": [\r\n {\r\n \"file_name\": \"name\",\r\n \"file_content\": \"content\"\r\n }\r\n ],\r\n \"executors\": []\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"public\": true, \"anonymous_autoassess_enabled\": false, \"grader_type\": \"TEACHER\", \"solution_file_name\": \"sol.py\", \"solution_file_type\": \"TEXT_EDITOR\"}"}, + "url": {"raw": "{{EMSBASE}}/exercises", "host": ["{{EMSBASE}}"], "path": ["exercises"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new exercise: fail-invalid-executor", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Add exercise 1 to course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('course_ex1_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"title\": \"string\",\r\n \"text_html\": \"string\",\r\n \"public\": true,\r\n \"grader_type\": \"AUTO\",\r\n \"grading_script\": \"string\",\r\n \"container_image\": \"string\",\r\n \"max_time_sec\": 0,\r\n \"max_mem_mb\": 0,\r\n \"assets\": [\r\n {\r\n \"file_name\": \"name\",\r\n \"file_content\": \"content\"\r\n }\r\n ],\r\n \"executors\": [\r\n \t{\r\n \t \"executor_id\": \"1337\"\r\n \t}\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"exercise_id\": \"{{exercise1_id}}\", \"threshold\": 50, \"student_visible\": true, \"assessments_student_visible\": true}"}, + "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new exercise: fail-missing-body", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Add exercise 2 to course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('course_ex2_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{EMSBASE}}/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"exercise_id\": \"{{exercise2_id}}\", \"threshold\": 0, \"student_visible\": true, \"assessments_student_visible\": true}"}, + "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new exercise: fail-missing-title", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Create course group g1", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('group1_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"text_html\": \"string\",\r\n \"public\": true,\r\n \"grader_type\": \"TEACHER\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Group 1\"}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new course exercise: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Delete course group g1", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"exercise_id\": \"1\",\r\n \"threshold\": 0,\r\n \"student_visible\": true,\r\n \"assessments_student_visible\": true,\r\n \"instructions_adoc\": \"string\",\r\n \"title_alias\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: post: create another new course exercise: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"exercise_id\": \"2\",\r\n \"threshold\": 0,\r\n \"student_visible\": true,\r\n \"assessments_student_visible\": true,\r\n \"instructions_adoc\": \"string\",\r\n \"title_alias\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: post: create new course group", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"g1\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: delete: rm course group", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, "method": "DELETE", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups", - "1" - ] - } + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group1_id}}", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups", "{{group1_id}}"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new course group again", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Create course group g2", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('group2_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"g2\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Group 2\"}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]} }, "response": [] }, { - "name": "EMS: teacher: post: create new course group another", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Create course group g3", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('group3_id', jsonData.id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"g3\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups" - ] - } + "body": {"mode": "raw", "raw": "{\"name\": \"Group 3\"}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]} }, "response": [] }, { - "name": "EMS: teacher: post: add new student to course: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Generate course invite", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('invite_id', jsonData.invite_id);"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", + "method": "PUT", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"students\": [\r\n {\r\n \"email\": \"foo@bar.com\",\r\n \"groups\": [{\"id\": 2}]\r\n },\r\n {\r\n \"email\": \"sk\",\r\n \"groups\": [{\"id\": 2}]\r\n },\r\n {\r\n \"email\": \"pp\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/students", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "students" - ] - } + "body": {"mode": "raw", "raw": "{\"expires_at\": \"2099-01-01T00:00:00.000Z\", \"allowed_uses\": 100}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/invite", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "invite"]} }, "response": [] }, { - "name": "EMS: teacher: post: add student to group", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Read course invite", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"active_students\": [\r\n {\r\n \"id\": \"fp\"\r\n }\r\n ],\r\n \"pending_students\": [\r\n {\r\n \"email\": \"sk\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups/3/students", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups", - "3", - "students" - ] - } + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/invite", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "invite"]} }, "response": [] }, { - "name": "EMS: teacher: delete: rm student from group", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Join course by invite", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, - "method": "DELETE", + "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, + {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "student", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"active_students\": [\r\n {\r\n \"id\": \"fp\"\r\n }\r\n ],\r\n \"pending_students\": [\r\n {\r\n \"email\": \"sk\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups/3/students", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups", - "3", - "students" - ] - } + "url": {"raw": "{{EMSBASE}}/courses/join/{{invite_id}}", "host": ["{{EMSBASE}}"], "path": ["courses", "join", "{{invite_id}}"]} }, "response": [] }, { - "name": "EMS: teacher: post: add teacher to group", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Add student to group", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "POST", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "body": { - "mode": "raw", - "raw": "{\r\n \"teachers\": [\r\n {\r\n \"id\": \"pf\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups/3/teachers", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups", - "3", - "teachers" - ] - } + "body": {"mode": "raw", "raw": "{\"active_students\": [{\"id\": \"fp\"}], \"moodle_pending_students\": []}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group2_id}}/students", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups", "{{group2_id}}", "students"]} }, "response": [] }, { - "name": "EMS: teacher: delete: rm teacher from group", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], + "name": "Remove student from group", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], "request": { - "auth": { - "type": "noauth" - }, "method": "DELETE", "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"teachers\": [\r\n {\r\n \"id\": \"pf\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/courses/1/groups/3/teachers", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "groups", - "3", - "teachers" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: get: read exercise: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/exercises/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "1" - ] - } - }, - "response": [] - }, - { - "name": "EMS: student: get: course summaries: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/student/courses", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses" - ] - } - }, - "response": [] - }, - { - "name": "EMS: student: get: get exercises details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } + {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, + {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, + {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, + {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, + {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, + {"key": "Content-Type", "value": "application/json", "type": "text"} ], - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises" - ] - } + "body": {"mode": "raw", "raw": "{\"active_students\": [{\"id\": \"fp\"}], \"moodle_pending_students\": []}"}, + "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group2_id}}/students", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups", "{{group2_id}}", "students"]} }, "response": [] }, { - "name": "EMS: student: get: specific course exercise details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"effective_title\");\r", - " pm.response.to.have.jsonBody(\"text_html\");\r", - " pm.response.to.have.jsonBody(\"deadline\");\r", - " pm.response.to.have.jsonBody(\"grader_type\");\r", - " pm.response.to.have.jsonBody(\"grader_type\");\r", - " pm.response.to.have.jsonBody(\"threshold\");\r", - " pm.response.to.have.jsonBody(\"instructions_html\");\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises", - "1" - ] - } - }, + "name": "Read exercise", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", "host": ["{{EMSBASE}}"], "path": ["exercises", "{{exercise1_id}}"]}}, "response": [] }, { - "name": "EMS: teacher: get: course exercise details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: get: course specific exercise details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"title\");\r", - " pm.response.to.have.jsonBody(\"text_html\");\r", - " pm.response.to.have.jsonBody(\"text_adoc\");\r", - " pm.response.to.have.jsonBody(\"soft_deadline\");\r", - " pm.response.to.have.jsonBody(\"hard_deadline\");\r", - " pm.response.to.have.jsonBody(\"grader_type\");\r", - " pm.response.to.have.jsonBody(\"threshold\");\r", - " pm.response.to.have.jsonBody(\"last_modified\");\r", - " pm.response.to.have.jsonBody(\"student_visible\");\r", - " pm.response.to.have.jsonBody(\"assessments_student_visible\");\r", - " pm.response.to.have.jsonBody(\"instructions_html\");\r", - " pm.response.to.have.jsonBody(\"instructions_adoc\");\r", - " pm.response.to.have.jsonBody(\"title_alias\");\r", - " pm.response.to.have.jsonBody(\"grading_script\");\r", - " pm.response.to.have.jsonBody(\"container_image\");\r", - " pm.response.to.have.jsonBody(\"max_time_sec\");\r", - " pm.response.to.have.jsonBody(\"max_mem_mb\");\r", - " pm.response.to.have.jsonBody(\"assets\");\r", - " pm.response.to.have.jsonBody(\"executors\");\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises", - "1" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: get: course participants: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"students\");\r", - " pm.response.to.have.jsonBody(\"teachers\");\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/1/participants", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "participants" - ] - } - }, - "response": [] - }, - { - "name": "EMS: teacher: get: all course participants: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"students\");\r", - " pm.response.to.have.jsonBody(\"teachers\");\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/1/participants?role=all", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "participants" - ], - "query": [ - { - "key": "role", - "value": "all" - } - ] - } - }, + "name": "Read student courses", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses", "host": ["{{EMSBASE}}"], "path": ["student", "courses"]}}, "response": [] }, { - "name": "EMS: teacher: get: all course teachers: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.not.jsonBody(\"students\");\r", - " pm.response.to.have.jsonBody(\"teachers\");\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/1/participants?role=teacher", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "participants" - ], - "query": [ - { - "key": "role", - "value": "teacher" - } - ] - } - }, + "name": "Read student exercises on course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises"]}}, "response": [] }, { - "name": "EMS: teacher: get: all course students: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"students\");\r", - " pm.response.to.not.jsonBody(\"teachers\");\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/1/participants?role=student", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "participants" - ], - "query": [ - { - "key": "role", - "value": "student" - } - ] - } - }, + "name": "Read student exercise details", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}"]}}, "response": [] }, { - "name": "EMS: student: post: submit submission: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"solution\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises/1/submissions", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises", - "1", - "submissions" - ] - } - }, + "name": "Read teacher exercises on course", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises"]}}, "response": [] }, { - "name": "EMS: unuauth: post: submit anonymous submission: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"solution\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/unauth/exercises/2/anonymous/autoassess", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "unauth", - "exercises", - "2", - "anonymous", - "autoassess" - ] - } - }, + "name": "Read teacher exercise details", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}"]}}, "response": [] }, { - "name": "EMS: unuauth: get: exercise details for anonymous", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "url": { - "raw": "{{EMSBASE}}/unauth/exercises/1/anonymous/details", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "unauth", - "exercises", - "1", - "anonymous", - "details" - ] - } - }, + "name": "Read course participants", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/participants", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "participants"]}}, "response": [] }, { - "name": "EMS: teacher: get: read anonymous submissions: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/exercises/2/anonymous/submissions", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "2", - "anonymous", - "submissions" - ] - } - }, + "name": "Submit solution 1", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"solution\": \"print('hello')\"}"}, "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions"]}}, "response": [] }, { - "name": "EMS: student: post: submit submission 2: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"solution\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises/1/submissions", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises", - "1", - "submissions" - ] - } - }, + "name": "Submit solution 2", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"solution\": \"print('hello world')\"}"}, "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions"]}}, "response": [] }, { - "name": "EMS: student: post: submit draft: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"solution\": \"string\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises/1/draft", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises", - "1", - "draft" - ] - } - }, + "name": "Save draft", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"solution\": \"draft solution\"}"}, "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/draft", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "draft"]}}, "response": [] }, { - "name": "EMS: student: get: read draft: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises/1/draft", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises", - "1", - "draft" - ] - } - }, + "name": "Read draft", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/draft", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "draft"]}}, "response": [] }, { - "name": "EMS: student: get: latest autograded submission: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/student/courses/1/exercises/1/submissions/latest/await", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "student", - "courses", - "1", - "exercises", - "1", - "submissions", - "latest", - "await" - ] - } - }, + "name": "Read student submissions", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/all", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "all"]}}, "response": [] }, { - "name": "EMS: teacher: get: all grades: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/teacher/1/grades", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "teacher", - "1", - "grades" - ] - } - }, + "name": "Read all grades", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/teacher/{{course_id}}/grades", "host": ["{{EMSBASE}}"], "path": ["courses", "teacher", "{{course_id}}", "grades"]}}, "response": [] }, { - "name": "EMS: teacher: get: all grades with params: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/teacher/1/grades?limit=1&offset=1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "teacher", - "1", - "grades" - ], - "query": [ - { - "key": "limit", - "value": "1" - }, - { - "key": "offset", - "value": "1" - } - ] - } - }, + "name": "Read all grades with search", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/teacher/{{course_id}}/grades?search=Fred", "host": ["{{EMSBASE}}"], "path": ["courses", "teacher", "{{course_id}}", "grades"], "query": [{"key": "search", "value": "Fred"}]}}, "response": [] }, { - "name": "EMS: teacher: get: all grades with search: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/teacher/1/grades?search=oo", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "teacher", - "1", - "grades" - ], - "query": [ - { - "key": "search", - "value": "oo" - } - ] - } - }, + "name": "Read all submissions by student", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "if (jsonData.submissions && jsonData.submissions.length > 0) {", " pm.environment.set('submission_id', jsonData.submissions[jsonData.submissions.length - 1].id);", "} else if (Array.isArray(jsonData) && jsonData.length > 0) {", " pm.environment.set('submission_id', jsonData[jsonData.length - 1].id);", "}"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/all/students/fp", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "all", "students", "fp"]}}, "response": [] }, { - "name": "EMS: teacher: post: add assessment: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"grade\": 0,\r\n \"feedback\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises/1/submissions/1/assessments", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises", - "1", - "submissions", - "1", - "assessments" - ] - } - }, + "name": "Read submission summaries", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/latest/students", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "latest", "students"]}}, "response": [] }, { - "name": "EMS: teacher: post: add assessment: success-2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"grade\": 100,\r\n \"feedback\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises/1/submissions/1/assessments", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises", - "1", - "submissions", - "1", - "assessments" - ] - } - }, + "name": "Post grade 0", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": 0, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, "response": [] }, { - "name": "EMS: teacher: post: add assessment: fail-negative-grade", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"grade\": -1,\r\n \"feedback\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises/1/submissions/1/assessments", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises", - "1", - "submissions", - "1", - "assessments" - ] - } - }, + "name": "Post grade 100", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": 100, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, "response": [] }, { - "name": "EMS: teacher: post: add assessment: fail-grade-over-100", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"grade\": 101,\r\n \"feedback\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises/1/submissions/1/assessments", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises", - "1", - "submissions", - "1", - "assessments" - ] - } - }, + "name": "Post grade fail -1", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": -1, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, "response": [] }, { - "name": "EMS: admin: post: create system message: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"message\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/management/notifications", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "notifications" - ] - } - }, + "name": "Post grade fail 101", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": 101, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, "response": [] }, { - "name": "EMS: admin: get: read system messages: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('Has data', function() {\r", - " pm.expect(jsonData).to.have.property('messages');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"message\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/management/notifications", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "notifications" - ] - } - }, + "name": "Post feedback", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"feedback_md\": \"Good work!\", \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/feedback", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "feedback"]}}, "response": [] }, { - "name": "EMS: common: get: read system messages: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('Has data', function() {\r", - " pm.expect(jsonData).to.have.property('messages');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"message\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/management/common/notifications", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "common", - "notifications" - ] - } - }, + "name": "Create notification", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"message\": \"Test notification\"}"}, "url": {"raw": "{{EMSBASE}}/management/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "notifications"]}}, "response": [] }, { - "name": "EMS: admin: patch: update system message: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"message\": \"string2\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/management/notifications/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "notifications", - "1" - ] - } - }, + "name": "Read notifications (admin)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "if (Array.isArray(jsonData) && jsonData.length > 0) {", " pm.environment.set('notification_id', jsonData[0].id);", "}"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "notifications"]}}, "response": [] }, { - "name": "EMS: admin: get: read system messages: success-2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "const jsonData = pm.response.json();\r", - "\r", - "pm.test('Has data', function() {\r", - " pm.expect(jsonData).to.have.property('messages');\r", - "});" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{EMSBASE}}/management/notifications", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "notifications" - ] - } - }, + "name": "Read common notifications", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/common/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "common", "notifications"]}}, "response": [] }, { - "name": "EMS: admin: delete: delete system message: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"message\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/management/notifications/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "notifications", - "1" - ] - } - }, + "name": "Update notification", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "PATCH", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"message\": \"Updated notification\"}"}, "url": {"raw": "{{EMSBASE}}/management/notifications/{{notification_id}}", "host": ["{{EMSBASE}}"], "path": ["management", "notifications", "{{notification_id}}"]}}, "response": [] }, { - "name": "EMS: teacher: get: all submissions: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/teacher/courses/1/exercises/1/submissions/all/students/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "teacher", - "courses", - "1", - "exercises", - "1", - "submissions", - "all", - "students", - "1" - ] - } - }, + "name": "Read notifications again", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "notifications"]}}, "response": [] }, { - "name": "EMS: teacher: post: submit teacher submission: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"solution\": \"string\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{EMSBASE}}/exercises/2/testing/autoassess", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "2", - "testing", - "autoassess" - ] - } - }, + "name": "Delete notification", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "DELETE", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/notifications/{{notification_id}}", "host": ["{{EMSBASE}}"], "path": ["management", "notifications", "{{notification_id}}"]}}, "response": [] }, { - "name": "EMS: teacher: get: read teacher submissions: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/exercises/1/testing/autoassess/submissions", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "1", - "testing", - "autoassess", - "submissions" - ] - } - }, + "name": "Report client log", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"log_level\": \"WARN\", \"log_message\": \"Test log message\", \"client_id\": \"postman-test\"}"}, "url": {"raw": "{{EMSBASE}}/management/log", "host": ["{{EMSBASE}}"], "path": ["management", "log"]}}, "response": [] }, { - "name": "EMS: student: post: report log: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"client_id\": \"string\",\r\n \"log_level\": \"DEBUG\",\r\n \"log_message\": \"string\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{EMSBASE}}/management/log", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "management", - "log" - ] - } - }, + "name": "Read statistics", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{}"}, "url": {"raw": "{{EMSBASE}}/statistics/common", "host": ["{{EMSBASE}}"], "path": ["statistics", "common"]}}, "response": [] }, { - "name": "EMS: student: post: statistics: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"in_auto_assessing\": 0,\r\n \"total_submissions\": 0,\r\n \"total_users\": 0\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{EMSBASE}}/statistics/common", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "statistics", - "common" - ] - } - }, + "name": "AsciiDoc preview", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"content\": \"= Hello\\n\\nWorld\"}"}, "url": {"raw": "{{EMSBASE}}/preview/adoc", "host": ["{{EMSBASE}}"], "path": ["preview", "adoc"]}}, "response": [] }, { - "name": "EMS: student: post: ascii preview: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"content\": \"[source,python]\\n----\\nimport asd\\nprint('hello')\\n----\\n.\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/preview/adoc", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "preview", - "adoc" - ] - } - }, + "name": "Markdown preview", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"content\": \"# Hello\\n\\nWorld\"}"}, "url": {"raw": "{{EMSBASE}}/preview/markdown", "host": ["{{EMSBASE}}"], "path": ["preview", "markdown"]}}, "response": [] }, { - "name": "EMS: admin: post: create new article: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"public\": true,\r\n \"title\": \"string\",\r\n \"text_adoc\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/articles", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles" - ] - } - }, + "name": "Create article", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('article_id', jsonData.id);"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"title\": \"Test Article\", \"text_adoc\": \"= Article\\n\\nContent\", \"public\": true}"}, "url": {"raw": "{{EMSBASE}}/articles", "host": ["{{EMSBASE}}"], "path": ["articles"]}}, "response": [] }, { - "name": "EMS: admin: get: article details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"error\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/articles/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "1" - ] - } - }, + "name": "Read article (admin)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}"]}}, "response": [] }, { - "name": "EMS: student: get: article details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"public\"); \r", - " pm.response.to.not.have.jsonBody(\"aliases\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/articles/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "1" - ] - } - }, + "name": "Read article (student)", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}"]}}, "response": [] }, { - "name": "EMS: admin: put: update article details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"public\": true,\r\n \"title\": \"string\",\r\n \"text_adoc\": \"string\"\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{EMSBASE}}/articles/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "1" - ] - } - }, + "name": "Update article", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "PUT", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"title\": \"Updated Article\", \"text_adoc\": \"= Updated\\n\\nContent\", \"public\": true}"}, "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}"]}}, "response": [] }, { - "name": "EMS: admin: put: update exercise details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"title\": \"string\",\r\n \"text_html\": \"string\",\r\n \"text_adoc\": \"string\",\r\n \"public\": true,\r\n \"anonymous_autoassess_enabled\": true,\r\n \"anonymous_autoassess_template\": \"template\",\r\n \"grader_type\": \"TEACHER\",\r\n \"grading_script\": \"string\",\r\n \"container_image\": \"string\",\r\n \"max_time_sec\": 0,\r\n \"max_mem_mb\": 0,\r\n \"assets\": [\r\n {\r\n \"file_name\": \"string\",\r\n \"file_content\": \"string\"\r\n }\r\n ],\r\n \"executors\": [\r\n \r\n \r\n \r\n ]\r\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{EMSBASE}}/exercises/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "1" - ] - } - }, + "name": "Update exercise", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "PUT", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"title\": \"Updated exercise TEACHER\", \"grader_type\": \"TEACHER\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\"}"}, "url": {"raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", "host": ["{{EMSBASE}}"], "path": ["exercises", "{{exercise1_id}}"]}}, "response": [] }, { - "name": "EMS: admin: get: read exercise details: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/exercises/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "1" - ] - } - }, + "name": "Read exercise after update", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", "host": ["{{EMSBASE}}"], "path": ["exercises", "{{exercise1_id}}"]}}, "response": [] }, { - "name": "EMS: admin: post: create new article alias: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"alias\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/articles/1/aliases", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "1", - "aliases" - ] - } - }, + "name": "Create article alias", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"alias\": \"testAlias\"}"}, "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}/aliases", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}", "aliases"]}}, "response": [] }, { - "name": "EMS: student: get: article details: success alias", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "pm.test(\"response should be okay to process\", function () { \r", - " pm.response.to.not.be.error; \r", - " pm.response.to.have.jsonBody(\"id\");\r", - " pm.response.to.not.have.jsonBody(\"public\"); \r", - " pm.response.to.not.have.jsonBody(\"aliases\"); \r", - "});" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/articles/string", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "string" - ] - } - }, + "name": "Read article by alias", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/testAlias", "host": ["{{EMSBASE}}"], "path": ["articles", "testAlias"]}}, "response": [] }, { - "name": "EMS: admin: post: create new article alias: fail pattern", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(400);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"alias\": \"string\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/articles/1/aliases", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "1", - "aliases" - ] - } - }, + "name": "Create article alias: fail duplicate", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"alias\": \"testAlias\"}"}, "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}/aliases", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}", "aliases"]}}, "response": [] }, { - "name": "EMS: admin: get: article aliases: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/article-aliases", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "article-aliases" - ] - } - }, + "name": "Read article aliases", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/article-aliases", "host": ["{{EMSBASE}}"], "path": ["article-aliases"]}}, "response": [] }, { - "name": "EMS: admin: delete: delete article alias: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/articles/1/aliases/string", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "articles", - "1", - "aliases", - "string" - ] - } - }, + "name": "Delete article alias", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "DELETE", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}/aliases/testAlias", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}", "aliases", "testAlias"]}}, "response": [] }, { - "name": "EMS: teacher: post: upload file: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"filename\": \"mm.PNG\",\r\n \"data\": \"iVBORw0KGgoAAAANSUhEUgAAAEMAAAArCAYAAAAjbTVEAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACB0SURBVGhDdZr3V1ZZvub9W2bWvavvzO2+3dVV1WVAQUTMCbOCChIElBwURDFnxawYUAQVA4IYQEBJEgRJkjGA8Gagqu7Mmh+f+eyD9u3Va+aHvV55hXP2fvb3+4R9zhRn3zGZ4eo/Ls/ACY33n9R43wm5e4/J0XVI9g/75ejcJ3vbbtne7dLXqjgNV+zQcHm0Pr2IVG9xqAaeR2q0Olnulr1ydeyXq+ugXH1H5Ow9KlvXYY12Hpaj+5jcA2fkGcySp/+0NVx9xzXSuV/D7Zka7sjg9/ZYw9m1X2M9h+XhOs6OfXK075Wz04w9sncwj/Z02fh0du2Vje9tnRnMM9P6NMPdvV+/cf/fWMN4L+vqOyln/ynZ+Pzac0wjPUc12n3Yure9a5+c/K2zY6+mOPuP8ouMvqNyA8gYYIxZYBz9BgZAdHCjljSNvE3WUEUMi9+mD482qyl3repvrlLnwxB9rUiSvX63HO8z5fpwyALTw3UcXdz4GxhjA1ma+HiOz9PW8AyclINFj344oNGuTNm7M/m9ffztQY31HmFRh+QyE27bI0dbhpytLLolXaNNOzXckGL97GgFjFYAagVIPkda09m8TLkB0s08nGyGEwDszMXO+mxsuIP72lnvCEAbEA0YDgOGa4DKAAwHN3cBgFmAGzT/EQwbO/O1KVVfqIrekjC15K1X+dl5erRvuh7v81LN1dUaLInVaFWa7A3sYAeVAfrfr+XqYfQCMmCMD1IdAG6Gu5+JAYa9+wA79G2XevYzDrAIrmHAMN9TlaNUpa1xp2z1qRqpSdZILWA07ZGtYbeG6lI1XJ8mWzOb0ZZpVZXrw0EWyHU7D3L9I7KzHrvpAsBwM1ys1866rKoDCKsyxj6dlAHEgGGndBz8oYtddYOcKXVXL+jSJmY3DBg9RWGqvxGgJ4CQHfVvuhj2rypI91LjtU0AkkQbsTvv9nIjFkNVuHtOWGU6Rpm6+XSwS2annIBl2siA4fgnMMzPo10A0kOFMBzs+HBdooa4/9CrGA0+i9LAsx0aKkvkM1adRZHqL6V9a9mMlkx5qAgDhpPNdLImp7nHt2F+dvG9g3a2NdNu7yaHqbApno+gZMBg4TZ+0aDoBAxXP2U+cFwTg6bvuAAlaatLVt/TcDXdXKMXh+YoZ8eflLXpvys74s8qOxagvsdJGn6zV0Nv6f9muKPjiMa6T2ii75TGaYtJMAzIXN8Ce3K4+ydBd1ERnj4mzFxGDUAAM9Z7QG5K/2t1nD6VRqr78VY1XV+jN+eWqSVnkzryw9VxLwJQEjVaA6c0ZLJxtE4boH4H5B+Gi2px0sr2pgyqa6c1Rut2ydG8R1Pcg8cn24QJOJiYAyD+X2C46Vv72xQLjIarqwDDT/eTpulq6J+UHfaDnu9brt67gFEGGFWA0QQY7Uc11gUpWy1CZdAahjRNe3wHwsNGjA3wbwuMgxYYLsAxYNioFAPGmGmT6nh9gqs6bm9QKfe+l/CzKg4tUcOFIDVlh6jnYZxGK+GWenigidZ+T6W103IGEHjH2UYrwCuOd7tlrwOAN7RaeSIjWaOvU+VgAy0CNUCYT8+g6ScmzGSdgOHsBwQm52HHTGU43qbKWZOi1luBKj3sr8cpM3U97C86u/5/6Eb4ND3bH6D3uahM+S7Ilpu3sLA2rt1uehhCNO3HdQ0ITtN+BgCzeD5thpsgaku9+G4YUvvanoGiZGqCT3cdLYhqtV1fqyc7p+nc2v+m/MhfVBDvCzC+enZwud7lRuhzKbvcyOLfA0I7RA5nONu4dhMCQGUPv6bCXmzXYHGkvhRt13BxLNdN0ihznuKEKxxm59khz+AkEC7DuAaMPibMbrlML7emAUaKHJWJGnwQoYZzq1S6d4EKYmfpwob/0OnVf9SNbTNVfnqD+p8ly/mWcmzkb1sOydNJGwCGIeSv7XssAJzsuLMPxh8AbEA3PGHAGDUDQEa7WQSbMMa9f+3Yo3GjZE8j1HKRqkz31Z3QH3Ut8C86tewPOrzkX3Q14hcVHliq/qIUqmivRt+iai1wjgEEybezkSMAMVAcpqZrq/Tm1ALWEKDe3HB9eZwAMImaYpGKAYPKGAOM8U+w/cczclElLsrX3W9kbp88+ABXIxcsi9WnR5FqPL9GJWn+ur/DWze3Tlf2lmlUh7cK91C6N0PU9yxJQ6/TYXz63gKDKkAhjI9wAYKzz4BxgI0whGnIkwEQI6jBV8rbBnBuKmaC/5toS5e7Nl6fC0MtMCr2zNeDqJm6GfSLspb/UYcX/kHHVv27soJ/UOW5QFo5mVZAXt/RqobM63fJXktLVMbQTpv15vR8FaZOVXGKl8ozF6rq2Go1XtxsKuMfwTglD2C4Pp6WEzCchtiYsKeHC7fvtsD4+DxKvQWhqj29Qo+TfHQncoZuh83Uja3euh7qo/sp8/Xm/Aa8xw59eZUmZz0Teg8ZQlo25OsrZskFwK5+AwSL/7AHvYePUCwHuj/SOgnGqClvAPIA3sT7NCojSSNPI9WRvVFV+5fpYbSvcjZ56eJqAFn1s46v/LMOrvyDCjOXqu9JioYr4ZkajBnexwUYzlrarCxaH+5u0OuTtHjyVN3d9qPyqah7kV4qYt5TLCanbA0YLgjTaQyJcWu0iY02cfTQIl0QTys6/zbRcp1d+VssMIpS51hgZG+Zqsub+Nzqo7z4uXpxbCXcEaHB5yl4AkNa+yBTQKEVHBgrZx8/91MRgDHcQS/TOk6k1A3ZjVIZNhjfBhg2Y7RadmmseacmGpJlo9fbr25U5d6lehTtrxuB3rq4yktXNszWxcCZOr3xJxXuXab+wp1wR5qGKgGhPkPj72izBuZPZXQXBKnmzCI9TfPW3cipytn8k67zdzlU9hSP1QqQmwEDh+Y08gcYdoCx08tOq4wptVYY+G0CTjNWPQUhaji/Wi/hjHsx3soOnqpLQbQKYOREz6ZVFqkhO1h9xXiDCibBhMZY3BjO0lzL1s3i+wwopjJgdxyjp+eQxnvgFWOUMG0Oft9ynYDheZdCmyToEx6n4fxKPUubr0cxC3U9aI7OBsBZa3x1KRBAtszU/Z2L1ZwTrS+l6RqpxoQ1AASc8dv7PfLUp2igKFRVZ5eocJc3BMwGbvwRQP+qC6t+nKyM/wKD1sATOPEETvjDOWi+M0SGU2tLBYw4SAgwsN8NFwAjEzBivXUjzEtXg9mlzTN1ees05SfNUdX5jeorjKdVzN/thnOoCnLEEKB+7cQc9RhAkEGu7YIXxlG0X43sQrS2VjgDBXDTOuO0leedkb9o9TzYrNqsFSrJWKRH8ct0a+sinV05RyeXe+vkyhk6G+ila1GzVXpyPZmJtqrmHk2ZGqf1fkNax5p26eOzSFVfXKnHu+coL5o5r/1JZ1f8yPgJO26k7e9tYngCIAhUDv5t/8YZrt5JMGwN8YS07eq8F6Q6mPh55jzlxcxCXilVwMjaOFWnN/ygK+E/6+mBxeq8G41jxEKj4R4cnx0S/tqeRjDD7HShTlzXAOIxXgKyHMOQjZNhRptxvJggDxwyYYi7iQorj1Dn/SDVomLPM5erIG65ciNW6Pya+Tq2zEdHlk7T6fXTdWbT33QvHaW4Ha6PyKUHif0N8h43tptI8aksBgnerLITK/UweaEuA+D5VdOoMNrk72CYdoFAXaRKJ8OAYTP+g4m6IFBnO4zchKmq3KH2/I2quxCg8iNLdC9xtq5QDeeDZuhcIJPZ8FdlBf1J+ck+aroRqs8vqQxTru+MK6UiOrHWbYDRiR+gOlyA4TZZhLBmFGe8CzfcCl80c09K2wVf2Opi9bkMp3k3ECVYridpi5S/Y7FywpYpa42/ji/31dHlXoDhpUMBf9KlqBkqv0hlvkzGsB3W74A8hlI5uP8w6fpDYbRqr2zWs8zVuhHqp8sbfHRu1UzAQElMBvEYxwkY7k9ZlpqYhDfKRYz0uuhnF33tfE8f1sSr63GI6nGh5ceW6H6y7yQYG6fpYtBMXdg0XWeDIKXteI5T69X9MJ4MsQuZTSHbJLLIXRptocraAANgTAx3QZ52iyuQ0y5CXSf3bSWfNGHDaxL1pTwafxCqthyU4OhSzN485UfNV/bmefibOTpGmxxeOl3H2N19S/6oM8G/qOzcRvWzEWOtRPlu2s+y4XBfY4YGXiSq4cZWjONa5W2fp+xN3jqzYipgQFqTcZvcD0+4P9MmHyFQ+GOUQOUwFzLjA6AYD4AT7H8ZRVhbq5KD/rqb6KMbEXBFEDK3YSaAzAKM6cra8otyE/xUc34LmQXyIzt8eZWAXQYEQp8xcS5stkmLJqo7iNr2D1RnL9UJII42lKWRRPomXh9Ltqnn7ma9u7halQcWqShpnu5F+evyRm+dXTtbJwO8dWjJVO1b9JMOrfhZlyN8VXl+M4vGMTcZ48V1qRBPx2HAOQyp71RjTohKjwbofryfrm2ZoTMBcIZJcd/PHjyfTmlsiEBlAKFK7CiLs4dq6QYkJmrkcQSJ+oiiNOdhyU8s0sM0P+VG+egqVXFpPaAE+sAdXjqy+gf692c9TF9EdtiqXnzH55eAUUsfs+MekqiHvOOCF8zZhRXeyDDj5vCHTzdV4iBMDVXEQcShagX86mNIYsps3cdw3Qnz1fm103V+3WydXz+XCoE3VkzT8dUzlIPSVGQF0Q4kWVK0A/PlaT+i37nu72SlEb5rvrNV5SdX6gFkf3Mr5LvagMFErEMdFj8GGJ7PLB4wXINnIFPcaF+WxvpOM2HAYvfcVMdIXYo+PEaicIMl+xfpfpyfrgcbMLx0AUDOrJuhE2t/Vlbg35QL8q+Or1YzrrS3cDsuMEXjLHKc0OQx4al9UlbdVpKlOpF1E/0drRgwLLQ5RWvNXY+KLNbLPb56FOulvLDpugZHnV8zg3v6WWBkAcrpdbPgrdm6FbNARQfIKndCLa/hemeyFfcw1hw++lqTTqtH6e2ljSpKm6dbYQaMH4y0mnRKqjR8YUKaOfiAMwyRugbYpYFzJNezmkBlJkzQAoxRMkp3Ubjqsteo9Ag9nDxPt7Hi2bTIhXVeOsMkz6ybRoX8omsRs/QkY7GqcaVteWEafBYnZ12aPC2oxPsMZBQvQRZxfTs+cJhPY8epnqGqePU8IbJnr1Tl0Xn4CzxMnI/ubfPWpXV4m/WzdDXQX2dWe+v0Gr7b4q9rW+cpBx64v8tfddcD9fFVknXg5GhBxluYe+MewNiFB9qu+ssbUKZFehA7RzeCvYzp+n7q9D2tfgfjLOMcYJzXOICM953BFNHLLZAaBqi7aKveXlut8hNLVbRrPoSGPQ5BWTbQx6u9dCLgZ0jtTzq19i+6vd1bL0iVb68GqbPAJMt4gtNO2sCcYCGxpkLIInayi60TP0L7OPEEI/BFb+FWNV5ZSVz3p0V8MFs4xwgfZW/w0uX13rqy0U9ZgHF2rQ9AzFdO+ELknuyxZ6HqrwWqi0V/ekPABIwJKtvTfoCKA4yS7Wq+EaSKY8tUzPxzI7w1ZYyFe76fNXwbRlE8ny4Q2BiAMdZ3lj7m9yhfS6IaUzT4cptacjeq+ix+gxvf2+GLCfLRFXbrLDJ1lt49sfxHHV/xHzof+Fflxs6EcBer7up6dTwI0xfSr6M+nZ2aBMMDibpNaOykSqgWFyRrI2X2PdqqhosBKsucQ1V46W74dN1mF3M2wxVrpmOafHRpgy9cNUdXN9OuIXNVkEhLHQ5Q861gdRVFa5B7uTFx/wtX/Ru04GzOQKHi1JEfrOrTK7Hmc5UTipp4BiFMHKcZTkjUjLGPtMWnSxr/eEljAxcB4/wkGN1H9Sv9PYFN/spEu5lo09UN7NoyPYI37hDUsqmMK+vmYJX92TVv2uYXnVv/gy6HEIqSZpFbFsPkQfr4IlaOt+mUMEEKhzhuUiqx3WXUBT/gQrWGSwlWEHXN6cV6sdsXMLDb22YpP4yAuHUu/mCWrm321fUtfsisr65u8oG7ZushYFSQRDvvbtPH0kS8xU7rHmbu4x8wefCVrTZFHfeCVYe9L9u/UPcxj1PM4l2QlrHhdtMGEKV1iv3pImBchksAZPACrZKlcf7PbQ5PyQtfseWmhNtYWM3JAD1N9deDaD8mCn8EL9D1QD9dWguhAkb25mnKiZiBQfPRs4MLVHdlPXIZy4IzqI7dclslDBgYL8d7fm5k8vDFl+Jwtd9Yr+rji/Qy3U/PU+bqefIilSQuhTdozW0LVBCzjBI3nsMbifSBDOfoUcIiVZ1Yp74HOzRcRoKtoiXJJx64ydnOPUnBDhL4cFmc3jP/V4eo7JjpmjJuTJZJqXgJO1bY9f8DA0Idp53GidUGjJE3sRp8GgEYgXqN+Xqa6qfC2Lns3Dx2bYE1bm/11012yoCRHfI3uGOGHqX5YsZW6NPTOEJfMmyfBJnuQ/KOaMKcaJvD2ToS6qsd+vgo1AKj5jiVsXOOnibMAYyFep66VM9S2YCUFXoQtxhp9weQudbI2zZXD+MW6NWBAHXlRWm0nFRcxebBE04succ8GjAHVYAx+jpefQ/D9OYEwS3VZxIM0yKmIuyYLHNoO0Y2GYcv/hmMCcD43YQ6cyaJE7XOJHPJC6eW6WUGbJ+yQIVo/L3wBcoLnU/b+APIbJh6Oj05TQ8SfPV83yJVnmKidyM09Dzeklp7TZrc7/bqV6TP0ZBmTfJzMVnk9kY1nF2uV5mQZ5IPFTFH5elL9Wbfar5bpWfpy6x8cWe7n26FUxWMvEg/FezwV3H6Yr27vAXQkzSKr7CjInbaY+Q9Dvg9lfLOnHzFqv9RmKrPLFUxmzRl3BCo4Q1AcJnnJaiJIVO3kdXBi4xJMMbwHGP83wSx3mVOq6tiqIwwC4y3Z1aoInOxSnctVnH8IhVEzofx5+nuNhzqtjnKDZuJ9M7QQ3a2dP8SvT4RgEIEauBRtOwVTKo8SbaanfqVgObg83MJXHFnk+rPLVcFLrck1Ru+mAHYfqo+uErNp4PUciHEaoVXh1apOGOp8nb46DYqc2cbqhY6k0Q6CzlepZ77MQCeARiZspF1Rozr7czAle4m1eJun0ap6fJalR5cOAnGGGAYs+PG9Ewe4xseAaB+pBUSNaC4AcNtKqeLRAmqn15HodXBgBGo+ix8AIssI0CVJC7SY0zPwx3IW9xCPYmnr7f70t9eKM4sZGwuhLXYAqT15hYNlSRo9BURvQL5o0K+liWygFB2dY3eHIUrMvyw3zMtMF6m+avp1Dp1Xw9XX24kvxOkxouBqjq1Ro9SaREk/Da+5uKGHzF/f0byF6o9J0qfXuwiF7GBhEUn85/oN8eJAF8Pn5TFUoFbaJUAA8Y3w2X8hXV8j7yZ02sqxd1/Vm7axY3fMOC4AMMcDhswPr/Zrv6nIRYYDWdXqZLyf5Hir2dJ81WShG1OMr29RC92UYJJgBPjQ/nOojq89SjZm8Q4T5XHl6n7TrgcBCpbaYo+Y8j6H0eq/dZmAOaaB7kmdv9Jwkyc5wyV0Yodlzfpy33unR+u99c36v21jaq/sFZFEOzdGCoQMC6s/6tOBfxPwPFBLYLV+4RrvyYt43wdhMLxgSMoywG5m9Jkg7N674Wr9swafIZ5PGABYZ6X0ALW8b3JCsZzmINhgBjAgBkwcKAGDHvLTg1Vx2igJEzt9HXd6RXWRIviffQ00Q+yAwjGy7TFKtu91PosIWkWxvvqYTzSuH2qCnfOpq991Xhhg0aeJmukJFG9BdvUmRuslisbVXVsuUq5ZnEyUgnTF/A3ZXv81XV9i+yGfPEPvfe3qCufAJe9Ts/24ToB+k7ULFLoVDzIX3QjZLpeHlyDBUjRaN0h0jLZCgft7N4PGAf1K//24IY/P4khBAbiM7DjJqiZZ4/OXnTePMGyDnsAAyJ1AoQT/rDAoI2sh7SQ0HBNnAbgjFZidc3JZSrd7a+iOG8VJ85mN+fr5e6FTH4JXLJMVTD76/0r+H6BHsd7q4B88Shxlh4kztDr48vVnRuhnvwIwtgmDNZaCBntp9KKUzBaCcZ1eukxbVLBgrtvbpLjeawmMFJ2fIglv7c3qJwQ9zDV12qVnNBZur5pGr5jKt8tIgbEyV5/VPb3hzWMxNoIh7+y8f8b3/Q7Ic5emqoPN8O/R3jaw6qK/wLDsumENQsQwx+WS8WUYVqckNBIbSKVgbTeMpUB0WXO1wvk9VkqJJkxXxUHlujNkRWqPbFa785u1LusjcTvpXqC13gKET4EkAJ2vIxsUHtyrerOrFUNpVp6aBGcskTP0xfCFchpEv4CPniZ7o/ELlXP7U0afRatsco4uVGDry+2WcbszamlKtwFN5FocwHj5mZzpPAzcu6r2sth+lS2V18bCGkmo5B9JshA/wdu/M/3KFhFuvryojXF5AGHscLWoz4AMQ92IBgT4CxCNU/Rrcoxn3AJv2/0eoRsMfgiWp35W9REeq0+sljllPFLevwF/Vt+YJEaz69VJyT54Xaw2q8HseAAnORcvaJqimIIXdGYsIT5LHaRSjNptUMrVZgO8RL8SjBXTxPn6Ukc7US1vTm4RJ3XN+kTnDL8PFpOHLCD8YVUaxSt+tRygpy/HkZ76x5g5GyeritUxvVwLz09sFIdBYkaepOpr/XmgdhBnOgh/WaySjPhDR/S+3C7ppgXNmws0Cx+7CMADBggDDimYo5gw78NOMVUkPXsktg9io3+Up7ARSCya+tVd3KJKjPZQfS6BANTtm++Wk1qfBKpwaIIfi+ENlivqqNY5YwFeh4H2UbNVXGkvx5jmoqTl+rp7gDdh2zvbp+LX1mgJ7HwQKSXHsTMUDWB6uODSI0S8obLYuSsQY7fJOozwLTnbrJay4D8dMdsFYTOAIxp5BQvZYdO191kfzVc26bPL4kR1XBG80EyFl3AOuyYvK+1qeop+gcwvj8EdvUZAjVgmEOfwxozA2DG+s0bMMdkazdv0hgXiqpUp6HTO9RxC0+AcakgTBUneakI9n99eIm67oRgrGL0hd7+xA723g+2HulV7lmg0gSqaDvKg60uCPW1Tq7uxS/ULQxUbsQc7LYfQPjqPn6hMNmXFloFsNvlqErBUSbK1UCQq0HVXiKNd82ji7VsBtIeO1v3AeDWFsDY6qWrW6cTEn1ViTfpL0K1qqmMpoOk10kwXCRkc2DVxzyn2GkRM8whj1ERh3kfwgzzwAcZMueTLhOg4BZzKmYe8jg7DnAx/qZhj768iMf2ssisZSrf6wvZTdVjEmrt8ZUaLIjGVhO4Xk0C8rGY/ga4OpSiMm2hyvEkJdG41WBIL2Qm+YWgh2G6GYpfYORH0kop8/WK+N+cvUnDL5M03rSXRJtOnjHPRNI09BoVKsRzENerjizDrs/Rw8iZuF+SbZg3bYLcUi3PMWvdBQl4GdbVsF+eNqqDLPSrWVsz63iVYE7HzRNxA4J5mcM8QZsEwnr2aYKNeSrOsNMe9k6+Z3i6j2jcPD9lYsOYpF5MUvMFjNe+ufQ5xgrbXHVkpXrzt0FOqXLVYn8p61FI70tRlNqublTt4RV6nbaUdgGMkFm6QY/fMDsZPEPXAMaUeP6OOThMCPjKVowYbrUyTb+1HtLYe3PSPfmSyTDVMfAsRi05W3Cky/WcOP44xlt5BgRj0SNmWyT6BJfalrtD9jdUdA1hE7c7xlp+x0p4uN7I6+RJMAwQ5r0qG1VgvSoAINYwqJnvAMd6A4ZhKmScdpkgWJnXiMy7XIPsTOe1DXpLYKvIwGOQUZ7vWqC359ZhreM01pAhT2Oa7NUJ7O4O9d0Lo5LW6PVeTFnyArLMLOuMIpdqyGFkA4Qp7wIM3NtzIeqD/L48T5Wjdq8mqEgPrWoeO4y8ozLqIXLSZ2se9pxY8BwSNyqUj/m6TdTPjfBVbjQVi+9pvBLKdbAFFeSft7vlwp5PILMePu2Aap2BmjYx3GFeBZisjEkgnHxvvVcFb0zK7+QYM/xinoVCogaMz8Xb1X17s5pOB6gqE5OVYk6wUY1DS9WeF8rNaZWqBH2u2K6hctJocaQaLwHGQfIM7WIUIJ/2uBs+W3lM/gZqcGMbXEEoa4b4hp+RJ6r2y9VoXlyh3w3PmV43GalpF244SR8eh6seC2/i+Iv0eZg0rmWuF+6LEaN1APb1yQ3qIqt8KTNv7KTJ1UJGIbyNv8+Qm1T7d86w3rgz5wmmXb5Vwd+H4QtAm+SVo5YMuwDDHOWPvElG6mLUd2ezGpG3yozFeplMYEvEdxDeGrMDrSO2z5WxGiLcjdYmaOjVdrXcXK9qzFoFOaUwDrOEBJpDG7OAW+xqLn1etGcZ7jJcwy92y/OWiniH6rVOPqA2T+DM40qrOlCDgec7MIBBqsFvlGeSnuPmEBbnKC9stnJpl3uxfirZu5T5hOgzJsteR2WQVRz8/fj73Zpo2q0pRkkMGJPvaUwu1uy+VQnfSNMC5xsY5u2eccaYeRHO9G7tLo2UxcMPW1R/mqrAID2MYnEk1XyItBSH2Wm8QRWg1cEbdQkk3li13Q9S/aVVVp8XpfD7EdN0B864zcgBmFz6vjB9geoubKKSUuSuA4gm2pVeN2D8ivI5O/ENVMZQbTILjFXX3WC9zVqucpzqJBhwxhYv3QxGUaK8kW0/VZxaa72IZ6tOp1XMOUeyPC1pmoCQ/w6GteN4je+LNmBYvsKA0TX5/ybUmRfirKdsfO9up5rotUFi8Ie8TXpP6b/Y6a/c4Gm6FviTLm/5i+7hOZpyQ/i9dFg7Q+Pte6gQqoRK6XsSoYara1V2YKEeJ/kS9zFLwRil0L/pWsTflJ/orTen12mgMBFJRQYb9/P3h/WfOEfzxN7ZuQ9pRF3YYVslgJBX2vA8rw8i2clzAcNbN4J+0S3AyAeM/FhvlRxYrI57kVS0eU0zXfamnVRGusaoEKtNrMX9ExjGU5hhquP7/098OqVfh87gR/AcfG/AGKlOUT+S2UZgqz+7Qs92+mF4flbWyn/T8YB/UXbkT6pBPWzVlHozE6ckh+swTtVx6nsKGDcA4yhmabcfeQV/sQM5DPtZV8P/qrwEL5VittpItl9MaVuvJiH1eITvYLjxCm6k0f6aZGqUCjAqD0yC8TCKFgmZofxwknIcKkNYLCUidN6LQj3SLCUaMS/XNu8E6FT9X+KOp1reHpbwAAAAAElFTkSuQmCC\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/files", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "files" - ] - } - }, + "name": "Read file metadata", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/files/metadata", "host": ["{{EMSBASE}}"], "path": ["files", "metadata"]}}, "response": [] }, { - "name": "EMS: admin: get: files metadata: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/files/metadata", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "files", - "metadata" - ] - } - }, + "name": "Get file: 404", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 404', function () { pm.response.to.have.status(404); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/resource/nonexistent-file-id", "host": ["{{EMSBASE}}"], "path": ["resource", "nonexistent-file-id"]}}, "response": [] }, { - "name": "EMS: admin: get: file: 404", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(404);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/resource/aab7b25dab794b00263ca63ace5cf7d15ac815055464566f86dfc1ba46dc92411578573273803s", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "resource", - "aab7b25dab794b00263ca63ace5cf7d15ac815055464566f86dfc1ba46dc92411578573273803s" - ] - } - }, + "name": "Export submissions", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/export/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", "host": ["{{EMSBASE}}"], "path": ["export", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions"]}}, "response": [] }, { - "name": "EMS: teacher: post: download submissions: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"courses\": [\r\n {\r\n \"id\": \"1\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/export/exercises/1/submissions/latest", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "export", - "exercises", - "1", - "submissions", - "latest" - ] - } - }, + "name": "Evict cache", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/remove-cache", "host": ["{{EMSBASE}}"], "path": ["remove-cache"]}}, "response": [] }, { - "name": "EMS: admin: post: evict cache: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "admin" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/remove-cache", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "remove-cache" - ] - } - }, + "name": "Read course groups", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]}}, "response": [] }, { - "name": "EMS: teacher: post: compare: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"courses\": [\r\n {\r\n \"id\": \"1\"\r\n }\r\n ],\r\n \"submissions\": [\r\n {\r\n \"id\": \"1\"\r\n },\r\n {\r\n \"id\": \"2\"\r\n }\r\n ]\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/exercises/1/similarity", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "exercises", - "1", - "similarity" - ] - } - }, + "name": "Export personal course submissions", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/export/submissions", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "export", "submissions"]}}, "response": [] }, { - "name": "EMS: teacher: put: library dir change to PR access: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"group_id\": \"2\",\r\n \"access_level\" : \"PR\"\r\n}" - }, - "url": { - "raw": "{{EMSBASE}}/lib/dirs/1/access", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "lib", - "dirs", - "1", - "access" - ] - } - }, + "name": "Export personal data", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/account/export", "host": ["{{EMSBASE}}"], "path": ["account", "export"]}}, "response": [] }, { - "name": "EMS: teacher: get: read dir: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "teacher" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/lib/dirs/1", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "lib", - "dirs", - "1" - ] - } - }, + "name": "Student access tracking", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/access", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "access"]}}, "response": [] }, { - "name": "EMS: student: get: export latest personal course submissions: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/courses/1/export/submissions", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "courses", - "1", - "export", - "submissions" - ] - } - }, + "name": "Teacher access tracking", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/access", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "access"]}}, "response": [] }, { - "name": "EMS: student: get: export personal data: success", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "pm.test(\"response is ok\", function () {\r", - " pm.response.to.have.status(200);\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - }, - { - "key": "oidc_claim_easy_role", - "type": "text", - "value": "student" - }, - { - "key": "oidc_claim_email", - "type": "text", - "value": "foo@bar.com" - }, - { - "key": "oidc_claim_given_name", - "type": "text", - "value": "Foo" - }, - { - "key": "oidc_claim_family_name", - "type": "text", - "value": "Bar" - }, - { - "key": "oidc_claim_preferred_username", - "type": "text", - "value": "fp" - } - ], - "url": { - "raw": "{{EMSBASE}}/account/export", - "host": [ - "{{EMSBASE}}" - ], - "path": [ - "account", - "export" - ] - } - }, + "name": "Read system properties", + "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], + "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/system/properties", "host": ["{{EMSBASE}}"], "path": ["system", "properties"]}}, "response": [] } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } ] -} \ No newline at end of file +} From 3b5f16db06a164e69e0a6274d7c219c256e42b8e Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Tue, 3 Mar 2026 10:52:22 +0200 Subject: [PATCH 6/7] EZ-1615 fix exposed migration related issues --- core/src/main/kotlin/core/db/Tables.kt | 6 +++--- .../course/ExportPersonalCourseLatestSubmissions.kt | 7 ++++++- .../ems/service/course/ReadParticipantsOnCourse.kt | 10 +++++++++- .../core/ems/service/course/ReadTeacherCourses.kt | 9 ++++++++- core/src/main/kotlin/core/ems/service/courses.kt | 8 +++++++- .../ems/service/exercise/TeacherDownloadSubmissions.kt | 7 ++++++- core/src/main/kotlin/core/util/combinations.kt | 2 +- 7 files changed, 40 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/core/db/Tables.kt b/core/src/main/kotlin/core/db/Tables.kt index 26f881bc..8c0409e3 100644 --- a/core/src/main/kotlin/core/db/Tables.kt +++ b/core/src/main/kotlin/core/db/Tables.kt @@ -141,8 +141,8 @@ object StudentCourseAccess : Table("student_course_access") { } object StudentCourseGroup : Table("student_course_group_access") { - val student = reference("student_id", StudentCourseAccess.student) - val course = reference("course_id", StudentCourseAccess.course) + val student = reference("student_id", Account) + val course = reference("course_id", Course) val courseGroup = reference("group_id", CourseGroup) override val primaryKey = PrimaryKey(student, course, courseGroup) } @@ -158,7 +158,7 @@ object StudentMoodlePendingAccess : Table("student_moodle_pending_access") { object StudentMoodlePendingCourseGroup : Table("student_moodle_pending_course_group_access") { val moodleUsername = reference("moodle_username", StudentMoodlePendingAccess.moodleUsername) - val course = reference("course_id", StudentMoodlePendingAccess.course) + val course = reference("course_id", Course) val courseGroup = reference("group_id", CourseGroup) override val primaryKey = PrimaryKey(moodleUsername, course, courseGroup) } diff --git a/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt b/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt index c5c974f9..63557ff3 100644 --- a/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/course/ExportPersonalCourseLatestSubmissions.kt @@ -79,10 +79,15 @@ class ExportPersonalCourseLatestSubmissions { private fun zip(courseSolution: CourseSolution): ByteArrayResource { val zipOutputStream = ByteArrayOutputStream() + val nameCount = mutableMapOf() ZipOutputStream(zipOutputStream).use { zipStream -> courseSolution.solutions.forEach { - val entry = ZipEntry(it.exerciseName + ".py") + val baseName = it.exerciseName + val count = nameCount.getOrDefault(baseName, 0) + nameCount[baseName] = count + 1 + val fileName = if (count == 0) "$baseName.py" else "$baseName ($count).py" + val entry = ZipEntry(fileName) entry.lastModifiedTime = FileTime.fromMillis(it.createdAt.millis) zipStream.putNextEntry(entry) zipStream.write(it.submission.toByteArray(Charsets.UTF_8)) diff --git a/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt b/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt index 319e2ced..e3ed440f 100644 --- a/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt +++ b/core/src/main/kotlin/core/ems/service/course/ReadParticipantsOnCourse.kt @@ -15,6 +15,8 @@ import core.ems.service.selectStudentsOnCourse import core.exception.InvalidRequestException import core.util.DateTimeSerializer import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.JoinType +import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.dao.id.EntityID import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.jdbc.andWhere @@ -128,7 +130,13 @@ class ReadParticipantsOnCourseController { transaction { data class PendingStudent(val moodleUsername: String, val email: String, val inviteId: String) - (StudentMoodlePendingAccess leftJoin StudentMoodlePendingCourseGroup leftJoin CourseGroup) + StudentMoodlePendingAccess + .join(StudentMoodlePendingCourseGroup, JoinType.LEFT, + additionalConstraint = { + (StudentMoodlePendingAccess.moodleUsername eq StudentMoodlePendingCourseGroup.moodleUsername) and + (StudentMoodlePendingAccess.course eq StudentMoodlePendingCourseGroup.course) + }) + .join(CourseGroup, JoinType.LEFT, StudentMoodlePendingCourseGroup.courseGroup, CourseGroup.id) .select( StudentMoodlePendingAccess.moodleUsername, StudentMoodlePendingAccess.email, diff --git a/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt b/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt index 03b62a29..a982b1e2 100644 --- a/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt +++ b/core/src/main/kotlin/core/ems/service/course/ReadTeacherCourses.kt @@ -8,7 +8,9 @@ import core.db.StudentCourseGroup import core.db.TeacherCourseAccess import core.util.DateTimeSerializer import io.github.oshai.kotlinlogging.KotlinLogging +import org.jetbrains.exposed.v1.core.JoinType import org.jetbrains.exposed.v1.core.alias +import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.count import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.jdbc.select @@ -135,7 +137,12 @@ class ReadTeacherCourses { private fun selectStudentCountForCourse(courseId: Long): Long = // Select distinct students, ignoring their groups - (StudentCourseAccess leftJoin StudentCourseGroup) + StudentCourseAccess.join( + StudentCourseGroup, JoinType.LEFT, + additionalConstraint = { + (StudentCourseAccess.student eq StudentCourseGroup.student) and + (StudentCourseAccess.course eq StudentCourseGroup.course) + }) .select(StudentCourseAccess.student) .where { StudentCourseAccess.course eq courseId } .withDistinct() diff --git a/core/src/main/kotlin/core/ems/service/courses.kt b/core/src/main/kotlin/core/ems/service/courses.kt index bcaf2baa..83c9bc3d 100644 --- a/core/src/main/kotlin/core/ems/service/courses.kt +++ b/core/src/main/kotlin/core/ems/service/courses.kt @@ -351,7 +351,13 @@ fun selectAllCourseExercisesLatestSubmissions( } fun selectStudentsOnCourse(courseId: Long, groupId: Long? = null): List = transaction { - val query = (Account innerJoin StudentCourseAccess leftJoin StudentCourseGroup leftJoin CourseGroup) + val query = (Account innerJoin StudentCourseAccess) + .join(StudentCourseGroup, JoinType.LEFT, + additionalConstraint = { + (StudentCourseAccess.student eq StudentCourseGroup.student) and + (StudentCourseAccess.course eq StudentCourseGroup.course) + }) + .join(CourseGroup, JoinType.LEFT, StudentCourseGroup.courseGroup, CourseGroup.id) .select( Account.id, Account.email, Account.givenName, Account.familyName, StudentCourseAccess.moodleUsername, StudentCourseAccess.createdAt, CourseGroup.id, CourseGroup.name diff --git a/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt b/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt index 4e6d5045..c6046d37 100644 --- a/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt +++ b/core/src/main/kotlin/core/ems/service/exercise/TeacherDownloadSubmissions.kt @@ -76,7 +76,12 @@ class TeacherDownloadSubmissionsController { transaction { val join1 = Submission innerJoin CourseExercise innerJoin Account - val join2 = StudentCourseAccess leftJoin (StudentCourseGroup innerJoin CourseGroup) + val join2 = StudentCourseAccess.join( + StudentCourseGroup.innerJoin(CourseGroup), JoinType.LEFT, + additionalConstraint = { + (StudentCourseAccess.student eq StudentCourseGroup.student) and + (StudentCourseAccess.course eq StudentCourseGroup.course) + }) val query = Join(join1, join2, onColumn = Submission.student, otherColumn = StudentCourseAccess.student) .select( diff --git a/core/src/main/kotlin/core/util/combinations.kt b/core/src/main/kotlin/core/util/combinations.kt index 68a0f6e5..66b0882c 100644 --- a/core/src/main/kotlin/core/util/combinations.kt +++ b/core/src/main/kotlin/core/util/combinations.kt @@ -11,7 +11,7 @@ fun , U> T.combinations(combinationSize: Int): Set> { fun , U> T.forEachCombination(combinationSize: Int, callback: (Set) -> Unit) { when { - combinationSize < 2 -> IllegalArgumentException("combinationSize must be at least 2, actual value: $combinationSize") + combinationSize < 2 -> throw IllegalArgumentException("combinationSize must be at least 2, actual value: $combinationSize") this.size <= combinationSize -> callback(this.toSet()) } From 491e4078f5c67cf859fefa6e5142ae70ece090b8 Mon Sep 17 00:00:00 2001 From: priitpaluoja Date: Tue, 3 Mar 2026 11:29:28 +0200 Subject: [PATCH 7/7] EZ-1615 do not leak exception messages, add TODOs --- .../kotlin/core/ems/service/statistics.kt | 3 +- .../core/exception/exception_handlers.kt | 4 +- .../resources/EMS.postman_collection.json | 6142 ++++++++++++++--- 3 files changed, 5249 insertions(+), 900 deletions(-) diff --git a/core/src/main/kotlin/core/ems/service/statistics.kt b/core/src/main/kotlin/core/ems/service/statistics.kt index 4e9f8540..9848a747 100644 --- a/core/src/main/kotlin/core/ems/service/statistics.kt +++ b/core/src/main/kotlin/core/ems/service/statistics.kt @@ -3,10 +3,10 @@ package core.ems.service import com.fasterxml.jackson.annotation.JsonProperty import core.conf.security.EasyUser import core.ems.service.cache.CachingService +import io.github.oshai.kotlinlogging.KotlinLogging import jakarta.validation.Valid import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.runBlocking -import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.scheduling.annotation.Scheduled import org.springframework.security.access.annotation.Secured import org.springframework.stereotype.Service @@ -22,6 +22,7 @@ private val log = KotlinLogging.logger {} @RequestMapping("/v2") class StatisticsController(private val statisticsService: StatisticsService) { + // TODO: does this controller really need dto as parameter? Postman test currently fails for it as this controller expects request body. @Secured("ROLE_TEACHER", "ROLE_ADMIN", "ROLE_STUDENT") @PostMapping("/statistics/common") fun controller(@Valid @RequestBody dto: StatResp?, caller: EasyUser): StatResp { diff --git a/core/src/main/kotlin/core/exception/exception_handlers.kt b/core/src/main/kotlin/core/exception/exception_handlers.kt index 490b01ee..3a4e3b42 100644 --- a/core/src/main/kotlin/core/exception/exception_handlers.kt +++ b/core/src/main/kotlin/core/exception/exception_handlers.kt @@ -20,7 +20,7 @@ import tools.jackson.databind.exc.InvalidNullException import tools.jackson.databind.exc.MismatchedInputException import java.util.* - +// TODO: check that exception handlers do not leak system information @ControllerAdvice class EasyExceptionHandler(private val mailService: SendMailService) : ResponseEntityExceptionHandler() { private val log = KotlinLogging.logger {} @@ -135,7 +135,7 @@ class EasyExceptionHandler(private val mailService: SendMailService) : ResponseE } is JsonParseException -> "Invalid JSON format: JSON parsing failed" - else -> ex.message ?: "Invalid request!" + else -> "Request body is malformed." } val resp = RequestErrorResponse(id, ReqError.INVALID_PARAMETER_VALUE.errorCodeStr, emptyMap(), msg) diff --git a/core/src/test/resources/EMS.postman_collection.json b/core/src/test/resources/EMS.postman_collection.json index 7878bfa3..6b895f75 100644 --- a/core/src/test/resources/EMS.postman_collection.json +++ b/core/src/test/resources/EMS.postman_collection.json @@ -1,898 +1,5246 @@ { - "info": { - "_postman_id": "af0266c7-020e-43e8-bc80-aaeab9e6f6bc", - "name": "EMS", - "description": "This collection implements tests for the API of easy:ems. Tests must run sequentially. Set EMSBASE variable to http://localhost:8080/v2", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Checkin admin (fp)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"first_name\": \"Fred\", \"last_name\": \"Pansen\"}"}, - "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} - }, - "response": [] - }, - { - "name": "Checkin student (fp)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"first_name\": \"Fred\", \"last_name\": \"Pansen\"}"}, - "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} - }, - "response": [] - }, - { - "name": "Checkin teacher (pf)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"first_name\": \"Pera\", \"last_name\": \"Fansen\"}"}, - "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} - }, - "response": [] - }, - { - "name": "Checkin teacher as admin (pf)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"first_name\": \"Pera\", \"last_name\": \"Fansen\"}"}, - "url": {"raw": "{{EMSBASE}}/account/checkin", "host": ["{{EMSBASE}}"], "path": ["account", "checkin"]} - }, - "response": [] - }, - { - "name": "Create container image", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"id\": \"sample-container-image\"}"}, - "url": {"raw": "{{EMSBASE}}/container-images", "host": ["{{EMSBASE}}"], "path": ["container-images"]} - }, - "response": [] - }, - { - "name": "Create executor 1", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('executor1_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Test executor 1\", \"base_url\": \"http://executor:8080\", \"max_load\": 10}"}, - "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} - }, - "response": [] - }, - { - "name": "Create executor 2", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('executor2_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Test executor 2\", \"base_url\": \"http://executor2:8080\", \"max_load\": 5}"}, - "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} - }, - "response": [] - }, - { - "name": "Create executor: fail wrong role", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 403', function () { pm.response.to.have.status(403); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Fail executor\", \"base_url\": \"http://fail:8080\", \"max_load\": 1}"}, - "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} - }, - "response": [] - }, - { - "name": "Update executor", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "PUT", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Test executor 1 updated\", \"base_url\": \"http://executor:8080\", \"max_load\": 20, \"drain\": false, \"containers\": [\"sample-container-image\"]}"}, - "url": {"raw": "{{EMSBASE}}/executors/{{executor1_id}}", "host": ["{{EMSBASE}}"], "path": ["executors", "{{executor1_id}}"]} - }, - "response": [] - }, - { - "name": "Read all executors", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "GET", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/executors", "host": ["{{EMSBASE}}"], "path": ["executors"]} - }, - "response": [] - }, - { - "name": "Read all container images", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "GET", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/container-images", "host": ["{{EMSBASE}}"], "path": ["container-images"]} - }, - "response": [] - }, - { - "name": "Delete executor 2", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "DELETE", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/executors/{{executor2_id}}", "host": ["{{EMSBASE}}"], "path": ["executors", "{{executor2_id}}"]} - }, - "response": [] - }, - { - "name": "Delete executor 2 again: expect error", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], - "request": { - "method": "DELETE", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/executors/{{executor2_id}}", "host": ["{{EMSBASE}}"], "path": ["executors", "{{executor2_id}}"]} - }, - "response": [] - }, - { - "name": "Create course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('course_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"title\": \"Test Course\", \"color\": \"#7986cb\"}"}, - "url": {"raw": "{{EMSBASE}}/admin/courses", "host": ["{{EMSBASE}}"], "path": ["admin", "courses"]} - }, - "response": [] - }, - { - "name": "Add teacher to course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"teachers\": [{\"email\": \"bar@foo.com\"}]}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "teachers"]} - }, - "response": [] - }, - { - "name": "Remove teacher from course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "DELETE", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"teachers\": [{\"id\": \"pf\"}]}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "teachers"]} - }, - "response": [] - }, - { - "name": "Re-add teacher to course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"teachers\": [{\"email\": \"bar@foo.com\"}]}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "teachers"]} - }, - "response": [] - }, - { - "name": "Read basic course info", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "GET", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/basic", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "basic"]} - }, - "response": [] - }, - { - "name": "Read teacher courses", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "GET", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/teacher/courses", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses"]} - }, - "response": [] - }, - { - "name": "Create exercise TEACHER type", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('exercise1_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"title\": \"Test exercise TEACHER\", \"public\": true, \"anonymous_autoassess_enabled\": false, \"grader_type\": \"TEACHER\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\"}"}, - "url": {"raw": "{{EMSBASE}}/exercises", "host": ["{{EMSBASE}}"], "path": ["exercises"]} - }, - "response": [] - }, - { - "name": "Create exercise AUTO type", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('exercise2_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"title\": \"Test exercise AUTO\", \"public\": true, \"anonymous_autoassess_enabled\": true, \"grader_type\": \"AUTO\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\", \"grading_script\": \"#!/bin/bash\\necho 100\", \"container_image\": \"sample-container-image\", \"max_time_sec\": 60, \"max_mem_mb\": 256, \"assets\": [{\"file_name\": \"test.sh\", \"file_content\": \"echo test\"}]}"}, - "url": {"raw": "{{EMSBASE}}/exercises", "host": ["{{EMSBASE}}"], "path": ["exercises"]} - }, - "response": [] - }, - { - "name": "Create exercise: fail missing title", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"public\": true, \"anonymous_autoassess_enabled\": false, \"grader_type\": \"TEACHER\", \"solution_file_name\": \"sol.py\", \"solution_file_type\": \"TEXT_EDITOR\"}"}, - "url": {"raw": "{{EMSBASE}}/exercises", "host": ["{{EMSBASE}}"], "path": ["exercises"]} - }, - "response": [] - }, - { - "name": "Add exercise 1 to course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('course_ex1_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"exercise_id\": \"{{exercise1_id}}\", \"threshold\": 50, \"student_visible\": true, \"assessments_student_visible\": true}"}, - "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises"]} - }, - "response": [] - }, - { - "name": "Add exercise 2 to course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('course_ex2_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"exercise_id\": \"{{exercise2_id}}\", \"threshold\": 0, \"student_visible\": true, \"assessments_student_visible\": true}"}, - "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises"]} - }, - "response": [] - }, - { - "name": "Create course group g1", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('group1_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Group 1\"}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]} - }, - "response": [] - }, - { - "name": "Delete course group g1", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "DELETE", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group1_id}}", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups", "{{group1_id}}"]} - }, - "response": [] - }, - { - "name": "Create course group g2", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('group2_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Group 2\"}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]} - }, - "response": [] - }, - { - "name": "Create course group g3", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('group3_id', jsonData.id);"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"name\": \"Group 3\"}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]} - }, - "response": [] - }, - { - "name": "Generate course invite", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('invite_id', jsonData.invite_id);"], "type": "text/javascript"}}], - "request": { - "method": "PUT", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"expires_at\": \"2099-01-01T00:00:00.000Z\", \"allowed_uses\": 100}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/invite", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "invite"]} - }, - "response": [] - }, - { - "name": "Read course invite", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "GET", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/invite", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "invite"]} - }, - "response": [] - }, - { - "name": "Join course by invite", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, - {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "student", "type": "text"} - ], - "url": {"raw": "{{EMSBASE}}/courses/join/{{invite_id}}", "host": ["{{EMSBASE}}"], "path": ["courses", "join", "{{invite_id}}"]} - }, - "response": [] - }, - { - "name": "Add student to group", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "POST", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"active_students\": [{\"id\": \"fp\"}], \"moodle_pending_students\": []}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group2_id}}/students", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups", "{{group2_id}}", "students"]} - }, - "response": [] - }, - { - "name": "Remove student from group", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": { - "method": "DELETE", - "header": [ - {"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, - {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, - {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, - {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, - {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, - {"key": "Content-Type", "value": "application/json", "type": "text"} - ], - "body": {"mode": "raw", "raw": "{\"active_students\": [{\"id\": \"fp\"}], \"moodle_pending_students\": []}"}, - "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group2_id}}/students", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups", "{{group2_id}}", "students"]} - }, - "response": [] - }, - { - "name": "Read exercise", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", "host": ["{{EMSBASE}}"], "path": ["exercises", "{{exercise1_id}}"]}}, - "response": [] - }, - { - "name": "Read student courses", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses", "host": ["{{EMSBASE}}"], "path": ["student", "courses"]}}, - "response": [] - }, - { - "name": "Read student exercises on course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises"]}}, - "response": [] - }, - { - "name": "Read student exercise details", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}"]}}, - "response": [] - }, - { - "name": "Read teacher exercises on course", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises"]}}, - "response": [] - }, - { - "name": "Read teacher exercise details", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}"]}}, - "response": [] - }, - { - "name": "Read course participants", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/participants", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "participants"]}}, - "response": [] - }, - { - "name": "Submit solution 1", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"solution\": \"print('hello')\"}"}, "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions"]}}, - "response": [] - }, - { - "name": "Submit solution 2", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"solution\": \"print('hello world')\"}"}, "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions"]}}, - "response": [] - }, - { - "name": "Save draft", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"solution\": \"draft solution\"}"}, "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/draft", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "draft"]}}, - "response": [] - }, - { - "name": "Read draft", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/draft", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "draft"]}}, - "response": [] - }, - { - "name": "Read student submissions", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/all", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "all"]}}, - "response": [] - }, - { - "name": "Read all grades", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/teacher/{{course_id}}/grades", "host": ["{{EMSBASE}}"], "path": ["courses", "teacher", "{{course_id}}", "grades"]}}, - "response": [] - }, - { - "name": "Read all grades with search", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/teacher/{{course_id}}/grades?search=Fred", "host": ["{{EMSBASE}}"], "path": ["courses", "teacher", "{{course_id}}", "grades"], "query": [{"key": "search", "value": "Fred"}]}}, - "response": [] - }, - { - "name": "Read all submissions by student", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "if (jsonData.submissions && jsonData.submissions.length > 0) {", " pm.environment.set('submission_id', jsonData.submissions[jsonData.submissions.length - 1].id);", "} else if (Array.isArray(jsonData) && jsonData.length > 0) {", " pm.environment.set('submission_id', jsonData[jsonData.length - 1].id);", "}"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/all/students/fp", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "all", "students", "fp"]}}, - "response": [] - }, - { - "name": "Read submission summaries", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/latest/students", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "latest", "students"]}}, - "response": [] - }, - { - "name": "Post grade 0", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": 0, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, - "response": [] - }, - { - "name": "Post grade 100", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": 100, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, - "response": [] - }, - { - "name": "Post grade fail -1", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": -1, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, - "response": [] - }, - { - "name": "Post grade fail 101", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"grade\": 101, \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "grade"]}}, - "response": [] - }, - { - "name": "Post feedback", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"feedback_md\": \"Good work!\", \"notify_student\": false}"}, "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/feedback", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions", "{{submission_id}}", "feedback"]}}, - "response": [] - }, - { - "name": "Create notification", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"message\": \"Test notification\"}"}, "url": {"raw": "{{EMSBASE}}/management/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "notifications"]}}, - "response": [] - }, - { - "name": "Read notifications (admin)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "if (Array.isArray(jsonData) && jsonData.length > 0) {", " pm.environment.set('notification_id', jsonData[0].id);", "}"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "notifications"]}}, - "response": [] - }, - { - "name": "Read common notifications", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/common/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "common", "notifications"]}}, - "response": [] - }, - { - "name": "Update notification", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "PATCH", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"message\": \"Updated notification\"}"}, "url": {"raw": "{{EMSBASE}}/management/notifications/{{notification_id}}", "host": ["{{EMSBASE}}"], "path": ["management", "notifications", "{{notification_id}}"]}}, - "response": [] - }, - { - "name": "Read notifications again", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/notifications", "host": ["{{EMSBASE}}"], "path": ["management", "notifications"]}}, - "response": [] - }, - { - "name": "Delete notification", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "DELETE", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/management/notifications/{{notification_id}}", "host": ["{{EMSBASE}}"], "path": ["management", "notifications", "{{notification_id}}"]}}, - "response": [] - }, - { - "name": "Report client log", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"log_level\": \"WARN\", \"log_message\": \"Test log message\", \"client_id\": \"postman-test\"}"}, "url": {"raw": "{{EMSBASE}}/management/log", "host": ["{{EMSBASE}}"], "path": ["management", "log"]}}, - "response": [] - }, - { - "name": "Read statistics", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{}"}, "url": {"raw": "{{EMSBASE}}/statistics/common", "host": ["{{EMSBASE}}"], "path": ["statistics", "common"]}}, - "response": [] - }, - { - "name": "AsciiDoc preview", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"content\": \"= Hello\\n\\nWorld\"}"}, "url": {"raw": "{{EMSBASE}}/preview/adoc", "host": ["{{EMSBASE}}"], "path": ["preview", "adoc"]}}, - "response": [] - }, - { - "name": "Markdown preview", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"content\": \"# Hello\\n\\nWorld\"}"}, "url": {"raw": "{{EMSBASE}}/preview/markdown", "host": ["{{EMSBASE}}"], "path": ["preview", "markdown"]}}, - "response": [] - }, - { - "name": "Create article", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", "var jsonData = pm.response.json();", "pm.environment.set('article_id', jsonData.id);"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"title\": \"Test Article\", \"text_adoc\": \"= Article\\n\\nContent\", \"public\": true}"}, "url": {"raw": "{{EMSBASE}}/articles", "host": ["{{EMSBASE}}"], "path": ["articles"]}}, - "response": [] - }, - { - "name": "Read article (admin)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}"]}}, - "response": [] - }, - { - "name": "Read article (student)", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}"]}}, - "response": [] - }, - { - "name": "Update article", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "PUT", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"title\": \"Updated Article\", \"text_adoc\": \"= Updated\\n\\nContent\", \"public\": true}"}, "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}"]}}, - "response": [] - }, - { - "name": "Update exercise", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "PUT", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"title\": \"Updated exercise TEACHER\", \"grader_type\": \"TEACHER\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\"}"}, "url": {"raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", "host": ["{{EMSBASE}}"], "path": ["exercises", "{{exercise1_id}}"]}}, - "response": [] - }, - { - "name": "Read exercise after update", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", "host": ["{{EMSBASE}}"], "path": ["exercises", "{{exercise1_id}}"]}}, - "response": [] - }, - { - "name": "Create article alias", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"alias\": \"testAlias\"}"}, "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}/aliases", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}", "aliases"]}}, - "response": [] - }, - { - "name": "Read article by alias", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/testAlias", "host": ["{{EMSBASE}}"], "path": ["articles", "testAlias"]}}, - "response": [] - }, - { - "name": "Create article alias: fail duplicate", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 400', function () { pm.response.to.have.status(400); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}, {"key": "Content-Type", "value": "application/json", "type": "text"}], "body": {"mode": "raw", "raw": "{\"alias\": \"testAlias\"}"}, "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}/aliases", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}", "aliases"]}}, - "response": [] - }, - { - "name": "Read article aliases", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/article-aliases", "host": ["{{EMSBASE}}"], "path": ["article-aliases"]}}, - "response": [] - }, - { - "name": "Delete article alias", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "DELETE", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/articles/{{article_id}}/aliases/testAlias", "host": ["{{EMSBASE}}"], "path": ["articles", "{{article_id}}", "aliases", "testAlias"]}}, - "response": [] - }, - { - "name": "Read file metadata", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/files/metadata", "host": ["{{EMSBASE}}"], "path": ["files", "metadata"]}}, - "response": [] - }, - { - "name": "Get file: 404", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 404', function () { pm.response.to.have.status(404); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/resource/nonexistent-file-id", "host": ["{{EMSBASE}}"], "path": ["resource", "nonexistent-file-id"]}}, - "response": [] - }, - { - "name": "Export submissions", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/export/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", "host": ["{{EMSBASE}}"], "path": ["export", "courses", "{{course_id}}", "exercises", "{{course_ex1_id}}", "submissions"]}}, - "response": [] - }, - { - "name": "Evict cache", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/remove-cache", "host": ["{{EMSBASE}}"], "path": ["remove-cache"]}}, - "response": [] - }, - { - "name": "Read course groups", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/groups", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "groups"]}}, - "response": [] - }, - { - "name": "Export personal course submissions", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/courses/{{course_id}}/export/submissions", "host": ["{{EMSBASE}}"], "path": ["courses", "{{course_id}}", "export", "submissions"]}}, - "response": [] - }, - { - "name": "Export personal data", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/account/export", "host": ["{{EMSBASE}}"], "path": ["account", "export"]}}, - "response": [] - }, - { - "name": "Student access tracking", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "student", "type": "text"}], "url": {"raw": "{{EMSBASE}}/student/courses/{{course_id}}/access", "host": ["{{EMSBASE}}"], "path": ["student", "courses", "{{course_id}}", "access"]}}, - "response": [] - }, - { - "name": "Teacher access tracking", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "POST", "header": [{"key": "oidc_claim_preferred_username", "value": "pf", "type": "text"}, {"key": "oidc_claim_email", "value": "bar@foo.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Pera", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Fansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "teacher", "type": "text"}], "url": {"raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/access", "host": ["{{EMSBASE}}"], "path": ["teacher", "courses", "{{course_id}}", "access"]}}, - "response": [] - }, - { - "name": "Read system properties", - "event": [{"listen": "test", "script": {"exec": ["pm.test('Status code is 200', function () { pm.response.to.have.status(200); });"], "type": "text/javascript"}}], - "request": {"method": "GET", "header": [{"key": "oidc_claim_preferred_username", "value": "fp", "type": "text"}, {"key": "oidc_claim_email", "value": "foo@bar.com", "type": "text"}, {"key": "oidc_claim_given_name", "value": "Fred", "type": "text"}, {"key": "oidc_claim_family_name", "value": "Pansen", "type": "text"}, {"key": "oidc_claim_easy_role", "value": "admin", "type": "text"}], "url": {"raw": "{{EMSBASE}}/system/properties", "host": ["{{EMSBASE}}"], "path": ["system", "properties"]}}, - "response": [] - } - ] -} + "info": { + "_postman_id": "1f23e238-a187-4d53-b801-5d96ec511836", + "name": "EMS", + "description": "This collection implements tests for the API of easy:ems. Tests must run sequentially. Set EMSBASE variable to http://localhost:8080/v2", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "31208025" + }, + "item": [ + { + "name": "Checkin admin (fp)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"first_name\": \"Fred\", \"last_name\": \"Pansen\"}" + }, + "url": { + "raw": "{{EMSBASE}}/account/checkin", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "account", + "checkin" + ] + } + }, + "response": [] + }, + { + "name": "Checkin student (fp)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"first_name\": \"Fred\", \"last_name\": \"Pansen\"}" + }, + "url": { + "raw": "{{EMSBASE}}/account/checkin", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "account", + "checkin" + ] + } + }, + "response": [] + }, + { + "name": "Checkin teacher (pf)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"first_name\": \"Pera\", \"last_name\": \"Fansen\"}" + }, + "url": { + "raw": "{{EMSBASE}}/account/checkin", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "account", + "checkin" + ] + } + }, + "response": [] + }, + { + "name": "Checkin teacher as admin (pf)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"first_name\": \"Pera\", \"last_name\": \"Fansen\"}" + }, + "url": { + "raw": "{{EMSBASE}}/account/checkin", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "account", + "checkin" + ] + } + }, + "response": [] + }, + { + "name": "Create container image", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"id\": \"sample-container-image\"}" + }, + "url": { + "raw": "{{EMSBASE}}/container-images", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "container-images" + ] + } + }, + "response": [] + }, + { + "name": "Create executor 1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('executor1_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Test executor 1\", \"base_url\": \"http://executor:8080\", \"max_load\": 10}" + }, + "url": { + "raw": "{{EMSBASE}}/executors", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors" + ] + } + }, + "response": [] + }, + { + "name": "Create executor 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('executor2_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Test executor 2\", \"base_url\": \"http://executor2:8080\", \"max_load\": 5}" + }, + "url": { + "raw": "{{EMSBASE}}/executors", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors" + ] + } + }, + "response": [] + }, + { + "name": "Create executor: fail wrong role", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 403', function () { pm.response.to.have.status(403); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Fail executor\", \"base_url\": \"http://fail:8080\", \"max_load\": 1}" + }, + "url": { + "raw": "{{EMSBASE}}/executors", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors" + ] + } + }, + "response": [] + }, + { + "name": "Update executor", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Test executor 1 updated\", \"base_url\": \"http://executor:8080\", \"max_load\": 20, \"drain\": false, \"containers\": [\"sample-container-image\"]}" + }, + "url": { + "raw": "{{EMSBASE}}/executors/{{executor1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors", + "{{executor1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Read all executors", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/executors", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors" + ] + } + }, + "response": [] + }, + { + "name": "Read all container images", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/container-images", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "container-images" + ] + } + }, + "response": [] + }, + { + "name": "Delete executor 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"force\":false}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{EMSBASE}}/executors/{{executor2_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors", + "{{executor2_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete executor 2 again: expect error", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () { pm.response.to.have.status(400); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/executors/{{executor2_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "executors", + "{{executor2_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Create course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('course_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"title\": \"Test Course\", \"color\": \"#7986cb\"}" + }, + "url": { + "raw": "{{EMSBASE}}/admin/courses", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "admin", + "courses" + ] + } + }, + "response": [] + }, + { + "name": "Add teacher to course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"teachers\": [{\"email\": \"bar@foo.com\"}]}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "teachers" + ] + } + }, + "response": [] + }, + { + "name": "Remove teacher from course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"teachers\": [{\"id\": \"pf\"}]}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "teachers" + ] + } + }, + "response": [] + }, + { + "name": "Re-add teacher to course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"teachers\": [{\"email\": \"bar@foo.com\"}]}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/teachers", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "teachers" + ] + } + }, + "response": [] + }, + { + "name": "Read basic course info", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/basic", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "basic" + ] + } + }, + "response": [] + }, + { + "name": "Read teacher courses", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/teacher/courses", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses" + ] + } + }, + "response": [] + }, + { + "name": "Create exercise TEACHER type", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('exercise1_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"title\": \"Test exercise TEACHER\", \"public\": true, \"anonymous_autoassess_enabled\": false, \"grader_type\": \"TEACHER\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\"}" + }, + "url": { + "raw": "{{EMSBASE}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Create exercise AUTO type", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('exercise2_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"title\": \"Test exercise AUTO\", \"public\": true, \"anonymous_autoassess_enabled\": true, \"grader_type\": \"AUTO\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\", \"grading_script\": \"#!/bin/bash\\necho 100\", \"container_image\": \"sample-container-image\", \"max_time_sec\": 60, \"max_mem_mb\": 256, \"assets\": [{\"file_name\": \"test.sh\", \"file_content\": \"echo test\"}]}" + }, + "url": { + "raw": "{{EMSBASE}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Create exercise: fail missing title", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () { pm.response.to.have.status(400); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"public\": true, \"anonymous_autoassess_enabled\": false, \"grader_type\": \"TEACHER\", \"solution_file_name\": \"sol.py\", \"solution_file_type\": \"TEXT_EDITOR\"}" + }, + "url": { + "raw": "{{EMSBASE}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Add exercise 1 to course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('course_ex1_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"exercise_id\": \"{{exercise1_id}}\", \"threshold\": 50, \"student_visible\": true, \"assessments_student_visible\": true}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Add exercise 2 to course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('course_ex2_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"exercise_id\": \"{{exercise2_id}}\", \"threshold\": 0, \"student_visible\": true, \"assessments_student_visible\": true}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Create course group g1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('group1_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Group 1\"}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups" + ] + } + }, + "response": [] + }, + { + "name": "Delete course group g1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups", + "{{group1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Create course group g2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('group2_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Group 2\"}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups" + ] + } + }, + "response": [] + }, + { + "name": "Create course group g3", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('group3_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\": \"Group 3\"}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups" + ] + } + }, + "response": [] + }, + { + "name": "Generate course invite", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('invite_id', jsonData.invite_id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"expires_at\": \"2099-01-01T00:00:00.000Z\", \"allowed_uses\": 100}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/invite", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "invite" + ] + } + }, + "response": [] + }, + { + "name": "Read course invite", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/invite", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "invite" + ] + } + }, + "response": [] + }, + { + "name": "Join course by invite", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/join/{{invite_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "join", + "{{invite_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Add student to group", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"active_students\": [{\"id\": \"fp\"}], \"moodle_pending_students\": []}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group2_id}}/students", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups", + "{{group2_id}}", + "students" + ] + } + }, + "response": [] + }, + { + "name": "Remove student from group", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"active_students\": [{\"id\": \"fp\"}], \"moodle_pending_students\": []}" + }, + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups/{{group2_id}}/students", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups", + "{{group2_id}}", + "students" + ] + } + }, + "response": [] + }, + { + "name": "Read exercise", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "exercises", + "{{exercise1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Read student courses", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/student/courses", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses" + ] + } + }, + "response": [] + }, + { + "name": "Read student exercises on course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Read student exercise details", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Read teacher exercises on course", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises" + ] + } + }, + "response": [] + }, + { + "name": "Read teacher exercise details", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Read course participants", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/participants", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "participants" + ] + } + }, + "response": [] + }, + { + "name": "Submit solution 1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"solution\": \"print('hello')\"}" + }, + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions" + ] + } + }, + "response": [] + }, + { + "name": "Submit solution 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"solution\": \"print('hello world')\"}" + }, + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions" + ] + } + }, + "response": [] + }, + { + "name": "Save draft", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"solution\": \"draft solution\"}" + }, + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/draft", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "draft" + ] + } + }, + "response": [] + }, + { + "name": "Read draft", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/draft", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "draft" + ] + } + }, + "response": [] + }, + { + "name": "Read student submissions", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/all", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "all" + ] + } + }, + "response": [] + }, + { + "name": "Read all grades", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/teacher/{{course_id}}/grades", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "teacher", + "{{course_id}}", + "grades" + ] + } + }, + "response": [] + }, + { + "name": "Read all grades with search", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/teacher/{{course_id}}/grades?search=Fred", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "teacher", + "{{course_id}}", + "grades" + ], + "query": [ + { + "key": "search", + "value": "Fred" + } + ] + } + }, + "response": [] + }, + { + "name": "Read all submissions by student", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "if (jsonData.submissions && jsonData.submissions.length > 0) {", + " pm.environment.set('submission_id', jsonData.submissions[jsonData.submissions.length - 1].id);", + "} else if (Array.isArray(jsonData) && jsonData.length > 0) {", + " pm.environment.set('submission_id', jsonData[jsonData.length - 1].id);", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/all/students/fp", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "all", + "students", + "fp" + ] + } + }, + "response": [] + }, + { + "name": "Read submission summaries", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/latest/students", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "latest", + "students" + ] + } + }, + "response": [] + }, + { + "name": "Post grade 0", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"grade\": 0, \"notify_student\": false}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "{{submission_id}}", + "grade" + ] + } + }, + "response": [] + }, + { + "name": "Post grade 100", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"grade\": 100, \"notify_student\": false}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "{{submission_id}}", + "grade" + ] + } + }, + "response": [] + }, + { + "name": "Post grade fail -1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () { pm.response.to.have.status(400); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"grade\": -1, \"notify_student\": false}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "{{submission_id}}", + "grade" + ] + } + }, + "response": [] + }, + { + "name": "Post grade fail 101", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () { pm.response.to.have.status(400); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"grade\": 101, \"notify_student\": false}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/grade", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "{{submission_id}}", + "grade" + ] + } + }, + "response": [] + }, + { + "name": "Post feedback", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"feedback_md\": \"Good work!\", \"notify_student\": false}" + }, + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions/{{submission_id}}/feedback", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions", + "{{submission_id}}", + "feedback" + ] + } + }, + "response": [] + }, + { + "name": "Create notification", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\": \"Test notification\"}" + }, + "url": { + "raw": "{{EMSBASE}}/management/notifications", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "notifications" + ] + } + }, + "response": [] + }, + { + "name": "Read notifications (admin)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "if (Array.isArray(jsonData) && jsonData.length > 0) {", + " pm.environment.set('notification_id', jsonData[0].id);", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/management/notifications", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "notifications" + ] + } + }, + "response": [] + }, + { + "name": "Read common notifications", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/management/common/notifications", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "common", + "notifications" + ] + } + }, + "response": [] + }, + { + "name": "Update notification", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\": \"Updated notification\"}" + }, + "url": { + "raw": "{{EMSBASE}}/management/notifications/1", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "notifications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Read notifications again", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/management/notifications", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "notifications" + ] + } + }, + "response": [] + }, + { + "name": "Delete notification", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/management/notifications/1", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "notifications", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Report client log", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"log_level\": \"WARN\", \"log_message\": \"Test log message\", \"client_id\": \"postman-test\"}" + }, + "url": { + "raw": "{{EMSBASE}}/management/log", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "management", + "log" + ] + } + }, + "response": [] + }, + { + "name": "Read statistics", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{}" + }, + "url": { + "raw": "{{EMSBASE}}/statistics/common", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "statistics", + "common" + ] + } + }, + "response": [] + }, + { + "name": "AsciiDoc preview", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"content\": \"= Hello\\n\\nWorld\"}" + }, + "url": { + "raw": "{{EMSBASE}}/preview/adoc", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "preview", + "adoc" + ] + } + }, + "response": [] + }, + { + "name": "Markdown preview", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"content\": \"# Hello\\n\\nWorld\"}" + }, + "url": { + "raw": "{{EMSBASE}}/preview/markdown", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "preview", + "markdown" + ] + } + }, + "response": [] + }, + { + "name": "Create article", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });", + "var jsonData = pm.response.json();", + "pm.environment.set('article_id', jsonData.id);" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"title\": \"Test Article\", \"text_adoc\": \"= Article\\n\\nContent\", \"public\": true}" + }, + "url": { + "raw": "{{EMSBASE}}/articles", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles" + ] + } + }, + "response": [] + }, + { + "name": "Read article (admin)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/articles/{{article_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "{{article_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Read article (student)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/articles/{{article_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "{{article_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Update article", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"title\": \"Updated Article\", \"text_adoc\": \"= Updated\\n\\nContent\", \"public\": true}" + }, + "url": { + "raw": "{{EMSBASE}}/articles/{{article_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "{{article_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Update exercise", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"title\": \"Updated exercise TEACHER\", \"grader_type\": \"TEACHER\", \"solution_file_name\": \"solution.py\", \"solution_file_type\": \"TEXT_EDITOR\"}" + }, + "url": { + "raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "exercises", + "{{exercise1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Read exercise after update", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/exercises/{{exercise1_id}}", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "exercises", + "{{exercise1_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Create article alias", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"alias\": \"testAlias\"}" + }, + "url": { + "raw": "{{EMSBASE}}/articles/{{article_id}}/aliases", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "{{article_id}}", + "aliases" + ] + } + }, + "response": [] + }, + { + "name": "Read article by alias", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/articles/testAlias", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "testAlias" + ] + } + }, + "response": [] + }, + { + "name": "Create article alias: fail duplicate", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 400', function () { pm.response.to.have.status(400); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + }, + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"alias\": \"testAlias\"}" + }, + "url": { + "raw": "{{EMSBASE}}/articles/{{article_id}}/aliases", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "{{article_id}}", + "aliases" + ] + } + }, + "response": [] + }, + { + "name": "Read article aliases", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/article-aliases", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "article-aliases" + ] + } + }, + "response": [] + }, + { + "name": "Delete article alias", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/articles/{{article_id}}/aliases/testAlias", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "articles", + "{{article_id}}", + "aliases", + "testAlias" + ] + } + }, + "response": [] + }, + { + "name": "Read file metadata", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/files/metadata", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "files", + "metadata" + ] + } + }, + "response": [] + }, + { + "name": "Get file: 404", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 404', function () { pm.response.to.have.status(404); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/resource/nonexistent-file-id", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "resource", + "nonexistent-file-id" + ] + } + }, + "response": [] + }, + { + "name": "Export submissions", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"submissions\":[{\"id\":1}]}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{EMSBASE}}/export/courses/{{course_id}}/exercises/{{course_ex1_id}}/submissions", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "export", + "courses", + "{{course_id}}", + "exercises", + "{{course_ex1_id}}", + "submissions" + ] + } + }, + "response": [] + }, + { + "name": "Evict cache", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/remove-cache", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "remove-cache" + ] + } + }, + "response": [] + }, + { + "name": "Read course groups", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/groups", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "groups" + ] + } + }, + "response": [] + }, + { + "name": "Export personal course submissions", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/courses/{{course_id}}/export/submissions", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "courses", + "{{course_id}}", + "export", + "submissions" + ] + } + }, + "response": [] + }, + { + "name": "Export personal data", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/account/export", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "account", + "export" + ] + } + }, + "response": [] + }, + { + "name": "Student access tracking", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "student", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/student/courses/{{course_id}}/access", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "student", + "courses", + "{{course_id}}", + "access" + ] + } + }, + "response": [] + }, + { + "name": "Teacher access tracking", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "pf", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "bar@foo.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Pera", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Fansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "teacher", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/teacher/courses/{{course_id}}/access", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "teacher", + "courses", + "{{course_id}}", + "access" + ] + } + }, + "response": [] + }, + { + "name": "Read system properties", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () { pm.response.to.have.status(200); });" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "oidc_claim_preferred_username", + "value": "fp", + "type": "text" + }, + { + "key": "oidc_claim_email", + "value": "foo@bar.com", + "type": "text" + }, + { + "key": "oidc_claim_given_name", + "value": "Fred", + "type": "text" + }, + { + "key": "oidc_claim_family_name", + "value": "Pansen", + "type": "text" + }, + { + "key": "oidc_claim_easy_role", + "value": "admin", + "type": "text" + } + ], + "url": { + "raw": "{{EMSBASE}}/system/properties", + "host": [ + "{{EMSBASE}}" + ], + "path": [ + "system", + "properties" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file