Files
mini-test-projects-2-release/module-b/module-b-kr.md
2026-04-12 18:58:03 +09:00

15 KiB
Raw Blame History

🛍️ Test Project: Module B — ShopPress (온라인 쇼핑몰)

1. 프로젝트 개요 (Project Overview)

빠르게 성장하는 이커머스 시장에서 스타트업 ShopPress는 관리자가 상품과 주문을 직접 운영할 수 있는 자체 쇼핑몰 플랫폼을 런칭하려 합니다. 관리자는 상품을 등록/관리하고 주문을 처리하며, 일반 방문자는 상품을 탐색하고 구매할 수 있습니다. 본 과제에서 선수는 데이터베이스 설계부터 핵심 기능 구현까지 전담합니다. 이 프로젝트는 MVP이므로 핵심 기능 구현에 집중하십시오. 디자인은 채점 항목이 아닙니다.


2. 기술 스택 및 제약 조건 (Tech Stack & Constraints)

  • 기술 스택: 사용 언어 및 프레임워크에 제한이 없습니다. Laravel, Django, Ruby on Rails, Spring Boot 등 서버 사이드 렌더링(SSR)이 가능한 프레임워크를 자유롭게 선택할 수 있습니다.
  • 데스크톱 환경: 반응형 디자인은 구현하지 않으며, 데스크톱 Chrome 브라우저에서만 동작하면 됩니다.
  • 이미지 업로드: 상품 이미지는 JPG, PNG 형식만 허용하며, 파일 크기는 5MB 이하로 제한됩니다. 이 검사는 서버 측에서 수행해야 합니다.
  • 데이터베이스: 카테고리는 별도 테이블(categories)로 관리하고, 상품 테이블(products)은 category_id를 외래 키(FK)로 참조하는 구조로 설계하십시오.
  • 초기 데이터: 애플리케이션 최초 실행 시(Seeder 또는 Migration) 다음 데이터가 자동으로 생성되어야 합니다.
    • 관리자 계정: 이름 Admin, 이메일 admin@shoppress.local, 비밀번호 password (비밀번호는 반드시 해시하여 저장)
    • 초기 카테고리 3개 이상 (예: Clothing, Electronics, Food)

3. 과제 요구사항 (Tasks to Complete)

1. 인증 (Authentication)

1.1 로그인 및 세션 관리

로그인 페이지는 /admin/login에서 접근 가능합니다. 사용자는 이메일과 비밀번호를 입력합니다. 로그인 성공 시 관리자 대시보드로 이동합니다. 로그인 실패 시 "Incorrect email or password." 메시지를 표시합니다.

/admin 하위 경로에 로그인 없이 접근하면 로그인 페이지로 리다이렉트됩니다. 로그아웃 시 세션이 파기되고 로그인 페이지로 이동합니다.

현재 로그인한 관리자 이름은 모든 관리자 페이지의 내비게이션에 표시되어야 합니다.


2. 관리자 기능 (Admin Features)

2.1 대시보드

대시보드는 관리자 패널의 기본 페이지(/admin 또는 /admin/dashboard)이며, 로그인 후 첫 번째로 표시되는 페이지입니다.

대시보드에는 다음 4가지 통계를 카드 형태로 표시하십시오.

카드 레이블 내용
Total Products 전체 상품 수
Active Products 상태가 Active인 상품 수
Total Orders 전체 주문 수
Total Revenue 전체 누적 주문 금액(쿠폰 할인 적용 후 최종 결제 금액 합산 기준)

또한 가장 최근 접수된 주문 5건의 목록을 표시하십시오. 각 항목에는 Order #(주문번호), Customer(구매자명), Total(최종 금액), Status(주문상태), Date(주문일시)가 포함되어야 합니다.

2.2 상품 관리

상품 관리 페이지(/admin/products)는 전체 상품 목록을 테이블로 표시합니다.

테이블 컬럼: Name, Category, Price, Stock, Status, Created At, Edit / Delete buttons.

상품명을 기준으로 검색할 수 있는 검색창을 제공하십시오. 검색어를 입력하고 제출하면 해당 키워드가 포함된 상품만 목록에 표시됩니다.

