Skip to content

win929/spring-gift-order

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

👥 회원 API


🎫 회원 가입

Request

POST /api/members/register HTTP/1.1
Content-Type: application/json
host: localhost:8080

{
    "email": "admin@email.com",
    "password": "password"
}

Response

HTTP/1.1 201 Created
Content-Type: application/json

{
    "token": ""
}
🔑 로그인

Request

POST /api/members/login HTTP/1.1
Content-Type: application/json
host: localhost:8080

{
    "email": "abc@gmail.com",
    "password": "123qwe"
}

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "token": ""
}

📦 상품 API


🔎 상품 조회 (전체 상품)

Request

GET /api/products HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
    {
        "id": 1,
        "name": "아이스 카페 아메리카노 T",
        "price": 4500,
        "imageUrl": "https://st.kakaocdn.net/product/api/product/20231010111814_9a667f9eccc943648797925498bdd8a3.jpg"
    },
    {
        "id": 2,
        "name": "(ICE)아메리카노",
        "price": 2000,
        "imageUrl": "https://img1.kakaocdn.net/thumb/C320x320@2x.fwebp.q82/?fname=https%3A%2F%2Fst.kakaocdn.net%2Fproduct%2Fgift%2Fproduct%2F20220622112804_d176787353ab48c690936557eefad11c.jpg"
    }
]
🔎 상품 조회 (특정 상품)

Request

GET /api/products/{productId} HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "id": 1,
    "name": "아이스 카페 아메리카노 T",
    "price": 4500,
    "imageUrl": "https://st.kakaocdn.net/product/api/product/20231010111814_9a667f9eccc943648797925498bdd8a3.jpg"
}
➕ 상품 추가

Request

POST /api/products HTTP/1.1
Content-Type: application/json

{
    "name": "(ICE)아메리카노",
    "price": 2000,
    "imageUrl": "https://img1.kakaocdn.net/thumb/C320x320@2x.fwebp.q82/?fname=https%3A%2F%2Fst.kakaocdn.net%2Fproduct%2Fgift%2Fproduct%2F20220622112804_d176787353ab48c690936557eefad11c.jpg"
}

Response

HTTP/1.1 201 Created
Content-Type: application/json

{
    "id": 1,
    "name": "(ICE)아케리카노",
    "price": 2000,
    "imageUrl": "https://img1.kakaocdn.net/thumb/C320x320@2x.fwebp.q82/?fname=https%3A%2F%2Fst.kakaocdn.net%2Fproduct%2Fgift%2Fproduct%2F20220622112804_d176787353ab48c690936557eefad11c.jpg"
}
✏️ 상품 수정

Request

PUT /api/products/{productId} HTTP/1.1
Content-Type: application/json

{
    "name": "[EVENT](ICE)아메리카노",
    "price": 1600,
    "imageUrl": "https://img1.kakaocdn.net/thumb/C320x320@2x.fwebp.q82/?fname=https%3A%2F%2Fst.kakaocdn.net%2Fproduct%2Fgift%2Fproduct%2F20250515110714_9664acdff2b84e4e806c4d7d55dd8de0.jpg"
}

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "id": 1,
    "name": "[EVENT](ICE)아메리카노",
    "price": 1600,
    "imageUrl": "https://img1.kakaocdn.net/thumb/C320x320@2x.fwebp.q82/?fname=https%3A%2F%2Fst.kakaocdn.net%2Fproduct%2Fgift%2Fproduct%2F20250515110714_9664acdff2b84e4e806c4d7d55dd8de0.jpg"
}
❌ 상품 삭제

Request

DELETE /api/products/{productId} HTTP/1.1

Response

HTTP/1.1 204 No Content

🔘 옵션 API


🔎 옵션 조회

Request

GET /api/products/{productId}/options HTTP/1.1

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
    {
        "id": 1,
        "name": "기본 옵션",
        "quantity": 1
    },
    {
        "id": 2,
        "name": "옵션 2",
        "quantity": 2
    }
]
➕ 옵션 추가

Request

POST /api/products/{productId}/options HTTP/1.1
Content-Type: application/json

{
    "name": "옵션 1",
    "quantity": 1
}

Response

HTTP/1.1 201 Created
Content-Type: application/json

