-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tacs 19 scheduler entry points (#19)
- Loading branch information
Showing
23 changed files
with
1,215 additions
and
1 deletion.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions
42
scheduler/src/main/java/com/schedutn/scheduler/adapters/ScheduleRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.schedutn.scheduler.adapters | ||
|
||
import com.schedutn.scheduler.domain.models.Schedule | ||
import java.util.* | ||
|
||
class ScheduleRepository( | ||
private val db: MutableMap<String, Schedule> = mutableMapOf() | ||
) { | ||
|
||
fun save(schedule: Schedule): Schedule { | ||
|
||
val id: String = schedule.id ?: UUID.randomUUID().toString() | ||
|
||
val saved = schedule.copy(id = id, version = schedule.version + 1) | ||
|
||
if (schedule.id == null) { | ||
db[id] = saved | ||
return saved | ||
} | ||
|
||
val current = db[id]!! | ||
|
||
if (schedule.version >= current.version) { | ||
db[id] = saved | ||
return saved | ||
} | ||
|
||
throw IllegalStateException("Schedule with id $id is not up to date") | ||
} | ||
|
||
fun findById(id: String): Schedule? { | ||
return db[id] | ||
} | ||
|
||
fun findAll(): Collection<Schedule> { | ||
return db.values | ||
} | ||
|
||
fun deleteById(id: String) { | ||
db.remove(id) | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
scheduler/src/main/java/com/schedutn/scheduler/api/DataWrapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.schedutn.scheduler.api | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude | ||
import com.fasterxml.jackson.annotation.JsonProperty | ||
import io.swagger.v3.oas.annotations.media.Schema | ||
import org.springframework.validation.annotation.Validated | ||
|
||
@Schema(description = "Data wrapper") | ||
@JsonInclude(JsonInclude.Include.NON_NULL) | ||
class DataWrapper<T>( | ||
|
||
@Schema(description = "Response status", example = "success") | ||
@JsonProperty("status") | ||
val status: String? = "success", | ||
|
||
@Schema(description = "Message of the response", example = "OK") | ||
@JsonProperty("message") | ||
val message: String? = null, | ||
|
||
@Schema(description = "Data of the response") | ||
@JsonProperty("data") | ||
@Validated | ||
val data: T? = null, | ||
|
||
@Schema(description = "Metadata of the response", example = "{ \"total\": 1 }") | ||
@JsonProperty("meta") | ||
val meta: Map<String, Any>? = null, | ||
) |
84 changes: 84 additions & 0 deletions
84
scheduler/src/main/java/com/schedutn/scheduler/api/GlobalControllerExceptionHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.schedutn.scheduler.api | ||
|
||
import com.schedutn.scheduler.api.errors.InvalidSchedule | ||
import com.schedutn.scheduler.api.errors.UnAuthorizedScheduleOperation | ||
import com.schedutn.scheduler.service.ScheduleAuthorizationException | ||
import com.schedutn.scheduler.service.ScheduleNotFoundException | ||
import org.springframework.http.HttpHeaders | ||
import org.springframework.http.HttpStatus | ||
import org.springframework.http.HttpStatusCode | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.validation.FieldError | ||
import org.springframework.validation.ObjectError | ||
import org.springframework.web.bind.MethodArgumentNotValidException | ||
import org.springframework.web.bind.annotation.ExceptionHandler | ||
import org.springframework.web.bind.annotation.ResponseStatus | ||
import org.springframework.web.bind.annotation.RestControllerAdvice | ||
import org.springframework.web.context.request.WebRequest | ||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler | ||
|
||
@RestControllerAdvice | ||
class GlobalControllerExceptionHandler : ResponseEntityExceptionHandler() { | ||
|
||
companion object { | ||
|
||
private val log = org.slf4j.LoggerFactory.getLogger( | ||
GlobalControllerExceptionHandler::class.java) | ||
} | ||
|
||
@ResponseStatus(HttpStatus.NOT_FOUND) | ||
@ExceptionHandler(ScheduleNotFoundException::class) | ||
fun handleScheduleNotFound( | ||
ex: ScheduleNotFoundException): ResponseEntity<InvalidSchedule> { | ||
|
||
val details = InvalidSchedule( | ||
code = "404", | ||
message = ex.message ?: "Not found" | ||
) | ||
|
||
log.error("404: $details") | ||
return ResponseEntity(details, HttpStatus.NOT_FOUND) | ||
} | ||
|
||
@ResponseStatus(HttpStatus.FORBIDDEN) | ||
@ExceptionHandler(ScheduleAuthorizationException::class) | ||
fun handleScheduleAuthorization( | ||
ex: ScheduleAuthorizationException): ResponseEntity<UnAuthorizedScheduleOperation> { | ||
|
||
val bodyOfResponse = UnAuthorizedScheduleOperation( | ||
code = "403", | ||
message = ex.message ?: "Forbidden" | ||
) | ||
|
||
return ResponseEntity(bodyOfResponse, HttpStatus.FORBIDDEN) | ||
} | ||
|
||
/** | ||
* Handles Unprocessable Entity Exceptions. | ||
* | ||
* @param ex The runtime exception | ||
* @return a response entity with the occurred errors and an unprocessable entity status | ||
*/ | ||
override fun handleMethodArgumentNotValid(ex: MethodArgumentNotValidException, | ||
headers: HttpHeaders, | ||
status: HttpStatusCode, | ||
request: WebRequest): ResponseEntity<Any> { | ||
|
||
val errors: MutableMap<String, String?> = HashMap() | ||
|
||
ex.bindingResult.allErrors.forEach { error: ObjectError -> | ||
val fieldName = (error as FieldError).field | ||
val message = error.getDefaultMessage() | ||
errors[fieldName] = message | ||
} | ||
|
||
val bodyOfResponse = mapOf<String, Any?>( | ||
"message" to "Cannot process Entity, please check the errors.", | ||
"detail" to errors | ||
) | ||
|
||
log.error("Cannot process Entity, please check the errors.") | ||
|
||
return ResponseEntity(bodyOfResponse, HttpStatus.UNPROCESSABLE_ENTITY) | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
scheduler/src/main/java/com/schedutn/scheduler/api/errors/InvalidSchedule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.schedutn.scheduler.api.errors | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty | ||
import io.swagger.v3.oas.annotations.media.Schema | ||
|
||
@Schema(description = "Error response") | ||
data class InvalidSchedule( | ||
|
||
@Schema(description = "Error code", example = "404") | ||
@JsonProperty("code") | ||
val code: String, | ||
|
||
@Schema(description = "Error message", example = "Not found") | ||
@JsonProperty("message") | ||
val message: String, | ||
) |
14 changes: 14 additions & 0 deletions
14
scheduler/src/main/java/com/schedutn/scheduler/api/errors/UnAuthorizedScheduleOperation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.schedutn.scheduler.api.errors | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema | ||
|
||
@Schema(description = "Error response") | ||
data class UnAuthorizedScheduleOperation( | ||
|
||
@Schema(description = "Error code", example = "403") | ||
val code: String, | ||
|
||
@Schema(description = "Error message", | ||
example = "User has no permission to perform this operation") | ||
val message: String, | ||
) |
139 changes: 139 additions & 0 deletions
139
scheduler/src/main/java/com/schedutn/scheduler/api/v1/SchedulesEntryPoint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package com.schedutn.scheduler.api.v1 | ||
|
||
|
||
import com.schedutn.scheduler.api.DataWrapper | ||
import com.schedutn.scheduler.api.v1.SchedulesEntryPoint.Companion.SCHEDULES_ENTRY_POINT_URL | ||
import com.schedutn.scheduler.domain.commands.ScheduleMeeting | ||
import com.schedutn.scheduler.domain.commands.ToggleVoting | ||
import com.schedutn.scheduler.domain.commands.VoteForOption | ||
import com.schedutn.scheduler.domain.events.MeetingScheduled | ||
import com.schedutn.scheduler.service.ScheduleService | ||
import io.swagger.v3.oas.annotations.Operation | ||
import io.swagger.v3.oas.annotations.tags.Tag | ||
import jakarta.validation.Valid | ||
import org.springframework.beans.factory.annotation.Autowired | ||
import org.springframework.http.MediaType | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.security.core.context.SecurityContextHolder | ||
import org.springframework.web.bind.annotation.* | ||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder | ||
import java.net.URI | ||
|
||
@RestController | ||
@RequestMapping(SCHEDULES_ENTRY_POINT_URL, produces = [MediaType.APPLICATION_JSON_VALUE]) | ||
@Tag(name = "Schedules", description = "Schedules Entry Point") | ||
class SchedulesEntryPoint { | ||
|
||
@Autowired | ||
lateinit var service: ScheduleService | ||
|
||
companion object { | ||
|
||
const val SCHEDULES_ENTRY_POINT_URL = "/api/v1/schedules" | ||
private val log = org.slf4j.LoggerFactory.getLogger(SchedulesEntryPoint::class.java) | ||
} | ||
|
||
@GetMapping | ||
@ResponseStatus(org.springframework.http.HttpStatus.OK) | ||
@Operation( | ||
summary = "Query Schedules", | ||
description = "Retrieves available schedules", | ||
tags = ["Queries"] | ||
) | ||
fun querySchedules(): DataWrapper<Collection<MeetingScheduled>> { | ||
log.info("Querying schedules") | ||
return DataWrapper(data = service.findAll()) | ||
} | ||
|
||
@GetMapping("/{id}") | ||
@ResponseStatus(org.springframework.http.HttpStatus.OK) | ||
@Operation( | ||
summary = "Query Schedule", | ||
description = "Retrieves a schedule by id", | ||
tags = ["Queries"] | ||
) | ||
fun querySchedule(@PathVariable id: String): DataWrapper<MeetingScheduled> { | ||
log.info("Querying schedule with id: $id") | ||
|
||
val schedule = service.scheduleById(id) | ||
|
||
return DataWrapper(data = schedule) | ||
} | ||
|
||
@PostMapping | ||
@ResponseStatus(org.springframework.http.HttpStatus.CREATED) | ||
@Operation( | ||
summary = "Commands to Schedule a Meeting", | ||
description = "Creates a new meeting proposal", | ||
tags = ["Commands"] | ||
) | ||
fun scheduleMeeting( | ||
@Valid @RequestBody command: ScheduleMeeting): ResponseEntity<DataWrapper<MeetingScheduled>> { | ||
log.info("Scheduling meeting: $command") | ||
|
||
val scheduled = service.scheduleMeeting(command) | ||
|
||
val uri = URI.create( | ||
ServletUriComponentsBuilder | ||
.fromCurrentContextPath() | ||
.path("$SCHEDULES_ENTRY_POINT_URL/{id}") | ||
.buildAndExpand(scheduled.id) | ||
.toUriString() | ||
) | ||
|
||
return ResponseEntity.created(uri) | ||
.body(DataWrapper(data = scheduled)) | ||
} | ||
|
||
@PatchMapping("/{id}/voting") | ||
@ResponseStatus(org.springframework.http.HttpStatus.OK) | ||
@Operation( | ||
summary = "Commands to Toggle Voting", | ||
description = "Enables or disables voting for a schedule", | ||
tags = ["Commands"] | ||
) | ||
fun toggleVoting(@PathVariable id: String, | ||
@Valid @RequestBody command: ToggleVoting | ||
): DataWrapper<MeetingScheduled> { | ||
log.info("Toggling voting for schedule with id: $id") | ||
|
||
val schedule = service.toggleVoting(id, command) | ||
|
||
return DataWrapper(data = schedule) | ||
} | ||
|
||
@PatchMapping("/{id}/options") | ||
@ResponseStatus(org.springframework.http.HttpStatus.OK) | ||
@Operation( | ||
summary = "Commands to Vote for an Option", | ||
description = "Adds or Revokes a vote for an option", | ||
tags = ["Commands"] | ||
) | ||
fun voteForOption(@PathVariable id: String, | ||
@Valid @RequestBody command: VoteForOption | ||
): DataWrapper<MeetingScheduled> { | ||
log.info("Voting for option for schedule with id: $id") | ||
|
||
val schedule = service.voteForAnOption(id = id, command = command) | ||
return DataWrapper(data = schedule) | ||
} | ||
|
||
@PostMapping("/{id}/relationships/guests") | ||
@ResponseStatus(org.springframework.http.HttpStatus.OK) | ||
@Operation( | ||
summary = "Commands to Join a Meeting", | ||
description = "Adds a guest to a meeting", | ||
tags = ["Commands"] | ||
) | ||
fun joinMeeting(@PathVariable id: String): DataWrapper<MeetingScheduled> { | ||
log.info("Joining meeting for schedule with id: $id") | ||
|
||
val auth = SecurityContextHolder.getContext().authentication.principal.toString() | ||
|
||
val joined = service.joinAMeeting(id = id, username = auth) | ||
|
||
log.info("$auth joined meeting for schedule with id: $id") | ||
|
||
return DataWrapper(data = joined) | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
scheduler/src/main/java/com/schedutn/scheduler/domain/Message.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.schedutn.scheduler.domain | ||
|
||
import java.io.Serializable | ||
|
||
/** | ||
* Message is an object that carries information from one part of a program to another. | ||
* | ||
* It is a means of communication between different components of a system, and it can be used to | ||
* trigger actions or update the state of the system. | ||
* | ||
* They can be passed synchronously or asynchronously and may be sent between different threads, | ||
* processes, or even across different systems. | ||
* | ||
* By using messages to communicate between components, the system becomes more modular and easier | ||
* to maintain, as each component only needs to understand the message format and not the | ||
* implementation details of other components. | ||
*/ | ||
interface Message : Serializable |
16 changes: 16 additions & 0 deletions
16
scheduler/src/main/java/com/schedutn/scheduler/domain/commands/Command.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.schedutn.scheduler.domain.commands | ||
|
||
import com.schedutn.scheduler.domain.Message | ||
|
||
|
||
/** | ||
* A command represents an intent to change the state of the system, it is a message that requests | ||
* some action to be taken. | ||
* | ||
* Commands are passed to command handlers, which interpret them and execute | ||
* the corresponding actions to produce new events that update the system state. | ||
* | ||
* Commands should be immutable, and their properties should be as minimal as possible. | ||
* | ||
*/ | ||
interface Command : Message |
27 changes: 27 additions & 0 deletions
27
scheduler/src/main/java/com/schedutn/scheduler/domain/commands/ProposeOption.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.schedutn.scheduler.domain.commands | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty | ||
import io.swagger.v3.oas.annotations.media.Schema | ||
import jakarta.validation.constraints.Max | ||
import jakarta.validation.constraints.Min | ||
import java.time.LocalDate | ||
|
||
@Schema(description = "Command to propose a meeting option") | ||
data class ProposeOption( | ||
|
||
@Schema(description = "Proposed Date", required = true) | ||
@JsonProperty("date") | ||
val date: LocalDate, | ||
|
||
@Schema(description = "Proposed Hour", required = true) | ||
@JsonProperty("hour") | ||
@field:Max(value = 23, message = "Hour must be between 0 and 23") | ||
@field:Min(value = 0, message = "Hour must be between 0 and 23") | ||
val hour: Int, | ||
|
||
@Schema(description = "Proposed Minute", required = true) | ||
@JsonProperty("minute") | ||
@field:Max(value = 59, message = "Minute must be between 0 and 59") | ||
@field:Min(value = 0, message = "Minute must be between 0 and 59") | ||
val minute: Int, | ||
) : Command |
Oops, something went wrong.