diff --git "a/keyword/chapter05/images/\354\213\244\354\212\265-1-\354\244\221\353\263\265\353\220\234_\355\232\214\354\233\220.png" "b/keyword/chapter05/images/\354\213\244\354\212\265-1-\354\244\221\353\263\265\353\220\234_\355\232\214\354\233\220.png" new file mode 100644 index 0000000..501cb97 Binary files /dev/null and "b/keyword/chapter05/images/\354\213\244\354\212\265-1-\354\244\221\353\263\265\353\220\234_\355\232\214\354\233\220.png" differ diff --git "a/keyword/chapter05/images/\354\213\244\354\212\265-1-\355\232\214\354\233\220_\353\223\261\353\241\235.png" "b/keyword/chapter05/images/\354\213\244\354\212\265-1-\355\232\214\354\233\220_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..98573b5 Binary files /dev/null and "b/keyword/chapter05/images/\354\213\244\354\212\265-1-\355\232\214\354\233\220_\353\223\261\353\241\235.png" differ diff --git a/keyword/chapter05/keyword.md b/keyword/chapter05/keyword.md new file mode 100644 index 0000000..0ed6af2 --- /dev/null +++ b/keyword/chapter05/keyword.md @@ -0,0 +1,383 @@ +### **๐Ÿ“ฆ ์‹ค์Šต** +--- +1. ํšŒ์›๊ฐ€์ž… API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํšŒ์› ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (ํšŒ์› ๋“ฑ๋ก) & ํšŒ์› ID ๋ฐ˜ํ™˜ + export const addMember = async(data) => { + const conn = await pool.getConnection(); // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ + try{ + // ํ•ด๋‹น ์ด๋ฉ”์ผ(์ค‘๋ณต๋œ ์ด๋ฉ”์ผ)์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + const [confirm] = await pool.query( // ์ฟผ๋ฆฌ ์‹คํ–‰ + `SELECT EXISTS(SELECT 1 FROM member WHERE email = ?) as isExistEmail;`, + data.email + ); + if (confirm[0].isExistEmail) { // ์ด๋ฏธ ๋“ฑ๋ก๋œ ์ด๋ฉ”์ผ์ผ ๊ฒฝ์šฐ + return null; + } + // ์ค‘๋ณต๋œ ์ด๋ฉ”์ผ์ด ์•„๋‹ ๊ฒฝ์šฐ, ํšŒ์› ์ •๋ณด member ํ…Œ์ด๋ธ”์— ์‚ฝ์ž… + const [result] = await pool.query( + `INSERT INTO member (member_name, nickname, gender, birth, location_address, email, phone_number) VALUES (?, ?, ?, ?, ?, ?, ?);`, + [ + data.name, + data.nickname, + data.gender, + data.birth, + data.location, + data.email, + data.phoneNumber, + ] // ์ •๋ณด ์‚ฝ์ž…, ๋‹ค๋ฅธ ์†์„ฑ๋“ค์€ default ๊ฐ’์„ ์ง€์ •ํ•ด์ฃผ์—ˆ๋‹ค. + ); + return result.insertId; // ์ƒ์„ฑ๋œ ํšŒ์›์˜ ID ๋ฐ˜ํ™˜ + // insertId - DB์— ์ƒˆ๋กœ์šด ๋ ˆ์ฝ”๋“œ ์‚ฝ์ž… + // ์ƒˆ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์‚ฝ์ž…๋  ๋•Œ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ์˜ ์ž๋™ ์ฆ๊ฐ€ ID ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. + } catch(err) { // ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + } finally { + conn.release(); // DB ์—ฐ๊ฒฐ ํ•ด์ง€ + } + }; + + // ํšŒ์› ์ •๋ณด ์กฐํšŒ + export const getMember = async (memberId) => { + const conn = await pool.getConnection(); + try{ + // ํšŒ์› ID๋กœ ์กฐํšŒํ•ด์„œ ํ•ด๋‹น ํšŒ์› ์ •๋ณด๋ฅผ ๋ชจ๋‘ ์กฐํšŒํ•œ๋‹ค. + const [member] = await pool.query( + `SELECT * FROM member WHERE id = ?;`, + memberId + ) + console.log(member); + if (member.length == 0){ // ์กฐํšŒ๋œ ํšŒ์›์ด ์—†์„ ๊ฒฝ์šฐ + return null; + } + return member; + }catch (err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + }; + + // ์Œ์‹ - ์„ ํ˜ธ_์Œ์‹_์ข…๋ฅ˜ ๋งคํ•‘ + export const setFavoriteFoodKind = async(memberId, favoriteFoodKindId) => { + const conn = await pool.getConnection(); + try{ + await pool.query( // ํšŒ์›-์Œ์‹_์ข…๋ฅ˜ ๋งคํ•‘ ํ…Œ์ด๋ธ”์— ํšŒ์› ID, ์Œ์‹_์ข…๋ฅ˜ ID๋ฅผ ์‚ฝ์ž…ํ•จ์œผ๋กœ์จ ๋‘ ๊ด€๊ณ„๋ฅผ ์ด์–ด์ค€๋‹ค. + `INSERT INTO member_food_kind (food_kind_id, member_id) VALUES (?, ?);`, + [favoriteFoodKindId, memberId] + ); + return; + } catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + } finally{ + conn.release(); + } + }; + + // ํšŒ์› - ์„ ํ˜ธ_์Œ์‹_์ข…๋ฅ˜ ๋ฐ˜ํ™˜ + export const getMemberFavoriteFoodKindByMemberId = async (memberId) => { + const conn = await pool.getConnection(); + try{ + const [favoriteFoodKinds] = await pool.query(` + SELECT mfk.id, mfk.food_kind_id, mfk.member_id, fk.kind + FROM member_food_kind mfk JOIN food_kind fk ON mfk.food_kind_id = fk.id + WHERE mfk.member_id = ? + ORDER BY mfk.food_kind_id ASC`, + memberId + ); // member ํ…Œ์ด๋ธ”๊ณผ food_kind ํ…Œ์ด๋ธ”์„ joinํ•ด ํ•ด๋‹น ํšŒ์›์˜ ์„ ํ˜ธ ์Œ์‹ ์ข…๋ฅ˜์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. + return favoriteFoodKinds; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![์‹ค์Šต-1-ํšŒ์›๋“ฑ๋ก](images/์‹ค์Šต-1-ํšŒ์›_๋“ฑ๋ก.png) + - ๋™์ผํ•œ ์ด๋ฉ”์ผ๋กœ ํšŒ์›๊ฐ€์ž…ํ•˜๋Š” ๊ฒฝ์šฐ
+ ![images/์‹ค์Šต-1-์ค‘๋ณต๋œ_ํšŒ์›](images/์‹ค์Šต-1-์ค‘๋ณต๋œ_ํšŒ์›.png) + - ํšŒ์› ์ด๋ฉ”์ผ๋กœ ์กฐํšŒํ–ˆ์„ ๋•Œ ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. +### ๐ŸŽฏ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ +--- +- ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (Environment Variables) + - ์šด์˜์ฒด์ œ๋‚˜ ์‹คํ–‰ ํ™˜๊ฒฝ์—์„œ ์„ค์ •๋œ ๋ณ€์ˆ˜ + - ์†Œํ”„ํŠธํ‰ค์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋™์ž‘์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ์ฆ‰, ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์†Œ์Šค์ฝ”๋“œ์— ์ง์ ‘ ํฌํ•จํ•˜์ง€ ์•Š์•„ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•œ๋‹ค. + - ๋ฏผ๊ฐํ•˜๊ฑฐ๋‚˜ ์ž์ฃผ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋Š” ์„ค์ • ๊ฐ’์„ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ  ์™ธ๋ถ€์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. + - ex. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด, API ํ‚ค, ์„œ๋ฒ„ ํฌํŠธ ๋ฒˆํ˜ธ ๋“ฑ + - ex. Node.js ํ”„๋กœ์ ํŠธ์—์„œ์˜ .env ํŒŒ์ผ + - dotenv ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด .env ํŒŒ์ผ์— ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ณ  ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค. + ```javascript + DB_HOST=localhost + DB_POST=3306 + DB_USER=root + DB_PASSWORD=mypassword + PORT=3000 + ``` + - Node.js์—์„œ process.env๋ฅผ ํ†ตํ•ด ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. + ```javascript + const dbHost = process.env.DB_HOST + const dbPort = process.env.DB_PORT + ``` +- CORS (Cross-Origin Resource Sharing) + - ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹ค๋ฅธ ๋„๋ฉ”์ธ, ํ”„๋กœํ† ์ฝœ, ํฌํŠธ์—์„œ ์‹คํ–‰๋˜๋Š” ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๋Š” ๋ณด์•ˆ ๊ธฐ๋Šฅ + - ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ๋™์ผํ•œ ์ถœ์ฒ˜ ์ •์ฑ…(Same-Origin Policy)์„ ์ ์šฉํ•ด ํ•œ ์ถœ์ฒ˜์—์„œ ๋กœ๋“œ๋œ ์›น ํŽ˜์ด์ง€๊ฐ€ ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์„ ์ œํ•œํ•œ๋‹ค. + - ex. http://umc.com์—์„œ ๋กœ๋“œ๋œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๊ธฐ๋ณธ์ ์œผ๋กœ http://api.umc.com๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์— ์žˆ๋Š” API์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค. โ†’ Cors๋Š” ์ด๋Ÿฌํ•œ ์ œํ•œ์„ ์™„ํ™”ํ•œ๋‹ค. + - ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์™ธ๋ถ€ API๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์— ์žˆ๋Š” ์„œ๋ฒ„์™€ ์ƒํ˜ธ์ž‘์šฉํ•ด์•ผ ํ•  ๋•Œ CORS ์„ค์ •์œผ๋กœ ์ด๋Ÿฌํ•œ ์š”์ฒญ์„ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. + 1. ๊ฐ„๋‹จํ•œ ์š”์ฒญ (Simple Request) + - ๋ธŒ๋ผ์šฐ์ €๋Š” GET, POST, HEAD ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹จ์ˆœํ•œ HTTP ์š”์ฒญ์— ๋Œ€ํ•ด origin ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ์„œ๋ฒ„๋Š” ์ด ์š”์ฒญ์„ ๋ฐ›๊ณ  ์‘๋‹ต ํ—ค๋”์— Access-Control-Allow-Origin์„ ํฌํ•จ์‹œ์ผœ ์š”์ฒญ์„ ํ—ˆ์šฉํ•  ์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. + - ex.
+ ์š”์ฒญ ํ—ค๋”: Origin: http://umc.com
+ ์‘๋‹ต ํ—ค๋”: Access-Control-Allow-Origin: http://umc.com + 2. ์˜ˆ๋น„ ์š”์ฒญ (Preflight Request) + - ๋ณด์•ˆ์ƒ์˜ ์ด์œ ๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‹จ์ˆœํ•˜์ง€ ์•Š์€ ์š”์ฒญ(ex. PUT, DELETE ๋ฉ”์„œ๋“œ, ์‚ฌ์šฉ์ž ์ง€์ • ํ—ค๋”๊ฐ€ ํฌํ•จ๋œ ์š”์ฒญ)์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์„œ๋ฒ„์— ์˜ˆ๋น„ ์š”์ฒญ(OPTIONS ๋ฉ”์„œ๋“œ)์„ ๋ณด๋‚ธ๋‹ค. ์ด๋Š” ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์— ์„œ๋ฒ„๊ฐ€ ํ•ด๋‹น ์š”์ฒญ์„ ํ—ˆ์šฉํ• ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด๋‹ค. + - CORS ๊ด€๋ จ ํ—ค๋” + 1. Access-Control-Allow-Origin + - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜(Origin)๋ฅผ ์ •์˜ํ•œ๋‹ค. + - ex. Access-Control-Allow-Origin: * โ†’ ๋ชจ๋“  ์ถœ์ฒ˜๋ฅผ ํ—ˆ์šฉํ•œ๋‹ค. + 2. Access-Control-Allow-Methods + - ํ—ˆ์šฉ๋œ HTTP ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์ •ํ•œ๋‹ค. + - ex. Access-Control-Allow-Methods: GET, POST, PUT, DELETE + 3. Access-Control-Allow-Headers + - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ—ค๋”๋ฅผ ๋ช…์‹œํ•œ๋‹ค. + - ex. Access-Control-Allow-Headers: Content-Type, Authorization + 4. Access-Control-Allow-Credentials + - ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฟ ํ‚ค์™€ ๊ฐ™์€ ์ž๊ฒฉ ์ฆ๋ช…์„ ํฌํ•จํ•œ ์š”์ฒญ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•  ์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•œ๋‹ค. + - ex. Access-Control-Allow-Credentials: true + - ex. CORS ์„ค์ • (Node.js/Express) + ```javascript + import express from 'express'; + import cors from 'cors';; + const app = express(); + + app.use(cors()); // ๋ชจ๋“  ์ถœ์ฒ˜์— ๋Œ€ํ•ด cors ํ—ˆ์šฉ + + // ํŠน์ • ์ถœ์ฒ˜๋งŒ ํ—ˆ์šฉํ•˜๋Š” ๊ฒฝ์šฐ + app.use(cors({ // cors ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€ + origin: 'http://umc.com', // ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜(Cross-Origin) ์ง€์ • + methods: 'GET, POST', // ํ—ˆ์šฉ๋œ HTTP ์ง€์ • + credentials: true // ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์ฟ ํ‚ค, ์ธ์ฆ ํ—ค๋” ๋“ฑ ์ž๊ฒฉ ์ฆ๋ช…(credentials)์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค. + })); + + app.listen(3000, () => { + console.log('Server is running on port 3000'); + }); + ``` + - http://umc.com์—์„œ ์˜ค๋Š” ์š”์ฒญ๋งŒ CORS ๊ทœ์น™์— ์˜ํ•ด ํ—ˆ์šฉ๋œ๋‹ค. + - Cross-Origin: ๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…์„ ์šฐํšŒํ•  ์ถœ์ฒ˜๋ฅผ ์ง€์ •ํ•œ๋‹ค. + - ์œ„์˜ ์˜ˆ์‹œ์—์„  ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ http://umc.com์—์„œ ์‹คํ–‰๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•œ๋‹ค. + - ์„œ๋ฒ„๋Š” GET๊ณผ POST ๋ฉ”์„œ๋“œ๋กœ๋งŒ ์š”์ฒญ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. + - Credential ์˜ต์…˜์ด true๋กœ ์„ค์ •๋  ๊ฒฝ์šฐ Access-Control-Allow-Origin์„ *๋กœ ์„ค์ •ํ•  ์ˆ˜ ์—†๊ณ  ํŠน์ • ์ถœ์ฒ˜๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค. +- DB Connection, DB Connection Pool + 1. DB Connection (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ) + - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์“ฐ๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ๋•Œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ํ•ด์ค€๋‹ค. + - ๊ฐ ์—ฐ๊ฒฐ์€ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฉฐ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. + - ๊ธฐ๋ณธ ํ๋ฆ„ + 1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•œ๋‹ค. + 2. ์ฟผ๋ฆฌ๋ฅผ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ›๋Š”๋‹ค. + 3. ์ž‘์—…์ด ๋๋‚˜๋ฉด ์—ฐ๊ฒฐ์„ ์ข…๋ฃŒํ•œ๋‹ค. + 2. DB Connection Pool (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€) + - ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด๋‘๊ณ  ์ด๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์žฌ์‚ฌ์šฉ(์„ฑ๋Šฅ, ํšจ์œจ โ†‘)ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ด€๋ฆฌํ•˜๋Š” ๋ฉ”ํ‚ค๋‹ˆ์ฆ˜ + - ์ž‘๋™ ์›๋ฆฌ + 1. ์ดˆ๊ธฐํ™”: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ๋•Œ ์ผ์ •ํ•œ ์ˆ˜(๊ณผ๋ถ€ํ•˜ ๋ฐฉ์ง€)์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด๋†“๋Š”๋‹ค. + 2. ์žฌ์‚ฌ์šฉ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์š”์ฒญํ•  ๋•Œ ์—ฐ๊ฒฐ ํ’€์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์—ฐ๊ฒฐ์„ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•œ๋‹ค. (์‹œ๊ฐ„ ๋ฐ ๋ฆฌ์†Œ์Šค ์ ˆ์•ฝ) + 3. ๋ฐ˜ํ™˜: ์ž‘์—…์ด ๋๋‚˜๋ฉด ์—ฐ๊ฒฐ์ด ๋‹ซํžˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ’€๋กœ ๋ฐ˜ํ™˜๋˜์–ด ๋‹ค๋ฅธ ์š”์ฒญ์ด ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. + 4. ํ™•์žฅ/์ถ•์†Œ: ์—ฐ๊ฒฐ ํ’€์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋” ๋งŽ์€ ์—ฐ๊ฒฐ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์—ฐ๊ฒฐ์„ ์ข…๋ฃŒํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. + - Node.js์—์„  mysql2 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. + ```javascript + import mysql from 'mysql2/promise'; // MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐ + + export const pool = mysql.createPool({ // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€(connection pool) ์ƒ์„ฑ + host: process.env.DB_HOST || 'localhost', // mysql์˜ hostname + user: process.env.DB_USER || 'root', // ์‚ฌ์šฉ์ž ์ด๋ฆ„ + port: process.env.DB_PORT || 3306, // ํฌํŠธ ๋ฒˆํ˜ธ + database: process.env.DB_TABLE || 'restaurant_service', // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„ + password: process.env.DB_PASSWORD || 'mypassword', // ๋น„๋ฐ€๋ฒˆํ˜ธ + waitForConnections: true, + // Pool์— ํš๋“ํ•  ์ˆ˜ ์žˆ๋Š” connection์ด ์—†์„ ๋•Œ + // true๋ฉด ์š”์ฒญ์„ queue์— ๋„ฃ๊ณ  connection์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋ฉด ์š”์ฒญ์„ ์‹คํ–‰ํ•œ๋‹ค. + // false๋ฉด ์ฆ‰์‹œ ์˜ค๋ฅ˜๋ฅผ ๋‚ด๋ณด๋‚ด๊ณ  ๋‹ค์‹œ ์š”์ฒญํ•œ๋‹ค. + connectionLimit: 10, // ๋ช‡ ๊ฐœ์˜ ์ปค๋„ฅ์…˜์„ ๊ฐ€์ง€๊ฒŒ๋” ํ•  ๊ฒƒ์ธ์ง€ + queueLimit: 0, // getConnection์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์ „์— Pool์— ๋Œ€๊ธฐํ•  ์š”์ฒญ์˜ ๊ฐœ์ˆ˜ ํ•œ๋„ + }); + + // ์—ฐ๊ฒฐ ์‚ฌ์šฉ + pool.query('SELECT * FROM users', (error, results) => { // sql ์ฟผ๋ฆฌ ์‹คํ–‰ + if (error) throw error; + console.log(results); + }); + ``` + +- ๋น„๋™๊ธฐ (async, await) + - **๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ (Synchronous Programming)** + - ์ž‘์—…๋“ค์ด ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜์–ด ์ด์ „ ์ž‘์—…์ด ๋๋‚˜์•ผ๋งŒ ๋‹ค์Œ ์ž‘์—…์„ ์‹œ์ž‘ํ•œ๋‹ค. + - ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์ด ์žˆ์œผ๋ฉด ๊ทธ๋™์•ˆ ํ”„๋กœ๊ทธ๋žจ์ด ๋ฉˆ์ถ˜ ์ƒํƒœ๋กœ ๋Œ€๊ธฐํ•œ๋‹ค. + ```javascript + console.log("Start"); + const result = fetchData(); // ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—… + console.log(result); + console.log("End"); + ``` + 1. fetchData()๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ ํ›„ + 2. โ€Endโ€๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. + - โ€œStartโ€ โ†’ fetchData() โ†’ โ€œEndโ€ + - **๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ (Asynchronous Programming)** + - ์ž‘์—…์„ ๋™์‹œ์— ์‹คํ–‰๋˜๋„๋ก ํ•˜์—ฌ ํ•˜๋‚˜์˜ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹ + - ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹ค๋ฅธ ์ž‘์—…์„ ๊ณ„์† ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค. (๊ฐ ์ž‘์—…์ด ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ง„ํ–‰๋œ๋‹ค) + - ์™„๋ฃŒ๋œ ์ž‘์—…์€ ๋‚˜์ค‘์— ์ฝœ๋ฐฑ, Promise ๋˜๋Š” async/await์„ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค. + ```javascript + console.log("Start"); + setTimeout(() => { + console.log("Fetching data... (completed)"); + }, 2000); // 2์ดˆ ํ›„ ์‹คํ–‰ + console.log("End"); + ``` + - setTimeout ํ•จ์ˆ˜์—์„œ 2์ดˆ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ ๋‹ค์Œ ์ฝ”๋“œ(โ€Endโ€ ์ถœ๋ ฅ)๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•œ๋‹ค. + - โ€œStartโ€ โ†’ setTimeout() ์‹คํ–‰ โ†’ โ€œEndโ€ โ†’ (2์ดˆ ํ›„) โ€Fetching data... (completed)" + - **๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์ฃผ์š” ํŒจํ„ด** + 1. **์ฝœ๋ฐฑ ํ•จ์ˆ˜ (Callback Function)** + - ์ž‘์—…์ด ์™„๋ฃŒ๋œ ํ›„ ํŠน์ • ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. + ```javascript + function fetchData(callback){ + setTimeout(() => { + **callback("Data loaded")**; + }, 1000); + } + console.log("Start"); + fetchData(**(data) => console.log(data)**); + console.log("End"); + ``` + - โ€œStartโ€ โ†’ setTimeout() ์‹คํ–‰ โ†’ โ€œEndโ€ โ†’ (1์ดˆ ํ›„) โ€œData loadedโ€ + - ๋ฌธ์ œ์ : ์ฝœ๋ฐฑ ํ•จ์ˆ˜(Callback Hell)๊ฐ€ ์ค‘์ฒฉ๋˜๋ฉด ์ฝœ๋ฐฑ ์ง€์˜ฅ์ด๋ผ๋Š” ๋ณต์žกํ•œ ์ฝ”๋“œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. + 2. **Promise** + - ์ž‘์—…์˜ ์„ฑ๊ณต/์‹คํŒจ๋ฅผ ํ‘œํ˜„ํ•˜๋ฉฐ then๊ณผ catch๋กœ ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. + ```javascript + const fetchData = new **Promise**((resolve, reject) => { + setTimeout(() => **resolve**("Data loaded"), 1000); + }); + console.log("Start"); + fetchData.**then**((data) => console.log(data)); + console.log("End"); + ``` + > resolve: ์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ๋œ๋‹ค. + > reject: ์ž‘์—…์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋œ๋‹ค. + - setTimeout์œผ๋กœ 1์ดˆ ํ›„์— resolve ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ์ด ์ž‘์—… ์„ฑ๊ณต ์‹œ Promise๊ฐ€ ํ•ด๊ฒฐ(resolved)๋œ๋‹ค. + - fetchData๋Š” Promise์ด๋ฏ€๋กœ then ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ๊ฒฐ๊ณผ(data)๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค. + - 1์ดˆ ํ›„ resolve ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ๊ฐ’("Data loaded")์ด then์— ์ „๋‹ฌ๋˜์–ด data๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. + - โ€œStartโ€ โ†’ setTimeout() ์‹คํ–‰ โ†’ โ€œEndโ€ โ†’ (1์ดˆ ํ›„) โ€œData loadedโ€ + 3. **async/await** + - ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ฌธ๋ฒ• + - ๋น„๋™๊ธฐ ์ž‘์—… ์ˆœ์„œ์™€ ํ๋ฆ„์„ ๋™๊ธฐ์  ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. + - async ํ‚ค์›Œ๋“œ๋ฅผ ํ•จ์ˆ˜ ์•ž์— ๋ถ™์—ฌ ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ ๋งŒ๋“ ๋‹ค. + ```javascript + async function example() { + return "Hello, Wenty!"; + } + example().then((result) => console.log(result)); // ํ˜ธ์ถœ ์‹œ Promise๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค. + ``` + - ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋Š” ํ•ญ์ƒ Promise ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. + - ๋ฐ˜ํ™˜ ๊ฐ’์ด ์ผ๋ฐ˜ ๊ฐ’์ด๋ฉด ์ž๋™์œผ๋กœ Promise.resolve(โ€ฆ)์œผ๋กœ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜๋œ๋‹ค. + - await๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. + ```javascript + async function fetchData() { + let data = await **new Promise**((resolve) => + setTimeout(() => resolve("Data loaded"), 1000) + ); + console.log(data); // 1์ดˆ ํ›„ ์ถœ๋ ฅ + } + fetchData(); + ``` + - Promise๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ํ•ด๋‹น ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ ๋ฐ›์•„ ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. + - await ๋’ค์—๋Š” Promise๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™€์•ผ ํ•œ๋‹ค. + - ex. + ```javascript + **async** function loadData(){ + console.log("Start"); + let data = **await** new **Promise**( + (resolve) => setTimeout(() => resolve("Data loaded"), 1000)); + ); + console.log(data); + condole.log("End"); + } + loadData(); + ``` + - await๋Š” Promise๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋ฏ€๋กœ ์ดํ›„์˜ ์ฝ”๋“œ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. + - 1์ดˆ ํ›„ Promise๊ฐ€ ํ•ด๊ฒฐ๋œ ํ›„์— resolve()๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด์„œ await๊ฐ€ ํ•ด์ œ๋˜๊ณ  โ€œData loadedโ€๊ฐ€ data ๋ณ€์ˆ˜์— ์ €์žฅ๋œ๋‹ค. + - โ€œStartโ€ โ†’ (1์ดˆ ํ›„) โ€œData loadedโ€ โ†’ โ€œEndโ€ + 4. async/await + Promise.all + - ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ž‘์—…์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๋ ค๋ฉด Promise.all๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค. + - async/await๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋น„๋™๊ธฐ ์ž‘์—…๋“ค์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜์–ด ์‹œ๊ฐ„์ด ๋” ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Promise.all๊ณผ async/await์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ž‘์—…๋“ค์„ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด ๋” ํšจ์œจ์ ์ด๋‹ค. + - ์ด ๊ฒฝ์šฐ ์ „์ฒด ์‹คํ–‰ ์‹œ๊ฐ„์€ ๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์˜ ์‹œ๊ฐ„๋งŒํผ๋งŒ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + - async/await๋งŒ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ + ```javascript + async function getStudent1() { + return new Promise((resolve) => setTimeout(() => resolve("Wenty", 1000)); + } + async function getStudent2() { + return new Promise((resolve) => setTimeout(() => resolve("Bongoo"), 2000)); + } + async function loadData() { + console.log("Start"); // 1 + let student1 = await getStudent1(); + console.log(`Student 1: ${student1}`); // 2 + let student2 = await getStudent2(); + console.log(`Student 2: ${student2}`); // 3 + console.log("End"); // 4 + } + loadData(); + ``` + - getStudent1()๋ฅผ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ getStudent2()๋ฅผ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. + - ์ด 1 + 2 = 3์ดˆ ์†Œ์š”๋œ๋‹ค. + - async/await + Promise.all์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ + ```javascript + async function getStudent1() { + return new Promise((resolve) => setTimeout(() => resolve("Wenty", 1000)); + } + async function getStudent2() { + return new Promise((resolve) => setTimeout(() => resolve("Bongoo"), 2000)); + } + async function loadData() { + console.log("Start"); // 1 + let [student1, student2] = await Promise.all([getStudent1(), getStuent2()]); // ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋˜๋ฉฐ ๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—… ์‹œ๊ฐ„(2์ดˆ)๋งŒ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. + console.log(`Student 1: ${student1}`); // 2 + console.log(`Student 2: ${student2}`); // 3 + console.log("End"); // 4 + } + loadData(); + ``` + - Promise.all์„ ์‚ฌ์šฉํ•ด student1๊ณผ student2 ์ •๋ณด๋ฅผ ๋™์‹œ์— ๊ฐ€์ ธ์˜จ๋‹ค. + - ๋‘ ์ž‘์—…์ด ๋ณ‘๋ ฌ๋กœ ์ง„ํ–‰๋˜์–ด ๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—… ์‹œ๊ฐ„๋งŒ ์†Œ์š”๋œ๋‹ค. + - getStudent1()๊ณผ getStudent2() ์ค‘ ์ž‘์—… ์‹œ๊ฐ„์ด ๋” ๊ธด ์‹œ๊ฐ„ ์ฆ‰, 2์ดˆ ์†Œ์š”๋œ๋‹ค. +- try/catch/finally + - ์ฝ”๋“œ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•  ์ง€๋ฅผ ์ œ์–ดํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. + - ์˜ˆ์™ธ ์ฒ˜๋ฆฌ(Exception Handling)์„ ํ†ตํ•ด ์ฝ”๋“œ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๋ฅผ ํƒ์ง€ํ•˜๊ณ  ์ด๋ฅผ ์ ์ ˆํžˆ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. + ```javascript + function riskyFunction() { // ์˜ค๋ฅ˜๊ฐ€ ๋‚  ์ˆ˜๋„ ์žˆ๋Š” ์ฝ”๋“œ + throw new Error("Error"); + } + + try { // try ๋ธ”๋ก - ์‹คํ–‰ํ•  ์ฝ”๋“œ + let result = riskyFunction(); // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ + console.log("Result:", result); // ์ด ์ฝ”๋“œ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. + } catch (error) { // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์ด ์ฝ”๋“œ๋กœ ์ด๋™ + console.error("Error:", error.message); // ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ + } finally { + console.log("End"); // ํ•ญ์ƒ ์‹คํ–‰ + } + ``` + - ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ try์— ๋„ฃ์–ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— ๋Œ€๋น„ํ•œ๋‹ค. + - catch์˜ ๋งค๊ฐœ๋ณ€์ˆ˜: ์ผ๋ฐ˜์ ์œผ๋กœ error๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ๋‹ค. + - finally: ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด ํ•ญ์ƒ ์‹คํ–‰๋œ๋‹ค. try ๋ธ”๋ก๊ณผ catch ๋ธ”๋ก์ด ๋๋‚œ ํ›„์— ์‹คํ–‰๋œ๋‹ค. \ No newline at end of file diff --git "a/keyword/chapter06/images/\354\213\244\354\212\265-1-\354\244\221\353\263\265\353\220\234_\355\232\214\354\233\220.png" "b/keyword/chapter06/images/\354\213\244\354\212\265-1-\354\244\221\353\263\265\353\220\234_\355\232\214\354\233\220.png" new file mode 100644 index 0000000..94164f6 Binary files /dev/null and "b/keyword/chapter06/images/\354\213\244\354\212\265-1-\354\244\221\353\263\265\353\220\234_\355\232\214\354\233\220.png" differ diff --git "a/keyword/chapter06/images/\354\213\244\354\212\265-1-\355\232\214\354\233\220\353\223\261\353\241\235.png" "b/keyword/chapter06/images/\354\213\244\354\212\265-1-\355\232\214\354\233\220\353\223\261\353\241\235.png" new file mode 100644 index 0000000..13aedcb Binary files /dev/null and "b/keyword/chapter06/images/\354\213\244\354\212\265-1-\355\232\214\354\233\220\353\223\261\353\241\235.png" differ diff --git "a/keyword/chapter06/images/\354\213\244\354\212\265-2-\355\212\271\354\240\225\352\260\200\352\262\214_\353\246\254\353\267\260\353\252\251\353\241\235.png" "b/keyword/chapter06/images/\354\213\244\354\212\265-2-\355\212\271\354\240\225\352\260\200\352\262\214_\353\246\254\353\267\260\353\252\251\353\241\235.png" new file mode 100644 index 0000000..7d70930 Binary files /dev/null and "b/keyword/chapter06/images/\354\213\244\354\212\265-2-\355\212\271\354\240\225\352\260\200\352\262\214_\353\246\254\353\267\260\353\252\251\353\241\235.png" differ diff --git a/keyword/chapter06/keyword.md b/keyword/chapter06/keyword.md new file mode 100644 index 0000000..a37c2ae --- /dev/null +++ b/keyword/chapter06/keyword.md @@ -0,0 +1,433 @@ +### **๐Ÿ“ฆ ์‹ค์Šต** +--- +1. ํšŒ์›๊ฐ€์ž… API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํšŒ์› ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (ํšŒ์› ๋“ฑ๋ก) & ํšŒ์› ID ๋ฐ˜ํ™˜ + export const addMember = async(data) => { + const member = await prisma.member.findFirst({where: {email: data.email}}); // ํ•ด๋‹น ์ด๋ฉ”์ผ๋กœ ๋“ฑ๋ก๋œ ํšŒ์›(์ค‘๋ณต ํšŒ์›)์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + if (member){ // ํ•ด๋‹น ์ด๋ฉ”์ผ๋กœ ๋“ฑ๋ก๋œ ํšŒ์›์ด ์žˆ์„ ๊ฒฝ์šฐ + return null; + } + const created = await prisma.member.create({data: data}); // ํšŒ์› ์ƒ์„ฑ + return created.id; // ์ƒ์„ฑ๋œ ํšŒ์› ID ๋ฐ˜ํ™˜ + }; + + // ํšŒ์› ์ •๋ณด ์กฐํšŒ + export const getMember = async (memberId) => { + const member = await prisma.member.findFirstOrThrow({ where: {id: memberId}}); + // prisma์—์„œ member ํ…Œ์ด๋ธ”์— ์ ‘๊ทผํ•˜์—ฌ ํ•ด๋‹น memberId์™€ ์ผ์น˜ํ•˜๋Š”, ์ฒซ ๋ฒˆ์งธ ๋ ˆ์ฝ”๋“œ๋ฅผ ์กฐํšŒํ•œ๋‹ค. + // ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ๊ฐ€ ์—†์„ ์‹œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค(์—๋Ÿฌ ๋ฐœ์ƒ). + return member; + }; + + // ์Œ์‹ - ์„ ํ˜ธ_์Œ์‹_์ข…๋ฅ˜ ๋งคํ•‘ + export const setFavoriteFoodKind = async(memberId, favoriteFoodKindId) => { + await prisma.memberFavoriteFoodKind.create({ + data:{ // ์ƒˆ ๋ ˆ์ฝ”๋“œ์˜ ํ•„๋“œ์™€ ๊ฐ’์„ ์ง€์ •ํ•œ๋‹ค. + memberId: memberId, + foodKindId: favoriteFoodKindId + }, + }); + }; + + // ํšŒ์› - ์„ ํ˜ธ_์Œ์‹_์ข…๋ฅ˜ ๋ฐ˜ํ™˜ + export const getMemberFavoriteFoodKindByMemberId = async (memberId) => { + const favoriteFoodKinds = await prisma.memberFavoriteFoodKind.findMany({ // ์—ฌ๋Ÿฌ ๋ ˆ์ฝ”๋“œ ์กฐํšŒ, ์กฐ๊ฑด์— ๋งž๋Š” ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ๋ฅผ ๋ฐฐ์—ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ + select: { // ๋ฐ˜ํ™˜ํ•  ํ•„๋“œ ๋ช…์‹œ + id: true, + memberId: true, + foodKindId: true, + foodKind: true, // ์ฐธ์กฐํ•˜๋Š” foodKind ํ…Œ์ด๋ธ” + }, + where: { memberId: memberId }, + orderBy: {foodKindId: "asc"}, // foodKindId ๊ธฐ์ค€ ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ + }); + return favoriteFoodKinds; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![์‹ค์Šต-1-ํšŒ์›๋“ฑ๋ก](images/์‹ค์Šต-1-ํšŒ์›๋“ฑ๋ก.png) + - ๋™์ผํ•œ ์ด๋ฉ”์ผ๋กœ ํšŒ์›๊ฐ€์ž…ํ•˜๋Š” ๊ฒฝ์šฐ
+ ![images/์‹ค์Šต-1-์ค‘๋ณต๋œ_ํšŒ์›](images/์‹ค์Šต-1-์ค‘๋ณต๋œ_ํšŒ์›.png) + - ํšŒ์› ์ด๋ฉ”์ผ๋กœ ์กฐํšŒํ–ˆ์„ ๋•Œ ์ด๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. +2. ํŠน์ • ๊ฐ€๊ฒŒ์˜ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํŠน์ • ์‹๋‹น์˜ ๋ชจ๋“  ๋ฆฌ๋ทฐ ์กฐํšŒ + export const getAllRestaurantReviews = async (restaurantId, cursor) => { + const reviews = await prisma.review.findMany({ // Prisma ORM์„ ์‚ฌ์šฉํ•˜์—ฌ review ํ…Œ์ด๋ธ”์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ์กฐํšŒํ•œ๋‹ค. + select: { + id: true, + member: true, + restaurant: true, + rating: true, + createdAt: true, + content: true, + status: true + }, + where: { restaurantId: restaurantId, id: { gt: cursor }}, + orderBy: { id: "asc"}, + take: 5, + }) + const formattedReviews = reviews.map(review => ({ + ...review, + id: review.id.toString(), + member: { + id: review.member.id.toString(), + name: review.member.name, + nickname: review.member.nickname, + birth: review.member.birth, + gender: review.member.gender, + location: review.member.location, + phoneNumber: review.member.phoneNumber + }, + restaurant: { + id: review.restaurant.id.toString(), + name: review.restaurant.name + }, + })); + + return formattedReviews; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![์‹ค์Šต-2-ํŠน์ •๊ฐ€๊ฒŒ_๋ฆฌ๋ทฐ๋ชฉ๋ก](images/์‹ค์Šต-2-ํŠน์ •๊ฐ€๊ฒŒ_๋ฆฌ๋ทฐ๋ชฉ๋ก.png) +### ๐ŸŽฏ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ +--- +- ORM (Object-Relation Mapping) + - ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์ˆ ๋กœ ๊ฐ์ฒด์™€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„์˜ ๋ณ€ํ™˜์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. + - ๊ฐœ๋ฐœ์ž๋Š” SQL์„ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ (๊ฐœ๋ฐœ ์†๋„ โ†‘) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ๊ฐ์ฒด ์ง€ํ–ฅ ์–ธ์–ด์˜ ์ฝ”๋“œ๋กœ ํ•  ์ˆ˜ ์žˆ๋‹ค. + - ํŠน์ • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ๋‹ค์–‘ํ•œ DBMS๋ฅผ ์‰ฝ๊ฒŒ ๊ต์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค. + - ORM ๋„๊ตฌ: Prisma + - Node.js ๋ฐ TypeScript๋ฅผ ์œ„ํ•œ ์ตœ์‹  ORM ๋„๊ตฌ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์ž‘์—…์„ ๋” ๊ฐ„ํŽธํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š”๋‹ค. + 1. **Prisma Client** + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์ž๋™ ์ƒ์„ฑ๋œ ํƒ€์ž… ์•ˆ์ „ํ•œ ์ฝ”๋“œ + - ์ด๋ฅผ ํ†ตํ•ด TypeScript๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์ฝ”๋“œ ์ž๋™ ์™„์„ฑ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. + 2. **Prisma Migrate** + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ + - Migration ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•ด ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์„ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. + 3. **Prisma Studio** + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํƒ์ƒ‰ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์›น ๊ธฐ๋ฐ˜ UI + - ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒ, ํŽธ์ง‘, ์‚ญ์ œํ•˜๋Š” ์ž‘์—…์„ ์ง๊ด€์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. + - ex. prisma client๋ฅผ ์‚ฌ์šฉํ•ด ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑ(๋ฆฌ๋ทฐ ๋“ฑ๋ก)ํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” ์ฝ”๋“œ + ```javascript + // prisma/schema.prisma + + ... + + // ๋ฆฌ๋ทฐ ๋ชจ๋ธ + model Review{ + id BigInt @id @default(autoincrement()) + member Member @relation(fields: [memberId], references: [id]) + memberId BigInt @map("member_id") + restaurant Restaurant @relation(fields: [restaurantId], references: [id]) + restaurantId BigInt @map("restaurant_id") + rating Decimal @db.Decimal(2, 1) @default(0.0) + content String + createdAt DateTime @map("created_at") @db.Timestamp(6) @default(now()) + updatedAt DateTime @map("updated_at") @db.Timestamp(6) @default(now()) + status Int @default(1) + + replys Reply[] + images Image[] + + @@index([memberId], map: "member_id") + @@index([restaurantId], map: "restaurant_id") + @@map("review") + } + + ... + ``` + ```javascript + // src/db.config.js + + import { PrismaClient } from '@prisma/client'; + // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ ํด๋ผ์ด์–ธํŠธ(PrismaClient)๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. + + export const prisma = new PrismaClient({ log: ["query"] }); + // PrismaClient์˜ ์ƒˆ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ + // ์‹คํ–‰๋˜๋Š” ๋ชจ๋“  ์ฟผ๋ฆฌ๋ฅผ ์ฝ˜์†”์— ๋กœ๊น…ํ•˜๋„๋ก ํ•œ๋‹ค. + ``` + ```javascript + // src/repositories/review.repository.js + + import { prisma, pool } from "../db.config.js"; + + // ๋ฆฌ๋ทฐ ๋“ฑ๋ก ๋ฐ ๋ฆฌ๋ทฐ ID ๋ฐ˜ํ™˜ + export const addReview = async(data) => { + // ๋ฆฌ๋ทฐ๋ฅผ ๋“ฑ๋กํ•˜๊ณ ์ž ํ•˜๋Š” ์‹๋‹น์ด ์กด์žฌํ•˜๋Š”์ง€ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ์ฒซ ๋ฒˆ์žฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜์—ฌ ํ™•์ธ + const restaurant = await prisma.restaurant.findFirst( { + where: { + id: data.restaurant // ๋ฆฌ๋ทฐ๋ฅผ ๋“ฑ๋กํ•˜๊ณ ์ž ํ•˜๋Š” ์‹๋‹น์˜ ID๋กœ ํ™•์ธํ•œ๋‹ค. + } + }) + if (restaurant == null){ // ์‹๋‹น์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ์‹œ + return null; + } + const created = await prisma.review.create({ // ๋ฆฌ๋ทฐ ์ƒ์„ฑ + data: { // ์ƒ์„ฑํ•  ๋ฐ์ดํ„ฐ ๊ฐ์ฒด + ...data, // data ๊ฐ์ฒด์˜ ์†์„ฑ์„ ํŽผ์นœ๋‹ค. + member: { + connect: { // member ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„ ์—ฐ๊ฒฐ + id: data.member + } + }, + restaurant:{ + connect: { // restaurant ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„ ์—ฐ๊ฒฐ + id: data.restaurant + } + } + } + }); + return created.id; // ์ƒ์„ฑ๋œ ๋ฆฌ๋ทฐ์˜ ID ๋ฐ˜ํ™˜ + } + + export const getReview = async(reviewId) => { + // ํ•ด๋‹น ๋ฆฌ๋ทฐ ID์— ์ผ์น˜ํ•˜๋Š” ๋ฆฌ๋ทฐ ์กฐํšŒ (๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ) + const review = await prisma.review.findFirstOrThrow({ + select: { // ๋ฐ˜ํ™˜ํ•  ํ•„๋“œ ๋ช…์‹œ + id: true, + member: true, // ์ฐธ์กฐํ•˜๋Š” memberํ…Œ์ด๋ธ” + restaurant: true, // ์ฐธ์กฐํ•˜๋Š” restaurant ํ…Œ์ด๋ธ” + rating: true, + content: true, + createdAt: true, + status: true + }, + where: { + id: reviewId // ํ•ด๋‹น reviewId์™€ ์ผ์น˜ํ•˜๋Š” ๋ฆฌ๋ทฐ ์กฐํšŒ + } + }); + const formattedReview = { // ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ ํ˜•์‹ํ™” + ...review, // ์œ„์—์„œ selectํ•œ ์†์„ฑ๋“ค์„ ํŽผ์นœ๋‹ค. + id: review.id.toString(), + // DB์˜ id ํ•„๋“œ๊ฐ€ BigInt ํƒ€์ž…์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ๋Š”๋ฐ + // javaScript์—์„  BigInt ํƒ€์ž…์€ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์—†์–ด + // BigInt ํƒ€์ž…์˜ id๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ์—ˆ๋‹ค. + member: { // ์ฐธ์กฐํ•˜๋Š” member ํ…Œ์ด๋ธ”์—์„œ ์ถ”์ถœํ•  ์†์„ฑ + id: review.member.id.toString(), + name: review.member.name, + }, + restaurant: { + id: review.restaurant.id.toString(), + name: review.restaurant.name, + }, + } + return formattedReview; + } + ``` + - Prisma ์‚ฌ์šฉ ํ๋ฆ„ + 1. prisma/schema.prisma ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ชจ๋ธ์„ ์ •์˜ํ•œ๋‹ค. + 2. ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด Prisma Migration์„ ์‹คํ–‰ํ•œ๋‹ค. + 3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด Prisma Client๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. + 4. Prisma Client๋ฅผ ์‚ฌ์šฉํ•ด ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— CRUD ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. + - ๊ธฐํƒ€ ORM ๋„๊ตฌ + 1. Entity Framework: .NET ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ORM + 2. Hibernate: Java ์ƒํƒœ๊ณ„์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ORM ํ”„๋ ˆ์ž„์›Œํฌ +- Prisma ๋ฌธ์„œ ์‚ดํŽด๋ณด๊ธฐ + - ex. Prisma์˜ Connection Pool ๊ด€๋ฆฌ ๋ฐฉ๋ฒ• + - Prisma๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ํšจ์œจ์ ์ธ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ์ž์ฒด์ ์ธ Connection Pool์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ณ  ํŠนํžˆ ์„œ๋ฒ„๋ฆฌ์Šค ํ™˜๊ฒฝ์—์„œ์˜ ์—ฐ๊ฒฐ ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค€๋‹ค. + - ๋™์ž‘ ๋ฐฉ์‹ + - Prisma์˜ ์ฟผ๋ฆฌ ์—”์ง„์€ ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹œ ์ปค๋„ฅ์…˜ ํ’€์„ ์ƒ์„ฑํ•œ๋‹ค. + - ์ดํ›„ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ๊ธฐ์กด ์—ฐ๊ฒฐ์„ ์žฌ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํ•„์š”์— ๋”ฐ๋ผ ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. + - ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์˜ ํšจ์œจ์„ฑ์„ ๋†’์ด๊ณ  ๋ถˆํ•„์š”ํ•œ ์—ฐ๊ฒฐ ์ƒ์„ฑ์„ ์ตœ์†Œํ™”ํ•œ๋‹ค. + - ๊ธฐ๋ณธ์ ์œผ๋กœ Prisma๋Š” ๋จธ์‹ ์˜ ๋ฌผ๋ฆฌ์  CPU ์ˆ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ปค๋„ฅ์…˜ ํ’€์˜ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. + - ์ปค๋„ฅ์…˜ ํ’€ ํฌ๊ธฐ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•˜๋ ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ URL์— connection_limit ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. + ```javascript + datasource db { + provider = "mysql" + url = "mysql://root:mypassword@localhost:3306/restaurant_service?connection_limit=5" + } + ``` + - ์ด ๊ฒฝ์šฐ ์ปค๋„ฅ์…˜ ํ’€์˜ ํฌ๊ธฐ๊ฐ€ 5๋กœ ์ œํ•œ๋œ๋‹ค. + - ex. Prisma์˜ Migration ๊ด€๋ฆฌ ๋ฐฉ๋ฒ• + - Prisma์˜ Migration ๊ด€๋ฆฌ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ + - Prisma ์Šคํ‚ค๋งˆ ํŒŒ์ผ(schema.prisma)์—์„œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์ •์˜ํ•˜๊ณ  ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•œ๋‹ค. + - ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ, ํ•ด๋‹น ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•œ SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. + - ์ƒ์„ฑ๋œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์— ์ ์šฉํ•˜์—ฌ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์„ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•œ๋‹ค. + - ์ฃผ์š” ๋ช…๋ น์–ด + - **prisma migrate dev** + - ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ ์ƒˆ๋กœ์šด Migration ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ์šฉํ•œ๋‹ค. + - Prisma Client๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์—ฌ ์ฝ”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. + - **prisma migrate deploy** + - ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ๋Œ€๊ธฐ ์ค‘์ธ Migration์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ ์šฉํ•œ๋‹ค. + - ์ด ๋ช…๋ น์–ด๋Š” CI/CD ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์‚ฌ์šฉ๋˜์–ด ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์„ ์ž๋™ํ™”ํ•œ๋‹ค. + - **prisma migrate reset** + - ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋ชจ๋“  Migration์„ ๋‹ค์‹œ ์ ์šฉํ•œ๋‹ค. + - ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์žฌ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์Šคํ‚ค๋งˆ๋ฅผ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆด ๋•Œ ์œ ์šฉํ•˜๋‹ค. + - Migration ์›Œํฌํ”Œ๋กœ์šฐ + 1. schema.prisma ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ์ˆ˜์ •ํ•œ๋‹ค. + 2. prisma migrate dev ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ์šฉํ•œ๋‹ค. + 3. ์ƒ์„ฑ๋œ Migration ํŒŒ์ผ๊ณผ schema.prisma ํŒŒ์ผ์„ ๋ฒ„์ „ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์— ์ปค๋ฐ‹ํ•˜์—ฌ ๋ณ€๊ฒฝ ์ด๋ ฅ์„ ์ถ”์ ํ•œ๋‹ค. + 4. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” prisma migrate deploy ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Œ€๊ธฐ ์ค‘์ธ Migration์„ ์ ์šฉํ•œ๋‹ค. +- ORM(Prisma)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ข‹์€ ์ ๊ณผ ๋‚˜์œ ์  + 1. ์ข‹์€ ์  + - TypeScript์™€ ๊ธด๋ฐ€ํ•˜๊ฒŒ ํ†ตํ•ฉ๋˜์–ด ์žˆ์–ด ๋ชจ๋“  ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์ž๋™์œผ๋กœ ํƒ€์ž…์„ ์ƒ์„ฑํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ ์‹ค์ˆ˜๋ฅผ ์ค„์ด๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋‹ค. + - Prisma์˜ ์ง๊ด€์ ์ธ API ๋•๋ถ„์— ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…๋„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ๋นจ๋ผ์ง„๋‹ค. + - Prisma Migrate๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๋ฒ„์ „ ๊ธฐ๋ก์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ๋‹ค. + - MySQL, PostgreSQL, SQLite, SQL Server ๋“ฑ ๋‹ค์–‘ํ•œ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ˜ธํ™˜๋œ๋‹ค. + 2. ๋‚˜์œ ์  + - ORM์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ ์ฟผ๋ฆฌ๋Š” ์ˆ˜์ž‘์—…์œผ๋กœ ์ตœ์ ํ™”๋œ SQL๋ณด๋‹ค ๋Š๋ฆด ์ˆ˜ ์žˆ๋‹ค. ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์ผ์ˆ˜๋ก ๋น„ํšจ์œจ์ ์ด ๋  ๊ฐ€๋Šฅ์„ฑ์ด ํฌ๋‹ค. + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ณ ์œ  ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ ์„ธ๋ถ€์ ์ธ ์ฟผ๋ฆฌ ์ตœ์ ํ™”๊ฐ€ ์–ด๋ ค์›Œ ์„ฑ๋Šฅ์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์กฐ์ •ํ•˜๊ธฐ ํž˜๋“ค ์ˆ˜ ์žˆ๋‹ค. + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•  ๊ฒฝ์šฐ ๋ชจ๋“  ๊ด€๊ณ„๋ฅผ ์ ์ ˆํžˆ ๋งคํ•‘ํ•˜๋Š” ๋ฐ ํ•œ๊ณ„๊ฐ€ ์žˆ๋‹ค. + - ์ž๋™ ์ƒ์„ฑ๋œ ์ฟผ๋ฆฌ์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋‚˜ ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๊ณ  ๋ณต์žกํ•  ์ˆ˜ ์žˆ๋‹ค. + - ํŠน์ • ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์˜์กดํ•˜๊ฒŒ ๋˜๋ฉด,์œ ์ง€๋ณด์ˆ˜๋‚˜ ๋ณ€๊ฒฝ ์‹œ ์–ด๋ ค์›€์„ ๊ฒช์„ ์ˆ˜ ์žˆ๋‹ค. +- ๋‹ค์–‘ํ•œ ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ดํŽด๋ณด๊ธฐ + - ex. Sequelize + - Node.js์—์„œ ์‚ฌ์šฉํ•˜๋Š” ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ + - ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ณต์žกํ•œ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ๋ชจ๋ธ๋กœ ์ •์˜ํ•˜์—ฌ ๋‹ค๋ฃฌ๋‹ค. ๊ฐ ๋ชจ๋ธ์€ ํ…Œ์ด๋ธ”์˜ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ CRUD ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•œ๋‹ค. + - ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ์™€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. findAll, create, update ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด SQL ์ฟผ๋ฆฌ๋ฅผ ๊ฐ„๋‹จํžˆ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. + ```javascript + // ํšŒ์› ๋ฐ์ดํ„ฐ ์ƒ์„ฑ + async function createMember() { + const member = await Member.create({ + locationAddress: '์„œ์šธ์‹œ ๊ด€์•…๊ตฌ', + email: 'ahnnn000@gmail.com', + phoneNumber: '010-9836-3964', + memberName: '์•ˆ์„ฑ์ง„', + nickname: '์›ฌํ‹ฐ', + gender: 1, + birth: 2000-04-24 + }); + } + + // ๋ชจ๋“  ํšŒ์› ๋ฐ์ดํ„ฐ ์กฐํšŒ + async function getMembers() { + const members = await Member.findAll(); + } + ``` + - Sequelize ๋ชจ๋ธ + - Sequelize ๋ชจ๋ธ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์„ ์ฝ”๋“œ ์ƒ์—์„œ ๊ฐ์ฒด๋กœ ํ‘œํ˜„ํ•œ ๊ฒƒ์ด๋‹ค. (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”๊ณผ 1:1๋กœ ๋งคํ•‘๋œ๋‹ค) + - ์ด ๋ชจ๋ธ์„ ํ†ตํ•ด SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. CRUD ์ž‘์—…์„ JavaScript ์ฝ”๋“œ๋กœ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค. + - ex. Member ๋ชจ๋ธ ์ •์˜ + ```javascript + const { DataTypes, Model } = require('sequelize'); + // DataTypes: ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ •์˜ ๋ชจ๋“ˆ(string, integer ๋“ฑ) + // Model: ๋ชจ๋ธ ํด๋ž˜์Šค๋ฅผ ํ™•์žฅํ•˜๋Š” ๊ธฐ๋ณธ ํด๋ž˜์Šค. ์ด๋ฅผ ์ƒ์†๋ฐ›์•„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜๋Š” ๊ฐ์ฒด๊ฐ€ ๋œ๋‹ค. + // Sequelize ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ + const sequelize = require('../config/database'); + // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • ํŒŒ์ผ(database.js)์—์„œ ์„ค์ •ํ•œ Sequelize ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. + class Member extends Model {} // Model ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›๋Š”๋‹ค. + Member.init( // ๋ชจ๋ธ์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์„ ์ •์˜ํ•œ๋‹ค. + { + id: { + **type**: DataTypes.BIGINT, + **primaryKey**: true, + **autoIncrement**: true, + }, + locationAddress: { + type: DataTypes.TEXT, + **allowNull**: false, + }, + email: { + type: DataTypes.STRING(50), + allowNull: false, + unique: true, + }, + phoneNumber: { + type: DataTypes.STRING(15), + allowNull: true, + }, + memberName: { + type: DataTypes.STRING(30), + allowNull: false, + }, + nickname: { + type: DataTypes.STRING(30), + allowNull: false, + }, + gender: { + type: DataTypes.INTEGER, + allowNull: false, + }, + birth: { + type: DataTypes.STRING(10), + allowNull: false, + }, + points: { + type: DataTypes.BIGINT, + defaultValue: 0, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE(6), + allowNull: false, + }, + updatedAt: { + type: DataTypes.DATE(6), + allowNull: false, + }, + status: { + type: DataTypes.INTEGER, + allowNull: false, + }, + inactiveAt: { + type: DataTypes.DATE(6), + allowNull: true, + }, + }, + { + sequelize, // Sequelize ์ธ์Šคํ„ด์Šค + modelName: 'Member', // ๋ชจ๋ธ ์ด๋ฆ„ ์„ค์ • + tableName: 'member', // ๋งคํ•‘๋  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ์ด๋ฆ„ + timestamps: true, // createdAt, updatedAt ์ž๋™ ๊ด€๋ฆฌ ํ™œ์„ฑํ™” + } + ); + module.exports = Member; // Member ๋ชจ๋ธ์„ ์™ธ๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋„๋ก ๋‚ด๋ณด๋‚ธ๋‹ค. + ``` + - ex. TypeORM + - **TypeScript**์™€ **JavaScript**๋ฅผ ์œ„ํ•œ ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ + - ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—”ํ„ฐํ‹ฐ์™€ ํ…Œ์ด๋ธ”, ์ปฌ๋Ÿผ ๋“ฑ์„ ์ •์˜ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๋งคํ•‘์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. + - ์—”ํ„ฐํ‹ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ž๋™ ์ƒ์„ฑํ•˜๊ณ  ์Šคํ‚ค๋งˆ๋ฅผ ๋™๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. + - ๋ณต์žกํ•œ SQL ์ฟผ๋ฆฌ๋ฅผ TypeORM์˜ ์ฟผ๋ฆฌ ๋นŒ๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. + - ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปฌ๋Ÿผ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. + - ๊ด€๊ณ„๋œ ์—”ํ„ฐํ‹ฐ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉ(Lazy Loading)ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. +- ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์‚ฌ์šฉํ–๋Š” ๋‹ค๋ฅธ API ์ฐพ์•„๋ณด๊ธฐ + - ex. https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28 + - **GitHub REST API** + - ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์‚ฌ์šฉํ•ด ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. + - ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•œ ํŽ˜์ด์ง€์— ์ผ์ • ์ˆ˜์˜ ํ•ญ๋ชฉ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ link ํ—ค๋”๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ํŽ˜์ด์ง€, ์ด์ „ ํŽ˜์ด์ง€ ๋“ฑ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” URL์„ ์ œ๊ณตํ•œ๋‹ค. + - per_page ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜๋ฅผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. + - ex. ์ด์Šˆ ๋ชฉ๋ก์„ ์š”์ฒญํ•  ๊ฒฝ์šฐ (๊ธฐ๋ณธ์ ์œผ๋กœ ํ•œ ํŽ˜์ด์ง€์— 30๊ฐœ ๋ฐ˜ํ™˜) + ```javascript + GET /repos/{owner}/{repo}/issues?per_page=10&page=2 + ``` + - per_page=10: ํŽ˜์ด์ง€๋‹น 10๊ฐœ์˜ ์ด์Šˆ๋ฅผ ์š”์ฒญํ•œ๋‹ค. + - page=2: ๋‘ ๋ฒˆ์งธ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•œ๋‹ค. + - ex. https://developers.notion.com/reference/intro#pagination + - **Notion์˜ REST API** + - ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์‚ฌ์šฉํ•œ๋‹ค. + - ์ด๋Š” ํ•œ ๋ฒˆ์˜ ์š”์ฒญ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ ์–‘์„ ์ œํ•œํ•˜๊ณ  ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ํ›„์† ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ์‹ + - ์ฃผ์š” ์š”์†Œ + 1. **page_size** + - ํ•œ ๋ฒˆ์˜ API ์š”์ฒญ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ํ•ญ๋ชฉ์˜ ์ตœ๋Œ€ ์ˆ˜๋ฅผ ์ง€์ •ํ•œ๋‹ค. + - ๊ธฐ๋ณธ๊ฐ’์€ 100์ด๋ฉฐ ์ตœ๋Œ€ 100๊นŒ์ง€ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + - ex. page_size=50 โ†’ ํ•œ ๋ฒˆ์˜ ์š”์ฒญ์œผ๋กœ ์ตœ๋Œ€ 50๊ฐœ์˜ ํ•ญ๋ชฉ ๋ฐ˜ํ™˜ + 2. **start_cursor** + - ๋ฐ์ดํ„ฐ์˜ ํŠน์ • ์ง€์ ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ์ปค์„œ + - ์ด์ „ ์‘๋‹ต์˜ next_cursor ํ•„๋“œ์—์„œ ๊ฐ€์ ธ์˜จ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค. + - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ•ญ๋ชฉ์„ ์กฐํšŒํ•  ๊ฒฝ์šฐ์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ตฌํ˜„ + 1. **์ฒซ ๋ฒˆ์งธ ์š”์ฒญ** + ```javascript + POST https://api.notion.com/v1/databases/{database_id}/query + Content-Type: application/json + Notion-Version: 2022-06-28 + { + "page_size": 100 + } + ``` + - ์ตœ๋Œ€ 100๊ฐœ์˜ ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ ์‘๋‹ต์—๋Š” next_cursor์™€ has_more ํ•„๋“œ๊ฐ€ ํฌํ•จ๋œ๋‹ค. + 2. **ํ›„์† ์š”์ฒญ** + ```javascript + POST https://api.notion.com/v1/databases/{database_id}/query + Content-Type: application/json + Notion-Version: 2022-06-28 + { + "page_size": 100, + "start_cursor": "{next_cursor}" + } + ``` + - ์ฒซ ๋ฒˆ์งธ ์‘๋‹ต์—์„œ has_more๊ฐ€ true์ด๊ณ  next_cursor๊ฐ€ ์กด์žฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ›„์† ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค. + - ์—ฌ๊ธฐ์„œ {next_cursor}๋Š” ์ด์ „ ์‘๋‹ต์˜ next_cursor ๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. \ No newline at end of file diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-1-\354\213\235\353\213\271_\353\223\261\353\241\235.png" "b/mission/chapter05/images/\353\257\270\354\205\230-1-\354\213\235\353\213\271_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..5b2a71f Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-1-\354\213\235\353\213\271_\353\223\261\353\241\235.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-1-\354\244\221\353\263\265\353\220\234_\354\213\235\353\213\271.png" "b/mission/chapter05/images/\353\257\270\354\205\230-1-\354\244\221\353\263\265\353\220\234_\354\213\235\353\213\271.png" new file mode 100644 index 0000000..8ba6107 Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-1-\354\244\221\353\263\265\353\220\234_\354\213\235\353\213\271.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-2-\353\246\254\353\267\260_\353\223\261\353\241\235.png" "b/mission/chapter05/images/\353\257\270\354\205\230-2-\353\246\254\353\267\260_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..fa3485a Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-2-\353\246\254\353\267\260_\353\223\261\353\241\235.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-2-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" "b/mission/chapter05/images/\353\257\270\354\205\230-2-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" new file mode 100644 index 0000000..3c0897f Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-2-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-3-\353\257\270\354\205\230_\353\223\261\353\241\235.png" "b/mission/chapter05/images/\353\257\270\354\205\230-3-\353\257\270\354\205\230_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..d3293bf Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-3-\353\257\270\354\205\230_\353\223\261\353\241\235.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-3-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" "b/mission/chapter05/images/\353\257\270\354\205\230-3-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" new file mode 100644 index 0000000..8988db0 Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-3-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-3-\354\244\221\353\263\265\353\220\234_\353\257\270\354\205\230.png" "b/mission/chapter05/images/\353\257\270\354\205\230-3-\354\244\221\353\263\265\353\220\234_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..3d9e30a Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-3-\354\244\221\353\263\265\353\220\234_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-4-\353\217\204\354\240\204\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" "b/mission/chapter05/images/\353\257\270\354\205\230-4-\353\217\204\354\240\204\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..dbd6bae Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-4-\353\217\204\354\240\204\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-4-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" "b/mission/chapter05/images/\353\257\270\354\205\230-4-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..6fd7460 Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-4-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter05/images/\353\257\270\354\205\230-4-\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270.png" "b/mission/chapter05/images/\353\257\270\354\205\230-4-\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270.png" new file mode 100644 index 0000000..70e6b46 Binary files /dev/null and "b/mission/chapter05/images/\353\257\270\354\205\230-4-\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270.png" differ diff --git a/mission/chapter05/mission.md b/mission/chapter05/mission.md new file mode 100644 index 0000000..21d6a8b --- /dev/null +++ b/mission/chapter05/mission.md @@ -0,0 +1,364 @@ +### ๐Ÿ”ฅ ๋ฏธ์…˜ +--- +> GitHub ์ €์žฅ์†Œ ์ฃผ์†Œ
+> [https://github.com/asjasj3964/UMC-7th-Node.js-Workbook](https://github.com/asjasj3964/UMC-7th-Node.js-Workbook) + +
๋ชจ๋“  ์ฝ”๋“œ์— ๋Œ€ํ•ด ์„ค๋ช…์„ ํ•˜๋ฉด ๋„ˆ๋ฌด ๊ธธ์–ด์งˆ ๊ฒƒ ๊ฐ™์•„ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ๋ฐ ์กฐํšŒ ๊ณผ์ •์ด ์žˆ๋Š” Repository ํ•จ์ˆ˜๋งŒ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.
+ +1. ํŠน์ • ์ง€์—ญ์— ์‹๋‹น ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ์‹๋‹น ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (์‹๋‹น ๋“ฑ๋ก) & ์‹๋‹น ID ๋ฐ˜ํ™˜ + export const addRestaurant = async(data) => { + const conn = await pool.getConnection(); + try{ + // ํ•ด๋‹น ์œ„์น˜์˜ ์‹๋‹น(์ค‘๋ณต๋œ ์‹๋‹น)์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + const [confirm] = await pool.query( + `SELECT EXISTS(SELECT 1 FROM restaurant WHERE region_id = ? and restaurant_name = ?) as isExistRestaurant`, + [data.region, data.name] + ); + if (confirm[0].isExistRestaurant) { // ์ค‘๋ณต๋œ ์‹๋‹น์ผ ๊ฒฝ์šฐ + return null; + } + // ์‹๋‹น ์ƒ์„ฑ + const [result] = await pool.query( + `INSERT INTO restaurant (ceo_id, region_id, restaurant_name, introduction, start_time, end_time) VALUES (?, ?, ?, ?, ?, ?);`, + [ + data.ceoId, + data.region, + data.name, + data.introduction, + data.startTime, + data.endTime, + ] + ); // ์‹๋‹น ๋ฐ์ดํ„ฐ ์‚ฝ์ž… + return result.insertId; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ์‹๋‹น ID๋กœ ์‹๋‹น ์กฐํšŒ + export const getRestaurant = async(restaurantId) => { + const conn = await pool.getConnection(); + try{ + const [restaurant] = await pool.query( + `SELECT * FROM restaurant WHERE id = ?`, + restaurantId + ) + console.log(restaurant); + if (restaurant.length == 0){ + return null; + } + return restaurant; + }catch (err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ์‹๋‹น - ์ง€์—ญ ๋ฐ˜ํ™˜ + export const getrestaurantRegionByRestaurantId = async (restaurantId) => { + const conn = await pool.getConnection(); + try{ + const [region] = await pool.query(` + SELECT rest.id, rest.region_id, re.address + FROM restaurant rest JOIN region re ON rest.region_id = re.id + WHERE rest.id = ?`, + restaurantId + ); // restaurant ํ…Œ์ด๋ธ”๊ณผ region ํ…Œ์ด๋ธ”์„ joinํ•ด ํ•ด๋‹น ์‹๋‹น์˜ ์œ„์น˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•œ๋‹ค. + return region; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ์‹๋‹น ceo ๋ฐ˜ํ™˜ + export const getrestaurantCeoByCeoId = async (restaurantCeoId) => { + const conn = await pool.getConnection(); + try{ + const [restaurantCeo] = await pool.query(` + SELECT rest.id, rest.ceo_id, mem.member_name + FROM restaurant rest JOIN member mem ON rest.ceo_id = mem.id + WHERE rest.ceo_id = ?`, + restaurantCeoId + ); // restaurant ํ…Œ์ด๋ธ”๊ณผ member ํ…Œ์ด๋ธ”์„ joinํ•ด ํ•ด๋‹น ์‹๋‹น์˜ CEO(ํšŒ์›) ์ •๋ณด๋ฅผ ์กฐํšŒํ•œ๋‹ค. + return restaurantCeo; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![์‹๋‹น ๋“ฑ๋ก](images/๋ฏธ์…˜-1-์‹๋‹น_๋“ฑ๋ก.png) + - ์ด๋ฏธ ๋“ฑ๋ก๋œ ์‹๋‹น์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์ค‘๋ณต๋œ ์‹๋‹น](images/๋ฏธ์…˜-1-์ค‘๋ณต๋œ_์‹๋‹น.png + ) + - ๊ฐ™์€ ์œ„์น˜์™€ ์ด๋ฆ„์˜ ์‹๋‹น์„ ์ค‘๋ณต ๋“ฑ๋กํ•  ์ˆ˜ ์—†๊ฒŒ ํ•˜์˜€๋‹ค. + +2. ์‹๋‹น์— ๋ฆฌ๋ทฐ ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (๋ฆฌ๋ทฐ ๋“ฑ๋ก) & ๋ฆฌ๋ทฐ ID ๋ฐ˜ํ™˜ + export const addReview = async(data) => { + const conn = await pool.getConnection(); + try{ + // ๋ฆฌ๋ทฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋Š” ๊ฐ€๊ฒŒ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ๊ฒ€์ฆ + const [confirm] = await pool.query( + `SELECT EXISTS(SELECT 1 FROM restaurant WHERE id = ?)as isExistRestaurant;`, + data.restaurant + ); + if (!confirm[0].isExistRestaurant){ // ๋“ฑ๋กํ•˜๋ ค๋Š” ์‹๋‹น์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ + return null; + } + // ๋ฆฌ๋ทฐ ์ƒ์„ฑ + const [result] = await pool.query( + `INSERT INTO review (member_id, restaurant_id, rating, content) VALUES (?, ?, ?, ?);`, + [ + data.member, + data.restaurant, + data.rating, + data.content + ] + ); // ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… + return result.insertId; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ๋ฆฌ๋ทฐ ID๋กœ ๋ฆฌ๋ทฐ ์กฐํšŒ + export const getReview = async(reviewId) => { + const conn = await pool.getConnection(); + try{ + const[review] = await pool.query( + `SELECT * FROM review WHERE id = ?`, + reviewId + ) + console.log(review); + if (review.length == 0){ + return null; + } + return review; + }catch (err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ๋ฆฌ๋ทฐ ID๋กœ ๋ฆฌ๋ทฐ ๋“ฑ๋กํ•œ ์‹๋‹น์˜ ์ด๋ฆ„ ์•Œ์•„๋‚ด๊ธฐ + export const getReviewRestaurantByReviewId = async(reviewId) => { + const conn = await pool.getConnection(); + try{ + const [review] = await pool.query(` + SELECT re.id, re.restaurant_id, rest.restaurant_name + FROM review as re JOIN restaurant rest ON re.restaurant_id = rest.id + WHERE re.id = ?`, + reviewId + ); // review ํ…Œ์ด๋ธ”๊ณผ restaurant ํ…Œ์ด๋ธ”์„ joinํ•ด ํ•ด๋‹น ๋ฆฌ๋ทฐ์˜ ์‹๋‹น ์ •๋ณด๋ฅผ ์กฐํšŒํ•œ๋‹ค. + return review; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ๋ฆฌ๋ทฐ ์ž‘์„ฑ์ž ID๋กœ ์ž‘์„ฑ์ž์˜ ์ด๋ฆ„ ์•Œ์•„๋‚ด๊ธฐ + export const getReviewWriterByWriterId = async(reviewWriterId) => { + const conn = await pool.getConnection(); + try{ + const [reviewWriter] = await pool.query(` + SELECT re.id, re.member_id, mem.member_name + FROM review as re JOIN member mem ON re.member_id = mem.id + WHERE re.member_id = ?`, + reviewWriterId + ); // review ํ…Œ์ด๋ธ”๊ณผ member ํ…Œ์ด๋ธ”์„ joinํ•ด ํ•ด๋‹น ๋ฆฌ๋ทฐ์˜ ์ž‘์„ฑ์ž(ํšŒ์›) ์ •๋ณด๋ฅผ ์กฐํšŒํ•œ๋‹ค. + return reviewWriter; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฆฌ๋ทฐ ๋“ฑ๋ก](images/๋ฏธ์…˜-2-๋ฆฌ๋ทฐ_๋“ฑ๋ก.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น์— ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น](images/๋ฏธ์…˜-2-์กด์žฌํ•˜์ง€์•Š๋Š”_์‹๋‹น.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID(150)์˜ ์‹๋‹น์— ๋ฆฌ๋ทฐ๋ฅผ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. +3. ์‹๋‹น์— ๋ฏธ์…˜ ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ๋ฏธ์…˜ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (๋ฏธ์…˜ ๋“ฑ๋ก) & ๋ฏธ์…˜ ID ๋ฐ˜ํ™˜ + export const addMission = async(data) => { + const conn = await pool.getConnection(); + try{ + // ๋“ฑ๋กํ•˜๋ ค๋Š” ์‹๋‹น ID, ๋ฏธ์…˜ ์ด๋ฆ„, ๋ฏธ์…˜ ๋‚ด์šฉ๊ณผ ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š” ์ค‘๋ณต ๋ฏธ์…˜์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const [confirm1] = await pool.query( + `SELECT EXISTS(SELECT 1 FROM mission WHERE restaurant_id = ? and mission_name = ? and introduction = ?) as isExistMission;`, + [data.restaurant, data.name, data.introduction] + ); + // ๋“ฑ๋กํ•˜๋ ค๋Š” ์‹๋‹น์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const [confirm2] = await pool.query( + `SELECT EXISTS(SELECT 1 FROM restaurant WHERE id = ?) as isExistRestaurant;`, + data.restaurant + ); + // ์ค‘๋ณต ๋ฏธ์…˜์ด ์žˆ๊ฑฐ๋‚˜ ์‹๋‹น์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ์‹œ + if (confirm1[0].isExistMission || (!confirm2[0].isExistRestaurant)){ + return null; + } + // ๋ฏธ์…˜ ์ƒ์„ฑ + const [result] = await pool.query( + `INSERT INTO mission (restaurant_id, mission_name, introduction, deadline, points) VALUES (?, ?, ?, ?, ?);`, + [data.restaurant, data.name, data.introduction, data.deadline, data.points] + ); // ๋ฏธ์…˜ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… + return result.insertId; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ๋ฏธ์…˜ ID๋กœ ๋ฏธ์…˜ ์กฐํšŒ + export const getMission = async(missionId) => { + const conn = await pool.getConnection(); + try{ + const[mission] = await pool.query( + `SELECT * FROM mission WHERE id = ?`, + missionId + ); + console.log(mission); + if (mission.length == 0){ + return null; + } + return mission; + }catch (err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + + // ๋ฏธ์…˜ ID๋กœ ์‹๋‹น ์กฐํšŒ + export const getRestaurantByMissionId = async(missionId) => { + const conn = await pool.getConnection(); + try{ + const [restaurant] = await pool.query(` + SELECT mi.id, mi.restaurant_id, rest.restaurant_name + FROM mission mi JOIN restaurant rest ON mi.restaurant_id = rest.id + WHERE mi.id = ?`, + missionId + ); // mission ํ…Œ์ด๋ธ”๊ณผ restaurant ํ…Œ์ด๋ธ”์„ joinํ•ด ํ•ด๋‹น ํšŒ์›์˜ ์‹๋‹น ์ •๋ณด๋ฅผ ์กฐํšŒํ•œ๋‹ค. + return restaurant; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฏธ์…˜ ๋“ฑ๋ก](images/๋ฏธ์…˜-3-๋ฏธ์…˜_๋“ฑ๋ก.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น์— ๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น](images/๋ฏธ์…˜-3-์กด์žฌํ•˜์ง€์•Š๋Š”_์‹๋‹น.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID(140)์˜ ์‹๋‹น์— ๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. + - ์ด๋ฏธ ๋“ฑ๋ก๋œ ๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์ค‘๋ณต๋œ ๋ฏธ์…˜](images/๋ฏธ์…˜-3-์ค‘๋ณต๋œ_๋ฏธ์…˜.png) + - ๊ฐ™์€ ์‹๋‹น, ๋ฏธ์…˜๋ช…, ๋ฏธ์…˜ ์„ค๋ช…์˜ ๋ฏธ์…˜์„ ์ค‘๋ณต ๋“ฑ๋กํ•  ์ˆ˜ ์—†๊ฒŒ ํ•˜์˜€๋‹ค. +4. ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜์„ ๋„์ „ ์ค‘์ธ ๋ฏธ์…˜์— ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํŠน์ • ๋ฏธ์…˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ(์ง„ํ–‰ X -> ์ง„ํ–‰ ์ค‘) + export const updateMissionStatus = async(missionId) => { + const conn = await pool.getConnection(); + try{ + // ์—…๋ฐ์ดํŠธํ•  ๋ฏธ์…˜์ด ์กด์žฌํ•˜๋Š”์ง€ ๋ฏธ์…˜ ID๋กœ ์กฐํšŒํ•˜์—ฌ ํ™•์ธ + const [confirm1] = await pool.query( + `SELECT EXISTS(SELECT 1 FROM mission WHERE id = ?) as isExistMission;`, + missionId + ); + // ํ•ด๋‹น ๋ฏธ์…˜์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด status ์„ ํƒ + const [confirm2] = await pool.query( + `SELECT status FROM mission WHERE id = ?`, + missionId + ); + // ํ•ด๋‹น ๋ฏธ์…˜์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ƒํƒœ๊ฐ€ ์ง„ํ–‰ X๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ + if((!confirm1[0].isExistMission) || (confirm2[0].status != 0)){ + return null; + } + // ํ•ด๋‹น ๋ฏธ์…˜์˜ status ๊ฐ’์„ 1(์ง„ํ–‰ ์ค‘)๋กœ ๋ณ€๊ฒฝ + await pool.query(` + UPDATE mission SET status = 1 WHERE id = ?;`, + missionId + ); + // ๋ฏธ์…˜ ID๋กœ ์กฐํšŒํ•ด์„œ ํ•ด๋‹น ๋ฏธ์…˜ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ์กฐํšŒํ•œ๋‹ค. + const [mission] = await pool.query(` + SELECT * FROM mission WHERE id = ?;`, + missionId + ) + return mission; + }catch(err){ + throw new Error(` + ๐Ÿšซ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๐Ÿšซ + ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ ๋ฐ”๋žŒ (${err}) + `); + }finally{ + conn.release(); + } + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฏธ์…˜ ์ง„ํ–‰ ์ค‘์œผ๋กœ ์—…๋ฐ์ดํŠธ](images/๋ฏธ์…˜-4-์ง„ํ–‰์ค‘_์—…๋ฐ์ดํŠธ.png) + - ๋ฏธ์…˜ 3์—์„œ ๋“ฑ๋กํ•œ ๋ฏธ์…˜(๋„์ „ํ•˜๊ธฐ ์ „์˜ ์ƒํƒœ(0))์„ ์ง„ํ–‰ ์ค‘์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•˜์˜€๋‹ค. + - ๋„์ „ํ•  ์ˆ˜ ์—†๋Š” ๋ฏธ์…˜(์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ด๊ฑฐ๋‚˜ ์™„๋ฃŒํ•œ ๋ฏธ์…˜)์˜ ๊ฒฝ์šฐ + ![๋„์ „ํ•  ์ˆ˜ ์—†๋Š” ๋ฏธ์…˜](images/๋ฏธ์…˜-4-๋„์ „ํ• ์ˆ˜์—†๋Š”_๋ฏธ์…˜.png) + - ๋ฐฉ๊ธˆ ์ „์— ์—…๋ฐ์ดํŠธ ์‹œํ‚จ ๋ฏธ์…˜(์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ธ ์ƒํƒœ)์„ ๋˜ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์•˜๋‹ค. + - ๋ฏธ์…˜์˜ ์ƒํƒœ(status)๊ฐ€ 0(์ง„ํ–‰ํ•˜๊ธฐ ์ „)์ด ์•„๋‹ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฏธ์…˜์„ ์—…๋ฐ์ดํŠธํ•  ๊ฒฝ์šฐ + ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฏธ์…˜](images/๋ฏธ์…˜-4-์กด์žฌํ•˜์ง€์•Š๋Š”_๋ฏธ์…˜.png) + - ์—…๋ฐ์ดํŠธ ํ•˜๋ ค๋Š” ๋ฏธ์…˜์˜ ID๋กœ ์กฐํšŒํ–ˆ์„ ๋•Œ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. \ No newline at end of file diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-1-\354\213\235\353\213\271_\353\223\261\353\241\235.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-1-\354\213\235\353\213\271_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..70d6e7a Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-1-\354\213\235\353\213\271_\353\223\261\353\241\235.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-1-\354\244\221\353\263\265\353\220\234_\354\213\235\353\213\271.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-1-\354\244\221\353\263\265\353\220\234_\354\213\235\353\213\271.png" new file mode 100644 index 0000000..40eb013 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-1-\354\244\221\353\263\265\353\220\234_\354\213\235\353\213\271.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-2-\353\246\254\353\267\260_\353\223\261\353\241\235.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-2-\353\246\254\353\267\260_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..207bc23 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-2-\353\246\254\353\267\260_\353\223\261\353\241\235.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-2-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-2-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" new file mode 100644 index 0000000..382bb74 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-2-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-3-\353\257\270\354\205\230_\353\223\261\353\241\235.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-3-\353\257\270\354\205\230_\353\223\261\353\241\235.png" new file mode 100644 index 0000000..463f33d Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-3-\353\257\270\354\205\230_\353\223\261\353\241\235.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-3-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-3-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" new file mode 100644 index 0000000..5e53076 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-3-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\354\213\235\353\213\271.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-3-\354\244\221\353\263\265\353\220\234_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-3-\354\244\221\353\263\265\353\220\234_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..75700a7 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-3-\354\244\221\353\263\265\353\220\234_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-4-\353\217\204\354\240\204\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\353\217\204\354\240\204\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..bce3a0c Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\353\217\204\354\240\204\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-4-\353\257\270\354\205\230_\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\353\257\270\354\205\230_\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270.png" new file mode 100644 index 0000000..0f3671e Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\353\257\270\354\205\230_\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-4-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..632c6c8 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-1-4-\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270\354\240\204\354\235\230_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270\354\240\204\354\235\230_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..3cdad9f Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-1-4-\354\247\204\355\226\211\354\244\221_\354\227\205\353\215\260\354\235\264\355\212\270\354\240\204\354\235\230_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-2-\355\212\271\354\240\225\355\232\214\354\233\220-\353\246\254\353\267\260\353\252\251\353\241\235.png" "b/mission/chapter06/images/\353\257\270\354\205\230-2-\355\212\271\354\240\225\355\232\214\354\233\220-\353\246\254\353\267\260\353\252\251\353\241\235.png" new file mode 100644 index 0000000..30d789f Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-2-\355\212\271\354\240\225\355\232\214\354\233\220-\353\246\254\353\267\260\353\252\251\353\241\235.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-3-\355\212\271\354\240\225\352\260\200\352\262\214_\353\257\270\354\205\230\353\252\251\353\241\235.png" "b/mission/chapter06/images/\353\257\270\354\205\230-3-\355\212\271\354\240\225\352\260\200\352\262\214_\353\257\270\354\205\230\353\252\251\353\241\235.png" new file mode 100644 index 0000000..fce1ce0 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-3-\355\212\271\354\240\225\352\260\200\352\262\214_\353\257\270\354\205\230\353\252\251\353\241\235.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-4-\355\212\271\354\240\225\355\232\214\354\233\220_\354\247\204\355\226\211\354\244\221\354\235\270_\353\257\270\354\205\230\353\252\251\353\241\235.png" "b/mission/chapter06/images/\353\257\270\354\205\230-4-\355\212\271\354\240\225\355\232\214\354\233\220_\354\247\204\355\226\211\354\244\221\354\235\270_\353\257\270\354\205\230\353\252\251\353\241\235.png" new file mode 100644 index 0000000..8ef5fa3 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-4-\355\212\271\354\240\225\355\232\214\354\233\220_\354\247\204\355\226\211\354\244\221\354\235\270_\353\257\270\354\205\230\353\252\251\353\241\235.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-5-\353\257\270\354\205\230_\354\247\204\355\226\211\354\231\204\353\243\214_\354\227\205\353\215\260\354\235\264\355\212\270.png" "b/mission/chapter06/images/\353\257\270\354\205\230-5-\353\257\270\354\205\230_\354\247\204\355\226\211\354\231\204\353\243\214_\354\227\205\353\215\260\354\235\264\355\212\270.png" new file mode 100644 index 0000000..b411f4c Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-5-\353\257\270\354\205\230_\354\247\204\355\226\211\354\231\204\353\243\214_\354\227\205\353\215\260\354\235\264\355\212\270.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-5-\354\231\204\353\243\214\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-5-\354\231\204\353\243\214\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..7b8e17a Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-5-\354\231\204\353\243\214\355\225\240\354\210\230\354\227\206\353\212\224_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-5-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-5-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..fb4713e Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-5-\354\241\264\354\236\254\355\225\230\354\247\200\354\225\212\353\212\224_\353\257\270\354\205\230.png" differ diff --git "a/mission/chapter06/images/\353\257\270\354\205\230-5-\354\247\204\355\226\211\354\231\204\353\243\214_\354\227\205\353\215\260\354\235\264\355\212\270\354\240\204\354\235\230_\353\257\270\354\205\230.png" "b/mission/chapter06/images/\353\257\270\354\205\230-5-\354\247\204\355\226\211\354\231\204\353\243\214_\354\227\205\353\215\260\354\235\264\355\212\270\354\240\204\354\235\230_\353\257\270\354\205\230.png" new file mode 100644 index 0000000..9a33115 Binary files /dev/null and "b/mission/chapter06/images/\353\257\270\354\205\230-5-\354\247\204\355\226\211\354\231\204\353\243\214_\354\227\205\353\215\260\354\235\264\355\212\270\354\240\204\354\235\230_\353\257\270\354\205\230.png" differ diff --git a/mission/chapter06/mission.md b/mission/chapter06/mission.md new file mode 100644 index 0000000..9cc93e6 --- /dev/null +++ b/mission/chapter06/mission.md @@ -0,0 +1,483 @@ +### ๐Ÿ”ฅ ๋ฏธ์…˜ +--- +> GitHub ์ €์žฅ์†Œ ์ฃผ์†Œ
+> [https://github.com/asjasj3964/UMC-7th-Node.js-Workbook](https://github.com/asjasj3964/UMC-7th-Node.js-Workbook) + +1. 5์ฃผ์ฐจ ๋•Œ ์ž‘์„ฑํ–ˆ๋˜ API๋ฅผ Prisma ORM์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๊ธฐ + 1. ํŠน์ • ์ง€์—ญ์— ์‹๋‹น ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ ์ˆ˜์ • + ```javascript + // ์‹๋‹น ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (์‹๋‹น ๋“ฑ๋ก) & ์‹๋‹น ID ๋ฐ˜ํ™˜ + export const addRestaurant = async(data) => { + // ๋“ฑ๋กํ•˜๊ณ ์ž ํ•˜๋Š” ์‹๋‹น์˜ ์ด๋ฆ„๊ณผ ์œ„์น˜๊ฐ€ ๊ฐ™์€ ์ค‘๋ณต ์‹๋‹น์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const restaurant = await prisma.restaurant.findFirst({ + where: { + name: data.name, + regionId: data.region + } + }); + if (restaurant){ // ์ค‘๋ณต ์‹๋‹น์ผ ๊ฒฝ์šฐ + return null; + } + const created = await prisma.restaurant.create({ + data: { + ...data, + region: { + connect: { id: data.region } // region ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์—ฐ๊ฒฐ + }, + ceo: { + connect: { id: data.ceo } // ceo(member) ํ…Œ์ด๋ธ” ๊ด€๊ณ„ ์—ฐ๊ฒฐ + }, + } + }); + return created.id; + } + + // ์‹๋‹น ID๋กœ ์‹๋‹น ์กฐํšŒ + export const getRestaurant = async(restaurantId) => { + const restaurant = await prisma.restaurant.findFirstOrThrow({ + select: { + id: true, + ceo: true, + region: true, + name: true, + introduction: true, + startTime: true, + endTime: true, + totalRating: true, + }, + where: { id: restaurantId } + }); + const formattedRestaurant = { + ...restaurant, + id: restaurant.id.toString(), + region: { + id: restaurant.region.id.toString(), + address: restaurant.region.address, + }, + ceo: { + id: restaurant.ceo.id.toString(), + name: restaurant.ceo.name.toString() + } + }; + return formattedRestaurant; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![์‹๋‹น ๋“ฑ๋ก](images/๋ฏธ์…˜-1-1-์‹๋‹น_๋“ฑ๋ก.png) + - ์ด๋ฏธ ๋“ฑ๋ก๋œ ์‹๋‹น์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์ค‘๋ณต๋œ ์‹๋‹น](images/๋ฏธ์…˜-1-1-์ค‘๋ณต๋œ_์‹๋‹น.png + ) + - ๊ฐ™์€ ์œ„์น˜์™€ ์ด๋ฆ„์˜ ์‹๋‹น์„ ์ค‘๋ณต ๋“ฑ๋กํ•  ์ˆ˜ ์—†๊ฒŒ ํ•˜์˜€๋‹ค. + + 2. ์‹๋‹น์— ๋ฆฌ๋ทฐ ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ ์ˆ˜์ • + ```javascript + // ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (๋ฆฌ๋ทฐ ๋“ฑ๋ก) & ๋ฆฌ๋ทฐ ID ๋ฐ˜ํ™˜ + export const addReview = async(data) => { + // ๋ฆฌ๋ทฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋Š” ์‹๋‹น์ด ์กด์žฌํ•˜๋Š”์ง€ ๊ฒ€์ฆ + const restaurant = await prisma.restaurant.findFirst( { + where: { + id: data.restaurant // ๋“ฑ๋กํ•  ์‹๋‹น์˜ ID๋ฅผ ๊ฐ€์ง„ ๊ฐ€๊ฒŒ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + } + }) + if (restaurant == null){ // ํ•ด๋‹น ์‹๋‹น์ด ์กด์žฌํ•˜์ง€ ์•Š๋‹ค๋ฉด + return null; + } + const created = await prisma.review.create({ + data: { + ...data, + member: { + connect: { // member ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„ ์—ฐ๊ฒฐ + id: data.member + } + }, + restaurant:{ + connect: { // restaurant ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„ ์—ฐ๊ฒฐ + id: data.restaurant + } + } + } + }); + return created.id; + } + + // ๋ฆฌ๋ทฐ ID๋กœ ๋ฆฌ๋ทฐ ์กฐํšŒ + export const getReview = async(reviewId) => { + const review = await prisma.review.findFirstOrThrow({ + select: { + id: true, + member: true, + restaurant: true, + rating: true, + content: true, + createdAt: true, + status: true + }, + where: { + id: reviewId + } + }); + const formattedReview = { + ...review, + id: review.id.toString(), + member: { + id: review.member.id.toString(), + name: review.member.name, + }, + restaurant: { + id: review.restaurant.id.toString(), + name: review.restaurant.name, + }, + } + return formattedReview; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฆฌ๋ทฐ ๋“ฑ๋ก](images/๋ฏธ์…˜-1-2-๋ฆฌ๋ทฐ_๋“ฑ๋ก.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น์— ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น](images/๋ฏธ์…˜-1-2-์กด์žฌํ•˜์ง€์•Š๋Š”_์‹๋‹น.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID(100)์˜ ์‹๋‹น์— ๋ฆฌ๋ทฐ๋ฅผ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. + 3. ์‹๋‹น์— ๋ฏธ์…˜ ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ ์ˆ˜์ • + ```javascript + // ๋ฏธ์…˜ ๋ฐ์ดํ„ฐ ์‚ฝ์ž… (๋ฏธ์…˜ ๋“ฑ๋ก) & ๋ฏธ์…˜ ID ๋ฐ˜ํ™˜ + export const addMission = async(data) => { + // ๋“ฑ๋กํ•˜๋ ค๋Š” ์‹๋‹น ID, ๋ฏธ์…˜ ์ด๋ฆ„, ๋ฏธ์…˜ ๋‚ด์šฉ๊ณผ ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š” ์ค‘๋ณต ๋ฏธ์…˜์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const mission = await prisma.mission.findFirst({ + where: { + restaurantId: data.restaurant, + name: data.name, + introduction: data.introduction + } + }); + // ๋“ฑ๋กํ•˜๋ ค๋Š” ์‹๋‹น์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const restaurant = await prisma.restaurant.findFirst({ + where: { + id: data.restaurant + } + }); + if (mission != null || restaurant == null){ // ์ค‘๋ณต ๋ฏธ์…˜์ด ์žˆ๊ฑฐ๋‚˜ ์‹๋‹น์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ์‹œ + return null; + } + const created = await prisma.mission.create({ // ๋ฏธ์…˜ ์ƒ์„ฑ + data: { // ์ƒ์„ฑํ•  ๋ฐ์ดํ„ฐ ๊ฐ์ฒด + ...data, // ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌ ๋ฐ›์€ data ๊ฐ์ฒด์˜ ๋ชจ๋“  ์†์„ฑ์„ ๋ณต์‚ฌํ•œ๋‹ค. + restaurant: { + connect: { id: data.restaurant } // restaurant ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„ ์—ฐ๊ฒฐ + } + } + }); + return created.id; // ์ƒ์„ฑ๋œ ๋ฏธ์…˜ ID ๋ฐ˜ํ™˜ + } + + // ๋ฏธ์…˜ ID๋กœ ๋ฏธ์…˜ ์กฐํšŒ + export const getMission = async(missionId) => { + const mission = await prisma.mission.findFirstOrThrow({ + select: { + id: true, + restaurant: true, + name: true, + introduction: true, + deadline: true, + points: true, + status: true + }, + where: { id: missionId }}); + + const formattedMission = { + ...mission, + id: mission.id.toString(), + points: mission.points.toString(), + restaurant: { + id: mission.restaurant.id.toString(), + name: mission.restaurant.name, + }, + }; + console.log(formattedMission); + return formattedMission; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฏธ์…˜ ๋“ฑ๋ก](images/๋ฏธ์…˜-1-3-๋ฏธ์…˜_๋“ฑ๋ก.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น์— ๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‹๋‹น](images/๋ฏธ์…˜-1-3-์กด์žฌํ•˜์ง€์•Š๋Š”_์‹๋‹น.png) + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ID(100)์˜ ์‹๋‹น์— ๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. + - ์ด๋ฏธ ๋“ฑ๋ก๋œ ๋ฏธ์…˜์„ ๋“ฑ๋กํ•  ๊ฒฝ์šฐ
+ ![์ค‘๋ณต๋œ ๋ฏธ์…˜](images/๋ฏธ์…˜-1-3-์ค‘๋ณต๋œ_๋ฏธ์…˜.png) + - ๊ฐ™์€ ์‹๋‹น, ๋ฏธ์…˜๋ช…, ๋ฏธ์…˜ ์„ค๋ช…์˜ ๋ฏธ์…˜์„ ์ค‘๋ณต ๋“ฑ๋กํ•  ์ˆ˜ ์—†๊ฒŒ ํ•˜์˜€๋‹ค. + 4. ๊ฐ€๊ฒŒ์˜ ๋ฏธ์…˜์„ ๋„์ „ ์ค‘์ธ ๋ฏธ์…˜์— ์ถ”๊ฐ€ํ•˜๊ธฐ API + - Repository ํ•จ์ˆ˜ ์ˆ˜์ • + ```javascript + // ํŠน์ • ๋ฏธ์…˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ(์ง„ํ–‰ X -> ์ง„ํ–‰ ์ค‘) + export const updateMissionStatus = async(missionId) => { + // ์—…๋ฐ์ดํŠธํ•  ๋ฏธ์…˜์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const mission = await prisma.mission.findFirst({ + where: { + id: missionId + } + }); + // ํ•ด๋‹น ๋ฏธ์…˜์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด status ์„ ํƒ + const missionStatus = await prisma.mission.findFirst({ + select: { + status: true + }, + where: { + id: missionId + } + }); + // ํ•ด๋‹น ๋ฏธ์…˜์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ƒํƒœ๊ฐ€ ์ง„ํ–‰ X๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ + if (mission == null || missionStatus.status != 0){ + return null; + } + const missionUpdated = await prisma.mission.update({ + where: { + id: missionId + }, + data: { + status: 1 // status ๊ฐ’์„ 1(์ง„ํ–‰ ์ค‘)๋กœ ๋ณ€๊ฒฝ + }, + select: { + id: true, + restaurant: true, + name: true, + introduction: true, + deadline: true, + points: true, + status: true, + } + }); + const formattedMission = { + ...missionUpdated, + id: missionUpdated.id.toString(), + points: missionUpdated.points.toString(), + restaurant: { + id: missionUpdated.restaurant.id.toString(), + name: missionUpdated.restaurant.name + }, + }; + + return formattedMission; + } + ``` + - ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์ „(์ง„ํ–‰ X)์˜ ๋ฐ์ดํ„ฐ
+ ![์ง„ํ–‰ X์ธ ๋ฏธ์…˜](images/๋ฏธ์…˜-1-4-์ง„ํ–‰์ค‘_์—…๋ฐ์ดํŠธ์ „์˜_๋ฏธ์…˜.png) + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฏธ์…˜ ์ง„ํ–‰ ์ค‘์œผ๋กœ ์—…๋ฐ์ดํŠธ](images/๋ฏธ์…˜-1-4-๋ฏธ์…˜_์ง„ํ–‰์ค‘_์—…๋ฐ์ดํŠธ.png) + - ๋„์ „ํ•  ์ˆ˜ ์—†๋Š” ๋ฏธ์…˜(์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ด๊ฑฐ๋‚˜ ์™„๋ฃŒํ•œ ๋ฏธ์…˜)์˜ ๊ฒฝ์šฐ + ![๋„์ „ํ•  ์ˆ˜ ์—†๋Š” ๋ฏธ์…˜](images/๋ฏธ์…˜-1-4-๋„์ „ํ• ์ˆ˜์—†๋Š”_๋ฏธ์…˜.png) + - ๋ฐฉ๊ธˆ ์ „์— ์—…๋ฐ์ดํŠธ ์‹œํ‚จ ๋ฏธ์…˜(์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ธ ์ƒํƒœ)์„ ๋˜ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์•˜๋‹ค. + - ๋ฏธ์…˜์˜ ์ƒํƒœ(status)๊ฐ€ 0(์ง„ํ–‰ํ•˜๊ธฐ ์ „)์ด ์•„๋‹ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. + - ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฏธ์…˜์„ ์—…๋ฐ์ดํŠธํ•  ๊ฒฝ์šฐ + ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฏธ์…˜](images/๋ฏธ์…˜-1-4-์กด์žฌํ•˜์ง€์•Š๋Š”_๋ฏธ์…˜.png) + - ์—…๋ฐ์ดํŠธ ํ•˜๋ ค๋Š” ๋ฏธ์…˜์˜ ID๋กœ ์กฐํšŒํ–ˆ์„ ๋•Œ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. +2. ํŠน์ • ํšŒ์›์ด ์ž‘์„ฑํ•œ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํŠน์ • ํšŒ์›์˜ ๋ชจ๋“  ๋ฆฌ๋ทฐ ์กฐํšŒ + export const getAllMemberReviews = async(memberId, cursor) => { + const reviews = await prisma.review.findMany({ + select: { + id: true, + member: true, // ์ฐธ์กฐํ•˜๋Š” member ํ…Œ์ด๋ธ” + restaurant: true, // ์ฐธ์กฐํ•˜๋Š” restaurantn ํ…Œ์ด๋ธ” + rating: true, + content: true, + status: true + }, + where: { memberId: memberId, id: { gt: cursor }}, + // ๋ฆฌ๋ทฐ์˜ ID๊ฐ€ cursor๋ณด๋‹ค ํฐ ๋ ˆ์ฝ”๋“œ๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค. + // gt: "greater than", ๊ฐ’์ด cursor๋ณด๋‹ค ํฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•œ๋‹ค. (ํŽ˜์ด์ง• ๊ตฌํ˜„) + orderBy: { id: "asc"}, // ID ๊ธฐ์ค€ ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌ + take: 5, // 5๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ๋งŒ ์กฐํšŒ + }) + + // review ๊ฐ์ฒด์˜ ํ˜•๋ณ€ํ™˜ (BigInt ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•จ) + const formattedReviews = reviews.map(review => ({ // reviews(DB์—์„œ ์ถ”์ถœํ•œ ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ) ๋ฐฐ์—ด์„ map() ๋ฉ”์„œ๋“œ๋กœ ๊ฐ review ๊ฐ์ฒด ๋ณ€ํ™˜ + ...review, // review ๊ฐ์ฒด์˜ ๋ชจ๋“  ์†์„ฑ ๋ณต์‚ฌ + id: review.id.toString(), + // DB์˜ id ํ•„๋“œ๊ฐ€ BigInt ํƒ€์ž…์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ๋Š”๋ฐ + // javaScript์—์„  BigInt ํƒ€์ž…์€ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์—†์–ด + // BigInt ํƒ€์ž…์˜ id๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ์—ˆ๋‹ค. + member: { // ์ฐธ์กฐํ•˜๋Š” member ํ…Œ์ด๋ธ”์—์„œ ์ถ”์ถœํ•  ์†์„ฑ + id: review.member.id.toString(), + name: review.member.name, + nickname: review.member.nickname, + birth: review.member.birth, + gender: review.member.gender, + location: review.member.location, + phoneNumber: review.member.phoneNumber + }, + restaurant: { // ์ฐธ์กฐํ•˜๋Š” restaurant ํ…Œ์ด๋ธ”์—์„œ ์ถ”์ถœํ•  ์†์„ฑ + id: review.restaurant.id.toString(), + name: review.restaurant.name + }, + })); + + return formattedReviews; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![ํŠน์ • ํšŒ์›์˜ ๋ฆฌ๋ทฐ ๋ชฉ๋ก](images/๋ฏธ์…˜-2-ํŠน์ •ํšŒ์›-๋ฆฌ๋ทฐ๋ชฉ๋ก.png) +3. ํŠน์ • ์‹๋‹น์˜ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํŠน์ • ์‹๋‹น์˜ ๋ชจ๋“  ๋ฏธ์…˜ ์กฐํšŒ + export const getAllRestaurantMissions = async(restaurantId, cursor) => { + const missions = await prisma.mission.findMany({ + select: { + id: true, + restaurant: true, + points: true, + name: true, + introduction: true, + status: true + }, + where: { restaurantId: restaurantId, id: { gt: cursor }}, + orderBy: { id: "asc" }, + take: 5 + }) + const formattedMissions = missions.map(mission => ({ + ...mission, + id: mission.id.toString(), + points: mission.points.toString(), + restaurant: { + id: mission.restaurant.id.toString(), + name: mission.restaurant.name + }, + })); + + return formattedMissions; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![ํŠน์ • ์‹๋‹น์˜ ๋ฏธ์…˜ ๋ชฉ๋ก](images/๋ฏธ์…˜-3-ํŠน์ •๊ฐ€๊ฒŒ_๋ฏธ์…˜๋ชฉ๋ก.png) +4. ํŠน์ • ํšŒ์›์˜ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก ์กฐํšŒ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํŠน์ • ํšŒ์›์˜ ๋ชจ๋“  ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ์กฐํšŒ + export const getAllMemberMissions = async(memberId, cursor) => { + const memberMissions = await prisma.memberMission.findMany({ + select: { + id: true, + member: true, + mission: true, + }, + where: { + memberId: memberId, + mission: { + status: 1 // mission ๊ฐ์ฒด์˜ status๊ฐ€ 1(์ง„ํ–‰ ์ค‘)์ธ ๋ฏธ์…˜๋“ค๋งŒ ์กฐํšŒ์˜จ๋‹ค. + }, + id: {gt: cursor} + }, + orderBy: {id: "asc"}, + take: 5 + }) + + const formattedMemberMissions = memberMissions.map(memberMission => ({ + ...memberMission, + id: memberMission.id.toString(), + member: { + id: memberMission.member.id.toString(), + nickname: memberMission.member.nickname, + }, + mission: { + id: memberMission.mission.id.toString(), + restaurantId: memberMission.mission.restaurantId.toString(), + name: memberMission.mission.name, + introduction: memberMission.mission.introduction, + points: memberMission.mission.points.toString(), + deadline: memberMission.mission.deadline, + status: memberMission.mission.status, + + }, + })); + + return formattedMemberMissions; + } + ``` + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![ํŠน์ • ํšŒ์›์˜ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜ ๋ชฉ๋ก](images/๋ฏธ์…˜-4-ํŠน์ •ํšŒ์›_์ง„ํ–‰์ค‘์ธ_๋ฏธ์…˜๋ชฉ๋ก.png) +5. ํŠน์ • ํšŒ์›์˜ ์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜์„ ์ง„ํ–‰ ์™„๋ฃŒ๋กœ ๋ฐ”๊พธ๊ธฐ API + - Repository ํ•จ์ˆ˜ + ```javascript + // ํŠน์ • ํšŒ์›์˜ ํŠน์ • ๋ฏธ์…˜์˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ(์ง„ํ–‰ ์ค‘ -> ์ง„ํ–‰ ์™„๋ฃŒ) + export const updateMissionCompleted = async(memberId, missionId) => { + // ํŠน์ • ํšŒ์›์— ์ฃผ์–ด์ง„ ํŠน์ • ๋ฏธ์…˜์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ + const confirmMemberMission = await prisma.memberMission.findFirst({ + where: { + missionId: missionId, + memberId: memberId + } + }) + + // ํ•ด๋‹น ๋ฏธ์…˜์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด status ์„ ํƒ + const missionStatus = await prisma.mission.findFirst({ + select: { + status: true + }, + where: { + id: missionId + } + }) + + // ํ•ด๋‹น ๋ฏธ์…˜์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋ฏธ์…˜์˜ ์ƒํƒœ๊ฐ€ 1(์ง„ํ–‰ ์ค‘)์ด ์•„๋‹ ๊ฒฝ์šฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ + if (confirmMemberMission == null || missionStatus.status != 1){ + return null; + } + + const memberMission = await prisma.memberMission.update({ + where: { + // update ๋ฉ”์„œ๋“œ๋Š” ์ง€์ •ํ•œ Unique key๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ๊ธฐ ๋•Œ๋ฌธ์— + // memberId, missionId๋กœ ์ด๋ฃจ์–ด์ง„ ๋ณตํ•ฉ ๊ณ ์œ  ํ‚ค์ธ memberId_missionId_unique์„ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค. + memberId_missionId_unique: { + memberId: memberId, + missionId: missionId + } + }, + data: { // ์ˆ˜์ •ํ•  ๋‚ด์šฉ ์ •์˜ + mission:{ + update:{ // mission์˜ ํŠน์ • ์†์„ฑ ์—…๋ฐ์ดํŠธ + status: 2 // ์ง„ํ–‰ ์™„๋ฃŒ๋กœ ์—…๋ฐ์ดํŠธ + } + }}, + select: { // ๋ฐ˜ํ™˜ํ•  ํŠน์ • ์†์„ฑ ์ง€์ • + id: true, + member: true, + mission: true, + } + }) + + const formattedMemberMissions = { + ...memberMission, + id: memberMission.id.toString(), + member: { + id: memberMission.member.id.toString(), + nickname: memberMission.member.nickname, + }, + mission: { + id: memberMission.mission.id.toString(), + restaurantId: memberMission.mission.restaurantId.toString(), + name: memberMission.mission.name, + introduction: memberMission.mission.introduction, + points: memberMission.mission.points.toString(), + deadline: memberMission.mission.deadline, + status: memberMission.mission.status, + }, + }; + + return formattedMemberMissions; + } + ``` + - ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์ „(์ง„ํ–‰ ์ค‘)์˜ ๋ฐ์ดํ„ฐ
+ ![์ง„ํ–‰ ์ค‘์ธ ๋ฏธ์…˜](images/๋ฏธ์…˜-5-์ง„ํ–‰์™„๋ฃŒ_์—…๋ฐ์ดํŠธ์ „์˜_๋ฏธ์…˜.png) + - ๋ฏธ์…˜ 1-4์—์„œ ์ง„ํ–‰ ์ค‘์œผ๋กœ ๋ฐ”๊พผ ๋ฏธ์…˜์„ ์ง„ํ–‰ ์™„๋ฃŒ๋กœ ์—…๋ฐ์ดํŠธ ํ•ด๋ณด๊ฒ ๋‹ค. + - ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
+ ![๋ฏธ์…˜ ์™„๋ฃŒ๋กœ ์—…๋ฐ์ดํŠธ](images/๋ฏธ์…˜-5-๋ฏธ์…˜_์ง„ํ–‰์™„๋ฃŒ_์—…๋ฐ์ดํŠธ.png) + - ์™„๋ฃŒํ•  ์ˆ˜ ์—†๋Š” ๋ฏธ์…˜(์ง„ํ–‰ํ•˜์ง€๋„ ์•Š์•˜๊ฑฐ๋‚˜ ์ด๋ฏธ ์™„๋ฃŒํ•œ ๋ฏธ์…˜)์˜ ๊ฒฝ์šฐ + ![์™„๋ฃŒํ•  ์ˆ˜ ์—†๋Š” ๋ฏธ์…˜](images/๋ฏธ์…˜-5-์™„๋ฃŒํ• ์ˆ˜์—†๋Š”_๋ฏธ์…˜.png) + - ๋ฐฉ๊ธˆ ์ „์— ์—…๋ฐ์ดํŠธ ์‹œํ‚จ ๋ฏธ์…˜(์ด๋ฏธ ์ง„ํ–‰์™„๋ฃŒ์ธ ์ƒํƒœ)์„ ๋˜ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์•˜๋‹ค. + - ๋ฏธ์…˜์˜ ์ƒํƒœ(status)๊ฐ€ 1(์ง„ํ–‰ ์ค‘)์ด ์•„๋‹ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. + - ํŠน์ • ํšŒ์›์—๊ฒŒ ํ• ๋‹น๋˜์ง€ ์•Š์€ ๋ฏธ์…˜์„ ์—…๋ฐ์ดํŠธํ•  ๊ฒฝ์šฐ + ![์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฏธ์…˜](images/๋ฏธ์…˜-5-์กด์žฌํ•˜์ง€์•Š๋Š”_๋ฏธ์…˜.png) + - ํšŒ์›์˜ ID ๋ฐ ๋ฏธ์…˜์˜ ID๋กœ ์กฐํšŒํ–ˆ์„ ๋•Œ ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—” ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผฐ๋‹ค. \ No newline at end of file