diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..c3f502a
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 디폴트 무시된 파일
+/shelf/
+/workspace.xml
+# 에디터 기반 HTTP 클라이언트 요청
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/Spring.iml b/.idea/Spring.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/Spring.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..23e1696
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml
new file mode 100644
index 0000000..4ea72a9
--- /dev/null
+++ b/.idea/copilot.data.migration.agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml
new file mode 100644
index 0000000..7ef04e2
--- /dev/null
+++ b/.idea/copilot.data.migration.ask.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml
new file mode 100644
index 0000000..1f2ea11
--- /dev/null
+++ b/.idea/copilot.data.migration.ask2agent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml
new file mode 100644
index 0000000..8648f94
--- /dev/null
+++ b/.idea/copilot.data.migration.edit.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..100ada2
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ mysql.8
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306/market
+
+
+
+
+
+
+ $ProjectFileDir$
+
+
+ mysql.8
+ true
+ true
+ com.mysql.cj.jdbc.Driver
+ ${DB_URL}
+
+
+
+
+
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..1a324e0
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..fdc392f
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..1edb17c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..374fdf8
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules/demo.main.iml b/.idea/modules/demo.main.iml
new file mode 100644
index 0000000..8b055e7
--- /dev/null
+++ b/.idea/modules/demo.main.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..dbac908
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..7f2e21e
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index ea5e249..2fcdc14 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ DKU UMC Spring Boot 스터디
| 이승준 | 정윤철 | 설원준 | 윤영주 | 고희연 | 고경수 | 구현모 |
| :------: | :------: | :------: | :------: | :------: | :------: | :------: |
-| [이승준](https://github.com/hilees) | [정윤철](https://github.com/Yoonchulchung) | [설원준]() | [윤영주]() | [고희연]() | [고경수]() | [구현모]() |
+| [이승준](https://github.com/hilees) | [정윤철](https://github.com/Yoonchulchung) | [설원준]() | [윤영주](https://github.com/som141) | [고희연]() | [고경수]() | [구현모]() |
diff --git a/week0/ddl.sql b/week0/ddl.sql
new file mode 100644
index 0000000..e2a8515
--- /dev/null
+++ b/week0/ddl.sql
@@ -0,0 +1,151 @@
+
+CREATE TABLE favorate_food
+(
+ id long NOT NULL COMMENT '유저선호음식코드',
+ food_id long NOT NULL COMMENT '음식 카테고리를 정하고 함',
+ user_id long NOT NULL COMMENT '유저 (id)db내에서의 불변고유식별자',
+ PRIMARY KEY (id)
+) COMMENT '선호음식';
+
+CREATE TABLE food
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '음식 카테고리를 정하고 함',
+ name VARCHAR(40) NOT NULL COMMENT '음식카테고리 이름',
+ PRIMARY KEY (id)
+) COMMENT '푸드카테고리';
+
+CREATE TABLE foodmarket
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '가게 id',
+ name VARCHAR(40) NOT NULL COMMENT '상호명(유니크는 아님)',
+ address VARCHAR(50) NOT NULL COMMENT '가게주소',
+ open BOOLEAN NOT NULL COMMENT '영업중이냐?',
+ content VARCHAR(200) NOT NULL COMMENT '가계 내용',
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE mission
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '고유미션_id',
+ market_id long NOT NULL COMMENT '가게 id',
+ content VARCHAR(100) NOT NULL COMMENT '미션내용',
+ mission_point INTEGER NOT NULL COMMENT '미션포인트',
+ PRIMARY KEY (id)
+) COMMENT '마켓과 유저 사이의 미션';
+
+CREATE TABLE mission+user
+(
+ id long NOT NULL DEFAULT autoincrement,
+ mission_id long NOT NULL COMMENT '고유미션_id',
+ user_id long NOT NULL COMMENT '유저 (id)db내에서의 불변고유식별자',
+ content VARCHAR NOT NULL COMMENT '새 미션에 대한 내용',
+ PRIMARY KEY (id)
+) COMMENT '신규 미션 받아라 유저!';
+
+CREATE TABLE permission_mission
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '유저 미션할당번호(이거 사장님한테 보여주면 댐)',
+ user_id long NOT NULL COMMENT '유저 (id)db내에서의 불변고유식별자',
+ mission_id long NOT NULL COMMENT '고유미션_id',
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE review
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '리뷰 고유(id)',
+ user_id long NOT NULL COMMENT '유저 (id)db내에서의 불변고유식별자',
+ market_id long NOT NULL COMMENT '가게 id',
+ content VARCHAR(100) NOT NULL COMMENT '리뷰내용',
+ star FLOAT NOT NULL COMMENT '0~5까지',
+ PRIMARY KEY (id)
+) COMMENT '마켓과 유저사이의 리뷰';
+
+CREATE TABLE review_alram
+(
+ id long NOT NULL DEFAULT autoincrement,
+ user_id long NOT NULL COMMENT '가게 id',
+ foodmarket_id long NOT NULL COMMENT '유저 (id)db내에서의 불변고유식별자',
+ content VARCHAR NOT NULL COMMENT '알람내용(리뷰관련 내용)',
+ PRIMARY KEY (id)
+) COMMENT '리뷰해주세용';
+
+CREATE TABLE review_image
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '고유이미지_id',
+ review_id long NOT NULL COMMENT '리뷰 고유(id)',
+ url VARCHAR(200) NOT NULL COMMENT '이미지 uri',
+ PRIMARY KEY (id)
+) COMMENT '리뷰에 쓰이는 이미지들';
+
+CREATE TABLE user
+(
+ id long NOT NULL DEFAULT autoincrement COMMENT '유저 (id)db내에서의 불변고유식별자',
+ email VARCHAR(50) NOT NULL COMMENT '로그인할때 이메일(유니크 속성주기)',
+ password VARCHAR(30) NULL COMMENT '8자리이상 영문 숫자 특수문자 조합하셈',
+ name VARCHAR(20) NOT NULL COMMENT '이름임',
+ gender INTEGER NOT NULL COMMENT '남자0,여자1, 제삼의 성(?)3',
+ birth DATETIME NOT NULL COMMENT '8자리',
+ address VARCHAR(50) NOT NULL COMMENT '주소',
+ point INTEGER NOT NULL COMMENT '총 point',
+ PRIMARY KEY (id)
+) COMMENT 'user_information';
+
+ALTER TABLE review_image
+ ADD CONSTRAINT FK_review_TO_review_image
+ FOREIGN KEY (review_id)
+ REFERENCES review (id);
+
+ALTER TABLE permission_mission
+ ADD CONSTRAINT FK_user_TO_permission_mission
+ FOREIGN KEY (user_id)
+ REFERENCES user (id);
+
+ALTER TABLE review
+ ADD CONSTRAINT FK_foodmarket_TO_review
+ FOREIGN KEY (market_id)
+ REFERENCES foodmarket (id);
+
+ALTER TABLE review
+ ADD CONSTRAINT FK_user_TO_review
+ FOREIGN KEY (user_id)
+ REFERENCES user (id);
+
+ALTER TABLE mission
+ ADD CONSTRAINT FK_foodmarket_TO_mission
+ FOREIGN KEY (market_id)
+ REFERENCES foodmarket (id);
+
+ALTER TABLE permission_mission
+ ADD CONSTRAINT FK_mission_TO_permission_mission
+ FOREIGN KEY (mission_id)
+ REFERENCES mission (id);
+
+ALTER TABLE favorate_food
+ ADD CONSTRAINT FK_food_TO_favorate_food
+ FOREIGN KEY (food_id)
+ REFERENCES food (id);
+
+ALTER TABLE favorate_food
+ ADD CONSTRAINT FK_user_TO_favorate_food
+ FOREIGN KEY (user_id)
+ REFERENCES user (id);
+
+ALTER TABLE review_alram
+ ADD CONSTRAINT FK_foodmarket_TO_review_alram
+ FOREIGN KEY (user_id)
+ REFERENCES foodmarket (id);
+
+ALTER TABLE review_alram
+ ADD CONSTRAINT FK_user_TO_review_alram
+ FOREIGN KEY (foodmarket_id)
+ REFERENCES user (id);
+
+ALTER TABLE mission+user
+ ADD CONSTRAINT FK_mission_TO_mission+user
+ FOREIGN KEY (mission_id)
+ REFERENCES mission (id);
+
+ALTER TABLE mission+user
+ ADD CONSTRAINT FK_user_TO_mission+user
+ FOREIGN KEY (user_id)
+ REFERENCES user (id);
diff --git a/week0/erd.vuerd.json b/week0/erd.vuerd.json
new file mode 100644
index 0000000..0501b0f
--- /dev/null
+++ b/week0/erd.vuerd.json
@@ -0,0 +1,2265 @@
+{
+ "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json",
+ "version": "3.0.0",
+ "settings": {
+ "width": 2000,
+ "height": 2000,
+ "scrollTop": -201.8457,
+ "scrollLeft": -140.64,
+ "zoomLevel": 0.85,
+ "show": 431,
+ "database": 4,
+ "databaseName": "",
+ "canvasType": "@dineug/erd-editor/builtin-schema-sql",
+ "language": 1,
+ "tableNameCase": 4,
+ "columnNameCase": 2,
+ "bracketType": 1,
+ "relationshipDataTypeSync": true,
+ "relationshipOptimization": false,
+ "columnOrder": [
+ 1,
+ 2,
+ 4,
+ 8,
+ 16,
+ 32,
+ 64
+ ],
+ "maxWidthComment": -1,
+ "ignoreSaveSettings": 0
+ },
+ "doc": {
+ "tableIds": [
+ "1h1BgNxEvKkk6gEY5CMPg",
+ "jAhMpDau6eAWbR8ifxIk7",
+ "U1KhhqX8xZco_6X76lUUF",
+ "eOdf64btd3jXZ3odam357",
+ "P7FiH9nZLSpro47ZFRs82",
+ "BrlSmoR0vOA5wp7DxG5we",
+ "0JvjupyrC_HXBhFN_shc4",
+ "spDJoG67LKxqns5k01Sw8",
+ "gb4iYsr3YyVUrKqBUXFw4",
+ "yoRJ0I0phZM05vvjpplpG"
+ ],
+ "relationshipIds": [
+ "95LGlwc5ro-THdZsaS9E_",
+ "-CCfH3cvSxvdbUzVIjRqx",
+ "3NAwaUh04XxCu9WtiJZGQ",
+ "hsUPR9Uagl9UsCj3_v7-D",
+ "TaYFbfPGvec7SZetfjcbm",
+ "nrUSGKD4XjLLYG8gtNeX0",
+ "rRR3UlrXRccW3-FvusZwo",
+ "lF_M2fq7NZ-MM9bQ_QFa9",
+ "Zh4XQbVDVYrT3m77ypVoQ",
+ "28vE14zEXeDiQ9WZAlOgr",
+ "Fl9pEIK2AlrkViNySHsbU",
+ "75cGtrFxrlxv8h9HNbs_x"
+ ],
+ "indexIds": [],
+ "memoIds": []
+ },
+ "collections": {
+ "tableEntities": {
+ "XWGdk108BNIBTS4E5LyA-": {
+ "id": "XWGdk108BNIBTS4E5LyA-",
+ "name": "user",
+ "comment": "유저임",
+ "columnIds": [
+ "HZU82lNKDOrgajhOG536V",
+ "t_RLBWhrZFUxlJ5bjRnfk",
+ "0kcH_8Z8tm3Ga1etxwa8U",
+ "9MBWULvb4IO29t17QfULg",
+ "4tkb0skf9LlAChFmM0d2q"
+ ],
+ "seqColumnIds": [
+ "HZU82lNKDOrgajhOG536V",
+ "t_RLBWhrZFUxlJ5bjRnfk",
+ "0kcH_8Z8tm3Ga1etxwa8U",
+ "C4drmy-Oz1fH_pvwRy2ya",
+ "9MBWULvb4IO29t17QfULg",
+ "4tkb0skf9LlAChFmM0d2q"
+ ],
+ "ui": {
+ "x": 173,
+ "y": 262,
+ "zIndex": 102,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758383326408,
+ "createAt": 1758064771910
+ }
+ },
+ "1h1BgNxEvKkk6gEY5CMPg": {
+ "id": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "user",
+ "comment": "user_information",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4",
+ "VT6dpeOGqyA1I7KQ6b4mK",
+ "R09EvHSVhLqVeSYQg8Dqa",
+ "sRdypptEf1G5rSsm6FX6g",
+ "k5QSZS8WtQ23EvpJsMuyy",
+ "4I1ptgrrjPfZ7kwaNL-gI",
+ "9YJk76nhsU2pQJHAgGuCQ",
+ "TdPHSn_WW5ipoJJugiMDr"
+ ],
+ "seqColumnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4",
+ "VT6dpeOGqyA1I7KQ6b4mK",
+ "R09EvHSVhLqVeSYQg8Dqa",
+ "sRdypptEf1G5rSsm6FX6g",
+ "k5QSZS8WtQ23EvpJsMuyy",
+ "4I1ptgrrjPfZ7kwaNL-gI",
+ "9YJk76nhsU2pQJHAgGuCQ",
+ "TdPHSn_WW5ipoJJugiMDr"
+ ],
+ "ui": {
+ "x": 37.9412,
+ "y": 715.9412,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 91,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758522460041,
+ "createAt": 1758383331119
+ }
+ },
+ "jAhMpDau6eAWbR8ifxIk7": {
+ "id": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "foodmarket",
+ "comment": "",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44",
+ "I-JEAuVy-GEHNuhd_R1Fl",
+ "sqis3p25lk_He3rwnCuDc",
+ "O0ry03x6NOpDvBTK0CMbb",
+ "cU-E1DpewiYVt3r5W3J67"
+ ],
+ "seqColumnIds": [
+ "Zq1avcoEnaVsfbppogJ44",
+ "I-JEAuVy-GEHNuhd_R1Fl",
+ "sqis3p25lk_He3rwnCuDc",
+ "O0ry03x6NOpDvBTK0CMbb",
+ "frGgeCTDN_R-BO-pHTIZU",
+ "qQ4TPs1xEDtsaN2F69b8n",
+ "rWUlSO-Zx2_CzblYkScil",
+ "cU-E1DpewiYVt3r5W3J67",
+ "dUWHJDNXCbHc3eEVauegi"
+ ],
+ "ui": {
+ "x": 1469,
+ "y": 363,
+ "zIndex": 9,
+ "widthName": 64,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758521924972,
+ "createAt": 1758383344116
+ }
+ },
+ "U1KhhqX8xZco_6X76lUUF": {
+ "id": "U1KhhqX8xZco_6X76lUUF",
+ "name": "review",
+ "comment": "마켓과 유저사이의 리뷰",
+ "columnIds": [
+ "icCcVDsFAlzlq4FjE0ubK",
+ "r28FAr4NRktLF6jeinkQI",
+ "CVbY3U1IRbHYIv2_ABSi8",
+ "K2GSuWKKwoSYl0WoAcju0",
+ "TL3kiOfuBdtpAHnnAtq7x"
+ ],
+ "seqColumnIds": [
+ "5rLifUB24BKsa7wxaS7UE",
+ "g3rYiPsqjN477uVH0qkLg",
+ "icCcVDsFAlzlq4FjE0ubK",
+ "r2POd-u5Q9w3HwIM7oZpS",
+ "VbAABiEWDtaW-BR5OX5e7",
+ "r28FAr4NRktLF6jeinkQI",
+ "CVbY3U1IRbHYIv2_ABSi8",
+ "K2GSuWKKwoSYl0WoAcju0",
+ "TL3kiOfuBdtpAHnnAtq7x"
+ ],
+ "ui": {
+ "x": 719,
+ "y": 363,
+ "zIndex": 25,
+ "widthName": 60,
+ "widthComment": 129,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758521507399,
+ "createAt": 1758383404844
+ }
+ },
+ "eOdf64btd3jXZ3odam357": {
+ "id": "eOdf64btd3jXZ3odam357",
+ "name": "mission",
+ "comment": "마켓과 유저 사이의 미션",
+ "columnIds": [
+ "ReQt_SxQicmPN6FInTteO",
+ "db5bXfLzqmyQYh_JjRPiy",
+ "bUJ9zMEJqs9SvY5IKsqc6",
+ "9WTFvYP-wRigq5OnVHaBt"
+ ],
+ "seqColumnIds": [
+ "ReQt_SxQicmPN6FInTteO",
+ "db5bXfLzqmyQYh_JjRPiy",
+ "aI9MAW7Hiyy_vI2M7Pm0-",
+ "8rJpsU0PXxwff0XAgxqMm",
+ "SBNOyYuMRXvVKFCbagHDJ",
+ "bUJ9zMEJqs9SvY5IKsqc6",
+ "9WTFvYP-wRigq5OnVHaBt",
+ "ldych5z2WqoYn7KrDYRkC"
+ ],
+ "ui": {
+ "x": 1492,
+ "y": 832,
+ "zIndex": 29,
+ "widthName": 60,
+ "widthComment": 132,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758522109141,
+ "createAt": 1758383411510
+ }
+ },
+ "fF0hgBzZeatjUrv0tbY6I": {
+ "id": "fF0hgBzZeatjUrv0tbY6I",
+ "name": "",
+ "comment": "",
+ "columnIds": [],
+ "seqColumnIds": [],
+ "ui": {
+ "x": 200,
+ "y": 110,
+ "zIndex": 291,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758385063879,
+ "createAt": 1758385063879
+ }
+ },
+ "P7FiH9nZLSpro47ZFRs82": {
+ "id": "P7FiH9nZLSpro47ZFRs82",
+ "name": "review_image",
+ "comment": "리뷰에 쓰이는 이미지들",
+ "columnIds": [
+ "46mPj-jrSqP7EJVOWDkx6",
+ "TuNRq7Dmvw9HkKmJzmHyA",
+ "JpL2_wJdOTerJ5aBqveT3"
+ ],
+ "seqColumnIds": [
+ "46mPj-jrSqP7EJVOWDkx6",
+ "TuNRq7Dmvw9HkKmJzmHyA",
+ "JpL2_wJdOTerJ5aBqveT3"
+ ],
+ "ui": {
+ "x": 778,
+ "y": 77,
+ "zIndex": 365,
+ "widthName": 74,
+ "widthComment": 129,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758452632100,
+ "createAt": 1758385868614
+ }
+ },
+ "rg9wZvwotSRlkEuHNiT-a": {
+ "id": "rg9wZvwotSRlkEuHNiT-a",
+ "name": "a",
+ "comment": "",
+ "columnIds": [],
+ "seqColumnIds": [],
+ "ui": {
+ "x": 158,
+ "y": 145,
+ "zIndex": 447,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758453350856,
+ "createAt": 1758453328467
+ }
+ },
+ "GNTqlOzVMcjrNhTKrLaOf": {
+ "id": "GNTqlOzVMcjrNhTKrLaOf",
+ "name": "",
+ "comment": "",
+ "columnIds": [],
+ "seqColumnIds": [],
+ "ui": {
+ "x": 229,
+ "y": 771,
+ "zIndex": 448,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758471120574,
+ "createAt": 1758471113180
+ }
+ },
+ "XWcAkkKP3pemRFkafGh7s": {
+ "id": "XWcAkkKP3pemRFkafGh7s",
+ "name": "",
+ "comment": "",
+ "columnIds": [],
+ "seqColumnIds": [],
+ "ui": {
+ "x": 200,
+ "y": 102,
+ "zIndex": 374,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758520909487,
+ "createAt": 1758520909487
+ }
+ },
+ "BrlSmoR0vOA5wp7DxG5we": {
+ "id": "BrlSmoR0vOA5wp7DxG5we",
+ "name": "permission_mission",
+ "comment": "",
+ "columnIds": [
+ "D831T8cqOSlCjjniUaDc2",
+ "Zl7soLlH7_f8dROOoajF6",
+ "7uVQuguLC9lssgHo5xhLG"
+ ],
+ "seqColumnIds": [
+ "D831T8cqOSlCjjniUaDc2",
+ "Zl7soLlH7_f8dROOoajF6",
+ "CkI_d-LBnoNOjBGw3ay_0",
+ "7uVQuguLC9lssgHo5xhLG"
+ ],
+ "ui": {
+ "x": 693,
+ "y": 848,
+ "zIndex": 378,
+ "widthName": 105,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758522022162,
+ "createAt": 1758520950080
+ }
+ },
+ "0JvjupyrC_HXBhFN_shc4": {
+ "id": "0JvjupyrC_HXBhFN_shc4",
+ "name": "review_alram",
+ "comment": "리뷰해주세용",
+ "columnIds": [
+ "_bry5dwUtsSl6H9ydBcm1",
+ "bHXNv5MX53K6nr7N3Q8eK",
+ "xUscWzzz5Gz5EdHQRfk_N",
+ "zpzXrlmb7ooqirK9DmIXM"
+ ],
+ "seqColumnIds": [
+ "_bry5dwUtsSl6H9ydBcm1",
+ "bHXNv5MX53K6nr7N3Q8eK",
+ "xUscWzzz5Gz5EdHQRfk_N",
+ "zpzXrlmb7ooqirK9DmIXM"
+ ],
+ "ui": {
+ "x": 766.2355,
+ "y": 592.1762,
+ "zIndex": 401,
+ "widthName": 71,
+ "widthComment": 74,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758523203551,
+ "createAt": 1758521232356
+ }
+ },
+ "spDJoG67LKxqns5k01Sw8": {
+ "id": "spDJoG67LKxqns5k01Sw8",
+ "name": "food",
+ "comment": "푸드카테고리",
+ "columnIds": [
+ "hxvxT750NkNHHEtYpUicL",
+ "s7jRm5AKMPwQqU88tFn6B"
+ ],
+ "seqColumnIds": [
+ "hxvxT750NkNHHEtYpUicL",
+ "s7jRm5AKMPwQqU88tFn6B"
+ ],
+ "ui": {
+ "x": 79.9999,
+ "y": 209.2066,
+ "zIndex": 487,
+ "widthName": 60,
+ "widthComment": 74,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758522508144,
+ "createAt": 1758522257974
+ }
+ },
+ "gb4iYsr3YyVUrKqBUXFw4": {
+ "id": "gb4iYsr3YyVUrKqBUXFw4",
+ "name": "favorate_food",
+ "comment": "선호음식",
+ "columnIds": [
+ "tVkpMHG_0E5JLY_yJf3c9",
+ "MbXpR0Vw0Si2O1rM9QRbA",
+ "TgQ6AVIBficrVrE_W4Sk2"
+ ],
+ "seqColumnIds": [
+ "tVkpMHG_0E5JLY_yJf3c9",
+ "MbXpR0Vw0Si2O1rM9QRbA",
+ "TgQ6AVIBficrVrE_W4Sk2"
+ ],
+ "ui": {
+ "x": 67.0591,
+ "y": 491.5598,
+ "zIndex": 530,
+ "widthName": 75,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758522510966,
+ "createAt": 1758522378614
+ }
+ },
+ "yoRJ0I0phZM05vvjpplpG": {
+ "id": "yoRJ0I0phZM05vvjpplpG",
+ "name": "mission+user",
+ "comment": "신규 미션 받아라 유저!",
+ "columnIds": [
+ "zVhoZPYp_b61QSqtMvvQq",
+ "qOaGuBBg_Wfz0JKwfaKUe",
+ "8U14FBzF_FqWWddLVhfeB",
+ "zJlYvdBeyNcIvgcKSBk4w"
+ ],
+ "seqColumnIds": [
+ "zVhoZPYp_b61QSqtMvvQq",
+ "qOaGuBBg_Wfz0JKwfaKUe",
+ "8U14FBzF_FqWWddLVhfeB",
+ "zJlYvdBeyNcIvgcKSBk4w"
+ ],
+ "ui": {
+ "x": 721.1765,
+ "y": 1039.7949,
+ "zIndex": 581,
+ "widthName": 73,
+ "widthComment": 123,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758528597424,
+ "createAt": 1758523047319
+ }
+ },
+ "rDtD8_KsF_UXR7P4hrBJt": {
+ "id": "rDtD8_KsF_UXR7P4hrBJt",
+ "name": "",
+ "comment": "",
+ "columnIds": [],
+ "seqColumnIds": [],
+ "ui": {
+ "x": 58.823529411764696,
+ "y": 185.67729411764702,
+ "zIndex": 590,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1758523083601,
+ "createAt": 1758523083601
+ }
+ }
+ },
+ "tableColumnEntities": {
+ "HZU82lNKDOrgajhOG536V": {
+ "id": "HZU82lNKDOrgajhOG536V",
+ "tableId": "XWGdk108BNIBTS4E5LyA-",
+ "name": "id",
+ "comment": "고유식별자임",
+ "dataType": "INTEGER",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 74,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758064848008,
+ "createAt": 1758064781776
+ }
+ },
+ "t_RLBWhrZFUxlJ5bjRnfk": {
+ "id": "t_RLBWhrZFUxlJ5bjRnfk",
+ "tableId": "XWGdk108BNIBTS4E5LyA-",
+ "name": "name",
+ "comment": "있어야함",
+ "dataType": "VARCHAR",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758064876275,
+ "createAt": 1758064857525
+ }
+ },
+ "0kcH_8Z8tm3Ga1etxwa8U": {
+ "id": "0kcH_8Z8tm3Ga1etxwa8U",
+ "tableId": "XWGdk108BNIBTS4E5LyA-",
+ "name": "kakao",
+ "comment": "디폴트는 false",
+ "dataType": "BOOLEAN",
+ "default": "false",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 77,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758064966134,
+ "createAt": 1758064876276
+ }
+ },
+ "C4drmy-Oz1fH_pvwRy2ya": {
+ "id": "C4drmy-Oz1fH_pvwRy2ya",
+ "tableId": "XWGdk108BNIBTS4E5LyA-",
+ "name": "",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758065019725,
+ "createAt": 1758065009459
+ }
+ },
+ "9MBWULvb4IO29t17QfULg": {
+ "id": "9MBWULvb4IO29t17QfULg",
+ "tableId": "XWGdk108BNIBTS4E5LyA-",
+ "name": "create_time",
+ "comment": "생성시간",
+ "dataType": "DATETIME(6)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 63,
+ "widthComment": 60,
+ "widthDataType": 69,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758065275286,
+ "createAt": 1758065274645
+ }
+ },
+ "4tkb0skf9LlAChFmM0d2q": {
+ "id": "4tkb0skf9LlAChFmM0d2q",
+ "tableId": "XWGdk108BNIBTS4E5LyA-",
+ "name": "update_time",
+ "comment": "업데이트 타임",
+ "dataType": "DATETIME(6)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 68,
+ "widthComment": 77,
+ "widthDataType": 69,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758065275286,
+ "createAt": 1758065275286
+ }
+ },
+ "Gh8Azk72nGvgic4QJ8XZ4": {
+ "id": "Gh8Azk72nGvgic4QJ8XZ4",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758385536289,
+ "createAt": 1758383453193
+ }
+ },
+ "sRdypptEf1G5rSsm6FX6g": {
+ "id": "sRdypptEf1G5rSsm6FX6g",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "name",
+ "comment": "이름임",
+ "dataType": "VARCHAR(20)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758383677590,
+ "createAt": 1758383651007
+ }
+ },
+ "k5QSZS8WtQ23EvpJsMuyy": {
+ "id": "k5QSZS8WtQ23EvpJsMuyy",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "gender",
+ "comment": "남자0,여자1, 제삼의 성(?)3",
+ "dataType": "INTEGER",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 142,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384280668,
+ "createAt": 1758383677591
+ }
+ },
+ "4I1ptgrrjPfZ7kwaNL-gI": {
+ "id": "4I1ptgrrjPfZ7kwaNL-gI",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "birth",
+ "comment": "8자리",
+ "dataType": "DATETIME",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384114992,
+ "createAt": 1758383799994
+ }
+ },
+ "9YJk76nhsU2pQJHAgGuCQ": {
+ "id": "9YJk76nhsU2pQJHAgGuCQ",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "address",
+ "comment": "주소",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384111863,
+ "createAt": 1758383858077
+ }
+ },
+ "VT6dpeOGqyA1I7KQ6b4mK": {
+ "id": "VT6dpeOGqyA1I7KQ6b4mK",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "email",
+ "comment": "로그인할때 이메일(유니크 속성주기)",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384244390,
+ "createAt": 1758383923497
+ }
+ },
+ "R09EvHSVhLqVeSYQg8Dqa": {
+ "id": "R09EvHSVhLqVeSYQg8Dqa",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "password",
+ "comment": "8자리이상 영문 숫자 특수문자 조합하셈",
+ "dataType": "VARCHAR(30)",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 214,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384159973,
+ "createAt": 1758384128863
+ }
+ },
+ "Zq1avcoEnaVsfbppogJ44": {
+ "id": "Zq1avcoEnaVsfbppogJ44",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758384854893,
+ "createAt": 1758384260781
+ }
+ },
+ "I-JEAuVy-GEHNuhd_R1Fl": {
+ "id": "I-JEAuVy-GEHNuhd_R1Fl",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "name",
+ "comment": "상호명(유니크는 아님)",
+ "dataType": "VARCHAR(40)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 121,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384675963,
+ "createAt": 1758384515354
+ }
+ },
+ "sqis3p25lk_He3rwnCuDc": {
+ "id": "sqis3p25lk_He3rwnCuDc",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "address",
+ "comment": "가게주소",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384684669,
+ "createAt": 1758384640113
+ }
+ },
+ "O0ry03x6NOpDvBTK0CMbb": {
+ "id": "O0ry03x6NOpDvBTK0CMbb",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "open",
+ "comment": "영업중이냐?",
+ "dataType": "BOOLEAN",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 67,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384720579,
+ "createAt": 1758384684669
+ }
+ },
+ "frGgeCTDN_R-BO-pHTIZU": {
+ "id": "frGgeCTDN_R-BO-pHTIZU",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "star",
+ "comment": "",
+ "dataType": "FLOAT",
+ "default": "0",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384751181,
+ "createAt": 1758384724228
+ }
+ },
+ "qQ4TPs1xEDtsaN2F69b8n": {
+ "id": "qQ4TPs1xEDtsaN2F69b8n",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "mission",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384908134,
+ "createAt": 1758384888313
+ }
+ },
+ "5rLifUB24BKsa7wxaS7UE": {
+ "id": "5rLifUB24BKsa7wxaS7UE",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758384978019,
+ "createAt": 1758384978019
+ }
+ },
+ "g3rYiPsqjN477uVH0qkLg": {
+ "id": "g3rYiPsqjN477uVH0qkLg",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384989514,
+ "createAt": 1758384989514
+ }
+ },
+ "rWUlSO-Zx2_CzblYkScil": {
+ "id": "rWUlSO-Zx2_CzblYkScil",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758384989952,
+ "createAt": 1758384989952
+ }
+ },
+ "r2POd-u5Q9w3HwIM7oZpS": {
+ "id": "r2POd-u5Q9w3HwIM7oZpS",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "market_id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758385507835,
+ "createAt": 1758385052865
+ }
+ },
+ "VbAABiEWDtaW-BR5OX5e7": {
+ "id": "VbAABiEWDtaW-BR5OX5e7",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "user_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758385488940,
+ "createAt": 1758385081817
+ }
+ },
+ "icCcVDsFAlzlq4FjE0ubK": {
+ "id": "icCcVDsFAlzlq4FjE0ubK",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "id",
+ "comment": "리뷰 고유(id)",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 71,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758385549054,
+ "createAt": 1758385470718
+ }
+ },
+ "K2GSuWKKwoSYl0WoAcju0": {
+ "id": "K2GSuWKKwoSYl0WoAcju0",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "content",
+ "comment": "리뷰내용",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758385732751,
+ "createAt": 1758385622322
+ }
+ },
+ "TL3kiOfuBdtpAHnnAtq7x": {
+ "id": "TL3kiOfuBdtpAHnnAtq7x",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "star",
+ "comment": "0~5까지",
+ "dataType": "FLOAT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758386182875,
+ "createAt": 1758385737635
+ }
+ },
+ "46mPj-jrSqP7EJVOWDkx6": {
+ "id": "46mPj-jrSqP7EJVOWDkx6",
+ "tableId": "P7FiH9nZLSpro47ZFRs82",
+ "name": "id",
+ "comment": "고유이미지_id",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 77,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758452601618,
+ "createAt": 1758385893212
+ }
+ },
+ "TuNRq7Dmvw9HkKmJzmHyA": {
+ "id": "TuNRq7Dmvw9HkKmJzmHyA",
+ "tableId": "P7FiH9nZLSpro47ZFRs82",
+ "name": "review_id",
+ "comment": "리뷰 고유(id)",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 71,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758386198028,
+ "createAt": 1758385949681
+ }
+ },
+ "cU-E1DpewiYVt3r5W3J67": {
+ "id": "cU-E1DpewiYVt3r5W3J67",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "content",
+ "comment": "가계 내용",
+ "dataType": "VARCHAR(200)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758386124234,
+ "createAt": 1758386101996
+ }
+ },
+ "JpL2_wJdOTerJ5aBqveT3": {
+ "id": "JpL2_wJdOTerJ5aBqveT3",
+ "tableId": "P7FiH9nZLSpro47ZFRs82",
+ "name": "url",
+ "comment": "이미지 uri",
+ "dataType": "VARCHAR(200)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758386311629,
+ "createAt": 1758386284163
+ }
+ },
+ "aI9MAW7Hiyy_vI2M7Pm0-": {
+ "id": "aI9MAW7Hiyy_vI2M7Pm0-",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "market_id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758452704017,
+ "createAt": 1758452663070
+ }
+ },
+ "8rJpsU0PXxwff0XAgxqMm": {
+ "id": "8rJpsU0PXxwff0XAgxqMm",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "user_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758452706979,
+ "createAt": 1758452672201
+ }
+ },
+ "ReQt_SxQicmPN6FInTteO": {
+ "id": "ReQt_SxQicmPN6FInTteO",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "id",
+ "comment": "고유미션_id",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 65,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758452735615,
+ "createAt": 1758452682271
+ }
+ },
+ "SBNOyYuMRXvVKFCbagHDJ": {
+ "id": "SBNOyYuMRXvVKFCbagHDJ",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "identifyed_number",
+ "comment": "고유 미션 번호(서로 확인 절차)",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 101,
+ "widthComment": 166,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758452829093,
+ "createAt": 1758452765615
+ }
+ },
+ "TdPHSn_WW5ipoJJugiMDr": {
+ "id": "TdPHSn_WW5ipoJJugiMDr",
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "name": "point",
+ "comment": "총 point",
+ "dataType": "INTEGER",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758453236942,
+ "createAt": 1758453214516
+ }
+ },
+ "bUJ9zMEJqs9SvY5IKsqc6": {
+ "id": "bUJ9zMEJqs9SvY5IKsqc6",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "content",
+ "comment": "미션내용",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758453268496,
+ "createAt": 1758453244654
+ }
+ },
+ "9WTFvYP-wRigq5OnVHaBt": {
+ "id": "9WTFvYP-wRigq5OnVHaBt",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "mission_point",
+ "comment": "미션포인트",
+ "dataType": "INTEGER",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 75,
+ "widthComment": 62,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758453321214,
+ "createAt": 1758453268497
+ }
+ },
+ "Zl7soLlH7_f8dROOoajF6": {
+ "id": "Zl7soLlH7_f8dROOoajF6",
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "name": "user_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758521084219,
+ "createAt": 1758521025058
+ }
+ },
+ "D831T8cqOSlCjjniUaDc2": {
+ "id": "D831T8cqOSlCjjniUaDc2",
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "name": "id",
+ "comment": "유저 미션할당번호(이거 사장님한테 보여주면 댐)",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 262,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758523290544,
+ "createAt": 1758521079088
+ }
+ },
+ "CkI_d-LBnoNOjBGw3ay_0": {
+ "id": "CkI_d-LBnoNOjBGw3ay_0",
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "name": "mission_id",
+ "comment": "고유미션_id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 65,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758521317800,
+ "createAt": 1758521244136
+ }
+ },
+ "dUWHJDNXCbHc3eEVauegi": {
+ "id": "dUWHJDNXCbHc3eEVauegi",
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "name": "id",
+ "comment": "리뷰 고유(id)",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 71,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758521399180,
+ "createAt": 1758521399180
+ }
+ },
+ "CVbY3U1IRbHYIv2_ABSi8": {
+ "id": "CVbY3U1IRbHYIv2_ABSi8",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "market_id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758521495607,
+ "createAt": 1758521413946
+ }
+ },
+ "r28FAr4NRktLF6jeinkQI": {
+ "id": "r28FAr4NRktLF6jeinkQI",
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "name": "user_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758521503092,
+ "createAt": 1758521432311
+ }
+ },
+ "db5bXfLzqmyQYh_JjRPiy": {
+ "id": "db5bXfLzqmyQYh_JjRPiy",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "market_id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522105219,
+ "createAt": 1758521673578
+ }
+ },
+ "7uVQuguLC9lssgHo5xhLG": {
+ "id": "7uVQuguLC9lssgHo5xhLG",
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "name": "mission_id",
+ "comment": "고유미션_id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 65,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522009610,
+ "createAt": 1758521996327
+ }
+ },
+ "ldych5z2WqoYn7KrDYRkC": {
+ "id": "ldych5z2WqoYn7KrDYRkC",
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "name": "point",
+ "comment": "",
+ "dataType": "INTEGER",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522098199,
+ "createAt": 1758522087155
+ }
+ },
+ "hxvxT750NkNHHEtYpUicL": {
+ "id": "hxvxT750NkNHHEtYpUicL",
+ "tableId": "spDJoG67LKxqns5k01Sw8",
+ "name": "id",
+ "comment": "음식 카테고리를 정하고 함",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 144,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758522355077,
+ "createAt": 1758522293697
+ }
+ },
+ "s7jRm5AKMPwQqU88tFn6B": {
+ "id": "s7jRm5AKMPwQqU88tFn6B",
+ "tableId": "spDJoG67LKxqns5k01Sw8",
+ "name": "name",
+ "comment": "음식카테고리 이름",
+ "dataType": "VARCHAR(40)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 101,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522377137,
+ "createAt": 1758522326861
+ }
+ },
+ "MbXpR0Vw0Si2O1rM9QRbA": {
+ "id": "MbXpR0Vw0Si2O1rM9QRbA",
+ "tableId": "gb4iYsr3YyVUrKqBUXFw4",
+ "name": "food_id",
+ "comment": "음식 카테고리를 정하고 함",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 144,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522487576,
+ "createAt": 1758522454178
+ }
+ },
+ "TgQ6AVIBficrVrE_W4Sk2": {
+ "id": "TgQ6AVIBficrVrE_W4Sk2",
+ "tableId": "gb4iYsr3YyVUrKqBUXFw4",
+ "name": "user_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522485448,
+ "createAt": 1758522460392
+ }
+ },
+ "tVkpMHG_0E5JLY_yJf3c9": {
+ "id": "tVkpMHG_0E5JLY_yJf3c9",
+ "tableId": "gb4iYsr3YyVUrKqBUXFw4",
+ "name": "id",
+ "comment": "유저선호음식코드",
+ "dataType": "long",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 98,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758522500384,
+ "createAt": 1758522474856
+ }
+ },
+ "bHXNv5MX53K6nr7N3Q8eK": {
+ "id": "bHXNv5MX53K6nr7N3Q8eK",
+ "tableId": "0JvjupyrC_HXBhFN_shc4",
+ "name": "user_id",
+ "comment": "가게 id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758523147397,
+ "createAt": 1758523031524
+ }
+ },
+ "xUscWzzz5Gz5EdHQRfk_N": {
+ "id": "xUscWzzz5Gz5EdHQRfk_N",
+ "tableId": "0JvjupyrC_HXBhFN_shc4",
+ "name": "foodmarket_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 79,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758523157435,
+ "createAt": 1758523036391
+ }
+ },
+ "zVhoZPYp_b61QSqtMvvQq": {
+ "id": "zVhoZPYp_b61QSqtMvvQq",
+ "tableId": "yoRJ0I0phZM05vvjpplpG",
+ "name": "id",
+ "comment": "",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758523291783,
+ "createAt": 1758523066388
+ }
+ },
+ "qOaGuBBg_Wfz0JKwfaKUe": {
+ "id": "qOaGuBBg_Wfz0JKwfaKUe",
+ "tableId": "yoRJ0I0phZM05vvjpplpG",
+ "name": "mission_id",
+ "comment": "고유미션_id",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 65,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758523183284,
+ "createAt": 1758523081911
+ }
+ },
+ "8U14FBzF_FqWWddLVhfeB": {
+ "id": "8U14FBzF_FqWWddLVhfeB",
+ "tableId": "yoRJ0I0phZM05vvjpplpG",
+ "name": "user_id",
+ "comment": "유저 (id)db내에서의 불변고유식별자",
+ "dataType": "long",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 196,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758523185505,
+ "createAt": 1758523093065
+ }
+ },
+ "_bry5dwUtsSl6H9ydBcm1": {
+ "id": "_bry5dwUtsSl6H9ydBcm1",
+ "tableId": "0JvjupyrC_HXBhFN_shc4",
+ "name": "id",
+ "comment": "",
+ "dataType": "long",
+ "default": "autoincrement",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 79
+ },
+ "meta": {
+ "updateAt": 1758523141535,
+ "createAt": 1758523099958
+ }
+ },
+ "zpzXrlmb7ooqirK9DmIXM": {
+ "id": "zpzXrlmb7ooqirK9DmIXM",
+ "tableId": "0JvjupyrC_HXBhFN_shc4",
+ "name": "content",
+ "comment": "알람내용(리뷰관련 내용)",
+ "dataType": "VARCHAR",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 133,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758523238236,
+ "createAt": 1758523203551
+ }
+ },
+ "zJlYvdBeyNcIvgcKSBk4w": {
+ "id": "zJlYvdBeyNcIvgcKSBk4w",
+ "tableId": "yoRJ0I0phZM05vvjpplpG",
+ "name": "content",
+ "comment": "새 미션에 대한 내용",
+ "dataType": "VARCHAR",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 108,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1758523270003,
+ "createAt": 1758523247840
+ }
+ }
+ },
+ "relationshipEntities": {
+ "8Z53qVGZIj6OhsaZ9vGMf": {
+ "id": "8Z53qVGZIj6OhsaZ9vGMf",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44"
+ ],
+ "x": 1143,
+ "y": 242,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "5rLifUB24BKsa7wxaS7UE"
+ ],
+ "x": 1073,
+ "y": 166,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758384978019,
+ "createAt": 1758384978019
+ }
+ },
+ "0WvFCow4O2GNAcz8b4Dql": {
+ "id": "0WvFCow4O2GNAcz8b4Dql",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "g3rYiPsqjN477uVH0qkLg"
+ ],
+ "x": 1073,
+ "y": 178,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "rWUlSO-Zx2_CzblYkScil"
+ ],
+ "x": 1143,
+ "y": 254,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758384989952,
+ "createAt": 1758384989952
+ }
+ },
+ "d8XK9y1W2Y2Sm6T5bJTST": {
+ "id": "d8XK9y1W2Y2Sm6T5bJTST",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44"
+ ],
+ "x": 1469,
+ "y": 462,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "r2POd-u5Q9w3HwIM7oZpS"
+ ],
+ "x": 1260,
+ "y": 451,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758385052865,
+ "createAt": 1758385052865
+ }
+ },
+ "eiWfcJs5w-HFv5PEUBniY": {
+ "id": "eiWfcJs5w-HFv5PEUBniY",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 620,
+ "y": 789,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "VbAABiEWDtaW-BR5OX5e7"
+ ],
+ "x": 719,
+ "y": 451,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758385081817,
+ "createAt": 1758385081817
+ }
+ },
+ "95LGlwc5ro-THdZsaS9E_": {
+ "id": "95LGlwc5ro-THdZsaS9E_",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "icCcVDsFAlzlq4FjE0ubK"
+ ],
+ "x": 989.5,
+ "y": 363,
+ "direction": 4
+ },
+ "end": {
+ "tableId": "P7FiH9nZLSpro47ZFRs82",
+ "columnIds": [
+ "TuNRq7Dmvw9HkKmJzmHyA"
+ ],
+ "x": 989,
+ "y": 205,
+ "direction": 8
+ },
+ "meta": {
+ "updateAt": 1758385949681,
+ "createAt": 1758385949681
+ }
+ },
+ "AhYoCwpMoUZjKuaTsV67y": {
+ "id": "AhYoCwpMoUZjKuaTsV67y",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44"
+ ],
+ "x": 1702,
+ "y": 563,
+ "direction": 8
+ },
+ "end": {
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "columnIds": [
+ "aI9MAW7Hiyy_vI2M7Pm0-"
+ ],
+ "x": 1745.5,
+ "y": 837,
+ "direction": 4
+ },
+ "meta": {
+ "updateAt": 1758452663071,
+ "createAt": 1758452663071
+ }
+ },
+ "bHRhHIOth5vgHKEWcIqCg": {
+ "id": "bHRhHIOth5vgHKEWcIqCg",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 545,
+ "y": 526,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "columnIds": [
+ "8rJpsU0PXxwff0XAgxqMm"
+ ],
+ "x": 742,
+ "y": 688,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758452672201,
+ "createAt": 1758452672201
+ }
+ },
+ "-CCfH3cvSxvdbUzVIjRqx": {
+ "id": "-CCfH3cvSxvdbUzVIjRqx",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 590.9412,
+ "y": 870.9412,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "columnIds": [
+ "Zl7soLlH7_f8dROOoajF6"
+ ],
+ "x": 693,
+ "y": 912,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758521025059,
+ "createAt": 1758521025059
+ }
+ },
+ "MBnlvlcsZqkaQ8PYk26m1": {
+ "id": "MBnlvlcsZqkaQ8PYk26m1",
+ "identification": false,
+ "relationshipType": 2,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "columnIds": [
+ "ReQt_SxQicmPN6FInTteO"
+ ],
+ "x": 1533,
+ "y": 913,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "columnIds": [
+ "CkI_d-LBnoNOjBGw3ay_0"
+ ],
+ "x": 1260,
+ "y": 912,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758521244136,
+ "createAt": 1758521244136
+ }
+ },
+ "TZyzCxNXZSBj2H19VgZBh": {
+ "id": "TZyzCxNXZSBj2H19VgZBh",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "icCcVDsFAlzlq4FjE0ubK"
+ ],
+ "x": 1260,
+ "y": 451,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "dUWHJDNXCbHc3eEVauegi"
+ ],
+ "x": 1469,
+ "y": 474,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758521399180,
+ "createAt": 1758521399180
+ }
+ },
+ "3NAwaUh04XxCu9WtiJZGQ": {
+ "id": "3NAwaUh04XxCu9WtiJZGQ",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44"
+ ],
+ "x": 1469,
+ "y": 407,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "CVbY3U1IRbHYIv2_ABSi8"
+ ],
+ "x": 1260,
+ "y": 451,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758521413946,
+ "createAt": 1758521413946
+ }
+ },
+ "hsUPR9Uagl9UsCj3_v7-D": {
+ "id": "hsUPR9Uagl9UsCj3_v7-D",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 590.9412,
+ "y": 746.9412,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "U1KhhqX8xZco_6X76lUUF",
+ "columnIds": [
+ "r28FAr4NRktLF6jeinkQI"
+ ],
+ "x": 719,
+ "y": 451,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758521432311,
+ "createAt": 1758521432311
+ }
+ },
+ "TaYFbfPGvec7SZetfjcbm": {
+ "id": "TaYFbfPGvec7SZetfjcbm",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44"
+ ],
+ "x": 1702,
+ "y": 539,
+ "direction": 8
+ },
+ "end": {
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "columnIds": [
+ "db5bXfLzqmyQYh_JjRPiy"
+ ],
+ "x": 1704.5,
+ "y": 832,
+ "direction": 4
+ },
+ "meta": {
+ "updateAt": 1758521673578,
+ "createAt": 1758521673578
+ }
+ },
+ "nrUSGKD4XjLLYG8gtNeX0": {
+ "id": "nrUSGKD4XjLLYG8gtNeX0",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "columnIds": [
+ "ReQt_SxQicmPN6FInTteO"
+ ],
+ "x": 1492,
+ "y": 870,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "BrlSmoR0vOA5wp7DxG5we",
+ "columnIds": [
+ "7uVQuguLC9lssgHo5xhLG"
+ ],
+ "x": 1279,
+ "y": 912,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758521996327,
+ "createAt": 1758521996327
+ }
+ },
+ "rRR3UlrXRccW3-FvusZwo": {
+ "id": "rRR3UlrXRccW3-FvusZwo",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "spDJoG67LKxqns5k01Sw8",
+ "columnIds": [
+ "hxvxT750NkNHHEtYpUicL"
+ ],
+ "x": 321.4999,
+ "y": 313.2066,
+ "direction": 8
+ },
+ "end": {
+ "tableId": "gb4iYsr3YyVUrKqBUXFw4",
+ "columnIds": [
+ "MbXpR0Vw0Si2O1rM9QRbA"
+ ],
+ "x": 317.5591,
+ "y": 491.5598,
+ "direction": 4
+ },
+ "meta": {
+ "updateAt": 1758522454179,
+ "createAt": 1758522454179
+ }
+ },
+ "lF_M2fq7NZ-MM9bQ_QFa9": {
+ "id": "lF_M2fq7NZ-MM9bQ_QFa9",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 314.4412,
+ "y": 715.9412,
+ "direction": 4
+ },
+ "end": {
+ "tableId": "gb4iYsr3YyVUrKqBUXFw4",
+ "columnIds": [
+ "TgQ6AVIBficrVrE_W4Sk2"
+ ],
+ "x": 317.5591,
+ "y": 619.5598,
+ "direction": 8
+ },
+ "meta": {
+ "updateAt": 1758522460392,
+ "createAt": 1758522460392
+ }
+ },
+ "Zh4XQbVDVYrT3m77ypVoQ": {
+ "id": "Zh4XQbVDVYrT3m77ypVoQ",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jAhMpDau6eAWbR8ifxIk7",
+ "columnIds": [
+ "Zq1avcoEnaVsfbppogJ44"
+ ],
+ "x": 1469,
+ "y": 495,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "0JvjupyrC_HXBhFN_shc4",
+ "columnIds": [
+ "bHXNv5MX53K6nr7N3Q8eK"
+ ],
+ "x": 1305.2355,
+ "y": 668.1762,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758523031524,
+ "createAt": 1758523031524
+ }
+ },
+ "28vE14zEXeDiQ9WZAlOgr": {
+ "id": "28vE14zEXeDiQ9WZAlOgr",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 590.9412,
+ "y": 808.9412,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "0JvjupyrC_HXBhFN_shc4",
+ "columnIds": [
+ "xUscWzzz5Gz5EdHQRfk_N"
+ ],
+ "x": 766.2355,
+ "y": 668.1762,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758523036392,
+ "createAt": 1758523036392
+ }
+ },
+ "Fl9pEIK2AlrkViNySHsbU": {
+ "id": "Fl9pEIK2AlrkViNySHsbU",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "eOdf64btd3jXZ3odam357",
+ "columnIds": [
+ "ReQt_SxQicmPN6FInTteO"
+ ],
+ "x": 1492,
+ "y": 946,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "yoRJ0I0phZM05vvjpplpG",
+ "columnIds": [
+ "qOaGuBBg_Wfz0JKwfaKUe"
+ ],
+ "x": 1241.1765,
+ "y": 1115.7949,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1758523081911,
+ "createAt": 1758523081911
+ }
+ },
+ "75cGtrFxrlxv8h9HNbs_x": {
+ "id": "75cGtrFxrlxv8h9HNbs_x",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "1h1BgNxEvKkk6gEY5CMPg",
+ "columnIds": [
+ "Gh8Azk72nGvgic4QJ8XZ4"
+ ],
+ "x": 590.9412,
+ "y": 932.9412,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "yoRJ0I0phZM05vvjpplpG",
+ "columnIds": [
+ "8U14FBzF_FqWWddLVhfeB"
+ ],
+ "x": 721.1765,
+ "y": 1115.7949,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1758523093065,
+ "createAt": 1758523093065
+ }
+ }
+ },
+ "indexEntities": {},
+ "indexColumnEntities": {},
+ "memoEntities": {}
+ }
+}
\ No newline at end of file
diff --git a/week1/ex1.sql b/week1/ex1.sql
new file mode 100644
index 0000000..f0eff4f
--- /dev/null
+++ b/week1/ex1.sql
@@ -0,0 +1,20 @@
+select * from review where review.market_id=”점포id”;
+select (유저란에 있는 관련정보)from user where=(여기에 로그인 인증때 쓴 id값을 넣어줘야함)
+select (선택할 정보)from permission_mission as p inner join mission as m on p.mission_id=m.id inner join food_market as f on m.market_id=f.id where p.user_id=”유저아이디”
+limit 10 : 20 (대충 한페이지에 10개씩 담아서 조회하고 지금은 3페이지임;
+홈 화면 쿼리(현재 선택 된 지역에서 도전이 가능한 미션 목록, 페이징 포함) 만약에 foodmarket테이블에 위치정보가 있다면 (거리 계산은 단순 차로 계산함)
+select(정보들) from food_market as f join mission as m on f.id= m.mission_id
+where foodmarket.distance<3
+
+limit 10 : 20 (대충 한페이지에 10개씩 담아서 조회하고 지금은 3페이지임
+select (선택할 정보)
+from permission_mission as p
+inner join mission as m on p.mission_id=m.id
+inner join food_market as f on m.market_id=f.id
+where p.user_id=”유저아이디”
+AND (
+ points < :p
+ OR (m .points = :p AND m.created_at < :t)
+ OR (m.points = :p AND m.created_at = :t AND m.id < :m.id)
+ )
+order by m.point m.created_at
diff --git a/week1/workbook.txt b/week1/workbook.txt
new file mode 100644
index 0000000..ed8baf8
--- /dev/null
+++ b/week1/workbook.txt
@@ -0,0 +1,25 @@
+- [ ] 다양한 트랜젝션 상태와, 트랜젝션 전파에 대해 조사해주세요!
+ - acid의 i의 상태는
+
+
+
+ 
+
+
+이렇게 되고 serializable로 갈수록 격리성이 높아져서 공유 데이터가 안전해 집니다.
+
+- [ ] 함수 기반 인덱스와 복합 인덱스에 대해 조사하고,
+성능상 이점과 단점을 적어주세요!
+ - 인덱스는 보통 select 시에 데이터를 빠르게 찾기위함이 주 목적인데 이때 찾는 조건 where절을 잘 볼 필요가 있다 우리가 where절의 조건을 기본키로 했으면 큰 문제가 없겠지만 만약 함수식조건, 복합컬럼으로 조회했을때는 문제가 되기에 이를 해결하기 위한것이
+ - 함수기반 인덱스와 복합 인덱스이다.
+ - 함수기반 인덱스는 abs(a,b)자체 계산값들을 인덱스로 저장하고 복합 인덱스는 CREATE INDEX idx_user_name_age ON users(name, age); 처럼 키를 묶어서 저장해 놓는다.
+
+함수기반 장단점
+
+장점: where절에서 함수기반 서칭을 해도 빠르게 찾기가능.
+
+단점: 계산하고 저장해서 저장공간을 잡아먹는다.
+
+장점 : 복합인덱스: 복합키로 찾을때 빠른 서칭가능.
+
+단점 : 만약 히트 못시키면 그냥 저장공간 낭비
\ No newline at end of file
diff --git a/week4/demo/.gitattributes b/week4/demo/.gitattributes
new file mode 100644
index 0000000..8af972c
--- /dev/null
+++ b/week4/demo/.gitattributes
@@ -0,0 +1,3 @@
+/gradlew text eol=lf
+*.bat text eol=crlf
+*.jar binary
diff --git a/week4/demo/.gitignore b/week4/demo/.gitignore
new file mode 100644
index 0000000..c2065bc
--- /dev/null
+++ b/week4/demo/.gitignore
@@ -0,0 +1,37 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
diff --git a/week4/demo/build.gradle b/week4/demo/build.gradle
new file mode 100644
index 0000000..a75d209
--- /dev/null
+++ b/week4/demo/build.gradle
@@ -0,0 +1,70 @@
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '3.5.6'
+ id 'io.spring.dependency-management' version '1.1.7'
+}
+
+group = 'study'
+version = '0.0.1-SNAPSHOT'
+description = 'demo'
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+configurations {
+ compileOnly {
+ extendsFrom annotationProcessor
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ compileOnly 'org.projectlombok:lombok'
+ annotationProcessor 'org.projectlombok:lombok'
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+ implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+ runtimeOnly 'com.mysql:mysql-connector-j'
+
+ // QueryDSL : OpenFeign
+ implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0"
+ implementation "io.github.openfeign.querydsl:querydsl-core:7.0"
+ annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa"
+ annotationProcessor "jakarta.persistence:jakarta.persistence-api"
+ annotationProcessor "jakarta.annotation:jakarta.annotation-api"
+ // Swagger
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'
+
+ // Swagger
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'
+
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
+}
+def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile
+
+// 소스 세트에 생성 경로 추가 (구체적인 경로 지정)
+sourceSets {
+ main.java.srcDirs += [ querydslDir ]
+}
+
+// 컴파일 시 생성 경로 지정
+tasks.withType(JavaCompile).configureEach {
+ options.generatedSourceOutputDirectory.set(querydslDir)
+}
+
+// clean 태스크에 생성 폴더 삭제 로직 추가
+clean.doLast {
+ file(querydslDir).deleteDir()
+}
\ No newline at end of file
diff --git a/week4/demo/gradle/wrapper/gradle-wrapper.jar b/week4/demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..1b33c55
Binary files /dev/null and b/week4/demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/week4/demo/gradle/wrapper/gradle-wrapper.properties b/week4/demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d4081da
--- /dev/null
+++ b/week4/demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/week4/demo/gradlew b/week4/demo/gradlew
new file mode 100644
index 0000000..23d15a9
--- /dev/null
+++ b/week4/demo/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/week4/demo/gradlew.bat b/week4/demo/gradlew.bat
new file mode 100644
index 0000000..db3a6ac
--- /dev/null
+++ b/week4/demo/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/week4/demo/settings.gradle b/week4/demo/settings.gradle
new file mode 100644
index 0000000..0a383dd
--- /dev/null
+++ b/week4/demo/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'demo'
diff --git a/week4/demo/src/main/java/com/example/demo/DemoApplication.java b/week4/demo/src/main/java/com/example/demo/DemoApplication.java
new file mode 100644
index 0000000..773eff3
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/DemoApplication.java
@@ -0,0 +1,15 @@
+package com.example.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+@EnableJpaAuditing
+@SpringBootApplication
+public class DemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoApplication.class, args);
+ }
+
+}
diff --git a/week4/demo/src/main/java/com/example/demo/ddl/market.sql b/week4/demo/src/main/java/com/example/demo/ddl/market.sql
new file mode 100644
index 0000000..9de3b7f
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/ddl/market.sql
@@ -0,0 +1,144 @@
+use market;
+CREATE TABLE users (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '유저 고유 식별자',
+ email VARCHAR(50) NOT NULL COMMENT '로그인 이메일(유니크)',
+ password VARCHAR(100) NOT NULL COMMENT '비밀번호 해시(예: bcrypt 60자)',
+ name VARCHAR(20) NOT NULL COMMENT '이름',
+ gender TINYINT NOT NULL COMMENT '0:남, 1:여, 2:기타 등',
+ birth DATE NOT NULL COMMENT '생년월일',
+ address VARCHAR(50) NOT NULL COMMENT '주소',
+ point INT NOT NULL DEFAULT 0 COMMENT '총 포인트',
+ PRIMARY KEY (id),
+ UNIQUE KEY UK_users_email (email)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='user_information';
+
+-- 음식 카테고리
+CREATE TABLE food (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '음식 카테고리 ID',
+ name VARCHAR(40) NOT NULL COMMENT '음식 카테고리 이름',
+ PRIMARY KEY (id),
+ UNIQUE KEY UK_food_name (name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='푸드카테고리';
+
+-- 선호 음식 (유저-음식)
+CREATE TABLE favorite_food (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '유저선호음식코드',
+ food_id BIGINT NOT NULL COMMENT '음식 카테고리 ID',
+ user_id BIGINT NOT NULL COMMENT '유저 ID',
+ PRIMARY KEY (id),
+ UNIQUE KEY UK_favorite_food_user_food (user_id, food_id),
+ KEY IDX_favorite_food_food (food_id),
+ CONSTRAINT FK_favorite_food_food
+ FOREIGN KEY (food_id) REFERENCES food (id)
+ ON UPDATE CASCADE ON DELETE RESTRICT,
+ CONSTRAINT FK_favorite_food_users
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='선호음식';
+
+-- 가게
+CREATE TABLE foodmarket (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '가게 id',
+ name VARCHAR(40) NOT NULL COMMENT '상호명(유니크는 아님)',
+ address VARCHAR(50) NOT NULL COMMENT '가게주소',
+ open BOOLEAN NOT NULL COMMENT '영업중 여부',
+ content VARCHAR(200) NOT NULL COMMENT '가게 소개',
+ PRIMARY KEY (id),
+ KEY IDX_foodmarket_name (name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='푸드 마켓';
+
+-- 미션(가게별)
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '고유 미션 ID',
+ market_id BIGINT NOT NULL COMMENT '가게 id',
+ content VARCHAR(100) NOT NULL COMMENT '미션 내용',
+ mission_point INT NOT NULL COMMENT '미션 포인트',
+ PRIMARY KEY (id),
+ KEY IDX_mission_market (market_id),
+ CONSTRAINT FK_mission_foodmarket
+ FOREIGN KEY (market_id) REFERENCES foodmarket (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='마켓과 유저 사이의 미션';
+
+-- 유저가 받은 미션(신규 신청 등)
+CREATE TABLE mission_user (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ mission_id BIGINT NOT NULL COMMENT '미션 ID',
+ user_id BIGINT NOT NULL COMMENT '유저 ID',
+ content VARCHAR(200) NOT NULL COMMENT '새 미션에 대한 내용',
+ PRIMARY KEY (id),
+ UNIQUE KEY UK_mission_user (mission_id, user_id),
+ KEY IDX_mission_user_mission (mission_id),
+ KEY IDX_mission_user_user (user_id),
+ CONSTRAINT FK_mission_user_mission
+ FOREIGN KEY (mission_id) REFERENCES mission (id)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT FK_mission_user_users
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='신규 미션 배정 내역';
+
+-- 미션 권한/배정 토큰(사장님 확인용 번호)
+CREATE TABLE permission_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '유저 미션할당번호(제시용)',
+ user_id BIGINT NOT NULL COMMENT '유저 ID',
+ mission_id BIGINT NOT NULL COMMENT '미션 ID',
+ PRIMARY KEY (id),
+ UNIQUE KEY UK_permission_mission (user_id, mission_id),
+ KEY IDX_permission_mission_user (user_id),
+ KEY IDX_permission_mission_mission (mission_id),
+ CONSTRAINT FK_permission_mission_users
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT FK_permission_mission_mission
+ FOREIGN KEY (mission_id) REFERENCES mission (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='미션 권한 배정';
+
+-- 리뷰
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '리뷰 ID',
+ user_id BIGINT NOT NULL COMMENT '유저 ID',
+ market_id BIGINT NOT NULL COMMENT '가게 ID',
+ content VARCHAR(100) NOT NULL COMMENT '리뷰 내용',
+ star DECIMAL(2,1) NOT NULL COMMENT '0.0~5.0',
+ PRIMARY KEY (id),
+ KEY IDX_review_user (user_id),
+ KEY IDX_review_market (market_id),
+ CONSTRAINT CHK_review_star CHECK (star >= 0.0 AND star <= 5.0),
+ CONSTRAINT FK_review_users
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT FK_review_foodmarket
+ FOREIGN KEY (market_id) REFERENCES foodmarket (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='마켓과 유저 사이의 리뷰';
+
+-- 리뷰 알림
+CREATE TABLE review_alarm (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ user_id BIGINT NOT NULL COMMENT '유저 ID',
+ foodmarket_id BIGINT NOT NULL COMMENT '가게 ID',
+ content VARCHAR(200) NOT NULL COMMENT '알람 내용(리뷰 관련)',
+ PRIMARY KEY (id),
+ KEY IDX_review_alarm_user (user_id),
+ KEY IDX_review_alarm_market (foodmarket_id),
+ CONSTRAINT FK_review_alarm_users
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ ON UPDATE CASCADE ON DELETE CASCADE,
+ CONSTRAINT FK_review_alarm_foodmarket
+ FOREIGN KEY (foodmarket_id) REFERENCES foodmarket (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='리뷰 알림';
+
+-- 리뷰 이미지
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT COMMENT '고유 이미지 ID',
+ review_id BIGINT NOT NULL COMMENT '리뷰 ID',
+ url VARCHAR(200) NOT NULL COMMENT '이미지 URL',
+ PRIMARY KEY (id),
+ KEY IDX_review_image_review (review_id),
+ CONSTRAINT FK_review_image_review
+ FOREIGN KEY (review_id) REFERENCES review (id)
+ ON UPDATE CASCADE ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='리뷰 이미지들';
\ No newline at end of file
diff --git a/week4/demo/src/main/java/com/example/demo/domain/alarm/entity/ReviewAlarm.java b/week4/demo/src/main/java/com/example/demo/domain/alarm/entity/ReviewAlarm.java
new file mode 100644
index 0000000..2513f2b
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/alarm/entity/ReviewAlarm.java
@@ -0,0 +1,25 @@
+package com.example.demo.domain.alarm.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import com.example.demo.domain.member.entity.Users;
+import com.example.demo.domain.restruant.entity.FoodMarket;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity @Table(name = "review_alarm")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class ReviewAlarm extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="user_id", nullable=false)
+ private Users user;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="foodmarket_id", nullable=false)
+ private FoodMarket foodmarket;
+
+ @Column(nullable=false, length=200)
+ private String content;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/member/entity/FavoriteFood.java b/week4/demo/src/main/java/com/example/demo/domain/member/entity/FavoriteFood.java
new file mode 100644
index 0000000..cc7dac5
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/member/entity/FavoriteFood.java
@@ -0,0 +1,29 @@
+package com.example.demo.domain.member.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+
+@Entity
+@Table(name ="favorite_food")
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class FavoriteFood extends BaseEntity {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional = false,fetch = FetchType.LAZY)
+ @JoinColumn(name = "food_id",nullable = false)
+ private Food food;
+
+ @ManyToOne(optional = false,fetch = FetchType.LAZY)
+ @JoinColumn(name = "user_id",nullable = false)
+ private Users user;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/member/entity/Food.java b/week4/demo/src/main/java/com/example/demo/domain/member/entity/Food.java
new file mode 100644
index 0000000..a396207
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/member/entity/Food.java
@@ -0,0 +1,21 @@
+package com.example.demo.domain.member.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Table(name = "food")
+@Entity@Getter@Setter
+@NoArgsConstructor@AllArgsConstructor
+public class Food extends BaseEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false,length = 40)
+ private String name;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/member/entity/Users.java b/week4/demo/src/main/java/com/example/demo/domain/member/entity/Users.java
new file mode 100644
index 0000000..c9af3b0
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/member/entity/Users.java
@@ -0,0 +1,40 @@
+package com.example.demo.domain.member.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDate;
+
+@Entity
+@Table(name = "users")
+@Getter@Setter@NoArgsConstructor@AllArgsConstructor
+public class Users extends BaseEntity {
+
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false,length = 50)
+ private String email;
+
+ @Column(nullable = false,length =100)
+ private String password;
+
+ @Column(nullable = false,length = 20)
+ private String name;
+
+ @Column(nullable = false)
+ private Byte gender;
+
+ @Column(nullable = false)
+ private LocalDate birth;
+
+ @Column(nullable = false,length = 50)
+ private String address;
+
+ @Column(nullable = false)
+ private Integer point;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/member/repository/FavoriteFoodRepository.java b/week4/demo/src/main/java/com/example/demo/domain/member/repository/FavoriteFoodRepository.java
new file mode 100644
index 0000000..0a1c94b
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/member/repository/FavoriteFoodRepository.java
@@ -0,0 +1,10 @@
+package com.example.demo.domain.member.repository;
+
+import com.example.demo.domain.member.entity.FavoriteFood;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface FavoriteFoodRepository extends JpaRepository {
+
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/member/repository/FoodRepository.java b/week4/demo/src/main/java/com/example/demo/domain/member/repository/FoodRepository.java
new file mode 100644
index 0000000..a7aec29
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/member/repository/FoodRepository.java
@@ -0,0 +1,9 @@
+package com.example.demo.domain.member.repository;
+
+import com.example.demo.domain.member.entity.Food;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface FoodRepository extends JpaRepository {
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/member/repository/UsersRepository.java b/week4/demo/src/main/java/com/example/demo/domain/member/repository/UsersRepository.java
new file mode 100644
index 0000000..10de128
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/member/repository/UsersRepository.java
@@ -0,0 +1,15 @@
+package com.example.demo.domain.member.repository;
+
+import com.example.demo.domain.member.entity.Users;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+@Repository
+public interface UsersRepository extends JpaRepository {
+ Optional findById(Long userId);
+
+ @Query("select u from Users u WHERE u.id=:userId")
+ Optional findById2(Long userId);
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/Handler/PagingExceptionHandler.java b/week4/demo/src/main/java/com/example/demo/domain/mission/Handler/PagingExceptionHandler.java
new file mode 100644
index 0000000..f375557
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/Handler/PagingExceptionHandler.java
@@ -0,0 +1,21 @@
+package com.example.demo.domain.mission.Handler;
+
+import com.example.demo.domain.mission.exception.code.MissionErrorCode;
+import jakarta.validation.ConstraintViolationException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import com.example.demo.global.apipayload.response.ApiResponse;
+import com.example.demo.global.apipayload.response.BaseErrorCode;
+
+
+@RestControllerAdvice
+public class PagingExceptionHandler {
+
+ @ExceptionHandler(ConstraintViolationException.class)
+ public ResponseEntity> handlePagingException(ConstraintViolationException e) {
+
+ BaseErrorCode errorCode = MissionErrorCode.INVALID_PAGE;
+ return ApiResponse.ERROR(errorCode);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/controller/MissionController.java b/week4/demo/src/main/java/com/example/demo/domain/mission/controller/MissionController.java
new file mode 100644
index 0000000..2f3d3be
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/controller/MissionController.java
@@ -0,0 +1,68 @@
+package com.example.demo.domain.mission.controller;
+
+import com.example.demo.domain.mission.dto.MissionDtos;
+import com.example.demo.domain.mission.exception.code.MissionSuccessCode;
+import com.example.demo.domain.mission.service.MissionService;
+import com.example.demo.global.apipayload.response.ApiResponse;
+import com.example.demo.global.validation.ValidPage;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@Validated
+@RestController
+@RequestMapping("/api")
+@RequiredArgsConstructor
+public class MissionController {
+
+ private final MissionService missionService;
+
+ @PostMapping("/markets/{marketId}/missions")
+ public ResponseEntity> createMission(
+ @PathVariable Long marketId,
+ @Valid @RequestBody MissionDtos.MissionCreateRequest request
+ ) {
+ MissionDtos.MissionResponse body = missionService.createMission(marketId, request);
+ return ApiResponse.success(MissionSuccessCode.MISSION_CREATED, body);
+ }
+
+ @GetMapping("/markets/{marketId}/missions")
+ public ResponseEntity>> getMissionsByMarket(
+ @PathVariable Long marketId,
+ @ValidPage
+ @RequestParam(name = "page", defaultValue = "1") Integer page
+ ) {
+ MissionDtos.PageResponse body =
+ missionService.getMissionsByMarket(marketId, page);
+ return ApiResponse.success(MissionSuccessCode.MISSION_LIST, body);
+ }
+
+ @PostMapping("/missions/accept")
+ public ResponseEntity> acceptMission(
+ @Valid @RequestBody MissionDtos.AcceptRequest request
+ ) {
+ MissionDtos.MissionUserResponse body = missionService.acceptMission(request);
+ return ApiResponse.success(MissionSuccessCode.MISSION_ACCEPTED, body);
+ }
+
+ @PostMapping("/missions/complete")
+ public ResponseEntity> completeMission(
+ @Valid @RequestBody MissionDtos.CompleteRequest request
+ ) {
+ MissionDtos.MissionUserResponse body = missionService.completeMission(request);
+ return ApiResponse.success(MissionSuccessCode.MISSION_COMPLETED, body);
+ }
+
+ @GetMapping("/users/{userId}/missions")
+ public ResponseEntity>> getMyMissions(
+ @PathVariable Long userId,
+ @ValidPage
+ @RequestParam(name = "page", defaultValue = "1") Integer page
+ ) {
+ MissionDtos.PageResponse body =
+ missionService.getMyMissions(userId, page);
+ return ApiResponse.success(MissionSuccessCode.MISSION_LIST, body);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/dto/MissionDtos.java b/week4/demo/src/main/java/com/example/demo/domain/mission/dto/MissionDtos.java
new file mode 100644
index 0000000..473a9d8
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/dto/MissionDtos.java
@@ -0,0 +1,99 @@
+package com.example.demo.domain.mission.dto;
+
+import com.example.demo.domain.mission.entity.Mission;
+import com.example.demo.domain.mission.entity.MissionUser;
+import com.example.demo.domain.mission.entity.MissionStatus;
+import lombok.Builder;
+
+import java.util.List;
+
+public class MissionDtos {
+
+ // 공통 페이지 응답 DTO (제네릭)
+ @Builder
+ public record PageResponse(
+ List content,
+ int page,
+ int size,
+ long totalElements,
+ int totalPages,
+ boolean last
+ ) {}
+
+ @Builder
+ public record MissionCreateRequest(
+ String content,
+ Integer missionPoint
+ ) {}
+
+ // 미션 기본 정보 (모든 곳에서 재사용)
+ @Builder
+ public record MissionResponse(
+ Long id,
+ Long marketId,
+ String marketName,
+ String content,
+ Integer missionPoint
+ ) {
+ public static MissionResponse from(Mission mission) {
+ return MissionResponse.builder()
+ .id(mission.getId())
+ .marketId(mission.getMarket().getId())
+ .marketName(mission.getMarket().getName())
+ .content(mission.getContent())
+ .missionPoint(mission.getMissionPoint())
+ .build();
+ }
+ }
+
+ @Builder
+ public record AcceptRequest(
+ Long missionId,
+ Long userId
+ ) {}
+
+ @Builder
+ public record CompleteRequest(
+ Long missionId,
+ Long userId,
+ String content // 인증 내용
+ ) {}
+
+ // 개별 유저-미션 정보 (상세 조회/accept/complete 응답 등에 사용)
+ @Builder
+ public record MissionUserResponse(
+ Long missionUserId,
+ MissionResponse mission, // 미션 정보 재사용
+ Long userId,
+ MissionStatus status,
+ String content
+ ) {
+ public static MissionUserResponse from(MissionUser mu) {
+ return MissionUserResponse.builder()
+ .missionUserId(mu.getId())
+ .mission(MissionResponse.from(mu.getMission()))
+ .userId(mu.getUser().getId())
+ .status(mu.getMissionStatus())
+ .content(mu.getContent())
+ .build();
+ }
+ }
+
+ // "내 미션 목록" 한 줄 (리스트용)
+ @Builder
+ public record MyMissionItemResponse(
+ Long missionUserId,
+ MissionResponse mission, // 동일하게 재사용
+ MissionStatus status,
+ String content
+ ) {
+ public static MyMissionItemResponse from(MissionUser mu) {
+ return MyMissionItemResponse.builder()
+ .missionUserId(mu.getId())
+ .mission(MissionResponse.from(mu.getMission()))
+ .status(mu.getMissionStatus())
+ .content(mu.getContent())
+ .build();
+ }
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/entity/Mission.java b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/Mission.java
new file mode 100644
index 0000000..45cac8b
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/Mission.java
@@ -0,0 +1,23 @@
+package com.example.demo.domain.mission.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import com.example.demo.domain.restruant.entity.FoodMarket;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity @Table(name = "mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class Mission extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="market_id", nullable=false)
+ private FoodMarket market;
+
+ @Column(nullable=false, length=100)
+ private String content;
+
+ @Column(name="mission_point", nullable=false)
+ private Integer missionPoint;
+}
\ No newline at end of file
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/entity/MissionStatus.java b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/MissionStatus.java
new file mode 100644
index 0000000..d706f0f
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/MissionStatus.java
@@ -0,0 +1,7 @@
+package com.example.demo.domain.mission.entity;
+
+public enum MissionStatus {
+ ACCEPTED,
+ COMPLETED,
+ CANCELLED,
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/entity/MissionUser.java b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/MissionUser.java
new file mode 100644
index 0000000..60ec22c
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/MissionUser.java
@@ -0,0 +1,28 @@
+package com.example.demo.domain.mission.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import com.example.demo.domain.member.entity.Users;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity @Table(name = "mission_user")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class MissionUser extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="mission_id", nullable=false)
+ private Mission mission;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="user_id", nullable=false)
+ private Users user;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = true)
+ private MissionStatus missionStatus;
+
+ @Column(nullable=false, length=200)
+ private String content;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/entity/PermissionMission.java b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/PermissionMission.java
new file mode 100644
index 0000000..5e13ede
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/entity/PermissionMission.java
@@ -0,0 +1,21 @@
+package com.example.demo.domain.mission.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import com.example.demo.domain.member.entity.Users;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity @Table(name = "permission_mission")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class PermissionMission extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="user_id", nullable=false)
+ private Users user;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="mission_id", nullable=false)
+ private Mission mission;
+}
\ No newline at end of file
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/exception/MissionException.java b/week4/demo/src/main/java/com/example/demo/domain/mission/exception/MissionException.java
new file mode 100644
index 0000000..e817c5f
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/exception/MissionException.java
@@ -0,0 +1,10 @@
+package com.example.demo.domain.mission.exception;
+
+import com.example.demo.domain.mission.exception.code.MissionErrorCode;
+import com.example.demo.global.apipayload.exception.GeneralException;
+
+public class MissionException extends GeneralException {
+ public MissionException(MissionErrorCode errorCode) {
+ super(errorCode);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/exception/code/MissionErrorCode.java b/week4/demo/src/main/java/com/example/demo/domain/mission/exception/code/MissionErrorCode.java
new file mode 100644
index 0000000..bf0abc9
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/exception/code/MissionErrorCode.java
@@ -0,0 +1,23 @@
+package com.example.demo.domain.mission.exception.code;
+
+import com.example.demo.global.apipayload.response.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+@Getter
+@AllArgsConstructor
+public enum MissionErrorCode implements BaseErrorCode {
+ MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "M4001", "존재하지 않는 미션입니다."),
+ MARKET_NOT_FOUND(HttpStatus.NOT_FOUND, "M4002", "존재하지 않는 가게입니다."),
+ USER_NOT_FOUND(HttpStatus.NOT_FOUND, "M4003", "존재하지 않는 유저입니다."),
+ INVALID_PAGE(HttpStatus.BAD_REQUEST,"P001", "페이지를 1이상으로 해라"),
+ MISSION_ALREADY_ACCEPTED(HttpStatus.CONFLICT, "M4004", "이미 수락한 미션입니다."),
+ MISSION_NOT_ACCEPTED(HttpStatus.BAD_REQUEST, "M4005", "아직 이 미션을 수락하지 않았습니다."),
+ MISSION_ALREADY_COMPLETED(HttpStatus.CONFLICT, "M4006", "이미 완료된 미션입니다."),
+ MISSION_CONDITION_NOT_MET(HttpStatus.BAD_REQUEST, "M4007", "미션 조건을 만족하지 않습니다.");
+
+ private final HttpStatus httpStatus;
+ private final String code;
+ private final String message;
+
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/exception/code/MissionSuccessCode.java b/week4/demo/src/main/java/com/example/demo/domain/mission/exception/code/MissionSuccessCode.java
new file mode 100644
index 0000000..daf514d
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/exception/code/MissionSuccessCode.java
@@ -0,0 +1,20 @@
+package com.example.demo.domain.mission.exception.code;
+
+import com.example.demo.global.apipayload.response.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+@Getter
+@AllArgsConstructor
+public enum MissionSuccessCode implements BaseSuccessCode {
+
+ MISSION_CREATED(HttpStatus.CREATED, "M001", "미션 생성에 성공했습니다."),
+ MISSION_LIST(HttpStatus.OK, "M002", "미션 목록 조회에 성공했습니다."),
+ MISSION_ACCEPTED(HttpStatus.OK, "M003", "미션을 수락했습니다."),
+ MISSION_COMPLETED(HttpStatus.OK, "M004", "미션을 완료했습니다."),
+ MISSION_DETAIL(HttpStatus.OK, "M005", "미션 상태 조회에 성공했습니다.");
+
+ private final HttpStatus httpStatus;
+ private final String code;
+ private final String message;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/repository/MissionRepository.java b/week4/demo/src/main/java/com/example/demo/domain/mission/repository/MissionRepository.java
new file mode 100644
index 0000000..bfa46be
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/repository/MissionRepository.java
@@ -0,0 +1,17 @@
+package com.example.demo.domain.mission.repository;
+
+import com.example.demo.domain.mission.entity.Mission;
+import com.example.demo.domain.restruant.entity.FoodMarket;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface MissionRepository extends JpaRepository {
+ List findMissionsByContentOrderByUpdatedAt(String Content);
+ @Query("select m from Mission m where m.content=:Content order by m.updatedAt desc")
+ List findMissionsByContentOrderByUpdatedAt2(String Content);
+ Page findByMarket(FoodMarket market, Pageable pageable);
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/repository/MissionUserRepository.java b/week4/demo/src/main/java/com/example/demo/domain/mission/repository/MissionUserRepository.java
new file mode 100644
index 0000000..9435a52
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/repository/MissionUserRepository.java
@@ -0,0 +1,21 @@
+package com.example.demo.domain.mission.repository;
+
+import com.example.demo.domain.member.entity.Users;
+import com.example.demo.domain.mission.entity.Mission;
+import com.example.demo.domain.mission.entity.MissionUser;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface MissionUserRepository extends JpaRepository {
+ List findByUser_IdOrderByUpdatedAtDesc(int userId);
+ @Query("select mu from MissionUser mu join fetch mu.mission where mu.user.id = :userId order by mu.updatedAt desc ")
+ List findByUser_IdOrderByUpdatedAtDesc2(int userId);
+ Optional findByMissionAndUser(Mission mission, Users user);
+ Page findByUser_IdOrderByUpdatedAtDesc(Long userId, Pageable pageable);
+ boolean existsByMissionAndUser(Mission mission, Users user);
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/mission/service/MissionService.java b/week4/demo/src/main/java/com/example/demo/domain/mission/service/MissionService.java
new file mode 100644
index 0000000..e740518
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/mission/service/MissionService.java
@@ -0,0 +1,137 @@
+package com.example.demo.domain.mission.service;
+
+import com.example.demo.domain.member.entity.Users;
+import com.example.demo.domain.member.repository.UsersRepository;
+import com.example.demo.domain.mission.dto.MissionDtos;
+import com.example.demo.domain.mission.entity.Mission;
+import com.example.demo.domain.mission.entity.MissionStatus;
+import com.example.demo.domain.mission.entity.MissionUser;
+import com.example.demo.domain.mission.exception.MissionException;
+import com.example.demo.domain.mission.exception.code.MissionErrorCode;
+import com.example.demo.domain.mission.repository.MissionRepository;
+import com.example.demo.domain.mission.repository.MissionUserRepository;
+import com.example.demo.domain.restruant.entity.FoodMarket;
+import com.example.demo.domain.restruant.entity.FoodMarketRepository;
+import jakarta.transaction.Transactional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class MissionService {
+
+ private final MissionRepository missionRepository;
+ private final MissionUserRepository missionUserRepository;
+ private final UsersRepository usersRepository;
+ private final FoodMarketRepository foodMarketRepository;
+
+ public MissionDtos.MissionResponse createMission(
+ Long marketId,
+ MissionDtos.MissionCreateRequest request
+ ) {
+ FoodMarket market = foodMarketRepository.findById(marketId)
+ .orElseThrow(() -> new MissionException(MissionErrorCode.MARKET_NOT_FOUND));
+
+ Mission mission = new Mission();
+ mission.setMarket(market);
+ mission.setContent(request.content());
+ mission.setMissionPoint(request.missionPoint());
+
+ Mission saved = missionRepository.save(mission);
+ return MissionDtos.MissionResponse.from(saved);
+ }
+
+ public MissionDtos.PageResponse getMissionsByMarket(Long marketId, int page) {
+ FoodMarket market = foodMarketRepository.findById(marketId)
+ .orElseThrow(() -> new MissionException(MissionErrorCode.MARKET_NOT_FOUND));
+
+ int pageIndex = page - 1;
+ PageRequest pageable = PageRequest.of(pageIndex, 10);
+
+ Page missions = missionRepository.findByMarket(market, pageable);
+
+ return MissionDtos.PageResponse.builder()
+ .content(
+ missions.getContent()
+ .stream()
+ .map(MissionDtos.MissionResponse::from)
+ .toList()
+ )
+ .page(page)
+ .size(missions.getSize())
+ .totalElements(missions.getTotalElements())
+ .totalPages(missions.getTotalPages())
+ .last(missions.isLast())
+ .build();
+ }
+
+ public MissionDtos.MissionUserResponse acceptMission(MissionDtos.AcceptRequest request) {
+ Mission mission = missionRepository.findById(request.missionId())
+ .orElseThrow(() -> new MissionException(MissionErrorCode.MISSION_NOT_FOUND));
+
+ Users user = usersRepository.findById(request.userId())
+ .orElseThrow(() -> new MissionException(MissionErrorCode.USER_NOT_FOUND));
+
+ if (missionUserRepository.existsByMissionAndUser(mission, user)) {
+ throw new MissionException(MissionErrorCode.MISSION_ALREADY_ACCEPTED);
+ }
+
+ MissionUser mu = new MissionUser();
+ mu.setMission(mission);
+ mu.setUser(user);
+ mu.setMissionStatus(MissionStatus.ACCEPTED);
+
+ MissionUser saved = missionUserRepository.save(mu);
+ return MissionDtos.MissionUserResponse.from(saved);
+ }
+
+ public MissionDtos.MissionUserResponse completeMission(MissionDtos.CompleteRequest request) {
+ Mission mission = missionRepository.findById(request.missionId())
+ .orElseThrow(() -> new MissionException(MissionErrorCode.MISSION_NOT_FOUND));
+
+ Users user = usersRepository.findById(request.userId())
+ .orElseThrow(() -> new MissionException(MissionErrorCode.USER_NOT_FOUND));
+
+ MissionUser mu = missionUserRepository.findByMissionAndUser(mission, user)
+ .orElseThrow(() -> new MissionException(MissionErrorCode.MISSION_NOT_ACCEPTED));
+
+ if (mu.getMissionStatus() == MissionStatus.COMPLETED) {
+ throw new MissionException(MissionErrorCode.MISSION_ALREADY_COMPLETED);
+ }
+
+ mu.setMissionStatus(MissionStatus.COMPLETED);
+ mu.setContent(request.content());
+
+ MissionUser updated = missionUserRepository.save(mu);
+ return MissionDtos.MissionUserResponse.from(updated);
+ }
+
+
+ public MissionDtos.PageResponse getMyMissions(Long userId, int page) {
+
+ Users user = usersRepository.findById(userId)
+ .orElseThrow(() -> new MissionException(MissionErrorCode.USER_NOT_FOUND));
+
+ int pageIndex = page - 1;
+ PageRequest pageable = PageRequest.of(pageIndex, 10);
+
+ Page muPage =
+ missionUserRepository.findByUser_IdOrderByUpdatedAtDesc(user.getId(), pageable);
+
+ return MissionDtos.PageResponse.builder()
+ .content(
+ muPage.getContent().stream()
+ .map(MissionDtos.MyMissionItemResponse::from)
+ .toList()
+ )
+ .page(page)
+ .size(muPage.getSize())
+ .totalElements(muPage.getTotalElements())
+ .totalPages(muPage.getTotalPages())
+ .last(muPage.isLast())
+ .build();
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/restruant/entity/FoodMarket.java b/week4/demo/src/main/java/com/example/demo/domain/restruant/entity/FoodMarket.java
new file mode 100644
index 0000000..fa2f740
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/restruant/entity/FoodMarket.java
@@ -0,0 +1,24 @@
+package com.example.demo.domain.restruant.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity @Table(name = "foodmarket")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class FoodMarket extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable=false, length=40)
+ private String name;
+
+ @Column(nullable=false, length=50)
+ private String address;
+
+ @Column(nullable=false)
+ private Boolean open;
+
+ @Column(nullable=false, length=200)
+ private String content;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/restruant/entity/FoodMarketRepository.java b/week4/demo/src/main/java/com/example/demo/domain/restruant/entity/FoodMarketRepository.java
new file mode 100644
index 0000000..5b715b6
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/restruant/entity/FoodMarketRepository.java
@@ -0,0 +1,6 @@
+package com.example.demo.domain.restruant.entity;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface FoodMarketRepository extends JpaRepository {
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/Exception/ReviewException.java b/week4/demo/src/main/java/com/example/demo/domain/review/Exception/ReviewException.java
new file mode 100644
index 0000000..e5cc449
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/Exception/ReviewException.java
@@ -0,0 +1,10 @@
+package com.example.demo.domain.review.exception;
+
+import com.example.demo.global.apipayload.exception.GeneralException;
+import com.example.demo.domain.review.exception.code.ReviewErrorCode;
+
+public class ReviewException extends GeneralException {
+ public ReviewException(ReviewErrorCode errorCode) {
+ super(errorCode);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/Exception/code/ReviewErrorCode.java b/week4/demo/src/main/java/com/example/demo/domain/review/Exception/code/ReviewErrorCode.java
new file mode 100644
index 0000000..1639185
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/Exception/code/ReviewErrorCode.java
@@ -0,0 +1,19 @@
+package com.example.demo.domain.review.exception.code;
+
+import com.example.demo.global.apipayload.response.BaseErrorCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@AllArgsConstructor
+@Getter
+public enum ReviewErrorCode implements BaseErrorCode {
+ REVIEW_CREATE_ERROR(HttpStatus.BAD_REQUEST, "R001", "리뷰 등록을 실패했습니다."),
+ REVIEW_UPDATE_ERROR(HttpStatus.BAD_REQUEST, "R002", "리뷰 수정이 실패했습니다."),
+ REVIEW_DELETE_ERROR(HttpStatus.BAD_REQUEST,"R003", "리뷰가 삭제에 실패했습니다."),
+ REVIEW_MY_LIST_ERROR(HttpStatus.BAD_REQUEST, "R004", "내 리뷰 목록 조회에 실패했습니다.");
+
+ private final HttpStatus httpStatus;
+ private final String code;
+ private final String message;
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/Exception/code/ReviewSuccessCode.java b/week4/demo/src/main/java/com/example/demo/domain/review/Exception/code/ReviewSuccessCode.java
new file mode 100644
index 0000000..9b94061
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/Exception/code/ReviewSuccessCode.java
@@ -0,0 +1,25 @@
+// src/main/java/com/example/demo/global/response/SuccessCode.java
+package com.example.demo.domain.review.exception.code;
+
+import com.example.demo.global.apipayload.response.BaseSuccessCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+@Getter
+@AllArgsConstructor
+public enum ReviewSuccessCode implements BaseSuccessCode {
+
+ // === Review 도메인 관련 응답 코드 ===
+ REVIEW_CREATE_SUCCESS(HttpStatus.OK, "R001", "리뷰가 성공적으로 등록되었습니다."),
+ REVIEW_UPDATE_SUCCESS(HttpStatus.OK, "R002", "리뷰가 성공적으로 수정되었습니다."),
+ REVIEW_DELETE_SUCCESS(HttpStatus.NO_CONTENT,"R003", "리뷰가 성공적으로 삭제되었습니다."),
+ REVIEW_MY_LIST_SUCCESS(HttpStatus.OK, "R004", "내 리뷰 목록 조회에 성공했습니다.");
+
+ private final HttpStatus httpStatus;
+ private final String code;
+ private final String message;
+
+
+
+
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/Handler/ReviewExceptionHandler.java b/week4/demo/src/main/java/com/example/demo/domain/review/Handler/ReviewExceptionHandler.java
new file mode 100644
index 0000000..b74005f
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/Handler/ReviewExceptionHandler.java
@@ -0,0 +1,20 @@
+package com.example.demo.domain.review.handler;
+
+import com.example.demo.domain.review.exception.ReviewException;
+import com.example.demo.global.apipayload.response.ApiResponse;
+import com.example.demo.domain.review.exception.code.ReviewErrorCode;
+import com.example.demo.global.apipayload.response.BaseErrorCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+@RestControllerAdvice
+public class ReviewExceptionHandler {
+
+ @ExceptionHandler(ReviewException.class)
+ public ResponseEntity> handleReviewException(ReviewException e) {
+ BaseErrorCode errorCode =e.getErrorCode();
+ return ApiResponse.ERROR(errorCode);
+ }
+
+
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/controller/ReviewController.java b/week4/demo/src/main/java/com/example/demo/domain/review/controller/ReviewController.java
new file mode 100644
index 0000000..23e12c6
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/controller/ReviewController.java
@@ -0,0 +1,56 @@
+// src/main/java/com/example/demo/controller/ReviewController.java
+package com.example.demo.domain.review.controller;
+
+import com.example.demo.domain.review.dto.ReviewCreateRequest;
+import com.example.demo.domain.review.dto.ReviewDto;
+import com.example.demo.domain.review.dto.ReviewResponse;
+import com.example.demo.domain.review.service.ReviewService;
+import com.example.demo.global.apipayload.response.ApiResponse;
+import com.example.demo.domain.review.exception.code.ReviewSuccessCode;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/api/reviews")
+public class ReviewController {
+
+ private final ReviewService service;
+
+
+ @PostMapping
+ public ResponseEntity> createReview(
+ @Valid @RequestBody ReviewCreateRequest request
+ ){
+ ReviewResponse reviewResponse=service.createReview(request);
+ return ApiResponse.success(ReviewSuccessCode.REVIEW_CREATE_SUCCESS,reviewResponse);
+ }
+ @PutMapping("/{reviewId}")
+ public ResponseEntity> updateReview(
+ @PathVariable Long reviewId,
+ @RequestBody ReviewCreateRequest request
+ ){
+ ReviewResponse reviewResponse = service.updateReview(request.userId(), reviewId, request);
+ return ApiResponse.success(ReviewSuccessCode.REVIEW_UPDATE_SUCCESS,reviewResponse);
+ }
+ @DeleteMapping("/{reviewId}")
+ public ResponseEntity> deleteReview(
+ @PathVariable Long reviewId,
+ @RequestParam Long userId
+ ) {
+ service.deleteReview(reviewId, userId);
+ return ApiResponse.success(ReviewSuccessCode.REVIEW_DELETE_SUCCESS);
+ }
+ @GetMapping("/my")
+ public ResponseEntity>> myReviews(
+ @RequestParam Long userId,
+ @RequestParam(required = false) String marketName,
+ @RequestParam(required = false) Integer starBand
+ ) {
+ List result = service.findMyReviews(userId, marketName, starBand);
+ return ApiResponse.success(ReviewSuccessCode.REVIEW_MY_LIST_SUCCESS, result);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewCreateRequest.java b/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewCreateRequest.java
new file mode 100644
index 0000000..4edb4a5
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewCreateRequest.java
@@ -0,0 +1,21 @@
+package com.example.demo.domain.review.dto;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+import java.math.BigDecimal;
+import java.util.List;
+/*
+* 나는 이 친구로 받을 데이터를 정의
+*/
+public record ReviewCreateRequest(
+ @NotNull @Min(0)
+ Long userId,
+ @NotNull @Min(0)
+ Long marketId,
+ @NotBlank
+ String content,
+ BigDecimal star,
+ List imageUrls // optional
+) {}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewDto.java b/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewDto.java
new file mode 100644
index 0000000..9b08333
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewDto.java
@@ -0,0 +1,17 @@
+package com.example.demo.domain.review.dto;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+import java.math.BigDecimal;
+
+/*
+* 이친구는 이미지는 안뿌려주는 dto에요.
+*/
+public record ReviewDto(
+ Long reviewId,
+ String marketName,
+ String content,
+ BigDecimal star
+) {}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewResponse.java b/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewResponse.java
new file mode 100644
index 0000000..872e9a7
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/dto/ReviewResponse.java
@@ -0,0 +1,16 @@
+package com.example.demo.domain.review.dto;
+
+import java.math.BigDecimal;
+import java.util.List;
+/*
+* 나는 이 친구로 이미지까지 뿌려줄 가장 디테일한 dto를 정의
+* */
+public record ReviewResponse(
+ Long reviewId,
+ Long userId,
+ Long marketId,
+ String marketName,
+ String content,
+ BigDecimal star,
+ List imageUrls
+) {}
\ No newline at end of file
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/entity/Review.java b/week4/demo/src/main/java/com/example/demo/domain/review/entity/Review.java
new file mode 100644
index 0000000..de12504
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/entity/Review.java
@@ -0,0 +1,32 @@
+package com.example.demo.domain.review.entity;
+
+
+import com.example.demo.global.entity.BaseEntity;
+import com.example.demo.domain.member.entity.Users;
+import com.example.demo.domain.restruant.entity.FoodMarket;
+import jakarta.persistence.*;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+@Entity @Table(name = "review")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class Review extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="user_id", nullable=false)
+ private Users user;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="market_id", nullable=false)
+ private FoodMarket market;
+
+ @Column(nullable=false, length=100)
+ private String content;
+
+ @Column(nullable=false, precision=2, scale=1)
+ private BigDecimal star;
+}
+
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/entity/ReviewImage.java b/week4/demo/src/main/java/com/example/demo/domain/review/entity/ReviewImage.java
new file mode 100644
index 0000000..11ff6c4
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/entity/ReviewImage.java
@@ -0,0 +1,19 @@
+package com.example.demo.domain.review.entity;
+
+import com.example.demo.global.entity.BaseEntity;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity @Table(name = "review_image")
+@Getter @Setter @NoArgsConstructor @AllArgsConstructor
+public class ReviewImage extends BaseEntity {
+ @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(optional=false, fetch=FetchType.LAZY)
+ @JoinColumn(name="review_id", nullable=false)
+ private Review review;
+
+ @Column(nullable=false, length=200)
+ private String url;
+}
\ No newline at end of file
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/repository/ReviewImageRepository.java b/week4/demo/src/main/java/com/example/demo/domain/review/repository/ReviewImageRepository.java
new file mode 100644
index 0000000..661c52c
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/repository/ReviewImageRepository.java
@@ -0,0 +1,13 @@
+package com.example.demo.domain.review.repository;
+
+import com.example.demo.domain.review.entity.Review;
+import com.example.demo.domain.review.entity.ReviewImage;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+@Repository
+public interface ReviewImageRepository extends JpaRepository {
+ List findByReview(Review review);
+ void deleteByReview(Review review);
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/repository/ReviewRepository.java b/week4/demo/src/main/java/com/example/demo/domain/review/repository/ReviewRepository.java
new file mode 100644
index 0000000..8c2d098
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/repository/ReviewRepository.java
@@ -0,0 +1,15 @@
+package com.example.demo.domain.review.repository;
+
+import com.example.demo.domain.review.entity.Review;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+@Repository
+public interface ReviewRepository extends JpaRepository {
+ List findReviewsByMarket_IdOrderByUpdatedAtDesc(Long marketId);
+ @Query("SELECT r from Review r join fetch r.user where r.market.id=:marketId order by r.updatedAt desc ")
+ List findReviewsByMarket_IdOrderByUpdatedAtDesc2(@Param("marketId") Long marketId);
+}
diff --git a/week4/demo/src/main/java/com/example/demo/domain/review/service/ReviewService.java b/week4/demo/src/main/java/com/example/demo/domain/review/service/ReviewService.java
new file mode 100644
index 0000000..172c51a
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/domain/review/service/ReviewService.java
@@ -0,0 +1,172 @@
+package com.example.demo.domain.review.service;
+
+import com.example.demo.domain.member.entity.Users;
+import com.example.demo.domain.member.repository.UsersRepository;
+import com.example.demo.domain.restruant.entity.FoodMarket;
+import com.example.demo.domain.restruant.entity.FoodMarketRepository;
+import com.example.demo.domain.review.dto.ReviewCreateRequest;
+import com.example.demo.domain.review.dto.ReviewDto;
+import com.example.demo.domain.review.dto.ReviewResponse;
+import com.example.demo.domain.review.entity.Review;
+import com.example.demo.domain.review.entity.ReviewImage;
+import com.example.demo.domain.review.repository.ReviewImageRepository;
+import com.example.demo.domain.review.repository.ReviewRepository;
+import com.example.demo.domain.review.exception.ReviewException;
+import com.example.demo.domain.review.exception.code.ReviewErrorCode;
+import com.querydsl.core.types.Projections;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import jakarta.transaction.Transactional;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.example.demo.domain.restruant.entity.QFoodMarket.foodMarket;
+import static com.example.demo.domain.review.entity.QReview.review;
+
+
+@Service
+@AllArgsConstructor
+public class ReviewService {
+ private final JPAQueryFactory qf;
+ private final ReviewRepository reviewRepository;
+ private final ReviewImageRepository reviewImageRepository;
+ private final UsersRepository usersRepository;
+ private final FoodMarketRepository foodMarketRepository;
+
+ @Transactional
+ public ReviewResponse createReview(ReviewCreateRequest request){
+ Users user=usersRepository.findById(request.userId())
+ .orElseThrow(()->new ReviewException(ReviewErrorCode.REVIEW_CREATE_ERROR));
+ FoodMarket market = foodMarketRepository.findById(request.marketId())
+ .orElseThrow(()->new ReviewException(ReviewErrorCode.REVIEW_CREATE_ERROR));
+ Review reviewEntity = new Review();
+ reviewEntity.setUser(user);
+ reviewEntity.setMarket(market);
+ reviewEntity.setContent(request.content());
+ reviewEntity.setStar(request.star());
+
+ Review saved = reviewRepository.save(reviewEntity);
+
+ if(request.imageUrls()!=null&& !request.imageUrls().isEmpty()){
+ for(String url:request.imageUrls()){
+ ReviewImage reviewImage=new ReviewImage();
+ reviewImage.setReview(saved);
+ reviewImage.setUrl(url);
+ reviewImageRepository.save(reviewImage);
+ }
+ }
+
+ return toResponse(saved);
+ }
+
+ @Transactional
+ public ReviewResponse updateReview(Long reviewId, Long userId,ReviewCreateRequest request) {
+
+ Review reviewEntity = reviewRepository.findById(reviewId)
+ .orElseThrow(()->new ReviewException(ReviewErrorCode.REVIEW_UPDATE_ERROR));
+
+ if(!reviewEntity.getUser().getId().equals(userId)){
+ new ReviewException(ReviewErrorCode.REVIEW_UPDATE_ERROR);
+ }
+
+ reviewEntity.setContent(request.content());
+ reviewEntity.setStar(request.star());
+
+ reviewImageRepository.deleteByReview(reviewEntity);
+
+ if(request.imageUrls()!=null&& !request.imageUrls().isEmpty()){
+ for(String url:request.imageUrls()){
+ ReviewImage reviewImage=new ReviewImage();
+ reviewImage.setReview(reviewEntity);
+ reviewImage.setUrl(url);
+ reviewImageRepository.save(reviewImage);
+ }
+ }
+ return toResponse(reviewEntity);
+ }
+ public void deleteReview(Long reviewId,Long userId) {
+
+ Review reviewEntity = reviewRepository.findById(reviewId)
+ .orElseThrow(()->new ReviewException(ReviewErrorCode.REVIEW_DELETE_ERROR));
+
+ if(!reviewEntity.getUser().getId().equals(userId)){
+ throw new ReviewException(ReviewErrorCode.REVIEW_DELETE_ERROR);
+ }
+ reviewRepository.delete(reviewEntity);
+ reviewImageRepository.deleteByReview(reviewEntity);
+ }
+
+ public List findMyReviews(Long userId, String marketName, Integer starBand) {
+ // userId should be required; validate if needed
+ return qf.select(Projections.constructor(ReviewDto.class,
+ review.id,
+ review.market.name,
+ review.content,
+ review.star))
+ .from(review)
+ .join(review.market, foodMarket)
+ .where(
+ review.user.id.eq(userId),
+ marketNameEquals(marketName),
+ starBandPredicate(starBand)
+ )
+ .orderBy(review.id.desc())
+ .fetch();
+ }
+
+ public List findReviewsByMarket(Long marketId, Integer starBand) {
+ List reviews = qf.selectFrom(review)
+ .leftJoin(review.market).fetchJoin()
+ .leftJoin(review.user).fetchJoin()
+ .where(
+ review.market.id.eq(marketId),
+ starBandPredicate(starBand)
+ )
+ .orderBy(review.id.desc())
+ .fetch();
+
+ return reviews.stream()
+ .map(this::toResponse)
+ .toList();
+ }
+
+ private BooleanExpression starBandPredicate(Integer band) {
+ if (band == null) return null;
+ if (band == 5) return review.star.eq(new BigDecimal("5.0"));
+ if (band >= 0 && band <= 4) {
+ BigDecimal min = new BigDecimal(band + ".0");
+ BigDecimal max = new BigDecimal((band + 1) + ".0");
+ return review.star.goe(min).and(review.star.lt(max));
+ }
+ return null;
+ }
+ private BooleanExpression marketNameEquals(String marketName) {
+ return (marketName == null || marketName.isBlank())
+ ? null
+ : review.market.name.eq(marketName);
+ }
+ private ReviewResponse toResponse(Review reviewEntity) {
+ List urls = reviewEntity.getId() == null
+ ? Collections.emptyList()
+ : reviewImageRepository.findByReview(reviewEntity)
+ .stream()
+ .map(ReviewImage::getUrl)
+ .toList();
+
+ return new ReviewResponse(
+ reviewEntity.getId(),
+ reviewEntity.getUser().getId(),
+ reviewEntity.getMarket().getId(),
+ reviewEntity.getMarket().getName(),
+ reviewEntity.getContent(),
+ reviewEntity.getStar(),
+ urls
+ );
+ }
+}
+
diff --git a/week4/demo/src/main/java/com/example/demo/global/apiPayload/Exception/GeneralException.java b/week4/demo/src/main/java/com/example/demo/global/apiPayload/Exception/GeneralException.java
new file mode 100644
index 0000000..5a44549
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/apiPayload/Exception/GeneralException.java
@@ -0,0 +1,16 @@
+package com.example.demo.global.apipayload.exception;
+
+import com.example.demo.global.apipayload.response.BaseErrorCode;
+import lombok.Getter;
+
+@Getter
+public class GeneralException extends RuntimeException {
+
+ private final BaseErrorCode errorCode;
+
+ public GeneralException(BaseErrorCode errorCode) {
+ super(errorCode.getMessage());
+ this.errorCode = errorCode;
+ }
+
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/ApiResponse.java b/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/ApiResponse.java
new file mode 100644
index 0000000..5234e46
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/ApiResponse.java
@@ -0,0 +1,60 @@
+
+package com.example.demo.global.apipayload.response;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.http.ResponseEntity;
+@Getter
+@AllArgsConstructor
+public class ApiResponse {
+
+ private final boolean success; // 성공 여부
+ private final String code; // 비즈니스 코드 (예: R001)
+ private final String message; // 기본 메시지
+ private final T data; // 실제 데이터 (ReviewResponse, List 등)
+
+
+
+ // data 있는 성공
+ public static ResponseEntity> success(BaseSuccessCode code, T data) {
+ ApiResponse body = new ApiResponse<>(
+ true,
+ code.getCode(),
+ code.getMessage(),
+ data
+ );
+ return ResponseEntity.status(code.getHttpStatus()).body(body);
+ }
+
+ // data 없는 성공 (DELETE 등)
+ public static ResponseEntity> success(BaseSuccessCode code) {
+ ApiResponse body = new ApiResponse<>(
+ true,
+ code.getCode(),
+ code.getMessage(),
+ null
+ );
+ return ResponseEntity.status(code.getHttpStatus()).body(body);
+ }
+ // data 있는 실패
+ public static ResponseEntity> ERROR(BaseErrorCode code, T data) {
+ ApiResponse body = new ApiResponse<>(
+ false,
+ code.getCode(),
+ code.getMessage(),
+ data
+ );
+
+ return ResponseEntity.status(code.getHttpStatus()).body(body);
+ }
+
+ // data 없는 실패(DELETE 등)
+ public static ResponseEntity> ERROR(BaseErrorCode code) {
+ ApiResponse body = new ApiResponse<>(
+ false,
+ code.getCode(),
+ code.getMessage(),
+ null
+ );
+ return ResponseEntity.status(code.getHttpStatus()).body(body);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/BaseErrorCode.java b/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/BaseErrorCode.java
new file mode 100644
index 0000000..9f1693e
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/BaseErrorCode.java
@@ -0,0 +1,9 @@
+package com.example.demo.global.apipayload.response;
+
+import org.springframework.http.HttpStatus;
+
+public interface BaseErrorCode {
+ HttpStatus getHttpStatus();
+ String getMessage();
+ String getCode();
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/BaseSuccessCode.java b/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/BaseSuccessCode.java
new file mode 100644
index 0000000..35e2015
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/apiPayload/response/BaseSuccessCode.java
@@ -0,0 +1,8 @@
+package com.example.demo.global.apipayload.response;
+import org.springframework.http.HttpStatus;
+
+public interface BaseSuccessCode {
+ HttpStatus getHttpStatus();
+ String getMessage();
+ String getCode();
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/config/QuerydslConfig.java b/week4/demo/src/main/java/com/example/demo/global/config/QuerydslConfig.java
new file mode 100644
index 0000000..a54c2ed
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/config/QuerydslConfig.java
@@ -0,0 +1,15 @@
+package com.example.demo.global.config;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import jakarta.persistence.EntityManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class QuerydslConfig {
+
+ @Bean
+ public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+ return new JPAQueryFactory(entityManager);
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/config/SwaggerConfig.java b/week4/demo/src/main/java/com/example/demo/global/config/SwaggerConfig.java
new file mode 100644
index 0000000..914ff7b
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/config/SwaggerConfig.java
@@ -0,0 +1,19 @@
+package com.example.demo.global.config;
+
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.OpenAPI;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public OpenAPI openAPI() {
+ return new OpenAPI()
+ .info(new Info()
+ .title("Project API")
+ .description("Project Swagger")
+ .version("0.0.1"));
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/entity/BaseEntity.java b/week4/demo/src/main/java/com/example/demo/global/entity/BaseEntity.java
new file mode 100644
index 0000000..282a93e
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/entity/BaseEntity.java
@@ -0,0 +1,25 @@
+package com.example.demo.global.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
+import lombok.Getter;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import java.time.Instant;
+
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)//이걸 해야 저게 자동 기록됨.
+@Getter
+public abstract class BaseEntity {
+
+ @CreatedDate
+ @Column(nullable = false, updatable = false)
+ private Instant createdAt;
+
+ @LastModifiedDate
+ @Column(nullable = false)
+ private Instant updatedAt;
+}
\ No newline at end of file
diff --git a/week4/demo/src/main/java/com/example/demo/global/validation/PageValidator.java b/week4/demo/src/main/java/com/example/demo/global/validation/PageValidator.java
new file mode 100644
index 0000000..83cc097
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/validation/PageValidator.java
@@ -0,0 +1,14 @@
+package com.example.demo.global.validation;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+public class PageValidator implements ConstraintValidator {
+
+ @Override
+ public boolean isValid(Integer value, ConstraintValidatorContext context) {
+ // null 은 defaultValue 로 대체되니까 허용
+ if (value == null) return true;
+ return value >= 1;
+ }
+}
diff --git a/week4/demo/src/main/java/com/example/demo/global/validation/ValidPage.java b/week4/demo/src/main/java/com/example/demo/global/validation/ValidPage.java
new file mode 100644
index 0000000..798a9df
--- /dev/null
+++ b/week4/demo/src/main/java/com/example/demo/global/validation/ValidPage.java
@@ -0,0 +1,19 @@
+package com.example.demo.global.validation;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.*;
+
+@Documented
+@Target({ ElementType.PARAMETER, ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = PageValidator.class)
+public @interface ValidPage {
+
+ String message() default "page must be greater than or equal to 1";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+}
diff --git a/week4/demo/src/main/resources/application.yml b/week4/demo/src/main/resources/application.yml
new file mode 100644
index 0000000..af2abb9
--- /dev/null
+++ b/week4/demo/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+spring:
+ application:
+ name :demo
+
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/market
+ username: root
+ password: 9897
+
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ database: mysql
+ database-platform: org.hibernate.dialect.MySQLDialect
+ show-sql: true
\ No newline at end of file
diff --git a/week4/demo/src/test/java/com/example/demo/DemoApplicationTests.java b/week4/demo/src/test/java/com/example/demo/DemoApplicationTests.java
new file mode 100644
index 0000000..2778a6a
--- /dev/null
+++ b/week4/demo/src/test/java/com/example/demo/DemoApplicationTests.java
@@ -0,0 +1,13 @@
+package com.example.demo;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class DemoApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}