Skip to content

feat: 설문 생성 API #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f43d163
feat: 프로젝트 초기 구성
fullth Mar 24, 2025
e552c3d
Add: ktlint
levi-gabia Mar 24, 2025
1fecd3b
chore: root -> project 경로 이동
levi-gabia Mar 24, 2025
20bbcaa
feat: 불필요한 allOpen 설정 제거
levi-gabia Mar 24, 2025
9d6527a
chore: .idea 포함되지 않도록 수정
levi-gabia Mar 24, 2025
2225ecb
delete: .idea directory
fullth Mar 24, 2025
eb196ad
delete: .idea directory
fullth Mar 24, 2025
f7aad21
fix: editconfig -> editorconfig
fullth Mar 31, 2025
2114fa9
add: domain entity
fullth Mar 31, 2025
1a804c3
Merge branch 'main' into taehwan-onboarding-#5
fullth Mar 31, 2025
8a62eb1
chore: lint
fullth Apr 1, 2025
5f78253
chore: 변수 사이 줄바꿈 허용
fullth Apr 1, 2025
e8fd9b3
chore: 변경한 lint 적용
fullth Apr 1, 2025
44d9d40
refactor: entity 패키지로 이동
fullth Apr 1, 2025
ba216d9
draft: 설문 생성
fullth Apr 1, 2025
c9a36e3
add: survey service, controller
levi-gabia Apr 2, 2025
fefb072
feat: 설문조사 생성
levi-gabia Apr 2, 2025
c673252
chore: 가독성을 위한 불필요 lint 비활성화 처리
levi-gabia Apr 2, 2025
70c88f2
feat: h2 설정
fullth Apr 3, 2025
2641585
chore: lint
fullth Apr 3, 2025
cd9c743
Merge branch 'taehwan-onboarding-#6' of https://github.com/InnerCircl…
fullth Apr 3, 2025
cefe4d6
chore: wildcard-imports 허용
fullth Apr 3, 2025
7f11538
feat: test용 h2 설정
fullth Apr 3, 2025
26869bb
chore: 불필요 import 제거
fullth Apr 5, 2025
8eb21a7
test: 설문조사 생성
fullth Apr 5, 2025
7e757ad
feat: 에러와 메시지 통일
fullth Apr 5, 2025
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
13 changes: 13 additions & 0 deletions project/taehwan-onboarding/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
root = true

[*]
charset=utf-8
end_of_line=lf
indent_style=space
indent_size=4
insert_final_newline=true

[*.{kt,kts}]
insert_final_newline=false
disabled_rules=no-wildcard-imports
ktlint_standard_no-blank-line-in-list = disabled
2 changes: 1 addition & 1 deletion project/taehwan-onboarding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Apply

```
2. 플러그인 설치 후 build를 수행하면 로컬 설정에 따라 에러가 발생할 수 있다.
![img.png](img.png)
![img.png](img.png)
3. 아래 명령어로 lint 설정에 맞도록 자동 수정할 수 있다
```shell
./gradlew ktlintFormat
Expand Down
2 changes: 1 addition & 1 deletion project/taehwan-onboarding/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ kotlin {

tasks.withType<Test> {
useJUnitPlatform()
}
}
Binary file added project/taehwan-onboarding/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion project/taehwan-onboarding/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = "taehwan-onboarding"
rootProject.name = "taehwan-onboarding"
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ class TaehwanOnboardingApplication

