Skip to content

JungyoKim/CMS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

8 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CMS (Client Management System)

고객, 계약, μ œν’ˆ, νŽŒμ›¨μ–΄(ν”„λ‘œν† μ½œ), AS 이λ ₯을 톡합 κ΄€λ¦¬ν•˜λŠ” 사내 CMS μ‹œμŠ€ν…œμž…λ‹ˆλ‹€.

기술 μŠ€νƒ

λΆ„λ₯˜ 기술
ν”„λ ˆμž„μ›Œν¬ SvelteKit 2 + Svelte 5
μ–Έμ–΄ TypeScript
λ°μ΄ν„°λ² μ΄μŠ€ SQLite (better-sqlite3)
ORM Drizzle ORM
인증 JWT (jose 라이브러리, HS256)
UI shadcn-svelte (bits-ui 기반), Tailwind CSS 4, Tabler Icons
ν…Œμ΄λΈ” TanStack Table
차트 LayerChart (D3 기반)
ν…ŒμŠ€νŠΈ Vitest (λ‹¨μœ„/톡합), Playwright (E2E)
CI/CD GitHub Actions (ν…ŒμŠ€νŠΈ β†’ Docker λΉŒλ“œ β†’ ghcr.io 배포)
배포 Docker (Node.js 20 Alpine)

ν”„λ‘œμ νŠΈ ꡬ쑰

