9 ๋ถ„ ์†Œ์š”

๐Ÿ“‘ 2์ฐจ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ


์œ ํŠœ๋ธŒ ์˜์ƒ ๐Ÿ˜Ž


๋ณด๋Ÿฌ๊ฐ€๊ธฐ โฌ‡๏ธ

Github [BackEnd]




ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ๐ŸŽฅ

๊ด€์ฐฐ ์‚ฌ์ดํŠธ : NIKE

ํŒ€ ๋ช… : Just Do It

ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ : Wecode์—์„œ ๋ฐฐ์šด๋‚ด์šฉ์„ ํ™œ์šฉํ•˜์—ฌ ๊ด€์ฐฐํ•œ ์‚ฌ์ดํŠธ์˜ ๊ธฐ๋Šฅ๊ฐœ๋ฐœ ๋ฐ ๋ ˆ์ด์•„์›ƒ ๊ทธ๋ฆฌ๊ธฐ

์ง„ํ–‰๊ธฐ๊ฐ„ : 2021๋…„ 1์›” 17์ผ ~ 2022๋…„ 1์›” 29์ผ ( total : 12days )

ํ”„๋กœ์ ํŠธ ์ฐธ์—ฌ์ž : ์ด์ค€ํ˜, ๊น€์˜์šฑ, ์ด์ง„์›…, ํ™ฉํฌ์œค


- ๊ธฐ์ˆ ์Šคํƒ -

FrontEnd ๊ธฐ์ˆ  ์Šคํƒ
โญ• [React]โญ• [Router]โญ• [Sass]โญ• [Restful API]โญ• [Git & GitHub]



BackEnd ๊ธฐ์ˆ  ์Šคํƒ
โญ• [Node.js]โญ• [Express]โญ• [MySQL]โญ• [Bcrypt, JWT]โญ• [Prisma]โญ• [babel]โญ• [node-cron] โญ• [Jest]โญ• [supertest]

- Page & API -

์ด์ค€ํ˜
[back]- Draw API- Detail API- Cart API- Search API- MiddleWare

๊น€์˜์šฑ
[back]- List API- Filter API- Review API- KAKAO API- Member API

์ด์ง„์›…
[Front]- ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€- ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€- ๋Œ€๋ฌธ ํŽ˜์ด์ง€- ์นดํŠธ ํŽ˜์ด์ง€

ํ™ฉํฌ์œค
[Front]- ๋””ํ…Œ์ผ ํŽ˜์ด์ง€- snkrs ๋””ํ…Œ์ผ ํŽ˜์ด์ง€


๐Ÿง‘ ํšŒ๊ณ 


1์ฐจ ํ”„๋กœ์ ํŠธ๋Š” e-commerce์‚ฌ์ดํŠธ๋ฅผ ํด๋ก ํ•˜์˜€๊ธฐ์— 2์ฐจ ํ”„๋กœ์ ํŠธ๋Š” e-commerce์‚ฌ์ดํŠธ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํ”Œ๋žซํผ์„ ํ•˜๊ณ  ์‹ถ์—ˆ์ง€๋งŒ

์•„์‰ฝ๊ฒŒ๋„ ๋‚˜์ดํ‚ค๋ฅผ ๋ชจํ‹ฐ๋ธŒ๋กœํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ๋งก๊ฒŒ๋˜์—ˆ๋‹ค.

๊ทธ๋ž˜๋„ ๋‚˜์ดํ‚ค๋งŒ์˜ ๊ฐ์„ฑ์ด ์žˆ์œผ๋‹ˆ ์ƒ๊ด€์€ ์—†๋‹ค.

ํšŒ๊ณ ๋Š” ๊ธฐ์–ต์— ๋‚จ๋Š”๊ฒƒ ๋ช‡๊ฐ€์ง€๋งŒ ์“ฐ๋„๋ก ํ•˜๊ฒ ๋‹ค.


๐Ÿคฎ [ํšŒ๊ณ -1] DB ๋ฐ์ดํ„ฐ์ž‘์—…

์šฐ์„  ๋‚˜์ดํ‚ค์˜ ํŠน์ง•์€ ๋งŽ์€ ์ œํ’ˆ๋“ค, ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ œํ’ˆ๋“ค์„ ์„ธ๋ถ„ํ™”์‹œ์ผœ์ฃผ๋Š” ํ•„ํ„ฐ๊ธฐ๋Šฅ, ์ถ”์ฒจ๊ธฐ๋Šฅ ์ด๋ ‡๊ฒŒ 3๊ฐ€์ง€์ด๋‹ค.

๋‚˜๋ž‘ ์˜์šฑ๋‹˜์€ DB ๋ชจ๋ธ๋ง์ด ๋๋‚˜์ž๋งˆ์ž ๋‚˜์ดํ‚ค์˜ ํŠน์ง•์„ ์‚ด๋ฆฌ๊ธฐ ์œ„ํ•ด DB์— ๋“ค์–ด๊ฐˆ ์ œํ’ˆ๋“ค, ์ œํ’ˆ์‚ฌ์ง„๋“ค์„

์ˆ˜์ง‘ํ•˜๊ณ  ์ง‘์–ด๋„ฃ์—ˆ๋Š”๋ฐ.. ์ด 166๊ฐœ์˜ ์ œํ’ˆ๊ณผ x4 ๋งŒํผ์˜ ์‚ฌ์ง„์„ DB์— ๋„ฃ์—ˆ๋‹ค.

