From 89f07d7369a34fd7daf69b058f45b8606de68238 Mon Sep 17 00:00:00 2001 From: Minky Date: Mon, 16 Sep 2024 22:47:05 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20ERD=20=EA=B8=B0=EB=B0=98=20Entity=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 15 +++- .../techpick/config/JpaAuditingConfig.java | 8 ++ .../techpick/config/P6SpySqlLoggerConfig.java | 63 +++++++++++++++ .../techpick/entity/admin/Administrator.java | 37 +++++++++ .../entity/admin/CrawlingTopicMapping.java | 38 ++++++++++ .../techpick/entity/admin/Topic.java | 32 ++++++++ .../techpick/entity/article/Article.java | 76 +++++++++++++++++++ .../techpick/entity/article/ArticleTopic.java | 36 +++++++++ .../entity/article/RawCrawledArticle.java | 35 +++++++++ .../kernel360/techpick/entity/blog/Blog.java | 44 +++++++++++ .../common/CreatedAndUpdatedTimeColumn.java | 28 +++++++ .../entity/event/AricleViewEvent.java | 48 ++++++++++++ .../techpick/entity/event/EventLocation.java | 11 +++ .../entity/subscriber/EmailSubscriber.java | 30 ++++++++ .../techpick/entity/user/AgeGroup.java | 10 +++ .../techpick/entity/user/JobGroup.java | 30 ++++++++ .../kernel360/techpick/entity/user/User.java | 71 +++++++++++++++++ .../techpick/entity/user/UserTopic.java | 36 +++++++++ backend/src/main/resources/application.yaml | 5 +- backend/src/main/resources/logback-spring.xml | 70 +++++++++++++++++ 20 files changed, 719 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/kernel360/techpick/config/JpaAuditingConfig.java create mode 100644 backend/src/main/java/kernel360/techpick/config/P6SpySqlLoggerConfig.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/admin/Administrator.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/admin/CrawlingTopicMapping.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/admin/Topic.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/article/Article.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/article/ArticleTopic.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/article/RawCrawledArticle.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/blog/Blog.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/common/CreatedAndUpdatedTimeColumn.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/event/AricleViewEvent.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/event/EventLocation.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/subscriber/EmailSubscriber.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/user/AgeGroup.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/user/JobGroup.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/user/User.java create mode 100644 backend/src/main/java/kernel360/techpick/entity/user/UserTopic.java create mode 100644 backend/src/main/resources/logback-spring.xml diff --git a/backend/build.gradle b/backend/build.gradle index a3afab7a..aa135552 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -24,11 +24,24 @@ repositories { } dependencies { + // spring boot implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' + + // lombok annotation + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + + // Sql logging formatter + // reference: https://www.baeldung.com/java-p6spy-intercept-sql-logging + implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.2' //이쁘게 + + // logback logger + implementation 'ch.qos.logback:logback-classic:1.4.12' + implementation 'org.slf4j:slf4j-api:2.0.3' + + // test environment testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/backend/src/main/java/kernel360/techpick/config/JpaAuditingConfig.java b/backend/src/main/java/kernel360/techpick/config/JpaAuditingConfig.java new file mode 100644 index 00000000..23ad236f --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/config/JpaAuditingConfig.java @@ -0,0 +1,8 @@ +package kernel360.techpick.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaAuditingConfig {} diff --git a/backend/src/main/java/kernel360/techpick/config/P6SpySqlLoggerConfig.java b/backend/src/main/java/kernel360/techpick/config/P6SpySqlLoggerConfig.java new file mode 100644 index 00000000..a1eab7cb --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/config/P6SpySqlLoggerConfig.java @@ -0,0 +1,63 @@ +package kernel360.techpick.config; + +import java.sql.SQLException; +import java.util.Locale; +import static org.springframework.util.StringUtils.hasText; + +import org.hibernate.engine.jdbc.internal.FormatStyle; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.p6spy.engine.common.ConnectionInformation; +import com.p6spy.engine.event.JdbcEventListener; +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.P6SpyOptions; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; + +/** + * Jdbc가 DB Connection을 얻은 이후에 로깅 포맷을 P6SpyOptions가 가로채도록 하는 Bean입니다. + */ +@Profile({"default", "local", "dev"}) // WARN: Do not use in production mode. +@Component +public class P6SpySqlLoggerConfig extends JdbcEventListener implements MessageFormattingStrategy { + + @Override + public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) { + P6SpyOptions.getActiveInstance().setLogMessageFormat(this.getClass().getName()); + } + + @Override + public String formatMessage(int connectionId, String now, long elapsed, String category, + String prepared, String sql, String url) { + return highlight(format(category, sql)); + } + + private String highlight(String sql) { + return FormatStyle.HIGHLIGHT.getFormatter().format(sql); + } + + private String format(String category, String sql) { + if (hasText(sql) && isStatement(category)) { + if (isDdl(trim(sql))) { + return FormatStyle.DDL.getFormatter().format(sql); + } + return FormatStyle.BASIC.getFormatter().format(sql); + } + return sql; + } + + private static boolean isDdl(String trimmedSql) { + return trimmedSql.startsWith("create") + || trimmedSql.startsWith("alter") + || trimmedSql.startsWith("drop") + || trimmedSql.startsWith("comment"); + } + + private static String trim(String sql) { + return sql.trim().toLowerCase(Locale.ROOT); + } + + private static boolean isStatement(String category) { + return Category.STATEMENT.getName().equals(category); + } +} diff --git a/backend/src/main/java/kernel360/techpick/entity/admin/Administrator.java b/backend/src/main/java/kernel360/techpick/entity/admin/Administrator.java new file mode 100644 index 00000000..fefb8c57 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/admin/Administrator.java @@ -0,0 +1,37 @@ +package kernel360.techpick.entity.admin; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "administrator") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Administrator { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "administrator_id") + private Long administratorId; + + // 관리자 로그인 이메일 + @Column(name = "email", nullable = false, unique = true) + private String email; + + // 비밀번호 + @Column(name = "password", nullable = false) + private String password; + + // 관리자 이름 + @Column(name = "name", nullable = false) + private String name; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/admin/CrawlingTopicMapping.java b/backend/src/main/java/kernel360/techpick/entity/admin/CrawlingTopicMapping.java new file mode 100644 index 00000000..5bf59fcb --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/admin/CrawlingTopicMapping.java @@ -0,0 +1,38 @@ +package kernel360.techpick.entity.admin; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * NOTE: 이 부분은 좀 더 토의가 필요 합니다. + * + */ +@Table(name = "crawling_topic_mapping") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CrawlingTopicMapping { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "crawling_topic_mapping_id") + private Long crawlingTopicMappingId; + + // TODO: 태그 종류는 블로그별로 다 달라질 것이다. + // 아래 처럼 하면, 블로그가 추가될 때마다 우리가 직접 매핑해줘야 한다. + + // TODO: 아래와 같이 하면, 블로그별 태그 mapper를 구현할 필요 없음. + // 아래 mappingString이 공용이기 때문. + // ex. "#Spring#스프링#spring" --> 모든 블로그에 적용 + @Column(name = "mapping_string", nullable = false) + private String mappingString; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/admin/Topic.java b/backend/src/main/java/kernel360/techpick/entity/admin/Topic.java new file mode 100644 index 00000000..6ced7ff7 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/admin/Topic.java @@ -0,0 +1,32 @@ +package kernel360.techpick.entity.admin; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +// NOTE: 관리자만 수정 가능한 테이블 입니다. +// TODO: 기본 값은 "분류 없음" 레코드 입니다. +// '분류 없음'을 테이블에 반드시 존재하도록 설정해야 합니다. +@Table(name = "topic") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Topic { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "topic_id") + private Long topicId; + + // 주제명 (중복된 주제 명은 없어야 합니다.) + @Column(name = "topic_name", nullable = false, unique = true) + private String topicName; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/article/Article.java b/backend/src/main/java/kernel360/techpick/entity/article/Article.java new file mode 100644 index 00000000..b2fff928 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/article/Article.java @@ -0,0 +1,76 @@ +package kernel360.techpick.entity.article; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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 kernel360.techpick.entity.blog.Blog; +import kernel360.techpick.entity.common.CreatedAndUpdatedTimeColumn; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "article") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Article extends CreatedAndUpdatedTimeColumn { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "article_id") + private Long articleId; + + // 원문 제목 + @Column(name = "title", nullable = false) + private String title; + + // 원문 링크 + @Column(name = "url", nullable = false) + private String url; + + // 원문 작성 일자 + @Column(name = "published_at", nullable = false) + private LocalDateTime publishedAt; + + // 원문 작성자 + @Column(name = "author") // nullable (수집 안될 수도 있음) + private String author; + + // 원문 출처 블로그 + @ManyToOne + @JoinColumn(name = "blog_id", nullable = false) + private Blog blog; + + // 수집 날짜 (크롤링 시각) + @Column(name = "crawled_at", nullable = false) + private LocalDateTime crawled_at; + + // 대표 이미지 CDN url + @Column(name = "image_url") + private String imageUrl; // nullable + + // [사용자 - 관심 토픽] 1:N 테이블 + @OneToMany(mappedBy = "article") + private List articleTopics = new ArrayList<>(); + + // 게시글-토픽 관계 테이블 + // @ManyToMany + // @JoinTable( + // name = "article_topic", + // joinColumns = @JoinColumn(name = "article_id", nullable = false), + // inverseJoinColumns = @JoinColumn(name = "topic_id", nullable = false) + // ) + // private List topics = new ArrayList<>(); +} diff --git a/backend/src/main/java/kernel360/techpick/entity/article/ArticleTopic.java b/backend/src/main/java/kernel360/techpick/entity/article/ArticleTopic.java new file mode 100644 index 00000000..2eec417c --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/article/ArticleTopic.java @@ -0,0 +1,36 @@ +package kernel360.techpick.entity.article; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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 kernel360.techpick.entity.admin.Topic; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "article_topic") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ArticleTopic { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "article_topic_id") + private Long articleTopicId; + + @ManyToOne + @JoinColumn(name = "article_id") + private Article article; + + @ManyToOne + @JoinColumn(name = "topic_id") + private Topic topic; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/article/RawCrawledArticle.java b/backend/src/main/java/kernel360/techpick/entity/article/RawCrawledArticle.java new file mode 100644 index 00000000..7fd412b6 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/article/RawCrawledArticle.java @@ -0,0 +1,35 @@ +package kernel360.techpick.entity.article; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import kernel360.techpick.entity.common.CreatedAndUpdatedTimeColumn; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * NOTE: 크롤링한 Raw 데이터를 삽입하는 테이블입니다. + * 데이터 형식은 바뀔 수 있습니다. + */ +@Table(name = "raw_crawled_article") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RawCrawledArticle extends CreatedAndUpdatedTimeColumn { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "raw_crawled_article_id") + private Long rawCrawledArticleId; + + // TODO: 아래 크롤링 데이터 칼럼은 토의 후 바뀔 예정 입니다. + // 크롤링 데이터 + @Column(name = "data", nullable = false) + private String data; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/blog/Blog.java b/backend/src/main/java/kernel360/techpick/entity/blog/Blog.java new file mode 100644 index 00000000..45ea0f4d --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/blog/Blog.java @@ -0,0 +1,44 @@ +package kernel360.techpick.entity.blog; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +// NOTE: 관리자만 수정 가능한 테이블 입니다. +// TODO: rss를 지원하지 않는 경우 어떻게 처리할지 고민 필요. +@Table(name = "blog") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Blog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "blog_id") + private Long blogId; + + // 블로그명 + @Column(name = "blog_name", nullable = false) + private String blogName; + + // 블로그 주소 + @Column(name = "blog_url", nullable = false) + private String blogUrl; + + // RSS service url + @Column(name = "rss_url") + private String rssUrl; // nullable + + // 블로그 기본 대표 이미지 + @Column(name = "default_image", nullable = false) + private String base64Image; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/common/CreatedAndUpdatedTimeColumn.java b/backend/src/main/java/kernel360/techpick/entity/common/CreatedAndUpdatedTimeColumn.java new file mode 100644 index 00000000..1d5d3a84 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/common/CreatedAndUpdatedTimeColumn.java @@ -0,0 +1,28 @@ +package kernel360.techpick.entity.common; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class CreatedAndUpdatedTimeColumn { + + // 생성 시간 자동 부여 + @CreatedDate + @Column(name = "created_at", updatable = false, nullable = false) + protected LocalDateTime createdAt; + + // 수정 시간 자동 부여 + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + protected LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/event/AricleViewEvent.java b/backend/src/main/java/kernel360/techpick/entity/event/AricleViewEvent.java new file mode 100644 index 00000000..7cb05b07 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/event/AricleViewEvent.java @@ -0,0 +1,48 @@ +package kernel360.techpick.entity.event; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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.Table; +import kernel360.techpick.entity.article.Article; +import kernel360.techpick.entity.common.CreatedAndUpdatedTimeColumn; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +// NOTE: 사용자 조회 추적 모델 +// TODO: 1분 동안 반복 되는 조회는 1번으로 칠 것. +@Table(name = "article_view_event") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AricleViewEvent extends CreatedAndUpdatedTimeColumn { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "article_view_event_id") + private Long articleViewEventId; + + // 이메일 + @Column(name = "email", nullable = false) + private String email; + + // 이벤트 발생 위치 + @Enumerated(EnumType.STRING) + @Column(name = "event_location", nullable = false) + private EventLocation eventLocation; + + // 조회한 게시글 + @ManyToOne(fetch = FetchType.LAZY) // article 은 즉시 조회될 필요 없음 + @JoinColumn(name = "article_id", nullable = false) + private Article article; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/event/EventLocation.java b/backend/src/main/java/kernel360/techpick/entity/event/EventLocation.java new file mode 100644 index 00000000..4e8de6b3 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/event/EventLocation.java @@ -0,0 +1,11 @@ +package kernel360.techpick.entity.event; + +// TODO: 아래는 일단 초안. 정확한 분류는 토의 후에 결정할 것. +// 이벤트 발생 위치 Enum. +// ArticleViewEvent 에서 사용. +public enum EventLocation { + SUBSCRIPTION_EMAIL, + SITE_HOME, + SITE_MY_PAGE, + // 추가 예정 +} diff --git a/backend/src/main/java/kernel360/techpick/entity/subscriber/EmailSubscriber.java b/backend/src/main/java/kernel360/techpick/entity/subscriber/EmailSubscriber.java new file mode 100644 index 00000000..a3bf9f86 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/subscriber/EmailSubscriber.java @@ -0,0 +1,30 @@ +package kernel360.techpick.entity.subscriber; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import kernel360.techpick.entity.common.CreatedAndUpdatedTimeColumn; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "email_subscriber") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class EmailSubscriber extends CreatedAndUpdatedTimeColumn { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "email_subscriber_id") + private Long emailSubscriberId; + + // 구독자 이메일 + @Column(name = "email", nullable = false) + private String email; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/user/AgeGroup.java b/backend/src/main/java/kernel360/techpick/entity/user/AgeGroup.java new file mode 100644 index 00000000..4f61a646 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/user/AgeGroup.java @@ -0,0 +1,10 @@ +package kernel360.techpick.entity.user; + +// TODO: 아래는 일단 초안. 정확한 분류는 토의 후에 결정할 것. +public enum AgeGroup { + G10, // 10대 + G20, // 20대 + G30, // 30대 + G40, // 40대 + G50, // 50대 +} diff --git a/backend/src/main/java/kernel360/techpick/entity/user/JobGroup.java b/backend/src/main/java/kernel360/techpick/entity/user/JobGroup.java new file mode 100644 index 00000000..8f437c45 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/user/JobGroup.java @@ -0,0 +1,30 @@ +package kernel360.techpick.entity.user; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +// NOTE: 관리자만 수정 가능한 테이블 입니다. +@Table(name = "job_group") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class JobGroup { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "job_group_id") + private Long jobGroupId; + + // 직군 명 + @Column(name = "job_group_name", nullable = false) + private String jobGroupName; +} diff --git a/backend/src/main/java/kernel360/techpick/entity/user/User.java b/backend/src/main/java/kernel360/techpick/entity/user/User.java new file mode 100644 index 00000000..14677090 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/user/User.java @@ -0,0 +1,71 @@ +package kernel360.techpick.entity.user; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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 lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "user") +@SQLDelete(sql = "UPDATE user SET removed_at = CURRENT_TIMESTAMP WHERE user_id=?") +@SQLRestriction("removed_at IS NULL") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long userId; + + // 소셜 로그인 이메일 + @Column(name = "email", nullable = false, unique = true) + private String email; + + // 연령층 분류 + @Column(name = "age_group", nullable = false) + @Enumerated(EnumType.STRING) + private AgeGroup ageGroup; + + // 직군 분류 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "job_group_id", nullable = false) + private JobGroup jobGroup; + + // Soft Delete - 삭제 시간 + @Column(name = "removed_at") + private LocalDateTime removedAt; // nullable + + // [사용자 - 관심 토픽] 1:N 테이블 + @OneToMany(mappedBy = "user") + private List userTopics = new ArrayList<>(); + + // [사용자 - 관심 토픽] 관계 테이블 + // @ManyToMany + // @JoinTable( + // name = "user_topic", + // joinColumns = @JoinColumn(name = "user_id", nullable = false), + // inverseJoinColumns = @JoinColumn(name = "topic_id", nullable = false) + // ) + // private List topics = new ArrayList<>(); +} diff --git a/backend/src/main/java/kernel360/techpick/entity/user/UserTopic.java b/backend/src/main/java/kernel360/techpick/entity/user/UserTopic.java new file mode 100644 index 00000000..2f3bd0e1 --- /dev/null +++ b/backend/src/main/java/kernel360/techpick/entity/user/UserTopic.java @@ -0,0 +1,36 @@ +package kernel360.techpick.entity.user; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +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 kernel360.techpick.entity.admin.Topic; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Table(name = "user_topic") +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserTopic { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_topic_id") + private Long userTopicInterestId; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @JoinColumn(name = "topic_id") + private Topic topic; +} diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index 4f7a6828..86cc7325 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -11,11 +11,10 @@ spring: jpa: properties: hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect # MySQL 버전에 맞게 설정하세요 - format_sql: true + format_sql: false + show_sql: false hibernate: ddl-auto: update # 이 부분 설정 주의 필요 - show-sql: true datasource: url: ${DOCKER_MYSQL_URL} username: ${DOCKER_MYSQL_USERNAME} diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..a35371d7 --- /dev/null +++ b/backend/src/main/resources/logback-spring.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + + + + + + 100MB + + ${LOG_PATH}/${DATE_DIR}/application.%i.log + + 20GB + + 60 + + + + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35}[%line] - %msg%n + + + + + + + + + + + + + + + + + + + + + + + +