원티드 프리온보딩 코스의 페이히어(payhere) 팀 프로젝트를 학습 목적으로 처음부터 재구현한 레포지토리입니다.
- 본 프로젝트에서 요구하는 서비스는 가계부(Account Book)입니다.
- 사용자는 본 서비스에 로그인하여, 본인의 소비내역을 기록하고 관리할 수 있습니다.
- 단, 로그인하지 않은 사용자는 가계부 내역에 대한 접근제한 처리가 되어야 합니다.
Index
팀 프로젝트 소개
-
> 과제 제출기업: 페이히어(payhere) > 팀명: F5-Refresh > 팀원: 5명 > 프로젝트 기간: 22.07.04 ~ 22.07.08
Period
Analysis
-
- 사용자 관리:
- 회원가입
* 고객은 이메일과 비밀번호를 통해서 회원가입을 할 수 있습니다.
- 로그인 및 로그아웃
* 고객은 회원가입 이후, 로그인과 로그아웃을 할 수 있습니다. * 고객은 로그인 이후에 가계부 관련 기능을 사용할 수 있습니다. * 로그인하지 않은 고객은 가계부에 접근할 수 없습니다.
- 회원가입
- 가계부 관리:
- 가계부 생성
* 고객은 가계부에 오늘 사용한 돈의 금액과 관련된 메모를 남길 수 있습니다.
- 가계부 목록
* 고객은 지금까지 기록한 가계부 리스트를 볼 수 있습니다.
- 가계부 상세
* 고객은 가계부의 상세내역을 볼 수 있습니다.
- 가계부 수정
* 고객은 가계부의 금액과 메모를 수정할 수 있습니다.
- 가계부 삭제 및 복구
* 고객은 가계부에서 원하는 내역을 삭제할 수 있습니다. * 단, 삭제한 내역은 언제든지 복구할 수 있습니다.
- 가계부 생성
- 사용자 관리:
-
- 별도의 요구사항이 없는 것은 지원자가 판단해서 개발해주세요.
- 테스트 케이스 작성시 가산점이 있습니다.
- Docker를 이용해서 개발시 가산점이 있습니다.
-
- 토큰을 발행해서 인증/인가를 제어하는 방식으로 구현해주세요.
- 구현이 불가능한 부분에 대해서는 가능한 부분까지 구현하는 것을 목표로 해주세요.
- 모든 코드에는 이유가 있어야 하고 동료에게 설명할 수 있어야 합니다.
- README에 구현하신 내용(API 및 설계 관련)과 코드에 대한 생각을 자유롭게 작성해주세요.
Development
-
- 사용자 관리:
- 회원가입
> 사용자 회원가입 기능입니다. * 이메일, 닉네임, 패스워드는 필수 입력값입니다. * 이메일, 닉네임은 중복이 허용되지 않습니다. * 패스워드는 최소 1개의 소문자/대문자/숫자/(숫자키)특수문자가 포함된 8~20자리로 구성되어야 합니다. * 패스워드는 해싱 후 DB에 저장됩니다.
- 로그인
> 사용자 로그인 기능입니다. * DRF-SimpleJwt 라이브리러를 활용했습니다. * 이메일, 패스워드는 필수 입력값입니다. * 입력받은 이메일과 패스워드가 유저 정보와 일치하는지 확인합니다. * 모든 유효성 검사에 통과하면 액세스토큰과 리프레시 토큰을 발급합니다.
- 로그아웃
> 사용자 로그아웃 기능입니다. * DRF-SimpleJwt 라이브리러를 활용했습니다. * 리프레시 토큰은 필수 입력값입니다. * 토큰의 타입이 유효한지 확인합니다.(리프레시 토큰만 허용) * 토큰의 기한이 만료되었는지 확인합니다. * 토큰의 유저정보와 API 요청자의 유저정보가 일치하는지 확인합니다. * 모든 유효성 검사에 통과하면, 요청받은 리프레시 토큰을 토큰 블랙리스트에 등록합니다.(사용 제한) * 또한, 기존에 발급받은 모든 리프레시 토큰도 함께 토큰 블랙리스트에 등록합니다.
- 토큰 재발급
> 사용자 토큰 재발급 기능입니다. * DRF-SimpleJwt의 TokenRefreshView 기능을 활용했습니다. * 리프레시 토큰은 필수 입력값입니다. * 유효한 토큰인지를 확인합니다. * 만료된 토큰인지를 확인합니다. * 토큰의 타입을 확인합니다.(오직 리프레시 토큰만 사용 가능) * 모든 유효성 검사에 통과하면, 요청받은 리프레시 토큰을 사용하여 액세스토큰을 발급합니다. * 단, 리프레시 토큰은 추가로 발급하지 않습니다.
- 회원가입
- 가계부:
- 가계부 목록
> 인증/인가에 통과한 사용자는 본인 가계부의 리스트 정보를 조회할 수 있습니다. * 키워드 검색기능(가계부 이름을 기준으로 검색 키워드 적용) * 정렬기능(가계부 생성일자/목표 예산을 기준으로 오름차순/내림차순 정렬) * 필터링 기능(현재 사용중인/삭제된 가계부를 기준으로 필터링 조회) * 페이지네이션 기능(사용자가 원하는 만큼의 가계부 데이터를 요청할 수 있음 >> default: 최신순 10개)
- 가계부 생성
> 인증/인가에 통과한 사용자는 본인의 가계부를 생성할 수 있습니다. * 사용자는 목표예산을 지정해서 가계부를 생성할 수 있습니다. * 단, 가계부의 이름과 목표예산은 필수 입력값입니다.
- 가계부 수정/삭제/복구
> 인증/인가에 통과한 사용자는 본인의 가계부를 수정/삭제/복구할 수 있습니다. - 가계부 수정: * 해당 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 사용자는 가계부의 이름과 목표예산만을 수정할 수 있습니다. * 가계부의 내용을 부분적으로 수정할 수 있습니다. - 가계부 삭제(soft delete): * 해당 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 이미 삭제된 가계부는 다시 삭제할 수 없습니다. - 가계부 복구: * 해당 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 이미 복구된 가계부는 다시 복구할 수 없습니다.
- 가계부 목록
- 가계부 카테고리
- 가계부 카테고리 목록
> 인증/인가에 통과한 사용자는 본인 가계부 카테고리의 리스트 정보를 조회할 수 있습니다. * 키워드 검색기능(가계부 카테고리 이름을 기준으로 검색 키워드 적용) * 정렬기능(가계부 카테고리 생성일자를 기준으로 오름차순/내림차순 정렬) * 필터링 기능(현재 사용중인/삭제된 카테고리를 기준으로 필터링 조회) * 페이지네이션 기능(사용자가 원하는 만큼의 카테고리 데이터를 요청할 수 있음 >> default: 최신순 10개)
- 가계부 카테고리 생성
> 인증/인가에 통과한 사용자는 본인의 카테고리를 생성할 수 있습니다. * 사용자가 원하는 이름의 가계부 카테고리를 생성할 수 있습니다. * 단, 카테고리의 이름은 필수 입력값입니다.
- 가계부 카테고리 수정/삭제/복구
> 인증/인가에 통과한 사용자는 본인의 카테고리를 수정/삭제/복구할 수 있습니다. - 가계부 카테고리 수정: * 해당 카테고리가 존재하는지, 본인의 카테고리인지를 확인합니다. * 사용자는 카테고리의 이름만을 수정할 수 있습니다. - 가계부 카테고리 삭제(soft delete): * 해당 카테고리가 존재하는지, 본인의 카테고리인지를 확인합니다. * 이미 삭제된 카테고리는 다시 삭제할 수 없습니다. - 가계부 카테고리 복구: * 해당 카테고리가 존재하는지, 본인의 카테고리인지를 확인합니다. * 이미 복구된 카테고리는 다시 복구할 수 없습니다.
- 가계부 카테고리 목록
- 가계부 기록
- 가계부 기록 목록
> 인증/인가에 통과한 사용자는 본인 가계부 기록의 리스트 정보를 조회할 수 있습니다. * 가계부가 존재하는지, 본인의 가계부인지 확인합니다. * 사용자 본인의 가계부 기록만을 리스트 조회합니다. * 가계부 기록의 총지출/총수입 데이터를 함께 반환합니다. * 가계부의 목표예산을 함께 반환합니다. * 사용자의 닉네임을 함께 반환합니다. - 부가기능: * 키워드 검색기능(가계부 기록의 제목/설명/가계부 카테고리 이름을 기준으로 검색 키워드 적용) * 정렬기능(가계부 기록 생성일자/가격을 기준으로 오름차순/내림차순 정렬) * 상태 필터링 기능(현재 사용중인/삭제된 가계부 기록을 기준으로 필터링 조회) * 카테고리 필터링 기능(카테고리 id를 기준으로 필터링 조회: 복수의 카테고리 적용가능) * 타입 필터링(가계부의 타입[expenditure/income]을 기준으로 필터링 조회) * 페이지네이션 기능(사용자가 원하는 만큼의 가계부 기록 데이터를 요청할 수 있음 >> default: 최신순 10개)
- 가계부 기록 생성
> 인증/인가에 통과한 사용자는 본인의 기록을 생성할 수 있습니다. * 가계부 id는 필수 입력값입니다.(path param) * 가계부 카테고리 id는 필수 입력값입니다.(query string) * 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 카테고리가 존재하는지, 본인의 카테고리인지를 확인합니다. * 사용자는 제목/설명/가격/타입/카테고리를 지정하여 가계부 기록을 생성할 수 있습니다. * 단, 가계부 기록의 제목과 가격은 필수 입력값입니다.
- 가계부 기록 수정/삭제/복구
> 인증/인가에 통과한 사용자는 본인의 기록을 수정/삭제/복구할 수 있습니다. - 가계부 기록 수정: * 가계부 id는 필수 입력값입니다.(path param) * 가계부 기록 id는 필수 입력값입니다.(path param) * 가계부 카테고리 id는 선택 입력값입니다.(query string) * 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 가계부 기록이 존재하는지, 본인의 기록인지, 해당 가계부의 기록인지를 확인합니다. * 카테고리가 존재하는지, 본인의 카테고리인지를 확인합니다.(카테고리의 입력값이 존재할 경우) * 사용자는 가계부 기록의 제목/설명/가격/타입/카테고리만을 수정할 수 있습니다. - 가계부 기록 삭제(soft delete): * 가계부 id는 필수 입력값입니다.(path param) * 가계부 기록 id는 필수 입력값입니다.(path param) * 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 가계부 기록이 존재하는지, 본인의 기록인지, 해당 가계부의 기록인지를 확인합니다. * 이미 삭제된 기록은 다시 삭제할 수 없습니다. - 가계부 기록 복구: * 가계부 id는 필수 입력값입니다.(path param) * 가계부 기록 id는 필수 입력값입니다.(path param) * 가계부가 존재하는지, 본인의 가계부인지를 확인합니다. * 가계부 기록이 존재하는지, 본인의 기록인지, 해당 가계부의 기록인지를 확인합니다. * 이미 복구된 기록은 다시 복구할 수 없습니다.
- 가계부 기록 목록
- 사용자 관리:
Modeling
API Docs
Deploy
Test
Issue
Guides
-
- 해당 프로젝트를 clone하고, 프로젝트 폴더로 이동합니다.
git clone https://github.com/pasitoapasito/payhere-assignment.git cd project directory
- 가상환경을 만들고, 프로젝트에 필요한 python package를 다운받습니다.
conda create --name project-name python=3.9 conda activate project-name pip install -r requirements.txt
- manage.py 파일과 동일한 위치에서 환경설정 파일을 만듭니다.
ex) .env file ## GENERAL ## DEBUG = True ALLOWED_HOSTS = ALLOWED_HOSTS SECRET_KEY = SECRET_KEY ## DOCKER DB ## MYSQL_TCP_PORT = '3306' MYSQL_DATABASE = MYSQL_DATABASE MYSQL_ROOT_PASSWORD = MYSQL_ROOT_PASSWORD MYSQL_USER = MYSQL_USER MYSQL_PASSWORD = MYSQL_PASSWORD ## AWS RDS ## RDS_HOSTNAME = RDS_HOSTNAME RDS_DB_NAME = RDS_DB_NAME RDS_USERNAME = RDS_USERNAME RDS_PASSWORD = RDS_PASSWORD RDS_PORT = '3306'
- project-name/settings.py에서 DB 설정을 적절하게 변경합니다.
Docker로 DB를 구축하는 경우 or AWS RDS로 DB를 구축하는 경우 등 DB를 구축하는 방법에 맞게 DB 설정을 변경합니다. ## DOCKER DB FOR LOCAL-DEV ## ''' DATABASES = { 'default': { 'ENGINE' : 'django.db.backends.mysql', 'NAME' : get_env_variable('MYSQL_DATABASE'), 'USER' : 'root', 'PASSWORD': get_env_variable('MYSQL_ROOT_PASSWORD'), 'HOST' : 'localhost', 'PORT' : get_env_variable('MYSQL_TCP_PORT'), } } ''' ## AWS RDS FOR LOCAL-DEV ## DATABASES = { 'default': { 'ENGINE' : 'django.db.backends.mysql', 'NAME' : get_env_variable('RDS_DB_NAME'), 'USER' : get_env_variable('RDS_USERNAME'), 'PASSWORD': get_env_variable('RDS_PASSWORD'), 'HOST' : get_env_variable('RDS_HOSTNAME'), 'PORT' : get_env_variable('RDS_PORT'), 'OPTIONS' : {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}, } }
- DB의 스키마(schema)를 최신 modeling에 맞게 동기화합니다.
python manage.py migrate
- 개발용 서버를 실행합니다.
python manage.py runserver 0:8000
- 배포용 서버에서 해당 프로젝트를 clone하고, 프로젝트 폴더로 이동합니다.
git clone https://github.com/pasitoapasito/payhere-assignment.git cd project directory
- manage.py 파일과 동일한 위치에서 도커 환경설정 파일을 만듭니다.
ex) .env file ## GENERAL ## DEBUG = True ALLOWED_HOSTS = ALLOWED_HOSTS SECRET_KEY = SECRET_KEY ## DOCKER DB ## MYSQL_TCP_PORT = '3306' MYSQL_DATABASE = MYSQL_DATABASE MYSQL_ROOT_PASSWORD = MYSQL_ROOT_PASSWORD MYSQL_USER = MYSQL_USER MYSQL_PASSWORD = MYSQL_PASSWORD ## AWS RDS ## RDS_HOSTNAME = RDS_HOSTNAME RDS_DB_NAME = RDS_DB_NAME RDS_USERNAME = RDS_USERNAME RDS_PASSWORD = RDS_PASSWORD RDS_PORT = '3306'
- project-name/settings.py에서 DB 설정을 적절하게 변경합니다.
Docker로 DB를 구축하는 경우 or AWS RDS로 DB를 구축하는 경우 등 DB를 구축하는 방법에 맞게 DB 설정을 변경합니다. ## DOCKER DB FOR DEPLOY ## DATABASES = { 'default': { 'ENGINE' : 'django.db.backends.mysql', 'NAME' : get_env_variable('MYSQL_DATABASE'), 'USER' : 'root', 'PASSWORD': get_env_variable('MYSQL_ROOT_PASSWORD'), 'HOST' : 'db', 'PORT' : get_env_variable('MYSQL_TCP_PORT'), } } ## AWS RDS FOR DEPLOY ## ''' DATABASES = { 'default': { 'ENGINE' : 'django.db.backends.mysql', 'NAME' : get_env_variable('RDS_DB_NAME'), 'USER' : get_env_variable('RDS_USERNAME'), 'PASSWORD': get_env_variable('RDS_PASSWORD'), 'HOST' : get_env_variable('RDS_HOSTNAME'), 'PORT' : get_env_variable('RDS_PORT'), 'OPTIONS' : {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}, } } '''
- docker-compose 명령을 사용하여, DB/Nginx/Django 서버 컨테이너를 실행시킵니다.
docker-compose -f ./docker-compose.yml up (-d)
Structure
-
📦account_books ┣ 📂migrations ┃ ┣ 📜0001_initial.py ┃ ┣ 📜0002_alter_accountbooklog_book.py ┃ ┗ 📜__init__.py ┣ 📂tests ┃ ┣ 📂account_book ┃ ┃ ┣ 📜__init__.py ┃ ┃ ┣ 📜tests_account_book_create.py ┃ ┃ ┣ 📜tests_account_book_delete.py ┃ ┃ ┣ 📜tests_account_book_list.py ┃ ┃ ┣ 📜tests_account_book_restore.py ┃ ┃ ┗ 📜tests_account_book_update.py ┃ ┣ 📂account_book_category ┃ ┃ ┣ 📜__init__.py ┃ ┃ ┣ 📜tests_account_book_category_create.py ┃ ┃ ┣ 📜tests_account_book_category_delete.py ┃ ┃ ┣ 📜tests_account_book_category_list.py ┃ ┃ ┣ 📜tests_account_book_category_restore.py ┃ ┃ ┗ 📜tests_account_book_category_update.py ┃ ┣ 📂account_book_log ┃ ┃ ┣ 📜__init__.py ┃ ┃ ┣ 📜tests_account_book_log_create.py ┃ ┃ ┣ 📜tests_account_book_log_delete.py ┃ ┃ ┣ 📜tests_account_book_log_list.py ┃ ┃ ┣ 📜tests_account_book_log_restore.py ┃ ┃ ┗ 📜tests_account_book_log_update.py ┃ ┗ 📜__init__.py ┣ 📂views ┃ ┣ 📜account_book_categories.py ┃ ┣ 📜account_book_logs.py ┃ ┗ 📜account_books.py ┣ 📜__init__.py ┣ 📜admin.py ┣ 📜apps.py ┣ 📜models.py ┣ 📜serializers.py ┗ 📜urls.py 📦config ┗ 📂nginx ┃ ┗ 📜nginx.conf 📦core ┣ 📂migrations ┃ ┗ 📜__init__.py ┣ 📂utils ┃ ┣ 📜decorator.py ┃ ┗ 📜get_obj_n_check_err.py ┣ 📜__init__.py ┣ 📜admin.py ┣ 📜apps.py ┣ 📜models.py ┣ 📜tests.py ┗ 📜views.py 📦payhere ┣ 📜__init__.py ┣ 📜asgi.py ┣ 📜settings.py ┣ 📜urls.py ┗ 📜wsgi.py 📦users ┣ 📂migrations ┃ ┣ 📜0001_initial.py ┃ ┗ 📜__init__.py ┣ 📂tests ┃ ┣ 📜__init__.py ┃ ┣ 📜tests_user_refresh_token.py ┃ ┣ 📜tests_user_signin.py ┃ ┣ 📜tests_user_signout.py ┃ ┗ 📜tests_user_signup.py ┣ 📂views ┃ ┣ 📜user_signin.py ┃ ┣ 📜user_signout.py ┃ ┗ 📜user_signup.py ┣ 📜__init__.py ┣ 📜admin.py ┣ 📜apps.py ┣ 📜models.py ┣ 📜serializers.py ┗ 📜urls.py ┣ 📜.env ┣ 📜.gitignore ┣ 📜docker-compose.yml ┣ 📜Dockerfile ┣ 📜manage.py ┣ 📜README.md ┗ 📜requirements.txt