ํŠนํžˆ ๊ฐ ์ œํ’ˆ๋ณ„ ์‚ฌ์ด์ฆˆ์˜ ์ˆ˜๋Ÿ‰์„ ์ง‘์–ด๋„ฃ๋Š” ๊ณผ์ •์ด ๊ดด๋ž„ํ–ˆ๋Š”๋ฐ

const style_code = ['ACA', 'ACB', 'ACC', 'ACD', 'ACE'];
for (let j = 0; j < style_code.length; j++) {
  for (let k = 1; k < 4; k++) {
    for (let i = 1; i < 10; i++) {
      console.log(`("${style_code[j]}-000${k}",${i},80),`);
    }
  }
} // ์ •ํ™•ํžˆ ์ƒ๊ฐ์€ ์•ˆ๋‚˜์ง€๋งŒ ๋Œ€์ถฉ ์ด๋Ÿฌํ•œ ํ˜•์‹์ด์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

๋„์ €ํžˆ ์ˆ˜์ž‘์—…์œผ๋ก  ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ์•Œ๋ฆฌ๊ณ ์ฆ˜์„ ํ†ตํ•ด DB์— ๋„ฃ์—ˆ๋‹ค.

๋ช‡๋ช‡ ๋‹ค๋ฅธ์ž‘์—…๋“ค๋„ ์ˆ˜์ž‘์—…์ด ํž˜๋“ค๋‹ค ์‹ถ์„๋• ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ†ตํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ์ƒ์„ฑํ•˜์—ฌ DB์— ๋„ฃ์—ˆ๋‹ค.

์ „ํ˜€ ์ƒ๊ฐ์น˜๋„ ๋ชปํ•œ ๋ถ€๋ถ„์—์„œ ์‹œ๊ฐ„์„ ๋งŽ์ด ์žก์•„๋จน์–ด์„œ ์กฐ๊ธˆ ๋†€๋žฌ๋Š”๋ฐ,

์•ž์œผ๋กœ DB ๋ฐ์ดํ„ฐ์ž‘์—…์ด ๋งŽ์€ ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ์ƒ˜ํ”Œ๋กœ ๋ช‡๊ฐœ๋งŒ ๋„ฃ๊ณ 

๋ชจ๋“  API๊ฐ€ ์™„์„ฑ๋˜์–ด์กŒ์„๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ๋Š”๊ฒƒ์ด ํ˜„๋ช…ํ•œ ํŒ๋‹จ์ผ ๊ฒƒ ๊ฐ™๋‹ค.


๐Ÿ˜ [ํšŒ๊ณ -2] ์†Œํ†ต

๋ฐ์ดํ„ฐ์ž‘์—…, prisma๋กœ ์Šคํ‚ค๋งˆ ๋งŒ๋“ค๊ธฐ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ฒƒ๋“ค์„

vsCode์˜ Rive Share์™€ Zoom, Slack์„ ์ด์šฉํ•˜์—ฌ ์˜์šฑ๋‹˜๊ณผ ํ•จ๊ป˜ํ•˜๋‹ˆ ํฌ๋กœ์Šค์ฒดํ‚น์„ ํ†ตํ•ด ๋†“์น˜๋Š” ๊ฒƒ์ด ์—†์—ˆ๋‹ค.

(๋ฌผ๋ก  ๋‘˜๋‹ค ๋ชฐ๋ž๋˜๊ฒƒ์„ ์ œ์™ธํ•˜๊ณค)

์ด๋Ÿฌํ•œ ๊ณผ์ •๋•Œ๋ฌธ์ด์—ˆ์„๊นŒ? ์˜์šฑ๋‹˜๊ณผ ์ผ€๋ฏธ๊ฐ€ ์ž˜๋งž์•˜๊ณ  ์†Œํ†ต์— ๋Œ€ํ•œ ๋ถˆํŽธํ•จ?์€ ๋Š๋ผ์ง€ ๋ชปํ–ˆ๋‹ค.

๋˜ํ•œ ์˜์šฑ๋‹˜์ด ์ฑ…์ž„๊ฐ๋„ ๊ฐ–๊ณ  ํ”„๋กœ์ ํŠธ์— ์ž„ํ•˜๋Š”๊ฑธ ์•Œ๊ฒŒ๋˜์–ด ๋ฏฟ๊ณ  ๋งก๊ธธ ์ˆ˜ ์žˆ์—ˆ๊ณ 

์†”์งํžˆ ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„๋‚ด๋‚ด ๋ฐฑ์—”๋“œ ์ž‘์—…์€ ์žฌ๋ฐŒ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ front์™€์˜ ์†Œํ†ต์ด ๋ฌธ์ œ์˜€๋‹ค.

๋‘๋ช…์ด์„œ UI์ž‘์—…์„ ๋ชจ๋‘ ํ•˜๋ ค๊ณ  ํ•˜๋‹ค๋ณด๋‹ˆ ๋„ˆ๋ฌด ๋ฐ”์œ๊ฒƒ ๊ฐ™์•˜๊ณ 

๊ทธ ๋•Œ๋ฌธ์— ์ƒ๊ฐ๋ณด๋‹ค ์šฐ๋ฆฌ์™€์˜ ๊ต๋ฅ˜๊ฐ€ ์—†์—ˆ๋‹ค.

โ€˜API๋ฅผ ~~~ํ•œ ํ˜•ํƒœ๋กœ ๋ณด๋‚ด์คฌ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค.โ€™ ๋ผ๋Š” ๊ฒƒ ๋˜ํ•œ ์—†์—ˆ๊ธฐ์—