{
    "id": 1,
    "name": "옵션 1",
    "quantity": 1
}
✏️ 옵션 수정

Request

PUT /api/products/{productId}/options/{optionId} HTTP/1.1
Content-Type: application/json

{
    "name": "수정된 옵션",
    "quantity": 3
}

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
    "id": 1,
    "name": "수정된 옵션",
    "quantity": 3
}
❌ 옵션 삭제

Request

DELETE /api/products/{productId}/options/{optionId} HTTP/1.1

Response

HTTP/1.1 204 No Content

🎁 위시 리스트 API


🔎 위시 리스트 보기

Request

  • Header: Authorization: Bearer {JWT}
GET /api/wishes?page=0&size=5&sort=createdDate,desc HTTP/1.1
Host: localhost:8080

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
    {
        "id": 1,
        "product": {
            "id": 10,
            "name": "테스트 상품 1",
            "price": 15000,
            "imageUrl": "http://example.com/image.jpg"
        }
    },
    {
        "id": 2,
        "product": {
            "id": 12,
            "name": "테스트 상품 2",
            "price": 20000,
            "imageUrl": "http://example.com/image2.jpg"
        }
    }
]
➕ 위시 리스트 추가

Request

  • Header: Authorization: Bearer {JWT}
POST /api/wishes HTTP/1.1
Content-Type: application/json
host: localhost:8080

{
    "productId": 1
}

Response

{
    "id": 1,
    "product": {
        "id": 10,
        "name": "테스트 상품 1",
        "price": 15000,
        "imageUrl": "[http://example.com/image.jpg](http://example.com/image.jpg)"
    }
}
❌ 위시 삭제

Request

  • Header: Authorization: Bearer {JWT}
DELETE /api/wishes/{wishId} HTTP/1.1
host: localhost:8080

Response

HTTP/1.1 204 No Content

🚚 주문 API


🔎 완료한 주문 보기

Request

  • Header: Authorization: Bearer {JWT}
GET /api/orders?page=0&size=5&sort=orderDateTime,desc HTTP/1.1
Host: localhost:8080

Response

HTTP/1.1 200 OK
Content-Type: application/json

[
    {
        "id": 1,
            "optionId": 1,
            "quantity": 2,
            "orderDateTime": "2025-07-29T12:00:00",
            "message": "생일 축하해!"
    },
    {
        "id": 2,
        "optionId": 2,
        "quantity": 1,
        "orderDateTime": "2025-07-30T14:30:00",
        "message": "추가 주문"
    }
]
➕ 주문 생성

Request

  • Header: Authorization: Bearer {JWT}
POST /api/orders HTTP/1.1
Content-Type: application/json
host: localhost:8080

{
    "optionId": 1,
    "quantity": 2,
    "message": "생일 축하해!"
}

Response

{
    "id": 1,
    "optionId": 1,
    "quantity": 2,
    "orderDateTime": "2025-07-29T12:00:00",
    "message": "생일 축하해!"
}

👤 유저 화면


✅ 로그인 및 회원가입

로그인

login

[GET] http://localhost:8080/members/login
→ 로그인 화면으로 이동합니다.

회원가입

register

[GET] http://localhost:8080/members/register
→ 회원가입 화면으로 이동합니다.

🔎 상품 조회

전체 상품 목록

오래된순
product-list-1
product-list-2
가격 내림차순
product-list-3
product-list-4

[GET] http://localhost:8080/members/products
→ 등록된 모든 상품을 목록으로 확인할 수 있는 화면입니다.

특정 상품 조회

product-detail

[GET] http://localhost:8080/members/products/{productId}
→ 선택한 상품의 상세 정보를 확인할 수 있는 화면입니다.

🔎 위시 리스트 조회

위시 리스트 조회

최신순
wish-list-1
wish-list-2
가격 오름차순
wish-list-3
wish-list-4

[GET] http://localhost:8080/members/wishes
→ 선택한 상품의 상세 정보를 확인할 수 있는 화면입니다.

❌ 위시 삭제

[DELETE] http://localhost:8080/members/wishes/{wishId}
→ HTML <form>에서 _method=delete로 전송됩니다.
→ 실제 HTTP 메서드는 POST이며,
→ MemberFrontController에서 @DeleteMapping으로 처리합니다.

🧑‍💻 관리자 화면


🔎 상품 조회

