{"id":51262434,"url":"https://github.com/prgrms-fullcycle-devcourse/webfull_9_10_todak","last_synced_at":"2026-06-29T13:01:19.661Z","repository":{"id":359268678,"uuid":"1245273038","full_name":"prgrms-fullcycle-devcourse/webfull_9_10_Todak","owner":"prgrms-fullcycle-devcourse","description":"프로그래머스 웹 풀스택 9기 10회차 토닥이들: 토닥윗미","archived":false,"fork":false,"pushed_at":"2026-06-19T07:19:28.000Z","size":24449,"stargazers_count":1,"open_issues_count":5,"forks_count":2,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-06-19T09:16:04.336Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://webfull-9-10-todak-client.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prgrms-fullcycle-devcourse.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-21T04:28:35.000Z","updated_at":"2026-06-19T00:04:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/prgrms-fullcycle-devcourse/webfull_9_10_Todak","commit_stats":null,"previous_names":["prgrms-fullcycle-devcourse/webfull_9_10_todak"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/prgrms-fullcycle-devcourse/webfull_9_10_Todak","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Todak","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Todak/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Todak/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Todak/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prgrms-fullcycle-devcourse","download_url":"https://codeload.github.com/prgrms-fullcycle-devcourse/webfull_9_10_Todak/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prgrms-fullcycle-devcourse%2Fwebfull_9_10_Todak/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34927687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-29T13:01:18.597Z","updated_at":"2026-06-29T13:01:19.652Z","avatar_url":"https://github.com/prgrms-fullcycle-devcourse.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"apps/client/public/assets/todak-owl-logo.png\" width=\"120\" alt=\"토닥윗미 로고\" /\u003e\n\n# 🦉 토닥윗미 (Todak With Me)\n\n### 개발자를 위한 다정하고 똑똑한 **올인원 협업 공간**\n\n흩어진 협업 도구를 하나로 모으고, 부엉이 **토닥이**가 팀의 소통을 돕습니다.\u003cbr/\u003e\n가상 오피스에서 함께 일하고, AI가 회의록을 정리하며, GitHub가 실시간으로 연결됩니다.\n\n\u003cbr/\u003e\n\n![Next.js](https://img.shields.io/badge/Next.js-16-000000?logo=nextdotjs\u0026logoColor=white)\n![React](https://img.shields.io/badge/React-19-61DAFB?logo=react\u0026logoColor=black)\n![TypeScript](https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript\u0026logoColor=white)\n![Express](https://img.shields.io/badge/Express-4.21-000000?logo=express\u0026logoColor=white)\n![Prisma](https://img.shields.io/badge/Prisma-7-2D3748?logo=prisma\u0026logoColor=white)\n![PostgreSQL](https://img.shields.io/badge/PostgreSQL-Supabase-4169E1?logo=postgresql\u0026logoColor=white)\n![Socket.io](https://img.shields.io/badge/Socket.IO-4.8-010101?logo=socketdotio\u0026logoColor=white)\n![Claude](https://img.shields.io/badge/Anthropic-Claude-D97757?logo=anthropic\u0026logoColor=white)\n\n**데브코스 웹 풀스택 9기 · 최종 프로젝트 3팀 「토닥이들」**\n\n📹 [시연 영상](https://youtu.be/UpVpmfKZ7qA) \u0026nbsp;·\u0026nbsp; 📑 [발표 자료](https://github.com/user-attachments/files/29086810/03._._._.pdf) \u0026nbsp;·\u0026nbsp; 🚀 [서비스 바로가기](https://webfull-9-10-todak-client.vercel.app/)\n\n\u003c/div\u003e\n\n\u003cbr/\u003e\n\n## 📑 목차\n\n- [기획 배경](#-기획-배경)\n- [주요 기능](#-주요-기능)\n- [기술 스택](#-기술-스택)\n- [시스템 아키텍처](#-시스템-아키텍처)\n- [데이터 모델](#-데이터-모델-erd)\n- [성능 최적화](#-성능-최적화)\n- [안정성 \u0026 보안](#-안정성--보안)\n- [프로젝트 구조](#-프로젝트-구조)\n- [시작하기](#-시작하기)\n- [API 문서](#-api-문서)\n- [배포](#-배포)\n- [팀 소개](#-팀-소개)\n- [컨벤션](#-컨벤션)\n\n\u003cbr/\u003e\n\n## 🎯 기획 배경\n\n\u003e \"협업, 생각보다 너무 번거롭지 않나요?\"\n\n팀 프로젝트를 하다 보면 **가상 오피스(zep)·메신저(Slack)·코드(GitHub)** 를 동시에 띄워 두게 됩니다.\n회의가 끝나면 회의록을 따로 정리하고, PR·이슈가 올라와도 일일이 팀원에게 알려야 하죠.\n도구가 흩어져 있다 보니 정작 **개발에 집중하기 어려웠습니다.**\n\n그래서 찾아봤지만, **개발팀에 딱 맞는 올인원 협업 서비스는 없었습니다.**\n\n| 서비스            | 한계                                               |\n| :---------------- | :------------------------------------------------- |\n| Jira / Notion     | 팀원의 **현재 상태(집중·회의·부재)** 를 알 수 없음 |\n| zep (가상 오피스) | 무겁고 **개발에 특화되지 않음**                    |\n| Discord / Slack   | 메시징 중심 — 상태 공유·목표 관리가 약함           |\n| Linear            | 이슈 관리는 깔끔하지만 **회의록 기능이 없음**      |\n\n**→ 그래서 저희가 직접 만들었습니다.** 개발자가 **단 하나의 창**만 열어둔 채로, 더 편하고 다양하게 협업할 수 있도록.\n\n\u003cbr/\u003e\n\n## ✨ 주요 기능\n\n### 🏠 2D 가상 워크스페이스\n\n- 내 캐릭터로 가상 오피스에 **출근**, 팀원의 **위치와 상태(집중 · 휴식 · 회의 · 자리비움)** 를 한눈에 확인\n- 캐릭터가 회의실 영역에 들어가면 **충돌 감지**로 자동 입장 / 퇴장 처리\n- Pixi.js 기반 실시간 캔버스 렌더링\n\n### 💬 실시간 채팅 \u0026 파일 공유\n\n- 이모지 리액션, 드래그\u0026드롭 **파일 전송(AWS S3)**, **프라이빗 채팅** 채널\n- Socket.IO 기반 양방향 실시간 통신\n\n### 🤖 AI 회의록\n\n- 회의가 끝나면 **Claude AI**가 대화를 분석해 **회의록 자동 생성** (참가자 · 주요 논의 · 결정 사항)\n- ✨ **AI 다듬기** — 마음에 들 때까지 요청을 반영해 **원하는 버전으로 재작성**\n- 회의 내용에서 **할 일을 GitHub 이슈 초안**으로 만들어 검토 후 바로 등록\n\n### 🔗 GitHub 실시간 연동\n\n- GitHub OAuth 로그인, **웹훅으로 PR · Issue · Push 이벤트 실시간 수신**\n- 토닥윗미 안에서 PR/이슈 조회 · 상세 확인 · **승인/머지**까지 처리\n- 이슈 ↔ Todo 자동 동기화\n\n### 🦉 토닥이 알림\n\n- PR 머지, 회의록 완성, 새 이슈 등 중요한 순간을 **캐릭터 토닥이**가 실시간으로 전달\n- 클릭 한 번으로 해당 PR · 이슈로 바로 이동\n\n\u003cbr/\u003e\n\n## 🛠 기술 스택\n\n**Frontend**\n\n![Next.js](https://img.shields.io/badge/Next.js_16-000000?logo=nextdotjs\u0026logoColor=white)\n![React](https://img.shields.io/badge/React_19-61DAFB?logo=react\u0026logoColor=black)\n![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript\u0026logoColor=white)\n![Zustand](https://img.shields.io/badge/Zustand-2D3748)\n![TanStack Query](https://img.shields.io/badge/TanStack_Query-FF4154?logo=reactquery\u0026logoColor=white)\n![Tailwind](https://img.shields.io/badge/Tailwind_CSS_4-06B6D4?logo=tailwindcss\u0026logoColor=white)\n![HeroUI](https://img.shields.io/badge/HeroUI-6366F1)\n![Pixi.js](https://img.shields.io/badge/Pixi.js-E91E63)\n![Axios](https://img.shields.io/badge/Axios-5A29E4?logo=axios\u0026logoColor=white)\n\n**Backend**\n\n![Node.js](https://img.shields.io/badge/Node.js_22-339933?logo=nodedotjs\u0026logoColor=white)\n![Express](https://img.shields.io/badge/Express-000000?logo=express\u0026logoColor=white)\n![Prisma](https://img.shields.io/badge/Prisma_7-2D3748?logo=prisma\u0026logoColor=white)\n![Socket.io](https://img.shields.io/badge/Socket.IO-010101?logo=socketdotio\u0026logoColor=white)\n![BullMQ](https://img.shields.io/badge/BullMQ-DD2C00)\n![Zod](https://img.shields.io/badge/Zod-3E67B1?logo=zod\u0026logoColor=white)\n![JWT](https://img.shields.io/badge/JWT-000000?logo=jsonwebtokens\u0026logoColor=white)\n![Swagger](https://img.shields.io/badge/OpenAPI-Swagger-85EA2D?logo=swagger\u0026logoColor=black)\n\n**Database · Infra · AI**\n\n![PostgreSQL](https://img.shields.io/badge/PostgreSQL-4169E1?logo=postgresql\u0026logoColor=white)\n![Supabase](https://img.shields.io/badge/Supabase-3FCF8E?logo=supabase\u0026logoColor=white)\n![Redis](https://img.shields.io/badge/Redis-DC382D?logo=redis\u0026logoColor=white)\n![AWS S3](https://img.shields.io/badge/AWS_S3-569A31?logo=amazons3\u0026logoColor=white)\n![Railway](https://img.shields.io/badge/Railway-0B0D0E?logo=railway\u0026logoColor=white)\n![Vercel](https://img.shields.io/badge/Vercel-000000?logo=vercel\u0026logoColor=white)\n![Anthropic](https://img.shields.io/badge/Anthropic_Claude-D97757?logo=anthropic\u0026logoColor=white)\n\n**개발 · 품질**\n\n![pnpm](https://img.shields.io/badge/pnpm_workspace-F69220?logo=pnpm\u0026logoColor=white)\n![Vitest](https://img.shields.io/badge/Vitest-6E9F18?logo=vitest\u0026logoColor=white)\n![ESLint](https://img.shields.io/badge/ESLint-4B32C3?logo=eslint\u0026logoColor=white)\n![Prettier](https://img.shields.io/badge/Prettier-F7B93E?logo=prettier\u0026logoColor=black)\n![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-2088FF?logo=githubactions\u0026logoColor=white)\n\n\u003cbr/\u003e\n\n## 🏗 시스템 아키텍처\n\n```mermaid\nflowchart TD\n    Browser([🧑‍💻 브라우저])\n    Client[\"\u003cb\u003eClient\u003c/b\u003e · Next.js · React\u003cbr/\u003e2D 캔버스 · 실시간 UI\"]\n    Server[\"\u003cb\u003eServer\u003c/b\u003e — 단일 Node 프로세스 · Railway\u003cbr/\u003eExpress REST API · Socket.IO · BullMQ 워커\"]\n\n    DB[(\"PostgreSQL\u003cbr/\u003eSupabase\")]\n    Redis[(\"Redis\u003cbr/\u003eQueue / Rate\")]\n    GitHub[\"GitHub\u003cbr/\u003eOAuth · Webhook\"]\n    Claude[\"Anthropic\u003cbr/\u003eClaude\"]\n    S3[(\"AWS S3\u003cbr/\u003e파일 저장\")]\n\n    Browser \u003c--\u003e Client\n    Client --\u003e|\"REST API\"| Server\n    Client \u003c--\u003e|\"WebSocket (Socket.IO)\"| Server\n\n    Server --\u003e|\"Prisma\"| DB\n    Server --\u003e|\"enqueue\"| Redis\n    Server --\u003e|\"Octokit\"| GitHub\n    Server --\u003e|\"AI 호출\"| Claude\n    Server --\u003e|\"업로드\"| S3\n```\n\n- **REST + WebSocket** 이원화: 일반 요청은 REST, 실시간 상태·채팅은 WebSocket\n- **단일 Node 프로세스** 안에서 API · 실시간 통신 · 비동기 워커(BullMQ)를 함께 운영\n- 회의록 생성/깃허브 동기화처럼 무거운 작업은 **큐로 분리**해 사용자 응답 지연 최소화\n\n\u003cbr/\u003e\n\n## 🗄 데이터 모델 (ERD)\n\n14개 모델을 룸(Room) 중심으로 설계했습니다. 모든 협업 데이터(채팅·회의·회의록·할 일·알림)가 룸에 귀속됩니다.\n\n```mermaid\n%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#FFF7ED','primaryBorderColor':'#D97757','lineColor':'#D97757','primaryTextColor':'#7C2D12','tertiaryColor':'#FEF3C7'}}}%%\nerDiagram\n    USER ||--o{ ROOM_MEMBER : \"가입\"\n    USER ||--o{ CHAT_MESSAGE : \"작성\"\n    USER ||--o{ CHAT_REACTION : \"반응\"\n    USER ||--o{ MEETING : \"주최\"\n    USER ||--o{ MEETING_PARTICIPANT : \"참여\"\n    USER ||--o{ MINUTES : \"작성\"\n    USER ||--o{ TODO : \"담당\"\n    USER ||--o{ NOTIFICATION : \"수신\"\n    USER ||--o{ PRIVATE_ROOM_SESSION : \"입장\"\n\n    ROOM ||--o{ ROOM_MEMBER : \"구성\"\n    ROOM ||--o{ REPO : \"연결\"\n    ROOM ||--o{ PRIVATE_ROOM : \"보유\"\n    ROOM ||--o{ CHAT_MESSAGE : \"채팅\"\n    ROOM ||--o{ MEETING : \"회의\"\n    ROOM ||--o{ MINUTES : \"회의록\"\n    ROOM ||--o{ TODO : \"할일\"\n    ROOM ||--o{ NOTIFICATION : \"알림\"\n\n    PRIVATE_ROOM ||--o{ PRIVATE_ROOM_SESSION : \"세션\"\n    PRIVATE_ROOM ||--o{ CHAT_MESSAGE : \"채팅\"\n    PRIVATE_ROOM ||--o{ MEETING : \"회의\"\n\n    CHAT_MESSAGE ||--o{ CHAT_REACTION : \"반응\"\n    CHAT_MESSAGE ||--o{ CHAT_ATTACHMENT : \"첨부\"\n\n    MEETING ||--o{ MEETING_PARTICIPANT : \"참가자\"\n    MEETING ||--o| MINUTES : \"생성\"\n\n    REPO ||--o{ TODO : \"이슈\"\n    MINUTES ||--o{ TODO : \"발행\"\n```\n\n\u003cbr/\u003e\n\n## ⚡ 성능 최적화\n\n실측 기반으로 병목을 찾아 개선했습니다.\n\n| 항목                                       | Before    | After        | 개선         |\n| :----------------------------------------- | :-------- | :----------- | :----------- |\n| **DB 인덱스** — `room_member` 포인트 조회  | 19.236 ms | **0.036 ms** | **534× ⚡**  |\n| **DB 인덱스** — 룸별 최신순 조회           | 18.381 ms | **0.041 ms** | **448× ⚡**  |\n| **쿼리 병렬화** (`Promise.all`) — 요청당   | ~325 ms   | **~163 ms**  | **약 2× ⚡** |\n| **이미지 최적화** (PNG → WebP) — 초기 로딩 | 32.89 s   | **3.8 s**    | **8.6× ⚡**  |\n| **이미지 최적화** — 에셋 용량              | 9.14 MB   | **1.36 MB**  | **−85%**     |\n\n\u003e DB 인덱스는 20만 행 시뮬(`EXPLAIN ANALYZE`)로 검증했고, 데이터가 커질수록 Postgres가 자동으로 Index Scan으로 전환됩니다.\n\u003e 원격 DB라 네트워크 왕복 비용이 큰 환경에서, 순차 await를 `Promise.all` 병렬 실행으로 바꿔 큰 효과를 봤습니다.\n\n\u003cbr/\u003e\n\n## 🔒 안정성 \u0026 보안\n\n흔히 놓치기 쉬운 문제를 **회피하지 않고 정면으로** 해결했습니다.\n\n| 예상 위협                    | 해결                                                            |\n| :--------------------------- | :-------------------------------------------------------------- |\n| 위조된 Webhook 요청          | **HMAC 서명 검증** (`timingSafeEqual`) 으로 차단                |\n| 무차별 인증 요청(브루트포스) | **Redis Lua 스크립트**로 원자적 Rate Limiting                   |\n| Access Token 탈취            | **Refresh Token Rotation** 으로 무력화                          |\n| 재배포 중 작업 유실          | **Graceful Shutdown** 순서 제어(HTTP→소켓→워커→DB→Redis)로 보존 |\n\n- 비동기 작업은 **BullMQ 재시도 정책 + stuck 작업 sweeper** 로 운영 안정성 확보\n- **유닛 · 통합 테스트 25개 · 200여 개 케이스** 로 핵심 로직을 검증\n- `dependency-cruiser` 로 의존성 규칙 검사, Husky + lint-staged 로 커밋 단위 품질 관리\n\n\u003cbr/\u003e\n\n## 📂 프로젝트 구조\n\n```\n.\n├── apps/\n│   ├── client/        # Next.js 16 · React 19 (Vercel 배포)\n│   │   └── src/\n│   │       ├── app/         # App Router (room, auth ...)\n│   │       ├── services/    # API 클라이언트 (axios · React Query)\n│   │       ├── store/       # Zustand 전역 상태\n│   │       └── lib/         # socket, device 등\n│   └── server/        # Express API (Railway 배포)\n│       └── src/\n│           ├── api/         # REST 라우트 (auth, rooms, minutes, github, webhooks ...)\n│           ├── socket/      # Socket.IO 핸들러 (chat, room, meeting, minutes ...)\n│           ├── services/    # 비즈니스 로직 (anthropic, webhook, minutes ...)\n│           ├── jobs/        # BullMQ 큐 · 워커\n│           ├── middleware/  # 인증 · rate limit · 검증\n│           └── prisma/      # schema.prisma · migrations\n├── .github/           # CI 워크플로 · PR 템플릿\n└── pnpm-workspace.yaml\n```\n\n\u003cbr/\u003e\n\n## 🚀 시작하기\n\n### 요구 사항\n\n- **Node.js** ≥ 20.19 (권장 22.x) · **pnpm** ≥ 9\n\n### 설치 \u0026 환경 변수\n\n```bash\npnpm install\n\ncp apps/server/.env.example apps/server/.env\n# apps/server/.env 값을 팀 Supabase / Railway / GitHub / Anthropic / AWS 키에 맞게 수정\n```\n\n### 개발 서버\n\n```bash\npnpm dev          # client + server 동시 실행\npnpm dev:client   # client 만\npnpm dev:server   # server 만\n```\n\n### 데이터베이스 (Prisma 7)\n\n```bash\npnpm db:generate  # Prisma Client 생성\npnpm db:migrate   # 마이그레이션\npnpm db:studio    # Prisma Studio\n```\n\n### 테스트\n\n```bash\npnpm test              # 유닛 테스트 (Vitest)\npnpm test:integration  # 통합 테스트 (임베디드 PostgreSQL)\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e전체 스크립트 (루트)\u003c/summary\u003e\n\n| 명령                                                                    | 설명                                        |\n| :---------------------------------------------------------------------- | :------------------------------------------ |\n| `pnpm dev`                                                              | client + server 동시 개발 모드              |\n| `pnpm build`                                                            | server 빌드 / `build:client` 로 client 빌드 |\n| `pnpm test` · `pnpm test:integration`                                   | 유닛 / 통합 테스트                          |\n| `pnpm lint` · `pnpm lint:fix`                                           | ESLint                                      |\n| `pnpm format` · `pnpm format:check`                                     | Prettier                                    |\n| `pnpm depcruise`                                                        | 의존성 규칙 검사                            |\n| `pnpm db:generate` · `db:migrate` · `db:push` · `db:studio` · `db:seed` | Prisma                                      |\n\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n## 📖 API 문서\n\n**Swagger UI** 로 모든 엔드포인트를 확인하고 브라우저에서 바로 호출해볼 수 있습니다.\n\n| 환경    | 주소                                                       |\n| :------ | :--------------------------------------------------------- |\n| 🔵 배포 | https://webfull910todak-production.up.railway.app/api-docs |\n| 🖥 로컬 | http://localhost:4000/api-docs                             |\n\n- OpenAPI 스펙은 **`zod-to-openapi`** 로 Zod 스키마에서 자동 생성됩니다. (JSON: `/api/docs/openapi.json`)\n\n\u003cbr/\u003e\n\n## 🚢 배포\n\n| 대상             | 플랫폼              | 비고                                                                                    |\n| :--------------- | :------------------ | :-------------------------------------------------------------------------------------- |\n| Client (Next.js) | **Vercel**          | https://webfull-9-10-todak-client.vercel.app                                            |\n| Server (Express) | **Railway**         | Health check: `GET /api/health` · API 문서 `GET /api-docs` · `apps/server/railway.toml` |\n| Database         | **Supabase**        | PostgreSQL (Pooling URL)                                                                |\n| Queue / Cache    | **Redis (Railway)** | BullMQ · Rate Limit                                                                     |\n| File Storage     | **AWS S3**          | 채팅 첨부파일 (Presigned URL)                                                           |\n\n\u003cbr/\u003e\n\n## 👥 팀 소개\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" width=\"160\"\u003e\n      \u003cimg src=\"https://avatars.githubusercontent.com/u/252135802?v=4\" width=\"110\" height=\"110\" style=\"border-radius:50%\"/\u003e\u003cbr/\u003e\n      \u003cb\u003e강영아\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eBackend\u003c/sub\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"160\"\u003e\n      \u003cimg src=\"https://avatars.githubusercontent.com/u/163396166?v=4\" width=\"110\" height=\"110\" style=\"border-radius:50%\"/\u003e\u003cbr/\u003e\n      \u003cb\u003e김가영\u003c/b\u003e\u003cbr/\u003e\u003csub\u003e팀장 · Backend\u003c/sub\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"160\"\u003e\n      \u003cimg src=\"https://avatars.githubusercontent.com/u/57696898?v=4\" width=\"110\" height=\"110\" style=\"border-radius:50%\"/\u003e\u003cbr/\u003e\n      \u003cb\u003e신상호\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eBackend\u003c/sub\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"160\"\u003e\n      \u003cimg src=\"https://avatars.githubusercontent.com/u/164767697?v=4\" width=\"110\" height=\"110\" style=\"border-radius:50%\"/\u003e\u003cbr/\u003e\n      \u003cb\u003e송정화\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eFrontend\u003c/sub\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"160\"\u003e\n      \u003cimg src=\"https://avatars.githubusercontent.com/u/160744163?v=4\" width=\"110\" height=\"110\" style=\"border-radius:50%\"/\u003e\u003cbr/\u003e\n      \u003cb\u003e이예은\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eFrontend\u003c/sub\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"160\"\u003e\n      \u003cimg src=\"https://avatars.githubusercontent.com/u/58125528?v=4\" width=\"110\" height=\"110\" style=\"border-radius:50%\"/\u003e\u003cbr/\u003e\n      \u003cb\u003e이중훈\u003c/b\u003e\u003cbr/\u003e\u003csub\u003eFrontend\u003c/sub\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eAuth · Room · Repo · Todo\u003cbr/\u003e소켓 기본 구조 · 인가/보안\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eChat · Meeting · PrivateRoom\u003cbr/\u003eInfra (CI · BullMQ · 종료처리)\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eAI 회의록 · Notification\u003cbr/\u003eGitHub Webhook\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003e2D 가상 공간 · 캐릭터\u003cbr/\u003e렌더링 최적화\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eAI 회의록 · 실시간 채팅\u003cbr/\u003e이슈 등록기\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eGitHub 인증 · PR 관리\u003cbr/\u003e설정 · 사용자 흐름\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003cbr/\u003e\n\n## 🤝 컨벤션\n\n### 📌 커밋 메시지 — Conventional Commits\n\n`type: subject` 형식을 사용합니다. (예: `feat: GitHub OAuth 콜백 추가`)\n`commit-msg` 훅에서 **commitlint** 가 자동 검증하며, 규칙을 어기면 커밋이 막힙니다.\n\n| type     | 설명                | type       | 설명                              |\n| :------- | :------------------ | :--------- | :-------------------------------- |\n| `feat`   | 새로운 기능         | `refactor` | 코드 리팩토링                     |\n| `fix`    | 버그 수정           | `comment`  | 주석 추가/변경                    |\n| `design` | UI 디자인 수정      | `rename`   | 파일/폴더명 수정·이동             |\n| `test`   | 테스트 코드         | `remove`   | 파일 삭제                         |\n| `chore`  | 빌드·환경 설정 변경 | `style`    | 포맷·세미콜론 등 (로직 변화 없음) |\n| `docs`   | 문서 수정           | `security` | 보안 취약점 해결                  |\n\n\u003e 규칙: `subject` 필수 · 제목 **50자** 이내 · 본문 한 줄 **72자** 이내\n\n### 🌿 브랜치 전략\n\n- `main` — 배포(릴리스) 브랜치\n- `develop` — 통합 개발 브랜치 (기능 PR이 모이는 곳)\n- `feature/\u003c기능명\u003e` · `fix/\u003c수정명\u003e` — 작업 브랜치 → `develop` 으로 PR\n\n### 🔁 Pull Request\n\n- 템플릿(**개요 · 작업 내용 · 변경 사항 · UI 스크린샷 · 패키지 설치 · 관련 이슈**) 작성\n- PR 제목도 커밋과 동일한 Conventional Commits 규칙으로 검사 (squash 머지 시 제목이 곧 커밋이 되므로)\n- **CI 통과 + 리뷰 승인** 후 머지\n\n### 🎨 코드 스타일\n\n- **ESLint** + **Prettier** — `pre-commit` 훅에서 `lint-staged` 로 변경 파일만 자동 검사·포맷\n- **dependency-cruiser** 로 모듈 간 의존성 규칙(순환 의존 등) 검사\n- 클라이언트/서버 모두 **TypeScript strict** + `tsc --noEmit` 타입 체크\n\n### ✅ CI 파이프라인 (GitHub Actions)\n\nPR이 열리면 다음 검사가 자동 실행됩니다.\n\n| 워크플로       | 검사 항목                                                                                                         |\n| :------------- | :---------------------------------------------------------------------------------------------------------------- |\n| **CI**         | 포맷 체크 · 린트 · 타입체크(server·client) · dependency-cruiser · 유닛 테스트 · 통합 테스트 · 빌드(server+client) |\n| **Commitlint** | 커밋 메시지 · PR 제목 규칙 검사                                                                                   |\n| **Gitleaks**   | 시크릿(키·토큰) 유출 스캔                                                                                         |\n\n\u003cbr/\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n**🦉 토닥윗미와 함께, 개발 협업의 모든 것을 한 곳에서.**\n\n\u003csub\u003e프로그래머스 데브코스 웹 풀스택 9기 · 최종 프로젝트 3팀 「토닥이들」\u003c/sub\u003e\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_todak","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_todak","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprgrms-fullcycle-devcourse%2Fwebfull_9_10_todak/lists"}