1์ฐจ ํ”„๋กœ์ ํŠธ๋•Œ front๋ฅผ ํ–ˆ๋˜ ๊ฒฝํ—˜์„ ์‚ด๋ ค front๊ฐ€ ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•œ ํ˜•ํƒœ๋กœ API๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  front๋ถ„๋“ค์ด โ€˜์–ด๋–ป๊ฒŒ ์š”์ฒญ์„ ๋‚ ๋ ค์•ผํ•˜๋Š”์ง€๋ฅผโ€™ ์•Œ๊ธฐ์œ„ํ•ด back์˜ code๋ฅผ ๋ณด๋ฉฐ

์‹œ๊ฐ„์„ ํ—ˆ๋น„ํ•˜๋Š”๊ฒƒ์„ ๋ง‰๊ธฐ์œ„ํ•ด ๊ฐ API๋งˆ๋‹ค ๋ช…์„ธ์„œ๋ฅผ ํŒŒ์ผ๋กœ ๋งŒ๋“ค์–ด์„œ Slack๋ฐฉ์—๋‹ค๊ฐ€ ๊ณต์œ ํ•˜์˜€๋‹ค.

nike-8


nike-5 nike-6 nike-7
(์ถ”์ฒจ API.md์˜ ๋‚ด์šฉ)


์œ„์™€๊ฐ™์ด API ๋ช…์„ธ์„œ๋ฅผ ๋งŒ๋“ค์–ด front์—๊ฒŒ ์ „๋‹ฌํ•˜๋‹ˆ

๋‚˜์˜ ์˜๋„๋ฅผ ์ƒ์„ธํžˆ ๋งํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ  ์ƒ๋Œ€๋ฐฉ ๋˜ํ•œ ๋น ๋ฅด๊ฒŒ ์ดํ•ดํ–ˆ๋‹ค.

ํ”„๋ก ํŠธ์ž…์žฅ์—์„œ APIํ˜•ํƒœ๋ฅผ ์š”์ฒญ์„ ๋‚ ๋ฆฌ์ง€ ์•Š์•„๋„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๊ณ 

back์„ ์‹ ๊ฒฝ์“ฐ์ง€์•Š๊ณ  ์ž‘์—…์— ๋ชฐ๋‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋˜์–ด ์ข‹์•˜๋‹ค๊ณ  ํ•œ๋‹ค.


๐Ÿค” [ํšŒ๊ณ -3] SQL์„ ์ข€ ๋” ๊ณต๋ถ€ํ•ด๋ณด์ž

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ SQL๋กœ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฒƒ์ด ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ Dao๋‹จ์—์„œ๋Š” ๊ธฐ์ดˆ์ ์ธ๊ฒƒ๋“ค๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ๋ชจ๋“  ๊ฐ€๊ณต์„ Service๋‹จ์—์„œ ์ฒ˜๋ฆฌํ•˜์˜€๋Š”๋ฐ

๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ์“ธ๋ฐ์—†๋Š” DB์˜ ์ ‘์†์ด ๋Š˜์—ˆ๊ณ  ์ฝ”๋“œ ๋˜ํ•œ ๊ฐ€๋…์„ฑ์ด ์ข‹์ง€ ์•Š์•˜๋‹ค.

์ด๋Ÿฌํ•œ ๋ถ€๋ถ„์„ ์ฝ”๋“œ๋ฆฌ๋ทฐ๋กœ ์ง€์ ๋ฐ›์•„ ๊ตฌ๊ธ€๋ง์„ ํ•˜๋‹ค๋ณด๋‹ˆ โ€˜์ฟผ๋ฆฌ๋ฌธ์œผ๋กœ ์ด๋Ÿฐ๊ฒƒ๋„ ํ•  ์ˆ˜ ์žˆ์–ด?โ€™ ๋ผ๋Š” ์ƒ๊ฐ๊ณผ ํ•จ๊ป˜ ๋‚˜์˜ ๋ฌด์ง€ํ•จ์„ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.

์ดˆ๊ธ‰์ˆ˜์ค€์ธ ๋‚˜์—๊ฒ ๊ทธ ๊นŠ์ด๋Š” ์•„๋“ํ–ˆ๊ธฐ์— ํ•œ๋™์•ˆ ์ •๋ณด์˜ ๋Šช์— ๋น ์ ธ ์•ฝ๊ฐ„์˜ ์ขŒ์ ˆ๊ฐ์— ๋น ์ ธ์žˆ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด์ œ ์‹œ์ž‘ํ•˜๋Š” ๋‹จ๊ณ„์ž„์„ ์ธ์ •ํ•˜์ž ๋งˆ์Œ์ด ์กฐ๊ธˆ ํŽธํ•ด์กŒ๊ณ  ๋งค์ผ ์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ๋˜๋Š” ์ฟผ๋ฆฌ๋ฌธ์„ ์ •๋ฆฌํ•˜๊ธฐ๋กœ ๋งˆ์Œ๋จน์—ˆ๋‹ค.


๐Ÿง‘โ€๐Ÿ’ป Backend (Just Do It)