상품 등록 폼 (/admin/products/new)과 상품 수정 폼 (/admin/products/{id}/edit)은 다음 필드를 포함해야 합니다.

  • Name (필수, 최대 100자)
  • Category (필수, 드롭다운 선택 — DB에 등록된 카테고리 목록으로 구성)
  • Price (필수, 정수, 0 이상)
  • Stock (필수, 정수, 0 이상)
  • Description (<textarea>, 선택 항목)
  • Image (파일 업로드, JPG/PNG, 5MB 이하, 선택 항목 — 이미지 없이도 상품 등록 가능)
  • Status (드롭다운: Active / Out of Stock / Hidden)

수정 폼 진입 시 기존 데이터가 각 필드에 미리 채워져야 합니다. 이미지 파일을 새로 업로드하지 않으면 기존 이미지를 유지합니다. 새 이미지를 업로드하면 기존 이미지 파일을 서버에서 삭제하고 새 이미지로 교체합니다.

상품 삭제 시 "Are you sure you want to delete this product?" 확인 메시지를 표시합니다. 확인 시 해당 상품이 포함된 주문(order_items)이 1건 이상 존재하면 삭제를 거부하고 "Cannot delete a product with order history." 오류 메시지를 표시합니다. 주문 이력이 없는 상품은 DB에서 삭제하고 대표 이미지 파일도 서버에서 함께 삭제합니다.

2.3 카테고리 관리

카테고리 관리 페이지(/admin/categories)에서 카테고리를 추가/수정/삭제할 수 있습니다.

  • 추가: 카테고리 이름을 입력하고 저장합니다. 이름이 중복될 경우 "Category name already exists." 오류 메시지를 표시합니다.
  • 수정: 카테고리 이름을 변경할 수 있습니다. categories 테이블의 해당 행 이름만 변경하면 FK로 연결된 상품에 자동으로 반영됩니다.
  • 삭제: 해당 카테고리를 참조하는 상품이 1개 이상 존재할 경우 삭제를 거부하고 "Cannot delete a category that has products assigned." 오류 메시지를 표시합니다.

2.4 쿠폰 관리

쿠폰 관리 페이지(/admin/coupons)에서 할인 쿠폰을 등록하고 관리할 수 있습니다.

쿠폰 등록 폼은 다음 필드를 포함합니다.

  • Coupon Code (필수, 영문 대문자 또는 숫자로만 구성, 6~12자, 중복 불가)
  • Discount Type (드롭다운: Fixed / Percent)
  • Discount Value (필수, 정수, 1 이상. Percent 선택 시 1~100 사이)
  • Minimum Order Amount (필수, 정수, 0 이상. 이 금액 미만의 주문에는 적용 불가)
  • Expires At (필수, <input type="date">. 오늘 날짜 이후만 선택 가능)
  • Usage Limit (필수, 정수, 1 이상)

쿠폰 목록 테이블 컬럼: Code, Type, Value, Min. Order, Expires At, Remaining Uses, Delete button. 잔여 사용 횟수는 remaining_uses 컬럼으로 관리하며, 등록 시 입력한 Usage Limit 값으로 초기화하고 쿠폰이 사용될 때마다 1씩 차감합니다.

2.5 주문 관리

주문 관리 페이지(/admin/orders)는 전체 주문 목록을 테이블로 표시합니다.

테이블 컬럼: Order #, Customer, Phone, Total, Status, Date.

주문 상태 기준으로 필터링하는 드롭다운을 제공하십시오. 상태값은 Pending / Processing / Shipped / Delivered / Cancelled이며, 선택한 상태의 주문만 목록에 표시됩니다.

주문 상세 페이지(/admin/orders/{id})에서는 다음 정보를 확인할 수 있어야 합니다.

  • Customer Info: 이름(Name), 연락처(Phone), 배송 주소(Shipping Address)
  • Order Items: 상품명(Product), 단가(Unit Price), 수량(Qty), 소계(Subtotal)
  • Coupon: 적용된 쿠폰 코드 및 할인 금액. 쿠폰 미적용 시 "None" 표시
  • Total: 최종 결제 금액
  • Order Status 변경 셀렉트박스(Pending / Processing / Shipped / Delivered / Cancelled) 및 Save 버튼. 저장 성공 시 동일 페이지에 "Order status updated." 메시지를 표시합니다.
  • 주문 상태가 **Cancelled**로 변경될 때 해당 주문에 포함된 상품의 재고를 주문 수량만큼 복원합니다. 이미 Cancelled 상태인 주문에서 다시 Cancelled로 변경하는 경우 재고 복원은 수행하지 않습니다.

