diff --git a/src/main/kotlin/com/snuxi/participant/entity/Participants.kt b/src/main/kotlin/com/snuxi/participant/entity/Participants.kt new file mode 100644 index 0000000..d243ddc --- /dev/null +++ b/src/main/kotlin/com/snuxi/participant/entity/Participants.kt @@ -0,0 +1,16 @@ +package com.snuxi.participant.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.LocalDateTime + +@Entity +@Table(name = "participants") +class Participants ( + @Id var id: Long? = null, + @Column(name = "user_id") var userId: Long, + @Column(name = "pot_id") var potId: Long, + @Column(name = "joined_at") var joinedAt: LocalDateTime +) \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/participant/repository/ParticipantRepository.kt b/src/main/kotlin/com/snuxi/participant/repository/ParticipantRepository.kt new file mode 100644 index 0000000..816772b --- /dev/null +++ b/src/main/kotlin/com/snuxi/participant/repository/ParticipantRepository.kt @@ -0,0 +1,8 @@ +package com.snuxi.participant.repository + +import com.snuxi.participant.entity.Participants +import org.springframework.data.jpa.repository.JpaRepository + +interface ParticipantRepository : JpaRepository{ + fun existsByUserId(userId: Long): Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/PotException.kt b/src/main/kotlin/com/snuxi/pot/PotException.kt new file mode 100644 index 0000000..e442730 --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/PotException.kt @@ -0,0 +1,32 @@ +package com.snuxi.pot + +import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatusCode + +sealed class PotException ( + errorCode: Int, + httpStatusCode: HttpStatusCode, + msg: String, + cause: Throwable? = null +): DomainException(errorCode, httpStatusCode, msg, cause) + +class MinMaxReversedException : + PotException( + errorCode = 0, + httpStatusCode = HttpStatus.BAD_REQUEST, + msg = "최소 인원수는 최대 인원수보다 반드시 작거나 같아야 합니다." + ) + +class InvalidCountException : + PotException( + errorCode = 0, + httpStatusCode = HttpStatus.BAD_REQUEST, + msg = "최소 인원수는 2명 이상, 최대 인원수는 4명 이하로 설정해주세요." + ) + +class DuplicateParticipationException : + PotException( + errorCode = 0, + httpStatusCode = HttpStatus.BAD_REQUEST, + msg = "한 명이 2개 이상의 택시팟 참여에 관여할 수 없습니다." + ) \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/PotStatus.kt b/src/main/kotlin/com/snuxi/pot/PotStatus.kt new file mode 100644 index 0000000..e635717 --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/PotStatus.kt @@ -0,0 +1,8 @@ +package com.snuxi.pot + +enum class PotStatus { + RECRUITING, + SUCCESS, + FAILED, + EXPIRED +} \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/controller/PotController.kt b/src/main/kotlin/com/snuxi/pot/controller/PotController.kt new file mode 100644 index 0000000..c3a5afe --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/controller/PotController.kt @@ -0,0 +1,39 @@ +package com.snuxi.pot.controller + +import com.snuxi.pot.dto.CreatePotRequest +import com.snuxi.pot.dto.CreatePotResponse +import com.snuxi.pot.service.PotService +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.time.LocalDateTime + +@RestController +class PotController ( + private val potService: PotService +) { + @PostMapping("/room") + fun create( + @RequestHeader("CERTIFIED_USER_ID") userId: Long, + @RequestBody createPotRequest: CreatePotRequest + ): ResponseEntity { + val ownerId = createPotRequest.ownerId + val departureId = createPotRequest.departureId + val destinationId = createPotRequest.destinationId + val departureTime = createPotRequest.departureTime + val minCapacity = createPotRequest.minCapacity + val maxCapacity = createPotRequest.maxCapacity + + val response = potService.createPot(userId, ownerId, departureId, destinationId, departureTime, minCapacity, maxCapacity) + return ResponseEntity.status(HttpStatus.CREATED).body(response) + } + + @DeleteMapping("/rooms/{roomId}") + fun delete( + @RequestHeader("CERTIFIED_USER_ID") userId: Long, + @PathVariable roomId: Long + ): ResponseEntity { + potService.deletePot(userId, roomId) + return ResponseEntity.noContent().build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/dto/CreatePotRequest.kt b/src/main/kotlin/com/snuxi/pot/dto/CreatePotRequest.kt new file mode 100644 index 0000000..d5b43f7 --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/dto/CreatePotRequest.kt @@ -0,0 +1,12 @@ +package com.snuxi.pot.dto + +import java.time.LocalDateTime + +data class CreatePotRequest ( + val ownerId: Long, + val departureId: Long, + val destinationId: Long, + val departureTime: LocalDateTime, + val minCapacity: Int, + val maxCapacity: Int +) diff --git a/src/main/kotlin/com/snuxi/pot/dto/CreatePotResponse.kt b/src/main/kotlin/com/snuxi/pot/dto/CreatePotResponse.kt new file mode 100644 index 0000000..fd556d9 --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/dto/CreatePotResponse.kt @@ -0,0 +1,5 @@ +package com.snuxi.pot.dto + +data class CreatePotResponse ( + val createdPotId: Long +) \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/entity/Pots.kt b/src/main/kotlin/com/snuxi/pot/entity/Pots.kt new file mode 100644 index 0000000..020f85a --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/entity/Pots.kt @@ -0,0 +1,22 @@ +package com.snuxi.pot.entity + +import com.snuxi.pot.PotStatus +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.LocalDateTime + +@Entity +@Table(name = "pots") +class Pots ( + @Id var id: Long? = null, + @Column(name = "owner_id") var ownerId: Long, + @Column(name = "departure_id") var departureId: Long, + @Column(name = "destination_id") var destinationId: Long, + @Column(name = "departure_time") var departureTime: LocalDateTime, + @Column(name = "min_capacity") var minCapacity: Int, + @Column(name = "max_capacity") var maxCapacity: Int, + @Column(name = "current_count") var currentCount: Int, + @Column(name = "status") var status: PotStatus +) \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/repository/PotRepository.kt b/src/main/kotlin/com/snuxi/pot/repository/PotRepository.kt new file mode 100644 index 0000000..dc1933d --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/repository/PotRepository.kt @@ -0,0 +1,8 @@ +package com.snuxi.pot.repository + +import com.snuxi.pot.entity.Pots +import org.springframework.data.jpa.repository.JpaRepository + +interface PotRepository : JpaRepository { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/snuxi/pot/service/PotService.kt b/src/main/kotlin/com/snuxi/pot/service/PotService.kt new file mode 100644 index 0000000..11ba005 --- /dev/null +++ b/src/main/kotlin/com/snuxi/pot/service/PotService.kt @@ -0,0 +1,68 @@ +package com.snuxi.pot.service + +import com.snuxi.participant.entity.Participants +import com.snuxi.participant.repository.ParticipantRepository +import com.snuxi.pot.DuplicateParticipationException +import com.snuxi.pot.InvalidCountException +import com.snuxi.pot.MinMaxReversedException +import com.snuxi.pot.PotStatus +import com.snuxi.pot.dto.CreatePotResponse +import com.snuxi.pot.entity.Pots +import com.snuxi.pot.repository.PotRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime + +@Service +class PotService ( + private val potRepository: PotRepository, + private val participantRepository: ParticipantRepository +) { + @Transactional + fun createPot( + userId: Long, + ownerId: Long, + departureId: Long, + destinationId: Long, + departureTime: LocalDateTime, + minCapacity: Int, + maxCapacity: Int + ): CreatePotResponse { + if(minCapacity > maxCapacity) throw MinMaxReversedException() + if(minCapacity < 2 || maxCapacity > 4) throw InvalidCountException() + if(participantRepository.existsByUserId(userId)) throw DuplicateParticipationException() + + val save = potRepository.save( + Pots( + ownerId = ownerId, + departureId = departureId, + destinationId = destinationId, + departureTime = departureTime, + minCapacity = minCapacity, + maxCapacity = maxCapacity, + currentCount = 1, + status = PotStatus.RECRUITING + ) + ) + + participantRepository.save( + Participants( + userId = userId, + potId = save.id!!, + joinedAt = LocalDateTime.now() + ) + ) + + return CreatePotResponse( + createdPotId = save.id!! + ) + } + + @Transactional + fun deletePot( + userId: Long, + potId: Long + ) { + + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__create_table_users.sql b/src/main/resources/db/migration/V1__create_table_users.sql new file mode 100644 index 0000000..3b5f7ad --- /dev/null +++ b/src/main/resources/db/migration/V1__create_table_users.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS users +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + email VARCHAR(64) NOT NULL, + username VARCHAR(32) NOT NULL, + profile_image_url TEXT, + `role` VARCHAR(16) NOT NULL, + created_at TIMESTAMP(6) NOT NULL, + updated_at TIMESTAMP(6), + active_pot_id BIGINT +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__create_table_landmarks.sql b/src/main/resources/db/migration/V2__create_table_landmarks.sql new file mode 100644 index 0000000..1c7bd4b --- /dev/null +++ b/src/main/resources/db/migration/V2__create_table_landmarks.sql @@ -0,0 +1,26 @@ +CREATE TABLE IF NOT EXISTS landmarks +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + landmark_name VARCHAR(128) NOT NULL, + latitude DECIMAL(7, 5) NOT NULL, + longitude DECIMAL(8, 5) NOT NULL +); + +INSERT INTO landmarks (landmark_name, longitude, latitude) VALUES +('서울대입구역 3번 출구', 126.95244, 37.48070), +('낙성대역 버스정류장', 126.96242, 37.47779), +('낙성대입구 버스정류장', 126.96188,37.47791), +('대학동고시촌입구 버스정류장(녹두)', 126.93838, 37.47054), +('사당역 4번 출구', 126.98148, 37.47500), +('경영대.행정대학원 버스정류장', 126.95205, 37.46614), +('자연대.행정관입구 버스정류장', 126.94896, 37.45999), +('법대.사회대입구 버스정류장', 126.94908, 37.46326), +('농생대 버스정류장', 126.94917, 37.45716), +('공대입구 버스정류장(글로벌공학교육센터)', 126.94984, 37.45493), +('제2공학관(302동) 버스정류장', 126.95208, 37.44880), +('학부생활관 버스정류장', 126.95807, 37.46375), +('수의대입구.보건대학원앞 버스정류장', 126.95463, 37.46612), +('기숙사 삼거리', 126.95677, 37.46046), +('국제대학원', 126.95526, 37.46418); + + diff --git a/src/main/resources/db/migration/V3__create_table_pots.sql b/src/main/resources/db/migration/V3__create_table_pots.sql new file mode 100644 index 0000000..8402f5b --- /dev/null +++ b/src/main/resources/db/migration/V3__create_table_pots.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS pots +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + owner_id BIGINT NOT NULL, + departure_id BIGINT NOT NULL, + destination_id BIGINT NOT NULL, + departure_time TIMESTAMP(6) NOT NULL, + min_capacity TINYINT NOT NULL, + max_capacity TINYINT NOT NULL, + current_count TINYINT NOT NULL, + status VARCHAR(16) NOT NULL, + + CONSTRAINT pot__fk_owner_id FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE, + CONSTRAINT pot__fk_departure_id FOREIGN KEY (departure_id) REFERENCES landmarks (id) ON DELETE CASCADE, + CONSTRAINT pot__fk_destination_id FOREIGN KEY (departure_id) REFERENCES landmarks (id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V4__create_table_participants.sql b/src/main/resources/db/migration/V4__create_table_participants.sql new file mode 100644 index 0000000..65697c8 --- /dev/null +++ b/src/main/resources/db/migration/V4__create_table_participants.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS participants +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + pot_id BIGINT NOT NULL, + joined_at TIMESTAMP(6) NOT NULL, + + CONSTRAINT __participants_fk_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, + CONSTRAINT __participants_fk_pot_id FOREIGN KEY (pot_id) REFERENCES pots (id) ON DELETE CASCADE, + + UNIQUE KEY __participants_uk (user_id) +); \ No newline at end of file