src/
β”œβ”€β”€ hooks.server.ts              # 인증 미듀웨어 (JWT 검증, HTTPS λ¦¬λ‹€μ΄λ ‰νŠΈ)
β”œβ”€β”€ service-worker.ts            # PWA μ„œλΉ„μŠ€ μ›Œμ»€
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ server/
β”‚   β”‚   β”œβ”€β”€ db/
β”‚   β”‚   β”‚   β”œβ”€β”€ index.ts         # DB μ—°κ²° (WAL λͺ¨λ“œ, FK ν™œμ„±ν™”)
β”‚   β”‚   β”‚   └── schema.ts        # Drizzle ν…Œμ΄λΈ” μŠ€ν‚€λ§ˆ μ •μ˜
β”‚   β”‚   β”œβ”€β”€ auth.ts              # JWT 토큰 생성/검증
β”‚   β”‚   β”œβ”€β”€ file-storage.ts      # 파일 μ—…λ‘œλ“œ/λ‹€μš΄λ‘œλ“œ/μ‚­μ œ
β”‚   β”‚   └── crud-helpers.ts      # 곡톡 CRUD 헬퍼 (soft delete, 파일 처리)
β”‚   └── components/
β”‚       β”œβ”€β”€ clients-table.svelte     # 고객 관리 ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ contracts-table.svelte   # 계약 관리 ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ products-table.svelte    # μ œν’ˆ 관리 ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ firmware-table.svelte    # νŽŒμ›¨μ–΄ 관리 ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ as-table.svelte          # AS(진행쀑) ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ completed-as-table.svelte # AS(μ™„λ£Œ) ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ incomplete-as-table.svelte # AS(λ―Έμ™„λ£Œ) ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ pre-sales-table.svelte   # μ‚¬μ „μ˜μ—… ν…Œμ΄λΈ”
β”‚       β”œβ”€β”€ home-contract-dialog.svelte  # ν™ˆ 계약 상세 λ‹€μ΄μ–Όλ‘œκ·Έ
β”‚       β”œβ”€β”€ home-as-dialog.svelte    # ν™ˆ AS 상세 λ‹€μ΄μ–Όλ‘œκ·Έ
β”‚       β”œβ”€β”€ data-table/utils.ts      # μ „ν™”λ²ˆν˜Έ/이메일 ν¬λ§·νŒ…Β·κ²€μ¦ μœ ν‹Έ
β”‚       └── ui/
β”‚           β”œβ”€β”€ file-upload-field.svelte  # μž¬μ‚¬μš© 파일 μ—…λ‘œλ“œ μ»΄ν¬λ„ŒνŠΈ
β”‚           β”œβ”€β”€ contact-section.svelte    # μž¬μ‚¬μš© λ‹΄λ‹Ήμž 정보 폼
β”‚           └── ...                       # shadcn-svelte 기반 UI μ»΄ν¬λ„ŒνŠΈ
β”œβ”€β”€ routes/
β”‚   β”œβ”€β”€ (auth)/login/            # 둜그인 νŽ˜μ΄μ§€
β”‚   β”œβ”€β”€ (app)/
β”‚   β”‚   β”œβ”€β”€ +page.*              # ν™ˆ (λŒ€μ‹œλ³΄λ“œ)
β”‚   β”‚   β”œβ”€β”€ clients/             # 고객 관리
β”‚   β”‚   β”œβ”€β”€ contracts/           # 계약 관리
β”‚   β”‚   β”‚   └── contract-helpers.ts  # 계약 μ „μš© 헬퍼 (λ‹΄λ‹ΉμžΒ·ν•˜μœ„ 데이터 처리)
β”‚   β”‚   β”œβ”€β”€ products/            # μ œν’ˆ 관리
β”‚   β”‚   β”œβ”€β”€ firmware/            # νŽŒμ›¨μ–΄(ν”„λ‘œν† μ½œ) 관리
β”‚   β”‚   β”œβ”€β”€ as/                  # AS 관리
β”‚   β”‚   β”œβ”€β”€ settings/            # μ„€μ • (λΉ„λ°€λ²ˆν˜Έ λ³€κ²½)
β”‚   β”‚   └── logout/              # λ‘œκ·Έμ•„μ›ƒ
β”‚   β”œβ”€β”€ api/                     # REST API μ—”λ“œν¬μΈνŠΈ
β”‚   β”‚   β”œβ”€β”€ clients/             # 고객 API
β”‚   β”‚   β”œβ”€β”€ contracts/[id]/      # 계약 상세 API
β”‚   β”‚   β”œβ”€β”€ products/            # μ œν’ˆ API
β”‚   β”‚   β”œβ”€β”€ firmware/            # νŽŒμ›¨μ–΄ API
β”‚   β”‚   β”œβ”€β”€ as/                  # AS API
β”‚   β”‚   β”œβ”€β”€ files/               # 파일 λ‹€μš΄λ‘œλ“œ API
β”‚   β”‚   └── settings/            # μ„€μ • API
β”‚   └── healthcheck/             # ν—¬μŠ€μ²΄ν¬ μ—”λ“œν¬μΈνŠΈ
e2e/                                 # Playwright E2E ν…ŒμŠ€νŠΈ
β”œβ”€β”€ auth.test.ts                 # 인증 흐름 (둜그인/λ‘œκ·Έμ•„μ›ƒ)
β”œβ”€β”€ navigation.test.ts           # νŽ˜μ΄μ§€ λ„€λΉ„κ²Œμ΄μ…˜
β”œβ”€β”€ products.test.ts             # μ œν’ˆ CRUD
β”œβ”€β”€ clients.test.ts              # 고객 CRUD
└── settings.test.ts             # μ„€μ • νŽ˜μ΄μ§€
drizzle/
β”œβ”€β”€ 0000_*.sql ~ 0011_*.sql      # SQL λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일
└── meta/                        # Drizzle λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 메타데이터
scripts/
β”œβ”€β”€ build-docker-image.sh        # Docker 이미지 λΉŒλ“œ 슀크립트
└── docker-entrypoint.sh         # Docker μ—”νŠΈλ¦¬ν¬μΈνŠΈ (λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ β†’ μ•± μ‹œμž‘)

λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ

ν…Œμ΄λΈ” μ„€λͺ…
clients 고객사 (μ‚¬μ—…μžλ“±λ‘λ²ˆν˜Έ, μ£Όμ†Œ, λ‹΄λ‹Ήμž λ“±)
contracts 계약 (고객·발주처 μ—°κ²°, κΈˆμ•‘, κΈ°κ°„, μ„€μΉ˜μ •λ³΄ λ“±)
products μ œν’ˆ (κ΄€λ¦¬μ½”λ“œ, 단가, 버전)
protocols νŽŒμ›¨μ–΄/ν”„λ‘œν† μ½œ (버전, 파일)
files 파일 μ €μž₯μ†Œ (FILE_LIST_ID둜 엔티티와 μ—°κ²°)
repeaters 쀑계기 ID (계약에 쒅속)
install_products μ„€μΉ˜μ œν’ˆ (계약에 쒅속, μ œν’ˆΒ·ν”„λ‘œν† μ½œ μ°Έμ‘°)
rooms 객싀 ID (계약에 쒅속)
as_records AS 이λ ₯ (μš”μ²­/λŒ€μ‘ λ‚΄μš©, λΉ„μš©, μ™„λ£Œ μ—¬λΆ€)
product_inventory μ œν’ˆ μž…μΆœκ³  기둝
passwords 둜그인 λΉ„λ°€λ²ˆν˜Έ (scrypt ν•΄μ‹œ)
settings μ‹œμŠ€ν…œ μ„€μ • (key-value)

