Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ object RoutePaths {
const val ES_ADMIN_DELETE_INDICES = "$ES_ADMIN/delete-indices"
const val ES_ADMIN_REINDEX = "$ES_ADMIN/reindex"

const val METADATA_PATH = "$API/metadata"
const val JOB_CODE_PATH = "$METADATA_PATH/jobcodes"
const val JOB_CODE_CREATE = JOB_CODE_PATH
const val JOB_CODE_LIST = JOB_CODE_PATH
const val JOB_CODE_DETAIL = "$JOB_CODE_PATH/{id}"
const val JOB_CODE_UPDATE = "$JOB_CODE_DETAIL/update"
const val JOB_CODE_REMOVE = "$JOB_CODE_DETAIL/remove"

const val NAMED_REFERENCES_PATH = "$METADATA_PATH/named-references"
const val NAMED_REFERENCES_CREATE = NAMED_REFERENCES_PATH
const val NAMED_REFERENCES_LIST = NAMED_REFERENCES_PATH
const val NAMED_REFERENCES_DETAIL = "$NAMED_REFERENCES_PATH/{id}"
const val NAMED_REFERENCES_UPDATE = "$NAMED_REFERENCES_DETAIL/update"
const val NAMED_REFERENCES_REMOVE = "$NAMED_REFERENCES_DETAIL/remove"

object QueryParams {
const val FROM = "from"
const val SIZE = "size"
Expand Down
20 changes: 20 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/api/model/JobCodeUpdate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package edu.wgu.osmt.api.model

import com.fasterxml.jackson.annotation.JsonProperty
import edu.wgu.osmt.db.JobCodeLevel

data class JobCodeUpdate(
@JsonProperty("code")
val code: String,
@JsonProperty("targetNode")
val targetNode: String? = null,
@JsonProperty("targetNodeName")
val targetNodeName: String? = null,
@JsonProperty("frameworkName")
val framework: String? = null,
@JsonProperty("level")
val level: JobCodeLevel? = null,
@JsonProperty("parents")
val parents: List<JobCodeUpdate>? = null
) {
}
82 changes: 82 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/jobcode/JobCodeController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package edu.wgu.osmt.jobcode;

import edu.wgu.osmt.RoutePaths
import edu.wgu.osmt.api.model.ApiJobCode
import edu.wgu.osmt.api.model.JobCodeUpdate
import edu.wgu.osmt.elasticsearch.OffsetPageable
import edu.wgu.osmt.task.TaskResult
import edu.wgu.osmt.task.TaskStatus
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.DeleteMapping
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.RequestParam
import org.springframework.web.server.ResponseStatusException

@Controller
@Transactional
class JobCodeController @Autowired constructor(
val jobCodeEsRepo: JobCodeEsRepo,
val jobCodeRepository: JobCodeRepository
) {

@GetMapping(RoutePaths.JOB_CODE_LIST, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("isAuthenticated()")
fun allPaginated(
@RequestParam(required = true) size: Int,
@RequestParam(required = true) from: Int,
@RequestParam(required = false) sort: String?
): HttpEntity<List<ApiJobCode>> {
val searchResults = jobCodeEsRepo.typeAheadSearch("", OffsetPageable(from, size, null))
return ResponseEntity.status(200).body(searchResults.map { ApiJobCode.fromJobCode(it.content) }.toList())
}

@GetMapping(RoutePaths.JOB_CODE_DETAIL, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("isAuthenticated()")
fun byId(
@PathVariable id: Long,
): HttpEntity<ApiJobCode> {
val jobCode = jobCodeRepository.findById(id)
if (jobCode != null) {
return ResponseEntity.status(200).body(ApiJobCode.fromJobCode(jobCode.toModel()))
} else {
throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
}

@PostMapping(RoutePaths.JOB_CODE_CREATE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("hasAuthority(@appConfig.roleAdmin)")
fun createJobCode(
@RequestBody jobCodes: List<JobCodeUpdate>
): HttpEntity<List<ApiJobCode>> {
val newJobCodes = jobCodeRepository.createFromApi(jobCodes)
return ResponseEntity.status(200).body(newJobCodes.map { ApiJobCode.fromJobCode(it.toModel()) }.toList())
}

@PostMapping(RoutePaths.JOB_CODE_UPDATE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("hasAuthority(@appConfig.roleAdmin)")
fun updateJobCode(
@PathVariable id: Int,
@RequestBody jobCodeUpdate: JobCodeUpdate
): HttpEntity<ApiJobCode> {
return ResponseEntity.status(200).body(ApiJobCode(code = "1", targetNode = "target", targetNodeName = "targetNodeName", frameworkName = "frameworkName", parents = listOf()))
}

@DeleteMapping(RoutePaths.JOB_CODE_REMOVE)
@PreAuthorize("hasAuthority(@appConfig.roleAdmin)")
fun deleteJobCode(
@PathVariable id: Int,
): HttpEntity<TaskResult> {
return ResponseEntity.status(200).body(TaskResult(uuid = "uuid", contentType = "application/json", status = TaskStatus.Processing, apiResultPath = "path"))
}

}
11 changes: 11 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/jobcode/JobCodeEsRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ interface CustomJobCodeRepository {
val elasticSearchTemplate: ElasticsearchRestTemplate
fun typeAheadSearch(query: String): SearchHits<JobCode>

fun typeAheadSearch(query: String, pageable: OffsetPageable): SearchHits<JobCode>

fun deleteIndex() {
elasticSearchTemplate.indexOps(IndexCoordinates.of(INDEX_JOBCODE_DOC)).delete()
}
Expand All @@ -28,6 +30,15 @@ interface CustomJobCodeRepository {
class CustomJobCodeRepositoryImpl @Autowired constructor(override val elasticSearchTemplate: ElasticsearchRestTemplate) :
CustomJobCodeRepository {

override fun typeAheadSearch(query: String, pageable: OffsetPageable): SearchHits<JobCode> {
val nsq: NativeSearchQueryBuilder
val disjunctionQuery = JobCodeQueries.multiPropertySearch(query)
nsq =
NativeSearchQueryBuilder().withPageable(pageable).withQuery(disjunctionQuery)
.withSort(SortBuilders.fieldSort("${JobCode::code.name}.keyword").order(SortOrder.ASC))
return elasticSearchTemplate.search(nsq.build(), JobCode::class.java)
}

override fun typeAheadSearch(query: String): SearchHits<JobCode> {
val nsq: NativeSearchQueryBuilder

Expand Down
15 changes: 15 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/jobcode/JobCodeRepository.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.wgu.osmt.jobcode

import edu.wgu.osmt.api.model.JobCodeUpdate
import org.jetbrains.exposed.sql.SizedIterable
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.and
Expand All @@ -19,6 +20,7 @@ interface JobCodeRepository {
fun findByCodeOrCreate(code: String, framework: String? = null): JobCodeDao
fun findBlsCode(code: String): JobCodeDao?
fun create(code: String, framework: String? = null): JobCodeDao
fun createFromApi(jobCodes: List<JobCodeUpdate>): List<JobCodeDao>
fun onetsByDetailCode(detailedCode: String): SizedIterable<JobCodeDao>

companion object {
Expand Down Expand Up @@ -52,6 +54,19 @@ class JobCodeRepositoryImpl: JobCodeRepository {
.firstOrNull()?.let { dao.wrapRow(it) }
}

override fun createFromApi(jobCodes: List<JobCodeUpdate>): List<JobCodeDao> {
return jobCodes.map { jobCodeUpdate ->
dao.new {
this.code = jobCodeUpdate.code
this.framework = jobCodeUpdate.framework
this.name = jobCodeUpdate.targetNodeName
this.creationDate = LocalDateTime.now(ZoneOffset.UTC)
this.name = "my name"
this.major = "my major"
}.also { jobCodeEsRepo.save(it.toModel()) }
}
}

override fun findByCodeOrCreate(code: String, framework: String?): JobCodeDao {
val existing = findByCode(code)
return existing ?: create(code, framework)
Expand Down
31 changes: 31 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/keyword/ApiKeyword.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package edu.wgu.osmt.keyword

import com.fasterxml.jackson.annotation.JsonProperty

data class ApiKeywordUpdate(
@JsonProperty("name")
val name: String?,
@JsonProperty("value")
val value: String?,
@JsonProperty("type")
val type: KeywordTypeEnum,
@JsonProperty("framework")
val framework: String?
) {
}

data class NamedReference(
val id: Long?,
val name: String?,
val value: String?,
val type: KeywordTypeEnum,
val framework: String?
) {

companion object factory {
fun fromKeyword(keyword: Keyword): NamedReference {
return NamedReference(keyword.id, keyword.value, keyword.value, keyword.type, keyword.framework)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package edu.wgu.osmt.keyword

import edu.wgu.osmt.PaginationDefaults
import edu.wgu.osmt.RoutePaths
import edu.wgu.osmt.task.TaskResult
import edu.wgu.osmt.task.TaskStatus
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.DeleteMapping
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.RequestParam
import org.springframework.web.server.ResponseStatusException

@Controller
@Transactional
class NamedReferencesController @Autowired constructor(
val keywordEsRepo: KeywordEsRepo,
val keywordRepository: KeywordRepository
) {

@GetMapping(RoutePaths.NAMED_REFERENCES_LIST, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("isAuthenticated()")
fun allPaginated(
@RequestParam(required = true) type: String,
@RequestParam(required = false, defaultValue = PaginationDefaults.size.toString()) size: Int,
@RequestParam(required = false, defaultValue = "0") from: Int,
@RequestParam(required = false) sort: String?
): HttpEntity<List<NamedReference>> {
val keywordType = KeywordTypeEnum.forApiValue(type) ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST)
val searchResults = keywordEsRepo.typeAheadSearch("", keywordType)
return ResponseEntity.status(200).body(searchResults.map { NamedReference.fromKeyword(it.content) }.toList())
}

@GetMapping(RoutePaths.NAMED_REFERENCES_DETAIL, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("isAuthenticated()")
fun byId(
@PathVariable id: Long,
): HttpEntity<NamedReference> {
val keyword = keywordRepository.findById(id)
if (keyword != null) {
return ResponseEntity.status(200).body(NamedReference.fromKeyword(keyword.toModel()))
} else {
throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
}

@PostMapping(RoutePaths.NAMED_REFERENCES_CREATE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("hasAuthority(@appConfig.roleAdmin)")
fun createNamedReference(
@RequestBody keywords: List<ApiKeywordUpdate>
): HttpEntity<List<NamedReference>> {
return ResponseEntity.status(200).body(
keywords.map {
NamedReference(id = 1, name = it.name, value = it.value, type = it.type, framework = it.framework)
}
)
}

@PostMapping(RoutePaths.NAMED_REFERENCES_UPDATE, produces = [MediaType.APPLICATION_JSON_VALUE])
@PreAuthorize("hasAuthority(@appConfig.roleAdmin)")
fun updateNamedReference(
@PathVariable id: Int,
@RequestBody apiKeyword: ApiKeywordUpdate
): HttpEntity<NamedReference> {
return ResponseEntity.status(200).body(NamedReference(134, "my name", "my value", KeywordTypeEnum.Keyword, "my framework"))
}

@DeleteMapping(RoutePaths.NAMED_REFERENCES_REMOVE)
@PreAuthorize("hasAuthority(@appConfig.roleAdmin)")
fun deleteNamedReference(
@PathVariable id: Int,
): HttpEntity<TaskResult> {
return ResponseEntity.status(200).body(TaskResult(uuid = "uuid", contentType = "application/json", status = TaskStatus.Processing, apiResultPath = "path"))
}

}
2 changes: 2 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/security/SecurityConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.http.HttpMethod.*
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
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
Expand All @@ -58,6 +59,7 @@ import javax.servlet.http.HttpServletResponse
@Configuration
@EnableWebSecurity
@Profile("oauth2-okta | OTHER-OAUTH-PROFILE")
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig : WebSecurityConfigurerAdapter() {

@Autowired
Expand Down
Loading