diff --git a/api/pom.xml b/api/pom.xml
index 5a40bddc7..ddd3ad093 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -298,7 +298,7 @@
io.mockk
mockk
- 1.12.4
+ 1.11.0
test
diff --git a/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt b/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
index 28e5acadd..240d6f4af 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
@@ -1,8 +1,10 @@
package edu.wgu.osmt
object RoutePaths {
- const val SEARCH_PATH = "/api/search"
+ const val API = "/api"
+ const val SEARCH_PATH = "$API/search"
const val SEARCH_SKILLS = "$SEARCH_PATH/skills"
+ const val EXPORT_LIBRARY = "$API/export/library"
const val SEARCH_SIMILAR_SKILLS = "$SEARCH_SKILLS/similarity"
const val SEARCH_SIMILARITIES = "$SEARCH_SKILLS/similarities"
const val SEARCH_COLLECTIONS = "$SEARCH_PATH/collections"
diff --git a/api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt b/api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt
index 4b2af8958..b87ca3fa0 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt
@@ -5,6 +5,7 @@ import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.richskill.RichSkillAndCollections
import edu.wgu.osmt.richskill.RichSkillCsvExport
import edu.wgu.osmt.richskill.RichSkillDescriptorDao
+import edu.wgu.osmt.richskill.RichSkillRepository
import edu.wgu.osmt.task.CsvTask
import edu.wgu.osmt.task.TaskMessageService
import edu.wgu.osmt.task.TaskStatus
@@ -28,6 +29,9 @@ class CsvTaskProcessor {
@Autowired
lateinit var collectionRepository: CollectionRepository
+ @Autowired
+ lateinit var richSkillRepository: RichSkillRepository
+
@Autowired
lateinit var appConfig: AppConfig
@@ -52,4 +56,24 @@ class CsvTaskProcessor {
logger.info("Task ${csvTask.uuid} completed")
}
+ @RqueueListener(
+ value = [TaskMessageService.skillsForFullLibraryCsv],
+ deadLetterQueueListenerEnabled = "true",
+ deadLetterQueue = TaskMessageService.deadLetters,
+ concurrency = "1"
+ )
+ fun csvSkillsInFullLibraryProcessor(csvTask: CsvTask) {
+ logger.info("Started processing task for Full Library export")
+
+ val csv = richSkillRepository.findAll()
+ ?.with(RichSkillDescriptorDao::collections)
+ ?.map { RichSkillAndCollections.fromDao(it) }
+ ?.let { RichSkillCsvExport(appConfig).toCsv(it) }
+
+ taskMessageService.publishResult(
+ csvTask.copy(result = csv, status = TaskStatus.Ready)
+ )
+ logger.info("Full Library export task completed")
+ }
+
}
diff --git a/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt b/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
index 3e17f8371..5924fb362 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
@@ -14,15 +14,31 @@ import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.elasticsearch.OffsetPageable
import edu.wgu.osmt.keyword.KeywordDao
-import edu.wgu.osmt.security.*
-import edu.wgu.osmt.task.*
+import edu.wgu.osmt.security.OAuthHelper
+import edu.wgu.osmt.task.AppliesToType
+import edu.wgu.osmt.task.CreateSkillsTask
+import edu.wgu.osmt.task.CsvTask
+import edu.wgu.osmt.task.PublishTask
+import edu.wgu.osmt.task.Task
+import edu.wgu.osmt.task.TaskMessageService
+import edu.wgu.osmt.task.TaskResult
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.http.*
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.stereotype.Controller
import org.springframework.transaction.annotation.Transactional
-import org.springframework.web.bind.annotation.*
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+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.RequestParam
+import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.util.UriComponentsBuilder
@@ -176,6 +192,7 @@ class RichSkillController @Autowired constructor(
fun skillAuditLog(
@PathVariable uuid: String
): HttpEntity> {
+
val pageable = OffsetPageable(0, Int.MAX_VALUE, AuditLogSortEnum.forValueOrDefault(AuditLogSortEnum.DateDesc.apiValue).sort)
val skill = richSkillRepository.findByUUID(uuid)
@@ -183,4 +200,23 @@ class RichSkillController @Autowired constructor(
val sizedIterable = auditLogRepository.findByTableAndId(RichSkillDescriptorTable.tableName, entityId = skill!!.id.value, offsetPageable = pageable)
return ResponseEntity.status(200).body(sizedIterable.toList().map{it.toModel()})
}
+
+ @Transactional(readOnly = true)
+ @GetMapping(RoutePaths.EXPORT_LIBRARY, produces = [MediaType.APPLICATION_JSON_VALUE])
+ @ResponseBody
+ fun exportLibrary(
+ @AuthenticationPrincipal user: Jwt?
+ ): HttpEntity {
+ if (!appConfig.allowPublicSearching && user === null) {
+ throw GeneralApiException("Unauthorized", HttpStatus.UNAUTHORIZED)
+ }
+ if (!oAuthHelper.hasRole(appConfig.roleAdmin)) {
+ throw GeneralApiException("OSMT user must have an Admin role.", HttpStatus.UNAUTHORIZED)
+ }
+
+ val task = CsvTask(collectionUuid = "FullLibrary")
+ taskMessageService.enqueueJob(TaskMessageService.skillsForFullLibraryCsv, task)
+
+ return Task.processingResponse(task)
+ }
}
diff --git a/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt b/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt
index 5a4f9fc34..0dd63dc48 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt
@@ -43,5 +43,6 @@ class TaskMessageService {
const val publishSkills = "batch-publish-skills"
const val updateCollectionSkills = "update-collection-skills"
const val skillsForCollectionCsv = "collection-skills-csv-process"
+ const val skillsForFullLibraryCsv = "full-library-skills-csv-process"
}
}
diff --git a/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt b/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt
index 675374e5f..a4e2f12f7 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt
@@ -3,30 +3,60 @@ package edu.wgu.osmt.richskill
import edu.wgu.osmt.BaseDockerizedTest
import edu.wgu.osmt.HasDatabaseReset
import edu.wgu.osmt.HasElasticsearchReset
+import edu.wgu.osmt.RoutePaths.EXPORT_LIBRARY
import edu.wgu.osmt.SpringTest
import edu.wgu.osmt.api.model.ApiSearch
import edu.wgu.osmt.collection.CollectionEsRepo
+import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.csv.BatchImportRichSkill
import edu.wgu.osmt.csv.RichSkillRow
import edu.wgu.osmt.jobcode.JobCodeEsRepo
import edu.wgu.osmt.keyword.KeywordEsRepo
import edu.wgu.osmt.mockdata.MockData
+import edu.wgu.osmt.security.OAuthHelper
+import edu.wgu.osmt.task.CsvTask
+import edu.wgu.osmt.task.Task
+import edu.wgu.osmt.task.TaskMessageService
+import edu.wgu.osmt.task.TaskResult
+import edu.wgu.osmt.task.TaskStatus
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
+import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.context.SecurityContext
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.oauth2.core.user.OAuth2UserAuthority
import org.springframework.security.oauth2.jwt.Jwt
+import org.springframework.test.util.ReflectionTestUtils
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.util.UriComponentsBuilder
+import java.time.Instant
+import java.util.*
+
@Transactional
internal class RichSkillControllerTest @Autowired constructor(
override val richSkillEsRepo: RichSkillEsRepo,
+ val taskMessageService: TaskMessageService,
+ val oAuthHelper: OAuthHelper,
+ val appConfig: AppConfig,
override val collectionEsRepo: CollectionEsRepo,
override val keywordEsRepo: KeywordEsRepo,
override val jobCodeEsRepo: JobCodeEsRepo
): SpringTest(), BaseDockerizedTest, HasDatabaseReset, HasElasticsearchReset {
+ var authentication: Authentication = Mockito.mock(Authentication::class.java)
+
@Autowired
lateinit var richSkillController: RichSkillController
@@ -36,9 +66,11 @@ internal class RichSkillControllerTest @Autowired constructor(
private lateinit var mockData : MockData
val nullJwt : Jwt? = null
+
@BeforeAll
fun setup() {
mockData = MockData()
+ ReflectionTestUtils.setField(appConfig, "roleAdmin", "ROLE_Osmt_Admin");
}
@Test
@@ -149,4 +181,43 @@ internal class RichSkillControllerTest @Autowired constructor(
assertThat(result.body?.get(0)?.operationType).isEqualTo("Insert")
assertThat(result.body?.get(0)?.user).isEqualTo("Batch Import")
}
+
+
+ @Disabled
+ @Test
+ fun testExportLibrary() {
+
+ val securityContext: SecurityContext = Mockito.mock(SecurityContext::class.java)
+ SecurityContextHolder.setContext(securityContext)
+
+ val attributes: MutableMap = HashMap()
+ attributes["email"] = "j.chavez@wgu.edu"
+
+ val authority: GrantedAuthority = OAuth2UserAuthority("ROLE_Osmt_Admin", attributes)
+ val authorities: MutableSet = HashSet()
+ authorities.add(authority)
+ Mockito.`when`(securityContext.authentication).thenReturn(authentication)
+ Mockito.`when`(SecurityContextHolder.getContext().authentication.authorities).thenReturn(authorities)
+
+
+ val responseHeaders = HttpHeaders()
+ responseHeaders.add("Content-Type", MediaType.APPLICATION_JSON_VALUE)
+ val headers : MutableMap = HashMap()
+ headers["key"] = "value"
+ val notNullJwt : Jwt? = Jwt("tokenValue", Instant.MIN, Instant.MAX,headers,headers)
+ val csvTaskResult = TaskResult(UUID.randomUUID().toString(),MediaType.APPLICATION_JSON_VALUE,TaskStatus.Processing, EXPORT_LIBRARY)
+
+
+ val service = mockk()
+ every { service.enqueueJob(any(), any()) } returns Unit
+ mockkStatic(CsvTask::class)
+ mockkStatic(TaskResult::class)
+ every { Task.processingResponse(any()) } returns HttpEntity(csvTaskResult)
+
+ val result = richSkillController.exportLibrary(user = notNullJwt)
+ assertThat(result.body?.uuid).isNotBlank()
+ }
+
+
+
}
\ No newline at end of file