μ£Όμš” 섀계 νŒ¨ν„΄

Soft Deletion

λͺ¨λ“  μ£Όμš” μ—”ν‹°ν‹°λŠ” deleted_at μ»¬λŸΌμ„ μ‚¬μš©ν•œ μ†Œν”„νŠΈ μ‚­μ œλ₯Ό μ μš©ν•©λ‹ˆλ‹€. 데이터λ₯Ό 물리적으둜 μ‚­μ œν•˜μ§€ μ•Šκ³  μ‚­μ œ μ‹œκ°μ„ κΈ°λ‘ν•˜λ©°, 쑰회 μ‹œ deleted_at IS NULL 쑰건으둜 ν•„ν„°λ§ν•©λ‹ˆλ‹€.

파일 μ €μž₯

νŒŒμΌμ€ FILE_LIST_ID(UUID)λ₯Ό 톡해 엔티티와 μ—°κ²°λ©λ‹ˆλ‹€. ν•˜λ‚˜μ˜ FILE_LIST_ID에 μ—¬λŸ¬ 파일이 연결될 수 있으며, 물리 νŒŒμΌμ€ uploads/ 디렉토리에 μ €μž₯λ©λ‹ˆλ‹€.

인증

  • 단일 λΉ„λ°€λ²ˆν˜Έ 방식 (사내 μ‹œμŠ€ν…œμš©)
  • JWT 토큰 (HS256, 7일 만료)을 쿠킀에 μ €μž₯
  • hooks.server.tsμ—μ„œ λͺ¨λ“  μš”μ²­μ— λŒ€ν•΄ 인증 검증

μ—λŸ¬ λͺ¨λ‹ˆν„°λ§

hooks.server.ts의 handleErrorμ—μ„œ μ„œλ²„ μ—λŸ¬λ₯Ό νƒ€μž„μŠ€νƒ¬ν”„, κ²½λ‘œμ™€ ν•¨κ»˜ λ‘œκΉ…ν•©λ‹ˆλ‹€. 404λŠ” μ œμ™Έν•˜λ©°, 개발 ν™˜κ²½μ—μ„œλŠ” 상세 μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό, ν”„λ‘œλ•μ…˜μ—μ„œλŠ” 일반 λ©”μ‹œμ§€λ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ— λ°˜ν™˜ν•©λ‹ˆλ‹€.

HTTPS 슀마트 λ¦¬λ‹€μ΄λ ‰νŠΈ

ν”„λ‘œλ•μ…˜ ν™˜κ²½μ—μ„œ HTTP 접속 μ‹œ HTTPS μ„œλ²„μ˜ /healthcheck둜 ν”„λ‘œλΈŒλ₯Ό 보내 응닡이 μžˆμ„ λ•Œλ§Œ λ¦¬λ‹€μ΄λ ‰νŠΈν•©λ‹ˆλ‹€. HTTPSκ°€ λ‹€μš΄λ˜μ—ˆμ„ λ•ŒλŠ” HTTP둜 정상 접속 κ°€λŠ₯ν•©λ‹ˆλ‹€.

μ‹œμž‘ν•˜κΈ°

사전 μš”κ΅¬μ‚¬ν•­

  • Node.js 20 이상
  • npm

μ„€μΉ˜ 및 μ‹€ν–‰

# μ˜μ‘΄μ„± μ„€μΉ˜
npm install

