ํ๊ธ๋ช ์ธ OPEN API ๊ธฐ์ ์ค๋ช ์
- -
๐ ํ๊ธ๋ช ์ธ OPEN API ๊ธฐ์ ์ค๋ช ์
๋ณธ ๋ฌธ์๋ ํ๊ธ๋ช ์ธ OPEN API ํ๋ก์ ํธ์ ์ ๋ฐ์ ์ธ ์์คํ ๊ตฌ์ฑ, ์ฌ์ฉ ๊ธฐ์ (๋ฒ์ ํฌํจ), ๋ชจ๋๋ณ ์ญํ , ์คํ ๋ฐฉ๋ฒ, ๋ฐฐํฌ ํ๊ฒฝ, ๊ด๋ จ ๋งํฌ ์ ๋ณด ๋ฐ CI/CD ํ์ดํ๋ผ์ธ ์ค์ ์ ๊ดํ ์ค๋ช ์์ ๋๋ค.
1. ๊ฐ์
api key๋ ๋ณต์กํ ์ธ์ฆ ํ์์์ด ํ๊ธ๋ก ๋ฒ์ญํ ๋ช ์ธ์ ๋ฐ์๋ณผ ์ ์๋ OPEN API ์๋น์ค ๊ตฌํ
2. ๊ด๋ จ ๋งํฌ ๐
์๋๋ ํ๋ก์ ํธ์ ๊ด๋ จ๋ ๋ค์ํ ๋งํฌ ์ ๋ณด์ ๋๋ค.
- API ๋ฌธ์: https://quote.aleph.kr/api-docs/
(API ๋ฌธ์ ํ์ด์ง) - ๊ด๋ฆฌ์ ํ์ด์ง: https://quote.aleph.kr/admin
(๊ด๋ฆฌ์ ์ ์ฉ ๋ช ์ธ ๊ด๋ฆฌ ํ์ด์ง) - ๊นํ๋ธ: https://github.com/Aleph-Kim/korean-quote
(ํ๋ก์ ํธ ์์ค ์ฝ๋ ํ์ด์ง)
3. ์์คํ ์ํคํ ์ฒ ๐๏ธ
3.1. ์์คํ ๊ตฌ์ฑ
[ํด๋ผ์ด์ธํธ (ํ๋ก ํธ์๋)]
• EJS ํ
ํ๋ฆฟ ์์ง: ๋์ HTML ํ์ด์ง ๋ ๋๋ง
• Tailwind CSS: ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ UI ์คํ์ผ๋ง
[์๋ฒ (๋ฐฑ์๋)]
• Express.js: RESTful API ์๋ํฌ์ธํธ ์ ๊ณต
• ๋ฏธ๋ค์จ์ด: ์์ฒญ ํ์ฑ, ์ฟ ํค ๋ฐ ์ธ์
๊ด๋ฆฌ, CORS ์ค์ ๋ฑ
[๋ฐ์ดํฐ๋ฒ ์ด์ค]
• PostgreSQL: ๋ช
์ธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ
• Sequelize ORM: ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ฒด ์งํฅ์ ์ธํฐ๋์
[๋ฐฐํฌ ๋ฐ ์ธํ๋ผ]
• Docker: ์ปจํ
์ด๋ ๊ธฐ๋ฐ ๋ฐฐํฌ ๊ด๋ฆฌ
• Nginx Proxy Manager: ํ๋ก์ ๊ด๋ฆฌ ๋ฐ SSL/TLS ์ธ์ฆ์ ๋ฐ๊ธ
• GCP (Google Cloud Platform): ํด๋ผ์ฐ๋ ์ธํ๋ผ ๋ฐฐํฌ
3.2. ๋ฐ์ดํฐ ํ๋ฆ ๐
- ์์ฒญ ์ฒ๋ฆฌ – ํด๋ผ์ด์ธํธ๊ฐ
/quote/random
๋ฑ API ์๋ํฌ์ธํธ๋ก HTTP ์์ฒญ์ ์ ์กํฉ๋๋ค. - ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ – Express ๋ผ์ฐํฐ์์ ์์ฒญ์ ์ฒ๋ฆฌํ๋ฉฐ, ํ์์ Sequelize๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํธ์์ฉํฉ๋๋ค.
- ์๋ต ์์ฑ – ์กฐํ๋ ๋ฐ์ดํฐ(๋๋ค ๋ช ์ธ ๋ฑ)๋ฅผ JSON ํ์ ๋๋ EJS ํ ํ๋ฆฟ์ ํตํด HTML๋ก ์๋ตํฉ๋๋ค.
- ์๋ฌ ์ฒ๋ฆฌ – ์๋ฌ ๋ฐ์ ์ ์ ์ ํ ์ํ ์ฝ๋์ ๋ฉ์์ง๋ก ํด๋ผ์ด์ธํธ์ ์๋ตํฉ๋๋ค.
4. ์ฌ์ฉ ๊ธฐ์ ๋ฐ ๋ฒ์ ๐ ๏ธ
4.1. ๋ฐฑ์๋
- Express.js – Node.js ๊ธฐ๋ฐ ์น ํ๋ ์์ํฌ, ๋ฒ์ :
^4.19.2
- EJS – ์๋ฒ ์ฌ์ด๋ ํ
ํ๋ฆฟ ์์ง, ๋ฒ์ :
^3.1.10
- body-parser – HTTP ์์ฒญ ๋ฐ์ดํฐ ํ์ฑ, ๋ฒ์ :
1.20.2
- cookie-parser – ์ฟ ํค ํ์ฑ ๋ฏธ๋ค์จ์ด, ๋ฒ์ :
^1.4.7
- express-session – ์ธ์
๊ด๋ฆฌ, ๋ฒ์ :
^1.18.1
- express-validator – ์์ฒญ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ, ๋ฒ์ :
^7.2.1
- multer – ํ์ผ ์
๋ก๋ ์ฒ๋ฆฌ, ๋ฒ์ :
^1.4.5-lts.1
- cors – CORS ์ค์ , ๋ฒ์ :
^2.8.5
4.2. ๋ฐ์ดํฐ๋ฒ ์ด์ค
- PostgreSQL – ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์คํ
, ๋ฒ์ :
^16.4
- Sequelize – ORM (Object Relational Mapping) ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ๋ฒ์ :
^6.37.3
4.3. ๊ธฐํ ๋๊ตฌ
- dotenv – ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ, ๋ฒ์ :
^16.4.5
- apidoc – API ๋ฌธ์ ์๋ ์์ฑ ๋๊ตฌ, ๋ฒ์ :
^1.2.0
- Tailwind CSS – ์ ํธ๋ฆฌํฐ ํด๋์ค ๊ธฐ๋ฐ CSS ํ๋ ์์ํฌ, ๋ฒ์ :
^3.4.17
- PostCSS – CSS ์ ์ฒ๋ฆฌ ๋๊ตฌ, ๋ฒ์ :
^8.5.1
- Autoprefixer – ์๋ ๋ฒค๋ ํ๋ฆฌํฝ์ค ์ถ๊ฐ ๋๊ตฌ, ๋ฒ์ :
^10.4.20
- nodemon – ํ์ผ ๋ณ๊ฒฝ ์ ์๋ ์๋ฒ ์ฌ์์ ๋๊ตฌ, ๋ฒ์ :
^3.1.4
4.4. ๋ฐฐํฌ ๋ฐ ์ธํ๋ผ ๊ด๋ จ ๋๊ตฌ
- Docker – ์ปจํ
์ด๋ ๊ธฐ๋ฐ ๋ฐฐํฌ ๊ด๋ฆฌ ๋๊ตฌ, ๋ฒ์ :
^26.1.4
- Nginx Proxy Manager – ํ๋ก์ ๊ด๋ฆฌ ๋ฐ SSL/TLS ์ธ์ฆ์ ๋ฐ๊ธ ๋๊ตฌ, ๋ฒ์ :
^2.11.3
- GCP (Google Cloud Platform) – ํด๋ผ์ฐ๋ ์ธํ๋ผ ์๋น์ค
5. ๋ชจ๋ ๊ตฌ์ฑ ๋ฐ ์ฃผ์ ๊ธฐ๋ฅ ๐ฆ
5.1. ์๋ฒ ์ํธ๋ฆฌ ํฌ์ธํธ
- ํ์ผ:
src/app.js
- ์ญํ : Express ์ ํ๋ฆฌ์ผ์ด์ ์ธ์คํด์ค ์์ฑ, ๋ฏธ๋ค์จ์ด ์ค์ ( body-parser, cookie-parser, cors, session ๋ฑ ), ๋ผ์ฐํฐ ๋ฐ API ์๋ํฌ์ธํธ ์ฐ๊ฒฐ, ์๋ฌ ํธ๋ค๋ง ๋ฏธ๋ค์จ์ด ์ค์ , ์๋ฒ ํฌํธ ๋ฆฌ์ค๋ ์์
5.2. ๋ผ์ฐํฐ ๋ชจ๋
- ๊ฒฝ๋ก:
src/routes/
- ์ฃผ์ ๊ธฐ๋ฅ:
• ๋๋ค ๋ช ์ธ ์กฐํ API:GET /quote/random
• ์ค๋์ ๋ช ์ธ ์กฐํ API:GET /quote/today
• ๊ด๋ฆฌ์ํ์ด์ง:GET /admin
5.3. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ
- Sequelize ๋ชจ๋ธ:
• Quote ๋ชจ๋ธ – ์์ฑ:id
,body
(๋ช ์ธ ๋ด์ฉ),author
(๋จ๊ธด์ด),createdAt
,updatedAt
๋ฑ
• ๋ชจ๋ธ ๊ฐ ๊ด๊ณ ๋ฐ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ ์ค์
5.4. ๋ทฐ ํ ํ๋ฆฟ
- EJS ํ
ํ๋ฆฟ
• ๊ฒฝ๋ก:views/
• ๋์ HTML ๋ ๋๋ง์ ํตํด ๋ช ์ธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์์๊ฒ ์ ๊ณต
6. ๋น๋ ๋ฐ ์คํ ํ๊ฒฝ ๐
6.1. ์์กด์ฑ ์ค์น
ํฐ๋ฏธ๋์์ npm install
๋ช
๋ น์ ์คํํ์ฌ ์์กด์ฑ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค.
6.2. ํ๊ฒฝ ๋ณ์ ์ค์
ํ๋ก์ ํธ ๋ฃจํธ์ .env ํ์ผ์ ์์ฑํ๊ณ , ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํฉ๋๋ค:
PORT=3000
DATABASE_URL=postgres://username:password@localhost:5432/database_name
SESSION_SECRET=your_secret_key
6.3. ์คํ ๋ช ๋ น์ด
- ๊ฐ๋ฐ ๋ชจ๋ ์คํ (nodemon ์ฌ์ฉ):
npm run dev
- ํ๋ก๋์
๋ชจ๋ ์คํ:
npm start
- Tailwind CSS ๋น๋ (์ค์๊ฐ ๊ฐ์):
npm run css
- Tailwind CSS ์ ์ ๋น๋:
npm run build:css
- API ๋ฌธ์ ์์ฑ (apidoc ์ฌ์ฉ):
npm run api
7. ๋ฐฐํฌ ๋ฐ ์ธํ๋ผ ์ค์ โ๏ธ
7.1. Docker ๊ธฐ๋ฐ ๋ฐฐํฌ
Docker Compose ํ์ผ (docker-compose.yml)
version: '3.8'
services:
prod:
build:
context: .
dockerfile: Dockerfile.prod
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
dev:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
์ฃผ์ ๋ด์ฉ
- ๋ฒ์ : Compose ํ์ผ ๋ฒ์ 3.8 ์ฌ์ฉ
- services: prod์ dev ๋ ๊ฐ์ง ์๋น์ค๋ก ๊ตฌ์ฑ
- prod ์๋น์ค:
- ํ์ฌ ๋๋ ํ ๋ฆฌ๋ฅผ ๋น๋ ์ปจํ
์คํธ๋ก ์ฌ์ฉํ๋ฉฐ,
Dockerfile.prod
๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋น๋ - ํฌํธ 3000์ ํธ์คํธ์ ์ปจํ ์ด๋ ๊ฐ ๋งคํ
- ์์ค์ฝ๋์ node_modules ๋๋ ํ ๋ฆฌ๋ฅผ ๋ณผ๋ฅจ์ผ๋ก ์ฐ๊ฒฐํ์ฌ ์ค์๊ฐ ๋๊ธฐํ
- NODE_ENV ํ๊ฒฝ ๋ณ์๋ฅผ development๋ก ์ค์
- ํ์ฌ ๋๋ ํ ๋ฆฌ๋ฅผ ๋น๋ ์ปจํ
์คํธ๋ก ์ฌ์ฉํ๋ฉฐ,
- dev ์๋น์ค: prod์ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ค์ ํ์ง๋ง, ๋ณ๋์
Dockerfile.dev
๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ฐ ํ๊ฒฝ์ ๋ง๋ ์ค์ ๊ฐ๋ฅ
- prod ์๋น์ค:
Dockerfile.prod
# ๋ฒ ์ด์ค ์ด๋ฏธ์ง ์ค์
FROM node:20
# ์์
๋๋ ํ ๋ฆฌ ์ค์
WORKDIR /app
# ํจํค์ง ์ค์น๋ฅผ ์ํด package.json ๋ฐ package-lock.json ๋ณต์ฌ
COPY package*.json ./
# ํจํค์ง ์ค์น (production ๋ชจ๋)
RUN npm install --production
# ์ดํ๋ฆฌ์ผ์ด์
์ฝ๋ ๋ณต์ฌ
COPY . .
# Tailwind CSS ๋น๋
RUN npm run build:css
# ์ดํ๋ฆฌ์ผ์ด์
์ด ์คํ๋ ํฌํธ ์ค์
EXPOSE 3000
# ์๋ฒ ์คํ
CMD [ "npm", "run", "start" ]
์ฃผ์ ๋ด์ฉ
- ๋ฒ ์ด์ค ์ด๋ฏธ์ง: node:20 ์ฌ์ฉ
- ์์
๋๋ ํ ๋ฆฌ:
/app
์ผ๋ก ์ค์ - ํจํค์ง ์ค์น: package.json ๋ฐ package-lock.json ํ์ผ์ ๋ณต์ฌ ํ, production ๋ชจ๋๋ก ์์กด์ฑ ์ค์น
- ์ฝ๋ ๋ณต์ฌ: ์ ์ฒด ์์ค ์ฝ๋๋ฅผ ์ปจํ
์ด๋ ๋ด
/app
๋๋ ํ ๋ฆฌ๋ก ๋ณต์ฌ - Tailwind CSS ๋น๋:
npm run build:css
๋ฅผ ํตํด CSS ๋น๋ ์ํ - ํฌํธ ๋ ธ์ถ: 3000๋ฒ ํฌํธ๋ฅผ ์ธ๋ถ์ ๋ ธ์ถ
- ์คํ ๋ช
๋ น์ด:
npm run start
๋ก ์๋ฒ ์คํ
7.2. Nginx Proxy Manager ์ค์
- ์ญํ : ๋๋ฉ์ธ ํ๋ก์ ์ค์ , SSL ์ธ์ฆ์ ๊ด๋ฆฌ, Docker ์ปจํ ์ด๋๋ก ๋ฐฐํฌ๋ API ์๋ฒ ์ ๊ทผ ์ ์ด
- ์ค์ ์์: API ์๋ฒ ๋ด๋ถ ์ฃผ์(์:
http://localhost:3000
)๋ฅผ ํ๋ก์ ๋์์ผ๋ก ๋ฑ๋กํ๊ณ , HTTPS ๋ฐ ํ์ํ ํฌํธ ํฌ์๋ฉ ์ค์
7.3. GCP ๋ฐฐํฌ
- ํ์ฉ ๋ฐฉ๋ฒ: Compute Engine, Kubernetes Engine ๋๋ Cloud Run์ ํตํด ์ปจํ ์ด๋ ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ๋ฐฐํฌ
- Docker ์ด๋ฏธ์ง๋ฅผ ๋น๋ ํ GCP Container Registry ๋๋ Artifact Registry์ ์ ๋ก๋, ๋ฐฐํฌ ์งํ
- Nginx Proxy Manager์ ์ฐ๋ํ์ฌ ๋๋ฉ์ธ ๊ด๋ฆฌ ๋ฐ SSL ์ธ์ฆ์ ์ ์ฉ
8. ์๋ฌ ์ฒ๋ฆฌ ๋ฐ ๋ก๊น ๐
8.1. ์๋ฌ ์ฒ๋ฆฌ
- 400 Bad Request – ์์ฒญ ๋ฐ์ดํฐ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ ๊ฒฝ์ฐ, ์ํ ์ฝ๋ 400๊ณผ ์๋ฌ ๋ฉ์์ง ๋ฐํ
- 404 Not Found – ์กด์ฌํ์ง ์๋ ์๋ํฌ์ธํธ ์ ๊ทผ ์, ์ํ ์ฝ๋ 404์ ์๋ฌ ๋ฉ์์ง ๋ฐํ
- 500 Internal Server Error – ์๋ฒ ๋ด๋ถ ์ค๋ฅ ๋ฐ์ ์, ์ํ ์ฝ๋ 500๊ณผ ์๋ฌ ๋ฉ์์ง ๋ฐํ ๋ฐ ๋ก๊น ์ ํตํด ์์ธ ๋ถ์
8.2. ๋ก๊น
- ์๋ฒ ๋ก๊ทธ๋ ์ฝ์ ์ถ๋ ฅ ๋ฐ ํ์์ ๋ฐ๋ผ ํ์ผ๋ก ์ ์ฅ
- ์๋ฌ ๋ฐ์ ์ ์คํ ํธ๋ ์ด์ค๋ฅผ ํจ๊ป ๊ธฐ๋กํ์ฌ ๋๋ฒ๊น ์ ํ์ฉ
9. ๋ณด์ ๊ณ ๋ ค ์ฌํญ ๐
- ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ: ๋ฏผ๊ฐํ ์ ๋ณด๋ .env ํ์ผ๋ก ๊ด๋ฆฌํ๊ณ , ๋ฒ์ ๊ด๋ฆฌ ์์คํ
์์ ์ ์ธ(
.gitignore
์ค์ ) - ์ธ์
๊ด๋ฆฌ:
express-session
์ ์ฌ์ฉํ์ฌ ์ธ์ ์ ์์ ํ๊ฒ ๊ด๋ฆฌํ๋ฉฐ,SESSION_SECRET
์ ๋ฐ๋์ ์์ ํ ๊ฐ์ผ๋ก ์ค์ - CORS ์ ์ฑ : ํ์ฉ๋ ๋๋ฉ์ธ๋ง ์ ๊ทผํ๋๋ก CORS ๋ฏธ๋ค์จ์ด ์ค์
- ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฆ:
express-validator
๋ฅผ ํตํด ์ ๋ ฅ ๋ฐ์ดํฐ ๊ฒ์ฆ์ ์ํํ์ฌ SQL Injection ๋ฐ XSS ๊ณต๊ฒฉ ๋๋น
10. CI/CD Pipeline (GitHub Actions) ๐
ํ๋ก์ ํธ๋ GitHub Actions๋ฅผ ํ์ฉํ์ฌ CI/CD ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๊ณ ์์ต๋๋ค.
ํ์ดํ๋ผ์ธ์ master ๋ธ๋์น์ ๋ํ push ๋๋ pull request ์ด๋ฒคํธ ๋ฐ์ ์ ์คํ๋๋ฉฐ, Docker ์ด๋ฏธ์ง๋ฅผ ๋น๋ํ์ฌ Docker Hub์ ์
๋ก๋ ํ, SSH๋ฅผ ํตํด ์๊ฒฉ ์๋ฒ์ ๋ฐฐํฌํฉ๋๋ค.
name: CI/CD Pipeline
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker image
run: |
docker build . -f Dockerfile.prod -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }}
docker tag ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
- name: Push Docker image to Docker Hub
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:${{ github.sha }}
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
sudo docker stop ${{ secrets.PROJECT_NAME }} || true
sudo docker rm ${{ secrets.PROJECT_NAME }} || true
sudo docker run -d -p 3000:3000 --name ${{ secrets.PROJECT_NAME }} --env-file ${{ secrets.ENV_PATH }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROJECT_NAME }}:latest
์ฃผ์ ๋ด์ฉ
- ํธ๋ฆฌ๊ฑฐ: push ๋ฐ pull_request ์ด๋ฒคํธ๊ฐ master ๋ธ๋์น์์ ๋ฐ์ํ๋ฉด ํ์ดํ๋ผ์ธ ์คํ
- ๋น๋ ๋จ๊ณ: Repository ์ฒดํฌ์์, Docker Buildx ์ค์ , Docker Hub ๋ก๊ทธ์ธ, Dockerfile.prod๋ฅผ ์ฌ์ฉํ ์ด๋ฏธ์ง ๋น๋ ๋ฐ ํ๊ทธ ์์ฑ, Docker Hub๋ก ์ด๋ฏธ์ง ์ ๋ก๋
- ๋ฐฐํฌ ๋จ๊ณ: ๋น๋ ์๋ฃ ํ SSH๋ก ์๊ฒฉ ์๋ฒ ์ ์, ์ต์ ์ด๋ฏธ์ง pull, ๊ธฐ์กด ์ปจํ ์ด๋ ์ค์ง ๋ฐ ์ญ์ ํ ์ ์ปจํ ์ด๋ ์คํ (ํฌํธ 3000 ๋งคํ ๋ฐ ํ๊ฒฝ ๋ณ์ ํ์ผ ์ ์ฉ)
11. ๋ง๋ฌด๋ฆฌ ๐
์ถ๊ฐ ๋ฌธ์๋ ๊ฐ์ ์์ฒญ์ด ์์ผ์๋ฉด ์ธ์ ๋ ์ง ์ฐ๋ฝ ๋ถํ๋๋ฆฝ๋๋ค.
๊ฐ๋ฐ์ ์ด๋ฉ์ผ : dktjdej@naver.com
๊ฐ์ฌํฉ๋๋ค! ๐
์์คํ ๊ณต๊ฐ ๊ฐ์ฌํฉ๋๋ค