diff --git a/survey/build.gradle b/survey/build.gradle index e69de29b..dd3fe53d 100644 --- a/survey/build.gradle +++ b/survey/build.gradle @@ -0,0 +1,11 @@ +repositories { + mavenCentral() +} + +dependencies { + implementation project(":core:data") + + implementation "org.springframework.boot:spring-boot-starter-data-jpa" + + testRuntimeOnly "com.h2database:h2" +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Bookmark.kt b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Bookmark.kt new file mode 100644 index 00000000..10f06165 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Bookmark.kt @@ -0,0 +1,19 @@ +package me.nalab.survey.domain.feedback + +import java.time.Instant +import javax.persistence.Column +import javax.persistence.Embeddable + +@Embeddable +class Bookmark( + @Column(name = "is_bookmarked", nullable = false) + var isBookmarked: Boolean = BOOKMARK_DEFAULT_STATE, + + @Column(name = "bookmarked_at", columnDefinition = "TIMESTAMP(6)", nullable = false) + var bookmarkedAt: Instant, +) { + + companion object { + private const val BOOKMARK_DEFAULT_STATE = false + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/feedback/ChoiceFormQuestionFeedback.kt b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/ChoiceFormQuestionFeedback.kt new file mode 100644 index 00000000..aadcd8ae --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/ChoiceFormQuestionFeedback.kt @@ -0,0 +1,17 @@ +package me.nalab.survey.domain.feedback + +import javax.persistence.* + +@Entity +class ChoiceFormQuestionFeedback( + id: Long? = null, + formQuestionId: Long, + isRead: Boolean = false, + bookmark: Bookmark, + feedback: Feedback, + + @ElementCollection + @Column(name = "selects") + @CollectionTable(name = "selects", joinColumns = [JoinColumn(name = "form_feedback_id")]) + private val selectedChoiceIds: MutableSet, +) : FormQuestionFeedbackable(id, formQuestionId, isRead, bookmark, feedback) diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Feedback.kt b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Feedback.kt new file mode 100644 index 00000000..30d22538 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Feedback.kt @@ -0,0 +1,50 @@ +package me.nalab.survey.domain.feedback + +import me.nalab.core.data.common.TimeBaseEntity +import javax.persistence.* + +@Entity(name = "feedback") +@Table(name = "feedback") +class Feedback( + @Id + @Column(name = "feedback_id") + private var id: Long? = null, + + @JoinColumn(name = "survey_id", nullable = false) + private val surveyId: Long, + + @OneToMany( + mappedBy = "feedback", + fetch = FetchType.LAZY, + cascade = [CascadeType.PERSIST, CascadeType.MERGE] + ) + private val allQuestionFeedbacks: MutableList, + + @Column(name = "is_read", nullable = false) + private var isRead: Boolean = false, + + @JoinColumn(name = "reviewer_id", nullable = false) + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST, CascadeType.MERGE]) + private val reviewer: Reviewer, +) : Comparable, TimeBaseEntity() { + + fun read(read: Boolean) { + isRead = read + } + + override fun compareTo(other: Feedback): Int { + if (updatedAt.isAfter(other.updatedAt)) { + return -1 + } + if (updatedAt.isBefore(other.updatedAt)) { + return 1 + } + if (createdAt.isAfter(other.createdAt)) { + return -1 + } + if (createdAt.isBefore(other.createdAt)) { + return 1 + } + return 0 + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.kt b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.kt new file mode 100644 index 00000000..988489f8 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/FormQuestionFeedbackable.kt @@ -0,0 +1,37 @@ +package me.nalab.survey.domain.feedback + +import java.time.Instant +import javax.persistence.* + +@Entity +@Table(name = "form_feedback") +abstract class FormQuestionFeedbackable( + @Id + @Column(name = "form_feedback_id") + protected open var id: Long? = null, + + @Column(name = "form_question_id") + @JoinColumn(name = "form_question_id", nullable = false) + protected open val formQuestionId: Long, + + @Column(name = "is_read") + protected open var isRead: Boolean = false, + + @Embedded + protected open val bookmark: Bookmark, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "feedback_id") + private var feedback: Feedback, +) { + + fun bookmark() { + bookmark.isBookmarked = true + bookmark.bookmarkedAt = Instant.now() + } + + fun cancel() { + bookmark.isBookmarked = false + bookmark.bookmarkedAt = Instant.now() + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Reviewer.kt b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Reviewer.kt new file mode 100644 index 00000000..106d5f67 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/Reviewer.kt @@ -0,0 +1,44 @@ +package me.nalab.survey.domain.feedback + +import me.nalab.core.data.common.TimeBaseEntity +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.Id +import javax.persistence.Table + +@Entity(name = "reviewer") +@Table(name = "reviewer") +class Reviewer( + @Id + @Column(name = "reviewer_id") + private var id: Long? = null, + + @Column(name = "nick_name", nullable = false) + private var nickName: String, + + @Column(name = "collaboration_experience", nullable = false) + private val collaborationExperience: Boolean, + + @Column(name = "position", nullable = false) + private var position: String? = null, +) : TimeBaseEntity() { + + fun generateFirstReviewerNickName() { + nickName = "A" + } + + fun generateNickName(lastName: String) { + nickName = getNextNickName(lastName) + } + + private fun getNextNickName(lastName: String): String { + for (i in lastName.length - 1 downTo 0) { + if (lastName[i] != 'Z') { + return lastName.substring(0, i) + (lastName[i].code + 1).toChar() + "A".repeat( + lastName.length - (i + 1) + ) + } + } + return "A".repeat(Math.max(0, lastName.length + 1)) + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/feedback/ShortFormQuestionFeedback.kt b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/ShortFormQuestionFeedback.kt new file mode 100644 index 00000000..defdaf54 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/feedback/ShortFormQuestionFeedback.kt @@ -0,0 +1,17 @@ +package me.nalab.survey.domain.feedback + +import javax.persistence.* + +@Entity +class ShortFormQuestionFeedback( + id: Long? = null, + formQuestionId: Long, + isRead: Boolean = false, + bookmark: Bookmark, + feedback: Feedback, + + @ElementCollection + @CollectionTable(name = "reply", joinColumns = [JoinColumn(name = "form_feedback_id")]) + @Column(name = "replies") + private val replies: MutableList, +) : FormQuestionFeedbackable(id, formQuestionId, isRead, bookmark, feedback) diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/Choice.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/Choice.kt new file mode 100644 index 00000000..163a19f8 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/Choice.kt @@ -0,0 +1,28 @@ +package me.nalab.survey.domain.survey + +import javax.persistence.* + +@Entity(name = "choice") +@Table(name = "choice") +class Choice( + + @Id + @Column(name = "choice_id") + private var id: Long? = null, + + @Column(name = "content", length = 18, nullable = false) + private val content: String, + + @Column(name = "orders", nullable = false) + val order: Int, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "form_question_id", nullable = false) + private val choiceFormQuestion: ChoiceFormQuestion, +) : Comparable { + + override fun compareTo(other: Choice): Int { + return Comparator.comparingInt { obj: Choice -> obj.order } + .compare(this, other) + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/ChoiceFormQuestion.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ChoiceFormQuestion.kt new file mode 100644 index 00000000..a0e3cf17 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ChoiceFormQuestion.kt @@ -0,0 +1,44 @@ +package me.nalab.survey.domain.survey + +import me.nalab.survey.domain.survey.exception.DuplicatedOrderException +import javax.persistence.* + +@Entity +class ChoiceFormQuestion( + id: Long? = null, + title: String, + order: Int, + questionType: QuestionType, + survey: Survey, + + @OneToMany( + mappedBy = "choiceFormQuestion", + fetch = FetchType.LAZY, + cascade = [CascadeType.PERSIST, CascadeType.MERGE], + ) + private val choices: MutableList, + + @Column(name = "max_selection_count") + private val maxSelectableCount: Int, + + @Enumerated(EnumType.STRING) + @Column(name = "choice_question_type") + private val choiceFormQuestionType: ChoiceFormQuestionType, +) : FormQuestionable(id, title, order, questionType, survey) { + + init { + choices.sorted() + validNoDuplicatedChoiceOrder(choices) + } + + private fun validNoDuplicatedChoiceOrder(choiceList: List) { + val orders = HashSet() + choiceList.forEach { choice -> + val order: Int = choice.order + if (orders.contains(order)) { + throw DuplicatedOrderException(order, orders) + } + orders.add(order) + } + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/ChoiceFormQuestionType.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ChoiceFormQuestionType.kt new file mode 100644 index 00000000..2f57fbaf --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ChoiceFormQuestionType.kt @@ -0,0 +1,6 @@ +package me.nalab.survey.domain.survey + +enum class ChoiceFormQuestionType { + TENDENCY, + CUSTOM, +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/FormQuestionable.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/FormQuestionable.kt new file mode 100644 index 00000000..22b38467 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/FormQuestionable.kt @@ -0,0 +1,37 @@ +package me.nalab.survey.domain.survey + +import me.nalab.core.data.common.TimeBaseEntity +import java.util.function.LongSupplier +import javax.persistence.* + +@Entity +@Table(name = "form_question") +abstract class FormQuestionable( + @Id + @Column(name = "form_question_id") + open var id: Long? = null, + + @Column(name = "title", nullable = false, length = 45) + protected open val title: String, + + @Column(name = "orders", nullable = false) + open val order: Int, + + @Enumerated(EnumType.STRING) + @Column(name = "question_type") + protected open val questionType: QuestionType, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "survey_id", nullable = false) + open val survey: Survey, +) : Comparable, TimeBaseEntity() { + + protected open fun cascadeId(idSupplier: LongSupplier) { + return + } + + override fun compareTo(other: FormQuestionable): Int { + return Comparator.comparingInt { obj: FormQuestionable -> obj.order } + .compare(this, other) + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/QuestionType.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/QuestionType.kt new file mode 100644 index 00000000..39183447 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/QuestionType.kt @@ -0,0 +1,6 @@ +package me.nalab.survey.domain.survey + +enum class QuestionType { + CHOICE, + SHORT, +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/ShortFormQuestion.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ShortFormQuestion.kt new file mode 100644 index 00000000..38ab0aa4 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ShortFormQuestion.kt @@ -0,0 +1,16 @@ +package me.nalab.survey.domain.survey + +import javax.persistence.* + +@Entity +class ShortFormQuestion( + id: Long? = null, + title: String, + order: Int, + questionType: QuestionType, + survey: Survey, + + @Enumerated(EnumType.STRING) + @Column(name = "short_form_question_type") + private val shortFormQuestionType: ShortFormQuestionType, +) : FormQuestionable(id, title, order, questionType, survey) diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/ShortFormQuestionType.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ShortFormQuestionType.kt new file mode 100644 index 00000000..67433a33 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/ShortFormQuestionType.kt @@ -0,0 +1,7 @@ +package me.nalab.survey.domain.survey + +enum class ShortFormQuestionType { + STRENGTH, + WEAKNESS, + CUSTOM, +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/Survey.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/Survey.kt new file mode 100644 index 00000000..bc481767 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/Survey.kt @@ -0,0 +1,41 @@ +package me.nalab.survey.domain.survey + +import me.nalab.core.data.common.TimeBaseEntity +import me.nalab.survey.domain.survey.exception.DuplicatedOrderException +import javax.persistence.* + +@Table(name = "survey") +@Entity(name = "survey") +class Survey( + @Id + @Column(name = "gallery_id") + private var id: Long? = null, + + @OneToMany( + mappedBy = "survey", + fetch = FetchType.LAZY, + cascade = [CascadeType.PERSIST, CascadeType.MERGE] + ) + private val formQuestionables: MutableList, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "target_id") + private val target: Target, +) : TimeBaseEntity() { + + init { + formQuestionables.sorted() + validNoDuplicatedFormOrder() + } + + private fun validNoDuplicatedFormOrder() { + val orders = HashSet() + this.formQuestionables.forEach { formQuestion -> + val order: Int = formQuestion.order + if (orders.contains(order)) { + throw DuplicatedOrderException(order, orders) + } + orders.add(order) + } + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/SurveyBookmark.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/SurveyBookmark.kt new file mode 100644 index 00000000..4c09415f --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/SurveyBookmark.kt @@ -0,0 +1,12 @@ +package me.nalab.survey.domain.survey + +import javax.persistence.Column +import javax.persistence.Embeddable +import javax.persistence.JoinColumn + +@Embeddable +class SurveyBookmark( + @Column(name = "bookmarked_survey_id") + @JoinColumn(name = "survey_id", nullable = false) + val surveyId: Long +) diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/SurveyRepository.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/SurveyRepository.kt new file mode 100644 index 00000000..cfe7781b --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/SurveyRepository.kt @@ -0,0 +1,5 @@ +package me.nalab.survey.domain.survey + +import org.springframework.data.jpa.repository.JpaRepository + +interface SurveyRepository : JpaRepository diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/Target.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/Target.kt new file mode 100644 index 00000000..5216a37a --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/Target.kt @@ -0,0 +1,46 @@ +package me.nalab.survey.domain.survey + +import me.nalab.core.data.common.TimeBaseEntity +import javax.persistence.* + +@Entity +@Table(name = "target") +class Target( + @Id + @Column(name = "target_id") + private var id: Long? = null, + + @Column(name = "target_name", nullable = false) + private val nickname: String, + + @Column(name = "job", columnDefinition = "TEXT") + private val job: String, + + @Column(name = "image_url", columnDefinition = "TEXT") + private val imageUrl: String, + + @Column(name = "position") + private var position: String? = null, + + @ElementCollection + @CollectionTable(name = "bookmarked_survey", joinColumns = [JoinColumn(name = "target_id")]) + private val bookmarkedSurveys: MutableSet = NONE_BOOKMARKED_SURVEYS, + + @Version + @Column(name = "version") + private var version: Long? = null +) : TimeBaseEntity() { + + fun setPosition(position: String) { + this.position = position + } + + fun bookmark(surveyId: Long) { + val bookmark = SurveyBookmark(surveyId) + bookmarkedSurveys.add(bookmark) + } + + companion object { + private val NONE_BOOKMARKED_SURVEYS: MutableSet = HashSet() + } +} diff --git a/survey/src/main/kotlin/me/nalab/survey/domain/survey/exception/DuplicatedOrderException.kt b/survey/src/main/kotlin/me/nalab/survey/domain/survey/exception/DuplicatedOrderException.kt new file mode 100644 index 00000000..796fb8a7 --- /dev/null +++ b/survey/src/main/kotlin/me/nalab/survey/domain/survey/exception/DuplicatedOrderException.kt @@ -0,0 +1,6 @@ +package me.nalab.survey.domain.survey.exception + +class DuplicatedOrderException internal constructor(duplicated: Int, orders: HashSet) : + RuntimeException( + "Duplicated order detected duplicated \"$duplicated\" ordinary \"$orders\"" + )