# ν™˜κ²½λ³€μˆ˜ μ„€μ •
cp .env.example .env
# .env νŒŒμΌμ„ μ—΄μ–΄ 값을 μ„€μ •:
#   DATABASE_URL=local.db          # SQLite 파일 경둜
#   JWT_SECRET=your-secret-key     # JWT μ„œλͺ… ν‚€ (32자 이상 ꢌμž₯)
#   PRODUCTION_DOMAIN=             # ν”„λ‘œλ•μ…˜ 도메인 (HTTPS λ¦¬λ‹€μ΄λ ‰νŠΈμš©, 개발 μ‹œ λΉ„μ›Œλ‘ )

# 개발 μ„œλ²„ μ‹€ν–‰
npm run dev

개발 μ„œλ²„κ°€ http://localhost:5173μ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€.

초기 λΉ„λ°€λ²ˆν˜Έ μ„€μ •

처음 μ‹€ν–‰ μ‹œ passwords ν…Œμ΄λΈ”μ΄ λΉ„μ–΄μžˆμœΌλ―€λ‘œ λ‘œκ·ΈμΈν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ•„λž˜ 방법 쀑 ν•˜λ‚˜λ‘œ λΉ„λ°€λ²ˆν˜Έλ₯Ό μ„€μ •ν•˜μ„Έμš”:

  1. μ„€μ • νŽ˜μ΄μ§€ μ‚¬μš©: 이미 둜그인된 μƒνƒœμ—μ„œ /settingsμ—μ„œ λΉ„λ°€λ²ˆν˜Έλ₯Ό λ³€κ²½ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  2. DB 직접 μ ‘κ·Ό: 개발 ν™˜κ²½μ—μ„œ SQLite DB에 직접 scrypt ν•΄μ‹œ λΉ„λ°€λ²ˆν˜Έλ₯Ό μ‚½μž…ν•©λ‹ˆλ‹€.

λΉŒλ“œ

# ν”„λ‘œλ•μ…˜ λΉŒλ“œ
npm run build

# λΉŒλ“œ κ²°κ³Ό μ‹€ν–‰
npm run start

Docker 배포

이미지 λΉŒλ“œ

./scripts/build-docker-image.sh
# λ˜λŠ”
docker build -t cms:latest .

μ‹€ν–‰

docker run --rm -p 3000:3000 \
  -v ./data:/app/data \
  -v ./uploads:/app/uploads \
  -e DATABASE_URL=/app/data/sqlite.db \
  -e JWT_SECRET=your-secret-key \
  cms:latest

λ³Όλ₯¨ 마운트

μ»¨ν…Œμ΄λ„ˆ 경둜 μš©λ„
/app/data SQLite λ°μ΄ν„°λ² μ΄μŠ€ 파일
/app/uploads μ—…λ‘œλ“œλœ 파일 μ €μž₯μ†Œ

Docker μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ μ‹œ docker-entrypoint.shκ°€ μžλ™μœΌλ‘œ Drizzle λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ„ μ‹€ν–‰ν•œ λ’€ 앱을 μ‹œμž‘ν•©λ‹ˆλ‹€.

Synology NAS 배포

Container Managerμ—μ„œ μœ„μ™€ λ™μΌν•œ μ„€μ •μœΌλ‘œ μ»¨ν…Œμ΄λ„ˆλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. λ³Όλ₯¨ 마운트 경둜λ₯Ό NAS의 곡유 폴더에 맞게 μ‘°μ •ν•˜μ„Έμš”.

ν™˜κ²½λ³€μˆ˜

λ³€μˆ˜ ν•„μˆ˜ μ„€λͺ… κΈ°λ³Έκ°’
DATABASE_URL O SQLite 파일 경둜 -
JWT_SECRET O JWT μ„œλͺ… ν‚€ -
PRODUCTION_DOMAIN X ν”„λ‘œλ•μ…˜ 도메인 (HTTPS λ¦¬λ‹€μ΄λ ‰νŠΈμš©) ''
BODY_SIZE_LIMIT X μ—…λ‘œλ“œ 파일 크기 μ œν•œ (λ°”μ΄νŠΈ) 524288
PORT X μ„œλ²„ 포트 3000

슀크립트

