# ๐Ÿ“‹ Weekly Report System SI ํ”„๋กœ์ ํŠธ ํŒ€์„ ์œ„ํ•œ ์ฃผ๊ฐ„์—…๋ฌด๋ณด๊ณ  ์ž‘์„ฑ ๋ฐ ์ทจํ•ฉ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ## โœจ ์ฃผ์š” ๊ธฐ๋Šฅ ### ๐Ÿ“ ์ฃผ๊ฐ„๋ณด๊ณ  ์ž‘์„ฑ - ํ”„๋กœ์ ํŠธ๋ณ„ ๊ธˆ์ฃผ ์‹ค์  / ์ฐจ์ฃผ ๊ณ„ํš ์ž‘์„ฑ - Task ๋‹จ์œ„๋กœ ์ž‘์—… ๋‚ด์šฉ, ์‹œ๊ฐ„, ์™„๋ฃŒ ์—ฌ๋ถ€ ๊ด€๋ฆฌ - ์ด์Šˆ/๋ฆฌ์Šคํฌ, ํœด๊ฐ€์ผ์ •, ๊ธฐํƒ€์‚ฌํ•ญ ์ž…๋ ฅ - ์ž‘์„ฑ โ†’ ์ œ์ถœ โ†’ ์ทจํ•ฉ ์›Œํฌํ”Œ๋กœ์šฐ - **์ด์ „ ์ฃผ ๊ณ„ํš โ†’ ์ด๋ฒˆ ์ฃผ ์‹ค์  ์ž๋™ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ** - **๋ฏธ๋ž˜ ์ฃผ์ฐจ ์ž‘์„ฑ ์ฐจ๋‹จ** (URL ์ง์ ‘ ์ ‘๊ทผ ๋ฐฉ์ง€) ### ๐Ÿค– AI ๊ธฐ๋Šฅ - **AI ์ž๋™์ฑ„์šฐ๊ธฐ**: ํ…์ŠคํŠธ/์ด๋ฏธ์ง€ โ†’ ํ”„๋กœ์ ํŠธ๋ณ„ ์‹ค์ /๊ณ„ํš ์ž๋™ ๋ถ„๋ฅ˜ - ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ, ํŒŒ์ผ์„ ํƒ, **Ctrl+V ๋ถ™์—ฌ๋„ฃ๊ธฐ** ์ง€์› - OpenAI GPT-4o ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ ์ž๋™ ๋งค์นญ - **PMO AI ๋ฆฌ๋ทฐ**: ์ฃผ๊ฐ„๋ณด๊ณ  ํ’ˆ์งˆ ํ‰๊ฐ€ (3๋‹จ๊ณ„ ๋“ฑ๊ธ‰: ์šฐ์ˆ˜/์ ํ•ฉ/๋ฏธํก) - **๊ด€๋ฆฌ์ž ์ผ๊ด„๋“ฑ๋ก**: ์—ฌ๋Ÿฌ ์ง์›์˜ ์ฃผ๊ฐ„๋ณด๊ณ  ์ผ๊ด„ ํŒŒ์‹ฑ ๋ฐ ๋“ฑ๋ก ### ๐Ÿ“Š ์ทจํ•ฉ ๋ณด๊ณ ์„œ - ์ฃผ์ฐจ๋ณ„ ์ „์ฒด ๋ณด๊ณ ์„œ ์ทจํ•ฉ - ํ”„๋กœ์ ํŠธ๋ณ„ ์‹ค์ /๊ณ„ํš AI ์š”์•ฝ (OpenAI) - ์ธ์›๋ณ„ ํˆฌ์ž… ์‹œ๊ฐ„ ํ˜„ํ™ฉ - ์›๋ฌธ ๋ณด๊ธฐ ๊ธฐ๋Šฅ ### ๐Ÿ” ์ธ์ฆ ์‹œ์Šคํ…œ - **DB ๊ธฐ๋ฐ˜ ์„ธ์…˜ ๊ด€๋ฆฌ** (Spring Session JDBC ๋ฐฉ์‹) - ์„ธ์…˜ ํƒ€์ž„์•„์›ƒ: 10๋ถ„ (Sliding Expiration) - ๋กœ๊ทธ์ธ ์ด๋ ฅ ๊ด€๋ฆฌ (IP, ์ ‘์†์‹œ๊ฐ„, ์„ธ์…˜์ƒํƒœ) - ์„ธ์…˜ ์ƒํƒœ: ์ ‘์†์ค‘ / ํ™œ์„ฑ / ์„ธ์…˜๋งŒ๋ฃŒ / ๋กœ๊ทธ์•„์›ƒ ### ๐Ÿ‘ฅ ์ง์›/ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ - ์ง์› ๋“ฑ๋ก ๋ฐ ๊ด€๋ฆฌ - ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋ฐ ์ƒํƒœ ๊ด€๋ฆฌ - ํ”„๋กœ์ ํŠธ๋ณ„ ํˆฌ์ž… ์ธ๋ ฅ ํ˜„ํ™ฉ ## ๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ | ๊ตฌ๋ถ„ | ๊ธฐ์ˆ  | |------|------| | Frontend | Vue 3, Nuxt 3 | | Backend | Nitro (Nuxt Server) | | Database | PostgreSQL | | AI | OpenAI GPT-4o / GPT-4o-mini | | UI | Bootstrap 5, Bootstrap Icons | | Session | DB ๊ธฐ๋ฐ˜ ์„ธ์…˜ (wr_session ํ…Œ์ด๋ธ”) | ## ๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ``` weeklyreport/ โ”œโ”€โ”€ frontend/ # ํ”„๋ก ํŠธ์—”๋“œ ํŽ˜์ด์ง€ โ”‚ โ”œโ”€โ”€ components/ # ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ โ”‚ โ”œโ”€โ”€ composables/ # Vue Composables โ”‚ โ”œโ”€โ”€ admin/ # ๊ด€๋ฆฌ์ž ์ „์šฉ โ”‚ โ”‚ โ””โ”€โ”€ bulk-import.vue # AI ์ผ๊ด„ ๋“ฑ๋ก โ”‚ โ”œโ”€โ”€ report/ # ์ฃผ๊ฐ„๋ณด๊ณ  ๊ด€๋ จ โ”‚ โ”‚ โ”œโ”€โ”€ weekly/ # ๊ฐœ์ธ ์ฃผ๊ฐ„๋ณด๊ณ  โ”‚ โ”‚ โ””โ”€โ”€ summary/ # ์ทจํ•ฉ ๋ณด๊ณ ์„œ โ”‚ โ”œโ”€โ”€ employee/ # ์ง์› ๊ด€๋ฆฌ โ”‚ โ”œโ”€โ”€ project/ # ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ โ”‚ โ””โ”€โ”€ mypage/ # ๋งˆ์ดํŽ˜์ด์ง€ โ”œโ”€โ”€ backend/ โ”‚ โ”œโ”€โ”€ api/ # API ์—”๋“œํฌ์ธํŠธ โ”‚ โ”‚ โ”œโ”€โ”€ auth/ # ์ธ์ฆ โ”‚ โ”‚ โ”œโ”€โ”€ admin/ # ๊ด€๋ฆฌ์ž API โ”‚ โ”‚ โ”œโ”€โ”€ ai/ # AI API โ”‚ โ”‚ โ”œโ”€โ”€ employee/ # ์ง์› API โ”‚ โ”‚ โ”œโ”€โ”€ project/ # ํ”„๋กœ์ ํŠธ API โ”‚ โ”‚ โ””โ”€โ”€ report/ # ๋ณด๊ณ ์„œ API โ”‚ โ”œโ”€โ”€ utils/ # ์œ ํ‹ธ๋ฆฌํ‹ฐ โ”‚ โ”‚ โ”œโ”€โ”€ db.ts # DB ์—ฐ๊ฒฐ โ”‚ โ”‚ โ”œโ”€โ”€ session.ts # ์„ธ์…˜ ๊ด€๋ฆฌ โ”‚ โ”‚ โ””โ”€โ”€ ip.ts # IP ์ถ”์ถœ โ”‚ โ””โ”€โ”€ sql/ # DDL ์Šคํฌ๋ฆฝํŠธ โ”œโ”€โ”€ claude_temp/ # Claude ์ž‘์—…์šฉ ์ž„์‹œ ํŒŒ์ผ โ”œโ”€โ”€ nuxt.config.ts # Nuxt ์„ค์ • โ””โ”€โ”€ package.json ``` ## ๐Ÿ—„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ### ์ฃผ์š” ํ…Œ์ด๋ธ” | ํ…Œ์ด๋ธ”๋ช… | ์„ค๋ช… | |----------|------| | `wr_employee_info` | ์ง์› ์ •๋ณด | | `wr_project_info` | ํ”„๋กœ์ ํŠธ ์ •๋ณด | | `wr_weekly_report` | ์ฃผ๊ฐ„๋ณด๊ณ  ๋งˆ์Šคํ„ฐ | | `wr_weekly_report_task` | ์ฃผ๊ฐ„๋ณด๊ณ  Task (์‹ค์ /๊ณ„ํš) | | `wr_aggregated_report_summary` | ์ทจํ•ฉ ๋ณด๊ณ ์„œ | | `wr_session` | DB ์„ธ์…˜ (์ธ์ฆ) | | `wr_login_history` | ๋กœ๊ทธ์ธ ์ด๋ ฅ | ### ์„ธ์…˜ ํ…Œ์ด๋ธ” (wr_session) ```sql CREATE TABLE wr_session ( session_id VARCHAR(64) PRIMARY KEY, -- ๋žœ๋ค ํ† ํฐ (64์ž hex) employee_id INTEGER NOT NULL, login_history_id INTEGER, created_at TIMESTAMP DEFAULT NOW(), last_access_at TIMESTAMP DEFAULT NOW(), expires_at TIMESTAMP NOT NULL, -- 10๋ถ„ ํ›„ ๋งŒ๋ฃŒ login_ip VARCHAR(45), user_agent TEXT ); ``` ## ๐Ÿ”— ๋ฉ”๋‰ด ๊ตฌ์กฐ | ๋ฉ”๋‰ด | ๊ฒฝ๋กœ | ์„ค๋ช… | ๊ถŒํ•œ | |------|------|------|------| | ๋กœ๊ทธ์ธ | `/login` | ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ | ์ „์ฒด | | ๋Œ€์‹œ๋ณด๋“œ | `/` | ๋Œ€์‹œ๋ณด๋“œ | ๋กœ๊ทธ์ธ | | **์ฃผ๊ฐ„๋ณด๊ณ ** | | | | | โ”œ ๋ชฉ๋ก | `/report/weekly` | ์ฃผ๊ฐ„๋ณด๊ณ  ๋ชฉ๋ก | ๋กœ๊ทธ์ธ | | โ”œ ์ž‘์„ฑ | `/report/weekly/write` | ์ฃผ๊ฐ„๋ณด๊ณ  ์ž‘์„ฑ | ๋กœ๊ทธ์ธ | | โ”” ์ƒ์„ธ/์ˆ˜์ • | `/report/weekly/[id]` | ์ฃผ๊ฐ„๋ณด๊ณ  ์ƒ์„ธ๋ณด๊ธฐ/์ˆ˜์ • | ๋กœ๊ทธ์ธ | | **์ทจํ•ฉ๋ณด๊ณ ์„œ** | `/report/summary` | ์ทจํ•ฉ ๋ณด๊ณ ์„œ | ๋กœ๊ทธ์ธ | | **๊ด€๋ฆฌ์ž** | | | ๊ด€๋ฆฌ์ž | | โ”œ ์ผ๊ด„๋“ฑ๋ก | `/admin/bulk-import` | AI ๊ธฐ๋ฐ˜ ์ผ๊ด„ ๋“ฑ๋ก | ๊ด€๋ฆฌ์ž | | โ”œ ์ง์›๊ด€๋ฆฌ | `/employee` | ์ง์› ๊ด€๋ฆฌ | ๊ด€๋ฆฌ์ž | | โ”” ํ”„๋กœ์ ํŠธ๊ด€๋ฆฌ | `/project` | ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ | ๊ด€๋ฆฌ์ž | | **๋งˆ์ดํŽ˜์ด์ง€** | `/mypage` | ๋‚ด ์ •๋ณด, ๋กœ๊ทธ์ธ ์ด๋ ฅ | ๋กœ๊ทธ์ธ | | **ํ”ผ๋“œ๋ฐฑ** | `/feedback` | ๊ฐœ์„ ์˜๊ฒฌ | ๋กœ๊ทธ์ธ | ## ๐Ÿš€ ์„ค์น˜ ๋ฐ ์‹คํ–‰ ### ์š”๊ตฌ์‚ฌํ•ญ - Node.js 18+ - PostgreSQL 14+ - OpenAI API Key ### ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (.env) ```env DB_HOST=localhost DB_PORT=5432 DB_NAME=weeklyreport DB_USER=weeklyreport DB_PASSWORD=xxx OPENAI_API_KEY=sk-xxx ``` ### ์„ค์น˜ ```bash npm install ``` ### ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ ```bash npm run dev # http://localhost:2026 ``` ### ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ ```bash npm run build npm run preview ``` ### Docker ๋นŒ๋“œ ```bash docker build -t weeklyreport:latest . ``` ## ๐Ÿ“ก ์ฃผ์š” API ### ์ธ์ฆ | Method | Endpoint | ์„ค๋ช… | |--------|----------|------| | POST | `/api/auth/login` | ์ด๋ฉ”์ผ+์ด๋ฆ„ ๋กœ๊ทธ์ธ | | POST | `/api/auth/select-user` | ์ตœ๊ทผ ์‚ฌ์šฉ์ž ์„ ํƒ ๋กœ๊ทธ์ธ | | POST | `/api/auth/logout` | ๋กœ๊ทธ์•„์›ƒ | | GET | `/api/auth/current-user` | ํ˜„์žฌ ์‚ฌ์šฉ์ž (์„ธ์…˜ ๊ฐฑ์‹ ) | | GET | `/api/auth/me` | ์‚ฌ์šฉ์ž ์ƒ์„ธ ์ •๋ณด | | GET | `/api/auth/login-history` | ๋กœ๊ทธ์ธ ์ด๋ ฅ | ### AI | Method | Endpoint | ์„ค๋ช… | |--------|----------|------| | POST | `/api/ai/parse-my-report` | ํ…์ŠคํŠธ โ†’ ์ฃผ๊ฐ„๋ณด๊ณ  ํŒŒ์‹ฑ | | POST | `/api/ai/parse-my-report-image` | ์ด๋ฏธ์ง€ โ†’ ์ฃผ๊ฐ„๋ณด๊ณ  ํŒŒ์‹ฑ | | POST | `/api/admin/parse-report` | ๊ด€๋ฆฌ์ž ์ผ๊ด„ ํ…์ŠคํŠธ ํŒŒ์‹ฑ | | POST | `/api/admin/parse-image` | ๊ด€๋ฆฌ์ž ์ผ๊ด„ ์ด๋ฏธ์ง€ ํŒŒ์‹ฑ | | POST | `/api/admin/bulk-register` | ์ผ๊ด„ ๋“ฑ๋ก | ### ์ฃผ๊ฐ„๋ณด๊ณ  | Method | Endpoint | ์„ค๋ช… | |--------|----------|------| | GET | `/api/report/weekly/list` | ์ฃผ๊ฐ„๋ณด๊ณ  ๋ชฉ๋ก | | POST | `/api/report/weekly/create` | ์ฃผ๊ฐ„๋ณด๊ณ  ์ƒ์„ฑ | | PUT | `/api/report/weekly/[id]/update` | ์ฃผ๊ฐ„๋ณด๊ณ  ์ˆ˜์ • | | POST | `/api/report/weekly/[id]/submit` | ์ฃผ๊ฐ„๋ณด๊ณ  ์ œ์ถœ | | DELETE | `/api/report/weekly/[id]/delete` | ์ฃผ๊ฐ„๋ณด๊ณ  ์‚ญ์ œ | | POST | `/api/report/review` | PMO AI ๋ฆฌ๋ทฐ ์š”์ฒญ | --- ## ๐Ÿค Claude ํ˜‘์—… ๊ฐ€์ด๋“œ ์ด ํ”„๋กœ์ ํŠธ๋Š” Claude AI์™€ ํ˜‘์—…ํ•˜์—ฌ ๊ฐœ๋ฐœ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์‚ฌํ•ญ์„ ์ˆ™์ง€ํ•ด์ฃผ์„ธ์š”. ### โš ๏ธ ํ•„์ˆ˜ ์ค€์ˆ˜์‚ฌํ•ญ #### 1. Git ์ปค๋ฐ‹ ๊ทœ์น™ ``` โŒ Claude๊ฐ€ ์ž„์˜๋กœ ์ปค๋ฐ‹/ํ‘ธ์‹œํ•˜์ง€ ์•Š์Œ โœ… ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ์š”์ฒญํ•  ๋•Œ๋งŒ ์ปค๋ฐ‹ ``` #### 2. ์ž„์‹œ ํŒŒ์ผ ๊ด€๋ฆฌ ``` ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ ๊ธˆ์ง€ ๋ชจ๋“  ์ž„์‹œ ํŒŒ์ผ์€ claude_temp/ ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅ - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ - ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ - ๋ฐฐ์น˜ ํŒŒ์ผ ๋“ฑ ``` #### 3. ํ•จ์ˆ˜๋ช… ์ถฉ๋Œ ์ฃผ์˜ ```typescript // โŒ h3 ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์ถฉ๋Œ export function getSession() { } // โœ… ์ ‘๋‘์–ด๋กœ ๊ตฌ๋ถ„ export function getDbSession() { } ``` Nuxt/h3 ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ž๋™ importํ•˜๋Š” ํ•จ์ˆ˜๋ช… ์ฃผ์˜: - `getSession` โ†’ `getDbSession` - `getQuery` โ†’ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (h3 ๊ฒƒ ์‚ฌ์šฉ) - `getCookie` โ†’ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (h3 ๊ฒƒ ์‚ฌ์šฉ) #### 4. ํ•œ๊ธ€ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ```bash # Windows CMD์—์„œ ํ•œ๊ธ€ ๊นจ์ง ๋ฐœ์ƒ # ๋ฐฐ์น˜ ํŒŒ์ผ ์‚ฌ์šฉ ์‹œ ์˜๋ฌธ ๋ฉ”์‹œ์ง€ ๊ถŒ์žฅ git commit -m "feat: add session management" ``` ### ๐Ÿ“‹ ๊ฐœ๋ฐœ ์ปจ๋ฒค์…˜ #### API ์‘๋‹ต ํ˜•์‹ ```typescript // ์„ฑ๊ณต return { success: true, data: ... } // ๋ชฉ๋ก return { reports: [...], total: 100 } // ์—๋Ÿฌ throw createError({ statusCode: 400, message: '์—๋Ÿฌ ๋ฉ”์‹œ์ง€' }) ``` #### ์ธ์ฆ ์ฒ˜๋ฆฌ ```typescript import { requireAuth, getAuthenticatedUserId } from '../../utils/session' // ํ•„์ˆ˜ ์ธ์ฆ (๋ฏธ์ธ์ฆ ์‹œ 401 ์—๋Ÿฌ) const userId = await requireAuth(event) // ์„ ํƒ ์ธ์ฆ (๋ฏธ์ธ์ฆ ์‹œ null) const userId = await getAuthenticatedUserId(event) ``` #### ๋‚ ์งœ/์ฃผ์ฐจ ๊ณ„์‚ฐ ```typescript // useWeekCalc composable ์‚ฌ์šฉ const { getWeekInfo, getWeekDates, getActualCurrentWeekInfo } = useWeekCalc() // ISO 8601 ์ฃผ์ฐจ ๊ธฐ์ค€ (์›”์š”์ผ ์‹œ์ž‘) ``` ### ๐Ÿ”ง ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์ด์Šˆ | ์ด์Šˆ | ์›์ธ | ํ•ด๊ฒฐ | |------|------|------| | `npm ci` ์‹คํŒจ | package-lock.json ๋ถˆ์ผ์น˜ | `npm install` ํ›„ lock ํŒŒ์ผ ์ปค๋ฐ‹ | | ์„ธ์…˜ ๋งŒ๋ฃŒ ์•ˆ๋จ | expires_at ๋น„๊ต ๋ˆ„๋ฝ | `expires_at > NOW()` ์กฐ๊ฑด ํ™•์ธ | | ๋ฏธ๋ž˜ ์ฃผ์ฐจ ์ ‘๊ทผ | URL ์ง์ ‘ ์ž…๋ ฅ | onMounted์—์„œ ์ฃผ์ฐจ ๊ฒ€์ฆ | | ํ•จ์ˆ˜๋ช… ์ถฉ๋Œ ๊ฒฝ๊ณ  | h3 auto-import | ํ•จ์ˆ˜๋ช… ๋ณ€๊ฒฝ (์ ‘๋‘์–ด ์ถ”๊ฐ€) | ### ๐Ÿ“‚ ์ฃผ์š” ํŒŒ์ผ ์œ„์น˜ | ๊ธฐ๋Šฅ | ํŒŒ์ผ | |------|------| | ์„ธ์…˜ ๊ด€๋ฆฌ | `backend/utils/session.ts` | | DB ์—ฐ๊ฒฐ | `backend/utils/db.ts` | | ์ฃผ์ฐจ ๊ณ„์‚ฐ | `frontend/composables/useWeekCalc.ts` | | ์ธ์ฆ ์ƒํƒœ | `frontend/composables/useAuth.ts` | | ํ—ค๋” ์ปดํฌ๋„ŒํŠธ | `frontend/components/AppHeader.vue` | --- ## ๐Ÿ“„ ๋ผ์ด์„ ์Šค Private - ํ„ฐ๋ณด์†Œํ”„ํŠธ ๋‚ด๋ถ€์šฉ --- ยฉ 2026 TurboSoft