전체 상품 목록

[GET] http://localhost:8080/admin/products
→ 등록된 모든 상품을 목록으로 확인할 수 있는 화면입니다.

특정 상품 조회

product-detail

[GET] http://localhost:8080/admin/products/{productId}
→ 선택한 상품의 상세 정보를 확인 및 옵션 수정을 할 수 있는 화면입니다.

➕ 상품 추가

상품 추가 화면

product-new

[GET] http://localhost:8080/admin/products/new
→ 새 상품을 입력하는 폼으로 이동합니다.

상품 추가 요청

[POST] http://localhost:8080/admin/products
→ 폼에서 입력된 내용을 서버에 전송해 새 상품을 추가합니다.

✏️ 상품 수정

상품 수정 화면

product-edit

[GET] http://localhost:8080/admin/products/edit/{productId}
→ 선택한 상품의 정보를 수정할 수 있는 화면입니다.

상품 수정 요청

[PUT] http://localhost:8080/admin/products/{productId}
→ HTML <form>에서 _method=put로 전송되는 요청입니다.
→ 실제 HTTP 메서드는 POST이며,
→ AdminController에서 @PutMapping으로 처리합니다.

❌ 상품 삭제

상품 삭제 요청

[DELETE] http://localhost:8080/admin/products/{productId}
→ HTML <form>에서 _method=delete로 전송됩니다.
→ 실제 HTTP 메서드는 POST이며,
→ AdminController에서 @DeleteMapping으로 처리합니다.

💾 데이터베이스


🛠️ 사용 DB

H2 Database (인메모리 DB)

  • JDBC URL: jdbc:h2:mem:spring-gift
  • Username: sa
  • Password: ``
📌 DB 초기화
Hibernate: 
    drop table if exists member
Hibernate: 
    drop table if exists option
Hibernate: 
    drop table if exists orders
Hibernate: 
    drop table if exists product
Hibernate: 
    drop table if exists token
Hibernate: 
    drop table if exists wish
Hibernate: 
    create table member (
        id bigint not null auto_increment,
        email varchar(255) not null,
        password varchar(255) not null,
        role enum ('ADMIN','USER') not null,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table option (
        quantity integer not null,
        id bigint not null auto_increment,
        product_id bigint not null,
        name varchar(50) not null,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table orders (
        quantity integer not null,
        id bigint not null auto_increment,
        member_id bigint not null,
        option_id bigint not null,
        order_date_time datetime(6) not null,
        message TEXT,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table product (
        id bigint not null auto_increment,
        price bigint not null,
        image_url varchar(255) not null,
        name varchar(255) not null,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table token (
        id bigint not null auto_increment,
        member_id bigint not null,
        access_token varchar(512) not null,
        provider varchar(255) not null,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table wish (
        created_date datetime(6) not null,
        id bigint not null auto_increment,
        member_id bigint not null,
        product_id bigint not null,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    alter table member 
       add constraint UKmbmcqelty0fbrvxp1q58dn57t unique (email)
Hibernate: 
    alter table option 
       add constraint UKe78vjnqbmknqwm7d6k2blhhnj unique (product_id, name)
Hibernate: 
    alter table wish 
       add constraint UKimrh37c61jscdegh9fi3jbpix unique (member_id, product_id)
Hibernate: 
    alter table option 
       add constraint FK5t6etuqa4wl7lyn0ysxnts7q4 
       foreign key (product_id) 
       references product (id)
Hibernate: 
    alter table orders 
       add constraint FKpktxwhj3x9m4gth5ff6bkqgeb 
       foreign key (member_id) 
       references member (id)
Hibernate: 
    alter table orders 
       add constraint FK4nmdwvy9x1gghgpsnj8mlm974 
       foreign key (option_id) 
       references option (id)
Hibernate: 
    alter table token 
       add constraint FK8a0sdl451qcw4ishfaxpdog0p 
       foreign key (member_id) 
       references member (id)
Hibernate: 
    alter table wish 
       add constraint FK70nrc4a6uvljrtemsn80eq1gd 
       foreign key (member_id) 
       references member (id)
Hibernate: 
    alter table wish 
       add constraint FKh3bvkvkslnehbxqma1x2eynqb 
       foreign key (product_id) 
       references product (id)

⚖️ 유효성 검사 및 예외 처리


🔍 유효성 검사

멤버

이메일

  • 필수 입력
  • 이메일 형식

비밀번호

  • 필수 입력

상품

상품 이름

  • 필수 입력
  • 최소 1자, 최대 15자
  • (), [], +, -, &, /, _ 외의 특수 문자를 사용할 수 없음
  • RequiresApprovalWords 어노테이션을 사용하여 특정 단어가 포함되지 않도록 검사

상품 가격

  • 0원 이상

상품 이미지 URL

  • 필수 입력

옵션

옵션 이름

  • 필수 입력
  • 최소 1자, 최대 50자
  • (), [], +, -, &, /, _ 외의 특수 문자를 사용할 수 없음
  • 중복된 옵션 이름은 허용하지 않음 (같은 상품 내에서)

옵션 수량

  • 필수 입력
  • 1개 이상 1억 개 미만
🚨 예외 처리

EntityNotFoundException 404 Not Found

  • MemberNotFoundException
    • 멤버가 존재하지 않을 경우 (조회 시)
  • ProductNotFoundException
    • 상품이 존재하지 않을 경우 (조회, 수정, 삭제 시)
  • WishNotFoundException
    • 위시가 존재하지 않을 경우 (삭제 시)
  • OptionNotFoundException
    • 옵션이 존재하지 않을 경우 (수정, 삭제 시)

DataConflictException 409 Conflict

  • EmailDuplicateException
    • 중복된 이메일로 회원가입 할 때
  • WishDuplicateException
    • 중복된 위시를 추가할 때
  • OptionNameDuplicateException
    • 옵션 이름이 중복될 때 (같은 상품 내에서)
  • InvalidOptionQuantityException
    • 옵션 수량이 1개 이상 1억 개 미만이 아닐 때

AuthenticationException 401 Unauthorized

  • 인증되지 않은 사용자 (로그인하지 않은 경우)
  • 인증 토큰이 유효하지 않은 경우 (예: 만료된 토큰)
  • LoginFailedException
    • 로그인 실패 시 (잘못된 이메일 또는 비밀번호)

AuthorizationException 403 Forbidden

  • 인증된 사용자 (로그인한 경우) 권한이 없는 요청
    • 일반 사용자가 관리자 권한이 필요한 행위 요청

MethodArgumentNotValidException 400 Bad Request

  • 상품을 생성할 때 제약조건에 맞지 않을 경우

InvalidOptionAccessException 400 Bad Request

  • 해당 상품에 속한 옵션이 아닐 때 수정 및 삭제하는 경우

OptionPolicyException 409 Conflict

  • 옵션이 1개 뿐인 상품의 옵션을 삭제하는 경우

🧪 테스트


E2E 테스트
  • AuthE2ETest
    • 관리자/일반 사용자 로그인, 페이지 접근 권한 등 인증/인가 테스트
  • PaginationE2ETest
    • 관리자/사용자 상품 목록, 위시리스트의 페이지네이션 및 정렬 기능 테스트
  • OrderE2ETest
    • 상품 주문 성공 테스트
Domain 테스트
  • OptionTest
    • 옵션 삭제 테스트
Repository 테스트
  • MemberRepositoryTest
    • 회원 정보 저장 및 이메일 중복 조회
  • ProductRepositoryTest
    • 상품 CRUD 기능 테스트
  • WishRepositoryTest
    • 위시리스트 저장, 조회 및 중복 저장 방지 테스트
Service 테스트
  • MemberServiceTest
    • 회원가입, 로그인 성공/실패(이메일 중복, 비밀번호 불일치) 케이스 테스트
  • ProductServiceTest
    • 상품 생성, 조회, 수정, 삭제 기능 테스트
  • WishServiceTest
    • 위시리스트 추가, 삭제 및 예외(중복, 권한 없음) 처리 테스트
  • OrderServiceTest
    • 상품 주문 성공 및 실패(재고 부족, 존재하지 않는 회원) 테스트
  • KakaoServiceTest
    • 카카오 로그인 성공 테스트
  • KakaoMessageTemplateGeneratorTest
    • 카카오 메시지 템플릿 생성 테스트

About

카카오테크 캠퍼스 3기 백엔드 2단계-4: 주문하기 - 외부 API 연동

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Java 100.0%