diff --git "a/0\354\243\274\354\260\250\353\257\270\354\205\230/0\354\243\274\354\260\250\353\257\270\354\205\230_\352\263\240\352\262\275\354\210\230.png" "b/0\354\243\274\354\260\250\353\257\270\354\205\230/0\354\243\274\354\260\250\353\257\270\354\205\230_\352\263\240\352\262\275\354\210\230.png" new file mode 100644 index 0000000..6d4c828 Binary files /dev/null and "b/0\354\243\274\354\260\250\353\257\270\354\205\230/0\354\243\274\354\260\250\353\257\270\354\205\230_\352\263\240\352\262\275\354\210\230.png" differ diff --git "a/0\354\243\274\354\260\250\353\257\270\354\205\230/hello.vuerd.json" "b/0\354\243\274\354\260\250\353\257\270\354\205\230/hello.vuerd.json" new file mode 100644 index 0000000..4737311 --- /dev/null +++ "b/0\354\243\274\354\260\250\353\257\270\354\205\230/hello.vuerd.json" @@ -0,0 +1,2976 @@ +{ + "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json", + "version": "3.0.0", + "settings": { + "width": 2000, + "height": 2000, + "scrollTop": -750.1514, + "scrollLeft": -373.2146, + "zoomLevel": 0.71, + "show": 431, + "database": 4, + "databaseName": "", + "canvasType": "ERD", + "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": [ + "SW3qSYcmcjF6fxRxL9C6W", + "EnyjPta3Re1to-0Ow_c0k", + "3sN0z9wOs5gRHtxPeLnNL", + "cRAlxAB1DS5CVIqN0YxH_", + "FbuZsipRsYu9OgkLtQvRH", + "U-iS9P7j9_XRpt7sokj1u", + "WzdtVxFyk4teMt3xZ8E-8", + "1m_s3Ee0XEsle86A1W9bt", + "QF-n1pnH466PZnRVifDEq", + "66s2GuXAH4IsvhVcW9jiZ", + "uc2j1aS_eaeKNmWqHIhjm", + "DCLfTq9CxYAqBLL9O5uV-" + ], + "relationshipIds": [ + "x5KesGnxWH7BV5U7SRUKf", + "eQ0Udv6TLmj-vhXKBJS9z", + "QHb8FIR-OWcNh55-2nQS3", + "MD92WiyC8by1qAm4I_XSo", + "hlb49ERgeQ1Wz6x78DVes", + "GOioAAFNsn_A5uVd9veiB", + "1bzWUSllbdQW-XkNzsFDL", + "hMvmzUWSPDWpRGtzvjQ_H", + "MNEmKtrdfaxLY7ulw1tGy", + "dxZac9LiwDnVGUoVgGBK_", + "t4n5Tkjog7EkRQY3WzvHA", + "jkWOiJoVYIrCuKnZXEIEX" + ], + "indexIds": [], + "memoIds": [] + }, + "collections": { + "tableEntities": { + "SW3qSYcmcjF6fxRxL9C6W": { + "id": "SW3qSYcmcjF6fxRxL9C6W", + "name": "User", + "comment": "", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd", + "PbYSFy11QfczYemBl6sN8", + "IyuLsAKxYBgglcCueMciJ", + "j6FYMzLSV8ObnYo640OZW", + "aRwenG0xV5S3tVe_HxfGr", + "c8eBgjgL943BPvdbqGlNx", + "2F4WIGFoal0Pfq5aKWJGR", + "EMxDdIiEeIocCGxgzqdY2" + ], + "seqColumnIds": [ + "FdInoxrEYMeqkCxyYQlEd", + "PbYSFy11QfczYemBl6sN8", + "IyuLsAKxYBgglcCueMciJ", + "j6FYMzLSV8ObnYo640OZW", + "aRwenG0xV5S3tVe_HxfGr", + "c8eBgjgL943BPvdbqGlNx", + "2F4WIGFoal0Pfq5aKWJGR", + "EMxDdIiEeIocCGxgzqdY2", + "qwK66PRnyh6wZ7ebcTFIa", + "unGW2Vhu7oVMAbbyb_uU8", + "IPxLAdyOC8VYOSQBns0-W", + "o3xi1phrDUYwXoYMLJvNP", + "QbmabyqvWYXUzYuw76G_K", + "aVEC_JkxE3fKxtkaAGdo_", + "Yymdw8aFEqAAoh_FDoQnk" + ], + "ui": { + "x": 692.698, + "y": 898.2007, + "zIndex": 2, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758454295079, + "createAt": 1758265403229 + } + }, + "cRAlxAB1DS5CVIqN0YxH_": { + "id": "cRAlxAB1DS5CVIqN0YxH_", + "name": "Mission", + "comment": "", + "columnIds": [ + "NG68JYiPiJ-OOvG6HiKys", + "x6gCvhNKdcN5NiRG-o2V1", + "wF8OtNJBqHscI54r3JBWY", + "JGl101X01OJCUEt54mALi", + "VwpFfMO3n1vJKpvDTSr3C", + "iOfII2VWsbj9mzzIOT53n", + "Ot0SIC-v-b6RBocUOwIHc", + "NvKLpAdjRzQgpUoTUmIou" + ], + "seqColumnIds": [ + "NG68JYiPiJ-OOvG6HiKys", + "x6gCvhNKdcN5NiRG-o2V1", + "wF8OtNJBqHscI54r3JBWY", + "JGl101X01OJCUEt54mALi", + "VwpFfMO3n1vJKpvDTSr3C", + "iOfII2VWsbj9mzzIOT53n", + "P9cavuKJGE8NxA5pnr2w_", + "87sEqz5-srk0VbG4yiWb2", + "Ot0SIC-v-b6RBocUOwIHc", + "NvKLpAdjRzQgpUoTUmIou" + ], + "ui": { + "x": 1651.263, + "y": 575.2659, + "zIndex": 129, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758456876153, + "createAt": 1758267265581 + } + }, + "e6c3szi_ECXJt-WIBHtd_": { + "id": "e6c3szi_ECXJt-WIBHtd_", + "name": "User_mission", + "comment": "", + "columnIds": [ + "QJfGm1SMwEAxzNijipiM4", + "wyBJqunyME-zl1-De524y", + "Dt2MU1hvPB0ihSmrydMny" + ], + "seqColumnIds": [ + "o8VGEZ3v_LBPKvH8ZdawX", + "QJfGm1SMwEAxzNijipiM4", + "7CiWEnTk56G5_bVXZb7B1", + "O4m50Mtp1WSycEW6FyxTF", + "egLozO6_0r3FSIp9c5Wpn", + "k-eAvJsD6k9GBQMdmy37y", + "wyBJqunyME-zl1-De524y", + "Dt2MU1hvPB0ihSmrydMny", + "fyCBGRc-o-aQC-EQBLTv3" + ], + "ui": { + "x": 807.5343, + "y": 393.1509, + "zIndex": 220, + "widthName": 78, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758290558424, + "createAt": 1758267910532 + } + }, + "EnyjPta3Re1to-0Ow_c0k": { + "id": "EnyjPta3Re1to-0Ow_c0k", + "name": "User_mission", + "comment": "", + "columnIds": [ + "0vodZWX_87XT9VbutIozY", + "Inuqs8_WWLi2BSn2yGcR8", + "LYVABXr59kbCKrzUnqLHM", + "1S2A9Kj1q0Zg1NjLnMd4x" + ], + "seqColumnIds": [ + "BTWnyvg3l8OqAD1jo6ns0", + "StKAxk-_jDp-US4C117il", + "dQFMxc6o44lnTzRBkWRX1", + "0vodZWX_87XT9VbutIozY", + "Inuqs8_WWLi2BSn2yGcR8", + "LYVABXr59kbCKrzUnqLHM", + "0W8hNlGVp449tH6a85DH8", + "1S2A9Kj1q0Zg1NjLnMd4x" + ], + "ui": { + "x": 669.9466, + "y": 597.6242, + "zIndex": 332, + "widthName": 78, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758455572842, + "createAt": 1758292158564 + } + }, + "3sN0z9wOs5gRHtxPeLnNL": { + "id": "3sN0z9wOs5gRHtxPeLnNL", + "name": "Store", + "comment": "", + "columnIds": [ + "AfeYLVYuvV8Wl821feyp7", + "bbfng-98l1GftLFmZFiJz", + "hIRya3_Znn8A6lvR0oQKD", + "7loiKwFcsBh-V-HXDU4qt" + ], + "seqColumnIds": [ + "AfeYLVYuvV8Wl821feyp7", + "bbfng-98l1GftLFmZFiJz", + "hIRya3_Znn8A6lvR0oQKD", + "mMOcNIkrGjGfNRqn9YDzj", + "nRMf6gQpPmzyCevOtE4-8", + "7loiKwFcsBh-V-HXDU4qt" + ], + "ui": { + "x": 1652.6988, + "y": 969.3323, + "zIndex": 402, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371770324, + "createAt": 1758292692269 + } + }, + "FbuZsipRsYu9OgkLtQvRH": { + "id": "FbuZsipRsYu9OgkLtQvRH", + "name": "location", + "comment": "", + "columnIds": [ + "H4d48_Pb2KZNa64mBsDPG", + "HdJ2IUlX-_VuFBeKr-A5F" + ], + "seqColumnIds": [ + "H4d48_Pb2KZNa64mBsDPG", + "iN3tJldWUJdh77mvXWKi9", + "HdJ2IUlX-_VuFBeKr-A5F" + ], + "ui": { + "x": 1686.6967, + "y": 1343.0536, + "zIndex": 474, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371772349, + "createAt": 1758292911987 + } + }, + "U-iS9P7j9_XRpt7sokj1u": { + "id": "U-iS9P7j9_XRpt7sokj1u", + "name": "User_term", + "comment": "유저 약관", + "columnIds": [ + "xsiBCEQ2379F08SJM44Q2", + "oO6A3-YfUIYuwwbbHAwak" + ], + "seqColumnIds": [ + "xsiBCEQ2379F08SJM44Q2", + "DyOGo0HbdYNLl3F6fFJoV", + "uN4FKxyEb5gnf7VCsybG5", + "mxT0CemzkBll17N1CaQca", + "ouZqKosGANQxQuTaKY53B", + "oO6A3-YfUIYuwwbbHAwak" + ], + "ui": { + "x": 243.8593, + "y": 907.7111, + "zIndex": 534, + "widthName": 61, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371793459, + "createAt": 1758293071658 + } + }, + "WzdtVxFyk4teMt3xZ8E-8": { + "id": "WzdtVxFyk4teMt3xZ8E-8", + "name": "Term", + "comment": "약관", + "columnIds": [ + "aDfTa3M4EJInboYW1AwJY", + "CAHkq77oQC59cHUkF9lfd" + ], + "seqColumnIds": [ + "JOnYVd6w4ZQXXijAEVaCG", + "aDfTa3M4EJInboYW1AwJY", + "CAHkq77oQC59cHUkF9lfd", + "s02QSeo8dSiozImiCBNFi", + "qN1PeVGlmvL6ncOuxtWr5" + ], + "ui": { + "x": 264.2814, + "y": 1253.7228, + "zIndex": 573, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371797226, + "createAt": 1758293158363 + } + }, + "1m_s3Ee0XEsle86A1W9bt": { + "id": "1m_s3Ee0XEsle86A1W9bt", + "name": "Review", + "comment": "", + "columnIds": [ + "EInU_RFu6bLHrzU6Q0B7n", + "55SgG8fQHW7ou5JDkXvKA", + "0zdmPP4N-6oeuWmSY2VKl", + "k7258dBY3kP6fzWqjW50q", + "dIZqWQhwiiNrzz92BX0G8", + "miNr6v2QE9PGLzoUleK6j" + ], + "seqColumnIds": [ + "ubc2_bhpYhqglGT5scNC3", + "EInU_RFu6bLHrzU6Q0B7n", + "55SgG8fQHW7ou5JDkXvKA", + "0zdmPP4N-6oeuWmSY2VKl", + "k7258dBY3kP6fzWqjW50q", + "dIZqWQhwiiNrzz92BX0G8", + "miNr6v2QE9PGLzoUleK6j", + "ui_LT2XpbGmhIg5fAS2Pi", + "D8BdbHLmKJm3uyowygI7N" + ], + "ui": { + "x": 1190.2824, + "y": 991.2705, + "zIndex": 665, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758453111994, + "createAt": 1758293542228 + } + }, + "QF-n1pnH466PZnRVifDEq": { + "id": "QF-n1pnH466PZnRVifDEq", + "name": "User_food", + "comment": "", + "columnIds": [ + "ypBgbxrNHcFXsICZQcZ2H", + "e1w70_JR6SjRa9NgGo_A5", + "-EseBZUybBvjTNt06vdVn" + ], + "seqColumnIds": [ + "ypBgbxrNHcFXsICZQcZ2H", + "e1w70_JR6SjRa9NgGo_A5", + "dXSf5AH3ZE3-iEAjP4chh", + "-EseBZUybBvjTNt06vdVn" + ], + "ui": { + "x": 715.3327, + "y": 1248.6227, + "zIndex": 735, + "widthName": 61, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371798997, + "createAt": 1758370772406 + } + }, + "-XsTDrkVEFThAtw5o3ffB": { + "id": "-XsTDrkVEFThAtw5o3ffB", + "name": "Food", + "comment": "", + "columnIds": [ + "_7FVj3JXN-FWncfuCO9fc", + "6lcMP4yv3KRQvHJT-VnXw" + ], + "seqColumnIds": [ + "_7FVj3JXN-FWncfuCO9fc", + "6lcMP4yv3KRQvHJT-VnXw", + "6qUzoeD5b_nQwCop5-hwf" + ], + "ui": { + "x": 682.8064, + "y": 1524.454, + "zIndex": 765, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758370916296, + "createAt": 1758370851007 + } + }, + "66s2GuXAH4IsvhVcW9jiZ": { + "id": "66s2GuXAH4IsvhVcW9jiZ", + "name": "Food", + "comment": "", + "columnIds": [ + "pDRsYIOgYuYMV6I6tKbxF", + "fsqZCVY3nKxoVoEII8SK-" + ], + "seqColumnIds": [ + "pDRsYIOgYuYMV6I6tKbxF", + "tkg6jpNA4gsXXB_wFOmZI", + "fsqZCVY3nKxoVoEII8SK-" + ], + "ui": { + "x": 652.1287, + "y": 1461.6571, + "zIndex": 777, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371805367, + "createAt": 1758370922625 + } + }, + "uc2j1aS_eaeKNmWqHIhjm": { + "id": "uc2j1aS_eaeKNmWqHIhjm", + "name": "Review_photo", + "comment": "", + "columnIds": [ + "1ULvVjxodU2ux6IItl1wu", + "yWEtyzLaYRh-GxP2rBAbN", + "UAN06zaHoneXEx6Zdkeim" + ], + "seqColumnIds": [ + "1ULvVjxodU2ux6IItl1wu", + "yWEtyzLaYRh-GxP2rBAbN", + "UAN06zaHoneXEx6Zdkeim" + ], + "ui": { + "x": 1167.0787, + "y": 1326.5081, + "zIndex": 821, + "widthName": 81, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758371521008, + "createAt": 1758371002822 + } + }, + "DCLfTq9CxYAqBLL9O5uV-": { + "id": "DCLfTq9CxYAqBLL9O5uV-", + "name": "Review_reply", + "comment": "", + "columnIds": [ + "rqOwtDSa3ID9yipZKxdHy", + "sgfvuuZDNafwtoRn92ETX", + "EvmR_T3qUbZVAAWd_4fc-" + ], + "seqColumnIds": [ + "rqOwtDSa3ID9yipZKxdHy", + "sgfvuuZDNafwtoRn92ETX", + "EvmR_T3qUbZVAAWd_4fc-" + ], + "ui": { + "x": 1195.2408, + "y": 801.8977, + "zIndex": 869, + "widthName": 76, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1758452986555, + "createAt": 1758371143408 + } + } + }, + "tableColumnEntities": { + "FdInoxrEYMeqkCxyYQlEd": { + "id": "FdInoxrEYMeqkCxyYQlEd", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 2, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267191583, + "createAt": 1758265422086 + } + }, + "PbYSFy11QfczYemBl6sN8": { + "id": "PbYSFy11QfczYemBl6sN8", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "email", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267190704, + "createAt": 1758265422908 + } + }, + "j6FYMzLSV8ObnYo640OZW": { + "id": "j6FYMzLSV8ObnYo640OZW", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "name", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267166778, + "createAt": 1758265423283 + } + }, + "IyuLsAKxYBgglcCueMciJ": { + "id": "IyuLsAKxYBgglcCueMciJ", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "password", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267156685, + "createAt": 1758265423480 + } + }, + "aRwenG0xV5S3tVe_HxfGr": { + "id": "aRwenG0xV5S3tVe_HxfGr", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "point", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267178236, + "createAt": 1758267092940 + } + }, + "2F4WIGFoal0Pfq5aKWJGR": { + "id": "2F4WIGFoal0Pfq5aKWJGR", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "creater_at", + "comment": "", + "dataType": "DATETIME", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 61, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267184778, + "createAt": 1758267093429 + } + }, + "EMxDdIiEeIocCGxgzqdY2": { + "id": "EMxDdIiEeIocCGxgzqdY2", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "updated_at", + "comment": "", + "dataType": "DATETIME", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 67, + "widthComment": 60, + "widthDataType": 61, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267186531, + "createAt": 1758267116062 + } + }, + "qwK66PRnyh6wZ7ebcTFIa": { + "id": "qwK66PRnyh6wZ7ebcTFIa", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267199296, + "createAt": 1758267199296 + } + }, + "NG68JYiPiJ-OOvG6HiKys": { + "id": "NG68JYiPiJ-OOvG6HiKys", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "mission_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292339893, + "createAt": 1758267269917 + } + }, + "x6gCvhNKdcN5NiRG-o2V1": { + "id": "x6gCvhNKdcN5NiRG-o2V1", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "title", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267717899, + "createAt": 1758267270691 + } + }, + "wF8OtNJBqHscI54r3JBWY": { + "id": "wF8OtNJBqHscI54r3JBWY", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "description", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 65, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267709501, + "createAt": 1758267270911 + } + }, + "JGl101X01OJCUEt54mALi": { + "id": "JGl101X01OJCUEt54mALi", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "point", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267705329, + "createAt": 1758267271091 + } + }, + "VwpFfMO3n1vJKpvDTSr3C": { + "id": "VwpFfMO3n1vJKpvDTSr3C", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "created_at", + "comment": "", + "dataType": "DATETIME", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 63, + "widthComment": 60, + "widthDataType": 61, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267697531, + "createAt": 1758267666779 + } + }, + "iOfII2VWsbj9mzzIOT53n": { + "id": "iOfII2VWsbj9mzzIOT53n", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "updated_at", + "comment": "", + "dataType": "DATETIME", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 67, + "widthComment": 60, + "widthDataType": 61, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290512081, + "createAt": 1758267667303 + } + }, + "unGW2Vhu7oVMAbbyb_uU8": { + "id": "unGW2Vhu7oVMAbbyb_uU8", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "mission_id", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267838696, + "createAt": 1758267838695 + } + }, + "P9cavuKJGE8NxA5pnr2w_": { + "id": "P9cavuKJGE8NxA5pnr2w_", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267887525, + "createAt": 1758267887525 + } + }, + "IPxLAdyOC8VYOSQBns0-W": { + "id": "IPxLAdyOC8VYOSQBns0-W", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "mission_id", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758267899656, + "createAt": 1758267899656 + } + }, + "QJfGm1SMwEAxzNijipiM4": { + "id": "QJfGm1SMwEAxzNijipiM4", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "is_complete", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 70, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290045198, + "createAt": 1758289793648 + } + }, + "7CiWEnTk56G5_bVXZb7B1": { + "id": "7CiWEnTk56G5_bVXZb7B1", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "description", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 65, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758289885652, + "createAt": 1758289793848 + } + }, + "O4m50Mtp1WSycEW6FyxTF": { + "id": "O4m50Mtp1WSycEW6FyxTF", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758289794072, + "createAt": 1758289794072 + } + }, + "egLozO6_0r3FSIp9c5Wpn": { + "id": "egLozO6_0r3FSIp9c5Wpn", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758289794285, + "createAt": 1758289794285 + } + }, + "k-eAvJsD6k9GBQMdmy37y": { + "id": "k-eAvJsD6k9GBQMdmy37y", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758289794472, + "createAt": 1758289794472 + } + }, + "wyBJqunyME-zl1-De524y": { + "id": "wyBJqunyME-zl1-De524y", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290518710, + "createAt": 1758290122020 + } + }, + "o3xi1phrDUYwXoYMLJvNP": { + "id": "o3xi1phrDUYwXoYMLJvNP", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290123213, + "createAt": 1758290123213 + } + }, + "87sEqz5-srk0VbG4yiWb2": { + "id": "87sEqz5-srk0VbG4yiWb2", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290495131, + "createAt": 1758290132755 + } + }, + "o8VGEZ3v_LBPKvH8ZdawX": { + "id": "o8VGEZ3v_LBPKvH8ZdawX", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "mission_id", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290506316, + "createAt": 1758290506316 + } + }, + "Dt2MU1hvPB0ihSmrydMny": { + "id": "Dt2MU1hvPB0ihSmrydMny", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "mission_id", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290515492, + "createAt": 1758290515492 + } + }, + "fyCBGRc-o-aQC-EQBLTv3": { + "id": "fyCBGRc-o-aQC-EQBLTv3", + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290535920, + "createAt": 1758290535919 + } + }, + "QbmabyqvWYXUzYuw76G_K": { + "id": "QbmabyqvWYXUzYuw76G_K", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290536668, + "createAt": 1758290536668 + } + }, + "aVEC_JkxE3fKxtkaAGdo_": { + "id": "aVEC_JkxE3fKxtkaAGdo_", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758290552767, + "createAt": 1758290552767 + } + }, + "LYVABXr59kbCKrzUnqLHM": { + "id": "LYVABXr59kbCKrzUnqLHM", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "is_complete", + "comment": "", + "dataType": "BOOLEAN", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 70, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292378420, + "createAt": 1758292176706 + } + }, + "BTWnyvg3l8OqAD1jo6ns0": { + "id": "BTWnyvg3l8OqAD1jo6ns0", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292177058, + "createAt": 1758292177058 + } + }, + "StKAxk-_jDp-US4C117il": { + "id": "StKAxk-_jDp-US4C117il", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292177231, + "createAt": 1758292177231 + } + }, + "dQFMxc6o44lnTzRBkWRX1": { + "id": "dQFMxc6o44lnTzRBkWRX1", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292191903, + "createAt": 1758292191903 + } + }, + "Yymdw8aFEqAAoh_FDoQnk": { + "id": "Yymdw8aFEqAAoh_FDoQnk", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292193405, + "createAt": 1758292193405 + } + }, + "0vodZWX_87XT9VbutIozY": { + "id": "0vodZWX_87XT9VbutIozY", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292288369, + "createAt": 1758292288369 + } + }, + "Inuqs8_WWLi2BSn2yGcR8": { + "id": "Inuqs8_WWLi2BSn2yGcR8", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "mission_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292339893, + "createAt": 1758292325129 + } + }, + "0W8hNlGVp449tH6a85DH8": { + "id": "0W8hNlGVp449tH6a85DH8", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292468361, + "createAt": 1758292468361 + } + }, + "bbfng-98l1GftLFmZFiJz": { + "id": "bbfng-98l1GftLFmZFiJz", + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "name": "store_name", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 69, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293038283, + "createAt": 1758292755585 + } + }, + "hIRya3_Znn8A6lvR0oQKD": { + "id": "hIRya3_Znn8A6lvR0oQKD", + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "name": "detail_address", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 85, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293045826, + "createAt": 1758292755934 + } + }, + "mMOcNIkrGjGfNRqn9YDzj": { + "id": "mMOcNIkrGjGfNRqn9YDzj", + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "name": "location_id", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 64, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292907905, + "createAt": 1758292756113 + } + }, + "nRMf6gQpPmzyCevOtE4-8": { + "id": "nRMf6gQpPmzyCevOtE4-8", + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "name": "mission_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292783115, + "createAt": 1758292783115 + } + }, + "AfeYLVYuvV8Wl821feyp7": { + "id": "AfeYLVYuvV8Wl821feyp7", + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "name": "store_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292870691, + "createAt": 1758292810918 + } + }, + "NvKLpAdjRzQgpUoTUmIou": { + "id": "NvKLpAdjRzQgpUoTUmIou", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "store_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292870691, + "createAt": 1758292812173 + } + }, + "iN3tJldWUJdh77mvXWKi9": { + "id": "iN3tJldWUJdh77mvXWKi9", + "tableId": "FbuZsipRsYu9OgkLtQvRH", + "name": "location_id", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 64, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758292942337, + "createAt": 1758292934368 + } + }, + "HdJ2IUlX-_VuFBeKr-A5F": { + "id": "HdJ2IUlX-_VuFBeKr-A5F", + "tableId": "FbuZsipRsYu9OgkLtQvRH", + "name": "location_name", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 85, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293052779, + "createAt": 1758292943766 + } + }, + "H4d48_Pb2KZNa64mBsDPG": { + "id": "H4d48_Pb2KZNa64mBsDPG", + "tableId": "FbuZsipRsYu9OgkLtQvRH", + "name": "location_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 2, + "ui": { + "keys": 1, + "widthName": 64, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293049076, + "createAt": 1758292964168 + } + }, + "7loiKwFcsBh-V-HXDU4qt": { + "id": "7loiKwFcsBh-V-HXDU4qt", + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "name": "location_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 2, + "widthName": 64, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293049076, + "createAt": 1758292964927 + } + }, + "DyOGo0HbdYNLl3F6fFJoV": { + "id": "DyOGo0HbdYNLl3F6fFJoV", + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "name": "term_id", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293141187, + "createAt": 1758293101287 + } + }, + "xsiBCEQ2379F08SJM44Q2": { + "id": "xsiBCEQ2379F08SJM44Q2", + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293121419, + "createAt": 1758293121418 + } + }, + "JOnYVd6w4ZQXXijAEVaCG": { + "id": "JOnYVd6w4ZQXXijAEVaCG", + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "name": "Term_id", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293206382, + "createAt": 1758293202932 + } + }, + "CAHkq77oQC59cHUkF9lfd": { + "id": "CAHkq77oQC59cHUkF9lfd", + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "name": "term_name", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 66, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371540668, + "createAt": 1758293212321 + } + }, + "uN4FKxyEb5gnf7VCsybG5": { + "id": "uN4FKxyEb5gnf7VCsybG5", + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293239893, + "createAt": 1758293239893 + } + }, + "s02QSeo8dSiozImiCBNFi": { + "id": "s02QSeo8dSiozImiCBNFi", + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293243929, + "createAt": 1758293243928 + } + }, + "mxT0CemzkBll17N1CaQca": { + "id": "mxT0CemzkBll17N1CaQca", + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293302757, + "createAt": 1758293302757 + } + }, + "qN1PeVGlmvL6ncOuxtWr5": { + "id": "qN1PeVGlmvL6ncOuxtWr5", + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293304598, + "createAt": 1758293304598 + } + }, + "ouZqKosGANQxQuTaKY53B": { + "id": "ouZqKosGANQxQuTaKY53B", + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293314990, + "createAt": 1758293314990 + } + }, + "aDfTa3M4EJInboYW1AwJY": { + "id": "aDfTa3M4EJInboYW1AwJY", + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "name": "term_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371537945, + "createAt": 1758293324974 + } + }, + "oO6A3-YfUIYuwwbbHAwak": { + "id": "oO6A3-YfUIYuwwbbHAwak", + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "name": "term_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371537945, + "createAt": 1758293326187 + } + }, + "ubc2_bhpYhqglGT5scNC3": { + "id": "ubc2_bhpYhqglGT5scNC3", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758293551653, + "createAt": 1758293551653 + } + }, + "k7258dBY3kP6fzWqjW50q": { + "id": "k7258dBY3kP6fzWqjW50q", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "content", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370733353, + "createAt": 1758293552010 + } + }, + "0zdmPP4N-6oeuWmSY2VKl": { + "id": "0zdmPP4N-6oeuWmSY2VKl", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "star", + "comment": "", + "dataType": "FLOAT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370726631, + "createAt": 1758293552173 + } + }, + "EInU_RFu6bLHrzU6Q0B7n": { + "id": "EInU_RFu6bLHrzU6Q0B7n", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370383612, + "createAt": 1758370383611 + } + }, + "55SgG8fQHW7ou5JDkXvKA": { + "id": "55SgG8fQHW7ou5JDkXvKA", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "store_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370393338, + "createAt": 1758370393337 + } + }, + "dIZqWQhwiiNrzz92BX0G8": { + "id": "dIZqWQhwiiNrzz92BX0G8", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "created_at", + "comment": "", + "dataType": "DATETIME", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 63, + "widthComment": 60, + "widthDataType": 61, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370747887, + "createAt": 1758370449226 + } + }, + "Ot0SIC-v-b6RBocUOwIHc": { + "id": "Ot0SIC-v-b6RBocUOwIHc", + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "name": "verification", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 65, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370692412, + "createAt": 1758370517201 + } + }, + "ypBgbxrNHcFXsICZQcZ2H": { + "id": "ypBgbxrNHcFXsICZQcZ2H", + "tableId": "QF-n1pnH466PZnRVifDEq", + "name": "user_food_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 76, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370848692, + "createAt": 1758370787809 + } + }, + "e1w70_JR6SjRa9NgGo_A5": { + "id": "e1w70_JR6SjRa9NgGo_A5", + "tableId": "QF-n1pnH466PZnRVifDEq", + "name": "user_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370801687, + "createAt": 1758370801686 + } + }, + "_7FVj3JXN-FWncfuCO9fc": { + "id": "_7FVj3JXN-FWncfuCO9fc", + "tableId": "-XsTDrkVEFThAtw5o3ffB", + "name": "food_id", + "comment": "", + "dataType": "", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370904863, + "createAt": 1758370862584 + } + }, + "6lcMP4yv3KRQvHJT-VnXw": { + "id": "6lcMP4yv3KRQvHJT-VnXw", + "tableId": "-XsTDrkVEFThAtw5o3ffB", + "name": "user_food_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 76, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370879876, + "createAt": 1758370879876 + } + }, + "6qUzoeD5b_nQwCop5-hwf": { + "id": "6qUzoeD5b_nQwCop5-hwf", + "tableId": "-XsTDrkVEFThAtw5o3ffB", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370901742, + "createAt": 1758370901742 + } + }, + "dXSf5AH3ZE3-iEAjP4chh": { + "id": "dXSf5AH3ZE3-iEAjP4chh", + "tableId": "QF-n1pnH466PZnRVifDEq", + "name": "food_id", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370930728, + "createAt": 1758370927670 + } + }, + "tkg6jpNA4gsXXB_wFOmZI": { + "id": "tkg6jpNA4gsXXB_wFOmZI", + "tableId": "66s2GuXAH4IsvhVcW9jiZ", + "name": "food_idc", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758370963412, + "createAt": 1758370937770 + } + }, + "pDRsYIOgYuYMV6I6tKbxF": { + "id": "pDRsYIOgYuYMV6I6tKbxF", + "tableId": "66s2GuXAH4IsvhVcW9jiZ", + "name": "food_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371526780, + "createAt": 1758370949384 + } + }, + "-EseBZUybBvjTNt06vdVn": { + "id": "-EseBZUybBvjTNt06vdVn", + "tableId": "QF-n1pnH466PZnRVifDEq", + "name": "food_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371526780, + "createAt": 1758370950298 + } + }, + "fsqZCVY3nKxoVoEII8SK-": { + "id": "fsqZCVY3nKxoVoEII8SK-", + "tableId": "66s2GuXAH4IsvhVcW9jiZ", + "name": "food_name", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 66, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371529490, + "createAt": 1758370971595 + } + }, + "1ULvVjxodU2ux6IItl1wu": { + "id": "1ULvVjxodU2ux6IItl1wu", + "tableId": "uc2j1aS_eaeKNmWqHIhjm", + "name": "review_photo_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 95, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371076136, + "createAt": 1758371013060 + } + }, + "yWEtyzLaYRh-GxP2rBAbN": { + "id": "yWEtyzLaYRh-GxP2rBAbN", + "tableId": "uc2j1aS_eaeKNmWqHIhjm", + "name": "review_photo_url", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 99, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371107362, + "createAt": 1758371013632 + } + }, + "miNr6v2QE9PGLzoUleK6j": { + "id": "miNr6v2QE9PGLzoUleK6j", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "review_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371048220, + "createAt": 1758371033308 + } + }, + "ui_LT2XpbGmhIg5fAS2Pi": { + "id": "ui_LT2XpbGmhIg5fAS2Pi", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371044596, + "createAt": 1758371044596 + } + }, + "UAN06zaHoneXEx6Zdkeim": { + "id": "UAN06zaHoneXEx6Zdkeim", + "tableId": "uc2j1aS_eaeKNmWqHIhjm", + "name": "review_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371059577, + "createAt": 1758371059577 + } + }, + "rqOwtDSa3ID9yipZKxdHy": { + "id": "rqOwtDSa3ID9yipZKxdHy", + "tableId": "DCLfTq9CxYAqBLL9O5uV-", + "name": "reply_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371211815, + "createAt": 1758371156738 + } + }, + "sgfvuuZDNafwtoRn92ETX": { + "id": "sgfvuuZDNafwtoRn92ETX", + "tableId": "DCLfTq9CxYAqBLL9O5uV-", + "name": "content", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371206948, + "createAt": 1758371157094 + } + }, + "D8BdbHLmKJm3uyowygI7N": { + "id": "D8BdbHLmKJm3uyowygI7N", + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "name": "review_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371167244, + "createAt": 1758371167244 + } + }, + "EvmR_T3qUbZVAAWd_4fc-": { + "id": "EvmR_T3qUbZVAAWd_4fc-", + "tableId": "DCLfTq9CxYAqBLL9O5uV-", + "name": "review_id", + "comment": "", + "dataType": "INT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758371174249, + "createAt": 1758371174248 + } + }, + "c8eBgjgL943BPvdbqGlNx": { + "id": "c8eBgjgL943BPvdbqGlNx", + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "name": "phone_num", + "comment": "", + "dataType": "VARCHAR", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 69, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758454322947, + "createAt": 1758454292626 + } + }, + "1S2A9Kj1q0Zg1NjLnMd4x": { + "id": "1S2A9Kj1q0Zg1NjLnMd4x", + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "name": "d_day", + "comment": "", + "dataType": "INT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1758455579192, + "createAt": 1758455572842 + } + } + }, + "relationshipEntities": { + "EkbFWggaU1It65_7uNSjS": { + "id": "EkbFWggaU1It65_7uNSjS", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "NG68JYiPiJ-OOvG6HiKys" + ], + "x": 1117.1233, + "y": 543.8356, + "direction": 1 + }, + "end": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "unGW2Vhu7oVMAbbyb_uU8" + ], + "x": 642.4006999999999, + "y": 547.4966, + "direction": 2 + }, + "meta": { + "updateAt": 1758267838696, + "createAt": 1758267838696 + } + }, + "5iUsaVpjTW4MU4K6NMQMr": { + "id": "5iUsaVpjTW4MU4K6NMQMr", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd" + ], + "x": 642.4006999999999, + "y": 535.4966, + "direction": 2 + }, + "end": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "P9cavuKJGE8NxA5pnr2w_" + ], + "x": 1117.1233, + "y": 555.8356, + "direction": 1 + }, + "meta": { + "updateAt": 1758267887526, + "createAt": 1758267887526 + } + }, + "rGsXeC_iDXqZ4y1hQ9xhm": { + "id": "rGsXeC_iDXqZ4y1hQ9xhm", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "NG68JYiPiJ-OOvG6HiKys" + ], + "x": 1117.1233, + "y": 611.8356, + "direction": 1 + }, + "end": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "IPxLAdyOC8VYOSQBns0-W" + ], + "x": 642.4006999999999, + "y": 609.4966, + "direction": 2 + }, + "meta": { + "updateAt": 1758267899657, + "createAt": 1758267899657 + } + }, + "_AWjE6cS9dAz6qVah7-UZ": { + "id": "_AWjE6cS9dAz6qVah7-UZ", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "columnIds": [ + "wyBJqunyME-zl1-De524y" + ], + "x": 807.5343, + "y": 457.1509, + "direction": 1 + }, + "end": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "o3xi1phrDUYwXoYMLJvNP" + ], + "x": 588.9761, + "y": 444.7569, + "direction": 2 + }, + "meta": { + "updateAt": 1758290123214, + "createAt": 1758290123214 + } + }, + "Jq2X7Q5CzYRNyKOzHIcs6": { + "id": "Jq2X7Q5CzYRNyKOzHIcs6", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "columnIds": [ + "wyBJqunyME-zl1-De524y" + ], + "x": 1182.5343, + "y": 446.5208, + "direction": 2 + }, + "end": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "87sEqz5-srk0VbG4yiWb2" + ], + "x": 1391.0959, + "y": 447.6165, + "direction": 1 + }, + "meta": { + "updateAt": 1758290132756, + "createAt": 1758290132756 + } + }, + "vc8VPwZFTZBBnV499eEyE": { + "id": "vc8VPwZFTZBBnV499eEyE", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "columnIds": [ + "fyCBGRc-o-aQC-EQBLTv3" + ], + "x": 807.5343, + "y": 469.1509, + "direction": 1 + }, + "end": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "QbmabyqvWYXUzYuw76G_K" + ], + "x": 588.9761, + "y": 444.7569, + "direction": 2 + }, + "meta": { + "updateAt": 1758290536668, + "createAt": 1758290536668 + } + }, + "oPSgwDtJYxercMoHAYg1p": { + "id": "oPSgwDtJYxercMoHAYg1p", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "e6c3szi_ECXJt-WIBHtd_", + "columnIds": [ + "fyCBGRc-o-aQC-EQBLTv3" + ], + "x": 807.5343, + "y": 469.1509, + "direction": 1 + }, + "end": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "aVEC_JkxE3fKxtkaAGdo_" + ], + "x": 588.9761, + "y": 444.7569, + "direction": 2 + }, + "meta": { + "updateAt": 1758290552767, + "createAt": 1758290552767 + } + }, + "Vd8Xywn-nyqjLc37CrASA": { + "id": "Vd8Xywn-nyqjLc37CrASA", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "columnIds": [ + "dQFMxc6o44lnTzRBkWRX1" + ], + "x": 732.6244, + "y": 406.6435, + "direction": 1 + }, + "end": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "Yymdw8aFEqAAoh_FDoQnk" + ], + "x": 588.9761, + "y": 444.7569, + "direction": 2 + }, + "meta": { + "updateAt": 1758292193405, + "createAt": 1758292193405 + } + }, + "x5KesGnxWH7BV5U7SRUKf": { + "id": "x5KesGnxWH7BV5U7SRUKf", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd" + ], + "x": 880.198, + "y": 898.2007, + "direction": 4 + }, + "end": { + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "columnIds": [ + "0vodZWX_87XT9VbutIozY" + ], + "x": 857.4466, + "y": 749.6242, + "direction": 8 + }, + "meta": { + "updateAt": 1758292288369, + "createAt": 1758292288369 + } + }, + "eQ0Udv6TLmj-vhXKBJS9z": { + "id": "eQ0Udv6TLmj-vhXKBJS9z", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "NG68JYiPiJ-OOvG6HiKys" + ], + "x": 1651.263, + "y": 699.2659, + "direction": 1 + }, + "end": { + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "columnIds": [ + "Inuqs8_WWLi2BSn2yGcR8" + ], + "x": 1044.9466, + "y": 673.6242, + "direction": 2 + }, + "meta": { + "updateAt": 1758292325130, + "createAt": 1758292325130 + } + }, + "c7zF524469Jx_P8stTYTY": { + "id": "c7zF524469Jx_P8stTYTY", + "identification": false, + "relationshipType": 8, + "startRelationshipType": 2, + "start": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd" + ], + "x": 588.9761, + "y": 488.7569, + "direction": 2 + }, + "end": { + "tableId": "EnyjPta3Re1to-0Ow_c0k", + "columnIds": [ + "0W8hNlGVp449tH6a85DH8" + ], + "x": 777.8297, + "y": 480.2598, + "direction": 1 + }, + "meta": { + "updateAt": 1758292468361, + "createAt": 1758292468361 + } + }, + "ppOMDQJnZUglApRYtpBNt": { + "id": "ppOMDQJnZUglApRYtpBNt", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "NG68JYiPiJ-OOvG6HiKys" + ], + "x": 1577.5959, + "y": 535.6165, + "direction": 8 + }, + "end": { + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "columnIds": [ + "nRMf6gQpPmzyCevOtE4-8" + ], + "x": 1575.0283, + "y": 632.0134, + "direction": 4 + }, + "meta": { + "updateAt": 1758292783115, + "createAt": 1758292783115 + } + }, + "QHb8FIR-OWcNh55-2nQS3": { + "id": "QHb8FIR-OWcNh55-2nQS3", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "columnIds": [ + "AfeYLVYuvV8Wl821feyp7" + ], + "x": 1847.6988, + "y": 969.3323, + "direction": 4 + }, + "end": { + "tableId": "cRAlxAB1DS5CVIqN0YxH_", + "columnIds": [ + "NvKLpAdjRzQgpUoTUmIou" + ], + "x": 1837.763, + "y": 823.2659, + "direction": 8 + }, + "meta": { + "updateAt": 1758292812174, + "createAt": 1758292812174 + } + }, + "MD92WiyC8by1qAm4I_XSo": { + "id": "MD92WiyC8by1qAm4I_XSo", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 1, + "start": { + "tableId": "FbuZsipRsYu9OgkLtQvRH", + "columnIds": [ + "H4d48_Pb2KZNa64mBsDPG" + ], + "x": 1881.6967, + "y": 1343.0536, + "direction": 4 + }, + "end": { + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "columnIds": [ + "7loiKwFcsBh-V-HXDU4qt" + ], + "x": 1847.6988, + "y": 1121.3323, + "direction": 8 + }, + "meta": { + "updateAt": 1758292964928, + "createAt": 1758292964928 + } + }, + "hlb49ERgeQ1Wz6x78DVes": { + "id": "hlb49ERgeQ1Wz6x78DVes", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd" + ], + "x": 692.698, + "y": 1022.2007, + "direction": 1 + }, + "end": { + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "columnIds": [ + "xsiBCEQ2379F08SJM44Q2" + ], + "x": 608.8593, + "y": 959.7111, + "direction": 2 + }, + "meta": { + "updateAt": 1758293121419, + "createAt": 1758293121419 + } + }, + "WMXN_t4yhZ_plMFEz3Lii": { + "id": "WMXN_t4yhZ_plMFEz3Lii", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "columnIds": [ + "uN4FKxyEb5gnf7VCsybG5" + ], + "x": 403.048, + "y": 805.2189, + "direction": 8 + }, + "end": { + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "columnIds": [ + "s02QSeo8dSiozImiCBNFi" + ], + "x": 404.3084, + "y": 953.9311, + "direction": 4 + }, + "meta": { + "updateAt": 1758293243929, + "createAt": 1758293243929 + } + }, + "s5NbCdJYvnyHaAWaSh2oy": { + "id": "s5NbCdJYvnyHaAWaSh2oy", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "columnIds": [ + "mxT0CemzkBll17N1CaQca" + ], + "x": 403.048, + "y": 805.2189, + "direction": 8 + }, + "end": { + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "columnIds": [ + "qN1PeVGlmvL6ncOuxtWr5" + ], + "x": 404.3084, + "y": 953.9311, + "direction": 4 + }, + "meta": { + "updateAt": 1758293304598, + "createAt": 1758293304598 + } + }, + "GOioAAFNsn_A5uVd9veiB": { + "id": "GOioAAFNsn_A5uVd9veiB", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "WzdtVxFyk4teMt3xZ8E-8", + "columnIds": [ + "aDfTa3M4EJInboYW1AwJY" + ], + "x": 449.7814, + "y": 1253.7228, + "direction": 4 + }, + "end": { + "tableId": "U-iS9P7j9_XRpt7sokj1u", + "columnIds": [ + "oO6A3-YfUIYuwwbbHAwak" + ], + "x": 426.35929999999996, + "y": 1011.7111, + "direction": 8 + }, + "meta": { + "updateAt": 1758293326188, + "createAt": 1758293326188 + } + }, + "1bzWUSllbdQW-XkNzsFDL": { + "id": "1bzWUSllbdQW-XkNzsFDL", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd" + ], + "x": 1067.6979999999999, + "y": 1022.2007, + "direction": 2 + }, + "end": { + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "columnIds": [ + "EInU_RFu6bLHrzU6Q0B7n" + ], + "x": 1190.2824, + "y": 1091.2705, + "direction": 1 + }, + "meta": { + "updateAt": 1758370383612, + "createAt": 1758370383612 + } + }, + "hMvmzUWSPDWpRGtzvjQ_H": { + "id": "hMvmzUWSPDWpRGtzvjQ_H", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "3sN0z9wOs5gRHtxPeLnNL", + "columnIds": [ + "AfeYLVYuvV8Wl821feyp7" + ], + "x": 1652.6988, + "y": 1045.3323, + "direction": 1 + }, + "end": { + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "columnIds": [ + "55SgG8fQHW7ou5JDkXvKA" + ], + "x": 1559.2824, + "y": 1091.2705, + "direction": 2 + }, + "meta": { + "updateAt": 1758370393338, + "createAt": 1758370393338 + } + }, + "MNEmKtrdfaxLY7ulw1tGy": { + "id": "MNEmKtrdfaxLY7ulw1tGy", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "SW3qSYcmcjF6fxRxL9C6W", + "columnIds": [ + "FdInoxrEYMeqkCxyYQlEd" + ], + "x": 880.198, + "y": 1146.2006999999999, + "direction": 8 + }, + "end": { + "tableId": "QF-n1pnH466PZnRVifDEq", + "columnIds": [ + "e1w70_JR6SjRa9NgGo_A5" + ], + "x": 905.8327, + "y": 1248.6227, + "direction": 4 + }, + "meta": { + "updateAt": 1758370801687, + "createAt": 1758370801687 + } + }, + "x5Z5XwztzLa-nZAhAScph": { + "id": "x5Z5XwztzLa-nZAhAScph", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "QF-n1pnH466PZnRVifDEq", + "columnIds": [ + "ypBgbxrNHcFXsICZQcZ2H" + ], + "x": 873.4383, + "y": 1407.5521, + "direction": 8 + }, + "end": { + "tableId": "-XsTDrkVEFThAtw5o3ffB", + "columnIds": [ + "6lcMP4yv3KRQvHJT-VnXw" + ], + "x": 873.3064, + "y": 1524.454, + "direction": 4 + }, + "meta": { + "updateAt": 1758370879876, + "createAt": 1758370879876 + } + }, + "dxZac9LiwDnVGUoVgGBK_": { + "id": "dxZac9LiwDnVGUoVgGBK_", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 1, + "start": { + "tableId": "66s2GuXAH4IsvhVcW9jiZ", + "columnIds": [ + "pDRsYIOgYuYMV6I6tKbxF" + ], + "x": 837.6287, + "y": 1461.6571, + "direction": 4 + }, + "end": { + "tableId": "QF-n1pnH466PZnRVifDEq", + "columnIds": [ + "-EseBZUybBvjTNt06vdVn" + ], + "x": 905.8327, + "y": 1376.6227, + "direction": 8 + }, + "meta": { + "updateAt": 1758370950298, + "createAt": 1758370950298 + } + }, + "t4n5Tkjog7EkRQY3WzvHA": { + "id": "t4n5Tkjog7EkRQY3WzvHA", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "columnIds": [ + "miNr6v2QE9PGLzoUleK6j" + ], + "x": 1374.7824, + "y": 1191.2705, + "direction": 8 + }, + "end": { + "tableId": "uc2j1aS_eaeKNmWqHIhjm", + "columnIds": [ + "UAN06zaHoneXEx6Zdkeim" + ], + "x": 1369.0787, + "y": 1326.5081, + "direction": 4 + }, + "meta": { + "updateAt": 1758371059577, + "createAt": 1758371059577 + } + }, + "mymht63SidIE5C-WzUI5A": { + "id": "mymht63SidIE5C-WzUI5A", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "columnIds": [ + "miNr6v2QE9PGLzoUleK6j" + ], + "x": 1539.2824, + "y": 991.2705, + "direction": 4 + }, + "end": { + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "columnIds": [ + "D8BdbHLmKJm3uyowygI7N" + ], + "x": 1559.2824, + "y": 1047.2705, + "direction": 2 + }, + "meta": { + "updateAt": 1758371167244, + "createAt": 1758371167244 + } + }, + "jkWOiJoVYIrCuKnZXEIEX": { + "id": "jkWOiJoVYIrCuKnZXEIEX", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "1m_s3Ee0XEsle86A1W9bt", + "columnIds": [ + "miNr6v2QE9PGLzoUleK6j" + ], + "x": 1374.7824, + "y": 991.2705, + "direction": 4 + }, + "end": { + "tableId": "DCLfTq9CxYAqBLL9O5uV-", + "columnIds": [ + "EvmR_T3qUbZVAAWd_4fc-" + ], + "x": 1377.7408, + "y": 929.8977, + "direction": 8 + }, + "meta": { + "updateAt": 1758371174249, + "createAt": 1758371174249 + } + } + }, + "indexEntities": {}, + "indexColumnEntities": {}, + "memoEntities": {} + } +} \ No newline at end of file diff --git "a/1\354\243\274\354\260\250\353\257\270\354\205\230/homepage.sql" "b/1\354\243\274\354\260\250\353\257\270\354\205\230/homepage.sql" new file mode 100644 index 0000000..c50ee4b --- /dev/null +++ "b/1\354\243\274\354\260\250\353\257\270\354\205\230/homepage.sql" @@ -0,0 +1,21 @@ +-- 4번 +SELECT + m.title, + m.description, + m.point, + um.is_complete, + um.d_day, + s.store_name, + l.location_name +FROM + User_mission um +JOIN + Mission m ON um.mission_id = m.mission_id +JOIN + Store s ON m.store_id = s.store_id +JOIN + Location l ON s.location_id = l.location_id +WHERE + um.user_id = 1 +ORDER BY + um.d_day ASC; -- 디데이 가까운 순서로 정렬 diff --git "a/1\354\243\274\354\260\250\353\257\270\354\205\230/mission_status.sql" "b/1\354\243\274\354\260\250\353\257\270\354\205\230/mission_status.sql" new file mode 100644 index 0000000..542d388 --- /dev/null +++ "b/1\354\243\274\354\260\250\353\257\270\354\205\230/mission_status.sql" @@ -0,0 +1,20 @@ +-- 3번 +SELECT + m.mission_id, + m.title, + m.description, + m.point, + um.status, + um.started_at, + um.completed_at +FROM + User_missions AS um +INNER JOIN + Missions AS m ON um.mission_id = m.mission_id -- 사용자가 이미 시작했거나 완료한 미션들 조회 +WHERE + um.user_id = 현재_사용자_ID + AND um.status IN ('in_progress', 'completed') -- 진행 중 또는 완료 상태 +ORDER BY + um.updated_at DESC -- 가장 최근에 갱신된 미션부터 정렬 +LIMIT + 10 OFFSET 0; \ No newline at end of file diff --git "a/1\354\243\274\354\260\250\353\257\270\354\205\230/mypage.sql" "b/1\354\243\274\354\260\250\353\257\270\354\205\230/mypage.sql" new file mode 100644 index 0000000..f7c09fd --- /dev/null +++ "b/1\354\243\274\354\260\250\353\257\270\354\205\230/mypage.sql" @@ -0,0 +1,8 @@ +-- 2번 +SELECT u.user_id, + u.email, + u.name, + u.point, + u.phone_num +FROM User AS u +WHERE u.user_id = '1'; \ No newline at end of file diff --git "a/1\354\243\274\354\260\250\353\257\270\354\205\230/review.sql" "b/1\354\243\274\354\260\250\353\257\270\354\205\230/review.sql" new file mode 100644 index 0000000..0a4530c --- /dev/null +++ "b/1\354\243\274\354\260\250\353\257\270\354\205\230/review.sql" @@ -0,0 +1,16 @@ +-- 1번 +-- 가게 리뷰와 유저 정보 조회 +SELECT r.review_id, + r.star, + r.content, + r.created_at, + u.user_id, + u.name, + u.email +FROM Review r +INNER JOIN User u ON r.user_id = u.user_id +WHERE r.store_id = 101; + +-- 리뷰 추가 +INSERT INTO Review (user_id, store_id, star, content, created_at) + VALUES (1, 101, 5, '음 너무 맛있어요', NOW()); \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" new file mode 100644 index 0000000..3b1da23 Binary files /dev/null and "b/4\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" differ diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/.DS_Store" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/.DS_Store" new file mode 100644 index 0000000..40bae72 Binary files /dev/null and "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/.DS_Store" differ diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/.DS_Store" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/.DS_Store" new file mode 100644 index 0000000..0f3439b Binary files /dev/null and "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/.DS_Store" differ diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/.DS_Store" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/.DS_Store" new file mode 100644 index 0000000..0f91354 Binary files /dev/null and "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/.DS_Store" differ diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/.DS_Store" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/.DS_Store" new file mode 100644 index 0000000..bb85c08 Binary files /dev/null and "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/.DS_Store" differ diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/Umc9thApplication.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/Umc9thApplication.java" new file mode 100644 index 0000000..0e3eec9 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/Umc9thApplication.java" @@ -0,0 +1,15 @@ +package com.example.umc9th; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + + +@SpringBootApplication +@EnableJpaAuditing +public class Umc9thApplication { + public static void main(String[] args) { + SpringApplication.run(Umc9thApplication.class, args); + + } +} diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/mission/entity/Mission.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/mission/entity/Mission.java" new file mode 100644 index 0000000..a67ba17 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/mission/entity/Mission.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.mission.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.mapping.UserMission; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "mission") +public class Mission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "mission_id") + private Long id; + + @Column(nullable = false, length = 100) + private String title; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(nullable = false) + private Integer point; + + @Column(length = 50) + private String verification; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // UserMission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) + @Builder.Default + private List userMissionList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/Review.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/Review.java" new file mode 100644 index 0000000..2dce1bf --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/Review.java" @@ -0,0 +1,54 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review") +public class Review extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_id") + private Long id; + + @Column(nullable = false) + private Float star; + + @Column(columnDefinition = "TEXT") + private String content; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 User는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 Store는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // ReviewPhoto와의 연관 관계 (1:N) + // Review 입장에서 ReviewPhoto는 1:N + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL) + @Builder.Default + private List reviewPhotoList = new ArrayList<>(); + + // ReviewReply와의 연관 관계 (1:N) + // Review 입장에서 ReviewReply는 1:N // 헷갈림 + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL) + @Builder.Default + private List reviewReplyList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" new file mode 100644 index 0000000..56310ba --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_photo") +public class ReviewPhoto extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_photo_id") + private Long id; + + @Column(name = "review_photo_url", nullable = false) + private String reviewPhotoUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/ReviewReply.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/ReviewReply.java" new file mode 100644 index 0000000..ccaa29e --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/review/entity/ReviewReply.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_reply") +public class ReviewReply extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "reply_id") + private Long id; + + @Column(columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/store/entity/Location.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/store/entity/Location.java" new file mode 100644 index 0000000..73384e1 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/store/entity/Location.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "location") +public class Location extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "location_id") + private Long id; + + @Column(name = "location_name", nullable = false, length = 50) + private String name; + + // Store와의 연관 관계 (1:N) + @OneToMany(mappedBy = "location", cascade = CascadeType.ALL) + @Builder.Default + private List storeList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/store/entity/Store.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/store/entity/Store.java" new file mode 100644 index 0000000..99ad0b6 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/store/entity/Store.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "store") +public class Store extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "store_id") + private Long id; + + @Column(name = "store_name", nullable = false, length = 50) + private String name; + + @Column(name = "detailAddress", length = 100) + private String detailAddress; + + // Location과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + private Location location; + + // Review와의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List reviewList = new ArrayList<>(); + + // Mission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List missionList = new ArrayList<>(); + +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/Food.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/Food.java" new file mode 100644 index 0000000..7dc8fc2 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/Food.java" @@ -0,0 +1,25 @@ +package com.example.umc9th.domain.user.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@Getter +@Table(name = "food") + +public class Food { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "food_id") + private Long id; + + @Column(nullable = false, length = 50) + private String foodName; +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/Term.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/Term.java" new file mode 100644 index 0000000..03bf421 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/Term.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.TermName; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "term") +public class Term { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "term_id") // DB 컬럼명 명시 + private Long id; + + @Column(name = "term_name", nullable = false) // ENUM 컬럼만 남김 + @Enumerated(EnumType.STRING) + private TermName name; +} diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/User.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/User.java" new file mode 100644 index 0000000..9fbbf0a --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/User.java" @@ -0,0 +1,47 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.Gender; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user") +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(nullable = false) + private Gender gender = Gender.NONE; + + @Column(nullable = false, length = 50, unique = true) + private String email; + + @Column(nullable = false) + private String password; + + @Column(length = 20) + private String phoneNum; + + @Builder.Default + @Column(nullable = false) + private Integer point = 0; + + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) + private List userFoodList = new ArrayList<>(); +} diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" new file mode 100644 index 0000000..fdb0822 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Food; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_food") +public class UserFood { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_food_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id") + private Food food; +} diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" new file mode 100644 index 0000000..ba7da35 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" @@ -0,0 +1,38 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_mission") +public class UserMission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_mission_id") + private Long id; + + @Column(nullable = false) + private Boolean isComplete; + + @Column + private Integer dDay; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Mission과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + +} \ No newline at end of file diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" new file mode 100644 index 0000000..0e46a62 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Term; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; + +import lombok.*; +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_term") +public class UserTerm { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_term_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "term_id") + private Term term; +} + + diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/enums/Gender.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/enums/Gender.java" new file mode 100644 index 0000000..e07fca7 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/enums/Gender.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum Gender { + MALE, FEMALE, NONE +} diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/enums/TermName.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/enums/TermName.java" new file mode 100644 index 0000000..1473bd4 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/domain/user/enums/TermName.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum TermName { + AGE, SERVICE, PRIVACY, LOCATION, MARKETING +} diff --git "a/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/global/entity/BaseEntity.java" "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/global/entity/BaseEntity.java" new file mode 100644 index 0000000..70f7459 --- /dev/null +++ "b/4\354\243\274\354\260\250\353\257\270\354\205\230/java/com/example/umc9th/global/entity/BaseEntity.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.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.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; +} + diff --git "a/5\354\243\274\354\260\250\353\257\270\354\205\230/mission.md" "b/5\354\243\274\354\260\250\353\257\270\354\205\230/mission.md" new file mode 100644 index 0000000..750fd92 --- /dev/null +++ "b/5\354\243\274\354\260\250\353\257\270\354\205\230/mission.md" @@ -0,0 +1,51 @@ +// 1번 +// JPA +public interface ReviewRepository extends JpaRepository { + List findReviewsByStoreId(Long storeId); +} + +// JPQL +public interface ReviewRepository extends JpaRepository { + @Query("SELECT r FROM Review r JOIN FETCH r.user WHERE r.store.id = :storeId") + List findReviewsByStoreId(@Param("storeId") Long storeId); +} + + +// 2번 +// JPA +public interface UserRepository extends JpaRepository { + Optional findById(Long userId); // Optinal은 NULL 방지 + + +// JPQL +@Query("SELECT u FROM User u WHERE u.id = :userId") +Optional findUserById(@Param("userId") Long userId); + + +// 3번 +public interface UserMissionRepository extends JpaRepository { + + @Query(""" + SELECT um FROM UserMission um + JOIN FETCH um.mission m + WHERE um.user.id = :userId + AND um.status IN ('in_progress', 'completed') + ORDER BY um.updatedAt DESC + """) + List findUserMissions(@Param("userId") Long userId, Pageable pageable); +} + +// 4번 +public interface UserMissionRepository extends JpaRepository { + + @Query(""" + SELECT um + FROM UserMission um + JOIN FETCH um.mission m + JOIN FETCH m.store s + JOIN FETCH s.location l + WHERE um.user.id = :userId + ORDER BY um.dDay ASC + """) + List findUserHome(@Param("userId") Long userId); +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" new file mode 100644 index 0000000..741587f Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" new file mode 100644 index 0000000..c9f0357 Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" new file mode 100644 index 0000000..c73db4f Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" new file mode 100644 index 0000000..e75394b Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" new file mode 100644 index 0000000..0f3439b Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" new file mode 100644 index 0000000..0f91354 Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" new file mode 100644 index 0000000..bb85c08 Binary files /dev/null and "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" differ diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" new file mode 100644 index 0000000..0e3eec9 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" @@ -0,0 +1,15 @@ +package com.example.umc9th; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + + +@SpringBootApplication +@EnableJpaAuditing +public class Umc9thApplication { + public static void main(String[] args) { + SpringApplication.run(Umc9thApplication.class, args); + + } +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" new file mode 100644 index 0000000..a67ba17 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.mission.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.mapping.UserMission; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "mission") +public class Mission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "mission_id") + private Long id; + + @Column(nullable = false, length = 100) + private String title; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(nullable = false) + private Integer point; + + @Column(length = 50) + private String verification; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // UserMission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) + @Builder.Default + private List userMissionList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" new file mode 100644 index 0000000..f8eba91 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.domain.review.controller; + +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.service.ReviewService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/reviews") +public class ReviewController { + + private final ReviewService reviewService; + + @GetMapping("/user/{userId}") + public List getReviewsByUser( // 별점 필터링 + @PathVariable Long userId, + @RequestParam(required = false) Long storeId, + @RequestParam(required = false) Integer minStar, + @RequestParam(required = false) Integer maxStar + ) { + return reviewService.getReviewsByUserWithFilters(userId, storeId, minStar, maxStar); + } +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" new file mode 100644 index 0000000..87cb90c --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" @@ -0,0 +1,18 @@ +package com.example.umc9th.domain.review.dto; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Getter; + +@Getter +public class ReviewResponse { + private Long reviewId; + private String content; + private Float star; + + @QueryProjection + public ReviewResponse(Long reviewId, String content, Float star) { + this.reviewId = reviewId; + this.content = content; + this.star = star; + } +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" new file mode 100644 index 0000000..d8df6ee --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" @@ -0,0 +1,54 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review") +public class Review extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_id") + private Long id; + + @Column(nullable = false) + private Float star; + + @Column(columnDefinition = "TEXT") + private String content; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 User는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 Store는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // ReviewPhoto와의 연관 관계 (1:N) + // Review 입장에서 ReviewPhoto는 1:N + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List reviewPhotoList = new ArrayList<>(); + + // ReviewReply와의 연관 관계 (1:N) + // Review 입장에서 ReviewReply는 1:N // 헷갈림 + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List reviewReplyList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" new file mode 100644 index 0000000..56310ba --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_photo") +public class ReviewPhoto extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_photo_id") + private Long id; + + @Column(name = "review_photo_url", nullable = false) + private String reviewPhotoUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" new file mode 100644 index 0000000..ccaa29e --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_reply") +public class ReviewReply extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "reply_id") + private Long id; + + @Column(columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" new file mode 100644 index 0000000..2781dcc --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.entity.Review; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReviewRepository extends JpaRepository, ReviewRepositoryCustom { +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" new file mode 100644 index 0000000..227197c --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.dto.ReviewResponse; + +import java.util.List; + +public interface ReviewRepositoryCustom { + List findReviewsByUserWithFilters(Long userId, Long storeId, Integer minStar, Integer maxStar); +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" new file mode 100644 index 0000000..e38d9fc --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" @@ -0,0 +1,38 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.dto.QReviewResponse; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.QReview; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class ReviewRepositoryImpl implements ReviewRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findReviewsByUserWithFilters(Long userId, Long storeId, Integer minStar, Integer maxStar) { + QReview review = QReview.review; + + var query = queryFactory + .select(new QReviewResponse( + review.id, + review.content, + review.star + )) + .from(review) + .where(review.user.id.eq(userId)); + + if (storeId != null) query.where(review.store.id.eq(storeId)); + if (minStar != null && maxStar != null) query.where(review.star.between(minStar, maxStar)); + else if (minStar != null) query.where(review.star.goe(minStar)); + else if (maxStar != null) query.where(review.star.loe(maxStar)); + + return query.orderBy(review.id.desc()).fetch(); + } +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java" new file mode 100644 index 0000000..f24cd1c --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java" @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.review.service; + +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.repository.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ReviewService { + + private final ReviewRepository reviewRepository; + + public List getReviewsByUserWithFilters(Long userId, Long storeId, Integer minStar, Integer maxStar) { + // Repository에서 바로 DTO Projection + return reviewRepository.findReviewsByUserWithFilters(userId, storeId, minStar, maxStar); + } +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" new file mode 100644 index 0000000..73384e1 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "location") +public class Location extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "location_id") + private Long id; + + @Column(name = "location_name", nullable = false, length = 50) + private String name; + + // Store와의 연관 관계 (1:N) + @OneToMany(mappedBy = "location", cascade = CascadeType.ALL) + @Builder.Default + private List storeList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" new file mode 100644 index 0000000..99ad0b6 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "store") +public class Store extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "store_id") + private Long id; + + @Column(name = "store_name", nullable = false, length = 50) + private String name; + + @Column(name = "detailAddress", length = 100) + private String detailAddress; + + // Location과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + private Location location; + + // Review와의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List reviewList = new ArrayList<>(); + + // Mission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List missionList = new ArrayList<>(); + +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" new file mode 100644 index 0000000..b5270f8 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" @@ -0,0 +1,29 @@ +package com.example.umc9th.domain.user.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@Getter +@Table(name = "food") + +public class Food { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "food_id") + private Long id; + + @Column(nullable = false, length = 50) + private String foodName; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" new file mode 100644 index 0000000..03bf421 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.TermName; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "term") +public class Term { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "term_id") // DB 컬럼명 명시 + private Long id; + + @Column(name = "term_name", nullable = false) // ENUM 컬럼만 남김 + @Enumerated(EnumType.STRING) + private TermName name; +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" new file mode 100644 index 0000000..1564108 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" @@ -0,0 +1,48 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.Gender; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user") +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(nullable = false) + private Gender gender = Gender.NONE; + + @Column(nullable = false, length = 50, unique = true) + private String email; + + @Column(nullable = false) + private String password; + + @Column(length = 20) + private String phoneNum; + + @Builder.Default + @Column(nullable = false) + private Integer point = 0; + + @Builder.Default + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) + private List userFoodList = new ArrayList<>(); +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" new file mode 100644 index 0000000..fdb0822 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Food; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_food") +public class UserFood { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_food_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id") + private Food food; +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" new file mode 100644 index 0000000..ba7da35 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" @@ -0,0 +1,38 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_mission") +public class UserMission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_mission_id") + private Long id; + + @Column(nullable = false) + private Boolean isComplete; + + @Column + private Integer dDay; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Mission과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" new file mode 100644 index 0000000..0e46a62 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Term; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; + +import lombok.*; +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_term") +public class UserTerm { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_term_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "term_id") + private Term term; +} + + diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" new file mode 100644 index 0000000..e07fca7 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum Gender { + MALE, FEMALE, NONE +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" new file mode 100644 index 0000000..1473bd4 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum TermName { + AGE, SERVICE, PRIVACY, LOCATION, MARKETING +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" new file mode 100644 index 0000000..0ba8875 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" @@ -0,0 +1,15 @@ +package com.example.umc9th.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 em) { + return new JPAQueryFactory(em); + } +} diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" new file mode 100644 index 0000000..70f7459 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.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.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; +} + diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" new file mode 100644 index 0000000..ec8fa85 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" @@ -0,0 +1,18 @@ +spring: + application: + name: umc9th + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PW} + + jpa: + database: mysql + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true diff --git "a/6\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" new file mode 100644 index 0000000..a96d2a7 --- /dev/null +++ "b/6\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" @@ -0,0 +1,13 @@ +package com.example.umc9th; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Umc9thApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" new file mode 100644 index 0000000..f8b7a96 Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" new file mode 100644 index 0000000..bf1533f Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" new file mode 100644 index 0000000..c73db4f Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" new file mode 100644 index 0000000..e75394b Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" new file mode 100644 index 0000000..0f3439b Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" new file mode 100644 index 0000000..0f91354 Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" new file mode 100644 index 0000000..bb85c08 Binary files /dev/null and "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" differ diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" new file mode 100644 index 0000000..0e3eec9 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" @@ -0,0 +1,15 @@ +package com.example.umc9th; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + + +@SpringBootApplication +@EnableJpaAuditing +public class Umc9thApplication { + public static void main(String[] args) { + SpringApplication.run(Umc9thApplication.class, args); + + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" new file mode 100644 index 0000000..a67ba17 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.mission.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.mapping.UserMission; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "mission") +public class Mission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "mission_id") + private Long id; + + @Column(nullable = false, length = 100) + private String title; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(nullable = false) + private Integer point; + + @Column(length = 50) + private String verification; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // UserMission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) + @Builder.Default + private List userMissionList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" new file mode 100644 index 0000000..d7ac886 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" @@ -0,0 +1,51 @@ +package com.example.umc9th.domain.review.controller; + +import com.example.umc9th.domain.review.converter.ReviewConverter; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.dto.res.ReviewResDTO; +import com.example.umc9th.domain.review.service.query.ReviewQueryService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/reviews") +public class ReviewController { + + private final ReviewQueryService reviewQueryService; + + /** + * 내가 작성한 리뷰 조회 API (필터링 포함) + *