3. 공개 페이지 (Public Pages)

3.1 홈 — 상품 목록

홈(/)은 상태가 **Active**인 상품 목록을 카드 형태로 표시합니다. 각 카드에는 대표 이미지(없는 경우 회색 placeholder 영역 표시), 상품명, 카테고리명, 가격이 표시됩니다.

한 페이지에 12개씩 표시하며 페이지네이션을 제공합니다. 최근 등록 순으로 정렬합니다.

카테고리 필터 링크를 제공하며, 특정 카테고리 클릭 시 해당 카테고리의 Active 상품만 표시됩니다. 페이지네이션은 현재 카테고리 필터를 유지합니다.

키워드 검색창을 제공하며, 상품명 기준으로 검색됩니다. 카테고리 필터와 검색이 동시에 적용됩니다. 검색 상태에서 페이지네이션도 검색 조건을 유지합니다.

3.2 상품 상세

상품 상세 페이지는 /products/{id}에서 접근 가능합니다. 존재하지 않는 ID 또는 상태가 **Hidden**인 상품에 접근하면 404 페이지를 반환합니다.

상품 상세 페이지에는 대표 이미지(없는 경우 회색 placeholder 영역 표시), 상품명, 카테고리명, 가격, 재고 수량, 상품 설명이 표시됩니다.

재고가 0이거나 상태가 **Out of Stock**인 상품은 "Out of Stock" 표시와 함께 구매 버튼이 비활성화됩니다.

재고가 1 이상이고 상태가 **Active**인 상품에는 수량 입력(<input type="number">, 최솟값 1, 최댓값은 현재 재고 수량)과 "Buy Now" 버튼이 표시됩니다. 버튼 클릭 시 선택한 상품 ID와 수량을 쿼리스트링으로 전달하여 주문 접수 페이지(/checkout?product_id={id}&quantity={n})로 이동합니다.

3.3 주문 접수

주문 접수 페이지(/checkout)는 쿼리스트링의 product_idquantity를 읽어 주문 대상 상품 정보(상품명, 단가, 수량, 소계)를 화면에 표시합니다. 서버에서 다음 조건을 확인하고, 하나라도 해당하면 홈(/)으로 리다이렉트합니다.

  • product_id에 해당하는 상품이 존재하지 않는 경우
  • 상품 상태가 Hidden 또는 **Out of Stock**인 경우
  • 재고가 0인 경우
  • quantity가 1 미만이거나 현재 재고를 초과하는 경우

구매자 이름(Name), 연락처(Phone), 배송 주소(Shipping Address)를 입력하는 폼이 있습니다. 모든 필드는 필수이며 서버 측 유효성 검사를 통과한 데이터만 주문이 접수됩니다.

**쿠폰 코드 입력란(Coupon Code)**과 "Apply" 버튼이 있습니다. "Apply" 버튼 클릭 시 fetch를 사용하여 /checkout/coupon 엔드포인트에 아래 형식으로 POST 요청을 보내고, 그 결과를 페이지 새로고침 없이 화면에 반영합니다. 해당 엔드포인트는 선수가 직접 구현해야 합니다.

  • 요청 Body (JSON): { "coupon_code": "ABCD1234", "subtotal": 30000 }

  • 성공 응답 (JSON): { "valid": true, "discount_type": "fixed"|"percent", "discount_value": 3000, "discount_amount": 3000, "final_amount": 27000 }

  • 실패 응답 (JSON): { "valid": false, "message": "error message" }

  • 유효한 쿠폰: 할인 금액을 계산하여 소계(Subtotal), 할인 금액(Discount), 최종 결제 금액(Total)을 화면에 표시합니다.

    • Fixed 할인: 소계에서 할인 값을 직접 차감합니다.
    • Percent 할인: 소계 × 할인율 / 100을 차감합니다. 소수점 이하는 버림(floor) 처리합니다.
    • 최종 결제 금액은 0 미만이 되지 않습니다.
  • 유효하지 않은 쿠폰: 각 상황에 맞는 오류 메시지를 표시합니다.

    • 존재하지 않는 코드: "Invalid coupon code."
    • 만료된 쿠폰: "This coupon has expired."
    • 잔여 횟수 소진: "This coupon has reached its usage limit."
    • 최소 주문금액 미달: "This coupon requires a minimum order of {n}."