์•„์‰ฝ๊ฒŒ๋„ 2์ฐจ ํ”„๋กœ์ ํŠธ ๋˜ํ•œ e-commerce์‚ฌ์ดํŠธ๋ฅผ ์ฝ”๋”ฉํ•˜๊ฒŒ ๋˜์–ด ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋งŽ์ด ์ถ”๊ฐ€ํ•˜์ง€๋Š” ๋ชปํ•˜์˜€๋‹ค.
ํ•˜์ง€๋งŒ 1์ฐจ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํ•˜์ง€ ๋ชปํ–ˆ๋˜ "์˜ˆ์™ธ์ฒ˜๋ฆฌ ๋ฐ HTTP STATUS CODE, Unit Test" ๋“ฑ์„ ์ ๊ทน ํ™œ์šฉํ•˜์—ฌ ์™„์„ฑ๋„ ๋†’์€ API๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ•˜์˜€๋‹ค.


- ์‚ฌ์ „ DB ๋ชจ๋ธ๋ง -

nike-1


๊ธฐ๋ณธ์ ์œผ๋กœ e-commerce์‚ฌ์ดํŠธ ํŠน์ง•์ธ products๊ฐ€ ์ค‘์‹ฌ์ด ๋˜์–ด ๋Œ€๋ถ€๋ถ„์˜ table์ด ์—ฐ๊ฒฐ๋˜์—ˆ๊ณ 

๋‚˜์ดํ‚ค๋Š” ํ•„ํ„ฐ๊ธฐ๋Šฅ์ด ์ƒ๋‹นํžˆ ๋งŽ๊ธฐ์— ๊ทธ์— ๋งž๊ฒŒ table์ด ๋‚˜๋ˆ ์ ธ ์žˆ๋Š”๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋‚˜์ดํ‚ค๋Š” ํ•œ๊ฐ€์ง€ ํŠน์ง•์ด ์žˆ๋Š”๋ฐ ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ์ถ”์ฒจ(Draw)๊ธฐ๋Šฅ์ด๋‹ค.

์ถ”์ฒจ์„ ํ•˜๊ธฐ ์œ„ํ•œ ์ƒํ’ˆ์€ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์—๋„ ๋“ค์–ด๊ฐ€์ง€ ์•Š์œผ๋ฉฐ ๋Œ“๊ธ€๋˜ํ•œ ๋‹ฌ ์ˆ˜ ์—†๊ธฐ์—

products๊ฐ€ ์•„๋‹Œ snkrs table์— ๋”ฐ๋กœ ๋‹ด์•„ ํŠน๋ณ„์ทจ๊ธ‰ํ•˜์˜€๋‹ค.

๋˜ํ•œ ์ถ”์ฒจ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ์œ„ํ•ด ์ถ”์ฒจ์„ ํ•œ ์‚ฌ๋žŒ๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” table์ธ snkrs_data

๋‹น์ฒจ๋œ ์‚ฌ๋žŒ๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” table์ธ snkrs_winners๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.



Detail API


models/productDao.js

const getProductData = async style_code => {
  return await prisma.$queryRaw`
    SELECT
      products.style_code,
      products.name,
      categories.name as category,
      product_colors.name as color,
      product_colors.color_hex as hex,
      product_genders.name as gender,
      normal_price,
      sale_rate,
      sale_price,
      is_member,
      sub_icon.name as shoes_type,
      sub_brand.name as brand,
      sub_clothes.name as clothes_type,
      sub_accessories.name as acc_type,
      (
        SELECT JSON_ARRAYAGG(JSON_OBJECT('url', product_img_urls.name, 'is_main', is_main))
        FROM product_img_urls
        JOIN products ON products.style_code = product_img_urls.style_code
        WHERE product_img_urls.style_code = ${style_code}
      ) AS img,
      (
        SELECT JSON_ARRAYAGG(JSON_OBJECT('size', product_sizes.name, 'quantity', product_with_sizes.quantity))
        FROM product_with_sizes
        JOIN products ON products.style_code = product_with_sizes.style_code
        JOIN product_sizes ON product_size_id = product_sizes.id
        WHERE product_with_sizes.style_code = ${style_code}
      ) AS info
    FROM
      products
    JOIN
      categories ON category_id = categories.id
    LEFT JOIN
      product_colors ON color_id = product_colors.id
    JOIN
      product_genders ON gender_id = product_genders.id
    LEFT JOIN
      sub_icon ON sub_icon_id = sub_icon.id
    LEFT JOIN
      sub_brand ON sub_brand_id = sub_brand.id
    LEFT JOIN
      sub_clothes ON sub_clothes_id = sub_clothes.id
    LEFT JOIN
      sub_accessories ON sub_accessories_id = sub_accessories.id
    WHERE
      products.style_code = ${style_code};
  `;
};

์ƒํ’ˆ์˜ ๋””ํ…Œ์ผ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ๋ฌธ์ด๋‹ค.

ํ•˜๋‚˜์˜ ์ œํ’ˆ์— 4๊ฐœ์”ฉ ์žˆ๋Š” ์ด๋ฏธ์ง€์™€ 10๊ฐœ์”ฉ ์žˆ๋Š” ์‚ฌ์ด์ฆˆ๋ฅผ โ€˜์–ด๋–ป๊ฒŒ ๊ฐ์ฒดํ™” ์‹œ์ผœ์„œ ๊ฐ€์ ธ์˜ฌ๊นŒ?โ€™์— ๋Œ€ํ•ด ๋งŽ์ด ๊ณ ๋ฏผํ–ˆ์—ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” GROUP_CONCAT๊ณผ CONCAT์œผ๋กœ [,]์œผ๋กœ ๋ฐฐ์—ด์˜ ๋ชจ์–‘์„ ๋งŒ๋“ค๊ณ  ๊ทธ๊ฒƒ์„ {,}๋กœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๋ ค๊ณ  ํ•˜์˜€๋‹ค.