fun main(args: Array<String>) {
runApplication<TaehwanOnboardingApplication>(*args)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.survey.taehwanonboarding.api.dto

data class SurveyCreateRequest(
val title: String,
val description: String? = null,
val questions: List<QuestionRequest>
)

data class QuestionRequest(
val title: String,
val description: String? = null,
val required: Boolean = false,
val orderNumber: Int = 0,
val type: QuestionType,
val options: List<String>? = null,
val maxLength: Int? = null,
val minSelections: Int? = null,
val maxSelections: Int? = null
)

enum class QuestionType {
SHORT_ANSWER,
LONG_ANSWER,
SINGLE_SELECTION,
MULTI_SELECTION
}

data class SurveyCreateResponse(
val id: Long,
val title: String,
val description: String?,
val status: String,
val createdAt: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.survey.taehwanonboarding.domain.entity.response

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.OneToMany
import jakarta.persistence.Table
import jakarta.persistence.Version
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import org.survey.taehwanonboarding.domain.entity.survey.Survey
import java.time.LocalDateTime

@EntityListeners(AuditingEntityListener::class)
@Entity
@Table(name = "survey_response")
class SurveyResponse(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "survey_id", nullable = false)
val survey: Survey,

// todo: 익명 응답 고려
@Column
var respondentId: String? = null,

@Version
var version: Long = 0,

@CreatedDate
@Column(updatable = false)
var responseAt: LocalDateTime? = null,

@Enumerated(EnumType.STRING)
var status: ResponseStatus = ResponseStatus.SUBMITTED,

// todo: cascade 고려
@OneToMany(mappedBy = "response")
var items: MutableList<SurveyResponseItem> = mutableListOf(),
) {
enum class ResponseStatus {
PENDING,
SUBMITTED,
}

fun addResponseItem(item: SurveyResponseItem) {
items.add(item)
item.response = this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.survey.taehwanonboarding.domain.entity.response

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.Table
import org.survey.taehwanonboarding.domain.entity.survey.SurveyItem

@Entity
@Table(name = "response_item")
class SurveyResponseItem(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "survey_response_id")
var response: SurveyResponse? = null,

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "survey_item_id", nullable = false)
val surveyItem: SurveyItem,

// todo: 단일 값 또는 쉼표로 구분된 다중 값으로 우선 고려
@Column
var value: String? = null,
) {
fun isValid(): Boolean = surveyItem.validateResponse(value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.survey.taehwanonboarding.domain.entity.survey

import jakarta.persistence.Column
import jakarta.persistence.DiscriminatorValue
import jakarta.persistence.Entity

@Entity
@DiscriminatorValue("LONG_ANSWER")
class LongAnswerItem(
title: String,
description: String? = null,
required: Boolean = false,
orderNumber: Int = 0,
@Column
var maxLength: Int? = 1000,

) : SurveyItem(
title = title,
description = description,
required = required,
orderNumber = orderNumber,
) {
override fun validateResponse(responseValue: String?): Boolean {
if (required && responseValue.isNullOrBlank()) return false
return responseValue == null ||
(maxLength == null || responseValue.length <= maxLength!!)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.survey.taehwanonboarding.domain.entity.survey

import jakarta.persistence.CollectionTable
import jakarta.persistence.Column
import jakarta.persistence.DiscriminatorValue
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.JoinColumn
import jakarta.persistence.OrderColumn

@Entity
@DiscriminatorValue("MULTI_SELECTION")
class MultiSelectionItem(
title: String,
description: String? = null,
required: Boolean = false,
orderNumber: Int = 0,

@ElementCollection
@CollectionTable(
name = "item_option",
joinColumns = [JoinColumn(name = "survey_item_id")],
)
@OrderColumn(name = "order")
var options: MutableList<String> = mutableListOf(),

@Column
var minSelections: Int? = null,

@Column
var maxSelections: Int? = null,
) : SurveyItem(
title = title,
description = description,
required = required,
orderNumber = orderNumber,
) {
override fun validateResponse(responseValue: String?): Boolean {
if (responseValue.isNullOrBlank()) {
return !required
}

val selectedOptions = responseValue.split(",").map { it.trim() }

// 선택한 옵션 검증
if (!selectedOptions.all { options.contains(it) }) {
return false
}

val count = selectedOptions.size
if (required && count == 0) return false
if (minSelections != null && count < minSelections!!) return false
if (maxSelections != null && count > maxSelections!!) return false

return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.survey.taehwanonboarding.domain.entity.survey

import jakarta.persistence.Column
import jakarta.persistence.DiscriminatorValue
import jakarta.persistence.Entity

@Entity
@DiscriminatorValue("SHORT_ANSWER")
class ShortAnswerItem(
title: String,
description: String? = null,
required: Boolean = false,
orderNumber: Int = 0,

@Column
var maxLength: Int? = 255,
) : SurveyItem(
title = title,
description = description,
required = required,
orderNumber = orderNumber,
) {
override fun validateResponse(responseValue: String?): Boolean {
if (required && responseValue.isNullOrBlank()) return false
return responseValue == null ||
(maxLength == null || responseValue.length <= maxLength!!)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.survey.taehwanonboarding.domain.entity.survey

import jakarta.persistence.CollectionTable
import jakarta.persistence.DiscriminatorValue
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.JoinColumn
import jakarta.persistence.OrderColumn

@Entity
@DiscriminatorValue("SINGLE_SELECTION")
class SingleSelectionItem(
title: String,
description: String? = null,
required: Boolean = false,
orderNumber: Int = 0,

@ElementCollection
@CollectionTable(
name = "survey_item_options",
joinColumns = [JoinColumn(name = "survey_item_id")],
)
@OrderColumn(name = "order")
var options: MutableList<String> = mutableListOf(),
) : SurveyItem(
title = title,
description = description,
required = required,
orderNumber = orderNumber,
) {
override fun validateResponse(responseValue: String?): Boolean {
if (required && responseValue.isNullOrBlank()) return false
return responseValue == null || options.contains(responseValue)
}
}
Loading
Loading