diff --git a/PR-rule.md b/PR-rule.md index 88f50b2..d3659ef 100644 --- a/PR-rule.md +++ b/PR-rule.md @@ -1,3 +1,4 @@ +n주차 - 준/박서윤 워크북 과제 제출 # ✅ n주차 워크북 완료 🎉 **워크북을 완료한 챌린저분들, 수고 많으셨습니다!** diff --git a/week00/mission/.gitkeep b/week00/mission/.gitkeep index e69de29..b4b8b26 100644 --- a/week00/mission/.gitkeep +++ b/week00/mission/.gitkeep @@ -0,0 +1,4 @@ +1. members +-> 서비스 사용자의 계정 및 프로필 정보 + +컬럼명 \ No newline at end of file diff --git a/week01/keyword/.gitkeep b/week01/keyword/.gitkeep index e69de29..d3eeb4d 100644 --- a/week01/keyword/.gitkeep +++ b/week01/keyword/.gitkeep @@ -0,0 +1,77 @@ +- 외래키 + + **정의:** + + 한 테이블의 컬럼이 다른 테이블의 기본 키(PK)를 참조하는 키. 테이블 간의 관계를 맺어주는 연결고리 역할을 수행함. + + **핵심 속성(제약 조건):** + + 참조 무결성: 외래 키는 참조하는 부모 테이블의 기본 키에 존재하는 값이거나, null값이어야 함. 즉, 존재하지 않는 '유령 데이터'를 참조할 수 없음. + + → 외래키는 관계의 신뢰성을 보장하기 위해 필요함. 예를 들어 수강신청 기록은 반드시 실존하는 학생에 의해 이루어져야한다는 규칙을 강제하기에 데이터는 일관성을 유지하고, 현실세계의 논리적 관계를 그대로 데이터베이스에 반영할 수 있게 됨. +- 기본키 + + **정의:** + + 테이블 내의 각 행(Row)을 유일하게 식별할 수 있는 값을 가진 컬럼 또는 컬럼들의 조합. + + **핵심 속성(제약 조건):** + + 유일성(Uniqueness), 최소성(Minimality), 개체 무결성(Not Null). 테이블 내에서 절대 중복될 수 없으며, 반드시 값을 가져야 함. + → 기본키는 데이터의 '신분증'과 같음. 이름은 같을 수 있어도 각자를 구분하는 주민등록번호가 있는 것처럼, 기본키가 있어야 수많은 데이터 속에서 원하는 특정 데이터를 정확히 찾아 수정하거나 삭제할 수 있음. + - ER 다이어그램 + + **정의:** + + 데이터베이스의 구조를 시각적으로 표현한 설계도. 데이터(개체), 데이터의 속성, 데이터 간의 관계를 한눈에 파악할 수 있게 함. + + **핵심 요소:** + + 개체(Entity), 속성(Attribute), 관계(Relationship). 이 세 가지 요소를 사용하여 시스템의 데이터 구조를 논리적으로 모델링함. + → ER 다이어그램은 집을 짓기 전의 '청사진'이다. 설계도 없이 집을 지으면 구조가 엉망이 되듯, ERD 없이 데이터베이스를 구축하면 데이터 중복과 불일치 문제가 발생하기 쉽다. 개발자와 설계자 간의 중요한 소통 도구이기도 함. + +- 복합 키 + + **정의:** + + 두 개 이상의 컬럼을 조합하여 하나의 기본 키(PK)를 만드는 것. + + **핵심 속성(제약 조건):** + + 단일 컬럼만으로는 행의 유일성을 보장할 수 없을 때 사용됨. 키를 구성하는 모든 컬럼의 조합이 테이블 내에서 유일해야 함. + → 복합 키는 '어떤 학생'이 '어떤 과목'을 수강했는지와 같이, 두 개 이상의 조건이 결합되어야만 하나의 특정 상황을 정의할 수 있을 때 사용됨. 주로 테이블 간의 다대다(N:M) 관계를 해소하기 위한 연결 테이블에서 기본 키로 활용됨. + +- 연관관계 + + **정의:** + + 테이블(개체)들 사이의 논리적인 연결을 의미하며, 주로 외래 키를 통해 구현됨. + + **핵심 종류:** + + 1:1 (일대일), 1:N (일대다), N:M (다대다). 특히 N:M 관계는 물리적으로 직접 구현할 수 없어, 중간에 두 테이블을 1:N 관계로 연결해주는 '연결 테이블'을 필요로 함. + → 연관관계는 흩어져 있는 데이터에 '맥락'과 '의미'를 부여하는 문법과 같음. 관계 설정이 없다면 데이터는 단순한 값의 나열일 뿐이지만, 관계를 통해 '부서에 소속된 사원', '학생이 수강하는 과목'처럼 유의미한 정보 시스템이 됨. + +- 정규화 + + **정의:** + + 데이터의 중복을 최소화하고 무결성을 향상시키기 위해, 정해진 규칙에 따라 테이블을 분해하는 과정. 데이터 변경 시 발생하는 이상 현상(Anomaly)을 방지하는 것이 주 목적임. + + **핵심 속성(정보처리 실기 핵심):** + + 1NF(원자값), 2NF(부분 함수 종속 제거), 3NF(이행 함수 종속 제거), BCNF(결정자이면서 후보키가 아닌 것 제거) 등의 단계적 규칙을 적용하여 테이블 구조를 개선함. + → 정규화는 잘 정리된 '서랍장'을 만드는 과정. 종류별로 물건을 나눠 담아 중복 구매를 막고(중복 최소화), 관리를 쉽게 만드는 것과 같음. 데이터 구조를 논리적이고 깔끔하게 만들어 장기적인 유지보수를 용이하게 하는 필수 설계 단계. + + [정보처리 실기_데이터베이스06강_정규화](https://youtu.be/RXQ1kZ_JHqg?si=f0OPsoOWnJXSbqca) + +- 반 정규화 + + **정의:** + + 정규화된 테이블에 대해, 시스템의 조회 성능 향상을 목적으로 의도적으로 데이터 중복을 허용하거나 테이블을 통합하는 기법. + + **핵심 속성(제약 조건):** + + 읽기(SELECT) 성능은 향상되지만, 데이터 저장 공간이 낭비되고 입력/수정/삭제 시 데이터 불일치가 발생할 수 있어 일관성 유지가 어려워짐. + → 반정규화는 서랍장에 잘 정리된 옷 중에서 '내일 당장 입을 옷을 미리 꺼내 의자에 걸어두는 것'과 같다. 아침에 찾는 시간(JOIN)을 줄여 매우 빠르지만, 방이 어수선해지고(중복) 다른 옷을 입고 싶을 때 다시 정리해야 하는(관리 복잡성) 비용이 따름. 반드시 정규화 이후, 성능 개선이 필요한 부분에 한해 신중하게 적용해야 함. \ No newline at end of file diff --git a/week01/keyword/keyword.md b/week01/keyword/keyword.md new file mode 100644 index 0000000..e69de29 diff --git a/week01/mission/.gitkeep b/week01/mission/.gitkeep index e69de29..0e89a17 100644 --- a/week01/mission/.gitkeep +++ b/week01/mission/.gitkeep @@ -0,0 +1,199 @@ +### **DB 설계** + +- **모델**: 관계형 데이터베이스 모델을 기반으로 설계 +- **핵심 엔티티(Entity)**: + 1. **Users**: 서비스에 가입한 사용자 정보 + 2. **Regions**: 가게가 위치한 지역 정보 + 3. **Stores**: 미션이 부여되는 가게 정보 + 4. **Missions**: 사용자가 수행할 미션 정보 + 5. **User_Missions**: 사용자가 어떤 미션을 수행하고 있는지, 완료했는지 상태를 기록하는 테이블 (매핑 테이블) + 6. **Reviews**: 사용자가 완료한 미션에 대해 작성한 리뷰 + 7. **Food_Categories & User_Food_Preferences**: 사용자의 음식 선호도를 관리하기 위한 테이블 + +### **1. users** + +**설명**: 서비스 사용자의 계정 및 프로필 정보. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 사용자 고유 식별자 | +| email | VARCHAR(255) | UNIQUE, NOT NULL | 로그인 이메일 | +| password | VARCHAR(255) | NOT NULL | 해시 처리된 비밀번호 | +| nickname | VARCHAR(50) | UNIQUE, NOT NULL | 닉네임 | +| gender | VARCHAR(10) | | 성별 ('MALE', 'FEMALE') | +| birth_date | DATE | | 생년월일 | +| address_main | VARCHAR(255) | | 주소 | +| total_points | INT | NOT NULL, DEFAULT 0 | 보유 총 포인트 | +| status | VARCHAR(15) | NOT NULL, DEFAULT 'ACTIVE' | 계정 상태 ('ACTIVE', 'INACTIVE') | +| created_at | DATETIME(6) | NOT NULL | 가입일 (밀리초 포함) | +| updated_at | DATETIME(6) | NOT NULL | 마지막 정보 수정일 (밀리초 포함) | +| deleted_at | DATETIME(6) | | 비활성화(탈퇴) 처리일 (밀리초 포함) | + +### **2. food_categories** + +**설명**: 음식 종류 (한식, 일식 등) + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 카테고리 고유 식별자 | +| name | VARCHAR(50) | UNIQUE, NOT NULL | 카테고리명 | +| created_at | DATETIME(6) | NOT NULL | 생성일 | +| updated_at | DATETIME(6) | NOT NULL | 수정일 | + +### **3. user_food_preferences** + +**설명**: users와 food_categories의 N:M 관계를 해소하기 위한 매핑 테이블. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 매핑 고유 식별자 | +| user_id | BIGINT | FK | 사용자 ID (users.id) | +| food_category_id | BIGINT | FK | 카테고리 ID (food_categories.id) | +| created_at | DATETIME(6) | NOT NULL | 선호 등록일 | +| update_at | DATETIME(6) | NOT NULL | 선호 수정일 | + +### **4. regions** + +**설명**: 가게가 위치한 지역 정보. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 지역 고유 식별자 | +| name | VARCHAR(100) | UNIQUE, NOT NULL | 지역명 | +| created_at | DATETIME(6) | NOT NULL | 생성일 | +| updated_at | DATETIME(6) | NOT NULL | 수정일 | + +### **5. stores** + +**설명**: 미션이 부여되는 가게 정보. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 가게 고유 식별자 | +| name | VARCHAR(100) | NOT NULL | 가게 이름 | +| address | VARCHAR(255) | NOT NULL | 가게 상세 주소 | +| region_id | BIGINT | FK, NOT NULL | 소속 지역 ID (regions.id) | +| created_at | DATETIME(6) | NOT NULL | 생성일 | +| updated_at | DATETIME(6) | NOT NULL | 수정일 | + +### **6. missions** + +**설명**: 각 가게에 할당되는 미션의 원본 데이터. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 미션 고유 식별자 | +| store_id | BIGINT | FK, NOT NULL | 소속 가게 ID (stores.id) | +| title | VARCHAR(255) | NOT NULL | 미션 제목 | +| description | TEXT | | 미션 상세 설명 | +| reward_points | INT | NOT NULL | 보상 포인트 | +| is_active | BOOLEAN | NOT NULL, DEFAULT true | 미션 활성화 여부 | +| created_at | DATETIME(6) | NOT NULL | 생성일 | +| updated_at | DATETIME(6) | NOT NULL | 수정일 | + +### **7. user_missions** + +**설명**: users와 missions의 N:M 관계를 해소하고, 사용자의 미션 수행 상태를 기록하는 매핑 테이블. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 수행 기록 고유 식별자 | +| user_id | BIGINT | FK, NOT NULL | 사용자 ID (users.id) | +| mission_id | BIGINT | FK, NOT NULL | 미션 ID (missions.id) | +| status | VARCHAR(20) | NOT NULL | 미션 상태 ('IN_PROGRESS', 'COMPLETED') | +| created_at | DATETIME(6) | NOT NULL | 미션 시작일 | +| updated_at | DATETIME(6) | NOT NULL | 상태 변경일 (미션 완료일) | + +### **8. reviews** + +**설명**: 완료된 미션(user_missions)에 대해 작성된 리뷰. + +| 컬럼명 | 데이터 타입 | 제약 조건 | 설명 | +| --- | --- | --- | --- | +| id | BIGINT | PK, AI | 리뷰 고유 식별자 | +| user_mission_id | BIGINT | UNIQUE, FK, NOT NULL | 완료된 미션 ID (user_missions.id) | +| user_id | BIGINT | FK, NOT NULL | 작성자 ID (users.id) | +| store_id | BIGINT | FK, NOT NULL | 가게 ID (stores.id) | +| rating | INT | NOT NULL | 별점 (1~5) | +| content | TEXT | NOT NULL | 리뷰 내용 | +| created_at | DATETIME(6) | NOT NULL | 작성일 | +| updated_at | DATETIME(6) | NOT NULL | 수정일 | +1. **users와 missions의 관계 → N:M (다대다)** + +논리적 해석: + +→ 한 명의 사용자는 여러 개의 미션을 수행할 수 있다. + + 하나의 미션(예: "12,000원 이상 식사")은 여러 사용자에 의해 수행될 수 있다. + +물리적 설계: + +→ N:M 관계는 관계형 데이터베이스에서 직접 표현할 수 없으므로, 중간에 매핑 테이블인 user_missions를 둔다. + +→ users와 user_missions는 1:N 관계가 된다. (한 명의 유저는 여러 미션 수행 기록을 가짐) + +→ missions와 user_missions는 1:N 관계가 된다. (하나의 미션은 여러 유저에 의해 수행됨) + + 따라서 user_missions 테이블은 user_id와 mission_id를 외래 키(FK)로 가진다. + +1. **users와 food_categories의 관계 → N:M (다대다)** + +논리적 해석: + +→ 한 명의 사용자는 여러 음식 카테고리(한식, 중식 등)를 선호할 수 있다. + +→ 하나의 음식 카테고리는 여러 사용자에게 선택될 수 있다. + +물리적 설계: + +→ 위와 동일하게 N:M 관계이므로, 매핑 테이블인 user_food_preferences를 생성한다. + +→ user_food_preferences는 user_id와 food_category_id를 FK로 가져와 두 테이블을 연결한다. + +1. **regions와 stores의 관계 → 1:N (일대다)** + +논리적 해석: + +→ 하나의 지역("안암동")에는 여러 가게가 포함될 수 있다. + +→ 하나의 가게는 오직 하나의 지역에만 속한다. + +물리적 설계: + +→ 1:N 관계에서는 N쪽이 1쪽의 PK를 FK로 가진다. + + 따라서 stores 테이블이 regions 테이블의 PK인 id를 region_id라는 이름의 FK로 가진다. + +1. **stores와 missions의 관계 → 1:N (일대다)** + +논리적 해석: + +→ 하나의 가게는 여러 종류의 미션(예: 금액 미션, 메뉴 미션)을 가질 수 있다. + +→ 하나의 미션은 특정 가게 하나에만 종속된다. + +물리적 설계: + +→ missions 테이블(N쪽)이 stores 테이블(1쪽)의 PK인 id를 store_id라는 FK로 가진다. + +1. **user_missions와 reviews의 관계 → 1:1 (일대일)** + +논리적 해석: + +→ 하나의 완료된 미션 수행 기록(user_missions)에 대해서는 단 하나의 리뷰만 작성할 수 있다. + +→ 하나의 리뷰는 특정 미션 수행 기록 하나에 대해서만 작성된다. + +물리적 설계: + +→ 미션을 완료하지 않으면 리뷰를 쓸 수 없도록 강제하는 역할을 한다. + +→ reviews 테이블이 user_missions의 PK인 id를 user_mission_id라는 FK로 가진다. + +→ 이때 user_mission_id 컬럼에 UNIQUE 제약 조건을 걸어주어야 1:1 관계가 완성된다. + + 이렇게 하면 하나의 user_mission에 대해 두 개 이상의 리뷰가 생성되는 것을 원천적으로 차단할 수 있다. + +![image.png](attachment:8b114e34-8e73-46d3-a9e7-eb1833a44c50:image.png) + +https://www.erdcloud.com/d/TvdLkZEA7MbGNCfMp \ No newline at end of file diff --git a/week02/keyword/keyword.md b/week02/keyword/keyword.md new file mode 100644 index 0000000..b1a1c9c --- /dev/null +++ b/week02/keyword/keyword.md @@ -0,0 +1,84 @@ +- 데이터베이스 정규화 + + **제1정규형(1NF)** + + → 원자값을 갖도록 하는 것 + + → 하나의 컬럼에 여러 개의 값이 들어가지 않도록 분리 + + **제2정규형(2NF)** + + → 제1정규형을 만족하고, 부분 함수 종속성을 제거하는 단계 + + → 기본키가 여러 컬럼으로 구성된 복합키일 경우, 기본키의 일부에만 종속되는 컬럼을 별도의 테이블로 분리 + + **제3정규형(3NF)** + + → 제2정규형을 만족하고, 이행적함수 종속성을 제거하는 단계 + + → 기본키가 아닌 일반 컬럼에 종속되는 컬럼을 별도의 테이블로 분리 + + (ex/ 학생 테이블에 학과명과 학과 전화번호가 모두 있는 경우, 학과 전화번호는 학과명에 종속되므로 별도의 학과 테이블로 분리) + + **BCNF** + + → 제3정규형보다 더 엄격한 조건으로, 모든 결정자가 후보키 집합에 속하도록 하는 것 + +- 인덱스(Index) + + 정의: + + → 데이터베이스 테이블에서 원하는 데이터를 빠르게 찾기 위해 사용하는 자료구조(주소 정보) + + → 인덱스가 없으면 테이블 전체를 순차적으로 탐색해야 하므로 데이터 양이 많아질수록 검색 속도가 현저히 느려짐 + + **장점** + + → SELECT 쿼리의 검색 속도를 획기적으로 향상시킨다. + + → WHERE, JOIN, ORDER BY 등의 성능을 개선한다. + + → 시스템 전반의 부하를 줄여준다. + + **단점** + + → INSERT, UPDATE, DELETE 연산의 속도가 저하될 수 있다. + + → 너무 많은 인덱스는 오히려 옵티마이저의 판단을 흐리게 하여 성능을 저하시킬 수 있다. + + *어떤 컬럼에 인덱스를 걸어야 할지 신중하게 결정해야함* + + *주로 고유성 정도가 높고, WHERE 절의 조건으로 자주 사용되는 컬럼에 인덱스를 생성하는 것이 좋음* + +- ORM VS Raw SQL + + **ORM (Object-Relational Mapping, 객체-관계 매핑)**: 프로그래밍 언어의 객체와 관계형 데이터베이스의 데이터를 자동으로 연결해주는 기술. (예: JPA, Hibernate, Django ORM) + + **장점** + + → 생산성 향상 : SQL을 몰라도 객체지향적으로 DB를 다룰 수 있어 개발 속도가 빠르다. + + → 데이터베이스 종속성 감소 : DB를 변경해도 코드 수정이 거의 필요 + + → 유지보수 용이성 : SQL문에 코드에 섞여 있지 않아 가독성이 높고 유지보수가 편하다. + + **단점** + + → 세밀한 튜닝의 어려움 : 복잡하고 최적화된 쿼리를 자동으로 생성하기 어려움 + + → 학습 어려움 : ORM 자체의 동작 방식과 내부 구조를 이해하는 데 시간이 필요 + + **Raw SQL (또는 SQL Mapper)**: 개발자가 **SQL 쿼리문을 직접 작성**하여 데이터베이스와 소통하는 방식. SQL Mapper는 작성된 SQL과 객체를 매핑해주는 라이브러리를 의미하기도 한다. (예: MyBatis, JDBC) + + **장점** + + → 최고의 성능 : 개발자가 직접 쿼리를 작성하므로, 가장 최적화된 SQL 실행이 가능 + + → 높은 자유도 : 어떤 형태의 복잡한 쿼리라도 자유롭게 작성하고 실행 가능 + + **단점** + + → 생산성 저하 : 반복적인 CRUD SQL을 직접 작성해야 하므로 개발 속도가 느림 + → 데이터베이스 종속성 : DB가 변경되면 해당 DB의 문법에 맞게 SQL을 수정해야한다. + + 한쪽이 절대적으로 우월하기보다는 ORM은 빠른 개발과 유지보스에 유리하고, 복잡한 통계 쿼리보다는 일반적인 CRUD 작업이 주를 이루는 서비스에 용이하다. Raw SQL은 성능이 매우 중요하거나, 복잡한 집계/ 통계 쿼리, 대용량 데이터 처리가 필요할 때 좋다. \ No newline at end of file diff --git a/week02/mission/mission.md b/week02/mission/mission.md new file mode 100644 index 0000000..cc30999 --- /dev/null +++ b/week02/mission/mission.md @@ -0,0 +1,94 @@ +## 🔥 미션 +1주차 때 설계한 데이터베이스를 토대로 아래의 화면에 대한 쿼리를 작성 + +--- + +1. + +내가 진행중, 진행 완료한 미션 모아서 보는 쿼리(페이징 포함) + +```sql +SELECT s.name AS store_name, m.title, m.reward_points, um.status +FROM user_missions um +JOIN missions m ON um.mission_id = m.id +JOIN stores s ON m.store_id = s.id +WHERE um.user_id = :user_id +ORDER BY um.updated_at DESC +LIMIT :page_size OFFSET :offset; +``` + +**→** ‘사용자가 수행한 미션’이 주제이므로, 사용자와 미션의 연결고리인 user_missions 테이블에서 쿼리를 시작했다. + +**→** 상세 정보(미션 제목, 가게 이름)를 보충하기 위해 missions, stores 테이블을 조인했다. + +**→** 사용자 경험을 위해, 최근 활동을 먼저 보여주도록 updated_at 기준으로 내림차순 정렬했다. + +2. + +리뷰 작성하는 쿼리, +* 사진의 경우는 일단 배제 + +```sql +INSERT INTO reviews ( + user_mission_id, + user_id, + store_id, + content, + rating, + created_at, + updated_at +) VALUES ( + :user_mission_id, + :user_id, + :store_id, + :content, + :rating, + NOW(), + NOW() +); +``` + +**→** ‘미션을 완료해야만 리뷰를 쓸 수 있다’는 서비스의 핵심 규칙을 데이터베이스 차원에서 강제해야 했다. + +**→** 그래서 INSERT할 때 어떤 미션 완료 건에 대한 리뷰인지를 특정하는 user_mission_id를 필수값으로 포함시켰다. + + +3. + +홈 화면 쿼리 +(현재 선택 된 지역에서 도전이 가능한 미션 목록, 페이징 포함) + +```sql +SELECT s.name AS store_name, m.title, m.reward_points +FROM missions m +JOIN stores s ON m.store_id = s.id +JOIN regions r ON s.region_id = r.id +WHERE r.name = :region_name + AND NOT EXISTS ( + SELECT 1 + FROM user_missions um + WHERE um.mission_id = m.id AND um.user_id = :user_id + ) +ORDER BY m.created_at DESC +LIMIT :page_size OFFSET :offset; +``` + +→ 먼저, 미션, 가게, 지역 정보를 한 번에 가져와야 해서 missions, stores, regions 테이블을 JOIN했다. + +**→** ‘사용자가 아직 도전하지 않은 미션’이라는 조건을 위해, user_missions 테이블에 사용자 기록이 없는 (NOT EXITS) 미션만 필터링했다. + +**→** 성능을 위해 LIMIT와 OFFSET으로 페이징을 적용했다. + +4. + +마이 페이지 화면 쿼리 + +```sql +SELECT nickname, email, total_points +FROM users +WHERE id = :user_id; +``` + +**→** 필요한 정보(닉네임, 이메일, 포인트)가 모두 users ****테이블 하나에 다 있었다. + +**→** JOIN 없이 WHERE절로 특정 사용자만 바로 찾는 게 가장 직접적이고 빠른 방법이라 선택했다. \ No newline at end of file diff --git a/week03/mission/image.png b/week03/mission/image.png new file mode 100644 index 0000000..c53e51e Binary files /dev/null and b/week03/mission/image.png differ diff --git a/week03/mission/mission.md b/week03/mission/mission.md new file mode 100644 index 0000000..10ab169 --- /dev/null +++ b/week03/mission/mission.md @@ -0,0 +1,312 @@ +![image](./image.png) + +1. 회원가입 +## **Request Header** + +```json +Content-Type: application/json +``` + +## Request Body + +```json + "email": "user@example.com", + "password": "123456!", + "name": "박서윤", + "gender": "FEMALE", + "birthdate": "2003-06-02", + "address": "서울특별시 노원구", + "preferredFoods": ["한식", "중식", "일식"] +``` + +## Response + +```json +// 성공 +{ + "success": true, + "code": "S201", + "message": "회원가입이 완료되었습니다.", + "data": { + "userId": 1, + "name": "박서윤", + "accessToken": "...." + } +} +``` + +```json +// 실패(400 Bad Request) +{ + "success": false, + "code": "E4001", + "message": "이미 존재하는 이메일입니다.", + "data": null +} +``` + +2. 홈화면 데이터 조회 +## **Request Header** + +```json +Authorization: Bearer {accessToken} +``` + +## Request Body + +```json + // 없음 +``` + +## Response + +```sql +// 성공 +{ + "success": true, + "code": "S200", + "message": "홈 화면 정보 조회가 완료되었습니다.", + "data": { + "nickname": "노원구떵개", + "points": 500, + "missionProgress": { "currentCount": 7, "totalCount": 10 }, + "myMissions": [ + { + "missionId": 12, + "storeName": "반이학생마라탕", + "condition": "10,000원 이상의 식사시", + "reward": 500, + "dDay": 7 + } + ] + } +} +``` + +```json +// 실패(401 Unauthorized) +{ + "success": false, + "code": "E4010", + "message": "인증에 실패하였습니다. 유효한 토큰이 필요합니다.", + "data": null +} +``` + +3. 신규 리뷰 작성 +※ Path Variable → storeId : 리뷰를 작성할 가게의 고유 id + +## **Request Header** + +```json +Authorization: Bearer {accessToken} +Content-Type: application/json +``` + +## Request Body + +```json +{ + "rating": 4.5, + "content": "미션 덕분에 와봤는데 정말 맛있네요!" +} +``` + +## Response + +```sql +// 성공(201 Created) +{ + "success": true, + "code": "S201", + "message": "리뷰가 성공적으로 등록되었습니다.", + "data": { "reviewId": 101, "storeId": 25, "rating": 4.5, ... } +} +``` + +```json +// 실패(409 Conflict) +{ + "success": false, + "code": "E4090", + "message": "이미 해당 가게에 대한 리뷰를 작성했습니다.", + "data": null +} +``` +4. 마이페이지 리뷰 조회 +## **Request Header** + +```json +Authorization: Bearer {accessToken} +``` + +## Request Body + +```json +{ + "rating": 4.5, + "content": "미션 덕분에 와봤는데 정말 맛있네요!" +} +``` + +## Response + +```sql +// 성공(200 ok) +{ + "success": true, + "code": "S200", + "message": "작성한 리뷰 목록 조회가 완료되었습니다.", + "data": [ + { "reviewId": 101, "storeName": "반이학생마라탕", ... } + ] +} +``` + +5. 리뷰 수정 +※ Path Variable → reviewId : 리뷰 고유 id + +## **Request Header** + +```json +Authorization: Bearer {accessToken} +Content-Type: application/json +``` + +## Request Body + +```json +{ + "rating": 4.0, + "content": "내용 수정" +} +``` + +## Response + +```sql +// 성공 +{ + "success": true, + "code": "S200", + "message": "리뷰가 성공적으로 수정되었습니다.", + "data": { "reviewId": 101, "rating": 4.0, ... } +} +``` +6. 리뷰 삭제 +※ Path Variable → reviewId : 삭제할 리뷰 고유 id + +## **Request Header** + +```json +Authorization: Bearer {accessToken} +``` + +## Request Body + +```json +// 없음 +``` + +## Response + +```sql +// 성공 +{ + "success": true, + "code": "S200", + "message": "리뷰가 성공적으로 삭제되었습니다.", + "data": { "reviewId": 101 } +} +``` + +7. 미션 목록 조회 +## **Request Header** + +```json +Authorization: Bearer {accessToken} +``` + +## Request Body + +```json +// 없음 +``` + +## Response + +```sql + // 성공(200 ok) + { + "success": true, + "code": "S200", + "message": "미션 목록 조회가 완료되었습니다.", + "data": [ + { + "missionId": 12, + "storeName": "반이학생마라탕", + "condition": "10,000원 이상의 식사시", + "reward": 500, + "status": "IN_PROGRESS" + } + ]/ +``` + +```json +// 실패(400 Bad Request) status 값이 잘못됐을때 +JSON + +{ + "success": false, + "code": "E4003", + "message": "잘못된 미션 상태입니다. 'in-progress' 또는 'completed'를 사용해주세요.", + "data": null +} + +``` + +QueryString: + +status(String): 미션 상태 필터링(in-progress/ completed) + +/api/v1/missions?status=in-progress + + +8. 미션 성공 요청 +※ Path Variable → missionId 성공 미션 고유 id + +## **Request Header** + +```json +Authorization: Bearer {accessToken} +``` + +## Request Body + +```json +// 없음 +``` + +## Response + +```sql +// 성공(200 ok) + { + "success": true, + "code": "S200", + "message": "미션 성공 요청이 완료되었습니다.", + "data": { + "authCode": "920394810" + } +} +``` + +```json +// 실패(404) +{ + "success": false, + "code": "E4040", + "message": "해당 미션을 찾을 수 없습니다.", + "data": null +} +``` \ No newline at end of file diff --git a/week04/keyword/keyword.md b/week04/keyword/keyword.md new file mode 100644 index 0000000..a7d179c --- /dev/null +++ b/week04/keyword/keyword.md @@ -0,0 +1,68 @@ +- ES + + ECMA Script + + JavaScript의 문법과 동작을 정의하는 공식 표준 + +- ES6 + + JavaScript의 문법과 동작을 정의하는 공식 표준 + + - ES6의 주요 변화 및 특징 + → 2015년에 발표된 ES의 6번째 버전 + + → 기존 자바스크립트의 한계를 개선하고 다양한 기능을 추가하여 코드의 **가독성과 유지보수성**을 크게 향상시킴. + + → 가장 큰 변화를 가져왔기에 현대 자바스크립트의 기준으로 여겨짐. + + 주요 변화: `let/const`, 화살표 함수, 클래스, 템플릿 리터럴, 구조 분해 할당, Promise, ES Module 등이 있음 + + - ES6를 중요시 하는 이유 + 코드의 **가독성과 유지보수성** 크게 향상 +- ES Module +자바스크립트 코드를 독립적인 파일(모듈)로 분리하고, `import`(가져오기)와 `export`(내보내기) 키워드를 사용해 서로의 기능을 공유할 수 있게 하는 공식 표준 모듈 시스템. + + 코드의 **재사용성과 관리**가 매우 용이해짐. + +- 프로젝트 아키텍처 + 소프트웨어의 기본적인 구조 또는 설계도 + - 프로젝트 아키텍처가 중요한 이유 + + **유지보수성과 확장성** + + - Service-Oriented Architecture(Service Layer Pattern) + + 서비스 지향 아키텍처는 애플리케이션의 로직을 독립적인 "서비스" 단위로 분리하여 구성하는 아키텍처 패턴 + + 각 부분이 독립적으로 작동하여 수정과 확장이 쉽다는 장점 + + - **Controller**: 클라이언트의 요청을 받고 응답을 보내는 역할. + - **Service**: 핵심적인 비즈니스 로직을 처리하는 역할. + - **Repository**: 데이터베이스와 직접 상호작용하는 역할. + - MVC 패턴 + + **Model-View-Controller**의 약자로, 애플리케이션을 세 가지 역할로 구분하는 매우 대중적인 아키텍처 패턴 + + - 그 외 다른 프로젝트 구조 + + 모놀리식 아키텍처: + **모든 기능을 하나의 큰 프로젝트에 통합**하여 개발하는 가장 전통적인 방식 + + 레이어드 아키텍처: + + 코드를 역할에 따라 논리적인 계층으로 나누는 구조 + + - **표현 계층 (Presentation Layer)**: UI 등 사용자에게 보이는 부분 + - **비즈니스 계층 (Business Layer)**: 핵심 비즈니스 로직 처리 + - **데이터 접근 계층 (Data Access Layer)**: 데이터베이스와 상호작용 + + 마이크로서비스 아키텍처: + 하나의 거대한 애플리케이션을 **여러 개의 작고 독립적인 서비스**로 잘게 나누어 개발하는 방식 + +- 비즈니스 로직 +서비스의 핵심 규칙과 데이터 처리 과정 + + +- DTO + + 계층 간 데이터 전송을 위해 사용되는 객체 \ No newline at end of file diff --git a/week04/mission/image.png b/week04/mission/image.png new file mode 100644 index 0000000..4814099 Binary files /dev/null and b/week04/mission/image.png differ diff --git a/week04/mission/mission.md b/week04/mission/mission.md new file mode 100644 index 0000000..65e8f57 --- /dev/null +++ b/week04/mission/mission.md @@ -0,0 +1,24 @@ +1. 로컬 DB 만들어서 users 테이블 생성 +![alt text](image.png) + + +CREATE DATABASE my_db; +USE my_db; + +CREATE TABLE users ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 사용자 고유 ID (자동 증가) + email VARCHAR(255) NOT NULL UNIQUE, -- 이메일 (필수, 중복 불가) + password VARCHAR(255) NOT NULL, -- 비밀번호 (필수) + nickname VARCHAR(50) NOT NULL UNIQUE, -- 닉네임 (필수, 중복 불가) + gender VARCHAR(10), -- 성별 + birth_date DATE, -- 생년월일 + address_main VARCHAR(255), -- 주소 + total_points INT NOT NULL DEFAULT 0, -- 보유 포인트 (기본값 0) + status VARCHAR(15) NOT NULL DEFAULT 'ACTIVE', -- 계정 상태 (기본값 'ACTIVE') + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), -- 생성 시각 + updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), -- 수정 시각 + deleted_at DATETIME(6) -- 삭제 시각 (Soft Delete용) +); + +2. 파일구조 만들어서 깃허브에 푸시 +https://github.com/Park-seoyun/umc-workbook \ No newline at end of file diff --git a/week05/keyword/keyword.md b/week05/keyword/keyword.md new file mode 100644 index 0000000..fe93395 --- /dev/null +++ b/week05/keyword/keyword.md @@ -0,0 +1,50 @@ +- 환경 변수 + + 코드 밖에서 주입하는 설정값 + + - DB 비밀번호, API 키처럼 코드에 직접 하드코딩하면 안 되는 민감한 정보나, 배포 환경(개발, 테스트, 운영)에 따라 달라지는 설정을 저장하는 변수 +- CORS + + 다른 출처(도메인)의 리소스 공유를 허용하는 정책 + + - 웹 브라우저의 강력한 보안 정책인 SOP(Same-Origin Policy, 동일 출처 정책)로 인해, 기본적으로 한 웹사이트(`http://localhost:3000`)는 다른 출처(`http://localhost:8080`)의 API를 호출할 수없다. + - 위 문제를 해결하기 위해, 서버가 "저 출처(`localhost:3000`)는 믿을 수 있으니 데이터를 줘도 돼"라고 허용해주는 메커니즘 + - 서버는 응답(Response) 헤더에 `Access-Control-Allow-Origin: http://localhost:3000` 같은 정보를 담아서 브라우저에 보내줘야 한다. +- DB Connection, DB Connection Pool + + DB와의 통로를 미리 만들고 재사용하기 + + - **DB Connection:** + - 애플리케이션(서버)이 DB와 통신하기 위해 연결하는 '통로' + - 이 통로를 만들고(인증, 세션 맺기) 닫는 과정은 시간과 자원이 많이 드는 비싼 작업 + - **DB Connection Pool (DBCP):** + - 사용자 요청이 올 때마다 비싼 Connection을 새로 만들지 않기 위해, 미리 여러 개의 Connection을 만들어 두고 '수영장(Pool)'처럼 관리하는 기술 + - 동작: + 1. 요청 발생 -> Pool에서 놀고 있는 Connection 1개를 빌려줌. + 2. DB 작업(SQL) 수행. + 3. 작업 완료 -> Connection을 닫지 않고 Pool에 반납(재사용 대기). + - 장점: 매번 연결을 만드는 비용이 사라져 애플리케이션 성능이 압도적으로 향상 +- 비동기 (async, await) + + 기다리지 않고 다음 일을 처리하는 방식 (feat. 깔끔한 코드) + + - **동기(Synchronous):** A 작업(예: DB 조회)이 끝날 때까지 10초가 걸리면, B 작업은 A가 끝날 때까지 기다린다**.** (프로그램 전체가 멈춤) + - **비동기(Asynchronous):** A 작업(DB 조회)을 시켜놓고, **결과를 기다리지 않고** 바로 B 작업을 시작합니다. A 작업이 10초 뒤에 끝나면 "저 끝났어요!" 하고 결과를 알려준다. (기다리는 동안 다른 일을 처리) + - **async / await 란?** + - 이런 비동기 코드는 원래 콜백(Callback)이나 `.then()`을 사용해서 코드가 복잡해지기 쉬웠다. (일명 '콜백 지옥') + - `async` / `await`는 비동기 코드를 **동기식 코드처럼** 순서대로 읽기 쉽게 만들어주는 '문법적 설탕(Syntactic Sugar)'입니다. + - **`async`**: "이 함수는 비동기 작업을 포함합니다"라고 선언. + - **`await`**: "이 비동기 작업(예: DB 조회)이 끝날 때까지 (이 함수 안에서만) 기다려줘"라고 지시. (프로그램 전체를 멈추는 것이 아님!) +- try/catch/finally + + 터지지 않게 관리하는 예외(에러) 처리 + + - 프로그램이 실행되다가 예기치 않은 오류(예외)가 발생했을 때, 프로그램이 비정상적으로 중단(Crash)되는 것을 막고 적절하게 대처하기 위한 구문 + - **`try { ... }`** + - **에러가 발생할 가능성이 있는** 코드(예: DB 연결, 파일 읽기)를 이 안에 넣는다. + - **`catch (error) { ... }`** + - `try` 블록 안에서 에러가 발생하면, 프로그램이 **즉시 멈추는 대신** 이 `catch` 블록으로 이동하여 실행된다. + - 이곳에서 `error` 객체를 받아 에러 로그를 남기거나, 사용자에게 "오류가 발생했습니다"라는 메시지를 보낼 수 있다. + - **`finally { ... }`** + - `try`가 성공하든, `catch`가 실행되든(에러가 나든 안 나든) 상관없이, 항상 마지막에 무조건 실행되는 코드 블록 + - 주로 사용한 자원(예: 파일, DB 커넥션)을 안전하게 닫아주는(뒷정리) 역할 \ No newline at end of file diff --git a/week05/mission/mission.md b/week05/mission/mission.md new file mode 100644 index 0000000..c98b7a0 --- /dev/null +++ b/week05/mission/mission.md @@ -0,0 +1 @@ +https://github.com/Park-seoyun/umc-workbook/tree/feature/chapter-05 \ No newline at end of file diff --git a/week06/keyword/keyword.md b/week06/keyword/keyword.md new file mode 100644 index 0000000..b67276a --- /dev/null +++ b/week06/keyword/keyword.md @@ -0,0 +1,42 @@ +- ORM + + **정의:** SQL 쿼리를 직접 작성하는 대신, 익숙한 프로그래밍 언어(예: JavaScript)의 객체와 메소드를 사용하여 데이터베이스를 조작할 수 있도록 매핑해주는 도구(라이브러리). + + - **사용 예시:** + - **Raw SQL:** `SELECT * FROM User WHERE id = 1;` + - **ORM (Prisma):** `await prisma.user.findUnique({ where: { id: 1 } });` +- Prisma 문서 살펴보기 + - ex. Prisma의 Connection Pool 관리 방법 + + + - ex. Prisma의 Migration 관리 방법 + + **개념:** `schema.prisma` 파일의 변경 이력을 **SQL 파일로 자동 생성**하고, 이력을 기반으로 DB 스키마를 **자동 적용/관리**하는 도구. + + **주요 명령어:** + + - **`npm exec prisma migrate dev`**: **(개발용)** + - `schema.prisma`와 실제 DB를 비교하여 변경 사항으로 **새로운 Migration 파일(SQL)을 생성**합니다. + - 생성된 Migration을 DB에 **즉시 적용**합니다. + - (주의: 기존 테이블/데이터 삭제 경고가 뜰 수 있음!) + - **`npm exec prisma migrate deploy`**: **(운영/배포용)** + - **새로운 Migration을 생성하지 않습니다.** + - `migrations` 폴더에 이미 존재하는 **모든 Migration 이력**을 순서대로 DB에 적용합니다. + - **`npm exec prisma migrate reset`**: **(개발용/초기화)** + - DB를 **초기화(Drop & Create)**합니다. (모든 데이터 삭제!) + - `migrations` 폴더의 모든 이력을 처음부터 다시 적용 +- ORM(Prisma)을 사용하여 좋은 점과 나쁜 점 + + **장점** + + - **생산성 및 가독성:** SQL 문자열보다 객체 기반 코드가 더 **직관적**이고 **가독성**이 높으며, **유지보수**가 쉬워집니다. + - **개발 편의성:** IDE(편집기)의 **자동 완성(Auto-completion)** 지원을 받을 수 있어 오타를 줄이고 개발 속도를 높일 수 있습니다. + - **데이터 가공 용이:** DB에서는 최소한의 조회/정렬만 수행하고, 복잡한 데이터 가공 및 조합은 JavaScript **코드 레벨**에서 처리하는 최근 트렌드에 적합합니다. + + **단점** + + - **학습 곡선:** ORM 자체의 사용법, 개념, 문법(예: `schema.prisma`)을 별도로 학습해야 합니다. + - **복잡성 증가:** + - 복잡하고 세밀하게 튜닝된 SQL 쿼리를 ORM으로 표현하기 어려울 수 있습니다. + - 문제가 발생했을 때 ORM이 생성하는 실제 SQL을 파악하고 디버깅하기 어려울 수 있습니다. (Prisma는 `log: ["query"]` 옵션으로 생성 SQL 확인 가능) + - **협업 복잡성:** 특히 **Migration** 기능을 팀 단위로 사용할 때, 변경 이력이 꼬이거나 충돌할 위험이 있어 명확한 **팀 규칙**이 필요합니다. \ No newline at end of file diff --git a/week06/mission/mission.md b/week06/mission/mission.md new file mode 100644 index 0000000..73313e9 --- /dev/null +++ b/week06/mission/mission.md @@ -0,0 +1 @@ +https://github.com/Park-seoyun/umc-workbook/tree/feature/chapter-06 \ No newline at end of file diff --git a/week07/keyword/keyword.md b/week07/keyword/keyword.md new file mode 100644 index 0000000..d5c019c --- /dev/null +++ b/week07/keyword/keyword.md @@ -0,0 +1,38 @@ +- 미들웨어 + + ### 정의 + + Express에서 미들웨어는 **HTTP 요청과 응답 사이에서 실행되는 함수**입니다. 요청 객체(`req`), 응답 객체(`res`), 그리고 다음 미들웨어를 호출하는 `next` 함수에 접근할 수 있습니다. + + ### 주요 속성 + + - **순차적 실행**: 등록된 순서대로 실행됨 + - **체이닝**: `next()`를 통해 다음 미들웨어로 제어권 전달 + - **전역/라우트별 적용**: `app.use()` 또는 특정 라우트에만 적용 가능 + - **요청/응답 수정 가능**: `req`, `res` 객체를 자유롭게 조작 가능 +- HTTP 상태 코드 + + ### 정의 + + HTTP 상태 코드는 **클라이언트의 요청에 대한 서버의 처리 결과를 나타내는 3자리 숫자 코드**입니다. 요청이 성공했는지, 실패했는지, 어떤 종류의 에러인지를 표준화된 방식으로 전달합니다. + + ### 주요 분류 + + - **1xx (정보)**: 요청을 받았으며 프로세스를 계속 진행 + - **2xx (성공)**: 요청을 성공적으로 처리 (200 OK, 201 Created) + - **3xx (리다이렉션)**: 요청 완료를 위해 추가 동작 필요 + - **4xx (클라이언트 에러)**: 잘못된 요청 (400, 401, 404, 409) + - **5xx (서버 에러)**: 서버가 요청 처리 실패 (500, 502, 503) +- 에러 핸들링(Error Handling) + + ### 정의 + + 에러 핸들링은 **프로그램 실행 중 발생하는 예외 상황을 적절히 포착하고 처리하는 방법**입니다. Express에서는 try-catch 블록과 전역 에러 미들웨어를 통해 체계적으로 에러를 관리합니다. + + ### 주요 구성 요소 + + - **try-catch**: 에러 포착 + - **throw**: 에러 발생시키기 + - **커스텀 Error 클래스**: 에러 타입별 구체화 + - **전역 에러 핸들러**: 모든 에러를 한 곳에서 처리 + - **에러 코드**: 에러를 식별하는 고유 코드 (U001, R001 등) \ No newline at end of file diff --git a/week07/mission/mission.md b/week07/mission/mission.md new file mode 100644 index 0000000..7d92cd3 --- /dev/null +++ b/week07/mission/mission.md @@ -0,0 +1 @@ +https://github.com/Park-seoyun/umc-workbook/tree/feature/chapter-07 \ No newline at end of file diff --git a/week08/keyword/keyword.md b/week08/keyword/keyword.md new file mode 100644 index 0000000..86a0dc7 --- /dev/null +++ b/week08/keyword/keyword.md @@ -0,0 +1,17 @@ +- Swagger + + RESTful API를 설계, 빌드, 문서화 및 사용하는 데 도움이 되는 Open API 사양을 중심으로 구축된 오픈 소스 도구 + + Swagger는 어떤 방식으로 통신해야 할 지 알려주는 API 명세를 도와주는 도구 + +- OpenAPI + + RESTful API를 정의하기 위한 표준 명세 + + API의 엔드포인트, 작동 방식, 파라미터, 응답 포맷, 인증 방법 등을 언어 중립적이고 기계가 읽을 수 있는 형태로 기술하는 방법을 정의 + +- OpenAPI Component + + OpenAPI Specification (OAS, 구 Swagger) 문서에서 **재사용 가능한 정의**들을 모아두는 섹션 + + API 문서 내에서 반복적으로 사용되는 데이터 구조, 보안 방식, 요청/응답 헤더 등을 한 곳에 정의해 놓고, 필요한 곳에서 참조(Reference)하여 사용함으로써 문서의 **가독성을 높이고 중복을 줄이는 역할** \ No newline at end of file diff --git a/week08/mission/mission.md b/week08/mission/mission.md new file mode 100644 index 0000000..75236dc --- /dev/null +++ b/week08/mission/mission.md @@ -0,0 +1,244 @@ +https://github.com/Park-seoyun/umc-workbook/tree/feature/chapter-08 + +# 미션 기록 + +## 변경 사항 + +### CORS 설정 추가 + +**Before:** + +```jsx +app.use(cors()); *// 모든 출처 허용* +``` + +**After:** + +```jsx +app.use(cors({ + origin: ["http://localhost:3000", "http://127.0.0.1:5500"], + credentials: true, *// 쿠키 허용* +})); +``` + +### Swagger 엔드포인트 추가 + +```jsx +*// Swagger UI: http://localhost:3000/docs* +app.use("/docs", swaggerUiExpress.serve, swaggerUiExpress.setup(...)); + +*// OpenAPI JSON: http://localhost:3000/openapi.json* +app.get("/openapi.json", async (req, res, next) => { ... }); +``` + +## Swagger 주석 구조 + +### 기본 구조 + +```jsx +export const handleAPI = async (req, res, next) => { + */** + #swagger.summary = 'API 제목'; + #swagger.tags = ['태그명'] + + // Path 파라미터 + #swagger.parameters['id'] = { + in: 'path', + required: true, + type: 'integer', + description: '설명', + example: 1 + } + + // Request Body + #swagger.requestBody = { ... }; + + // 성공 응답 + #swagger.responses[200] = { ... }; + + // 실패 응답 + #swagger.responses[404] = { ... }; + **/* + + *// 실제 로직...* +}; +``` + +### RequestBody 예시 + +```jsx +#swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["필수필드1", "필수필드2"], + properties: { + email: { + type: "string", + format: "email", + example: "user@example.com" + }, + password: { + type: "string", + format: "password", + example: "pass123" + } + } + } + } + } +}; +``` + +### 성공 응답 예시 + +```jsx +#swagger.responses[200] = { + description: "성공 설명", + content: { + "application/json": { + schema: { + type: "object", + properties: { + resultType: { type: "string", example: "SUCCESS" }, + error: { type: "object", nullable: true, example: null }, + success: { + type: "object", + properties: { + data: { type: "string", example: "응답 데이터" } + } + } + } + } + } + } +}; +``` + +### 실패 응답 예시 + +```jsx +#swagger.responses[404] = { + description: "리소스를 찾을 수 없음", + content: { + "application/json": { + schema: { + type: "object", + properties: { + resultType: { type: "string", example: "FAIL" }, + error: { + type: "object", + properties: { + errorCode: { type: "string", example: "U001" }, + reason: { type: "string", example: "오류 메시지" }, + data: { type: "object" } + } + }, + success: { type: "object", nullable: true, example: null } + } + } + } + } +}; +``` + +## Components (재사용 스키마) + +index.js의 doc.components.schemas에 정의: + +```jsx +components: { + schemas: { + SuccessResponse: { + type: "object", + properties: { + resultType: { type: "string", example: "SUCCESS" }, + error: { type: "object", nullable: true, example: null }, + success: { type: "object" } + } + }, + ErrorResponse: { + type: "object", + properties: { + resultType: { type: "string", example: "FAIL" }, + error: { type: "object" }, + success: { type: "object", nullable: true, example: null } + } + } + } +} +``` + +**사용 방법:** + +```jsx +#swagger.responses[500] = { + description: "서버 오류", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/ErrorResponse" + } + } + } +}; +``` + +## API별 적용 + +### 1. 회원가입 (POST /api/v1/users/signup) + +![image.png](attachment:2d36c792-002d-4068-a1da-09c9768169b3:image.png) + +![image.png](attachment:5a9f45be-719d-4a28-a459-2daa9ac94295:image.png) + +- Request Body: email, password, nickname, gender, birth_date, address_main, preferences +- 200 Success: 회원가입 성공 +- 409 Conflict: 이메일/닉네임 중복 (U001, U002) +- 500 Error: 서버 오류 + +### 2. 가게 추가 (POST /api/v1/regions/:regionId/stores) + +![image.png](attachment:842f21c9-edb3-48dc-8c84-055a49bc583e:image.png) + +![image.png](attachment:ef16fb13-3806-433a-bccb-3c8a25f75d4e:image.png) + +- Path Parameter: regionId +- Request Body: name, address +- 201 Created: 가게 추가 성공 +- 404 Not Found: 지역 없음 (R001) + +### 3. 리뷰 추가 (POST /api/v1/stores/:storeId/reviews) + +![image.png](attachment:ca25b182-7ebb-411e-8354-8333e497a937:image.png) + +![image.png](attachment:04d0f6df-0f54-4ee2-8e92-05e747abe55e:image.png) + +- Path Parameter: storeId +- Request Body: rating (1-5), content +- 201 Created: 리뷰 추가 성공 +- 404 Not Found: 가게 없음 (S001) + +### 4. 미션 추가 (POST /api/v1/stores/:storeId/missions) + +![image.png](attachment:9ae212ff-72bf-45a9-bfe4-089a15d2e450:image.png) + +![image.png](attachment:fad9fb9a-23b7-4e1d-9020-ed5ff44399c2:image.png) + +- Path Parameter: storeId +- Request Body: title, description, reward_points +- 201 Created: 미션 추가 성공 +- 404 Not Found: 가게 없음 (S001) + +### 5. 미션 도전 (POST /api/v1/missions/:missionId/challenge) + +![image.png](attachment:00b6ff7b-a6fb-475a-90aa-dd353b1e3245:image.png) + +![image.png](attachment:b8a0e43c-57b7-40f1-aa86-3b2fce2e6a1c:image.png) + +- Path Parameter: missionId +- 201 Created: 미션 도전 시작 +- 404 Not Found: 미션 없음 (M001) +- 409 Conflict: 이미 도전 중 (M002) \ No newline at end of file diff --git a/week09/keyword/keyword.md b/week09/keyword/keyword.md new file mode 100644 index 0000000..0171c0d --- /dev/null +++ b/week09/keyword/keyword.md @@ -0,0 +1,23 @@ +- OAuth 2.0 + + **"권한 부여를 위한 프로토콜(약속)"** + + - **개념:** 사용자가 비밀번호를 직접 알려주지 않아도, 특정 애플리케이션이 사용자의 정보(구글, 카카오 등)에 접근할 수 있도록 권한을 위임해 주는 표준 프로토콜 +- JWT + + **"정보를 담은 토큰(티켓)의 형식"** + + - **개념:** 인증에 필요한 정보(유저 ID, 만료 시간 등)를 암호화된 JSON 문자열로 만든 **토큰의 표준 규격** + - **구조:** `aaaaa.bbbbb.ccccc` 처럼 점(.)으로 구분된 세 부분(헤더, 페이로드, 서명)으로 이루어져 있다. + - **장점:** 서버가 별도의 세션 저장소(메모리)를 뒤지지 않아도, **토큰 자체만 보면 "이 사람이 누구고, 위조되지 않았는지" 바로 알 수 있다.** +- Bearer Token + + **"토큰을 사용하는 방식(운반자)"** + + - **개념:** HTTP 통신을 할 때 토큰을 서버로 보내는 **규칙 중 하나** + - **의미:** "Bearer"는 "소지자"라는 뜻입니다. 즉, **"이 토큰을 가지고 있는(Bearer) 사람에게 권한을 줘라"**라는 의미 + - **사용법:** 주로 HTTP 헤더에 다음과 같이 담아서 보낸다. + + > Authorization: Bearer <여기에_JWT_토큰> + > + - **주의점:** 마치 현금이나 영화 표와 같다. 잃어버리면 **주운 사람이 주인 행세**를 할 수 있으므로 HTTPS 등을 통해 탈취되지 않게 관리해야 한다. \ No newline at end of file diff --git a/week09/mission/mission.md b/week09/mission/mission.md new file mode 100644 index 0000000..dbf698d --- /dev/null +++ b/week09/mission/mission.md @@ -0,0 +1,69 @@ +### 미션 1: 하드코딩 제거 + +**Before (기존):** + +```jsx +*// review.controller.js* +const userId = 1; *// 하드코딩// mission.controller.js* +const userId = 1; *// 하드코딩* +``` + +**After (개선):** + +```jsx +*// review.controller.js* +const userId = req.user.id; *// JWT에서 가져옴// mission.controller.js* +const userId = req.user.id; *// JWT에서 가져옴* +``` + +### 미션 2: 사용자 정보 수정 API 추가 + +**새 API 추가:** + +PATCH /api/v1/users/me + +**Request:** + +```json +{ + "nickname": "새닉네임", + "gender": "MALE", + "birth_date": "1995-05-15", + "address_main": "서울시 강남구" +} +``` + +**Response:** + +```json +{ + "resultType": "SUCCESS", + "error": null, + "success": { + "id": 1, + "email": "user@example.com", + "nickname": "새닉네임", + "gender": "MALE", + "birth_date": "1995-05-15", + "address_main": "서울시 강남구" + } +} +``` + +### 미션 3: JWT 인증 미들웨어 적용 (auth.config.js) + +**isLogin 미들웨어:** + +```json +const isLogin = passport.authenticate('jwt', { session: false }); +``` + +**보호된 API 목록:** +API인증 필요 +POST /api/v1/users/signup 로그인 불필요 +공개GET /api/v1/users/me 로그인 필요 +PATCH /api/v1/users/me 로그인 필요 +POST /api/v1/regions/:regionId/stores 로그인 필요 +POST /api/v1/stores/:storeId/reviews 로그인 필요 +POST /api/v1/stores/:storeId/missions 로그인 필요 +POST /api/v1/missions/:missionId/challenge 로그인 필요 \ No newline at end of file diff --git a/week10/keyword/keyword.md b/week10/keyword/keyword.md new file mode 100644 index 0000000..e83bb12 --- /dev/null +++ b/week10/keyword/keyword.md @@ -0,0 +1,38 @@ +- CI/CD + + **"코드 작성부터 배포까지의 과정을 자동화하는 것”** + + **CI (Continuous Integration):** + + - 여러 개발자가 작성한 코드를 자주(지속적으로) 합치고, 자동으로 테스트하여 에러를 미리 잡아내는 과정 + + **CD (Continuous Deployment/Delivery):** + + - 테스트를 통과한 코드를 실제 사용자가 쓰는 서버에 자동으로 배포하는 과정 + + **핵심:** 개발자가 수동으로 서버에 파일을 올리는 수고를 없애고, 실수를 방지 + +- GitHub Actions + + **"GitHub에서 제공하는 CI/CD 자동화 도구"** + + - **역할:** CI/CD를 실현시켜 주는 구체적인 도구(Tool) + - **특징:** GitHub 리포지토리(저장소)와 찰떡궁합입니다. `.yml` 파일을 만들어 "누군가 코드를 `push`하면, 자동으로 빌드하고 테스트해라"라고 명령을 내릴 수 있다. + - **비유:** 공장의 '자동 조립 로봇'과 같다. +- Reverse Proxy + + **"클라이언트와 서버 사이의 중계자"** + + - **개념:** 사용자가 웹사이트에 접속할 때, 요청을 가장 먼저 받아서 내부의 실제 서버(Backend)로 전달해 주는 서버입니다. (대표적인 예: Nginx, Apache) + + **왜 쓰는가?** + + - **보안:** 실제 서버의 IP를 숨길 수 있다. + - **부하 분산 (Load Balancing):** 사용자가 많을 때 여러 서버로 요청을 나눠준다. + - **HTTPS 처리:** 암호화/복호화 과정을 여기서 담당하여 뒷단의 서버 부담을 줄인다. +- HTTPS + + **"보안이 강화된 인터넷 통신 규약"** + + - **개념:** 기존의 HTTP에 SSL/TLS라는 자물쇠를 채운 것이다. + - **역할:** 사용자가 보내는 데이터(비밀번호 등)를 암호화하여 해커가 중간에서 가로채도 내용을 알 수 없게 만든다. \ No newline at end of file diff --git a/week10/mission/mission.md b/week10/mission/mission.md new file mode 100644 index 0000000..a99c749 --- /dev/null +++ b/week10/mission/mission.md @@ -0,0 +1,82 @@ +/.github/workflows/deploy-main.yml + +```jsx +name: deploy-main + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure SSH + run: | + mkdir -p ~/.ssh + echo "$EC2_SSH_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + + cat >>~/.ssh/config <