์–ด์ฐŒ์ €์ฐŒ ๋งŒ๋“ค๊ธด ํ•˜์˜€์œผ๋‚˜ ์žฌํ™œ์šฉํ•˜๊ธฐ์—๋Š” ๋„ˆ๋ฌด๋‚˜ ์ฝ”๋“œ๊ฐ€ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ณ  ๋ณต์žกํ•ด๋ณด์˜€๋‹ค.

๊ทธ๋Ÿฌ๋˜์ค‘ JSON์„ ์‚ฌ์šฉํ•˜์—ฌ ์†์‰ฝ๊ฒŒ ๋ฐฐ์—ด๊ณผ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๊ตฌ๊ธ€๋ง์„ ํ†ตํ•ด ์•Œ์•„๋ƒˆ๋‹ค.!

JSON_OBJECT์œผ๋กœ ๊ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒดํ™” ์‹œํ‚ค๊ณ  JSON_ARRAYAGG์„ ํ†ตํ•ด ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค.

์ƒ๊ฐ๋ณด๋‹ค ์‚ฌ์šฉ๋ฒ•๋„ ๊ฐ„๋‹จํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฌผ๋„ ์ข‹์•„์„œ ์ž์ฃผ ์‚ฌ์šฉํ•  ๊ฒƒ ๊ฐ™๋‹ค.

{
    "message": "์„ฑ๊ณต",
    "data": {
        "style_code": "AAA-0001",
        "name": "๋‚˜์ดํ‚ค ์—์–ดํฌ์Šค 1 GORE-TEX",
        "category": "shoes",
        "color": "์ฃผํ™ฉ์ƒ‰",
        "hex": "#EB621D",
        "gender": "๋‚จ์„ฑ",
        "normal_price": 189000,
        "sale_rate": null,
        "sale_price": null,
        "is_member": 0,
        "shoes_type": "์—์–ด ํฌ์Šค 1",
        "brand": "๋‚˜์ดํ‚ค ์Šคํฌ์ธ ์›จ์–ด",
        "clothes_type": null,
        "acc_type": null,
        "img": [
            {
                "url": "/Images/Men/Shoes/Airforce/1/1-1.png",
                "is_main": 1
            },
            {
                "url": "/Images/Men/Shoes/Airforce/1/1-2.png",
                "is_main": 0
            },
            {
                "url": "/Images/Men/Shoes/Airforce/1/1-3.png",
                "is_main": 0
            },
            {
                "url": "/Images/Men/Shoes/Airforce/1/1-4.png",
                "is_main": 0
            }
        ],
        "info": [
            {
                "size": "220",
                "quantity": 80
            },
            {
                "size": "230",
                "quantity": 80
            },
            {
                "size": "240",
                "quantity": 80
            },

          ...

        ]
    }
}


Cart API


๋ชจ๋‘๋‹ค ์„ค๋ช…ํ•  ์ˆ˜ ์—†์œผ๋‹ˆ Cart API์˜ ํ•œ ๋ถ€๋ถ„์ธ Create ๋ถ€๋ถ„๋งŒ ๋ณด๋„๋ก ํ•œ๋‹ค.

middleWare/authorization.js

const authentication = async (req, res, next) => {
  try {
    const token = req.body.user_id;
    const validToken = verifyToken(token);

    if (validToken) {
      const [check] = await userDao.isExistUser(validToken.id[0].id);
      const isExsitUser = new IsExistItem(check, resultType, 404);
      isExsitUser.notExistErr('์กด์žฌํ•˜์ง€ ์•Š๋Š” UserId ์ž…๋‹ˆ๋‹ค.');
      req.body.user_id = validToken.id[0].id;
    } else {
      const err = new Error('ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
      err.status = 401;
      throw err;
    }
    next();
  } catch (err) {
    res.status(err.status || 500).send({ message: '์‹คํŒจ', err: err.message });
  }
};

ํด๋ผ์ด์–ธํŠธ๊ฐ€ user_id๋ฅผ ๊ฐ–๋Š” ํ† ํฐ์„ ๋‹ด์•„ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด

ํ† ํฐ์„ ํ™•์ธํ•˜์—ฌ ์œ ํšจํ•œ์ง€ ํ™•์ธํ•˜๊ณ , ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๋ฉด ์ธ์ฆ์—๋Ÿฌ์ธ 401์„ ๋„์šด๋‹ค.

๋˜ํ•œ ํ† ํฐ์€ ์œ ํšจํ•˜์ง€๋งŒ DB์— ์—†๋Š” user๋ผ๋ฉด ์ž์›์ด ์กด์žฌํ•˜์ง€์•Š๋Š”๋‹ค๋Š” ๋œป์ด๋ฏ€๋กœ 404์—๋Ÿฌ๋ฅผ ๋„์šด๋‹ค.

utils/err.js

class RequiredKeys {
  constructor(REQUIRED_KEYS) {
    this.REQUIRED_KEYS = REQUIRED_KEYS;
  }
  verify() {
    for (let key in this.REQUIRED_KEYS) {
      if (!this.REQUIRED_KEYS[key] && this.REQUIRED_KEYS[key] !== 0) {
        const err = new Error(`${key} ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.`);
        err.status = 400;
        throw err;
      }
    }
  }
}

class AffectedRow {...} // ์ƒ๋žต

class IsExistItem {...} // ์ƒ๋žต

export { RequiredKeys, AffectedRow, IsExistItem };

์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์—๋Ÿฌํ•จ์ˆ˜๋“ค์„ ์บก์Šํ™” ํ•˜์—ฌ ๊ด€๋ฆฌํ•˜์˜€๋‹ค.

ํ•˜๋‚˜๋กœ ํ•ฉ์น ๊นŒ ์ƒ๊ฐํ•˜์˜€์ง€๋งŒ ์ผ๋‹จ์€ ๊ฐ๊ฐ ๋‚˜๋ˆ ๋†“์•˜๋‹ค.

(๋„ˆ๋ฌด ๊ธธ์–ด์ง€๋ฏ€๋กœ ๋‚˜๋จธ์ง€๋Š” ์ƒ๋žต)

controllers/cartControllers.js

const createCart = async (req, res) => {
  try {
    const { style_code, user_id, size, quantity } = req.body;
    const REQUIRED_KEYS = { style_code, user_id, size, quantity };

    const keys = new RequiredKeys(REQUIRED_KEYS);
    keys.verify();

    const result = await cartServices.createCart(
      style_code,
      user_id,
      size,
      quantity
    );

    res.status(201).send({ message: '์„ฑ๊ณต', result });
  } catch (err) {
    res.status(err.status || 500).send({ message: '์‹คํŒจ', err: err.message });
  }
};

์ปจํŠธ๋กค๋Ÿฌ๋‹จ์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์— ํ•„์š”ํ•œ ๊ฐ’๋“ค์„ ์ œ๋Œ€๋กœ ๋„ฃ์—ˆ๋Š”์ง€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ณ 

์•„๋‹ˆ๋ผ๋ฉด 400์—๋Ÿฌ๋ฅผ ๋„์šฐ๋„๋ก ํ–ˆ๋‹ค.

์„ฑ๊ณต์‹œ์—๋Š” 201์ฝ”๋“œ๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ์—ˆ๋‹ค.

services/cartServices.js

import { cartDao, productDao } from '../models';
import { resultType } from '../type';
import { IsExistItem } from '../utils/err';

const createCart = async (style_code, user_id, size, quantity) => {
  const [check] = await cartDao.checkCart(style_code, user_id, size);
  const [checkSize] = await productDao.isExistSizes(style_code, size);
  const [checkStyleCode] = await productDao.isExistStyleCode(style_code);

  const isExistStyleCode = new IsExistItem(checkStyleCode, resultType, 404);
  isExistStyleCode.notExistErr('์œ ํšจํ•˜์ง€ ์•Š๋Š” styleCode ์ž…๋‹ˆ๋‹ค.');

  const isExistSize = new IsExistItem(checkSize, resultType, 404);
  isExistSize.notExistErr('์œ ํšจํ•˜์ง€ ์•Š๋Š” size ์ž…๋‹ˆ๋‹ค.');

  const isExistItem = new IsExistItem(check, resultType, 409);
  isExistItem.existErr('์ด๋ฏธ ํ’ˆ๋ชฉ์— ์žˆ์Šต๋‹ˆ๋‹ค.');

  await cartDao.createCart(style_code, user_id, size, quantity);

  const data = await cartDao.getCartList(user_id);

  return data;
};

์ด๋ฏธ Create๋œ ์ œํ’ˆ์ด๋ผ๋ฉด 409์—๋Ÿฌ๋ฅผ ๋„์šฐ๋„๋ก ํ•˜์˜€๋‹ค.

์„œ๋น„์Šค๋‹จ์—์„œ๋Š” ํ•œ๋ฒˆ๋” ์š”์ฒญ์— ๋Œ€ํ•œ ๊ฐ’์„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌํ•˜์—ฌ 404์—๋Ÿฌ๋ฅผ ๋„์šฐ๋„๋ก ํ–ˆ๋‹ค.

์‚ฌ์‹ค 404์—๋Ÿฌ๋“ค์„ ํ•œ๊ณณ์— ๋ชจ์•„ middleWare๋กœ ๋งŒ๋“œ๋Š”๊ฒŒ ๋” ์ข‹์„๊ฒƒ ๊ฐ™์•˜์ง€๋งŒ

ํŒ€์›๋“ค๊ณผ ์‚ฌ์ „ํ•ฉ์˜๊ฐ€ ๋˜์ง€์•Š์€ ์ƒํ™ฉ์—์„œ ๋…๋‹จ์ ์œผ๋กœ ํ•  ์ˆ˜๊ฐ€์—†์–ด ์ด๋Œ€๋กœ ๋‘์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์นดํŠธ์— Create๊ฐ€ ์„ฑ๊ณตํ–ˆ์„์‹œ

ํ”„๋ก ํŠธ์—์„œ ์นดํŠธ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” API๋ฅผ ๋˜ ์š”์ฒญํ•˜๋Š”๊ฒƒ์ด ๊ท€์ฐฎ์„์ˆ˜๋„(๋˜๋Š” ์–ด๋ ค์›€์ด) ์žˆ์„๊ฒƒ ๊ฐ™์•„

getCartList๋ฅผ return ํ•ด์ฃผ์—ˆ๋‹ค.

models/cartDao.js

import { PrismaClient } from '@prisma/client';
import { createType, updateType, deleteType } from '../type';
import { AffectedRow } from '../utils/err';

const prisma = new PrismaClient();

const createCart = async (style_code, user_id, size, quantity) => {
  await prisma.$queryRaw`
    INSERT INTO
      carts(
        style_code,
        user_id,
        size,
        quantity
      )
    VALUES
      (
        ${style_code},
        ${user_id},
        ${size},
        ${quantity}
      );
  `;

  const [row] = await prisma.$queryRaw`
    SELECT ROW_COUNT() as result;
  `;

  const newRow = new AffectedRow(row, createType, 409);
  newRow.result();

  return;
};

์‚ฌ์‹ค ์ด๋ถ€๋ถ„์„ ๋„ฃ์„์ง€ ๋ง์ง€ ๊ณ ๋ฏผํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ณด๋‹ค ์™„์„ฑ๋„ ๋†’์€ API๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด

Create๋œ ํ›„ Affected Row๊ฐ€ 1์ด๋ž€ ๊ฐ’์„ ์ œ๋Œ€๋กœ ๋ฑ‰์–ด๋‚ด๋Š”์ง€ ํ™•์ธ์„ ํ–ˆ๋‹ค.

๋งŒ์•ฝ ์ œ๋Œ€๋กœ row๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด 409์—๋Ÿฌ๋ฅผ ๋„์šฐ๋„๋ก ํ–ˆ๋‹ค.


์•„์ง ์ด๋ถ€๋ถ„์€ 50%๋ฐ–์— ์™„์„ฑ์ด ๋˜์ง€์•Š์•˜๋‹ค.

์–ด๋–ค ์ด์œ ์—์„œ๋“ ์ง€ ๋งŒ์•ฝ row๊ฐ€ 2๊ฐœ ์ƒ๊ฒผ์„ ์‹œ ์—๋Ÿฌ๋ฅผ ๋„์šฐ๊ฒ ์ง€๋งŒ

์ด๋ฏธ ๊ทธ ๊ฐ’์€ table์— ๋“ค์–ด๊ฐ”์„ ๊ฒƒ์ด๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ถ”ํ›„ TRANSACTION์„ ํ†ตํ•ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ๋˜๋ฉด ROLLBACK ์‹œํ‚ค๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ด๋‹ค.


Draw API


๋‚˜์ดํ‚ค์˜ ๊ฝƒ ์ถ”์ฒจ๊ธฐ๋Šฅ์ด๋‹ค.


nike-4


server.js

const lottoSchedule = async () => {
  const list = await snkrsDao.getSnkrsList();
  let isOpen = false;

  for (let i = 0; i < list.length; i++) {
    cron.schedule(`00 09 * * *`, async () => {
      isOpen = true;
      await snkrsDao.updataOpenClose(isOpen, list[i].style_code);
      console.log('์‹œ์ž‘');
    });

    cron.schedule(`${(i + 1) * 5} 30 09 * * *`, async () => {
      isOpen = false;
      await snkrsDao.updataOpenClose(isOpen, list[i].style_code);
      await snkrsServices.selectWinner(list[i].style_code);
      console.log('๋งˆ๊ฐ');
    });
  }
};


nike-2
nike-3

snkrs ์ œํ’ˆ๋“ค์€ ๋งค์ผ 09:00 ~ 09:30์— ์ถ”์ฒจ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฒ˜์Œ์—๋Š” mysql table์˜ ์นผ๋Ÿผ์˜ ๊ฐ’์„ ์–ด๋–ป๊ฒŒ ์‹œ๊ฐ„์œผ๋กœ ์ œ์–ด ํ•  ์ˆ˜ ์žˆ์„์ง€ ๋ง‰๋ง‰ํ•˜์˜€๋‹ค.

ํ•˜์ง€๋งŒ ๋‹คํ–‰ํžˆ๋„ ๊ตฌ๊ธ€๋งํ•ด๋ณด๋‹ˆ ์†Œ์Šค๋Š” ๋งŽ์•˜๋‹ค.

Event Scheduler, Cron ๋“ฑ ๋‹ค์–‘ํ•œ๊ฒƒ์ด ์žˆ์—ˆ์ง€๋งŒ

node์—์„œ ์ง€์›ํ•ด์ฃผ๋Š” node-cron์ด๋ž€๊ฒƒ์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ๋˜์–ด ์ด๊ฒƒ์„ ํ™œ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

์œ„์˜ ํ•จ์ˆ˜๋Š” ์ •ํ•ด์ง„ ์‹œ๊ฐ„์— snkrs ์ œํ’ˆ๋“ค์˜ ์ถ”์ฒจ์—ฌ๋ถ€๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ณ  ๋‹น์ฒจ์ž๋ฅผ ๋ฝ‘๊ฒŒํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

services/snkrsServices.js

const createUsersToLottoBox = async (user_id, style_code, size) => {
  const [snkrs] = await snkrsDao.getSnkrsData(style_code);
  const [checkStyleCode] = await snkrsDao.isExistStyleCode(style_code);
  const [checkSize] = await snkrsDao.isExistSizes(style_code, size);

  const isExistStyleCode = new IsExistItem(checkStyleCode, resultType, 404);
  isExistStyleCode.notExistErr('์œ ํšจํ•˜์ง€ ์•Š๋Š” styleCode ์ž…๋‹ˆ๋‹ค.');
  const isExistSize = new IsExistItem(checkSize, resultType, 404);
  isExistSize.notExistErr('์œ ํšจํ•˜์ง€ ์•Š๋Š” size ์ž…๋‹ˆ๋‹ค.');

  if (snkrs.is_open === statusType.OPEN) {
    const [check] = await snkrsDao.checkUserLottoBox(user_id, style_code);

    const isExistItem = new IsExistItem(check, resultType, 409);
    isExistItem.existErr('์ด๋ฏธ ์ถ”์ฒจ์„ ํ•˜์…จ์Šต๋‹ˆ๋‹ค.');

    await snkrsDao.addLottoBox(user_id, style_code, size);
    await snkrsDao.addWinnerBox(user_id, style_code, size);

    return;
  } else if (snkrs.is_open === statusType.CLOSE) {
    const err = new Error('์ถ”์ฒจ๊ธฐ๊ฐ„์ด ์•„๋‹™๋‹ˆ๋‹ค');
    err.status = 409;
    throw err;
  }
};

๋ฐ”๋กœ ์„œ๋น„์Šค๋‹จ์—์„œ ์„ค๋ช…์„ ํ•˜๊ฒ ๋‹ค.

์šฐ์„  ํ”„๋ก ํŠธ์—์„œ โ€˜์‘๋ชจํ•˜๊ธฐโ€™๋ฒ„ํŠผ์€ ์ œํ’ˆ์˜ is_open์ด๋ผ๋Š” ๊ฐ’์— ์˜ํ•ด active๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฐฑ์—์„œ๋„ is_open๊ฐ’์— ๋”ฐ๋ผ ์ถ”์ฒจ๊ฐ€๋Šฅ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•œ๋‹ค.

์ถ”์ฒจ์‹œ๊ฐ„์ด ๋˜์–ด ์‚ฌ์šฉ์ž๊ฐ€ โ€˜์‘๋ชจํ•˜๊ธฐโ€™๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์š”์ฒญ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•œ๋‹ค.

๊ฒ€์‚ฌ์— ์ด์ƒ์ด ์—†๋‹ค๋ฉด ,

addLottoBox, addWinnerBoxํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด DB์˜ snkrs_data, snkrs_winner ํ…Œ์ด๋ธ”์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•œ๋‹ค.

(Dao๋‹จ์—์„œ๋Š” Affected Row๋ฅผ ํ†ตํ•ด ์ œ๋Œ€๋กœ ์ƒ์„ฑ์ด ๋˜์—ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  ์•„๋‹ˆ๋ผ๋ฉด ์—๋Ÿฌ๋ฅผ ๋„์šฐ๋Š” ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.)

services/snkrsServices.js

const selectWinner = async style_code => {
  const [check] = await snkrsDao.checkUserWinnerBox(style_code);
  const [count] = await snkrsDao.getCount(style_code);
  const participants = await snkrsDao.getNumOfParticipants(style_code);

  if (check.result === resultType.EXIST) {
    const [winnerInfo] = await snkrsDao.selectWinner(style_code);

    await snkrsDao.updateCount(
      winnerInfo.style_code,
      count['MAX(count)'] + 1,
      participants.length
    );

    await snkrsDao.updateWinner(winnerInfo.style_code, winnerInfo.user_id);
    await snkrsDao.deleteLottoBox(style_code);

    return;
  } else {
    return;
  }
};

๋‹น์ฒจ์ž๋ฅผ ๋ฝ‘๋Š” ์ฝ”๋“œ์ด๋‹ค.

checkUserWinnerBoxํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด snkrs_winner์— ๋ฐ์ดํ„ฐ ์œ ๋ฌด๋ฅผ ํ™•์ธํ•˜๊ณ  ๋งŒ์•ฝ ์•„๋ฌด๊ฐ’๋„ ์—†๋‹ค๋ฉด

์•„๋ฌด๋„ ์‘๋ชจํ•˜์ง€ ์•Š์€ ๊ฒƒ์ด๋ฏ€๋กœ ๊ทธ๋ƒฅ return ๋œ๋‹ค.

ํ•œ๋ช…์ด์ƒ ์‘๋ชจ๋ฅผ ํ–ˆ์„์‹œ selectWinnerํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋žœ๋ค์œผ๋กœ 1๋ช…์„ ๋ฝ‘๋Š”๋‹ค.

๋‹น์ฒจ์ž๊ฐ€ ์ •ํ•ด์ง€๋ฉด updateCount๋ฅผ ํ†ตํ•ด ํ•ด๋‹นํšŒ์ฐจ์— ์‘๋ชจํ•œ ์‚ฌ๋žŒ๋งŒํผ๋งŒ n+1ํšŒ์ฐจ๋กœ ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ค€๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ๋‹น์ฒจ์ž๊ฐ€ ๋ฝ‘ํ˜”์œผ๋‹ˆ ์ž„์‹œ๋กœ ์‘๋ชจํ•œ ์‚ฌ๋žŒ๋“ค์„ ์ €์žฅํ–ˆ๋˜ snkrs_data ํ…Œ์ด๋ธ”์€ ์‚ญ์ œ์‹œ์ผœ์ค€๋‹ค.

(Dao๋‹จ์—์„œ๋Š” Affected Row๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ์ƒํ’ˆ์— ์ฐธ๊ฐ€ํ•œ ์ธ์›๋งŒํผ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œ์‹œ์ผฐ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.)



๋ณด๋Ÿฌ๊ฐ€๊ธฐ โฌ‡๏ธ

1์ฐจ ํ”„๋กœ์ ํŠธ ํšŒ๊ณ ๋ก

์—…๋ฐ์ดํŠธ: