# πŸ“‹ λΆ€μ„œ 관리 API λ¬Έμ„œ ## κ°œμš” νšŒμ›κ°€μž… μ‹œ λΆ€μ„œ 선택을 μœ„ν•œ 동적 λΆ€μ„œ λͺ©λ‘ APIμž…λ‹ˆλ‹€. --- ## API μ—”λ“œν¬μΈνŠΈ ### GET `/api/departments` λΆ€μ„œ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€. **인증**: λΆˆν•„μš” (곡개 API) **μš”μ²­**: ```http GET /api/departments HTTP/1.1 Host: localhost:5000 ``` **응닡 (성곡)**: ```json { "success": true, "departments": [ { "id": 1, "code": "SPC", "name": "μ‹ ν‰μ°¨λŸ‰", "description": "μ‹ ν‰μ°¨λŸ‰μ‚¬μ—…μ†Œ", "is_active": true }, { "id": 2, "code": "NPC", "name": "λ…Έν¬μ°¨λŸ‰", "description": "λ…Έν¬μ°¨λŸ‰μ‚¬μ—…μ†Œ", "is_active": true }, { "id": 3, "code": "VHD", "name": "μ°¨λŸ‰μ²˜", "description": "μ°¨λŸ‰μ²˜", "is_active": true } ] } ``` **응닡 (μ‹€νŒ¨)**: ```json { "success": false, "error": "였λ₯˜ λ©”μ‹œμ§€" } ``` --- ## ν”„λ‘ νŠΈμ—”λ“œ μ‚¬μš©λ²• ### Vue.js (TWA ν”„λ‘ νŠΈμ—”λ“œ) `SignupView.vue`μ—μ„œ λΆ€μ„œ λͺ©λ‘ λ‘œλ“œ: ```typescript // λΆ€μ„œ λͺ©λ‘ κ°€μ Έμ˜€κΈ° async function fetchDepartments() { loadingDepartments.value = true try { const response = await fetch('/api/departments') if (!response.ok) { throw new Error('λΆ€μ„œ λͺ©λ‘μ„ κ°€μ Έμ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.') } const data = await response.json() departments.value = data.departments || [] } catch (error) { console.error('λΆ€μ„œ λͺ©λ‘ λ‘œλ”© μ‹€νŒ¨:', error) errorMessage.value = 'λΆ€μ„œ λͺ©λ‘μ„ λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.' } finally { loadingDepartments.value = false } } // μ»΄ν¬λ„ŒνŠΈ 마운트 μ‹œ λ‘œλ“œ onMounted(() => { fetchDepartments() }) ``` ### HTML ν…œν”Œλ¦Ώ μ‚¬μš© ```html ``` --- ## λ°μ΄ν„°λ² μ΄μŠ€ ꡬ쑰 ### departments ν…Œμ΄λΈ” ```sql CREATE TABLE public.departments ( id SERIAL PRIMARY KEY, code VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); ``` ### 초기 데이터 ```sql INSERT INTO public.departments (code, name, description) VALUES ('SPC', 'μ‹ ν‰μ°¨λŸ‰', 'μ‹ ν‰μ°¨λŸ‰μ‚¬μ—…μ†Œ'), ('NPC', 'λ…Έν¬μ°¨λŸ‰', 'λ…Έν¬μ°¨λŸ‰μ‚¬μ—…μ†Œ'), ('VHD', 'μ°¨λŸ‰μ²˜', 'μ°¨λŸ‰μ²˜') ON CONFLICT (code) DO NOTHING; ``` --- ## λΆ€μ„œ μΆ”κ°€ 방법 ### 1. Supabase SQL μ—λ””ν„°μ—μ„œ 직접 μΆ”κ°€ ```sql INSERT INTO public.departments (code, name, description) VALUES ('NEW001', 'μƒˆλ‘œμš΄λΆ€μ„œ', 'λΆ€μ„œ μ„€λͺ…'); ``` ### 2. κ΄€λ¦¬μž νŽ˜μ΄μ§€ (ν–₯ν›„ κ΅¬ν˜„) κ΄€λ¦¬μž κΆŒν•œμ΄ μžˆλŠ” μ‚¬μš©μžκ°€ μ›Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 λΆ€μ„œλ₯Ό μΆ”κ°€/μˆ˜μ •/μ‚­μ œν•  수 μžˆμŠ΅λ‹ˆλ‹€. --- ## 필터링 ### ν™œμ„±ν™”λœ λΆ€μ„œλ§Œ 쑰회 ν˜„μž¬ APIλŠ” λͺ¨λ“  λΆ€μ„œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. λΉ„ν™œμ„±ν™”λœ λΆ€μ„œλ₯Ό μ œμ™Έν•˜λ €λ©΄: ```python @app.route("/api/departments") def api_departments(): """λΆ€μ„œ λͺ©λ‘ 쑰회 API (ν™œμ„±ν™”λœ λΆ€μ„œλ§Œ)""" try: with build_pg_client() as c: r = c.get( "/departments", params={ "select": "id,code,name,description,is_active", "is_active": "eq.true", # ν™œμ„±ν™”λœ λΆ€μ„œλ§Œ "order": "name.asc" } ) r.raise_for_status() departments = r.json() or [] return { "success": True, "departments": departments } except Exception as e: return { "success": False, "error": str(e) }, 500 ``` --- ## μ—λŸ¬ 처리 ### ν”„λ‘ νŠΈμ—”λ“œ μ—λŸ¬ 처리 ```typescript try { const response = await fetch('/api/departments') if (!response.ok) { throw new Error('λΆ€μ„œ λͺ©λ‘μ„ κ°€μ Έμ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.') } const data = await response.json() if (!data.success) { throw new Error(data.error || 'μ•Œ 수 μ—†λŠ” 였λ₯˜') } departments.value = data.departments || [] } catch (error) { console.error('λΆ€μ„œ λͺ©λ‘ λ‘œλ”© μ‹€νŒ¨:', error) errorMessage.value = 'λΆ€μ„œ λͺ©λ‘μ„ λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.' setTimeout(() => errorMessage.value = '', 3000) } ``` --- ## μ„±λŠ₯ μ΅œμ ν™” ### 캐싱 λΆ€μ„œ λͺ©λ‘μ€ 자주 λ³€κ²½λ˜μ§€ μ•ŠμœΌλ―€λ‘œ 캐싱을 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€: ```typescript // κ°„λ‹¨ν•œ λ©”λͺ¨λ¦¬ μΊμ‹œ (5λΆ„) let cachedDepartments: Department[] | null = null let cacheTime: number | null = null const CACHE_DURATION = 5 * 60 * 1000 // 5λΆ„ async function fetchDepartments() { const now = Date.now() // μΊμ‹œκ°€ μœ νš¨ν•œ 경우 if (cachedDepartments && cacheTime && now - cacheTime < CACHE_DURATION) { departments.value = cachedDepartments return } // API 호좜 loadingDepartments.value = true try { const response = await fetch('/api/departments') const data = await response.json() if (data.success) { cachedDepartments = data.departments cacheTime = now departments.value = data.departments } } finally { loadingDepartments.value = false } } ``` --- ## ν…ŒμŠ€νŠΈ ### cURL둜 ν…ŒμŠ€νŠΈ ```bash curl -X GET http://localhost:5000/api/departments ``` ### λΈŒλΌμš°μ € μ½˜μ†”μ—μ„œ ν…ŒμŠ€νŠΈ ```javascript fetch('/api/departments') .then(res => res.json()) .then(data => console.log(data)) ``` --- ## 문제 ν•΄κ²° ### λΆ€μ„œ λͺ©λ‘μ΄ λΉ„μ–΄μžˆμŒ **원인**: `departments` ν…Œμ΄λΈ”μ— 데이터가 μ—†μŒ **ν•΄κ²°**: ```sql SELECT * FROM public.departments; -- 데이터가 μ—†μœΌλ©΄ 초기 데이터 μ‚½μž… INSERT INTO public.departments (code, name, description) VALUES ('SPC', 'μ‹ ν‰μ°¨λŸ‰', 'μ‹ ν‰μ°¨λŸ‰μ‚¬μ—…μ†Œ'), ('NPC', 'λ…Έν¬μ°¨λŸ‰', 'λ…Έν¬μ°¨λŸ‰μ‚¬μ—…μ†Œ'), ('VHD', 'μ°¨λŸ‰μ²˜', 'μ°¨λŸ‰μ²˜') ON CONFLICT (code) DO NOTHING; ``` ### API 호좜 μ‹€νŒ¨ **원인**: Flask μ„œλ²„κ°€ μ‹€ν–‰λ˜μ§€ μ•ŠμŒ λ˜λŠ” CORS 였λ₯˜ **ν•΄κ²°**: ```bash # μ„œλ²„ μ‹€ν–‰ 확인 python app.py # CORS μ„€μ • 확인 (app.py) CORS(app, origins=["*"], supports_credentials=True) ``` --- **뢀산ꡐ톡곡사 μ°¨λŸ‰μ²˜** **1ν˜Έμ„  κ³ μž₯μ½”λ“œ μ‹œμŠ€ν…œ**