From ba5e02cb3fa85509871d324dccd6d4a5242e115f Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 21:19:43 +0900 Subject: [PATCH 1/7] =?UTF-8?q?chore:=20Flyway=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 31696399..7adfbd8f 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,10 @@ dependencies { // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // Flyway + implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-database-postgresql' } jar { enabled = false } From 29bdee6cffa15342553d790605e80ee39f3882ed Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 21:20:08 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20Flyway=20baseline=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yaml | 6 +++++- src/main/resources/application.yaml | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml index 9505d639..94b5a336 100644 --- a/src/main/resources/application-prod.yaml +++ b/src/main/resources/application-prod.yaml @@ -11,6 +11,10 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + flyway: + enabled: true + baseline-on-migrate: true + baseline-version: 0 data: redis: host: ${REDIS_HOST} @@ -32,4 +36,4 @@ tracking: consumer-group: ${TRACKING_CONSUMER_GROUP} test: - secret-key: ${TEST_SECRET_KEY} \ No newline at end of file + secret-key: ${TEST_SECRET_KEY} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 3f3b1846..945a37a5 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -18,6 +18,10 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect + flyway: + enabled: true + baseline-on-migrate: true + baseline-version: 0 jwt: secret: ${JWT_SECRET} From a4a272fe2079a689451253913b9accdfb0e45b4e Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 21:20:40 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20Hibernate=20ddl-auto=20validate?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yaml | 2 +- src/main/resources/application.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml index 94b5a336..a662c989 100644 --- a/src/main/resources/application-prod.yaml +++ b/src/main/resources/application-prod.yaml @@ -6,7 +6,7 @@ spring: driver-class-name: org.postgresql.Driver jpa: hibernate: - ddl-auto: update + ddl-auto: validate show-sql: false properties: hibernate: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 945a37a5..a05801cb 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -12,7 +12,7 @@ spring: driver-class-name: org.postgresql.Driver jpa: hibernate: - ddl-auto: update + ddl-auto: validate show-sql: false properties: From 59c410bd91ec40bcb775ec39f747d86085467393 Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 21:21:46 +0900 Subject: [PATCH 4/7] =?UTF-8?q?docs:=20Flyway=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20=EA=B7=9C=EC=B9=99=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/migration/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/resources/db/migration/README.md diff --git a/src/main/resources/db/migration/README.md b/src/main/resources/db/migration/README.md new file mode 100644 index 00000000..f84c5ceb --- /dev/null +++ b/src/main/resources/db/migration/README.md @@ -0,0 +1,16 @@ +# Flyway Migration Guide + +이 디렉터리에는 운영 DB 스키마 변경 SQL을 추가합니다. + +- 한 번 배포된 migration 파일은 수정하지 않습니다. +- 변경이 필요하면 새 버전의 migration 파일을 추가합니다. +- 파일명은 `V{version}__{description}.sql` 형식을 사용합니다. +- 엔티티 변경과 대응되는 DDL migration은 같은 PR에 포함합니다. +- destructive DDL은 컬럼 추가, 코드 배포, 데이터 백필, 제약 강화 순서로 나눠서 적용합니다. + +예시: + +```text +V1__add_review_image_url.sql +V2__create_hiking_record_table.sql +``` From de3ce490ecf7365db974e30a463244f1bf16ba14 Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 21:31:28 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20=EC=B4=88=EA=B8=B0=20Flyway=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-prod.yaml | 2 +- src/main/resources/application.yaml | 2 +- src/main/resources/db/migration/README.md | 4 +- src/main/resources/db/migration/V1__INIT.sql | 389 +++++++++++++++++++ 4 files changed, 393 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/db/migration/V1__INIT.sql diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml index a662c989..1431fd00 100644 --- a/src/main/resources/application-prod.yaml +++ b/src/main/resources/application-prod.yaml @@ -14,7 +14,7 @@ spring: flyway: enabled: true baseline-on-migrate: true - baseline-version: 0 + baseline-version: 1 data: redis: host: ${REDIS_HOST} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index a05801cb..c77270aa 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -21,7 +21,7 @@ spring: flyway: enabled: true baseline-on-migrate: true - baseline-version: 0 + baseline-version: 1 jwt: secret: ${JWT_SECRET} diff --git a/src/main/resources/db/migration/README.md b/src/main/resources/db/migration/README.md index f84c5ceb..1c170b4e 100644 --- a/src/main/resources/db/migration/README.md +++ b/src/main/resources/db/migration/README.md @@ -11,6 +11,6 @@ 예시: ```text -V1__add_review_image_url.sql -V2__create_hiking_record_table.sql +V2__add_review_image_url.sql +V3__create_hiking_record_table.sql ``` diff --git a/src/main/resources/db/migration/V1__INIT.sql b/src/main/resources/db/migration/V1__INIT.sql new file mode 100644 index 00000000..8e3efcb5 --- /dev/null +++ b/src/main/resources/db/migration/V1__INIT.sql @@ -0,0 +1,389 @@ +DO $$ +BEGIN + CREATE TYPE device_type_enum AS ENUM ('ANDROID', 'IOS'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +DO $$ +BEGIN + CREATE TYPE gender_enum AS ENUM ('FEMALE', 'MALE', 'NONE'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +DO $$ +BEGIN + CREATE TYPE oauth_provider_enum AS ENUM ('APPLE', 'KAKAO', 'TEST'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +DO $$ +BEGIN + CREATE TYPE onboarding_status_enum AS ENUM ('COMPLETE', 'INCOMPLETE'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +CREATE TABLE IF NOT EXISTS users +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + age integer, + is_deleted boolean not null, + device_type device_type_enum not null, + email varchar(50), + fcm_token varchar(255), + gender gender_enum, + name varchar(30), + oauth_id varchar(255) not null, + oauth_provider oauth_provider_enum not null, + onboarding_status onboarding_status_enum not null, + profile_url varchar(255), + weight double precision, + nickname varchar(30), + birth_date date, + height double precision, + refresh_token varchar(512), + constraint users_pk + unique (oauth_id, oauth_provider) +); + +CREATE UNIQUE INDEX IF NOT EXISTS uk_users_active_nickname + on users (nickname) + where ((nickname IS NOT NULL) AND (is_deleted = false)); + +CREATE TABLE IF NOT EXISTS fcm_tokens +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + device_type device_type_enum not null, + token varchar(512) not null + constraint ukqopjyk0c1cho2ep0abxd9hi5q + unique, + user_id bigint not null +); + +CREATE INDEX IF NOT EXISTS idx_fcm_tokens_user_id + on fcm_tokens (user_id); + +CREATE TABLE IF NOT EXISTS mountains +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + address varchar(255) not null, + altitude double precision not null, + difficulty varchar(20) not null + constraint mountains_difficulty_check + check ((difficulty)::text = ANY + ((ARRAY ['EASY'::character varying, 'NORMAL'::character varying, 'HARD'::character varying])::text[])), + duration integer, + image_url varchar(500), + latitude double precision, + longitude double precision, + name varchar(50) not null +); + +CREATE TABLE IF NOT EXISTS amenities +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + direction varchar(50) not null, + type varchar(20) not null + constraint amenities_type_check + check ((type)::text = ANY + ((ARRAY ['RESTROOM'::character varying, 'INFORMATION'::character varying, 'SHELTER'::character varying, 'PARKING'::character varying, 'STORE'::character varying])::text[])), + mountain_id bigint not null + constraint fkei4md0u9m9co2vnkm7oehecvk + references mountains +); + +CREATE TABLE IF NOT EXISTS courses +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + difficulty varchar(20) not null + constraint courses_difficulty_check + check ((difficulty)::text = ANY + ((ARRAY ['EASY'::character varying, 'NORMAL'::character varying, 'HARD'::character varying])::text[])), + distance double precision not null, + duration integer not null, + name varchar(100) not null, + mountain_id bigint not null + constraint fkbus8p693p0o69ufkxjyr80ods + references mountains +); + +CREATE TABLE IF NOT EXISTS notifications +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + body varchar(500) not null, + extras jsonb, + is_read boolean not null, + read_at timestamp(6), + title varchar(100) not null, + type varchar(50) not null + constraint notifications_type_check + check ((type)::text = 'COMMUNITY_COMMENT'::text), + user_id bigint not null +); + +CREATE INDEX IF NOT EXISTS idx_notifications_user_id_created_at + on notifications (user_id asc, created_at desc); + +CREATE TABLE IF NOT EXISTS restaurant_sections +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + title varchar(100) not null, + mountain_id bigint not null + constraint fkgr63prvstv4ijxhsqfxkjxgkc + references mountains +); + +CREATE TABLE IF NOT EXISTS restaurants +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + category varchar(50), + image_url varchar(500), + name varchar(100) not null, + section_id bigint not null + constraint fkhksvos93swrecisxt6dss0iux + references restaurant_sections +); + +CREATE TABLE IF NOT EXISTS reviews +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + content text not null, + difficulty varchar(20) not null + constraint reviews_difficulty_check + check ((difficulty)::text = ANY + ((ARRAY ['EASY'::character varying, 'NORMAL'::character varying, 'HARD'::character varying])::text[])), + image_url varchar(500), + course_id bigint + constraint fkccbfc9u1qimejr5ll7yuxbtqs + references courses, + mountain_id bigint not null + constraint fk52hwkbpt6ygjvshk6aj1016el + references mountains, + user_id bigint not null + constraint fkcgy7qjc1r99dp117y9en6lxye + references users +); + +CREATE TABLE IF NOT EXISTS transportations +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + description varchar(500), + direction varchar(50) not null, + name varchar(100) not null, + type varchar(20) not null + constraint transportations_type_check + check ((type)::text = ANY + ((ARRAY ['SUBWAY'::character varying, 'BUS'::character varying, 'PARKING'::character varying])::text[])), + mountain_id bigint not null + constraint fks9mes69yi1rvrfajfrlpyatql + references mountains +); + +CREATE TABLE IF NOT EXISTS user_onboardings +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + user_id bigint not null + unique + constraint fk_user_onboardings_user + references users, + hiking_level varchar(20) not null + constraint user_onboardings_hiking_level_check + check ((hiking_level)::text = ANY + ((ARRAY ['BEGINNER'::character varying, 'INTERMEDIATE'::character varying, 'EXPERT'::character varying])::text[])), + exercise_type varchar(30) not null + constraint user_onboardings_exercise_type_check + check ((exercise_type)::text = ANY + ((ARRAY ['GYM'::character varying, 'CARDIO'::character varying, 'HOME_TRAINING'::character varying, 'PILATES'::character varying, 'YOGA'::character varying, 'WALKING_HIKING'::character varying, 'SPORTS'::character varying, 'CROSSFIT'::character varying, 'SWIMMING'::character varying, 'NONE'::character varying])::text[])), + exercise_frequency varchar(30) + constraint user_onboardings_exercise_frequency_check + check ((exercise_frequency IS NULL) OR ((exercise_frequency)::text = ANY + ((ARRAY ['DAILY'::character varying, 'WEEK_3_4'::character varying, 'WEEK_1_2'::character varying, 'MONTH_1_2'::character varying, 'LESS_THAN_MONTH_1'::character varying])::text[]))), + exercise_duration varchar(30) + constraint user_onboardings_exercise_duration_check + check ((exercise_duration IS NULL) OR ((exercise_duration)::text = ANY + ((ARRAY ['OVER_4H'::character varying, 'HOUR_2_4'::character varying, 'HOUR_1_2'::character varying, 'UNDER_1H'::character varying])::text[]))) +); + +CREATE TABLE IF NOT EXISTS user_notification_settings +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + user_id bigint not null + unique + constraint fk_user_notification_settings_user + references users, + push_notification_enabled boolean not null, + live_activity_enabled boolean not null, + voice_enabled boolean not null +); + +CREATE TABLE IF NOT EXISTS mountain_likes +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + user_id bigint not null + constraint fk_mountain_likes_user + references users, + mountain_id bigint not null + constraint fk_mountain_likes_mountain + references mountains, + constraint uk_mountain_likes_user_id_mountain_id + unique (user_id, mountain_id) +); + +CREATE INDEX IF NOT EXISTS idx_mountain_likes_user_id_created_at + on mountain_likes (user_id asc, created_at desc); + +CREATE TABLE IF NOT EXISTS hiking_records +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + mountain_id bigint not null + constraint fk_hiking_records_mountain + references mountains, + course_id bigint not null + constraint fk_hiking_records_course + references courses, + duration integer not null, + max_altitude double precision not null, + calories integer not null, + clive_image_url varchar(500), + photo_report_image_url varchar(500) +); + +CREATE TABLE IF NOT EXISTS hiking_members +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + hiking_record_id bigint not null + constraint fk_hiking_members_hiking_record + references hiking_records, + user_id bigint not null + constraint fk_hiking_members_user + references users, + constraint uk_hiking_member_record_user + unique (hiking_record_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_hiking_member_user_id + on hiking_members (user_id); + +CREATE TABLE IF NOT EXISTS posts +( + post_type varchar(20) not null, + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + content text, + is_deleted boolean not null, + view_count integer not null, + author_id bigint not null + references users +); + +CREATE TABLE IF NOT EXISTS comments +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + content text not null, + is_deleted boolean not null, + author_id bigint not null + references users, + mentioned_user_id bigint + references users, + parent_id bigint + references comments, + post_id bigint not null + references posts +); + +CREATE TABLE IF NOT EXISTS free_posts +( + title varchar(200) not null, + id bigint not null + primary key + references posts +); + +CREATE TABLE IF NOT EXISTS post_images +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + image_url varchar(500) not null, + is_main boolean not null, + sort_order integer not null, + post_id bigint not null + references posts +); + +CREATE TABLE IF NOT EXISTS post_likes +( + id bigint generated by default as identity + primary key, + created_at timestamp(6) not null, + updated_at timestamp(6) not null, + post_id bigint not null + references posts, + user_id bigint not null + references users, + constraint uk_post_like_post_user + unique (post_id, user_id) +); + +CREATE TABLE IF NOT EXISTS record_posts +( + id bigint not null + primary key + references posts, + hiking_record_id bigint not null + references hiking_records +); From d0db3c531eae001402408ac72ff2f30cda4400e1 Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 21:37:32 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20application.yaml=20redis=20=EB=93=A4?= =?UTF-8?q?=EC=97=AC=EC=93=B0=EA=B8=B0=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index c77270aa..17892458 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -22,6 +22,10 @@ spring: enabled: true baseline-on-migrate: true baseline-version: 1 + data: + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} jwt: secret: ${JWT_SECRET} @@ -34,11 +38,6 @@ kakao: client-secret: ${KAKAO_CLIENT_SECRET} admin-key: ${KAKAO_ADMIN_KEY} - data: - redis: - host: ${REDIS_HOST} - port: ${REDIS_PORT} - tracking: stream-key: ${TRACKING_STREAM_KEY} consumer-group: ${TRACKING_CONSUMER_GROUP} From c37301be13d1bc1eb515b38eafa48c54248ca0d9 Mon Sep 17 00:00:00 2001 From: pooreumjung Date: Sun, 17 May 2026 22:32:07 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20VI=5F=5FINIT.sql=EC=97=90=EC=84=9C?= =?UTF-8?q?=20user=ED=85=8C=EC=9D=B4=EB=B8=94=20refresh=5Ftoken=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/db/migration/V1__INIT.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/db/migration/V1__INIT.sql b/src/main/resources/db/migration/V1__INIT.sql index 8e3efcb5..4703e6d1 100644 --- a/src/main/resources/db/migration/V1__INIT.sql +++ b/src/main/resources/db/migration/V1__INIT.sql @@ -47,7 +47,6 @@ CREATE TABLE IF NOT EXISTS users nickname varchar(30), birth_date date, height double precision, - refresh_token varchar(512), constraint users_pk unique (oauth_id, oauth_provider) );