+ * 사용 예시: + * - 전체 조회: GET /reviews/user/1 + * - 가게별: GET /reviews/user/1?storeId=5 + * - 별점 범위: GET /reviews/user/1?minStar=3&maxStar=5 + * - 조합: GET /reviews/user/1?storeId=5&minStar=4&maxStar=5 + */ + @GetMapping("/user/{userId}") + public ApiResponse getMyReviews( + @PathVariable Long userId, + @RequestParam(required = false) Long storeId, + @RequestParam(required = false) Integer minStar, + @RequestParam(required = false) Integer maxStar + ) { + // Service 호출 + List reviews = reviewQueryService.getReviewsByUserWithFilters( + userId, storeId, minStar, maxStar + ); + + // 응답 코드 정의 + GeneralSuccessCode code = GeneralSuccessCode.OK; + + // Converter를 통해 DTO 변환 후 응답 + return ApiResponse.onSuccess( + code, + ReviewConverter.toReviewListDTO(reviews) + ); + } +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java" new file mode 100644 index 0000000..8d45bb8 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java" @@ -0,0 +1,35 @@ +package com.example.umc9th.domain.review.converter; + +import com.example.umc9th.domain.review.dto.res.ReviewResDTO; +import com.example.umc9th.domain.review.dto.ReviewResponse; + +import java.util.List; +import java.util.stream.Collectors; + +public class ReviewConverter { + + /** + * QueryDSL 조회 결과 → 응답 DTO 변환 + */ + public static ReviewResDTO.ReviewListDTO toReviewListDTO(List reviews) { + List reviewDTOs = reviews.stream() + .map(ReviewConverter::toReviewDTO) + .collect(Collectors.toList()); + + return ReviewResDTO.ReviewListDTO.builder() + .reviews(reviewDTOs) + .totalCount(reviewDTOs.size()) + .build(); + } + + /** + * 개별 리뷰 변환 + */ + private static ReviewResDTO.ReviewDTO toReviewDTO(ReviewResponse review) { + return ReviewResDTO.ReviewDTO.builder() + .reviewId(review.getReviewId()) + .content(review.getContent()) + .star(review.getStar()) + .build(); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" new file mode 100644 index 0000000..f25bf7d --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.review.dto; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Getter; + +@Getter +public class ReviewResponse { + private Long reviewId; + private String content; + private Integer star; + + @QueryProjection + public ReviewResponse(Long reviewId, String content, Integer star) { + this.reviewId = reviewId; + this.content = content; + this.star = star; + } +} + diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/req/ReviewReqDTO.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/req/ReviewReqDTO.java" new file mode 100644 index 0000000..5914f94 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/req/ReviewReqDTO.java" @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.review.dto.req; + +public class ReviewReqDTO { +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/res/ReviewResDTO.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/res/ReviewResDTO.java" new file mode 100644 index 0000000..7d5765d --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/res/ReviewResDTO.java" @@ -0,0 +1,37 @@ +package com.example.umc9th.domain.review.dto.res; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public class ReviewResDTO { + + /** + * 리뷰 목록 응답 DTO + * result 안에 들어갈 데이터 + */ + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewListDTO { + private List reviews; + private Integer totalCount; + } + + /** + * 개별 리뷰 DTO + */ + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewDTO { + private Long reviewId; + private String content; + private Integer star; + } +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" new file mode 100644 index 0000000..9bc62ff --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" @@ -0,0 +1,54 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review") +public class Review extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_id") + private Long id; + + @Column(nullable = false) + private Integer star; + + @Column(columnDefinition = "TEXT") + private String content; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 User는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 Store는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // ReviewPhoto와의 연관 관계 (1:N) + // Review 입장에서 ReviewPhoto는 1:N + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List reviewPhotoList = new ArrayList<>(); + + // ReviewReply와의 연관 관계 (1:N) + // Review 입장에서 ReviewReply는 1:N // 헷갈림 + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List reviewReplyList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" new file mode 100644 index 0000000..56310ba --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_photo") +public class ReviewPhoto extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_photo_id") + private Long id; + + @Column(name = "review_photo_url", nullable = false) + private String reviewPhotoUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" new file mode 100644 index 0000000..ccaa29e --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_reply") +public class ReviewReply extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "reply_id") + private Long id; + + @Column(columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java" new file mode 100644 index 0000000..2ae42f6 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.review.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class ReviewException extends GeneralException { + + public ReviewException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java" new file mode 100644 index 0000000..921f95a --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.review.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ReviewErrorCode implements BaseErrorCode { + + // Review 관련 에러 + INVALID_USER_ID(HttpStatus.BAD_REQUEST, "REVIEW400_1", "유효하지 않은 사용자 ID입니다."), + INVALID_STAR_RANGE(HttpStatus.BAD_REQUEST, "REVIEW400_2", "별점은 1~5 사이여야 하며, 최소값이 최대값보다 클 수 없습니다."), + + REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW404_1", "리뷰를 찾을 수 없습니다."), + REVIEW_ACCESS_DENIED(HttpStatus.FORBIDDEN, "REVIEW403_1", "해당 리뷰에 접근할 권한이 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" new file mode 100644 index 0000000..2781dcc --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.entity.Review; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReviewRepository extends JpaRepository, ReviewRepositoryCustom { +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" new file mode 100644 index 0000000..9989c1c --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.querydsl.core.types.Predicate; + +import java.util.List; + +public interface ReviewRepositoryCustom { + List searchReview(Predicate predicate); // predicate를 사용한 동적 쿼리 메서드 +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" new file mode 100644 index 0000000..ad1eef3 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" @@ -0,0 +1,41 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.dto.QReviewResponse; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.QReview; +import com.example.umc9th.domain.store.entity.QStore; +import com.querydsl.core.types.Predicate; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +@Transactional(readOnly = true) // 조회 작업이므로 readOnly +public class ReviewRepositoryImpl implements ReviewRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List searchReview(Predicate predicate) { // predicate를 사용한 동적 쿼리 메서드 + // Q 클래스 선언 + QReview review = QReview.review; + QStore store = QStore.store; + + return queryFactory + .select(new QReviewResponse( + review.id, + review.content, + review.star + )) + .from(review) + .leftJoin(review.store, store) // Store 정보가 필요하면 join + .where(predicate) // 동적으로 전달받은 조건 적용 + .orderBy(review.id.desc()) // 최신 리뷰가 먼저 오도록 정렬 + .fetch(); + } + +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandService.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandService.java" new file mode 100644 index 0000000..c2b8277 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandService.java" @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.review.service.command; + +public interface ReviewCommandService { + + // 나중에 리뷰 작성, 수정, 삭제 메서드 추가 + // ex) Long createReview(ReviewReqDTO dto); + // void updateReview(Long reviewId, ReviewReqDTO dto); + // void deleteReview(Long reviewId); +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandServiceImpl.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandServiceImpl.java" new file mode 100644 index 0000000..b05640f --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandServiceImpl.java" @@ -0,0 +1,16 @@ +package com.example.umc9th.domain.review.service.command; + +import com.example.umc9th.domain.review.repository.ReviewRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional // 쓰기 작업이므로 readOnly 없음 +public class ReviewCommandServiceImpl implements ReviewCommandService { + + private final ReviewRepository reviewRepository; + + // 나중에 구현 +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryService.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryService.java" new file mode 100644 index 0000000..9a477a9 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryService.java" @@ -0,0 +1,18 @@ +package com.example.umc9th.domain.review.service.query; + +import com.example.umc9th.domain.review.dto.ReviewResponse; + +import java.util.List; + +public interface ReviewQueryService { + + /** + * 사용자의 리뷰 조회 (필터링 포함) + */ + List getReviewsByUserWithFilters( + Long userId, + Long storeId, + Integer minStar, + Integer maxStar + ); +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryServiceImpl.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryServiceImpl.java" new file mode 100644 index 0000000..eefcec8 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryServiceImpl.java" @@ -0,0 +1,87 @@ +package com.example.umc9th.domain.review.service.query; + +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.QReview; +import com.example.umc9th.domain.review.exception.ReviewException; +import com.example.umc9th.domain.review.exception.code.ReviewErrorCode; +import com.example.umc9th.domain.review.repository.ReviewRepository; +import com.querydsl.core.BooleanBuilder; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) // 조회 작업이므로 readOnly +public class ReviewQueryServiceImpl implements ReviewQueryService { + + private final ReviewRepository reviewRepository; + + @Override + public List getReviewsByUserWithFilters( + Long userId, + Long storeId, + Integer minStar, + Integer maxStar + ) { + // 검증: 필수 파라미터 체크 + validateUserId(userId); + + // 검증: 별점 범위 체크 + validateStarRange(minStar, maxStar); + + // Q 클래스 선언 + QReview review = QReview.review; + + // BooleanBuilder로 동적 조건 생성 + BooleanBuilder builder = new BooleanBuilder(); + + // 필수 조건: 특정 사용자의 리뷰만 + builder.and(review.user.id.eq(userId)); + + // 선택 조건: 가게 필터링 + if (storeId != null) { + builder.and(review.store.id.eq(storeId)); + } + + // 선택 조건: 별점 필터링 + if (minStar != null && maxStar != null) { + builder.and(review.star.between(minStar.floatValue(), maxStar.floatValue())); + } else if (minStar != null) { + builder.and(review.star.goe(minStar.floatValue())); + } else if (maxStar != null) { + builder.and(review.star.loe(maxStar.floatValue())); + } + + // Repository 호출 & 결과 반환 + return reviewRepository.searchReview(builder); + } + + /** + * 사용자 ID 검증 + */ + private void validateUserId(Long userId) { + if (userId == null || userId <= 0) { + throw new ReviewException(ReviewErrorCode.INVALID_USER_ID); + } + } + + /** + * 별점 범위 검증 + */ + private void validateStarRange(Integer minStar, Integer maxStar) { + if (minStar != null && (minStar < 1 || minStar > 5)) { + throw new ReviewException(ReviewErrorCode.INVALID_STAR_RANGE); + } + + if (maxStar != null && (maxStar < 1 || maxStar > 5)) { + throw new ReviewException(ReviewErrorCode.INVALID_STAR_RANGE); + } + + if (minStar != null && maxStar != null && minStar > maxStar) { + throw new ReviewException(ReviewErrorCode.INVALID_STAR_RANGE); + } + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" new file mode 100644 index 0000000..73384e1 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "location") +public class Location extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "location_id") + private Long id; + + @Column(name = "location_name", nullable = false, length = 50) + private String name; + + // Store와의 연관 관계 (1:N) + @OneToMany(mappedBy = "location", cascade = CascadeType.ALL) + @Builder.Default + private List storeList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" new file mode 100644 index 0000000..99ad0b6 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "store") +public class Store extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "store_id") + private Long id; + + @Column(name = "store_name", nullable = false, length = 50) + private String name; + + @Column(name = "detailAddress", length = 100) + private String detailAddress; + + // Location과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + private Location location; + + // Review와의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List reviewList = new ArrayList<>(); + + // Mission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List missionList = new ArrayList<>(); + +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/controller/TestController.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/controller/TestController.java" new file mode 100644 index 0000000..851510f --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/controller/TestController.java" @@ -0,0 +1,44 @@ +package com.example.umc9th.domain.test.controller; + +import com.example.umc9th.domain.test.converter.TestConverter; +import com.example.umc9th.domain.test.dto.res.TestResDTO; +import com.example.umc9th.domain.test.service.query.TestQueryService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/temp") +public class TestController { + + private final TestQueryService testQueryService; + + @GetMapping("/test") + public ApiResponse test() { + // 응답 코드 정의 + GeneralSuccessCode code = GeneralSuccessCode.OK; + + return ApiResponse.onSuccess( + code, + TestConverter.toTestingDTO("This is Test!") + ); + } + + // 예외 상황 + @GetMapping("/exception") + public ApiResponse exception( + @RequestParam Long flag + ) { + + testQueryService.checkFlag(flag); + + // 응답 코드 정의 + GeneralSuccessCode code = GeneralSuccessCode.OK; + return ApiResponse.onSuccess(code, TestConverter.toExceptionDTO("This is Test!")); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java" new file mode 100644 index 0000000..af81183 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java" @@ -0,0 +1,24 @@ +package com.example.umc9th.domain.test.converter; + +import com.example.umc9th.domain.test.dto.res.TestResDTO; + +public class TestConverter { + + // 객체 -> DTO + public static TestResDTO.Testing toTestingDTO( + String testing + ) { + return TestResDTO.Testing.builder() + .testString(testing) + .build(); + } + + // 객체 -> DTO + public static TestResDTO.Exception toExceptionDTO( + String testing + ){ + return TestResDTO.Exception.builder() + .testString(testing) + .build(); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/req/TestReqDTO.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/req/TestReqDTO.java" new file mode 100644 index 0000000..405e3b9 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/req/TestReqDTO.java" @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.test.dto.req; + +public class TestReqDTO { +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java" new file mode 100644 index 0000000..9f13f52 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.test.dto.res; + +import lombok.Builder; +import lombok.Getter; + +public class TestResDTO { + + @Builder + @Getter + public static class Testing { + private String testString; + } + + @Builder + @Getter + public static class Exception { + private String testString; + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/TestException.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/TestException.java" new file mode 100644 index 0000000..c26ed05 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/TestException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.test.exception; + +import com.example.umc9th.global.apiPayload.exception.GeneralException; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; + +public class TestException extends GeneralException { + + public TestException(BaseErrorCode code) { + super(code); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java" new file mode 100644 index 0000000..7264f21 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.test.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum TestErrorCode implements BaseErrorCode { + + // For test + TEST_EXCEPTION(HttpStatus.BAD_REQUEST, "TEST400_1", "이거는 테스트"), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryService.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryService.java" new file mode 100644 index 0000000..a6419ab --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryService.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.test.service.query; + +public interface TestQueryService { + void checkFlag(Long flag); +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryServiceImpl.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryServiceImpl.java" new file mode 100644 index 0000000..1af8423 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryServiceImpl.java" @@ -0,0 +1,18 @@ +package com.example.umc9th.domain.test.service.query; + +import com.example.umc9th.domain.test.exception.TestException; +import com.example.umc9th.domain.test.exception.code.TestErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TestQueryServiceImpl implements TestQueryService { + + @Override + public void checkFlag(Long flag) { + if (flag == 1) { + throw new TestException(TestErrorCode.TEST_EXCEPTION); + } + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" new file mode 100644 index 0000000..b5270f8 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" @@ -0,0 +1,29 @@ +package com.example.umc9th.domain.user.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@Getter +@Table(name = "food") + +public class Food { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "food_id") + private Long id; + + @Column(nullable = false, length = 50) + private String foodName; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" new file mode 100644 index 0000000..03bf421 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.TermName; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "term") +public class Term { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "term_id") // DB 컬럼명 명시 + private Long id; + + @Column(name = "term_name", nullable = false) // ENUM 컬럼만 남김 + @Enumerated(EnumType.STRING) + private TermName name; +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" new file mode 100644 index 0000000..1564108 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" @@ -0,0 +1,48 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.Gender; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user") +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(nullable = false) + private Gender gender = Gender.NONE; + + @Column(nullable = false, length = 50, unique = true) + private String email; + + @Column(nullable = false) + private String password; + + @Column(length = 20) + private String phoneNum; + + @Builder.Default + @Column(nullable = false) + private Integer point = 0; + + @Builder.Default + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) + private List userFoodList = new ArrayList<>(); +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" new file mode 100644 index 0000000..fdb0822 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Food; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_food") +public class UserFood { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_food_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id") + private Food food; +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" new file mode 100644 index 0000000..ba7da35 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" @@ -0,0 +1,38 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_mission") +public class UserMission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_mission_id") + private Long id; + + @Column(nullable = false) + private Boolean isComplete; + + @Column + private Integer dDay; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Mission과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + +} \ No newline at end of file diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" new file mode 100644 index 0000000..0e46a62 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Term; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; + +import lombok.*; +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_term") +public class UserTerm { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_term_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "term_id") + private Term term; +} + + diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" new file mode 100644 index 0000000..e07fca7 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum Gender { + MALE, FEMALE, NONE +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" new file mode 100644 index 0000000..1473bd4 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum TermName { + AGE, SERVICE, PRIVACY, LOCATION, MARKETING +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java" new file mode 100644 index 0000000..e15b755 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java" @@ -0,0 +1,37 @@ +package com.example.umc9th.global.apiPayload; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonProperty("result") + private T result; + + + // ✅ 성공한 경우 (성공 코드 인터페이스 사용) + public static ApiResponse onSuccess(BaseSuccessCode code, T result) { + return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + } + + // 실패한 경우 (result 포함) + public static ApiResponse onFailure(BaseErrorCode code, T result) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java" new file mode 100644 index 0000000..d0d553f --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java" new file mode 100644 index 0000000..19d1b22 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java" @@ -0,0 +1,9 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java" new file mode 100644 index 0000000..d41e910 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java" @@ -0,0 +1,31 @@ +package com.example.umc9th.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode{ + + BAD_REQUEST(HttpStatus.BAD_REQUEST, + "COMMON400_1", + "잘못된 요청입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, + "AUTH401_1", + "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, + "AUTH403_1", + "요청이 거부되었습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, + "COMMON404_1", + "요청한 리소스를 찾을 수 없습니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500_1", + "예기치 않은 서버 에러가 발생했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java" new file mode 100644 index 0000000..ef060c8 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java" @@ -0,0 +1,24 @@ +package com.example.umc9th.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode { + + OK(HttpStatus.OK, + "COMMON200", + "요청이 성공적으로 처리되었습니다."), + CREATED(HttpStatus.CREATED, + "COMMON201", + "리소스가 성공적으로 생성되었습니다."), + NO_CONTENT(HttpStatus.NO_CONTENT, + "COMMON204", + "요청이 성공적으로 처리되었으며, 반환할 내용이 없습니다."), + ; + private final HttpStatus status; + private final String code; + private final String message; +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java" new file mode 100644 index 0000000..902d4b5 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java" @@ -0,0 +1,14 @@ +package com.example.umc9th.global.apiPayload.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; + +@Getter +public class GeneralException extends RuntimeException { + private final BaseErrorCode code; + + public GeneralException(BaseErrorCode code) { + super(code.getMessage()); + this.code = code; + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java" new file mode 100644 index 0000000..195e36c --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java" @@ -0,0 +1,42 @@ +package com.example.umc9th.global.apiPayload.handler; + +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.GeneralErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GeneralExceptionAdvice { + + // 애플리케이션에서 발생하는 커스텀 예외를 처리 + @ExceptionHandler(GeneralException.class) + public ResponseEntity> handleException( + GeneralException ex + ) { + + return ResponseEntity.status(ex.getCode().getStatus()) + .body(ApiResponse.onFailure( + ex.getCode(), + null + ) + ); + } + + // 그 외의 정의되지 않은 모든 예외 처리 + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException( + Exception ex + ) { + + BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR; + return ResponseEntity.status(code.getStatus()) + .body(ApiResponse.onFailure( + code, + ex.getMessage() + ) + ); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" new file mode 100644 index 0000000..0ba8875 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" @@ -0,0 +1,15 @@ +package com.example.umc9th.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 em) { + return new JPAQueryFactory(em); + } +} diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" new file mode 100644 index 0000000..70f7459 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.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.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; +} + diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" new file mode 100644 index 0000000..ec8fa85 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" @@ -0,0 +1,18 @@ +spring: + application: + name: umc9th + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PW} + + jpa: + database: mysql + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true diff --git "a/7\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" new file mode 100644 index 0000000..a96d2a7 --- /dev/null +++ "b/7\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" @@ -0,0 +1,13 @@ +package com.example.umc9th; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Umc9thApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" new file mode 100644 index 0000000..f8b7a96 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" new file mode 100644 index 0000000..bf1533f Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" new file mode 100644 index 0000000..35142a6 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" new file mode 100644 index 0000000..991310f Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" new file mode 100644 index 0000000..e961383 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" new file mode 100644 index 0000000..8e25204 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" new file mode 100644 index 0000000..b00ab83 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" new file mode 100644 index 0000000..0e3eec9 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/Umc9thApplication.java" @@ -0,0 +1,15 @@ +package com.example.umc9th; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + + +@SpringBootApplication +@EnableJpaAuditing +public class Umc9thApplication { + public static void main(String[] args) { + SpringApplication.run(Umc9thApplication.class, args); + + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/.DS_Store" new file mode 100644 index 0000000..01ca410 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" new file mode 100644 index 0000000..a67ba17 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.mission.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.mapping.UserMission; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "mission") +public class Mission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "mission_id") + private Long id; + + @Column(nullable = false, length = 100) + private String title; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(nullable = false) + private Integer point; + + @Column(length = 50) + private String verification; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // UserMission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "mission", cascade = CascadeType.ALL) + @Builder.Default + private List userMissionList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/exception/MissionException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/exception/MissionException.java" new file mode 100644 index 0000000..b0faea9 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/exception/MissionException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.mission.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class MissionException extends GeneralException { + + public MissionException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java" new file mode 100644 index 0000000..23de689 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.mission.repository; + +import com.example.umc9th.domain.mission.entity.Mission; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MissionRepository extends JpaRepository { +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" new file mode 100644 index 0000000..55d8dc4 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/controller/ReviewController.java" @@ -0,0 +1,55 @@ +package com.example.umc9th.domain.review.controller; + +import com.example.umc9th.domain.review.converter.ReviewConverter; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.dto.req.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.res.ReviewResDTO; +import com.example.umc9th.domain.review.service.command.ReviewCommandService; +import com.example.umc9th.domain.review.service.query.ReviewQueryService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/reviews") +public class ReviewController { + + private final ReviewQueryService reviewQueryService; + private final ReviewCommandService reviewCommandService; + + /** + * 내가 작성한 리뷰 조회 API + */ + @GetMapping("/user/{userId}") + public ApiResponse getMyReviews( + @PathVariable Long userId, + @RequestParam(required = false) Long storeId, + @RequestParam(required = false) Integer minStar, + @RequestParam(required = false) Integer maxStar + ) { + List reviews = reviewQueryService.getReviewsByUserWithFilters( + userId, storeId, minStar, maxStar + ); + return ApiResponse.onSuccess( + GeneralSuccessCode.OK, + ReviewConverter.toReviewListDTO(reviews) + ); + } + + /** + * 2. 가게에 리뷰 추가하기 API + * POST /reviews + */ + @PostMapping + public ApiResponse createReview( + @RequestBody @Valid ReviewReqDTO.CreateReviewDTO request + ) { + ReviewResDTO.CreateReviewResultDTO result = reviewCommandService.createReview(request); + return ApiResponse.onSuccess(GeneralSuccessCode.CREATED, result); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java" new file mode 100644 index 0000000..0e3abac --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/converter/ReviewConverter.java" @@ -0,0 +1,51 @@ +package com.example.umc9th.domain.review.converter; + +import com.example.umc9th.domain.review.dto.req.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.res.ReviewResDTO; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.User; + +import java.util.List; +import java.util.stream.Collectors; + +public class ReviewConverter { + + public static ReviewResDTO.ReviewListDTO toReviewListDTO(List reviews) { + List reviewDTOs = reviews.stream() + .map(ReviewConverter::toReviewDTO) + .collect(Collectors.toList()); + + return ReviewResDTO.ReviewListDTO.builder() + .reviews(reviewDTOs) + .totalCount(reviewDTOs.size()) + .build(); + } + + private static ReviewResDTO.ReviewDTO toReviewDTO(ReviewResponse review) { + return ReviewResDTO.ReviewDTO.builder() + .reviewId(review.getReviewId()) + .content(review.getContent()) + .star(review.getStar()) + .build(); + } + + // Review 엔티티 생성 + public static Review toReview(ReviewReqDTO.CreateReviewDTO dto, User user, Store store) { + return Review.builder() + .star(dto.getStar()) + .content(dto.getContent()) + .user(user) + .store(store) + .build(); + } + + // 생성 결과 DTO + public static ReviewResDTO.CreateReviewResultDTO toCreateReviewResultDTO(Review review) { + return ReviewResDTO.CreateReviewResultDTO.builder() + .reviewId(review.getId()) + .createdAt(review.getCreatedAt()) + .build(); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" new file mode 100644 index 0000000..f25bf7d --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/ReviewResponse.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.review.dto; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Getter; + +@Getter +public class ReviewResponse { + private Long reviewId; + private String content; + private Integer star; + + @QueryProjection + public ReviewResponse(Long reviewId, String content, Integer star) { + this.reviewId = reviewId; + this.content = content; + this.star = star; + } +} + diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/req/ReviewReqDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/req/ReviewReqDTO.java" new file mode 100644 index 0000000..32d9429 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/req/ReviewReqDTO.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.review.dto.req; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class ReviewReqDTO { + + @Getter + public static class CreateReviewDTO { + @NotNull(message = "별점은 필수입니다.") + @Min(value = 1, message = "별점은 1 이상이어야 합니다.") + @Max(value = 5, message = "별점은 5 이하여야 합니다.") + private Integer star; + + private String content; + + @NotNull(message = "가게 ID는 필수입니다.") + private Long storeId; + } + +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/res/ReviewResDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/res/ReviewResDTO.java" new file mode 100644 index 0000000..9673953 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/dto/res/ReviewResDTO.java" @@ -0,0 +1,40 @@ +package com.example.umc9th.domain.review.dto.res; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class ReviewResDTO { + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewListDTO { + private List reviews; + private Integer totalCount; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ReviewDTO { + private Long reviewId; + private String content; + private Integer star; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateReviewResultDTO { + private Long reviewId; + private LocalDateTime createdAt; + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" new file mode 100644 index 0000000..9bc62ff --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/Review.java" @@ -0,0 +1,54 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review") +public class Review extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_id") + private Long id; + + @Column(nullable = false) + private Integer star; + + @Column(columnDefinition = "TEXT") + private String content; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 User는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Store와의 연관 관계 (N:1) - 연관 관계의 주인 + // Review 입장에서 Store는 N:1 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "store_id") + private Store store; + + // ReviewPhoto와의 연관 관계 (1:N) + // Review 입장에서 ReviewPhoto는 1:N + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List reviewPhotoList = new ArrayList<>(); + + // ReviewReply와의 연관 관계 (1:N) + // Review 입장에서 ReviewReply는 1:N // 헷갈림 + @OneToMany(mappedBy = "review", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List reviewReplyList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" new file mode 100644 index 0000000..56310ba --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewPhoto.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_photo") +public class ReviewPhoto extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_photo_id") + private Long id; + + @Column(name = "review_photo_url", nullable = false) + private String reviewPhotoUrl; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; + +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" new file mode 100644 index 0000000..ccaa29e --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/entity/ReviewReply.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.domain.review.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "review_reply") +public class ReviewReply extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "reply_id") + private Long id; + + @Column(columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id") + private Review review; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java" new file mode 100644 index 0000000..2ae42f6 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/ReviewException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.review.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class ReviewException extends GeneralException { + + public ReviewException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java" new file mode 100644 index 0000000..921f95a --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/exception/code/ReviewErrorCode.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.review.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ReviewErrorCode implements BaseErrorCode { + + // Review 관련 에러 + INVALID_USER_ID(HttpStatus.BAD_REQUEST, "REVIEW400_1", "유효하지 않은 사용자 ID입니다."), + INVALID_STAR_RANGE(HttpStatus.BAD_REQUEST, "REVIEW400_2", "별점은 1~5 사이여야 하며, 최소값이 최대값보다 클 수 없습니다."), + + REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW404_1", "리뷰를 찾을 수 없습니다."), + REVIEW_ACCESS_DENIED(HttpStatus.FORBIDDEN, "REVIEW403_1", "해당 리뷰에 접근할 권한이 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" new file mode 100644 index 0000000..2781dcc --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.entity.Review; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReviewRepository extends JpaRepository, ReviewRepositoryCustom { +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" new file mode 100644 index 0000000..9989c1c --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryCustom.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.querydsl.core.types.Predicate; + +import java.util.List; + +public interface ReviewRepositoryCustom { + List searchReview(Predicate predicate); // predicate를 사용한 동적 쿼리 메서드 +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" new file mode 100644 index 0000000..ad1eef3 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepositoryImpl.java" @@ -0,0 +1,41 @@ +package com.example.umc9th.domain.review.repository; + +import com.example.umc9th.domain.review.dto.QReviewResponse; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.QReview; +import com.example.umc9th.domain.store.entity.QStore; +import com.querydsl.core.types.Predicate; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +@Transactional(readOnly = true) // 조회 작업이므로 readOnly +public class ReviewRepositoryImpl implements ReviewRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List searchReview(Predicate predicate) { // predicate를 사용한 동적 쿼리 메서드 + // Q 클래스 선언 + QReview review = QReview.review; + QStore store = QStore.store; + + return queryFactory + .select(new QReviewResponse( + review.id, + review.content, + review.star + )) + .from(review) + .leftJoin(review.store, store) // Store 정보가 필요하면 join + .where(predicate) // 동적으로 전달받은 조건 적용 + .orderBy(review.id.desc()) // 최신 리뷰가 먼저 오도록 정렬 + .fetch(); + } + +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandService.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandService.java" new file mode 100644 index 0000000..261022c --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandService.java" @@ -0,0 +1,8 @@ +package com.example.umc9th.domain.review.service.command; + +import com.example.umc9th.domain.review.dto.req.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.res.ReviewResDTO; + +public interface ReviewCommandService { + ReviewResDTO.CreateReviewResultDTO createReview(ReviewReqDTO.CreateReviewDTO dto); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandServiceImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandServiceImpl.java" new file mode 100644 index 0000000..35f0a73 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/command/ReviewCommandServiceImpl.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.review.service.command; + +import com.example.umc9th.domain.review.converter.ReviewConverter; +import com.example.umc9th.domain.review.dto.req.ReviewReqDTO; +import com.example.umc9th.domain.review.dto.res.ReviewResDTO; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.review.repository.ReviewRepository; +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.store.exception.StoreException; +import com.example.umc9th.domain.store.exception.code.StoreErrorCode; +import com.example.umc9th.domain.store.repository.StoreRepository; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class ReviewCommandServiceImpl implements ReviewCommandService { + + private final ReviewRepository reviewRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; + + @Override + public ReviewResDTO.CreateReviewResultDTO createReview(ReviewReqDTO.CreateReviewDTO dto) { + // 하드코딩된 유저 (DB에 있는 유저 ID 1번) + User user = userRepository.findById(1L) + .orElseThrow(() -> new RuntimeException("유저를 찾을 수 없습니다.")); + + // 가게 조회 + Store store = storeRepository.findById(dto.getStoreId()) + .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); + + // Review 엔티티 생성 + Review review = ReviewConverter.toReview(dto, user, store); + + // DB 저장 + Review savedReview = reviewRepository.save(review); + + // 응답 DTO 반환 + return ReviewConverter.toCreateReviewResultDTO(savedReview); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryService.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryService.java" new file mode 100644 index 0000000..9a477a9 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryService.java" @@ -0,0 +1,18 @@ +package com.example.umc9th.domain.review.service.query; + +import com.example.umc9th.domain.review.dto.ReviewResponse; + +import java.util.List; + +public interface ReviewQueryService { + + /** + * 사용자의 리뷰 조회 (필터링 포함) + */ + List getReviewsByUserWithFilters( + Long userId, + Long storeId, + Integer minStar, + Integer maxStar + ); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryServiceImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryServiceImpl.java" new file mode 100644 index 0000000..eefcec8 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/review/service/query/ReviewQueryServiceImpl.java" @@ -0,0 +1,87 @@ +package com.example.umc9th.domain.review.service.query; + +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.QReview; +import com.example.umc9th.domain.review.exception.ReviewException; +import com.example.umc9th.domain.review.exception.code.ReviewErrorCode; +import com.example.umc9th.domain.review.repository.ReviewRepository; +import com.querydsl.core.BooleanBuilder; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) // 조회 작업이므로 readOnly +public class ReviewQueryServiceImpl implements ReviewQueryService { + + private final ReviewRepository reviewRepository; + + @Override + public List getReviewsByUserWithFilters( + Long userId, + Long storeId, + Integer minStar, + Integer maxStar + ) { + // 검증: 필수 파라미터 체크 + validateUserId(userId); + + // 검증: 별점 범위 체크 + validateStarRange(minStar, maxStar); + + // Q 클래스 선언 + QReview review = QReview.review; + + // BooleanBuilder로 동적 조건 생성 + BooleanBuilder builder = new BooleanBuilder(); + + // 필수 조건: 특정 사용자의 리뷰만 + builder.and(review.user.id.eq(userId)); + + // 선택 조건: 가게 필터링 + if (storeId != null) { + builder.and(review.store.id.eq(storeId)); + } + + // 선택 조건: 별점 필터링 + if (minStar != null && maxStar != null) { + builder.and(review.star.between(minStar.floatValue(), maxStar.floatValue())); + } else if (minStar != null) { + builder.and(review.star.goe(minStar.floatValue())); + } else if (maxStar != null) { + builder.and(review.star.loe(maxStar.floatValue())); + } + + // Repository 호출 & 결과 반환 + return reviewRepository.searchReview(builder); + } + + /** + * 사용자 ID 검증 + */ + private void validateUserId(Long userId) { + if (userId == null || userId <= 0) { + throw new ReviewException(ReviewErrorCode.INVALID_USER_ID); + } + } + + /** + * 별점 범위 검증 + */ + private void validateStarRange(Integer minStar, Integer maxStar) { + if (minStar != null && (minStar < 1 || minStar > 5)) { + throw new ReviewException(ReviewErrorCode.INVALID_STAR_RANGE); + } + + if (maxStar != null && (maxStar < 1 || maxStar > 5)) { + throw new ReviewException(ReviewErrorCode.INVALID_STAR_RANGE); + } + + if (minStar != null && maxStar != null && minStar > maxStar) { + throw new ReviewException(ReviewErrorCode.INVALID_STAR_RANGE); + } + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/.DS_Store" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/.DS_Store" new file mode 100644 index 0000000..e90f688 Binary files /dev/null and "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/.DS_Store" differ diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java" new file mode 100644 index 0000000..4a7fec9 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.store.controller; + +import com.example.umc9th.domain.store.dto.req.StoreReqDTO; +import com.example.umc9th.domain.store.dto.res.StoreResDTO; +import com.example.umc9th.domain.store.service.command.StoreCommandService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/stores") +public class StoreController { + + private final StoreCommandService storeCommandService; + + /** + * 1. 특정 지역에 가게 추가하기 API + * POST /stores + */ + @PostMapping + public ApiResponse createStore( + @RequestBody @Valid StoreReqDTO.CreateStoreDTO request + ) { + StoreResDTO.CreateStoreResultDTO result = storeCommandService.createStore(request); + return ApiResponse.onSuccess(GeneralSuccessCode.CREATED, result); + } + + /** + * 2. 가게의 미션을 도전 중인 미션에 추가(미션 도전하기) API + * POST /stores/{storeId}/missions/{missionId}/challenge + */ + @PostMapping("/{storeId}/missions/{missionId}/challenge") + public ApiResponse challengeMission( + @PathVariable Long storeId, + @PathVariable Long missionId, + @RequestParam Long userId + ) { + StoreResDTO.ChallengeMissionResultDTO result = + storeCommandService.challengeMission(storeId, missionId, userId); + + return ApiResponse.onSuccess(GeneralSuccessCode.CREATED, result); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/converter/StoreConverter.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/converter/StoreConverter.java" new file mode 100644 index 0000000..c0861ce --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/converter/StoreConverter.java" @@ -0,0 +1,35 @@ +package com.example.umc9th.domain.store.converter; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.store.dto.req.StoreReqDTO; +import com.example.umc9th.domain.store.dto.res.StoreResDTO; +import com.example.umc9th.domain.store.entity.Location; +import com.example.umc9th.domain.store.entity.Store; + +public class StoreConverter { + + public static Store toStore(StoreReqDTO.CreateStoreDTO dto, Location location) { + return Store.builder() + .name(dto.getName()) + .detailAddress(dto.getDetailAddress()) + .location(location) + .build(); + } + + public static StoreResDTO.CreateStoreResultDTO toCreateStoreResultDTO(Store store) { + return StoreResDTO.CreateStoreResultDTO.builder() + .storeId(store.getId()) + .name(store.getName()) + .createdAt(store.getCreatedAt()) + .build(); + } + + public static StoreResDTO.ChallengeMissionResultDTO toChallengeMissionResultDTO(Store store, Mission mission) { + return StoreResDTO.ChallengeMissionResultDTO.builder() + .storeId(store.getId()) + .storeName(store.getName()) + .missionId(mission.getId()) + .missionName(mission.getTitle()) + .build(); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/dto/req/StoreReqDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/dto/req/StoreReqDTO.java" new file mode 100644 index 0000000..ee796bf --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/dto/req/StoreReqDTO.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.store.dto.req; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +public class StoreReqDTO { + + @Getter + public static class CreateStoreDTO { + @NotBlank(message = "가게 이름은 필수입니다.") + private String name; + + private String detailAddress; + + @NotNull(message = "지역 ID는 필수입니다.") + private Long locationId; + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/dto/res/StoreResDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/dto/res/StoreResDTO.java" new file mode 100644 index 0000000..76a10f6 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/dto/res/StoreResDTO.java" @@ -0,0 +1,32 @@ +package com.example.umc9th.domain.store.dto.res; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class StoreResDTO { + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateStoreResultDTO { + private Long storeId; + private String name; + private LocalDateTime createdAt; + } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class ChallengeMissionResultDTO { + private Long storeId; + private String storeName; + private Long missionId; + private String missionName; + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" new file mode 100644 index 0000000..73384e1 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Location.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "location") +public class Location extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "location_id") + private Long id; + + @Column(name = "location_name", nullable = false, length = 50) + private String name; + + // Store와의 연관 관계 (1:N) + @OneToMany(mappedBy = "location", cascade = CascadeType.ALL) + @Builder.Default + private List storeList = new ArrayList<>(); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" new file mode 100644 index 0000000..99ad0b6 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/entity/Store.java" @@ -0,0 +1,46 @@ +package com.example.umc9th.domain.store.entity; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "store") +public class Store extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "store_id") + private Long id; + + @Column(name = "store_name", nullable = false, length = 50) + private String name; + + @Column(name = "detailAddress", length = 100) + private String detailAddress; + + // Location과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "location_id") + private Location location; + + // Review와의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List reviewList = new ArrayList<>(); + + // Mission과의 연관 관계 (1:N) + @OneToMany(mappedBy = "store", cascade = CascadeType.ALL) + @Builder.Default + private List missionList = new ArrayList<>(); + +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/exception/StoreException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/exception/StoreException.java" new file mode 100644 index 0000000..296a979 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/exception/StoreException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.store.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class StoreException extends GeneralException { + + public StoreException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/exception/code/StoreErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/exception/code/StoreErrorCode.java" new file mode 100644 index 0000000..a1709e1 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/exception/code/StoreErrorCode.java" @@ -0,0 +1,21 @@ +package com.example.umc9th.domain.store.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum StoreErrorCode implements BaseErrorCode { + + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE404_1", "가게를 찾을 수 없습니다."), + LOCATION_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE404_2", "지역을 찾을 수 없습니다."), + INVALID_STORE_ID(HttpStatus.BAD_REQUEST, "STORE400_1", "유효하지 않은 가게 ID입니다."), + MISSION_NOT_FOUND_IN_STORE(HttpStatus.NOT_FOUND, "STORE404_3", "가게에 해당 미션이 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/repository/LocationRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/repository/LocationRepository.java" new file mode 100644 index 0000000..2feba23 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/repository/LocationRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.store.repository; + +import com.example.umc9th.domain.store.entity.Location; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LocationRepository extends JpaRepository { +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/repository/StoreRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/repository/StoreRepository.java" new file mode 100644 index 0000000..e49ad93 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/repository/StoreRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.store.repository; + +import com.example.umc9th.domain.store.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StoreRepository extends JpaRepository { +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/service/command/StoreCommandService.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/service/command/StoreCommandService.java" new file mode 100644 index 0000000..7e2b19d --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/service/command/StoreCommandService.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.store.service.command; + +import com.example.umc9th.domain.store.dto.req.StoreReqDTO; +import com.example.umc9th.domain.store.dto.res.StoreResDTO; + +public interface StoreCommandService { + StoreResDTO.CreateStoreResultDTO createStore(StoreReqDTO.CreateStoreDTO dto); + StoreResDTO.ChallengeMissionResultDTO challengeMission(Long storeId, Long missionId, Long userId); +} + diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/service/command/StoreCommandServiceImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/service/command/StoreCommandServiceImpl.java" new file mode 100644 index 0000000..33fa37c --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/store/service/command/StoreCommandServiceImpl.java" @@ -0,0 +1,86 @@ +package com.example.umc9th.domain.store.service.command; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.store.converter.StoreConverter; +import com.example.umc9th.domain.store.dto.req.StoreReqDTO; +import com.example.umc9th.domain.store.dto.res.StoreResDTO; +import com.example.umc9th.domain.store.entity.Location; +import com.example.umc9th.domain.store.entity.Store; +import com.example.umc9th.domain.store.exception.StoreException; +import com.example.umc9th.domain.store.exception.code.StoreErrorCode; +import com.example.umc9th.domain.store.repository.LocationRepository; +import com.example.umc9th.domain.store.repository.StoreRepository; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.domain.user.entity.mapping.UserMission; +import com.example.umc9th.domain.user.enums.UserMissionStatus; +import com.example.umc9th.domain.user.exception.UserException; +import com.example.umc9th.domain.user.repository.UserMissionRepository; +import com.example.umc9th.domain.user.repository.UserRepository; +import com.example.umc9th.domain.user.exception.code.UserErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional +public class StoreCommandServiceImpl implements StoreCommandService { + + private final StoreRepository storeRepository; + private final LocationRepository locationRepository; + private final UserMissionRepository userMissionRepository; + private final UserRepository userRepository; + + @Override + public StoreResDTO.CreateStoreResultDTO createStore(StoreReqDTO.CreateStoreDTO dto) { + // 지역 조회 + Location location = locationRepository.findById(dto.getLocationId()) + .orElseThrow(() -> new StoreException(StoreErrorCode.LOCATION_NOT_FOUND)); + + // Store 엔티티 생성 + Store store = StoreConverter.toStore(dto, location); + + // DB 저장 + Store savedStore = storeRepository.save(store); + + // 응답 DTO 반환 + return StoreConverter.toCreateStoreResultDTO(savedStore); + } + + @Override + public StoreResDTO.ChallengeMissionResultDTO challengeMission(Long storeId, Long missionId, Long userId) { + // 가게 조회 + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); + + // 미션 조회 + Mission mission = store.getMissionList().stream() + .filter(m -> m.getId().equals(missionId)) + .findFirst() + .orElseThrow(() -> new StoreException(StoreErrorCode.MISSION_NOT_FOUND_IN_STORE)); + + // 유저 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new UserException(UserErrorCode.NOT_FOUND)); + + Optional existing = userMissionRepository.findByUserIdAndMissionId(userId, missionId); + if(existing.isPresent()) { + throw new UserException(UserErrorCode.ALREADY_CHALLENGING); + } + + // UserMission 엔티티 생성 + UserMission userMission = UserMission.builder() + .mission(mission) + .user(user) + .status(UserMissionStatus.CHALLENGING) // 도전 중 상태로 설정 + .build(); + + // DB 저장 + userMissionRepository.save(userMission); + + // 응답 DTO 반환 + return StoreConverter.toChallengeMissionResultDTO(store, mission); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/controller/TestController.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/controller/TestController.java" new file mode 100644 index 0000000..851510f --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/controller/TestController.java" @@ -0,0 +1,44 @@ +package com.example.umc9th.domain.test.controller; + +import com.example.umc9th.domain.test.converter.TestConverter; +import com.example.umc9th.domain.test.dto.res.TestResDTO; +import com.example.umc9th.domain.test.service.query.TestQueryService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/temp") +public class TestController { + + private final TestQueryService testQueryService; + + @GetMapping("/test") + public ApiResponse test() { + // 응답 코드 정의 + GeneralSuccessCode code = GeneralSuccessCode.OK; + + return ApiResponse.onSuccess( + code, + TestConverter.toTestingDTO("This is Test!") + ); + } + + // 예외 상황 + @GetMapping("/exception") + public ApiResponse exception( + @RequestParam Long flag + ) { + + testQueryService.checkFlag(flag); + + // 응답 코드 정의 + GeneralSuccessCode code = GeneralSuccessCode.OK; + return ApiResponse.onSuccess(code, TestConverter.toExceptionDTO("This is Test!")); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java" new file mode 100644 index 0000000..af81183 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/converter/TestConverter.java" @@ -0,0 +1,24 @@ +package com.example.umc9th.domain.test.converter; + +import com.example.umc9th.domain.test.dto.res.TestResDTO; + +public class TestConverter { + + // 객체 -> DTO + public static TestResDTO.Testing toTestingDTO( + String testing + ) { + return TestResDTO.Testing.builder() + .testString(testing) + .build(); + } + + // 객체 -> DTO + public static TestResDTO.Exception toExceptionDTO( + String testing + ){ + return TestResDTO.Exception.builder() + .testString(testing) + .build(); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/req/TestReqDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/req/TestReqDTO.java" new file mode 100644 index 0000000..405e3b9 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/req/TestReqDTO.java" @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.test.dto.req; + +public class TestReqDTO { +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java" new file mode 100644 index 0000000..9f13f52 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/dto/res/TestResDTO.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.test.dto.res; + +import lombok.Builder; +import lombok.Getter; + +public class TestResDTO { + + @Builder + @Getter + public static class Testing { + private String testString; + } + + @Builder + @Getter + public static class Exception { + private String testString; + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/TestException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/TestException.java" new file mode 100644 index 0000000..c26ed05 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/TestException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.test.exception; + +import com.example.umc9th.global.apiPayload.exception.GeneralException; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; + +public class TestException extends GeneralException { + + public TestException(BaseErrorCode code) { + super(code); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java" new file mode 100644 index 0000000..7264f21 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/exception/code/TestErrorCode.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.test.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum TestErrorCode implements BaseErrorCode { + + // For test + TEST_EXCEPTION(HttpStatus.BAD_REQUEST, "TEST400_1", "이거는 테스트"), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryService.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryService.java" new file mode 100644 index 0000000..a6419ab --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryService.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.test.service.query; + +public interface TestQueryService { + void checkFlag(Long flag); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryServiceImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryServiceImpl.java" new file mode 100644 index 0000000..1af8423 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/test/service/query/TestQueryServiceImpl.java" @@ -0,0 +1,18 @@ +package com.example.umc9th.domain.test.service.query; + +import com.example.umc9th.domain.test.exception.TestException; +import com.example.umc9th.domain.test.exception.code.TestErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TestQueryServiceImpl implements TestQueryService { + + @Override + public void checkFlag(Long flag) { + if (flag == 1) { + throw new TestException(TestErrorCode.TEST_EXCEPTION); + } + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/controller/UserController.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/controller/UserController.java" new file mode 100644 index 0000000..84c50ba --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/controller/UserController.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.user.controller; + +import com.example.umc9th.domain.user.dto.UserReqDTO.UserReqDTO; +import com.example.umc9th.domain.user.dto.UserResDTO.UserResDTO; +import com.example.umc9th.domain.user.exception.code.UserSuccessCode; +import com.example.umc9th.domain.user.service.command.UserCommandService; +import com.example.umc9th.global.apiPayload.ApiResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class UserController { + + private final UserCommandService userCommandService; + + @PostMapping("/sign-up") + public ApiResponse signUp( + @RequestBody @Valid UserReqDTO.JoinDTO dto + ){ + UserResDTO.JoinDTO response = userCommandService.signup(dto); + return ApiResponse.onSuccess(UserSuccessCode.FOUND, response); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/converter/UserConverter.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/converter/UserConverter.java" new file mode 100644 index 0000000..2b68fe1 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/converter/UserConverter.java" @@ -0,0 +1,35 @@ +package com.example.umc9th.domain.user.converter; + +import com.example.umc9th.domain.user.dto.UserReqDTO.UserReqDTO; +import com.example.umc9th.domain.user.dto.UserResDTO.UserResDTO; +import com.example.umc9th.domain.user.entity.User; + +public class UserConverter { + + // Entity -> DTO + public static UserResDTO.JoinDTO toJoinDTO(User user) { + return UserResDTO.JoinDTO.builder() + .userId(user.getId()) + .createAt(user.getCreatedAt()) + .build(); + } + + // DTO -> Entity + public static User toUser(UserReqDTO.JoinDTO dto) { + // address와 specAddress를 합쳐서 저장 + String fullAddress = dto.address(); + if (dto.specAddress() != null && !dto.specAddress().isEmpty()) { + fullAddress = dto.address() + " " + dto.specAddress(); + } + + return User.builder() + .name(dto.name()) + .birth(dto.birth()) + .address(fullAddress) + .gender(dto.gender()) + .email(dto.email()) + .password(dto.password()) + .phoneNum(dto.phoneNum()) + .build(); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/dto/UserReqDTO/UserReqDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/dto/UserReqDTO/UserReqDTO.java" new file mode 100644 index 0000000..ba26efc --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/dto/UserReqDTO/UserReqDTO.java" @@ -0,0 +1,22 @@ +package com.example.umc9th.domain.user.dto.UserReqDTO; + +import com.example.umc9th.domain.user.enums.Gender; +import com.example.umc9th.global.annotation.ExistFoods; + +import java.time.LocalDate; +import java.util.List; + +public class UserReqDTO { + public record JoinDTO( + String name, + Gender gender, + LocalDate birth, + String address, // Address -> String으로 변경 + String specAddress, + String email, + String password, + String phoneNum, + @ExistFoods + List preferCategory + ) {} +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/dto/UserResDTO/UserResDTO.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/dto/UserResDTO/UserResDTO.java" new file mode 100644 index 0000000..e26317c --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/dto/UserResDTO/UserResDTO.java" @@ -0,0 +1,13 @@ +package com.example.umc9th.domain.user.dto.UserResDTO; + +import lombok.Builder; + +import java.time.LocalDateTime; + +public class UserResDTO { + @Builder + public record JoinDTO( + Long userId, // memberId -> userId로 변경 + LocalDateTime createAt + ){} +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" new file mode 100644 index 0000000..ca884aa --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Food.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.domain.user.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@AllArgsConstructor(access = lombok.AccessLevel.PRIVATE) +@Getter +@Table(name = "food") + +public class Food { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "food_id") + private Long id; + + @Column(nullable = false, length = 50) + private String foodName; + +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" new file mode 100644 index 0000000..03bf421 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/Term.java" @@ -0,0 +1,23 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.enums.TermName; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Table(name = "term") +public class Term { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "term_id") // DB 컬럼명 명시 + private Long id; + + @Column(name = "term_name", nullable = false) // ENUM 컬럼만 남김 + @Enumerated(EnumType.STRING) + private TermName name; +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" new file mode 100644 index 0000000..b75ff30 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/User.java" @@ -0,0 +1,55 @@ +package com.example.umc9th.domain.user.entity; + +import com.example.umc9th.domain.user.entity.mapping.UserFood; +import com.example.umc9th.domain.user.enums.Gender; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user") +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(nullable = false) + private Gender gender = Gender.NONE; + + @Column(nullable = false, length = 50, unique = true) + private String email; + + @Column + private LocalDate birth; + + @Column + private String address; + + @Column(nullable = false) + private String password; + + @Column(length = 20) + private String phoneNum; + + @Builder.Default + @Column(nullable = false) + private Integer point = 0; + + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) + private List userFoodList = new ArrayList<>(); +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" new file mode 100644 index 0000000..fdb0822 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserFood.java" @@ -0,0 +1,27 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Food; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_food") +public class UserFood { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_food_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "food_id") + private Food food; +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" new file mode 100644 index 0000000..4dd978f --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserMission.java" @@ -0,0 +1,40 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.domain.user.enums.UserMissionStatus; +import com.example.umc9th.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_mission") +public class UserMission extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_mission_id") + private Long id; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private UserMissionStatus status; + + @Column + private Integer dDay; + + // User와의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + // Mission과의 연관 관계 (N:1) - 연관 관계의 주인 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "mission_id") + private Mission mission; + +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" new file mode 100644 index 0000000..0e46a62 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/entity/mapping/UserTerm.java" @@ -0,0 +1,30 @@ +package com.example.umc9th.domain.user.entity.mapping; + +import com.example.umc9th.domain.user.entity.Term; +import com.example.umc9th.domain.user.entity.User; +import jakarta.persistence.*; + +import lombok.*; +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "user_term") +public class UserTerm { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_term_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "term_id") + private Term term; +} + + diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" new file mode 100644 index 0000000..e07fca7 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/Gender.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum Gender { + MALE, FEMALE, NONE +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" new file mode 100644 index 0000000..1473bd4 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/TermName.java" @@ -0,0 +1,5 @@ +package com.example.umc9th.domain.user.enums; + +public enum TermName { + AGE, SERVICE, PRIVACY, LOCATION, MARKETING +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/UserMissionStatus.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/UserMissionStatus.java" new file mode 100644 index 0000000..89c2c71 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/enums/UserMissionStatus.java" @@ -0,0 +1,6 @@ +package com.example.umc9th.domain.user.enums; + +public enum UserMissionStatus { + CHALLENGING, + COMPLETE +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/FoodException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/FoodException.java" new file mode 100644 index 0000000..32fdfb4 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/FoodException.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.user.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class FoodException extends GeneralException { + + public FoodException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/UserException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/UserException.java" new file mode 100644 index 0000000..44031b5 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/UserException.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.user.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; + +public class UserException extends GeneralException{ + public UserException(BaseErrorCode code) { + super(code); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/FoodErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/FoodErrorCode.java" new file mode 100644 index 0000000..f0f91fe --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/FoodErrorCode.java" @@ -0,0 +1,19 @@ +package com.example.umc9th.domain.user.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum FoodErrorCode implements BaseErrorCode { + + FOOD_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD404_1", "음식을 찾을 수 없습니다."), + INVALID_FOOD_ID(HttpStatus.BAD_REQUEST, "FOOD400_1", "유효하지 않은 음식 ID입니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/FoodSuccessCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/FoodSuccessCode.java" new file mode 100644 index 0000000..6048d56 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/FoodSuccessCode.java" @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.user.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum FoodSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "MEMBER200_1", + "성공적으로 조회했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/UserErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/UserErrorCode.java" new file mode 100644 index 0000000..29ddac6 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/UserErrorCode.java" @@ -0,0 +1,24 @@ +package com.example.umc9th.domain.user.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum UserErrorCode implements BaseErrorCode { + + NOT_FOUND(HttpStatus.NOT_FOUND, + "MEMBER404_1", + "해당 사용자를 찾지 못했습니다."), + + ALREADY_CHALLENGING(HttpStatus.BAD_REQUEST, + "MEMBER400_1", + "이미 진행중인 미션입니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/UserSuccessCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/UserSuccessCode.java" new file mode 100644 index 0000000..7fa877e --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/exception/code/UserSuccessCode.java" @@ -0,0 +1,20 @@ +package com.example.umc9th.domain.user.exception.code; + +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum UserSuccessCode implements BaseSuccessCode { + + FOUND(HttpStatus.OK, + "MEMBER200_1", + "성공적으로 사용자를 조회했습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/FoodRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/FoodRepository.java" new file mode 100644 index 0000000..d739c62 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/FoodRepository.java" @@ -0,0 +1,9 @@ +package com.example.umc9th.domain.user.repository; + +import com.example.umc9th.domain.user.entity.Food; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FoodRepository extends JpaRepository { +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserFoodRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserFoodRepository.java" new file mode 100644 index 0000000..0afa938 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserFoodRepository.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.domain.user.repository; + +import com.example.umc9th.domain.user.entity.mapping.UserFood; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +// UserFoodRepository - UserFood 엔티티 관리 +@Repository +public interface UserFoodRepository extends JpaRepository { +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserMissionRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserMissionRepository.java" new file mode 100644 index 0000000..15da22d --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserMissionRepository.java" @@ -0,0 +1,11 @@ +package com.example.umc9th.domain.user.repository; + +import com.example.umc9th.domain.user.entity.mapping.UserMission; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserMissionRepository extends JpaRepository { + // 이미 도전 중인 미션인지 확인 + Optional findByUserIdAndMissionId(Long userId, Long missionId); +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserRepository.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserRepository.java" new file mode 100644 index 0000000..8b1333d --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/repository/UserRepository.java" @@ -0,0 +1,7 @@ +package com.example.umc9th.domain.user.repository; + +import com.example.umc9th.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/command/UserCommandService.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/command/UserCommandService.java" new file mode 100644 index 0000000..31af9a4 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/command/UserCommandService.java" @@ -0,0 +1,8 @@ +package com.example.umc9th.domain.user.service.command; + +import com.example.umc9th.domain.user.dto.UserReqDTO.UserReqDTO; +import com.example.umc9th.domain.user.dto.UserResDTO.UserResDTO; + +public interface UserCommandService { + UserResDTO.JoinDTO signup(UserReqDTO.JoinDTO dto); // ReqDTO로 받아서 ResDTO로 반환 +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/command/UserCommandServiceImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/command/UserCommandServiceImpl.java" new file mode 100644 index 0000000..49e86e7 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/command/UserCommandServiceImpl.java" @@ -0,0 +1,65 @@ +package com.example.umc9th.domain.user.service.command; + +import com.example.umc9th.domain.user.converter.UserConverter; +import com.example.umc9th.domain.user.dto.UserReqDTO.UserReqDTO; +import com.example.umc9th.domain.user.dto.UserResDTO.UserResDTO; +import com.example.umc9th.domain.user.entity.Food; +import com.example.umc9th.domain.user.entity.User; +import com.example.umc9th.domain.user.entity.mapping.UserFood; +import com.example.umc9th.domain.user.exception.FoodException; +import com.example.umc9th.domain.user.exception.code.FoodErrorCode; +import com.example.umc9th.domain.user.repository.FoodRepository; +import com.example.umc9th.domain.user.repository.UserFoodRepository; +import com.example.umc9th.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class UserCommandServiceImpl implements UserCommandService { + + private final UserRepository userRepository; + private final UserFoodRepository userFoodRepository; + private final FoodRepository foodRepository; + + @Override + @Transactional + public UserResDTO.JoinDTO signup(UserReqDTO.JoinDTO dto) { + // 사용자 생성 + User user = UserConverter.toUser(dto); + + // DB 저장 + userRepository.save(user); + + // 선호 음식 존재 여부 확인 + if (dto.preferCategory() != null && dto.preferCategory().size() > 0) { + + List userFoodList = new ArrayList<>(); + + // 선호 음식 ID 별 조회 + for (Long id : dto.preferCategory()) { + // 음식 존재 여부 검증 + Food food = foodRepository.findById(id) + .orElseThrow(() -> new FoodException(FoodErrorCode.FOOD_NOT_FOUND)); + + // UserFood 엔티티 생성 + UserFood userFood = UserFood.builder() + .user(user) + .food(food) + .build(); + + userFoodList.add(userFood); + } + + // DB 저장 + userFoodRepository.saveAll(userFoodList); + } + + // 응답 DTO로 변환 + return UserConverter.toJoinDTO(user); + } +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/query/UserQueryService.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/query/UserQueryService.java" new file mode 100644 index 0000000..d45073c --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/query/UserQueryService.java" @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.user.service.query; + +public class UserQueryService { +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/query/UserQueryServiceImpl.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/query/UserQueryServiceImpl.java" new file mode 100644 index 0000000..fd39b44 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/domain/user/service/query/UserQueryServiceImpl.java" @@ -0,0 +1,4 @@ +package com.example.umc9th.domain.user.service.query; + +public class UserQueryServiceImpl { +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/annotation/ExistFoods.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/annotation/ExistFoods.java" new file mode 100644 index 0000000..71049f1 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/annotation/ExistFoods.java" @@ -0,0 +1,21 @@ +package com.example.umc9th.global.annotation; + +import com.example.umc9th.global.validator.FoodExistValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Constraint(validatedBy = FoodExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistFoods { + //여기서 디폴트 메시지를 설정합니다. + String message() default "해당 음식이 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java" new file mode 100644 index 0000000..e15b755 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/ApiResponse.java" @@ -0,0 +1,37 @@ +package com.example.umc9th.global.apiPayload; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.BaseSuccessCode; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonProperty("result") + private T result; + + + // ✅ 성공한 경우 (성공 코드 인터페이스 사용) + public static ApiResponse onSuccess(BaseSuccessCode code, T result) { + return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + } + + // 실패한 경우 (result 포함) + public static ApiResponse onFailure(BaseErrorCode code, T result) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java" new file mode 100644 index 0000000..d0d553f --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseErrorCode.java" @@ -0,0 +1,10 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java" new file mode 100644 index 0000000..19d1b22 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/BaseSuccessCode.java" @@ -0,0 +1,9 @@ +package com.example.umc9th.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java" new file mode 100644 index 0000000..c1ed6f2 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralErrorCode.java" @@ -0,0 +1,35 @@ +package com.example.umc9th.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode{ + + BAD_REQUEST(HttpStatus.BAD_REQUEST, + "COMMON400_1", + "잘못된 요청입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, + "AUTH401_1", + "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, + "AUTH403_1", + "요청이 거부되었습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, + "COMMON404_1", + "요청한 리소스를 찾을 수 없습니다."), + VALID_FAIL(HttpStatus.BAD_REQUEST, + "COMMON400_2", + "요청값이 올바르지 않습니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, + "COMMON500_1", + "예기치 않은 서버 에러가 발생했습니다."), + ; + + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java" new file mode 100644 index 0000000..ef060c8 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/code/GeneralSuccessCode.java" @@ -0,0 +1,24 @@ +package com.example.umc9th.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode { + + OK(HttpStatus.OK, + "COMMON200", + "요청이 성공적으로 처리되었습니다."), + CREATED(HttpStatus.CREATED, + "COMMON201", + "리소스가 성공적으로 생성되었습니다."), + NO_CONTENT(HttpStatus.NO_CONTENT, + "COMMON204", + "요청이 성공적으로 처리되었으며, 반환할 내용이 없습니다."), + ; + private final HttpStatus status; + private final String code; + private final String message; +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java" new file mode 100644 index 0000000..902d4b5 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/exception/GeneralException.java" @@ -0,0 +1,14 @@ +package com.example.umc9th.global.apiPayload.exception; + +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; + +@Getter +public class GeneralException extends RuntimeException { + private final BaseErrorCode code; + + public GeneralException(BaseErrorCode code) { + super(code.getMessage()); + this.code = code; + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java" new file mode 100644 index 0000000..ee21fd2 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/apiPayload/handler/GeneralExceptionAdvice.java" @@ -0,0 +1,50 @@ +package com.example.umc9th.global.apiPayload.handler; + +import com.example.umc9th.global.apiPayload.ApiResponse; +import com.example.umc9th.global.apiPayload.code.BaseErrorCode; +import com.example.umc9th.global.apiPayload.code.GeneralErrorCode; +import com.example.umc9th.global.apiPayload.exception.GeneralException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GeneralExceptionAdvice { + + // 1. 커스텀 예외 처리 + @ExceptionHandler(GeneralException.class) + public ResponseEntity> handleException(GeneralException ex) { + return ResponseEntity.status(ex.getCode().getStatus()) + .body(ApiResponse.onFailure(ex.getCode(), null)); + } + + // 2. @Valid 검증 실패 처리 + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity>> handleMethodArgumentNotValidException( + MethodArgumentNotValidException ex + ) { + Map errors = new HashMap<>(); + + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage()) + ); + + GeneralErrorCode code = GeneralErrorCode.VALID_FAIL; + ApiResponse> errorResponse = ApiResponse.onFailure(code, errors); + + return ResponseEntity.status(code.getStatus()).body(errorResponse); + } + + // 3. 그 외 모든 예외 + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception ex) { + + BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR; + return ResponseEntity.status(code.getStatus()) + .body(ApiResponse.onFailure(code, ex.getMessage())); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" new file mode 100644 index 0000000..0ba8875 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/QuerydslConfig.java" @@ -0,0 +1,15 @@ +package com.example.umc9th.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 em) { + return new JPAQueryFactory(em); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/SwaggerConfig.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/SwaggerConfig.java" new file mode 100644 index 0000000..70757f8 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/config/SwaggerConfig.java" @@ -0,0 +1,47 @@ +package com.example.umc9th.global.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + + // 기본 정보 + Info info = new Info() + .title("Project API") + .description("Project Swagger Documentation") + .version("0.0.1"); + + // JWT 인증 스키마 이름 + String schemeName = "JWT"; + + // JWT 인증 요구 + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList(schemeName); + + // JWT 스키마 설정 + Components components = new Components() + .addSecuritySchemes(schemeName, + new SecurityScheme() + .name(schemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ); + + return new OpenAPI() + .info(info) + .addServersItem(new Server().url("/")) + .addSecurityItem(securityRequirement) + .components(components); + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" new file mode 100644 index 0000000..70f7459 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/entity/BaseEntity.java" @@ -0,0 +1,26 @@ +package com.example.umc9th.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.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; +} + diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/validator/FoodExistValidator.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/validator/FoodExistValidator.java" new file mode 100644 index 0000000..32ea16d --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/java/com/example/umc9th/global/validator/FoodExistValidator.java" @@ -0,0 +1,35 @@ +package com.example.umc9th.global.validator; + +import com.example.umc9th.domain.user.repository.FoodRepository; +import com.example.umc9th.domain.user.exception.code.FoodErrorCode; +import com.example.umc9th.global.annotation.ExistFoods; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class FoodExistValidator implements ConstraintValidator> { + + private final FoodRepository foodRepository; + + @Override + public boolean isValid(List values, ConstraintValidatorContext context) { + + if (values == null || values.isEmpty()) return true; // 값 없으면 통과 + + boolean isValid = values.stream() + .allMatch(foodRepository::existsById); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(FoodErrorCode.FOOD_NOT_FOUND.getMessage()) + .addConstraintViolation(); + } + + return isValid; + } +} diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" new file mode 100644 index 0000000..ec8fa85 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/main/resources/application.yml" @@ -0,0 +1,18 @@ +spring: + application: + name: umc9th + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PW} + + jpa: + database: mysql + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true diff --git "a/8\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" new file mode 100644 index 0000000..a96d2a7 --- /dev/null +++ "b/8\354\243\274\354\260\250\353\257\270\354\205\230/src/test/java/com/example/umc9th/Umc9thApplicationTests.java" @@ -0,0 +1,13 @@ +package com.example.umc9th; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Umc9thApplicationTests { + + @Test + void contextLoads() { + } + +}