Skip to content

Commit

Permalink
Tacs 14 ddd domain migration (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasanchez authored Apr 21, 2023
2 parents 289cc91 + 01adf28 commit 052f497
Show file tree
Hide file tree
Showing 7 changed files with 529 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.schedutn.scheduler.domain

class IllegalVoteException(message: String) : Exception(message)

class IllegalScheduleException(message: String) : Exception(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.schedutn.scheduler.domain.models

import jakarta.validation.constraints.NotBlank

/**
* Meeting.
*
* @property title name of the meeting
* @property description brief description of the meeting
* @property location where this meeting will be held
*/
data class Meeting(

@field:NotBlank(message = "Schedule title must not be blank")
val title: String,

val description: String?,

val location: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.schedutn.scheduler.domain.models

import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min
import java.time.LocalDate
import java.time.LocalDateTime

/**
* Event option is a possible date for a Schedule to be held.
*
* @property date tentative date for the event
* @property hour tentative hour for the event
* @property minute tentative minute for the event
*/
data class MeetingOption(

val date: LocalDate,

@field:Max(value = 23, message = "Hour must be less than 24")
@field:Min(value = 0, message = "Hour must be greater or equal to 0")
val hour: Int = 0,

@field:Max(value = 59, message = "Minute must be less than 60")
@field:Min(value = 0, message = "Minute must be greater or equal to 0")
val minute: Int = 0,

val votes: Set<String> = setOf(),
) {

/**
* Adds or removes a vote from the option.
*
* @param username username of the user that voted
* @return a new instance of [MeetingOption] with the vote added or removed
*/
fun vote(username: String): MeetingOption {

val newVotes = votes.toMutableSet()

if (newVotes.contains(username)) {
newVotes.remove(username)
} else {
newVotes.add(username)
}

return copy(votes = newVotes)
}

/**
* Obtains the date time of the option.
*
* @return date time of the option
*/
fun dateTime(): LocalDateTime = LocalDateTime.of(date, java.time.LocalTime.of(hour, minute))

override fun equals(other: Any?): Boolean {
if (other == null) return false

if (other !is MeetingOption) return false

return date == other.date && hour == other.hour && minute == other.minute
}

override fun hashCode(): Int {
var result = date.hashCode()
result = 31 * result + hour
result = 31 * result + minute
return result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.schedutn.scheduler.domain.models;


import java.io.Serializable;

/**
* Base class for all entities.
*/
interface PersistentEntity extends Serializable {

long serialVersionUID = 1L;

/**
* Obtains an unique identifier for the entity.
*
* @return the id of the entity.
*/
String getIdentifier();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.schedutn.scheduler.domain.models

import com.schedutn.scheduler.domain.IllegalScheduleException
import com.schedutn.scheduler.domain.IllegalVoteException
import jakarta.validation.Valid
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotEmpty
import java.time.LocalDateTime

/**
* Schedule
*
* Aggregate all the information about a Schedule.
*
* @property version version of the schedule
* @property organizer username of the organizer
* @property voting whether voting is enabled or not
* @property event meeting information
* @property guests list of guests
* @property options list of options
* @property date voted date of the event
*/
data class Schedule(

val id: String? = null,

@field:Min(value = 0, message = "Version must be greater or equal to 0")
val version: Int = 0,

@field:NotBlank(message = "Organizer username must not be blank")
val organizer: String,

val voting: Boolean = false,

@field:Valid
val event: Meeting,

val guests: Set<String> = setOf(),

@field:NotEmpty
@field:Valid
val options: Set<MeetingOption>,

val date: LocalDateTime? = null,
) : PersistentEntity {

/**
* Votes or revokes a vote for an option.
*
* @param username username of the guest
* @param option option to vote
* @return new schedule with the option voted
* @throws IllegalVoteException if voting is not enabled, user is not a guest or option is not valid
*/
fun vote(username: String, option: MeetingOption): Schedule {

if (!voting)
throw IllegalVoteException("Voting is not enabled")

if (!guests.contains(username) && username != organizer)
throw IllegalVoteException("User is not a guest")

if (!options.contains(option))
throw IllegalVoteException("Option is not valid")


return copy(options = options.map { if (it == option) it.vote(username) else it }.toSet(),
version = version + 1
)
}

/**
* Returns the option with the most votes.
*
* @return option with the most votes
*/
private fun getMostVotedOption(): MeetingOption =
options.maxByOrNull { it.votes.size } ?: options.first()

/**
* Sets the date of the event to the most voted option.
*
* @param username user who wants to set the date
* @return new schedule with the date set
*/
fun schedule(username: String): Schedule {

if (username != organizer)
throw IllegalScheduleException("Only the organizer can schedule the event")

return copy(
date = getMostVotedOption().dateTime(),
voting = false,
version = version + 1
)
}

/**
* Enables or disables voting.
*
* @param username user who wants to enable or disable voting
* @param enabledVotes whether voting is enabled or not
* @return new schedule with the new voting policy
*/
fun toggleVoting(username: String, enabledVotes: Boolean): Schedule {

if (username != organizer)
throw IllegalScheduleException("Only the organizer can enable or disable voting")

return copy(
voting = enabledVotes,
version = version + 1
)
}

override fun getIdentifier(): String? = id

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.schedutn.scheduler.domain.models

import org.junit.jupiter.api.Test
import java.time.LocalDate
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue

internal class MeetingOptionTest {


@Test
fun `options with same date, hour and minute are equals `() {

val option1 = MeetingOption(date = LocalDate.now(), hour = 0, minute = 0, votes = setOf("user1"))
val option2 = MeetingOption(date = LocalDate.now(), hour = 0, minute = 0, votes = setOf("user2"))

assertEquals(option1, option2)
}

@Test
fun `options with different date, hour and minute are not equals `() {

val option1 = MeetingOption(
date = LocalDate.now(),
hour = 0,
minute = 0,
votes = setOf("user1"))

val option2 = MeetingOption(
date = LocalDate.now().plusDays(1),
hour = 0,
minute = 0,
votes = setOf("user1")
)

assertNotEquals(option1, option2)
}

@Test
fun `an option can be voted`() {
// Given
val username = "user1"
val option = MeetingOption(date = LocalDate.now(), hour = 0, minute = 0)

// When
val votedOption = option.vote(username)

// Then
assertEquals(setOf(username), votedOption.votes)
}

@Test
fun `an option can have voting revoked`() {
// Given
val username = "user1"
val option = MeetingOption(date = LocalDate.now(), hour = 0, minute = 0, votes = setOf(username))

// When
val votedOption = option.vote(username)

// Then
assertTrue { votedOption.votes.isEmpty() }
}

}
Loading

0 comments on commit 052f497

Please sign in to comment.