주문 접수 시 다음을 하나의 트랜잭션으로 처리하십시오.

  1. orders 테이블에 주문 정보(구매자명, 연락처, 배송주소, 적용된 쿠폰 ID — 없으면 NULL, 할인 금액 — 없으면 0, 최종 결제 금액)를 저장합니다.
  2. order_items 테이블에 주문 상품 정보(주문 ID, 상품 ID, 상품명 스냅샷, 수량, 단가)를 저장합니다. 상품명은 주문 시점의 이름을 그대로 저장하여 이후 상품이 삭제되거나 이름이 변경되어도 주문 내역에서 올바르게 표시되도록 합니다.
  3. 해당 상품의 재고(stock)를 주문 수량만큼 차감합니다.
  4. 쿠폰이 적용된 경우 해당 쿠폰의 remaining_uses를 1 차감합니다.

재고가 요청 수량보다 부족할 경우 트랜잭션을 롤백하고 "Not enough stock." 오류 메시지를 표시합니다.

주문 접수 성공 시 주문 완료 페이지(/orders/{id}/complete)로 이동합니다. 주문 완료 페이지는 별도 인증 없이 접근 가능하며, 주문번호(Order #), 주문 상품 내역, 할인 금액(Discount), 최종 결제 금액(Total)을 표시합니다.

3.4 레이아웃

모든 공개 페이지에는 헤더와 푸터가 포함됩니다. 헤더에는 사이트명(ShopPress)과 홈으로 이동하는 링크가 포함됩니다. 각 페이지의 내용을 반영한 <title> 태그가 적절히 설정되어야 합니다.

관리자 패널 페이지에는 사이드바 또는 상단 내비게이션이 포함됩니다. 내비게이션 메뉴 항목: Dashboard, Products, Categories, Coupons, Orders.


4. 제출 안내 (Submission Guidelines)

선수는 작업 결과물을 서버 디렉토리 /module_b/에 업로드하십시오.


5. 채점 기준 요약 (Marking Scheme — Total 25점)

항목 세부 기준 배점
A. 인증 로그인/로그아웃, 세션 보호, 미인증 접근 리다이렉트 2
B. 대시보드 통계 4종 카드(영문 레이블), 최근 주문 5건 목록 1
C. 상품 관리 목록(검색 포함), 등록(이미지 선택), 수정(이미지 유지/교체+기존 삭제), 삭제(주문 이력 있는 경우 거부 + 이미지 파일 삭제) 5
D. 카테고리 관리 추가(중복 검사), 수정, 삭제(참조 상품 존재 시 거부) 2
E. 쿠폰 관리 쿠폰 등록(전체 유효성 검사 포함), 목록 조회(잔여 횟수 표시), 삭제 3
F. 주문 관리 목록(상태 필터), 상세 조회, 주문 상태 변경, Cancelled 시 재고 복원(중복 복원 방지) 4
G. 공개 — 상품 목록 Active 상품 필터링, 카테고리 필터, 키워드 검색, 페이지네이션(조건 유지) 3
H. 공개 — 상품 상세 상품 정보 표시, Out of Stock 처리(재고 0 또는 Out of Stock 상태), 수량 입력 및 checkout 이동 1
I. 공개 — 주문 접수 checkout 진입 유효성(Out of Stock/Hidden/재고 초과 포함), 쿠폰 적용(fetch + 할인 계산), 트랜잭션(주문저장+재고차감+쿠폰차감), 완료 페이지 3
J. 유효성 검사 서버 측 이미지 용량/확장자 검사, 주문 폼 필드 검사 1
합계 25