diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/controller/SampleController.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/controller/SampleController.kt index d699ff36..106d1a95 100644 --- a/backend/src/main/kotlin/com/andlife/InvitationServer/controller/SampleController.kt +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/controller/SampleController.kt @@ -1,6 +1,7 @@ package com.andlife.InvitationServer.controller import com.andlife.InvitationServer.entity.Sample +import com.andlife.InvitationServer.response.BaseResponse import com.andlife.InvitationServer.service.SampleService import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController @@ -11,8 +12,9 @@ class SampleController( ) { @GetMapping("/") - fun getSamples(): List { - return sampleService.getAllSamples() + fun getSamples(): BaseResponse> { + val samples = sampleService.getAllSamples() + return BaseResponse.success(samples) } } \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/AnnouncementSection.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/AnnouncementSection.kt new file mode 100644 index 00000000..0cac1c0e --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/AnnouncementSection.kt @@ -0,0 +1,32 @@ +package com.andlife.InvitationServer.entity + +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 + +@Entity +@Table(name = "announcement_sections") +class AnnouncementSection( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "invitation_id") + val invitation: Invitation, + + @Column(nullable = false) + val title: String, + + @Column(nullable = false) + val content: String, + + @Column(name = "display_order", nullable = false) + val displayOrder: Int, +): BaseCreatedEntity() \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/BaseTimeEntity.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/BaseTimeEntity.kt new file mode 100644 index 00000000..714ce84d --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/BaseTimeEntity.kt @@ -0,0 +1,24 @@ +package com.andlife.InvitationServer.entity + +import jakarta.persistence.Column +import jakarta.persistence.EntityListeners +import jakarta.persistence.MappedSuperclass +import org.hibernate.annotations.CreationTimestamp +import org.hibernate.annotations.UpdateTimestamp +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.LocalDateTime + +@MappedSuperclass +@EntityListeners(AuditingEntityListener::class) +abstract class BaseCreatedEntity { + @CreationTimestamp + @Column(name = "created_at", nullable = false, updatable = false) + val createdAt: LocalDateTime = LocalDateTime.now() +} + +@MappedSuperclass +abstract class BaseTimeEntity : BaseCreatedEntity() { + @UpdateTimestamp + @Column(name = "updated_at", nullable = false) + var updatedAt: LocalDateTime = LocalDateTime.now() +} \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/GuestBook.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/GuestBook.kt new file mode 100644 index 00000000..45dd036b --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/GuestBook.kt @@ -0,0 +1,123 @@ +package com.andlife.InvitationServer.entity + +import jakarta.persistence.CascadeType +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.OneToMany +import jakarta.persistence.Table + +@Entity +@Table(name = "guestbooks") +class GuestBook( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "invitation_id", nullable = false) + val invitation: Invitation, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + val user: User, + + @Column(name = "text_content", columnDefinition = "TEXT", nullable = false) + var textContent: String, + + @OneToMany(mappedBy = "guestBook", cascade = [CascadeType.ALL]) + val images: MutableList = mutableListOf(), + + @OneToMany(mappedBy = "guestBook", cascade = [CascadeType.ALL]) + val audios: MutableList = mutableListOf(), + + @OneToMany(mappedBy = "guestBook", cascade = [CascadeType.ALL]) + val videos: MutableList = mutableListOf() +) : BaseTimeEntity() + +@Entity +@Table(name = "guestbook_images") +class GuestBookImage( + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "guestbook_post_id", nullable = false) + val guestBook: GuestBook, + + @Column(name = "image_url", nullable = false) + val imageUrl: String, + + @Column(name = "display_order", nullable = false) + val displayOrder: Int = 0 +) + +@Entity +@Table(name = "guestbook_audios") +class GuestBookAudio( + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "guestbook_post_id", nullable = false) + val guestBook: GuestBook, + + @Column(name = "audio_url", nullable = false) + val audioUrl: String, + + @Column(name = "duration_seconds", nullable = false) + val durationSeconds: Int, + + @Column(name = "display_order", nullable = false) + val displayOrder: Int = 0 +) + +@Entity +@Table(name = "guestbook_videos") +class GuestBookVideo( + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "guestbook_post_id", nullable = false) + val guestBook: GuestBook, + + @Column(name = "video_url", nullable = false) + val videoUrl: String, + + @Column(name = "thumbnail_url", nullable = false) + val thumbnailUrl: String, + + + @Column(name = "duration_seconds", nullable = false) + val durationSeconds: Int, + + @Column(name = "display_order", nullable = false) + val displayOrder: Int = 0, + + @OneToMany(mappedBy = "video", cascade = [CascadeType.ALL]) + val previewThumbnails: MutableList = mutableListOf() +) + + +@Entity +@Table(name = "video_preview_thumbnails") +class VideoPreviewThumbnail( + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "guestbook_video_id", nullable = false) + val video: GuestBookVideo, + + @Column(name = "thumbnail_url", nullable = false) + val thumbnailUrl: String, + + @Column(name = "time_seconds", nullable = false) + val timeSeconds: Double +) \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/Invitation.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/Invitation.kt new file mode 100644 index 00000000..e58da36e --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/Invitation.kt @@ -0,0 +1,61 @@ +package com.andlife.InvitationServer.entity + +import com.andlife.InvitationServer.util.StringListConverter +import jakarta.persistence.Column +import jakarta.persistence.Convert +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 java.time.LocalDate +import java.time.LocalTime + +@Entity +@Table(name = "invitations") +class Invitation( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "host_id", nullable = false) + val host: User, + + @Column(nullable = false) + var title: String, + + @Column(name = "display_host_name", nullable = false) + var displayHostName: String, + + @Convert(converter = StringListConverter::class) + @Column(name = "thumbnail_urls") + var thumbnailUrls: List = emptyList(), + + @Column(name = "invitation_date", nullable = false) + val invitationDate: LocalDate, + + @Column(name = "start_time", nullable = false) + val startTime: LocalTime, + + @Column(name = "end_time") + val endTime: LocalTime? = null, + + @Column(name = "place_name", nullable = false) + var placeName: String, + + @Column(nullable = false) + var address: String, + + @Column(nullable = false) + val lat: Double, + + @Column(nullable = false) + val lng: Double, + + @Column(name = "location_guide") + var locationGuide: String? = null +) : BaseTimeEntity() \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/InvitationCard.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/InvitationCard.kt new file mode 100644 index 00000000..11c0984b --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/InvitationCard.kt @@ -0,0 +1,29 @@ +package com.andlife.InvitationServer.entity + +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.OneToOne +import jakarta.persistence.Table + +@Entity +@Table(name = "invitation_cards") +class InvitationCard( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "invitation_id", nullable = false) + val invitation: Invitation, + + @Column(name = "content_json", columnDefinition = "TEXT") + var contentJson: String, + + @Column(name = "background_image_url") + var backgroundImageUrl: String? = null +) \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/InvitationParticipant.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/InvitationParticipant.kt new file mode 100644 index 00000000..6b3f2cb4 --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/InvitationParticipant.kt @@ -0,0 +1,33 @@ +package com.andlife.InvitationServer.entity + +import jakarta.persistence.* +import org.hibernate.annotations.CreationTimestamp +import java.time.LocalDateTime + +@Entity +@Table( + name = "invitation_participants", + uniqueConstraints = [ + UniqueConstraint( + name = "uk_invitation_user", + columnNames = ["invitation_id", "user_id"] + ) + ] +) +class InvitationParticipant( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "invitation_id", nullable = false) + val invitation: Invitation, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + val user: User, + + @CreationTimestamp + @Column(name = "joined_at", nullable = false, updatable = false) + val joinedAt: LocalDateTime = LocalDateTime.now() +) \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/ThanksCard.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/ThanksCard.kt new file mode 100644 index 00000000..fd1ddf4b --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/ThanksCard.kt @@ -0,0 +1,29 @@ +package com.andlife.InvitationServer.entity + +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.OneToOne +import jakarta.persistence.Table + +@Entity +@Table(name = "thanks_cards") +class ThanksCard( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0, + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "invitation_id", nullable = false) + val invitation: Invitation, + + @Column(name = "content_json", columnDefinition = "TEXT") + var contentJson: String, + + @Column(name = "background_image_url") + var backgroundImageUrl: String? = null +): BaseTimeEntity() \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/entity/User.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/User.kt new file mode 100644 index 00000000..f38955fc --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/entity/User.kt @@ -0,0 +1,25 @@ +package com.andlife.InvitationServer.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Table +import jakarta.persistence.Id + +@Entity +@Table(name = "users") +class User( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, + + @Column(nullable = false, unique = true) + val email: String, + + @Column(nullable = false) + val name: String, + + @Column(name = "profile_image_url") + val profileImageUrl: String? = null, +): BaseTimeEntity() \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/response/BaseResponse.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/response/BaseResponse.kt new file mode 100644 index 00000000..ed182c64 --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/response/BaseResponse.kt @@ -0,0 +1,33 @@ +package com.andlife.InvitationServer.response + +data class BaseResponse( + val code: Int, + val data: T?, + val message: String? +) { + companion object { + + fun success( + data: T? = null, + responseCode: ResponseCode = CommonResponseCode.OK + ): BaseResponse { + return BaseResponse( + code = responseCode.httpStatus.value(), + data = data, + message = responseCode.message + ) + } + + fun error( + responseCode: ResponseCode, + customMessage: String? = null + ): BaseResponse { + return BaseResponse( + code = responseCode.httpStatus.value(), + data = null, + message = customMessage ?: responseCode.message + ) + } + } + +} \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/response/ResponseCode.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/response/ResponseCode.kt new file mode 100644 index 00000000..760bfe79 --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/response/ResponseCode.kt @@ -0,0 +1,25 @@ +package com.andlife.InvitationServer.response + +import org.springframework.http.HttpStatus + +interface ResponseCode { + val httpStatus: HttpStatus + val message: String + val name: String +} + +enum class CommonResponseCode( + override val httpStatus: HttpStatus, + override val message: String +): ResponseCode { + // 2xx 성공 + OK(HttpStatus.OK, "성공적으로 처리되었습니다."), + + // 4xx 클라이언트 에러 + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증 정보가 유효하지 않습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 정보를 찾을 수 없습니다."), + BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), + + // 5xx 서버 에러 + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 오류가 발생했습니다. 잠시 후 다시 시도해주세요.") +} \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/response/home/LatestGuestBookResponse.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/response/home/LatestGuestBookResponse.kt new file mode 100644 index 00000000..cbf6df1d --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/response/home/LatestGuestBookResponse.kt @@ -0,0 +1,35 @@ +package com.andlife.InvitationServer.response.home + +import java.time.LocalDateTime + +data class LatestGuestBookResponse( + val id: Long, + val userName: String, + val userProfileUrl: String, + val content: String, + val createAt: LocalDateTime, + val images: List = emptyList(), + val audios: List = emptyList(), + val videos: List = emptyList() +) + +data class GuestbookImageResponse( + val id: Long, + val imageUrl: String, + val displayOrder: Int +) + +data class GuestbookAudioResponse( + val id: Long, + val audioUrl: String, + val durationSeconds: Int, + val displayOrder: Int +) + +data class GuestbookVideoResponse( + val id: Long, + val videoUrl: String, + val thumbnailUrl: String, + val durationSeconds: Int, + val displayOrder: Int +) \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/response/home/UpcomingInvitationResponse.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/response/home/UpcomingInvitationResponse.kt new file mode 100644 index 00000000..0b4c017a --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/response/home/UpcomingInvitationResponse.kt @@ -0,0 +1,12 @@ +package com.andlife.InvitationServer.response.home + +import java.time.LocalDateTime + +data class UpcomingInvitationResponse( + val id: Long, + val title: String, + val hostName: String, + val startTime: LocalDateTime, + val thumbnailUrl: String, + val dDay: Int +) \ No newline at end of file diff --git a/backend/src/main/kotlin/com/andlife/InvitationServer/util/StringListConverter.kt b/backend/src/main/kotlin/com/andlife/InvitationServer/util/StringListConverter.kt new file mode 100644 index 00000000..f0e95c2f --- /dev/null +++ b/backend/src/main/kotlin/com/andlife/InvitationServer/util/StringListConverter.kt @@ -0,0 +1,21 @@ +package com.andlife.InvitationServer.util + +import jakarta.persistence.AttributeConverter +import jakarta.persistence.Converter + +@Converter +class StringListConverter : AttributeConverter, String> { + + private val splitChar = "," + + // 엔티티의 List를 DB에 넣을 String으로 변환 (List -> "url1,url2") + override fun convertToDatabaseColumn(attribute: List?): String? { + return attribute?.joinToString(splitChar) + } + + // DB에서 가져온 String을 엔티티의 List로 변환 ("url1,url2" -> List) + override fun convertToEntityAttribute(dbData: String?): List { + return dbData?.split(splitChar)?.filter { it.isNotBlank() } ?: emptyList() + } + +} \ No newline at end of file