WorkingDead Backend - Codebase λΆμ
When:D (μ¬λ) - λ©ν° νλ«νΌ μΌμ μ‘°μ¨ μλΉμ€
ν/κ·Έλ£Ήμ νμ, λͺ¨μ μΌμ μ μ‘°μ¨νκΈ° μν ν¬ν μμ€ν
μ μ 곡νλ©°, Discord λ΄ κ³Ό μΉ΄μΉ΄μ€ν‘ μ±λ΄ λ νλ«νΌμ ν΅ν΄ μλνλ ν¬ν μμ± λ° μλ¦Ό κΈ°λ₯μ μ 곡ν©λλ€.
ꡬλΆ
κΈ°μ
Language
Java 21
Framework
Spring Boot 3.5.7
Database
PostgreSQL (AWS RDS)
ORM
Spring Data JPA + Hibernate
Security
Spring Security
Session
Spring Session JDBC
Cache
Redis
API Docs
SpringDoc OpenAPI (Swagger)
Discord Bot
JDA 5.0.0-beta.24
Kakao Chatbot
Kakao i Open Builder (REST API)
Build Tool
Gradle
src/main/java/com/workingdead/
βββ WorkingdeadApplication.java # λ©μΈ μ ν리μΌμ΄μ
β
βββ config/ # μ€μ ν΄λμ€
β βββ SecurityConfig.java # Spring Security μ€μ
β βββ CorsConfig.java # CORS μ€μ
β βββ OpenApiConfig.java # Swagger μ€μ
β βββ DiscordBotConfig.java # Discord JDA μ€μ
β βββ KakaoConfig.java # Kakao API μ€μ
β
βββ enum/
β βββ Period.java # μκ°λ enum (LUNCH/DINNER)
β
βββ meet/ # ν΅μ¬ λλ©μΈ (ν¬ν μμ€ν
)
β βββ controller/
β β βββ VoteController.java # ν¬ν CRUD API
β β βββ ParticipantController.java # μ°Έμ¬μ κ΄λ¦¬ API
β β βββ VoteResultController.java # ν¬ν κ²°κ³Ό μ‘°ν API
β β βββ VoteDateRangeController.java # λ μ§ λ²μ μ‘°ν API
β β
β βββ service/
β β βββ VoteService.java # ν¬ν λΉμ¦λμ€ λ‘μ§
β β βββ ParticipantService.java # μ°Έμ¬μ λΉμ¦λμ€ λ‘μ§
β β βββ VoteResultService.java # κ²°κ³Ό μ§κ³ λ‘μ§
β β βββ VoteDateRangeService.java # λ μ§ λ²μ λ‘μ§
β β βββ PriorityService.java # μ°μ μμ λ‘μ§
β β
β βββ entity/
β β βββ Vote.java # ν¬ν μν°ν°
β β βββ Participant.java # μ°Έμ¬μ μν°ν°
β β βββ ParticipantSelection.java # μΌμ μ ν μν°ν°
β β βββ PriorityPreference.java # μ°μ μμ μν°ν°
β β
β βββ repository/
β β βββ VoteRepository.java
β β βββ ParticipantRepository.java
β β βββ ParticipantSelectionRepository.java
β β βββ PriorityPreferenceRepository.java
β β
β βββ dto/
β β βββ VoteDtos.java
β β βββ ParticipantDtos.java
β β βββ VoteResultDtos.java
β β βββ VoteDateRangeDtos.java
β β βββ PriorityDtos.java
β β
β βββ application/
β βββ VoteApplicationService.java # μ ν리μΌμ΄μ
μλΉμ€
β
βββ chatbot/ # μ±λ΄ λͺ¨λ (λ©ν° νλ«νΌ)
β
βββ discord/ # Discord λ΄
β βββ command/
β β βββ DiscordWendyCommand.java # λμ€μ½λ λͺ
λ Ήμ΄ νΈλ€λ¬
β βββ service/
β β βββ DiscordWendyService.java # λ΄ μλΉμ€ μΈν°νμ΄μ€
β β βββ DiscordWendyServiceImpl.java # λ΄ μλΉμ€ ꡬν체
β β βββ DiscordWendyNotifier.java # μλ¦Ό μλΉμ€
β βββ scheduler/
β β βββ DiscordWendyScheduler.java # μ€μΌμ€λ¬ (리λ§μΈλ)
β βββ dto/
β βββ DiscordVoteResult.java # ν¬ν κ²°κ³Ό DTO
β
βββ kakao/ # Kakao μ±λ΄
βββ controller/
β βββ KakaoSkillController.java # Skill Server μλν¬μΈνΈ
βββ service/
β βββ KakaoWendyService.java # μΈμ
κ΄λ¦¬ μλΉμ€
β βββ KakaoNotifier.java # μλ¦Ό μλΉμ€
βββ scheduler/
β βββ KakaoWendyScheduler.java # μ€μΌμ€λ¬ (리λ§μΈλ)
βββ dto/
βββ KakaoRequest.java # Skill μμ² DTO
βββ KakaoResponse.java # Skill μλ΅ DTO
ERD (Entity Relationship)
βββββββββββββββββββ
β Vote β
βββββββββββββββββββ€
β id (PK) β
β name β
β code (unique) β
β startDate β
β endDate β
β createdAt β
ββββββββββ¬βββββββββ
β 1:N
βΌ
βββββββββββββββββββ
β Participant β
βββββββββββββββββββ€
β id (PK) β
β vote_id (FK) β
β displayName β
β submitted β
β submittedAt β
ββββββββββ¬βββββββββ
β 1:N 1:N
ββββββββββββββββββββββββββββ
βΌ βΌ
βββββββββββββββββββββββ βββββββββββββββββββββββ
β ParticipantSelectionβ β PriorityPreference β
βββββββββββββββββββββββ€ βββββββββββββββββββββββ€
β id (PK) β β id (PK) β
β participant_id (FK) β β participant_id (FK) β
β vote_id (FK) β β vote_id (FK) β
β date β β date β
β period (LUNCH/DINNERβ β period β
β selected β β priorityIndex (1~3) β
βββββββββββββββββββββββ β weight β
β createdAt β
βββββββββββββββββββββββ
μν°ν°
μ€λͺ
Vote
ν¬ν μΈμ
. κ³ μ codeλ‘ κ³΅μ λ§ν¬ μμ±
Participant
ν¬ν μ°Έμ¬μ. Voteμ μ’
μ
ParticipantSelection
μ°Έμ¬μμ λ μ§/μκ°λ μ ν (true/false)
PriorityPreference
μ°Έμ¬μμ μ°μ μμ (1μμ, 2μμ, 3μμ)
Method
Endpoint
μ€λͺ
GET
/votes
μ 체 ν¬ν λͺ©λ‘ μ‘°ν
GET
/votes/{id}
ν¬ν μμΈ μ‘°ν
GET
/votes/share/{code}
곡μ μ½λλ‘ ν¬ν μ‘°ν
POST
/votes
μ ν¬ν μμ±
PATCH
/votes/{id}
ν¬ν μ 보 μμ
DELETE
/votes/{id}
ν¬ν μμ
GET
/votes/{voteId}/dateRange
λ μ§ λ²μ μ‘°ν
GET
/votes/{voteId}/result
ν¬ν κ²°κ³Ό μ‘°ν
Method
Endpoint
μ€λͺ
GET
/votes/{voteId}/participants
μ°Έμ¬μ λͺ©λ‘ μ‘°ν
POST
/votes/{voteId}/participants
μ°Έμ¬μ μΆκ°
PATCH
/participants/{id}
μ°Έμ¬μ μ 보 μμ
DELETE
/participants/{id}
μ°Έμ¬μ μμ
GET
/participants/{id}/choices
μ°Έμ¬μ μ ν μ 보 μ‘°ν
PATCH
/participants/{id}/schedule
μΌμ μ μΆ
POST
/participants/{id}
μ°μ μμ μ€μ
Kakao Skill API (/kakao/skill)
Method
Endpoint
μ€λͺ
POST
/kakao/skill/start
μ¬λ μμ (μΈμ
μμ±)
POST
/kakao/skill/participants
μ°Έμμ μΆκ°
POST
/kakao/skill/weeks
μ£Όμ°¨ μ ν λ° ν¬ν μμ±
POST
/kakao/skill/revote
μ¬ν¬ν
POST
/kakao/skill/end
μΈμ
μ’
λ£
POST
/kakao/skill/status
νμ¬ μν μ‘°ν
POST
/kakao/skill/help
λμλ§
λ©ν° νλ«νΌ μν€ν
μ²
νλ«νΌλ³ νΉμ± λΉκ΅
ꡬλΆ
Discord
Kakao
ν΅μ λ°©μ
WebSocket (JDA)
REST API (Skill Server)
μΈμ
μλ³μ
channelId
userKey
μλ¦Ό λ°©μ
Push (λ΄μ΄ μ§μ μ μ‘)
Pull (μ¬μ©μ μμ² μ μλ΅)
λ©ν° μ μ
μ±λ λ΄ λ€μ μ°Έμ¬
κ°μΈ μ± κΈ°λ°
μ΄λ²€νΈ μ²λ¦¬
ListenerAdapter
REST Controller
μν€ν
μ² λ€μ΄μ΄κ·Έλ¨
βββββββββββββββββββββββββββββββββββββββββββ
β Core Services (meet/) β
β βββββββββββββββ βββββββββββββββββββ β
β β VoteService β β ParticipantSvc β β
β βββββββββββββββ βββββββββββββββββββ β
β βββββββββββββββ βββββββββββββββββββ β
β βVoteResultSvcβ β PriorityServiceβ β
β βββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
β²
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ
β β β
βΌ β βΌ
ββββββββββββββββββββββββββββ β ββββββββββββββββββββββββββββ
β Discord Module β β β Kakao Module β
ββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββββββ€
β DiscordWendyCommand β β β KakaoSkillController β
β (ListenerAdapter) β β β (REST Controller) β
ββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββββββ€
β DiscordWendyService β β β KakaoWendyService β
β (channelId κΈ°λ° μΈμ
) β β β (userKey κΈ°λ° μΈμ
) β
ββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββββββ€
β DiscordWendyScheduler β β β KakaoWendyScheduler β
β DiscordWendyNotifier β β β KakaoNotifier β
ββββββββββββββββββββββββββββ β ββββββββββββββββββββββββββββ
β β β
βΌ β βΌ
Discord Server β Kakao Talk
(WebSocket) β (REST API)
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
βDiscordWendyCommandββββββΆβDiscordWendyServiceββββββΆβ VoteService β
β (ListenerAdapter β β Impl β β Participant β
β μ΄λ²€νΈ νΈλ€λ¬) β β (μΈμ
κ΄λ¦¬) β β Service β
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββ βββββββββββββββββββββ
βDiscordWendySchedulerβββββΆβDiscordWendyNotifierβ
β (μκ° κΈ°λ° β β (λ©μμ§ μ μ‘) β
β νμ€ν¬ κ΄λ¦¬) β βββββββββββββββββββββ
βββββββββββββββββββββ
λͺ
λ Ήμ΄
μ€λͺ
μ¬λ μμ
μΌμ μ‘°μ¨ μΈμ
μμ
μ¬λ μ’
λ£
μΈμ
μ’
λ£
μ¬λ μ¬ν¬ν
λμΌ μ°Έμμλ‘ μ ν¬ν μμ±
μ¬λ λμλ§ / /help
λμλ§ νμ
μκ°
μλ¦Ό λ΄μ©
10λΆ ν
ν¬ν νν© κ³΅μ
15λΆ ν
λ―Έν¬νμ λ
μ΄ (1μ°¨)
1μκ° ν
λ―Έν¬νμ λ
μ΄ (2μ°¨)
6μκ° ν
λ―Έν¬νμ λ
μ΄ (3μ°¨)
12μκ° ν
λ―Έν¬νμ λ
μ΄ (4μ°¨)
24μκ° ν
μ΅νν΅μ²© (1μμλ‘ νμ μλ΄)
μΈμ
κ΄λ¦¬ (DiscordWendyServiceImpl)
// μ±λλ³ μν κ΄λ¦¬
private final Set <String > activeSessions ; // νμ± μΈμ
private final Map <String , Map <String , String >> participants ; // μ°Έμμ
private final Map <String , Long > channelVoteId ; // μ±λ -> ν¬νID
private final Map <String , String > channelShareUrl ; // μ±λ -> 곡μ URL
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
βKakaoSkillControllerβββββΆβ KakaoWendyService ββββββΆβ VoteService β
β (REST API β β (μΈμ
κ΄λ¦¬) β β Participant β
β Skill Server) β β β β Service β
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββ βββββββββββββββββββββ
βKakaoWendySchedulerββββββΆβ KakaoNotifier β
β (μκ° κΈ°λ° β β (μλ¦Ό μ μ‘) β
β νμ€ν¬ κ΄λ¦¬) β βββββββββββββββββββββ
βββββββββββββββββββββ
μΈμ
μν (SessionState)
public enum SessionState {
IDLE , // λκΈ° μν
WAITING_PARTICIPANTS , // μ°Έμμ μ
λ ₯ λκΈ°
WAITING_WEEKS , // μ£Όμ°¨ μ ν λκΈ°
VOTE_CREATED // ν¬ν μμ± μλ£
}
μΈμ
κ΄λ¦¬ (KakaoWendyService)
// userKey κΈ°λ° μν κ΄λ¦¬
private final Map <String , SessionState > sessionStates ; // μΈμ
μν
private final Map <String , Map <String , String >> participants ; // μ°Έμμ
private final Map <String , Long > userVoteId ; // ν¬ν ID
private final Map <String , String > userShareUrl ; // 곡μ URL
private final Map <String , String > userVoteName ; // ν¬ν μ΄λ¦
Kakao i Open Builder μ°λ
μμ² DTO (KakaoRequest): userRequest, action, intent, contexts ν¬ν¨
μλ΅ DTO (KakaoResponse): simpleText, textWithQuickReplies, basicCard λ± λ€μν μλ΅ νμ μ§μ
ν¬ν κ²°κ³Ό μ§κ³ λ‘μ§
μ λ ¬ κΈ°μ€ (VoteResultService)
1μμ: ν¬ν μΈμμ (λ§μμλ‘ μμ)
2μμ: μ°μ μμ Index ν©κ³ (μμμλ‘ μμ)
3μμ: λ μ§ (λΉ λ₯Όμλ‘ μμ)
λ μ§
μκ°λ
μΈμ
μ°μ μμν©
μμ
01/20
LUNCH
5λͺ
3
1μ
01/21
DINNER
5λͺ
7
2μ
01/19
LUNCH
4λͺ
2
3μ
spring :
datasource :
url : jdbc:postgresql://[RDS_HOST]:5432/workingdead
username : postgres
password : ${DB_PASSWORD}
jpa :
hibernate :
ddl-auto : update
properties :
hibernate :
dialect : org.hibernate.dialect.PostgreSQLDialect
session :
store-type : jdbc
server :
port : 8080
discord :
token : ${DISCORD_TOKEN}
kakao :
rest-api-key : ${KAKAO_REST_API_KEY}
admin-key : ${KAKAO_ADMIN_KEY}
channel-id : ${KAKAO_CHANNEL_ID}
λ³μλͺ
μ€λͺ
DB_PASSWORD
PostgreSQL λΉλ°λ²νΈ
DISCORD_TOKEN
Discord Bot ν ν°
KAKAO_REST_API_KEY
Kakao REST API ν€
KAKAO_ADMIN_KEY
Kakao Admin ν€
KAKAO_CHANNEL_ID
Kakao μ±λ ID
AWS_ACCESS_KEY_ID
AWS μ‘μΈμ€ ν€
AWS_SECRET_ACCESS_KEY
AWS μν¬λ¦Ώ ν€
// SecurityConfig.java
.authorizeHttpRequests (auth -> auth
.requestMatchers ("/v3/api-docs/**" , "/swagger-ui/**" ).permitAll ()
.requestMatchers ("/kakao/skill/**" ).permitAll () // Kakao Skill Server
.anyRequest ().permitAll () // λͺ¨λ μμ² νμ©
);
μΈμ¦ : 미ꡬν (λͺ¨λ API 곡κ°)
CSRF : λΉνμ±ν
CORS : μ§μ λ λλ©μΈλ§ νμ©
νμ© λλ©μΈ (CorsConfig)
localhost:3000, 5173, 8080, 8081
whend.app (HTTP/HTTPS)
whendy.netlify.app
ν΅μ¬ λΉμ¦λμ€ νλ‘μ°
1. Discord ν¬ν μμ± νλ‘μ°
1. λμ€μ½λμμ "μ¬λ μμ" μ
λ ₯
2. μ°Έμμ μ ν (λλ‘λ€μ΄ λ©λ΄)
3. μ£Όμ°¨ μ ν (μ΄λ² μ£Ό ~ 6μ£Ό λ€)
4. Vote μν°ν° μμ± + Participant μΌκ΄ μμ±
5. 곡μ URL λ°ν (whendy.netlify.app/v/{code})
6. μ€μΌμ€λ¬ μμ (리λ§μΈλ μλ¦Ό)
2. Kakao ν¬ν μμ± νλ‘μ°
1. μΉ΄μΉ΄μ€ν‘μμ "μ¬λ μμ" λ°ν
2. μ°Έμμ μ΄λ¦ μ
λ ₯ (μΌν ꡬλΆ)
3. μ£Όμ°¨ μ ν (Quick Reply λ²νΌ)
4. Vote μν°ν° μμ± + Participant μΌκ΄ μμ±
5. 곡μ URL λ°ν
6. μ€μΌμ€λ¬ μμ (리λ§μΈλ μλ¦Ό)
3. ν¬ν μ°Έμ¬ νλ‘μ° (곡ν΅)
1. 곡μ URL μ μ
2. μ°Έμ¬μ μΉ© μ ν (λ³ΈμΈ μ ν)
3. λ μ§/μκ°λ μ ν (LUNCH/DINNER)
4. μ°μ μμ μ€μ (1~3μμ, μ νμ¬ν)
5. μ μΆ β ParticipantSelection, PriorityPreference μ μ₯
4. κ²°κ³Ό μ‘°ν νλ‘μ°
1. GET /votes/{voteId}/result
2. μ νλ μΌμ μ§κ³ (selected=true)
3. μ°μ μμ κ°μ€μΉ κ³μ°
4. μ λ ¬: μΈμ > μ°μ μμν© > λ μ§
5. μμ 3κ° λνΉ λ°ν
μμ‘΄μ± λͺ©λ‘ (build.gradle)
// Core
implementation ' org.springframework.boot:spring-boot-starter-web'
implementation ' org.springframework.boot:spring-boot-starter-data-jpa'
implementation ' org.springframework.boot:spring-boot-starter-security'
implementation ' org.springframework.boot:spring-boot-starter-validation'
// Database
runtimeOnly ' org.postgresql:postgresql'
runtimeOnly ' com.h2database:h2'
implementation ' org.flywaydb:flyway-core'
// Session & Cache
implementation ' org.springframework.session:spring-session-jdbc'
implementation ' org.springframework.boot:spring-boot-starter-data-redis'
// Discord Bot
implementation ' net.dv8tion:JDA:5.0.0-beta.24'
// API Docs
implementation ' org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8'
// Utilities
compileOnly ' org.projectlombok:lombok'
λ©ν° νλ«νΌ μ§μ : Discord + Kakao λμ μ΄μ
νλ«νΌ λ
립μ μ½μ΄ : ν΅μ¬ ν¬ν λ‘μ§μ 곡μ , νλ«νΌλ³ μ΄λν° λΆλ¦¬
μ€μκ° μλ¦Ό : μ€μΌμ€λ¬ κΈ°λ° μλ 리λ§μΈλ (νλ«νΌλ³ ꡬν)
μ°μ μμ μμ€ν
: λ¨μ ν¬νκ° μλ κ°μ€μΉ κΈ°λ° κ²°κ³Ό λμΆ
곡μ URL : 8μ리 κ³ μ μ½λλ‘ κ°νΈν 곡μ
μΈμ
κ΄λ¦¬ : νλ«νΌλ³ λ
립μ μΈ μΈμ
μν κ΄λ¦¬
Discord: channelId κΈ°λ°
Kakao: userKey κΈ°λ°
λ¬Έμ μμ±μΌ: 2026-01-19
μ΅μ’
μ
λ°μ΄νΈ: 2026-01-19 (λ©ν° νλ«νΌ μν€ν
μ² λ°μ)