λͺ…λ Ή μ„€λͺ…
npm run dev 개발 μ„œλ²„ μ‹€ν–‰
npm run build ν”„λ‘œλ•μ…˜ λΉŒλ“œ
npm run start λΉŒλ“œλœ μ•± μ‹€ν–‰
npm run check Svelte/TypeScript νƒ€μž… 검사
npm run lint ESLint + Prettier 검사
npm run format Prettier μžλ™ 포맷
npm run test Vitest λ‹¨μœ„/톡합 ν…ŒμŠ€νŠΈ
npm run test:e2e Playwright E2E ν…ŒμŠ€νŠΈ
npm run test:all λ‹¨μœ„ + E2E 전체 ν…ŒμŠ€νŠΈ
npm run db:generate μŠ€ν‚€λ§ˆ λ³€κ²½μ—μ„œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ SQL 생성
npm run db:migrate λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용
npm run db:push μŠ€ν‚€λ§ˆλ₯Ό DB에 직접 ν‘Έμ‹œ (개발용)
npm run db:studio Drizzle Studio (DB λΈŒλΌμš°μ €)

ν…ŒμŠ€νŠΈ

λ‹¨μœ„/톡합 ν…ŒμŠ€νŠΈ (Vitest)

μ„œλ²„ μœ ν‹Έλ¦¬ν‹°, API 라우트, νŽ˜μ΄μ§€ μ•‘μ…˜, ν΄λΌμ΄μ–ΈνŠΈ μœ ν‹Έλ¦¬ν‹° λ“± 201개 ν…ŒμŠ€νŠΈλ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.

npm test

μ£Όμš” ν…ŒμŠ€νŠΈ 파일:

  • src/lib/server/*.test.ts β€” 인증, λΉ„λ°€λ²ˆν˜Έ, 파일 μ €μž₯, CRUD 헬퍼
  • src/routes/**/server.test.ts β€” API μ—”λ“œν¬μΈνŠΈ 및 νŽ˜μ΄μ§€ μ•‘μ…˜
  • src/lib/components/data-table/*.test.ts β€” 폼/파일 μœ ν‹Έλ¦¬ν‹°
  • src/hooks.server.test.ts β€” 인증 미듀웨어

E2E ν…ŒμŠ€νŠΈ (Playwright)

Playwright둜 λΈŒλΌμš°μ € 기반 E2E ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. μ‹€ν–‰ μ „ 앱을 λΉŒλ“œν•˜κ³  프리뷰 μ„œλ²„λ₯Ό μžλ™ μ‹œμž‘ν•©λ‹ˆλ‹€.

# Playwright λΈŒλΌμš°μ € μ„€μΉ˜ (졜초 1회)
npx playwright install chromium

# E2E ν…ŒμŠ€νŠΈ μ‹€ν–‰
npm run test:e2e

CI/CD

GitHub Actions둜 main λΈŒλžœμΉ˜μ— push μ‹œ μžλ™ μ‹€ν–‰λ©λ‹ˆλ‹€.

  1. ν…ŒμŠ€νŠΈ β€” npm test둜 λ‹¨μœ„/톡합 ν…ŒμŠ€νŠΈ μ‹€ν–‰
  2. λΉŒλ“œ 및 배포 β€” ν…ŒμŠ€νŠΈ 톡과 μ‹œ Docker 이미지 λΉŒλ“œ ν›„ ghcr.io에 ν‘Έμ‹œ

μ›Œν¬ν”Œλ‘œ 파일: .github/workflows/docker-publish.yml

λ§ˆμ΄κ·Έλ ˆμ΄μ…˜

Drizzle ORM의 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μ‹œμŠ€ν…œμ„ μ‚¬μš©ν•©λ‹ˆλ‹€.

# μŠ€ν‚€λ§ˆ λ³€κ²½ ν›„ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일 생성
npm run db:generate

# λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용 (개발 ν™˜κ²½)
npm run db:migrate

# Drizzle Studio (DB λΈŒλΌμš°μ €)
npm run db:studio

Docker ν™˜κ²½μ—μ„œλŠ” μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ μ‹œ μžλ™μœΌλ‘œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜μ΄ μ‹€ν–‰λ©λ‹ˆλ‹€.

About

ClientManagementSystem | 2025

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors