From 9a7c80a562154ce2ab8faae1d3ae282692543c8e Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 13 Jan 2023 05:12:39 +0900 Subject: [PATCH 01/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 6 ++---- backend/crud/user_crud.py | 21 +++++++++------------ backend/models/user.py | 30 ++++++++++++++++++++++-------- backend/schemas/user_schema.py | 12 ++++-------- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index eb48c5a..05f2b1f 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -9,13 +9,11 @@ router = APIRouter() -# TODO: 에러 처리 - # 유저 생성 @router.post("", status_code=HTTP_201_CREATED, response_model=user_schema.User) def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): - user_ = user_crud.create_user(db, user=user) - return user_ + new_user = user_crud.create_user(db, user=user) + return new_user # 유저 상세 조회 diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index 51e0532..df27073 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -3,29 +3,28 @@ from fastapi import Response, HTTPException from sqlalchemy.orm import Session from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND +from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED # user # id로 user 검색 def get_user_by_token(db: Session, token: str): - user = ( + username = ( db.query(User) .filter(User.is_active == True) .filter(User.token == token) .first() ) - if not user: - return Response(status_code=HTTP_404_NOT_FOUND) - return user + if not username: + return Response(status_code=HTTP_401_UNAUTHORIZED) + return username # user 생성 def create_user(db: Session, user: user_schema.UserCreate): existing_user = ( db.query(User) - .filter(User.phone_num == user.phone_num) - .filter(User.user_type == user.user_type) + .filter(User.username == user.username) .first() ) if existing_user: @@ -38,11 +37,9 @@ def create_user(db: Session, user: user_schema.UserCreate): return db.query(User).filter(User.id == existing_user.id).first() db_user = User( name=user.name, - user_type=user.user_type, - gender=user.gender, - age_range=user.age_range, - phone_num=user.phone_num, - token=user.token, + username=user.username, + password = user.password, + token=user.token ) db.add(db_user) db.commit() diff --git a/backend/models/user.py b/backend/models/user.py index 575287a..92eb2a8 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -6,18 +6,32 @@ from sqlalchemy import func +# class User(Base): +# __tablename__ = "users" + +# id = Column(Integer, primary_key=True, index=True) +# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 +# name = Column(String(100), index=True) +# gender = Column(String(6), index=True) +# age_range = Column(String(20), index=True) +# phone_num = Column(String(16), index=True) +# token = Column(String(255), nullable=False, index=True) +# is_active = Column(Boolean, nullable=False, default=True) +# created_at = Column(TIMESTAMP, server_default=func.now()) +# updated_at = Column( +# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") +# ) + class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) - user_type = Column(Boolean, nullable=False, default=True) - name = Column(String(100), index=True) - gender = Column(String(6), index=True) - age_range = Column(String(20), index=True) - phone_num = Column(String(16), index=True) - token = Column(String(255), nullable=False, index=True) - is_active = Column(Boolean, nullable=False, default=True) + name = Column(String(32), nullable=False, index=True) # 실명 + username = Column(String(32), unique=True, nullable=False, index=True) # 아이디 + password = Column(String(32), nullable=False, index=True) # 비밀번호 + token = Column(String(255), nullable=False, index=True) #JWT를 위한 토큰 created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column( TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") - ) \ No newline at end of file + ) + is_active = Column(Boolean, nullable=False, default=True) \ No newline at end of file diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index 40e9d97..6a62522 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -8,11 +8,9 @@ class Config: class User(UserBase): id: int - user_type: int name: str - gender: str - age_range: str - phone_num: str + username: str + password: str token: str is_active: bool @@ -20,10 +18,8 @@ class User(UserBase): class UserCreate(UserBase): name: str - gender: str - age_range: str - phone_num: str - user_type: bool + username : str + password: str token: str From e341fe22017a33a2ce09544bf3bde0ef782ea719 Mon Sep 17 00:00:00 2001 From: heejung Date: Sun, 15 Jan 2023 02:10:43 +0900 Subject: [PATCH 02/57] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 72 +++++++++++++++++++++++++++++----- backend/crud/user_crud.py | 66 ++++++++++++++++--------------- backend/models/user.py | 17 -------- backend/requirements.txt | 3 ++ backend/schemas/user_schema.py | 39 ++++++++++++++++-- 5 files changed, 136 insertions(+), 61 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index 05f2b1f..a8f8cb3 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -1,26 +1,80 @@ -from fastapi import APIRouter, Depends +from datetime import timedelta, datetime + +from fastapi import APIRouter, HTTPException, Depends +from fastapi.security import OAuth2PasswordRequestForm +from jose import jwt from sqlalchemy.orm import Session -from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT +from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_409_CONFLICT from crud import user_crud +from crud.user_crud import pwd_context from schemas import user_schema from api.dep import get_db router = APIRouter() +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 토큰의 유효기간 +SECRIT_KEY = "bf7c0f28771c1acb9f2da15b0eaf1a76273fd4cf366af0f7fd0e85b079741237" # 암호화시 사용 +ALGORITHM = "HS256" # 토큰 생성시 사용하는 알고리즘 -# 유저 생성 -@router.post("", status_code=HTTP_201_CREATED, response_model=user_schema.User) +@router.post("/create", status_code=HTTP_201_CREATED, response_model=user_schema.User) def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): + ''' + 회원가입 API + user_crud의 existing_user 메소드를 호출하여 사용자가 존재하는지 확인하는 변수 exist_user 생성 + + parameters + ---------- + user: user_schema.UserCreate + db: Session = Depends(get_db) + + return + ------ + 사용자가 존재한다면: + 상태코드 409, "이미 존재하는 사용자입니다" + 존재하지 않는다면: + 사용자 생성 후 new_user 리턴 + + ''' + exist_user = user_crud.existing_user(db, create_user_info=user) + if exist_user: + raise HTTPException(status_code=HTTP_409_CONFLICT, detail="이미 존재하는 사용자입니다.") new_user = user_crud.create_user(db, user=user) return new_user +@router.post("/login", response_model=user_schema.Token) +def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + ''' + 로그인 API + ID를 사용하여 사용자 모델 객체를 가져오는 변수 user 생성 + jwt를 사용하여 access_token 생성 + + return + ------ + user가 아니거나 비밀번호가 일치하지 않을경우: + HTTPException + 일치할 경우: + json값 반환 + ''' + user = user_crud.get_user_by_username(db, form_data.username) + if not user or not pwd_context.verify(form_data.password, user.password): + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + data = { + "sub": user.username, + "exp": datetime.utcow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + } + access_token = jwt.encode(data, SECRIT_KEY, algorithm=ALGORITHM) -# 유저 상세 조회 -@router.get("/{token}") -def get_user_by_id(token: str, db: Session = Depends(get_db)): - users = user_crud.get_user_by_token(db, token=token) - return users + return { + "access_token": access_token, + "token_type": "bearer", + "username": user.username + } # 유저 삭제 diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index df27073..6a1b17c 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -1,52 +1,56 @@ from models import User from schemas import user_schema -from fastapi import Response, HTTPException +from fastapi import Response from sqlalchemy.orm import Session from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED +from starlette.status import HTTP_204_NO_CONTENT +from passlib.context import CryptContext +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -# user -# id로 user 검색 -def get_user_by_token(db: Session, token: str): - username = ( - db.query(User) - .filter(User.is_active == True) - .filter(User.token == token) - .first() - ) - if not username: - return Response(status_code=HTTP_401_UNAUTHORIZED) - return username +def get_user_by_uername(db: Session, username: str): + ''' + ID로 User데이터를 가져와서 비밀번호를 비교하기 위한 메소드 + + parameters + ---------- + db: Session + username: str + + return + ------ + ID로 사용자 모델 객체 리턴 + ''' + return db.query(User).filter(User.username == username).first() -# user 생성 def create_user(db: Session, user: user_schema.UserCreate): - existing_user = ( - db.query(User) - .filter(User.username == user.username) - .first() - ) - if existing_user: - db_user = ( - db.query(User) - .filter(User.id == existing_user.id) - .update({"token": user.token}) - ) - db.commit() - return db.query(User).filter(User.id == existing_user.id).first() db_user = User( name=user.name, username=user.username, - password = user.password, - token=user.token + password = pwd_context.hash(user.password) ) db.add(db_user) db.commit() return db_user -# user 삭제 +def existing_user(db: Session, user: user_schema.UserCreate): + ''' + 동일한 ID로 등록된 사용자가 있는지 조회 + + parameters + ---------- + db: Session + user: user_schema.UserCreate + + return + ------ + ID로 사용자 모델 객체 리턴 + ''' + return db.query(User).filter(User.username == user.username).first() + + def delete_user_by_id(db: Session, user_id: int): user = db.query(User).filter(User.id == user_id).update({"is_active": False}) db.commit() diff --git a/backend/models/user.py b/backend/models/user.py index 92eb2a8..44897d1 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -6,22 +6,6 @@ from sqlalchemy import func -# class User(Base): -# __tablename__ = "users" - -# id = Column(Integer, primary_key=True, index=True) -# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 -# name = Column(String(100), index=True) -# gender = Column(String(6), index=True) -# age_range = Column(String(20), index=True) -# phone_num = Column(String(16), index=True) -# token = Column(String(255), nullable=False, index=True) -# is_active = Column(Boolean, nullable=False, default=True) -# created_at = Column(TIMESTAMP, server_default=func.now()) -# updated_at = Column( -# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") -# ) - class User(Base): __tablename__ = "users" @@ -29,7 +13,6 @@ class User(Base): name = Column(String(32), nullable=False, index=True) # 실명 username = Column(String(32), unique=True, nullable=False, index=True) # 아이디 password = Column(String(32), nullable=False, index=True) # 비밀번호 - token = Column(String(255), nullable=False, index=True) #JWT를 위한 토큰 created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column( TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") diff --git a/backend/requirements.txt b/backend/requirements.txt index 1c610b1..dfe97d3 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,3 +10,6 @@ SQLAlchemy==1.4.46 starlette==0.22.0 typing_extensions==4.4.0 uvicorn==0.20.0 +passlib==1.7.4 +python-jose==3.3.0 +python-multipart==0.0.5 diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index 6a62522..bbc29f4 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, validator class UserBase(BaseModel): @@ -11,16 +11,47 @@ class User(UserBase): name: str username: str password: str - token: str is_active: bool +class Token(BaseModel): + ''' + Token 스키마 + 로그인 API의 출력 항목인 access_token, token_type, username을 속성으로 함 + ''' + access_token: str + token_type: str + username: str + class UserCreate(UserBase): name: str - username : str + username: str password: str - token: str + password_check: str + + @validator('password_check') + def password_match(cls, v, values): + ''' + password와 password_check가 동일한지 확인 + + parameters + ---------- + cls : class 메소드 사용 + v : password_check + values : UserCreate의 속성들이 {변수명: 값} 형태로 전달 + + return + ------ + password가 UserCreate에 있고 입력된 password_check의 값이 password와 일치하지 않는다면: + "비밀번호가 일치하지 않습니다" + 일치한다면: + password + + ''' + if 'password' in values and v != values['password']: + raise ValueError("비밀번호가 일치하지 않습니다") + return v class UserRead(UserCreate): From bac2261882574ea605208fb6dc8fe302b2d78106 Mon Sep 17 00:00:00 2001 From: kckoh Date: Wed, 18 Jan 2023 02:05:05 -0800 Subject: [PATCH 03/57] =?UTF-8?q?chore:=20docker-compose=EC=97=90=20cadvis?= =?UTF-8?q?or=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index db266a3..7b94d4d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -77,6 +77,17 @@ services: image: grafana/grafana ports: - 3001:3000 + cadvisor: + image: google/cadvisor + ports: + - 8080:8080 + volumes: + - /:/rootfs:ro + - /var/run:/var/run:rw + - /sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro + networks: + - mynet volumes: mysql_data_dev: null From 3f95e6dcab31995faef281208105eb6f419607bd Mon Sep 17 00:00:00 2001 From: kimkimgungunwoo Date: Thu, 19 Jan 2023 02:58:05 +0900 Subject: [PATCH 04/57] =?UTF-8?q?feat:=EB=AC=BC=EA=B3=A0=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/fish.py | 37 ++++++++++++++++++++++++++++++++++ backend/crud/fish_crud.py | 18 +++++++++++++++++ backend/main.py | 3 ++- backend/model/fish.py | 23 +++++++++++++++++++++ backend/schemas/fish_schema.py | 36 +++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 backend/api/endpoints/fish.py create mode 100644 backend/crud/fish_crud.py create mode 100644 backend/model/fish.py create mode 100644 backend/schemas/fish_schema.py diff --git a/backend/api/endpoints/fish.py b/backend/api/endpoints/fish.py new file mode 100644 index 0000000..931cdb8 --- /dev/null +++ b/backend/api/endpoints/fish.py @@ -0,0 +1,37 @@ +from fastapi import APIRouter,Depends +from database import SessionLocal + +from database import SessionLocal,engine,Base +from schemas import fish_schema +from crud import fish_crud +import model +from sqlalchemy.orm import Session +from model import fish +fish.Base.metadata.create_all(bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +router=APIRouter( + prefix="/api/fish", +) + + + +@router.post("/",response_model=fish_schema.FishCreate) +def post_fish(fish:fish_schema.FishCreate,db:Session=Depends(get_db)): + fish_crud.create_fish(db,fish) + # return fish_crud.create_fish(db, fish=fish) + + + +@router.get("/{fish_id}",response_model=fish_schema.Fish) +def get_fish(fish_id:int,db:Session=Depends(get_db)): + fish_information=db.query(fish.Fish).filter(fish.Fish.fish_id==fish_id).first() + return fish_information + + \ No newline at end of file diff --git a/backend/crud/fish_crud.py b/backend/crud/fish_crud.py new file mode 100644 index 0000000..206a2e5 --- /dev/null +++ b/backend/crud/fish_crud.py @@ -0,0 +1,18 @@ +from sqlalchemy.orm import Session +from schemas import fish_schema +from model.fish import Fish + +def create_fish(db:Session,fish:fish_schema.FishCreate): + db_fish=Fish( + fish_id=fish.fish_id, + fish_type=fish.fish_type, + open_season=fish.open_season, + closed_season=fish.closed_season, + fish_url=fish.fish_url, + description=fish.description, + toxicity=fish.toxicity + ) + db.add(db_fish) + db.commit() + db.refresh(db_fish) + return db_fish \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 5b6d774..4962090 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,7 @@ from database import engine from api.api import api_router - +from api.endpoints import fish Base.metadata.create_all(bind=engine) # cors @@ -25,3 +25,4 @@ allow_headers=["*"], ) app.include_router(api_router, prefix="/api") +app.include_router(fish.router) \ No newline at end of file diff --git a/backend/model/fish.py b/backend/model/fish.py new file mode 100644 index 0000000..0fc5208 --- /dev/null +++ b/backend/model/fish.py @@ -0,0 +1,23 @@ +from sqlalchemy import Boolean, Column,Integer, String +from sqlalchemy.types import TIMESTAMP,DateTime +from sqlalchemy.sql import text, func + + +from database import Base +from sqlalchemy import func + +class Fish(Base): + __tablename__="fish" + + fish_id=Column(Integer,primary_key=True,index=True) + fish_type=Column(String(64),nullable=False,index=True) + toxicity=Column(String(16),nullable=False,index=True) + open_season=Column(String(64),nullable=False,index=True) + closed_season=Column(String(64),nullable=False,index=True) + description=Column(String(255),nullable=False,index=True) + fish_url=Column(String(100),nullable=False,index=True) + created_at=Column(TIMESTAMP,server_default=func.now()) + updated_at=Column(TIMESTAMP,server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) + is_active=Column(Boolean,nullable=False,default=True) + + \ No newline at end of file diff --git a/backend/schemas/fish_schema.py b/backend/schemas/fish_schema.py new file mode 100644 index 0000000..704925e --- /dev/null +++ b/backend/schemas/fish_schema.py @@ -0,0 +1,36 @@ +from pydantic import BaseModel + +class FishBase(BaseModel): + + + class Config: + orm_mode=True + + + + +class Fish(FishBase): + fish_id:int + description:str + toxicity:str + fish_type:str + open_season:str + closed_season:str + fish_url:str + is_active:bool + + + +class FishCreate(FishBase): + fish_id:int + description:str + toxicity:str + fish_type:str + open_season:str + closed_season:str + fish_url:str + +class FishRead(FishCreate): + fish_id:int + + \ No newline at end of file From b2f4dd81f3cdd27471bb7574da318f3bf84753c9 Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 13 Jan 2023 05:12:39 +0900 Subject: [PATCH 05/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 6 ++---- backend/crud/user_crud.py | 21 +++++++++------------ backend/model/user.py | 30 ++++++++++++++++++++++-------- backend/schemas/user_schema.py | 12 ++++-------- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index eb48c5a..05f2b1f 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -9,13 +9,11 @@ router = APIRouter() -# TODO: 에러 처리 - # 유저 생성 @router.post("", status_code=HTTP_201_CREATED, response_model=user_schema.User) def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): - user_ = user_crud.create_user(db, user=user) - return user_ + new_user = user_crud.create_user(db, user=user) + return new_user # 유저 상세 조회 diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index 571dd25..c387404 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -3,29 +3,28 @@ from fastapi import Response, HTTPException from sqlalchemy.orm import Session from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND +from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED # user # id로 user 검색 def get_user_by_token(db: Session, token: str): - user = ( + username = ( db.query(User) .filter(User.is_active == True) .filter(User.token == token) .first() ) - if not user: - return Response(status_code=HTTP_404_NOT_FOUND) - return user + if not username: + return Response(status_code=HTTP_401_UNAUTHORIZED) + return username # user 생성 def create_user(db: Session, user: user_schema.UserCreate): existing_user = ( db.query(User) - .filter(User.phone_num == user.phone_num) - .filter(User.user_type == user.user_type) + .filter(User.username == user.username) .first() ) if existing_user: @@ -38,11 +37,9 @@ def create_user(db: Session, user: user_schema.UserCreate): return db.query(User).filter(User.id == existing_user.id).first() db_user = User( name=user.name, - user_type=user.user_type, - gender=user.gender, - age_range=user.age_range, - phone_num=user.phone_num, - token=user.token, + username=user.username, + password = user.password, + token=user.token ) db.add(db_user) db.commit() diff --git a/backend/model/user.py b/backend/model/user.py index 575287a..92eb2a8 100644 --- a/backend/model/user.py +++ b/backend/model/user.py @@ -6,18 +6,32 @@ from sqlalchemy import func +# class User(Base): +# __tablename__ = "users" + +# id = Column(Integer, primary_key=True, index=True) +# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 +# name = Column(String(100), index=True) +# gender = Column(String(6), index=True) +# age_range = Column(String(20), index=True) +# phone_num = Column(String(16), index=True) +# token = Column(String(255), nullable=False, index=True) +# is_active = Column(Boolean, nullable=False, default=True) +# created_at = Column(TIMESTAMP, server_default=func.now()) +# updated_at = Column( +# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") +# ) + class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) - user_type = Column(Boolean, nullable=False, default=True) - name = Column(String(100), index=True) - gender = Column(String(6), index=True) - age_range = Column(String(20), index=True) - phone_num = Column(String(16), index=True) - token = Column(String(255), nullable=False, index=True) - is_active = Column(Boolean, nullable=False, default=True) + name = Column(String(32), nullable=False, index=True) # 실명 + username = Column(String(32), unique=True, nullable=False, index=True) # 아이디 + password = Column(String(32), nullable=False, index=True) # 비밀번호 + token = Column(String(255), nullable=False, index=True) #JWT를 위한 토큰 created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column( TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") - ) \ No newline at end of file + ) + is_active = Column(Boolean, nullable=False, default=True) \ No newline at end of file diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index 40e9d97..6a62522 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -8,11 +8,9 @@ class Config: class User(UserBase): id: int - user_type: int name: str - gender: str - age_range: str - phone_num: str + username: str + password: str token: str is_active: bool @@ -20,10 +18,8 @@ class User(UserBase): class UserCreate(UserBase): name: str - gender: str - age_range: str - phone_num: str - user_type: bool + username : str + password: str token: str From 5d44ba1345d37f5196019804198a018cca49d587 Mon Sep 17 00:00:00 2001 From: heejung Date: Sun, 15 Jan 2023 02:10:43 +0900 Subject: [PATCH 06/57] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 72 +++++++++++++++++++++++++++++----- backend/crud/user_crud.py | 66 ++++++++++++++++--------------- backend/model/user.py | 17 -------- backend/schemas/user_schema.py | 39 ++++++++++++++++-- 4 files changed, 133 insertions(+), 61 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index 05f2b1f..a8f8cb3 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -1,26 +1,80 @@ -from fastapi import APIRouter, Depends +from datetime import timedelta, datetime + +from fastapi import APIRouter, HTTPException, Depends +from fastapi.security import OAuth2PasswordRequestForm +from jose import jwt from sqlalchemy.orm import Session -from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT +from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_409_CONFLICT from crud import user_crud +from crud.user_crud import pwd_context from schemas import user_schema from api.dep import get_db router = APIRouter() +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 토큰의 유효기간 +SECRIT_KEY = "bf7c0f28771c1acb9f2da15b0eaf1a76273fd4cf366af0f7fd0e85b079741237" # 암호화시 사용 +ALGORITHM = "HS256" # 토큰 생성시 사용하는 알고리즘 -# 유저 생성 -@router.post("", status_code=HTTP_201_CREATED, response_model=user_schema.User) +@router.post("/create", status_code=HTTP_201_CREATED, response_model=user_schema.User) def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): + ''' + 회원가입 API + user_crud의 existing_user 메소드를 호출하여 사용자가 존재하는지 확인하는 변수 exist_user 생성 + + parameters + ---------- + user: user_schema.UserCreate + db: Session = Depends(get_db) + + return + ------ + 사용자가 존재한다면: + 상태코드 409, "이미 존재하는 사용자입니다" + 존재하지 않는다면: + 사용자 생성 후 new_user 리턴 + + ''' + exist_user = user_crud.existing_user(db, create_user_info=user) + if exist_user: + raise HTTPException(status_code=HTTP_409_CONFLICT, detail="이미 존재하는 사용자입니다.") new_user = user_crud.create_user(db, user=user) return new_user +@router.post("/login", response_model=user_schema.Token) +def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + ''' + 로그인 API + ID를 사용하여 사용자 모델 객체를 가져오는 변수 user 생성 + jwt를 사용하여 access_token 생성 + + return + ------ + user가 아니거나 비밀번호가 일치하지 않을경우: + HTTPException + 일치할 경우: + json값 반환 + ''' + user = user_crud.get_user_by_username(db, form_data.username) + if not user or not pwd_context.verify(form_data.password, user.password): + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + data = { + "sub": user.username, + "exp": datetime.utcow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + } + access_token = jwt.encode(data, SECRIT_KEY, algorithm=ALGORITHM) -# 유저 상세 조회 -@router.get("/{token}") -def get_user_by_id(token: str, db: Session = Depends(get_db)): - users = user_crud.get_user_by_token(db, token=token) - return users + return { + "access_token": access_token, + "token_type": "bearer", + "username": user.username + } # 유저 삭제 diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index c387404..b1e1cec 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -1,52 +1,56 @@ from model import User from schemas import user_schema -from fastapi import Response, HTTPException +from fastapi import Response from sqlalchemy.orm import Session from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED +from starlette.status import HTTP_204_NO_CONTENT +from passlib.context import CryptContext +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -# user -# id로 user 검색 -def get_user_by_token(db: Session, token: str): - username = ( - db.query(User) - .filter(User.is_active == True) - .filter(User.token == token) - .first() - ) - if not username: - return Response(status_code=HTTP_401_UNAUTHORIZED) - return username +def get_user_by_uername(db: Session, username: str): + ''' + ID로 User데이터를 가져와서 비밀번호를 비교하기 위한 메소드 + + parameters + ---------- + db: Session + username: str + + return + ------ + ID로 사용자 모델 객체 리턴 + ''' + return db.query(User).filter(User.username == username).first() -# user 생성 def create_user(db: Session, user: user_schema.UserCreate): - existing_user = ( - db.query(User) - .filter(User.username == user.username) - .first() - ) - if existing_user: - db_user = ( - db.query(User) - .filter(User.id == existing_user.id) - .update({"token": user.token}) - ) - db.commit() - return db.query(User).filter(User.id == existing_user.id).first() db_user = User( name=user.name, username=user.username, - password = user.password, - token=user.token + password = pwd_context.hash(user.password) ) db.add(db_user) db.commit() return db_user -# user 삭제 +def existing_user(db: Session, user: user_schema.UserCreate): + ''' + 동일한 ID로 등록된 사용자가 있는지 조회 + + parameters + ---------- + db: Session + user: user_schema.UserCreate + + return + ------ + ID로 사용자 모델 객체 리턴 + ''' + return db.query(User).filter(User.username == user.username).first() + + def delete_user_by_id(db: Session, user_id: int): user = db.query(User).filter(User.id == user_id).update({"is_active": False}) db.commit() diff --git a/backend/model/user.py b/backend/model/user.py index 92eb2a8..44897d1 100644 --- a/backend/model/user.py +++ b/backend/model/user.py @@ -6,22 +6,6 @@ from sqlalchemy import func -# class User(Base): -# __tablename__ = "users" - -# id = Column(Integer, primary_key=True, index=True) -# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 -# name = Column(String(100), index=True) -# gender = Column(String(6), index=True) -# age_range = Column(String(20), index=True) -# phone_num = Column(String(16), index=True) -# token = Column(String(255), nullable=False, index=True) -# is_active = Column(Boolean, nullable=False, default=True) -# created_at = Column(TIMESTAMP, server_default=func.now()) -# updated_at = Column( -# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") -# ) - class User(Base): __tablename__ = "users" @@ -29,7 +13,6 @@ class User(Base): name = Column(String(32), nullable=False, index=True) # 실명 username = Column(String(32), unique=True, nullable=False, index=True) # 아이디 password = Column(String(32), nullable=False, index=True) # 비밀번호 - token = Column(String(255), nullable=False, index=True) #JWT를 위한 토큰 created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column( TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index 6a62522..bbc29f4 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, validator class UserBase(BaseModel): @@ -11,16 +11,47 @@ class User(UserBase): name: str username: str password: str - token: str is_active: bool +class Token(BaseModel): + ''' + Token 스키마 + 로그인 API의 출력 항목인 access_token, token_type, username을 속성으로 함 + ''' + access_token: str + token_type: str + username: str + class UserCreate(UserBase): name: str - username : str + username: str password: str - token: str + password_check: str + + @validator('password_check') + def password_match(cls, v, values): + ''' + password와 password_check가 동일한지 확인 + + parameters + ---------- + cls : class 메소드 사용 + v : password_check + values : UserCreate의 속성들이 {변수명: 값} 형태로 전달 + + return + ------ + password가 UserCreate에 있고 입력된 password_check의 값이 password와 일치하지 않는다면: + "비밀번호가 일치하지 않습니다" + 일치한다면: + password + + ''' + if 'password' in values and v != values['password']: + raise ValueError("비밀번호가 일치하지 않습니다") + return v class UserRead(UserCreate): From 8bc7c6f6138c5f2605ef13581af90e4a4b6a7cb8 Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:42:16 +0900 Subject: [PATCH 07/57] =?UTF-8?q?feat:=20user=5Fschema=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/schemas/user_schema.py | 73 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index bbc29f4..07eab97 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -1,62 +1,61 @@ from pydantic import BaseModel, validator -class UserBase(BaseModel): - class Config: - orm_mode = True +class UserBase(BaseModel): + name: str + username: str class User(UserBase): id: int - name: str - username: str - password: str is_active: bool + password: str - -class Token(BaseModel): - ''' - Token 스키마 - 로그인 API의 출력 항목인 access_token, token_type, username을 속성으로 함 - ''' - access_token: str - token_type: str - username: str + class Config: + orm_mode = True class UserCreate(UserBase): name: str - username: str - password: str + password1: str password_check: str - @validator('password_check') - def password_match(cls, v, values): + @validator('name', 'username', 'password1', 'password_check') + def not_empty(cls, v): ''' - password와 password_check가 동일한지 확인 + name, username, password1, password_check에 빈 칸이 있는지 확인 - parameters - ---------- - cls : class 메소드 사용 - v : password_check - values : UserCreate의 속성들이 {변수명: 값} 형태로 전달 + parameter + --------- + 값 return ------ - password가 UserCreate에 있고 입력된 password_check의 값이 password와 일치하지 않는다면: - "비밀번호가 일치하지 않습니다" - 일치한다면: - password - + 없으면: + ValueError + 있으면: + 값 반환 ''' - if 'password' in values and v != values['password']: - raise ValueError("비밀번호가 일치하지 않습니다") + if not v: + raise ValueError("모든 값을 입력해야 합니다.") return v + @validator('password_check') + def password_match(cls, v, values): + ''' + password1과 password1이 일치하는지 확인 -class UserRead(UserCreate): - id: int - + parameter + --------- + 값 -class UserDelete(UserBase): - is_active: bool \ No newline at end of file + return + ------ + 일치하지 않으면: + ValueError + 일치하면: + 값 반환 + ''' + if 'password1' in values and v != values['password1']: + raise ValueError("비밀번호가 일치하지 않습니다.") + return v \ No newline at end of file From 3f8eaabca5bdcbec264b289b43de7f473c91fe7c Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:42:48 +0900 Subject: [PATCH 08/57] =?UTF-8?q?feat:=20user=5Fcrud=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/crud/user_crud.py | 144 ++++++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 28 deletions(-) diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index b1e1cec..94034e2 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -1,57 +1,145 @@ -from model import User +from model import user from schemas import user_schema + from fastapi import Response from sqlalchemy.orm import Session -from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT -from passlib.context import CryptContext +from starlette import status + + +SECRET_KEY = "DeepBlue" # 비밀키 + + +def get_user(db: Session, user_id: int): + ''' + 특정 유저 조회를 위해 사용하는 user_id로 유저 객체 반환 + + parameter + --------- + db: Session + user_id: 특정 유저를 불러올 때 입력받는 user_id + + return + ------ + 입력받은 id값과 일치하는 유저 객체 반환 + ''' + return db.query(user.User).filter(user.User.id == user_id).first() + +후 +def get_user_by_username(db: Session, username: str): + ''' + 로그인 유효성 검사를 위해 사용하는 username으로 유저 객체 반환 + + parameter + --------- + db: Session + username: 로그인 시 입력받는 username + + return + ------ + 입력받은 username값과 일치하는 유저 객체 반환 + ''' + return db.query(user.User).filter(user.User.username == username).first() + + +def get_user_by_username_and_password(db: Session, username: str, password: str): + ''' + 로그인 유효성 검사를 위해 사용하는 username과 passowrd로 유저 객체 반환 + + parameter + --------- + db: Session + username: 로그인 시 입력하는 username + passoword: 로그인 시 입력하는 password + + return + ------ + 입력받은 username값과 password값이 일치하는 유저 객체 반환 + ''' + return db.query(user.User).filter((user.User.username == username) & (user.User.password == hash_password(password))).first() -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -def get_user_by_uername(db: Session, username: str): +def hash_password(password: str): ''' - ID로 User데이터를 가져와서 비밀번호를 비교하기 위한 메소드 + 입력받은 password를 hash처리 + + parameter + --------- + password: 로그인 시 입력하는 password - parameters - ---------- + return + ------ + hash처리한 password + ''' + password = password + SECRET_KEY + return password + + +def get_users(db: Session, skip: int = 0, limit: int = 100): + ''' + skip으로 시작하여 limit 수 만큼의 유저 객체 반환 + + parameter + --------- db: Session - username: str + skip: 불러올 유저 순서 + limit: skip으로부터 불러올 유저 수 return ------ - ID로 사용자 모델 객체 리턴 + skip에서 시작하여 limit 수 만큼의 유저 객체 반환 + ''' + return db.query(user.User).offset(skip).limit(limit).all() + + +def create_user(db: Session, user_: user_schema.UserCreate): ''' - return db.query(User).filter(User.username == username).first() + 유저 생성 + parameter + --------- + db: Session + user_: UserCreate 스키마 -def create_user(db: Session, user: user_schema.UserCreate): - db_user = User( - name=user.name, - username=user.username, - password = pwd_context.hash(user.password) - ) + return + ------ + hash처리된 password가 들어간 유저 데이터 + ''' + hashed_password = get_password_hash(user_.password1) + db_user = user.User(name=user_.name, username=user_.username, password=hashed_password) db.add(db_user) db.commit() + db.refresh(db_user) return db_user -def existing_user(db: Session, user: user_schema.UserCreate): +def get_password_hash(password): ''' - 동일한 ID로 등록된 사용자가 있는지 조회 + password hash처리 - parameters - ---------- - db: Session - user: user_schema.UserCreate + parameter + --------- + 회원가입 시 입력받은 password return ------ - ID로 사용자 모델 객체 리턴 + 비밀키를 이용하여 hash처리된 password ''' - return db.query(User).filter(User.username == user.username).first() + return password + SECRET_KEY def delete_user_by_id(db: Session, user_id: int): - user = db.query(User).filter(User.id == user_id).update({"is_active": False}) + ''' + 유저 삭제 + + parameter + --------- + db: Session + user_id : 유저 삭제 시 입력받는 user_id + + return + ------ + 유저 삭제(입력받은 user_id와 일치하는 유저의 is_active값을 False로 설정) 후 상태코드 204 반환 + ''' + user_ = db.query(user.User).filter(user.User.id == user_id).update({"is_active": False}) db.commit() - return Response(status_code=HTTP_204_NO_CONTENT) \ No newline at end of file + return Response(status_code=status.HTTP_204_NO_CONTENT) \ No newline at end of file From b76cd7d6592fabb5181cf25ed81bc501edc28710 Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:43:15 +0900 Subject: [PATCH 09/57] =?UTF-8?q?feat:=20user=20api=20=EC=84=A4=EA=B3=84?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 125 ++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index a8f8cb3..92de112 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -1,83 +1,94 @@ -from datetime import timedelta, datetime +from typing import List -from fastapi import APIRouter, HTTPException, Depends -from fastapi.security import OAuth2PasswordRequestForm -from jose import jwt -from sqlalchemy.orm import Session - -from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_409_CONFLICT -from crud import user_crud -from crud.user_crud import pwd_context from schemas import user_schema +from crud import user_crud + +from fastapi import Depends, APIRouter, HTTPException +from sqlalchemy.orm import Session +from starlette import status from api.dep import get_db + router = APIRouter() -ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 토큰의 유효기간 -SECRIT_KEY = "bf7c0f28771c1acb9f2da15b0eaf1a76273fd4cf366af0f7fd0e85b079741237" # 암호화시 사용 -ALGORITHM = "HS256" # 토큰 생성시 사용하는 알고리즘 -@router.post("/create", status_code=HTTP_201_CREATED, response_model=user_schema.User) -def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): +@router.post("/signup", response_model=user_schema.User) +def create_user(user: user_schema.UserCreate, db: Session = Depends(get_db)): ''' 회원가입 API - user_crud의 existing_user 메소드를 호출하여 사용자가 존재하는지 확인하는 변수 exist_user 생성 - - parameters - ---------- - user: user_schema.UserCreate - db: Session = Depends(get_db) return ------ - 사용자가 존재한다면: - 상태코드 409, "이미 존재하는 사용자입니다" - 존재하지 않는다면: - 사용자 생성 후 new_user 리턴 + 입력받은 username을 사용하는 유저가 있을 경우: + 상태코드 400 + 없을 경우: + 유저 데이터 저장 + ''' + db_user = user_crud.get_user_by_username(db, username=user.username) + if db_user: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="이미 존재하는 아이디 입니다.") + return user_crud.create_user(db=db, user_=user) + + +@router.get("", response_model=List[user_schema.User]) +def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + ''' + 설정 범위 내의 유저 조회 API + ''' + users = user_crud.get_users(db, skip=skip, limit=limit) + return users + +@router.get("/{user_id}", response_model=user_schema.User) +def read_user(user_id: int, db: Session = Depends(get_db)): ''' - exist_user = user_crud.existing_user(db, create_user_info=user) - if exist_user: - raise HTTPException(status_code=HTTP_409_CONFLICT, detail="이미 존재하는 사용자입니다.") - new_user = user_crud.create_user(db, user=user) - return new_user - -@router.post("/login", response_model=user_schema.Token) -def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + 입력한 user_id와 일치하는 유저 조회 API + + return + ------ + 없을 경우: + 상태코드 404 + 있을 경우: + 유저 반환 + ''' + db_user = user_crud.get_user(db, user_id=user_id) + if db_user is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="사용자를 찾을 수 없습니다.") + return db_user + + +@router.post("/login") +def user_login(username: str, password: str, db: Session = Depends(get_db)): ''' 로그인 API - ID를 사용하여 사용자 모델 객체를 가져오는 변수 user 생성 - jwt를 사용하여 access_token 생성 return ------ - user가 아니거나 비밀번호가 일치하지 않을경우: - HTTPException - 일치할 경우: - json값 반환 + 입력받은 username이 없을 경우: + 잘못된 아이디임을 알려줌 + username은 있지만 그 username에 대한 password가 다를경우: + 잘못된 비밀번호임을 알려줌 + 제대로 입력했을 경우: + 로그인 성공 ''' - user = user_crud.get_user_by_username(db, form_data.username) - if not user or not pwd_context.verify(form_data.password, user.password): + user_ = user_crud.get_user_by_username(db, username=username) + user__=user_crud.get_user_by_username_and_password(db, username=username ,password=password) + if user_ is None: raise HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, + status_code=status.HTTP_401_UNAUTHORIZED, + detail="잘못된 아이디 입니다." ) - - data = { - "sub": user.username, - "exp": datetime.utcow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - } - access_token = jwt.encode(data, SECRIT_KEY, algorithm=ALGORITHM) - - return { - "access_token": access_token, - "token_type": "bearer", - "username": user.username - } + elif user__ is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="잘못된 비밀번호 입니다." + ) + return "로그인 성공!" -# 유저 삭제 -@router.delete("/{user_id}", status_code=HTTP_204_NO_CONTENT) +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_user_by_id(user_id: int, db: Session = Depends(get_db)): + ''' + 유저 삭제 API + ''' user_crud.delete_user_by_id(db, user_id=user_id) \ No newline at end of file From 899005b7b14956c7d595ac3fdb616797e9c12487 Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:43:47 +0900 Subject: [PATCH 10/57] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=EB=90=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/requirements.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index b03330d..c9598e3 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -59,10 +59,4 @@ traitlets==5.8.0 typing_extensions==4.4.0 urllib3==1.26.13 uvicorn==0.20.0 -<<<<<<< HEAD wcwidth==0.2.5 -======= -passlib==1.7.4 -python-jose==3.3.0 -python-multipart==0.0.5 ->>>>>>> #12 From 135042b723046c4ee875262965163f142c7f28db Mon Sep 17 00:00:00 2001 From: kckoh Date: Wed, 18 Jan 2023 18:53:00 -0800 Subject: [PATCH 11/57] chore: add cadivosor to docker-compose --- docker-compose.yaml | 7 +++++-- prometheus.yml | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 7b94d4d..7f7caaf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,14 +78,17 @@ services: ports: - 3001:3000 cadvisor: - image: google/cadvisor + image: gcr.io/cadvisor/cadvisor:latest + platform: linux/amd64 + container_name: cadvisor ports: - - 8080:8080 + - 8081:8080 volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro + - /var/run/docker.sock:/var/run/docker.sock:rw networks: - mynet volumes: diff --git a/prometheus.yml b/prometheus.yml index 2b31152..943b002 100644 --- a/prometheus.yml +++ b/prometheus.yml @@ -8,3 +8,7 @@ scrape_configs: - job_name: backend static_configs: - targets: ['backend:8000'] + - job_name: cadvisor + static_configs: + - targets: ['cadvisor:8081'] + From 762414ba0723b7825e724548bb7bd994e6ac1140 Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 13 Jan 2023 05:12:39 +0900 Subject: [PATCH 12/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 6 ++---- backend/crud/user_crud.py | 21 +++++++++------------ backend/model/user.py | 30 ++++++++++++++++++++++-------- backend/schemas/user_schema.py | 12 ++++-------- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index eb48c5a..05f2b1f 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -9,13 +9,11 @@ router = APIRouter() -# TODO: 에러 처리 - # 유저 생성 @router.post("", status_code=HTTP_201_CREATED, response_model=user_schema.User) def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): - user_ = user_crud.create_user(db, user=user) - return user_ + new_user = user_crud.create_user(db, user=user) + return new_user # 유저 상세 조회 diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index 571dd25..c387404 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -3,29 +3,28 @@ from fastapi import Response, HTTPException from sqlalchemy.orm import Session from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_404_NOT_FOUND +from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED # user # id로 user 검색 def get_user_by_token(db: Session, token: str): - user = ( + username = ( db.query(User) .filter(User.is_active == True) .filter(User.token == token) .first() ) - if not user: - return Response(status_code=HTTP_404_NOT_FOUND) - return user + if not username: + return Response(status_code=HTTP_401_UNAUTHORIZED) + return username # user 생성 def create_user(db: Session, user: user_schema.UserCreate): existing_user = ( db.query(User) - .filter(User.phone_num == user.phone_num) - .filter(User.user_type == user.user_type) + .filter(User.username == user.username) .first() ) if existing_user: @@ -38,11 +37,9 @@ def create_user(db: Session, user: user_schema.UserCreate): return db.query(User).filter(User.id == existing_user.id).first() db_user = User( name=user.name, - user_type=user.user_type, - gender=user.gender, - age_range=user.age_range, - phone_num=user.phone_num, - token=user.token, + username=user.username, + password = user.password, + token=user.token ) db.add(db_user) db.commit() diff --git a/backend/model/user.py b/backend/model/user.py index 575287a..92eb2a8 100644 --- a/backend/model/user.py +++ b/backend/model/user.py @@ -6,18 +6,32 @@ from sqlalchemy import func +# class User(Base): +# __tablename__ = "users" + +# id = Column(Integer, primary_key=True, index=True) +# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 +# name = Column(String(100), index=True) +# gender = Column(String(6), index=True) +# age_range = Column(String(20), index=True) +# phone_num = Column(String(16), index=True) +# token = Column(String(255), nullable=False, index=True) +# is_active = Column(Boolean, nullable=False, default=True) +# created_at = Column(TIMESTAMP, server_default=func.now()) +# updated_at = Column( +# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") +# ) + class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) - user_type = Column(Boolean, nullable=False, default=True) - name = Column(String(100), index=True) - gender = Column(String(6), index=True) - age_range = Column(String(20), index=True) - phone_num = Column(String(16), index=True) - token = Column(String(255), nullable=False, index=True) - is_active = Column(Boolean, nullable=False, default=True) + name = Column(String(32), nullable=False, index=True) # 실명 + username = Column(String(32), unique=True, nullable=False, index=True) # 아이디 + password = Column(String(32), nullable=False, index=True) # 비밀번호 + token = Column(String(255), nullable=False, index=True) #JWT를 위한 토큰 created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column( TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") - ) \ No newline at end of file + ) + is_active = Column(Boolean, nullable=False, default=True) \ No newline at end of file diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index 40e9d97..6a62522 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -8,11 +8,9 @@ class Config: class User(UserBase): id: int - user_type: int name: str - gender: str - age_range: str - phone_num: str + username: str + password: str token: str is_active: bool @@ -20,10 +18,8 @@ class User(UserBase): class UserCreate(UserBase): name: str - gender: str - age_range: str - phone_num: str - user_type: bool + username : str + password: str token: str From ce48c88e221a1868825250c8f0d3062d1245e466 Mon Sep 17 00:00:00 2001 From: heejung Date: Sun, 15 Jan 2023 02:10:43 +0900 Subject: [PATCH 13/57] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 72 +++++++++++++++++++++++++++++----- backend/crud/user_crud.py | 66 ++++++++++++++++--------------- backend/model/user.py | 17 -------- backend/schemas/user_schema.py | 39 ++++++++++++++++-- 4 files changed, 133 insertions(+), 61 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index 05f2b1f..a8f8cb3 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -1,26 +1,80 @@ -from fastapi import APIRouter, Depends +from datetime import timedelta, datetime + +from fastapi import APIRouter, HTTPException, Depends +from fastapi.security import OAuth2PasswordRequestForm +from jose import jwt from sqlalchemy.orm import Session -from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT +from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_409_CONFLICT from crud import user_crud +from crud.user_crud import pwd_context from schemas import user_schema from api.dep import get_db router = APIRouter() +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 토큰의 유효기간 +SECRIT_KEY = "bf7c0f28771c1acb9f2da15b0eaf1a76273fd4cf366af0f7fd0e85b079741237" # 암호화시 사용 +ALGORITHM = "HS256" # 토큰 생성시 사용하는 알고리즘 -# 유저 생성 -@router.post("", status_code=HTTP_201_CREATED, response_model=user_schema.User) +@router.post("/create", status_code=HTTP_201_CREATED, response_model=user_schema.User) def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): + ''' + 회원가입 API + user_crud의 existing_user 메소드를 호출하여 사용자가 존재하는지 확인하는 변수 exist_user 생성 + + parameters + ---------- + user: user_schema.UserCreate + db: Session = Depends(get_db) + + return + ------ + 사용자가 존재한다면: + 상태코드 409, "이미 존재하는 사용자입니다" + 존재하지 않는다면: + 사용자 생성 후 new_user 리턴 + + ''' + exist_user = user_crud.existing_user(db, create_user_info=user) + if exist_user: + raise HTTPException(status_code=HTTP_409_CONFLICT, detail="이미 존재하는 사용자입니다.") new_user = user_crud.create_user(db, user=user) return new_user +@router.post("/login", response_model=user_schema.Token) +def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + ''' + 로그인 API + ID를 사용하여 사용자 모델 객체를 가져오는 변수 user 생성 + jwt를 사용하여 access_token 생성 + + return + ------ + user가 아니거나 비밀번호가 일치하지 않을경우: + HTTPException + 일치할 경우: + json값 반환 + ''' + user = user_crud.get_user_by_username(db, form_data.username) + if not user or not pwd_context.verify(form_data.password, user.password): + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + data = { + "sub": user.username, + "exp": datetime.utcow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + } + access_token = jwt.encode(data, SECRIT_KEY, algorithm=ALGORITHM) -# 유저 상세 조회 -@router.get("/{token}") -def get_user_by_id(token: str, db: Session = Depends(get_db)): - users = user_crud.get_user_by_token(db, token=token) - return users + return { + "access_token": access_token, + "token_type": "bearer", + "username": user.username + } # 유저 삭제 diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index c387404..b1e1cec 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -1,52 +1,56 @@ from model import User from schemas import user_schema -from fastapi import Response, HTTPException +from fastapi import Response from sqlalchemy.orm import Session from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED +from starlette.status import HTTP_204_NO_CONTENT +from passlib.context import CryptContext +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -# user -# id로 user 검색 -def get_user_by_token(db: Session, token: str): - username = ( - db.query(User) - .filter(User.is_active == True) - .filter(User.token == token) - .first() - ) - if not username: - return Response(status_code=HTTP_401_UNAUTHORIZED) - return username +def get_user_by_uername(db: Session, username: str): + ''' + ID로 User데이터를 가져와서 비밀번호를 비교하기 위한 메소드 + + parameters + ---------- + db: Session + username: str + + return + ------ + ID로 사용자 모델 객체 리턴 + ''' + return db.query(User).filter(User.username == username).first() -# user 생성 def create_user(db: Session, user: user_schema.UserCreate): - existing_user = ( - db.query(User) - .filter(User.username == user.username) - .first() - ) - if existing_user: - db_user = ( - db.query(User) - .filter(User.id == existing_user.id) - .update({"token": user.token}) - ) - db.commit() - return db.query(User).filter(User.id == existing_user.id).first() db_user = User( name=user.name, username=user.username, - password = user.password, - token=user.token + password = pwd_context.hash(user.password) ) db.add(db_user) db.commit() return db_user -# user 삭제 +def existing_user(db: Session, user: user_schema.UserCreate): + ''' + 동일한 ID로 등록된 사용자가 있는지 조회 + + parameters + ---------- + db: Session + user: user_schema.UserCreate + + return + ------ + ID로 사용자 모델 객체 리턴 + ''' + return db.query(User).filter(User.username == user.username).first() + + def delete_user_by_id(db: Session, user_id: int): user = db.query(User).filter(User.id == user_id).update({"is_active": False}) db.commit() diff --git a/backend/model/user.py b/backend/model/user.py index 92eb2a8..44897d1 100644 --- a/backend/model/user.py +++ b/backend/model/user.py @@ -6,22 +6,6 @@ from sqlalchemy import func -# class User(Base): -# __tablename__ = "users" - -# id = Column(Integer, primary_key=True, index=True) -# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 -# name = Column(String(100), index=True) -# gender = Column(String(6), index=True) -# age_range = Column(String(20), index=True) -# phone_num = Column(String(16), index=True) -# token = Column(String(255), nullable=False, index=True) -# is_active = Column(Boolean, nullable=False, default=True) -# created_at = Column(TIMESTAMP, server_default=func.now()) -# updated_at = Column( -# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") -# ) - class User(Base): __tablename__ = "users" @@ -29,7 +13,6 @@ class User(Base): name = Column(String(32), nullable=False, index=True) # 실명 username = Column(String(32), unique=True, nullable=False, index=True) # 아이디 password = Column(String(32), nullable=False, index=True) # 비밀번호 - token = Column(String(255), nullable=False, index=True) #JWT를 위한 토큰 created_at = Column(TIMESTAMP, server_default=func.now()) updated_at = Column( TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index 6a62522..bbc29f4 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import BaseModel, validator class UserBase(BaseModel): @@ -11,16 +11,47 @@ class User(UserBase): name: str username: str password: str - token: str is_active: bool +class Token(BaseModel): + ''' + Token 스키마 + 로그인 API의 출력 항목인 access_token, token_type, username을 속성으로 함 + ''' + access_token: str + token_type: str + username: str + class UserCreate(UserBase): name: str - username : str + username: str password: str - token: str + password_check: str + + @validator('password_check') + def password_match(cls, v, values): + ''' + password와 password_check가 동일한지 확인 + + parameters + ---------- + cls : class 메소드 사용 + v : password_check + values : UserCreate의 속성들이 {변수명: 값} 형태로 전달 + + return + ------ + password가 UserCreate에 있고 입력된 password_check의 값이 password와 일치하지 않는다면: + "비밀번호가 일치하지 않습니다" + 일치한다면: + password + + ''' + if 'password' in values and v != values['password']: + raise ValueError("비밀번호가 일치하지 않습니다") + return v class UserRead(UserCreate): From a85f3a0f0e3ee413fa5aea6620598e5024f49698 Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 13 Jan 2023 05:12:39 +0900 Subject: [PATCH 14/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/model/user.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/model/user.py b/backend/model/user.py index 44897d1..aa18608 100644 --- a/backend/model/user.py +++ b/backend/model/user.py @@ -6,6 +6,22 @@ from sqlalchemy import func +# class User(Base): +# __tablename__ = "users" + +# id = Column(Integer, primary_key=True, index=True) +# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 +# name = Column(String(100), index=True) +# gender = Column(String(6), index=True) +# age_range = Column(String(20), index=True) +# phone_num = Column(String(16), index=True) +# token = Column(String(255), nullable=False, index=True) +# is_active = Column(Boolean, nullable=False, default=True) +# created_at = Column(TIMESTAMP, server_default=func.now()) +# updated_at = Column( +# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") +# ) + class User(Base): __tablename__ = "users" From ed69067bce32be488d9f4a393a2003315c4855fc Mon Sep 17 00:00:00 2001 From: heejung Date: Sun, 15 Jan 2023 02:10:43 +0900 Subject: [PATCH 15/57] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/model/user.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/backend/model/user.py b/backend/model/user.py index aa18608..44897d1 100644 --- a/backend/model/user.py +++ b/backend/model/user.py @@ -6,22 +6,6 @@ from sqlalchemy import func -# class User(Base): -# __tablename__ = "users" - -# id = Column(Integer, primary_key=True, index=True) -# user_type = Column(Boolean, nullable=False, default=True) # 사용자와 관리자 -# name = Column(String(100), index=True) -# gender = Column(String(6), index=True) -# age_range = Column(String(20), index=True) -# phone_num = Column(String(16), index=True) -# token = Column(String(255), nullable=False, index=True) -# is_active = Column(Boolean, nullable=False, default=True) -# created_at = Column(TIMESTAMP, server_default=func.now()) -# updated_at = Column( -# TIMESTAMP, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") -# ) - class User(Base): __tablename__ = "users" From 1e665223e586b210de3af1c2b70d5dd29797b29c Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:42:16 +0900 Subject: [PATCH 16/57] =?UTF-8?q?feat:=20user=5Fschema=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/schemas/user_schema.py | 73 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/backend/schemas/user_schema.py b/backend/schemas/user_schema.py index bbc29f4..07eab97 100644 --- a/backend/schemas/user_schema.py +++ b/backend/schemas/user_schema.py @@ -1,62 +1,61 @@ from pydantic import BaseModel, validator -class UserBase(BaseModel): - class Config: - orm_mode = True +class UserBase(BaseModel): + name: str + username: str class User(UserBase): id: int - name: str - username: str - password: str is_active: bool + password: str - -class Token(BaseModel): - ''' - Token 스키마 - 로그인 API의 출력 항목인 access_token, token_type, username을 속성으로 함 - ''' - access_token: str - token_type: str - username: str + class Config: + orm_mode = True class UserCreate(UserBase): name: str - username: str - password: str + password1: str password_check: str - @validator('password_check') - def password_match(cls, v, values): + @validator('name', 'username', 'password1', 'password_check') + def not_empty(cls, v): ''' - password와 password_check가 동일한지 확인 + name, username, password1, password_check에 빈 칸이 있는지 확인 - parameters - ---------- - cls : class 메소드 사용 - v : password_check - values : UserCreate의 속성들이 {변수명: 값} 형태로 전달 + parameter + --------- + 값 return ------ - password가 UserCreate에 있고 입력된 password_check의 값이 password와 일치하지 않는다면: - "비밀번호가 일치하지 않습니다" - 일치한다면: - password - + 없으면: + ValueError + 있으면: + 값 반환 ''' - if 'password' in values and v != values['password']: - raise ValueError("비밀번호가 일치하지 않습니다") + if not v: + raise ValueError("모든 값을 입력해야 합니다.") return v + @validator('password_check') + def password_match(cls, v, values): + ''' + password1과 password1이 일치하는지 확인 -class UserRead(UserCreate): - id: int - + parameter + --------- + 값 -class UserDelete(UserBase): - is_active: bool \ No newline at end of file + return + ------ + 일치하지 않으면: + ValueError + 일치하면: + 값 반환 + ''' + if 'password1' in values and v != values['password1']: + raise ValueError("비밀번호가 일치하지 않습니다.") + return v \ No newline at end of file From 511b327e5dcebb02b9ac71ad1205038cdd3b487b Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:42:48 +0900 Subject: [PATCH 17/57] =?UTF-8?q?feat:=20user=5Fcrud=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/crud/user_crud.py | 144 ++++++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 28 deletions(-) diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index b1e1cec..94034e2 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -1,57 +1,145 @@ -from model import User +from model import user from schemas import user_schema + from fastapi import Response from sqlalchemy.orm import Session -from starlette.responses import Response -from starlette.status import HTTP_204_NO_CONTENT -from passlib.context import CryptContext +from starlette import status + + +SECRET_KEY = "DeepBlue" # 비밀키 + + +def get_user(db: Session, user_id: int): + ''' + 특정 유저 조회를 위해 사용하는 user_id로 유저 객체 반환 + + parameter + --------- + db: Session + user_id: 특정 유저를 불러올 때 입력받는 user_id + + return + ------ + 입력받은 id값과 일치하는 유저 객체 반환 + ''' + return db.query(user.User).filter(user.User.id == user_id).first() + +후 +def get_user_by_username(db: Session, username: str): + ''' + 로그인 유효성 검사를 위해 사용하는 username으로 유저 객체 반환 + + parameter + --------- + db: Session + username: 로그인 시 입력받는 username + + return + ------ + 입력받은 username값과 일치하는 유저 객체 반환 + ''' + return db.query(user.User).filter(user.User.username == username).first() + + +def get_user_by_username_and_password(db: Session, username: str, password: str): + ''' + 로그인 유효성 검사를 위해 사용하는 username과 passowrd로 유저 객체 반환 + + parameter + --------- + db: Session + username: 로그인 시 입력하는 username + passoword: 로그인 시 입력하는 password + + return + ------ + 입력받은 username값과 password값이 일치하는 유저 객체 반환 + ''' + return db.query(user.User).filter((user.User.username == username) & (user.User.password == hash_password(password))).first() -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -def get_user_by_uername(db: Session, username: str): +def hash_password(password: str): ''' - ID로 User데이터를 가져와서 비밀번호를 비교하기 위한 메소드 + 입력받은 password를 hash처리 + + parameter + --------- + password: 로그인 시 입력하는 password - parameters - ---------- + return + ------ + hash처리한 password + ''' + password = password + SECRET_KEY + return password + + +def get_users(db: Session, skip: int = 0, limit: int = 100): + ''' + skip으로 시작하여 limit 수 만큼의 유저 객체 반환 + + parameter + --------- db: Session - username: str + skip: 불러올 유저 순서 + limit: skip으로부터 불러올 유저 수 return ------ - ID로 사용자 모델 객체 리턴 + skip에서 시작하여 limit 수 만큼의 유저 객체 반환 + ''' + return db.query(user.User).offset(skip).limit(limit).all() + + +def create_user(db: Session, user_: user_schema.UserCreate): ''' - return db.query(User).filter(User.username == username).first() + 유저 생성 + parameter + --------- + db: Session + user_: UserCreate 스키마 -def create_user(db: Session, user: user_schema.UserCreate): - db_user = User( - name=user.name, - username=user.username, - password = pwd_context.hash(user.password) - ) + return + ------ + hash처리된 password가 들어간 유저 데이터 + ''' + hashed_password = get_password_hash(user_.password1) + db_user = user.User(name=user_.name, username=user_.username, password=hashed_password) db.add(db_user) db.commit() + db.refresh(db_user) return db_user -def existing_user(db: Session, user: user_schema.UserCreate): +def get_password_hash(password): ''' - 동일한 ID로 등록된 사용자가 있는지 조회 + password hash처리 - parameters - ---------- - db: Session - user: user_schema.UserCreate + parameter + --------- + 회원가입 시 입력받은 password return ------ - ID로 사용자 모델 객체 리턴 + 비밀키를 이용하여 hash처리된 password ''' - return db.query(User).filter(User.username == user.username).first() + return password + SECRET_KEY def delete_user_by_id(db: Session, user_id: int): - user = db.query(User).filter(User.id == user_id).update({"is_active": False}) + ''' + 유저 삭제 + + parameter + --------- + db: Session + user_id : 유저 삭제 시 입력받는 user_id + + return + ------ + 유저 삭제(입력받은 user_id와 일치하는 유저의 is_active값을 False로 설정) 후 상태코드 204 반환 + ''' + user_ = db.query(user.User).filter(user.User.id == user_id).update({"is_active": False}) db.commit() - return Response(status_code=HTTP_204_NO_CONTENT) \ No newline at end of file + return Response(status_code=status.HTTP_204_NO_CONTENT) \ No newline at end of file From 5901597f82437c77dd51d9613d0a38079aa11807 Mon Sep 17 00:00:00 2001 From: heejung Date: Thu, 19 Jan 2023 04:43:15 +0900 Subject: [PATCH 18/57] =?UTF-8?q?feat:=20user=20api=20=EC=84=A4=EA=B3=84?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 125 ++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index a8f8cb3..92de112 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -1,83 +1,94 @@ -from datetime import timedelta, datetime +from typing import List -from fastapi import APIRouter, HTTPException, Depends -from fastapi.security import OAuth2PasswordRequestForm -from jose import jwt -from sqlalchemy.orm import Session - -from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, HTTP_409_CONFLICT -from crud import user_crud -from crud.user_crud import pwd_context from schemas import user_schema +from crud import user_crud + +from fastapi import Depends, APIRouter, HTTPException +from sqlalchemy.orm import Session +from starlette import status from api.dep import get_db + router = APIRouter() -ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 토큰의 유효기간 -SECRIT_KEY = "bf7c0f28771c1acb9f2da15b0eaf1a76273fd4cf366af0f7fd0e85b079741237" # 암호화시 사용 -ALGORITHM = "HS256" # 토큰 생성시 사용하는 알고리즘 -@router.post("/create", status_code=HTTP_201_CREATED, response_model=user_schema.User) -def create_user_info(user: user_schema.UserCreate, db: Session = Depends(get_db)): +@router.post("/signup", response_model=user_schema.User) +def create_user(user: user_schema.UserCreate, db: Session = Depends(get_db)): ''' 회원가입 API - user_crud의 existing_user 메소드를 호출하여 사용자가 존재하는지 확인하는 변수 exist_user 생성 - - parameters - ---------- - user: user_schema.UserCreate - db: Session = Depends(get_db) return ------ - 사용자가 존재한다면: - 상태코드 409, "이미 존재하는 사용자입니다" - 존재하지 않는다면: - 사용자 생성 후 new_user 리턴 + 입력받은 username을 사용하는 유저가 있을 경우: + 상태코드 400 + 없을 경우: + 유저 데이터 저장 + ''' + db_user = user_crud.get_user_by_username(db, username=user.username) + if db_user: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="이미 존재하는 아이디 입니다.") + return user_crud.create_user(db=db, user_=user) + + +@router.get("", response_model=List[user_schema.User]) +def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + ''' + 설정 범위 내의 유저 조회 API + ''' + users = user_crud.get_users(db, skip=skip, limit=limit) + return users + +@router.get("/{user_id}", response_model=user_schema.User) +def read_user(user_id: int, db: Session = Depends(get_db)): ''' - exist_user = user_crud.existing_user(db, create_user_info=user) - if exist_user: - raise HTTPException(status_code=HTTP_409_CONFLICT, detail="이미 존재하는 사용자입니다.") - new_user = user_crud.create_user(db, user=user) - return new_user - -@router.post("/login", response_model=user_schema.Token) -def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + 입력한 user_id와 일치하는 유저 조회 API + + return + ------ + 없을 경우: + 상태코드 404 + 있을 경우: + 유저 반환 + ''' + db_user = user_crud.get_user(db, user_id=user_id) + if db_user is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="사용자를 찾을 수 없습니다.") + return db_user + + +@router.post("/login") +def user_login(username: str, password: str, db: Session = Depends(get_db)): ''' 로그인 API - ID를 사용하여 사용자 모델 객체를 가져오는 변수 user 생성 - jwt를 사용하여 access_token 생성 return ------ - user가 아니거나 비밀번호가 일치하지 않을경우: - HTTPException - 일치할 경우: - json값 반환 + 입력받은 username이 없을 경우: + 잘못된 아이디임을 알려줌 + username은 있지만 그 username에 대한 password가 다를경우: + 잘못된 비밀번호임을 알려줌 + 제대로 입력했을 경우: + 로그인 성공 ''' - user = user_crud.get_user_by_username(db, form_data.username) - if not user or not pwd_context.verify(form_data.password, user.password): + user_ = user_crud.get_user_by_username(db, username=username) + user__=user_crud.get_user_by_username_and_password(db, username=username ,password=password) + if user_ is None: raise HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, + status_code=status.HTTP_401_UNAUTHORIZED, + detail="잘못된 아이디 입니다." ) - - data = { - "sub": user.username, - "exp": datetime.utcow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - } - access_token = jwt.encode(data, SECRIT_KEY, algorithm=ALGORITHM) - - return { - "access_token": access_token, - "token_type": "bearer", - "username": user.username - } + elif user__ is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="잘못된 비밀번호 입니다." + ) + return "로그인 성공!" -# 유저 삭제 -@router.delete("/{user_id}", status_code=HTTP_204_NO_CONTENT) +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_user_by_id(user_id: int, db: Session = Depends(get_db)): + ''' + 유저 삭제 API + ''' user_crud.delete_user_by_id(db, user_id=user_id) \ No newline at end of file From 72dfdacca5a1b00d84e2fe3e19243d4e31d78efb Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 20 Jan 2023 03:02:11 +0900 Subject: [PATCH 19/57] =?UTF-8?q?feat:=20access=20token=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/crud/user_crud.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/crud/user_crud.py b/backend/crud/user_crud.py index 94034e2..abdacf8 100644 --- a/backend/crud/user_crud.py +++ b/backend/crud/user_crud.py @@ -1,12 +1,16 @@ +from datetime import datetime, timedelta from model import user from schemas import user_schema from fastapi import Response +from jose import jwt from sqlalchemy.orm import Session from starlette import status SECRET_KEY = "DeepBlue" # 비밀키 +ACCESS_TOKEN_EXPIRE_MINUTES = 60*24 # Access Token 유효기간 : 하루 +ALGORITHM = "HS256" # jwt 인코딩을 위해 필요한 알고리즘 def get_user(db: Session, user_id: int): @@ -24,7 +28,7 @@ def get_user(db: Session, user_id: int): ''' return db.query(user.User).filter(user.User.id == user_id).first() -후 + def get_user_by_username(db: Session, username: str): ''' 로그인 유효성 검사를 위해 사용하는 username으로 유저 객체 반환 @@ -127,6 +131,28 @@ def get_password_hash(password): return password + SECRET_KEY +def create_access_token(data: dict, expires_delta: timedelta | None = None): + ''' + access token 생성 + + parameter + --------- + 유효기간 + + return + ------ + 인코딩 된 jwt + ''' + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + + def delete_user_by_id(db: Session, user_id: int): ''' 유저 삭제 From 9a43c172663ee9d7c68cec9d1c524520cfc63993 Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 20 Jan 2023 03:02:36 +0900 Subject: [PATCH 20/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20access=20token=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/users.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/backend/api/endpoints/users.py b/backend/api/endpoints/users.py index 92de112..3f6d703 100644 --- a/backend/api/endpoints/users.py +++ b/backend/api/endpoints/users.py @@ -1,4 +1,5 @@ from typing import List +from datetime import timedelta from schemas import user_schema from crud import user_crud @@ -69,21 +70,32 @@ def user_login(username: str, password: str, db: Session = Depends(get_db)): username은 있지만 그 username에 대한 password가 다를경우: 잘못된 비밀번호임을 알려줌 제대로 입력했을 경우: - 로그인 성공 + access_token 생성 ''' user_ = user_crud.get_user_by_username(db, username=username) user__=user_crud.get_user_by_username_and_password(db, username=username ,password=password) if user_ is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="잘못된 아이디 입니다." + detail="잘못된 아이디 입니다.", + headers={"WWW-Authenticate": "Bearer"} ) elif user__ is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="잘못된 비밀번호 입니다." + detail="잘못된 비밀번호 입니다.", + headers={"WWW-Authenticate": "Bearer"} ) - return "로그인 성공!" + + access_token_expires = timedelta(minutes=user_crud.ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = user_crud.create_access_token(data = {"sub": username}, expires_delta = access_token_expires) + + return { + "access_token": access_token, + "token_type": "Bearer", + "username": username + } + @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) From 8af9f6acd7fb77cad8c4815a758223f50931d5ea Mon Sep 17 00:00:00 2001 From: heejung Date: Fri, 20 Jan 2023 03:03:56 +0900 Subject: [PATCH 21/57] =?UTF-8?q?feat:=20jwt=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index c9598e3..b6a4e9d 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -60,3 +60,4 @@ typing_extensions==4.4.0 urllib3==1.26.13 uvicorn==0.20.0 wcwidth==0.2.5 +python-jose==3.3.0 \ No newline at end of file From ce23d9a1a4714eefb3ad3b7a7e40524d9c667300 Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 13:00:50 -0800 Subject: [PATCH 22/57] chore: add fish model --- backend/model/__init__.py | 3 ++- backend/model/fish.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/model/__init__.py b/backend/model/__init__.py index b7bb9be..49cf135 100644 --- a/backend/model/__init__.py +++ b/backend/model/__init__.py @@ -1 +1,2 @@ -from .user import User \ No newline at end of file +from .user import User +from .fish import Fish \ No newline at end of file diff --git a/backend/model/fish.py b/backend/model/fish.py index 0fc5208..165d75f 100644 --- a/backend/model/fish.py +++ b/backend/model/fish.py @@ -2,7 +2,6 @@ from sqlalchemy.types import TIMESTAMP,DateTime from sqlalchemy.sql import text, func - from database import Base from sqlalchemy import func From d449c32c080c76a84ddbff9f64ace9a9d74f4073 Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 13:01:15 -0800 Subject: [PATCH 23/57] chore: add fish schema --- backend/schemas/fish_schema.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/backend/schemas/fish_schema.py b/backend/schemas/fish_schema.py index 704925e..802d6ad 100644 --- a/backend/schemas/fish_schema.py +++ b/backend/schemas/fish_schema.py @@ -1,14 +1,9 @@ from pydantic import BaseModel class FishBase(BaseModel): - - class Config: orm_mode=True - - - - + class Fish(FishBase): fish_id:int description:str @@ -18,8 +13,6 @@ class Fish(FishBase): closed_season:str fish_url:str is_active:bool - - class FishCreate(FishBase): fish_id:int From dbfa0202bb2b6a92944912417cd7be64c2ddd538 Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 13:01:39 -0800 Subject: [PATCH 24/57] chore: add ./idea to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ecb57ff..569b1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *__pycache__* .DS_Store .env +./.idea From edeaca7969f6f182f6db64104382f9c0c1586bba Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 13:02:04 -0800 Subject: [PATCH 25/57] chore: add alembic to requiremnets.txt --- backend/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index c9598e3..54df412 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,3 +1,4 @@ +alembic==1.9.2 anyio==3.6.2 appnope==0.1.3 asttokens==2.2.1 @@ -60,3 +61,4 @@ typing_extensions==4.4.0 urllib3==1.26.13 uvicorn==0.20.0 wcwidth==0.2.5 +boto3 \ No newline at end of file From fae5f5da326700a1b33b1a3a65db2ed513ad4083 Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 13:02:47 -0800 Subject: [PATCH 26/57] chore: populate fish --- backend/alembic.ini | 105 ++++++++++++++++++ backend/alembic/README | 1 + backend/alembic/env.py | 78 +++++++++++++ backend/alembic/script.py.mako | 24 ++++ .../versions/3928deaba815_populate_data.py | 53 +++++++++ 5 files changed, 261 insertions(+) create mode 100644 backend/alembic.ini create mode 100644 backend/alembic/README create mode 100644 backend/alembic/env.py create mode 100644 backend/alembic/script.py.mako create mode 100644 backend/alembic/versions/3928deaba815_populate_data.py diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..de26833 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,105 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 +# 172.17.0.1 +sqlalchemy.url = mysql+pymysql://taegong:taegong@localhost:3306/taegong + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/alembic/README b/backend/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 0000000..c8ca5e9 --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +from database import Base +target_metadata = Base.metadata +#target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 0000000..55df286 --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/3928deaba815_populate_data.py b/backend/alembic/versions/3928deaba815_populate_data.py new file mode 100644 index 0000000..c1234b9 --- /dev/null +++ b/backend/alembic/versions/3928deaba815_populate_data.py @@ -0,0 +1,53 @@ +"""populate data + +Revision ID: 3928deaba815 +Revises: +Create Date: 2023-01-20 11:36:44.234410 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '3928deaba815' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + fish_table =op.create_table('fish', + sa.Column('fish_id', mysql.INTEGER(), autoincrement=True, nullable=False), + sa.Column('fish_type', mysql.VARCHAR(length=64), nullable=False), + sa.Column('toxicity', mysql.VARCHAR(length=16), nullable=False), + sa.Column('open_season', mysql.VARCHAR(length=64), nullable=False), + sa.Column('closed_season', mysql.VARCHAR(length=64), nullable=False), + sa.Column('description', mysql.VARCHAR(length=255), nullable=False), + sa.Column('fish_url', mysql.VARCHAR(length=100), nullable=False), + sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=True), + sa.Column('updated_at', mysql.TIMESTAMP(), + server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True), + sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('fish_id'), + mysql_collate='utf8mb4_0900_ai_ci', + mysql_default_charset='utf8mb4', + mysql_engine='InnoDB' + ) + + # ### end Alembic commands ### + op.bulk_insert(fish_table,[ + {'fish_type': 'mackerel', + 'toxicity': 'toxic', + 'open_season': 'january~december', 'closed_season': 'jan~dec', + 'description': 'some description', + 'fish_url': 'https://www.google.com', + 'is_active': 1}, + ] + ) + + +def downgrade() -> None: + op.drop_table('fish') From 3a40b84a3c88464d8ba1e4bbe5d5c896771e68a8 Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 15:53:31 -0800 Subject: [PATCH 27/57] chore: additional changes to versions --- backend/alembic/versions/3928deaba815_populate_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/alembic/versions/3928deaba815_populate_data.py b/backend/alembic/versions/3928deaba815_populate_data.py index c1234b9..126271d 100644 --- a/backend/alembic/versions/3928deaba815_populate_data.py +++ b/backend/alembic/versions/3928deaba815_populate_data.py @@ -15,9 +15,8 @@ branch_labels = None depends_on = None - def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### + # create a fish table fish_table =op.create_table('fish', sa.Column('fish_id', mysql.INTEGER(), autoincrement=True, nullable=False), sa.Column('fish_type', mysql.VARCHAR(length=64), nullable=False), @@ -37,7 +36,7 @@ def upgrade() -> None: mysql_engine='InnoDB' ) - # ### end Alembic commands ### + # populate some fish data op.bulk_insert(fish_table,[ {'fish_type': 'mackerel', 'toxicity': 'toxic', @@ -50,4 +49,5 @@ def upgrade() -> None: def downgrade() -> None: + # drop table op.drop_table('fish') From 7fae087ffb9ccc2d6dd3590c625e158c5b828ac8 Mon Sep 17 00:00:00 2001 From: kckoh Date: Fri, 20 Jan 2023 15:54:11 -0800 Subject: [PATCH 28/57] chore: add almebic upgrade head to docker-compose.yaml --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 7f7caaf..c5391ec 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -44,7 +44,7 @@ services: container_name: deepBlue_backend build: ./backend entrypoint: /bin/bash - command: -c "uvicorn main:app --host 0.0.0.0 --reload" + command: -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --reload" ports: - 8000:8000 networks: From c16b6975909b6242b90fa8ca2797ae502119397f Mon Sep 17 00:00:00 2001 From: kckoh Date: Sat, 21 Jan 2023 11:40:54 -0800 Subject: [PATCH 29/57] chore: add a simple nginx to docker-compose --- .gitignore | 3 ++- backend/nginx.conf | 0 docker-compose.yaml | 9 +++++---- index.html | 10 ---------- nginx.conf | 22 +++++++++------------- 5 files changed, 16 insertions(+), 28 deletions(-) delete mode 100644 backend/nginx.conf delete mode 100644 index.html diff --git a/.gitignore b/.gitignore index 24e41b4..3f7bf42 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ .env ./.idea .idea/ -aws_key.py +backend/api/endpoints/aws/aws_key.py + diff --git a/backend/nginx.conf b/backend/nginx.conf deleted file mode 100644 index e69de29..0000000 diff --git a/docker-compose.yaml b/docker-compose.yaml index 2bfe66a..e30731c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -17,9 +17,10 @@ services: context: ./frontend dockerfile: Dockerfile ports: - - "3000:3000" + - 3000:3000 tty: true - + networks: + - mynet mysql: container_name: deepBlue_mysql # image: mysql:8.0-oracle @@ -41,7 +42,7 @@ services: retries: 3 backend: - container_name: deepBlue_backend + container_name: backend build: ./backend entrypoint: /bin/bash command: -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --reload" @@ -96,9 +97,9 @@ services: container_name: nginx ports: - 80:80 + - 443:443 volumes: - ./nginx.conf:/etc/nginx/nginx.conf - - ./index.html:/usr/share/nginx/html/index.html networks: - mynet volumes: diff --git a/index.html b/index.html deleted file mode 100644 index 3efb39d..0000000 --- a/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Title - - -

Hello World!

- - \ No newline at end of file diff --git a/nginx.conf b/nginx.conf index 87578d7..309d009 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,27 +1,23 @@ -# Complete Nginx Docker reverse proxy config file +user nginx; + events{ worker_connections 1024; } http { + upstream frontend { + server frontend:3000; + } + server { listen 80; listen [::]:80; - server_name localhost; +# server_name frontend; + location / { -# proxy_pass http://localhost:80; - root /usr/share/nginx/html; - index index.html; + proxy_pass http://frontend; } } } -# location /sample { -# proxy_pass http://192.168.246.131:8080/sample; -# } - -# error_page 500 502 503 504 /50x.html; -# location = /50x.html { -# root /usr/share/nginx/html; -# } From 509e825649cfee8c3e624f2e4f62b7d8439ae608 Mon Sep 17 00:00:00 2001 From: kckoh Date: Sat, 21 Jan 2023 15:17:41 -0800 Subject: [PATCH 30/57] chore: delete unnecessary comments --- nginx.conf | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nginx.conf b/nginx.conf index 309d009..7cec545 100644 --- a/nginx.conf +++ b/nginx.conf @@ -8,15 +8,12 @@ http { upstream frontend { server frontend:3000; } - server { listen 80; listen [::]:80; -# server_name frontend; - location / { - proxy_pass http://frontend; + proxy_pass http://frontend; } } } From 8a6f85753e77e2784f1d94b54b912ab09635ed0f Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 18:19:10 +0900 Subject: [PATCH 31/57] =?UTF-8?q?refactor:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/login/index.tsx | 46 ++++ frontend/src/components/signup/index.tsx | 70 ++++++ frontend/src/page/login/index.scss | 266 +---------------------- frontend/src/page/login/index.tsx | 17 +- frontend/src/page/login/loginpage.tsx | 92 +------- 5 files changed, 130 insertions(+), 361 deletions(-) create mode 100644 frontend/src/components/login/index.tsx create mode 100644 frontend/src/components/signup/index.tsx diff --git a/frontend/src/components/login/index.tsx b/frontend/src/components/login/index.tsx new file mode 100644 index 0000000..b2e11c3 --- /dev/null +++ b/frontend/src/components/login/index.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; + +const LoginComponent = () => { + const [id, setId] = useState(''); + const [pw, setPw] = useState(''); + + const [button, setButton] = useState(true); + + function changeButton() { + id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); + } + return ( +
+
LOGIN
+
+
아이디
+ { + setId(e.target.value); + }} + //onKeyup={changeButton} + /> +
+ +
+
비밀번호
+ { + setPw(e.target.value); + }} + onKeyUp={changeButton} + /> +
+ +
+ ); +}; + +export default LoginComponent; diff --git a/frontend/src/components/signup/index.tsx b/frontend/src/components/signup/index.tsx new file mode 100644 index 0000000..af2e3b8 --- /dev/null +++ b/frontend/src/components/signup/index.tsx @@ -0,0 +1,70 @@ +import React, { useState } from 'react'; + +const SignUpComponents = () => { + const [id, setId] = useState(''); + const [pw, setPw] = useState(''); + + const [button, setButton] = useState(true); + + function changeButton() { + id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); + } + return ( +
+
JOIN US
+
+
이름
+ { + setPw(e.target.value); + }} + onKeyUp={changeButton} + /> +
+
+
아이디
+ { + setId(e.target.value); + }} + //onKeyup={changeButton} + /> +
+
+
비밀번호
+ { + setPw(e.target.value); + }} + onKeyUp={changeButton} + /> +
+
+
비밀번호 확인
+ { + setPw(e.target.value); + }} + onKeyUp={changeButton} + /> +
+ +
+ ); +}; + +export default SignUpComponents; diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index 2bbb905..8400649 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -1,245 +1,25 @@ @import '../../media.scss'; -//#높이가 줄어들때 지정 반응형 생각해야함 - .login_background { - //배경 지정을 위한 CSS width: 100%; height: 100vh; - max-height: 100vh; - min-height: 700px; - min-width: 500px; - display: flex; - flex-direction: row; - justify-content: center; background: linear-gradient(180deg, #006ccf 0%, #061a38 82.81%, #071228 100%); - @include tablet { - justify-content: center; - } - @include desktop { - justify-content: center; - } - @media (width < 1440) { - justify-content: flex-start; - } -} - -.login_left_background { - //flex 레이아웃을 위한 css - width: 50%; - height: 100%; - flex-direction: row; - justify-content: center; - display: none; - - @include laptop { - display: flex; - } -} - -.login_logo_background { - //flex 레이아웃을 위한 css - width: 100%; - min-width: 336px; - height: 56px; - display: flex; - flex-direction: row; - justify-self: center; -} - -.login_right_background { - //flex 레이아웃을 위한 css - width: 100%; - height: 100%; - min-height: 200px; - min-width: 264px; - max-height: 100%; - display: flex; - flex-direction: column; - align-items: center; - align-self: center; - justify-content: center; - - @include tablet { - } - @include desktop { - width: 50%; - } } -.insert_login_layout { - //로그인,회원가입 창 지정 - align-self: center; - width: 100%; - height: 100%; - min-width: 200px; - min-height: 600px; - max-width: 400px; - max-height: 600px; - //min-width: 20px; - //min-height: 20px; +.login_mainLayout { display: flex; - flex-direction: column; - justify-self: center; - align-items: center; - background-color: white; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); - border-radius: 20px; - - @include tablet { - //min-width: 400px; - //min-height: 400px; - max-width: 600px; - max-height: 600px; - } - - @include laptop { - //min-width: 400px; - //min-height: 400px; - max-width: 600px; - max-height: 600px; - } - - @include desktop { - //min-width: 400px; - min-height: 400px; - max-width: 600px; - max-height: 600px; - } } -.insert_switch_login { - display: flex; - width: 100%; - height: 3.125rem; - flex-direction: row; - background-color: #d7d7d7; -} - -.switch_button_true { - width: 50%; - height: 100%; - border-radius: 20px 20px 0px 0px; - text-align: center; - font-size: 1.35rem; - font-weight: bold; -} - -.switch_button_false { - width: 50%; - height: 100%; - border-radius: 20px 20px 0px 0px; - background-color: white; - text-align: center; - color: #053366; - font-size: 1.35rem; - font-weight: bold; -} - -.login_logo { - //좌측 상단 로고 CSS - width: 56px; - height: 56px; - justify-items: flex-start; - align-self: flex-start; -} - -.login_web_name { - //좌측상단 이름 css - justify-items: flex-start; - align-self: flex-start; - color: white; - font-size: 36px; -} - -.login_welcome { - //좌측 중앙 문장 CSS - justify-self: center; - align-self: center; - color: white; - font-size: 5rem; +.login_input { + width: 80%; + height: 100px; } .login_welcome_background { - //환영 문구 위치 조정 - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; -} - -.login_ment { - //환영 문구 css - justify-self: center; - align-self: center; - color: white; - font-size: 1.5rem; - opacity: 50%; -} - -.login_page { - // - width: 100%; - height: 100%; - min-width: 400px; - min-height: 164px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-around; - padding: 28px 40px; -} - -.login_button { - width: 350px; - height: 50px; - background-color: #053366; - border-radius: 20px; - color: white; - - @include desktop { - width: 500px; - } - - @include laptop { - width: 500px; - } - - @include tablet { - width: 500px; - } -} - -.login_input { - width: 350px; - height: 50px; - border: 1.5px solid #d8d8d8; - border-radius: 20px; - font-size: 1.25rem; - text-indent: 4%; + display: none; @include tablet { - width: 500px; - font-size: 1rem; - } - @include laptop { - width: 500px; - font-size: 1rem; + display: block; } - @include desktop { - width: 500px; - font-size: 1rem; - } -} - -.whale_img { - //고래 이미지 css - width: 50vh; - height: auto; - justify-self: center; - align-self: center; - - animation: move_whale 4.5s infinite alternate forwards; } @keyframes move_whale { @@ -253,37 +33,3 @@ transform: translateY(0px); } } - -.small_whale { - height: 38%; - width: auto; -} - -.login_text { - font-size: 1.5rem; - font-weight: bold; - color: black; - @include tablet { - font-size: 1rem; - } - @include laptop { - font-size: 1rem; - } - @include desktop { - font-size: 1rem; - } -} - -.join_us { - font-size: 1.7rem; - font-weight: bold; -} - -.mobile_welcome { - color: white; - font-size: 3rem; - justify-self: flex-start; - @include laptop { - display: none; - } -} diff --git a/frontend/src/page/login/index.tsx b/frontend/src/page/login/index.tsx index 599b70c..198adf5 100644 --- a/frontend/src/page/login/index.tsx +++ b/frontend/src/page/login/index.tsx @@ -3,24 +3,15 @@ import './index.scss'; import Is_login_check from './is_login'; import Icon from '@/assets/icon.png'; import Whale from '@/assets/whale.png'; +import Nav from '@/components/nav'; const Login = () => { return ( -
+ <>
-
-
-
Welcome!
-
Please login to use more service
- -
-
-
- {/**/} - -
+
-
+ ); }; diff --git a/frontend/src/page/login/loginpage.tsx b/frontend/src/page/login/loginpage.tsx index faae701..ae1d05a 100644 --- a/frontend/src/page/login/loginpage.tsx +++ b/frontend/src/page/login/loginpage.tsx @@ -1,5 +1,7 @@ import React, { useState } from 'react'; import whale from '@/assets/png_whale2.jpeg'; +import LoginComponent from '@/components/login'; +import SignUpComponents from '@/components/signup'; const Login_page = (props: any) => { const [id, setId] = useState(''); @@ -14,95 +16,9 @@ const Login_page = (props: any) => { const is_login_page = props.is_login_input; if (!is_login_page) { - return ( -
-
LOGIN
-
-
아이디
- { - setId(e.target.value); - }} - //onKeyup={changeButton} - /> -
- -
-
비밀번호
- { - setPw(e.target.value); - }} - onKeyUp={changeButton} - /> -
- -
- ); + return ; } else { - return ( -
-
JOIN US
-
-
이름
- { - setPw(e.target.value); - }} - onKeyUp={changeButton} - /> -
-
-
아이디
- { - setId(e.target.value); - }} - //onKeyup={changeButton} - /> -
-
-
비밀번호
- { - setPw(e.target.value); - }} - onKeyUp={changeButton} - /> -
-
-
비밀번호 확인
- { - setPw(e.target.value); - }} - onKeyUp={changeButton} - /> -
- -
- ); + return ; } }; From 5935a09c115dfea9f6b1b0a29ef78cb701ca012f Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 18:21:45 +0900 Subject: [PATCH 32/57] =?UTF-8?q?fix:=20any=20=3D>=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/login/is_login.tsx | 39 ++++++++++++++++----------- frontend/src/page/login/loginpage.tsx | 16 ++++------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/frontend/src/page/login/is_login.tsx b/frontend/src/page/login/is_login.tsx index 8678957..6af6ddb 100644 --- a/frontend/src/page/login/is_login.tsx +++ b/frontend/src/page/login/is_login.tsx @@ -1,24 +1,33 @@ -import React,{useState} from "react"; -import Login_page from "./loginpage"; +import React, { useState } from 'react'; +import Login_page from './loginpage'; -function Is_login_check({round=false}){ - const [is_login,set_is_login] = useState(false); - const set_login = () =>{ +function Is_login_check({ round = false }) { + const [is_login, set_is_login] = useState(false); + const set_login = () => { set_is_login(true); - - } - const set_signin = () =>{ + }; + const set_signin = () => { set_is_login(false); - } - return( -
+ }; + return ( +
- - + +
- +
); } -export default Is_login_check; \ No newline at end of file +export default Is_login_check; diff --git a/frontend/src/page/login/loginpage.tsx b/frontend/src/page/login/loginpage.tsx index ae1d05a..657c5a3 100644 --- a/frontend/src/page/login/loginpage.tsx +++ b/frontend/src/page/login/loginpage.tsx @@ -3,17 +3,11 @@ import whale from '@/assets/png_whale2.jpeg'; import LoginComponent from '@/components/login'; import SignUpComponents from '@/components/signup'; -const Login_page = (props: any) => { - const [id, setId] = useState(''); - const [pw, setPw] = useState(''); - - const [button, setButton] = useState(true); - - function changeButton() { - id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); - } - - const is_login_page = props.is_login_input; +interface Props { + is_login: boolean; +} +const Login_page = ({ is_login }: Props) => { + const is_login_page = is_login; if (!is_login_page) { return ; From f2b5382187d6cb2eeefe7d67c45dbdeaef18633c Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 18:56:21 +0900 Subject: [PATCH 33/57] =?UTF-8?q?refactor:=20=ED=86=A0=EA=B8=80=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/login/index.scss | 46 +++++++++++++++++++++++++--- frontend/src/page/login/index.tsx | 11 +++++++ frontend/src/page/login/is_login.tsx | 21 +++++++------ 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index 8400649..616c9fa 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -2,26 +2,62 @@ .login_background { width: 100%; + min-height: 100vh; height: 100vh; background: linear-gradient(180deg, #006ccf 0%, #061a38 82.81%, #071228 100%); + display: flex; + flex-direction: column; } .login_mainLayout { display: flex; + width: 100%; + height: 100%; +} + +.login_explanation { + display: none; + @include tablet { + display: flex; + flex-direction: column; + } } .login_input { + display: flex; + justify-content: center; + margin: 0 auto; + margin-top: 100px; + width: 90%; +} + +////Is_login_check +.insert_login_layout { + height: 450px; width: 80%; - height: 100px; + background-color: white; + border-radius: 1rem; } -.login_welcome_background { - display: none; - @include tablet { - display: block; +.insert_switch_login { + display: flex; + align-items: center; + + & > button { + flex-grow: 1; + height: 50px; } + &:hover { + background-color: #eee; + } +} + +.switch_button_true { + background-color: red; } +/// loginpage + @keyframes move_whale { 0% { transform: translateY(0px); diff --git a/frontend/src/page/login/index.tsx b/frontend/src/page/login/index.tsx index 198adf5..5a6d8d6 100644 --- a/frontend/src/page/login/index.tsx +++ b/frontend/src/page/login/index.tsx @@ -4,12 +4,23 @@ import Is_login_check from './is_login'; import Icon from '@/assets/icon.png'; import Whale from '@/assets/whale.png'; import Nav from '@/components/nav'; +import Login_page from './loginpage'; const Login = () => { return ( <>
); diff --git a/frontend/src/page/login/is_login.tsx b/frontend/src/page/login/is_login.tsx index 6af6ddb..53d535c 100644 --- a/frontend/src/page/login/is_login.tsx +++ b/frontend/src/page/login/is_login.tsx @@ -1,31 +1,34 @@ import React, { useState } from 'react'; import Login_page from './loginpage'; - +import './index.scss'; function Is_login_check({ round = false }) { const [is_login, set_is_login] = useState(false); - const set_login = () => { - set_is_login(true); - }; - const set_signin = () => { - set_is_login(false); + + const set_toggle = (e: React.MouseEvent) => { + if (e.currentTarget.innerHTML == '로그인') { + set_is_login(true); + } else { + set_is_login(false); + } }; + return (
- +
); } From 5200997b8468ab30a3417302e5a232e7fa687ae6 Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 19:17:52 +0900 Subject: [PATCH 34/57] =?UTF-8?q?feat:=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/login/index.scss | 14 +++++++ frontend/src/components/login/index.tsx | 6 +-- frontend/src/components/signup/index.scss | 0 frontend/src/components/signup/index.tsx | 22 +++++------ frontend/src/page/login/index.scss | 45 +++++++++++++++++++++-- frontend/src/page/login/is_login.tsx | 2 +- frontend/src/page/login/loginpage.tsx | 6 +-- 7 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 frontend/src/components/login/index.scss create mode 100644 frontend/src/components/signup/index.scss diff --git a/frontend/src/components/login/index.scss b/frontend/src/components/login/index.scss new file mode 100644 index 0000000..4bbf62c --- /dev/null +++ b/frontend/src/components/login/index.scss @@ -0,0 +1,14 @@ +.login_page { + display: flex; + flex-direction: column; +} + +.join_us { + font-size: 2rem; + text-align: center; +} + +.login_page_input { + width: 80%; + height: 30px; +} diff --git a/frontend/src/components/login/index.tsx b/frontend/src/components/login/index.tsx index b2e11c3..2d7f64a 100644 --- a/frontend/src/components/login/index.tsx +++ b/frontend/src/components/login/index.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; - +import './index.scss'; const LoginComponent = () => { const [id, setId] = useState(''); const [pw, setPw] = useState(''); @@ -17,7 +17,7 @@ const LoginComponent = () => { { setId(e.target.value); }} @@ -31,7 +31,7 @@ const LoginComponent = () => { type="password" placeholder="비밀번호를 입력해주세요" id="password" - className="login_input" + className="login_page_input" onChange={(e) => { setPw(e.target.value); }} diff --git a/frontend/src/components/signup/index.scss b/frontend/src/components/signup/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/components/signup/index.tsx b/frontend/src/components/signup/index.tsx index af2e3b8..4e91ad7 100644 --- a/frontend/src/components/signup/index.tsx +++ b/frontend/src/components/signup/index.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; - +import './index.scss'; const SignUpComponents = () => { const [id, setId] = useState(''); const [pw, setPw] = useState(''); @@ -10,14 +10,14 @@ const SignUpComponents = () => { id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); } return ( -
+
JOIN US
-
이름
+
이름
{ setPw(e.target.value); }} @@ -25,11 +25,11 @@ const SignUpComponents = () => { />
-
아이디
+
아이디
{ setId(e.target.value); }} @@ -37,12 +37,12 @@ const SignUpComponents = () => { />
-
비밀번호
+
비밀번호
{ setPw(e.target.value); }} @@ -50,19 +50,19 @@ const SignUpComponents = () => { />
-
비밀번호 확인
+
비밀번호 확인
{ setPw(e.target.value); }} onKeyUp={changeButton} />
- +
); }; diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index 616c9fa..e7ad8a1 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -13,13 +13,42 @@ display: flex; width: 100%; height: 100%; + + & > div { + width: 80%; + } + @include tablet { + justify-content: center; + align-items: center; + margin: 0 auto; + & > div { + width: 50%; + } + } } .login_explanation { display: none; + color: white; @include tablet { display: flex; flex-direction: column; + & > h1 { + font-size: 3rem; + text-align: center; + } + & > p { + font-size: 2rem; + text-align: center; + color: white; + opacity: 0.5; + } + & > img { + width: 30rem; + height: 30rem; + margin: 0 auto; + animation: move_whale 5s infinite alternate forwards; + } } } @@ -27,16 +56,26 @@ display: flex; justify-content: center; margin: 0 auto; - margin-top: 100px; - width: 90%; + margin-top: 50px; + width: 100%; + + @include tablet { + width: 100%; + margin-top: 0; + } } ////Is_login_check .insert_login_layout { height: 450px; - width: 80%; + width: 100%; background-color: white; border-radius: 1rem; + + @include tablet { + height: 600px; + width: 70%; + } } .insert_switch_login { diff --git a/frontend/src/page/login/is_login.tsx b/frontend/src/page/login/is_login.tsx index 53d535c..529fe11 100644 --- a/frontend/src/page/login/is_login.tsx +++ b/frontend/src/page/login/is_login.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import Login_page from './loginpage'; import './index.scss'; function Is_login_check({ round = false }) { - const [is_login, set_is_login] = useState(false); + const [is_login, set_is_login] = useState(true); const set_toggle = (e: React.MouseEvent) => { if (e.currentTarget.innerHTML == '로그인') { diff --git a/frontend/src/page/login/loginpage.tsx b/frontend/src/page/login/loginpage.tsx index 657c5a3..909c514 100644 --- a/frontend/src/page/login/loginpage.tsx +++ b/frontend/src/page/login/loginpage.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import whale from '@/assets/png_whale2.jpeg'; import LoginComponent from '@/components/login'; import SignUpComponents from '@/components/signup'; - +import './index.scss'; interface Props { is_login: boolean; } @@ -10,9 +10,9 @@ const Login_page = ({ is_login }: Props) => { const is_login_page = is_login; if (!is_login_page) { - return ; - } else { return ; + } else { + return ; } }; From 42cf2168f691f18cf0393f6eacda034220f5fb7c Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 19:23:53 +0900 Subject: [PATCH 35/57] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=ED=8B=80=EC=9E=A1=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/login/index.scss | 13 ++++++++++++- frontend/src/components/signup/index.scss | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/login/index.scss b/frontend/src/components/login/index.scss index 4bbf62c..5621799 100644 --- a/frontend/src/components/login/index.scss +++ b/frontend/src/components/login/index.scss @@ -10,5 +10,16 @@ .login_page_input { width: 80%; - height: 30px; + border-radius: 1rem; + height: 50px; + padding: 1rem; + border: 1.5px solid #d8d8d8; +} + +.login_button { + width: 25rem; + height: 3.5rem; + background-color: #053366; + border-radius: 20px; + color: white; } diff --git a/frontend/src/components/signup/index.scss b/frontend/src/components/signup/index.scss index e69de29..699558a 100644 --- a/frontend/src/components/signup/index.scss +++ b/frontend/src/components/signup/index.scss @@ -0,0 +1,18 @@ +.signup_page { +} + +.signup_page_input { + width: 80%; + border-radius: 1rem; + height: 50px; + padding: 1rem; + border: 1.5px solid #d8d8d8; +} + +.signup_page_button { + width: 25rem; + height: 3.5rem; + background-color: #053366; + border-radius: 20px; + color: white; +} From c205ed3bb3f99859b1e11be34da58c24d421caa9 Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 19:26:54 +0900 Subject: [PATCH 36/57] =?UTF-8?q?refactor:=20=ED=85=8C=EB=B8=94=EB=A6=BF?= =?UTF-8?q?=20=EB=B0=98=EC=9D=91=ED=98=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/login/index.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index e7ad8a1..28852d9 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -73,9 +73,12 @@ border-radius: 1rem; @include tablet { - height: 600px; + height: 500px; width: 70%; } + @include laptop { + height: 600px; + } } .insert_switch_login { From f8de0b2d397b85ff6ddc6d80404c8de745a3ff24 Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 20:48:20 +0900 Subject: [PATCH 37/57] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20api=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/signup/index.tsx | 47 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/signup/index.tsx b/frontend/src/components/signup/index.tsx index 4e91ad7..b4a2a53 100644 --- a/frontend/src/components/signup/index.tsx +++ b/frontend/src/components/signup/index.tsx @@ -1,14 +1,49 @@ +import { restFetcher } from '@/queryClient'; +import { useMutation } from '@tanstack/react-query'; import React, { useState } from 'react'; import './index.scss'; const SignUpComponents = () => { + const [userName, setUserName] = useState(''); const [id, setId] = useState(''); const [pw, setPw] = useState(''); + const [checkPw, setCheckPw] = useState(''); const [button, setButton] = useState(true); function changeButton() { id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); } + + //유저생성 + interface User { + name: string; + username: string; + password1: string; + password_check: string; + } + + const { mutate, isLoading } = useMutation((newUser: User) => { + return restFetcher({ + method: 'POST', + path: 'http://localhost:8000/api/users/signup', + body: newUser, + }); + }); + + const createUser = () => { + const newUser = { + name: userName, + username: id, + password1: pw, + password_check: checkPw, + }; + mutate(newUser, { + onSuccess: (data) => { + console.log(data); + alert('회원가입 완료!'); + }, + }); + }; return (
JOIN US
@@ -17,9 +52,10 @@ const SignUpComponents = () => { { - setPw(e.target.value); + setUserName(e.target.value); }} onKeyUp={changeButton} /> @@ -27,6 +63,7 @@ const SignUpComponents = () => {
아이디
{
비밀번호
{
비밀번호 확인
{ - setPw(e.target.value); + setCheckPw(e.target.value); }} onKeyUp={changeButton} />
- +
); }; From cad9f18e252be38d005847583c008b6272793ccb Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 22:53:58 +0900 Subject: [PATCH 38/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=84=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/atom/atom.ts | 7 +++++ frontend/src/components/login/index.tsx | 38 +++++++++++++++++++++++- frontend/src/components/signup/index.tsx | 23 +++++++++----- frontend/src/main.tsx | 3 +- frontend/src/page/main/index.tsx | 9 +++++- 5 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 frontend/src/atom/atom.ts diff --git a/frontend/src/atom/atom.ts b/frontend/src/atom/atom.ts new file mode 100644 index 0000000..c25bbca --- /dev/null +++ b/frontend/src/atom/atom.ts @@ -0,0 +1,7 @@ +import { User } from '@/components/signup'; +import { atom } from 'recoil'; + +export const UUid = atom({ + key: 'user', + default: { name: '', username: '', password1: '', password_check: '' }, +}); diff --git a/frontend/src/components/login/index.tsx b/frontend/src/components/login/index.tsx index 2d7f64a..08a018f 100644 --- a/frontend/src/components/login/index.tsx +++ b/frontend/src/components/login/index.tsx @@ -1,6 +1,15 @@ +import { restFetcher } from '@/queryClient'; +import { useMutation } from '@tanstack/react-query'; import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import './index.scss'; +import axios from 'axios'; +interface userType { + username: string; + password: string; +} const LoginComponent = () => { + const navigator = useNavigate(); const [id, setId] = useState(''); const [pw, setPw] = useState(''); @@ -9,6 +18,31 @@ const LoginComponent = () => { function changeButton() { id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); } + + const { mutate, isLoading, isError } = useMutation((user: userType) => { + return restFetcher({ + method: 'POST', + path: 'http://localhost:8000/api/users/login', + params: user, + }); + }); + + const loginUser = () => { + const user = { + username: id, + password: pw, + }; + mutate(user, { + onSuccess: (data) => { + console.log(data); + axios.defaults.headers.common[ + 'Authorization' + ] = `Bearer ${data.accessToken}`; + localStorage.setItem('access_token', data.access_token); + navigator('/'); + }, + }); + }; return (
LOGIN
@@ -38,7 +72,9 @@ const LoginComponent = () => { onKeyUp={changeButton} />
- +
); }; diff --git a/frontend/src/components/signup/index.tsx b/frontend/src/components/signup/index.tsx index b4a2a53..9e4746b 100644 --- a/frontend/src/components/signup/index.tsx +++ b/frontend/src/components/signup/index.tsx @@ -1,7 +1,16 @@ +import { UUid } from '@/atom/atom'; import { restFetcher } from '@/queryClient'; import { useMutation } from '@tanstack/react-query'; import React, { useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; import './index.scss'; +//유저생성 +export interface User { + name: string; + username: string; + password1: string; + password_check: string; +} const SignUpComponents = () => { const [userName, setUserName] = useState(''); const [id, setId] = useState(''); @@ -10,18 +19,12 @@ const SignUpComponents = () => { const [button, setButton] = useState(true); + const [userInform, setUserInform] = useRecoilState(UUid); + function changeButton() { id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); } - //유저생성 - interface User { - name: string; - username: string; - password1: string; - password_check: string; - } - const { mutate, isLoading } = useMutation((newUser: User) => { return restFetcher({ method: 'POST', @@ -40,8 +43,12 @@ const SignUpComponents = () => { mutate(newUser, { onSuccess: (data) => { console.log(data); + setUserInform(data); alert('회원가입 완료!'); }, + onError: () => { + alert('중복된 아이디입니다!'); + }, }); }; return ( diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 9a206dc..49a8429 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -11,6 +11,7 @@ import { getClient } from './queryClient'; import * as Sentry from '@sentry/react'; import { BrowserTracing } from '@sentry/tracing'; import reset from './reset.scss'; +import axios from 'axios'; // if (import.meta.env.DEV) { // worker.start(); // } @@ -19,7 +20,7 @@ Sentry.init({ integrations: [new BrowserTracing()], tracesSampleRate: 1.0, }); - +axios.defaults.withCredentials = true; const queryClient = getClient(); ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/frontend/src/page/main/index.tsx b/frontend/src/page/main/index.tsx index d3b0a24..da664f4 100644 --- a/frontend/src/page/main/index.tsx +++ b/frontend/src/page/main/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import './index.scss'; import Nav from '@/components/nav'; import InsertImage from '@/components/insertImage'; @@ -6,8 +6,15 @@ import shark from '@/assets/shark.jpg'; import crucian from '@/assets/crucian.jpg'; import turtle from '@/assets/turtle.jpg'; import jellyfish from '@/assets/jellyfish.jpg'; +import { useRecoilState } from 'recoil'; +import { User } from '@/components/signup'; +import { UUid } from '@/atom/atom'; const Main = () => { + const [userInform, setUserInform] = useRecoilState(UUid); + useEffect(() => { + console.log(userInform); + }, []); return (
From 24ef0e8ad073caf306d5afa76f92547bc206edbb Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 23:07:52 +0900 Subject: [PATCH 39/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=EA=B8=B0=EB=8A=A5=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/nav/index.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/nav/index.tsx b/frontend/src/components/nav/index.tsx index 4e810ed..420d386 100644 --- a/frontend/src/components/nav/index.tsx +++ b/frontend/src/components/nav/index.tsx @@ -13,6 +13,12 @@ const Nav = () => { const gotoMain = () => { navigator('/'); }; + + const goLogout = () => { + alert('로그아웃되었습니다!'); + localStorage.removeItem('access_token'); + }; + let token = localStorage.getItem('access_token'); return (
@@ -27,9 +33,15 @@ const Nav = () => { Storage - - Login - + {!token ? ( + + Login + + ) : ( + + Logout + + )}
); From dadcd58ca6f9b6d0662d06a3712979a2b6f4fd95 Mon Sep 17 00:00:00 2001 From: seunghwan Date: Sun, 22 Jan 2023 23:37:55 +0900 Subject: [PATCH 40/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/atom/atom.ts | 8 ++++++- frontend/src/components/signup/index.tsx | 1 + frontend/src/page/storage/index.tsx | 29 ++++++++++++++++++++---- frontend/src/queryClient.ts | 1 + 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/frontend/src/atom/atom.ts b/frontend/src/atom/atom.ts index c25bbca..bd1004b 100644 --- a/frontend/src/atom/atom.ts +++ b/frontend/src/atom/atom.ts @@ -3,5 +3,11 @@ import { atom } from 'recoil'; export const UUid = atom({ key: 'user', - default: { name: '', username: '', password1: '', password_check: '' }, + default: { + name: '', + username: '', + password1: '', + password_check: '', + id: '', + }, }); diff --git a/frontend/src/components/signup/index.tsx b/frontend/src/components/signup/index.tsx index 9e4746b..d32a543 100644 --- a/frontend/src/components/signup/index.tsx +++ b/frontend/src/components/signup/index.tsx @@ -10,6 +10,7 @@ export interface User { username: string; password1: string; password_check: string; + id: string; } const SignUpComponents = () => { const [userName, setUserName] = useState(''); diff --git a/frontend/src/page/storage/index.tsx b/frontend/src/page/storage/index.tsx index fd39ffa..8114e06 100644 --- a/frontend/src/page/storage/index.tsx +++ b/frontend/src/page/storage/index.tsx @@ -5,12 +5,16 @@ import glassPreview from '../../assets/glassPreview.png'; import React, { useEffect, useState } from 'react'; import axios from 'axios'; import './index.scss'; -import { useNavigate } from 'react-router-dom'; +import { Navigate, redirect, useNavigate } from 'react-router-dom'; import DetailFishList from '@/components/DetailFishList'; import logo from '../../assets/logo.png'; import Nav from '@/components/nav'; +import { useRecoilState } from 'recoil'; +import { User } from '@/components/signup'; +import { UUid } from '@/atom/atom'; const Storage = () => { const navigator = useNavigate(); + const [userInform, setUserInform] = useRecoilState(UUid); const [modal, setModal] = useState(false); const [currentModalInform, setCurrentModalInform] = useState({ fish_type: '', @@ -20,13 +24,27 @@ const Storage = () => { description: '', }); - const { data } = useQuery(['FISHLIST'], () => + let token = localStorage.getItem('access_token'); + console.log(userInform.id); + const { data, isLoading } = useQuery(['USER'], () => restFetcher({ method: 'GET', - path: '/api/v1/fishList/all', + path: `http://localhost:8000/api/users/${userInform.id}`, }), ); + if (!token && !data) { + return ; + } + console.log(data); + + // const { data } = useQuery(['FISHLIST'], () => + // restFetcher({ + // method: 'GET', + // path: '/api/v1/fishList/all', + // }), + // ); + const showDetailFish = (item: fishInform) => { setCurrentModalInform(() => item); setModal(true); @@ -49,6 +67,7 @@ const Storage = () => { behavior: 'smooth', }); }; + return (
-
+ {/*
{data?.map((item, index) => { return (
{
); })} -
+
*/} {modal ? ( Date: Sun, 22 Jan 2023 23:43:51 +0900 Subject: [PATCH 41/57] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/nav/index.tsx | 12 +++++++++++- frontend/src/components/signup/index.tsx | 10 +++++++--- frontend/src/page/storage/index.tsx | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/nav/index.tsx b/frontend/src/components/nav/index.tsx index 420d386..5792d65 100644 --- a/frontend/src/components/nav/index.tsx +++ b/frontend/src/components/nav/index.tsx @@ -3,6 +3,9 @@ import { Link, useMatch, useNavigate } from 'react-router-dom'; import home from '../../assets/home.png'; import './index.scss'; import logo from '../../assets/logo.png'; +import { useRecoilState } from 'recoil'; +import { User } from '../signup'; +import { UUid } from '@/atom/atom'; const Nav = () => { const main = useMatch('/'); const storage = useMatch('/storage'); @@ -13,9 +16,16 @@ const Nav = () => { const gotoMain = () => { navigator('/'); }; - + const [userInform, setUserInform] = useRecoilState(UUid); const goLogout = () => { alert('로그아웃되었습니다!'); + setUserInform({ + name: '', + username: '', + password1: '', + password_check: '', + id: '', + }); localStorage.removeItem('access_token'); }; let token = localStorage.getItem('access_token'); diff --git a/frontend/src/components/signup/index.tsx b/frontend/src/components/signup/index.tsx index d32a543..1a67c44 100644 --- a/frontend/src/components/signup/index.tsx +++ b/frontend/src/components/signup/index.tsx @@ -5,13 +5,17 @@ import React, { useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import './index.scss'; //유저생성 -export interface User { +export interface User extends createUser { + id: string; +} + +interface createUser { name: string; username: string; password1: string; password_check: string; - id: string; } + const SignUpComponents = () => { const [userName, setUserName] = useState(''); const [id, setId] = useState(''); @@ -26,7 +30,7 @@ const SignUpComponents = () => { id.includes('@') && pw.length >= 5 ? setButton(false) : setButton(true); } - const { mutate, isLoading } = useMutation((newUser: User) => { + const { mutate, isLoading } = useMutation((newUser: createUser) => { return restFetcher({ method: 'POST', path: 'http://localhost:8000/api/users/signup', diff --git a/frontend/src/page/storage/index.tsx b/frontend/src/page/storage/index.tsx index 8114e06..62f506f 100644 --- a/frontend/src/page/storage/index.tsx +++ b/frontend/src/page/storage/index.tsx @@ -33,7 +33,7 @@ const Storage = () => { }), ); - if (!token && !data) { + if (!token) { return ; } console.log(data); From 92193217bf6ddd6a7197cd668335f15473a01c87 Mon Sep 17 00:00:00 2001 From: kckoh Date: Sun, 22 Jan 2023 12:18:12 -0800 Subject: [PATCH 42/57] =?UTF-8?q?fix:=20fish=20=EB=AA=A8=EB=8D=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/model/fish.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/backend/model/fish.py b/backend/model/fish.py index 165d75f..c35e735 100644 --- a/backend/model/fish.py +++ b/backend/model/fish.py @@ -9,14 +9,20 @@ class Fish(Base): __tablename__="fish" fish_id=Column(Integer,primary_key=True,index=True) + fish_type=Column(String(64),nullable=False,index=True) + scientific_name=Column(String(128),nullable=False,index=True) + classification = Column(String(64), nullable=False, index=True) + description = Column(String(512), nullable=False, index=False) + habitat = Column(String(64), nullable=False, index=False) + toxicity=Column(String(16),nullable=False,index=True) - open_season=Column(String(64),nullable=False,index=True) - closed_season=Column(String(64),nullable=False,index=True) - description=Column(String(255),nullable=False,index=True) - fish_url=Column(String(100),nullable=False,index=True) + open_season=Column(String(64),nullable=False,index=False) + closed_season=Column(String(64),nullable=False,index=False) + fish_url=Column(String(100),nullable=False,index=False) + created_at=Column(TIMESTAMP,server_default=func.now()) updated_at=Column(TIMESTAMP,server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) - is_active=Column(Boolean,nullable=False,default=True) + is_active=Column(Boolean,nullable=False,default=False) \ No newline at end of file From 3e310c54d87b04076d29cc8d69b3a437f63d6619 Mon Sep 17 00:00:00 2001 From: kckoh Date: Sun, 22 Jan 2023 12:18:40 -0800 Subject: [PATCH 43/57] =?UTF-8?q?chore:=20fish=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/alembic.ini | 2 +- .../versions/3928deaba815_populate_data.py | 64 +++++++++++++++++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/backend/alembic.ini b/backend/alembic.ini index de26833..2e5b205 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -55,7 +55,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne # are written from script.py.mako # output_encoding = utf-8 # 172.17.0.1 -sqlalchemy.url = mysql+pymysql://taegong:taegong@localhost:3306/taegong +sqlalchemy.url = mysql+pymysql://taegong:taegong@172.17.0.1:3306/taegong [post_write_hooks] diff --git a/backend/alembic/versions/3928deaba815_populate_data.py b/backend/alembic/versions/3928deaba815_populate_data.py index 126271d..98c6d67 100644 --- a/backend/alembic/versions/3928deaba815_populate_data.py +++ b/backend/alembic/versions/3928deaba815_populate_data.py @@ -23,10 +23,12 @@ def upgrade() -> None: sa.Column('toxicity', mysql.VARCHAR(length=16), nullable=False), sa.Column('open_season', mysql.VARCHAR(length=64), nullable=False), sa.Column('closed_season', mysql.VARCHAR(length=64), nullable=False), - sa.Column('description', mysql.VARCHAR(length=255), nullable=False), - sa.Column('fish_url', mysql.VARCHAR(length=100), nullable=False), - sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), - nullable=True), + sa.Column('description', mysql.VARCHAR(length=512), nullable=False), + sa.Column('classification', mysql.VARCHAR(length=64), nullable=False), + sa.Column('scientific_name', mysql.VARCHAR(length=128), nullable=False), + sa.Column('habitat', mysql.VARCHAR(length=64), nullable=False), + sa.Column('fish_url', mysql.VARCHAR(length=100), nullable=True), + sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'),nullable=True), sa.Column('updated_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True), sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False), @@ -39,11 +41,61 @@ def upgrade() -> None: # populate some fish data op.bulk_insert(fish_table,[ {'fish_type': 'mackerel', - 'toxicity': 'toxic', + 'toxicity': 'No', + 'classification': 'Pisces', + 'scientific_name': 'Scomber japonicus', 'open_season': 'january~december', 'closed_season': 'jan~dec', - 'description': 'some description', + 'description': """It is one of the representative blue-backed fish in the family such as mackerel and tuna and tuna. +It is a migratory fish species that likes the warm sea with a body length of more than 40cm and a temperature of 10-22℃. +It is widely distributed worldwide, eating plankton when fried, and adult fish mainly feed on anchovies or small fish.""", 'fish_url': 'https://www.google.com', + 'habitat': 'Pacific Ocean', 'is_active': 1}, + {'fish_type': 'red stingray', + 'classification': 'Chondrichthyes', + 'scientific_name': 'Dasyatis akajei', + 'toxicity': 'Yes', + 'open_season': 'May', 'closed_season': 'August', + 'description': """ The yellow stingray is a species of fish in the family Colomidae. +There is a long poisonous thorn on the tail, which is about 15cm long, so not only is it very long, but it also has teeth on both sides. if it pokes a human body, +not only does it hurt terribly, but it also has poison at the end of the venomous thorn, which can lead to fainting and even death. """, + 'fish_url': 'https://www.google.com', + 'habitat': 'Pacific Ocean', + 'is_active': 1}, + {'fish_type': 'red snapper', + 'classification': 'Actinopterygii', + 'scientific_name': 'Lutjanus campechanus', + 'toxicity': 'Yes', + 'open_season': 'June', 'closed_season': 'October', + 'description': """Red tung sea bream is a coastal fish species that lives well in brackish waters where fresh water and seawater are mixed, especially in the mangrove area of the estuary of the river. +It is found mainly in the sea, but some species live in the mouth of the river or feed in fresh water. +Red tung sea bream caught in the tropics is said to require attention because cases of cigar terra poisoning have been found in the past.""", + 'fish_url': 'https://www.google.com', + 'habitat': 'Indian Ocean', + 'is_active': 1}, + {'fish_type': 'flat fish', + 'classification': 'Actinopterygii', + 'scientific_name': 'Paralichthys olivaceus', + 'toxicity': 'Yes', + 'open_season': 'January', 'closed_season': 'December', + 'description': """It is a species of sea fish belonging to the flounder family of flounder order It is a flat fish that is also well known as flatfish in Korea. + Sand floors located between 10 and 200 meters deep are mainly preferred. It is not good at swimming long distances. + Some flatfish live in parts of the Korean Peninsula's freshwater depending on the season.You should be careful not to get bitten when you catch a halibut caught by fishing because it has sharp teeth in your mouth.""", + 'fish_url': 'https://www.google.com', + 'habitat': 'The western part of Pacific Ocean', + 'is_active': 1}, + {'fish_type': 'dark-banded rockfish', + 'classification': 'Actinopterygii', + 'scientific_name': 'Sebastes inermis', + 'toxicity': 'No', + 'open_season': 'December', 'closed_season': 'February', + 'description': """It is distributed in all coasts of Korea, southern North Sea of Japan, and northern coast of China, especially in the Yellow Sea and Balhae Bay. +At the age of 4-6 months, 2-6 centimeters of fry appear in a seaweed-rich place and live under the sea from summer to autumn. +Small fish can be caught recklessly and your hands can be cut by pointed dorsal fins and gill caps. """ + , 'fish_url': 'https://www.google.com', + 'habitat': 'a reef of Korean Peninsula', + 'is_active': 1}, + ] ) From 481329a5396b7d34af5eb7ef0dc7472385411bdb Mon Sep 17 00:00:00 2001 From: seunghwan Date: Tue, 24 Jan 2023 02:33:06 +0900 Subject: [PATCH 44/57] =?UTF-8?q?fix:=20alert=20=EB=91=90=EB=B2=88?= =?UTF-8?q?=EB=9C=A8=EB=8A=94=20=EC=98=A4=EB=A5=98=20/=20=EB=A1=9C?= =?UTF-8?q?=EB=94=A9=EC=8A=A4=ED=94=BC=EB=84=88=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=B6=A9=EB=8F=8C=20=EC=98=A4=EB=A5=98=20/=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/loading2/index.scss | 4 ++-- frontend/src/components/loading2/index.tsx | 4 ++-- frontend/src/components/nav/index.scss | 2 +- frontend/src/main.tsx | 18 ++++++++---------- frontend/src/page/login/index.scss | 5 +++-- frontend/src/page/storage/index.tsx | 9 ++++++++- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/loading2/index.scss b/frontend/src/components/loading2/index.scss index a081941..b2d1bd0 100644 --- a/frontend/src/components/loading2/index.scss +++ b/frontend/src/components/loading2/index.scss @@ -1,6 +1,6 @@ @import '../../media.scss'; -.loading { +.loading2 { width: 100%; height: 100vh; display: flex; @@ -10,7 +10,7 @@ z-index: 99; } -.spinner { +.spinner2 { min-width: 60px; min-height: 60px; border: 5px solid rgba(255, 255, 255, 0.1); diff --git a/frontend/src/components/loading2/index.tsx b/frontend/src/components/loading2/index.tsx index bae0587..c2938cc 100644 --- a/frontend/src/components/loading2/index.tsx +++ b/frontend/src/components/loading2/index.tsx @@ -2,8 +2,8 @@ import React from 'react'; import './index.scss'; const Loading2 = () => { return ( -
-
+
+
); }; diff --git a/frontend/src/components/nav/index.scss b/frontend/src/components/nav/index.scss index 1705bb7..2356b47 100644 --- a/frontend/src/components/nav/index.scss +++ b/frontend/src/components/nav/index.scss @@ -1,7 +1,7 @@ @import '@/media.scss'; .nav { width: 95%; - height: 50px; + height: 60px; color: white; margin: 0 auto; display: flex; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 49a8429..3a2bd46 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -23,14 +23,12 @@ Sentry.init({ axios.defaults.withCredentials = true; const queryClient = getClient(); ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - - - - - - - - , + + + + + + + + , ); diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index 28852d9..42e31e4 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -31,6 +31,7 @@ display: none; color: white; @include tablet { + height: 80%; display: flex; flex-direction: column; & > h1 { @@ -44,8 +45,8 @@ opacity: 0.5; } & > img { - width: 30rem; - height: 30rem; + width: 25rem; + height: 25rem; margin: 0 auto; animation: move_whale 5s infinite alternate forwards; } diff --git a/frontend/src/page/storage/index.tsx b/frontend/src/page/storage/index.tsx index 62f506f..6f8c7d6 100644 --- a/frontend/src/page/storage/index.tsx +++ b/frontend/src/page/storage/index.tsx @@ -34,7 +34,14 @@ const Storage = () => { ); if (!token) { - return ; + (async () => { + alert('로그인이 필요한 서비스입니다.'); + })(); + return ( + <> + + + ); } console.log(data); From 5d410827c641e6a2cbd49608a8ab6ab95cbbc820 Mon Sep 17 00:00:00 2001 From: seunghwan Date: Tue, 24 Jan 2023 02:46:29 +0900 Subject: [PATCH 45/57] =?UTF-8?q?fix:=20result=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=ED=8F=B0=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/result/index.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/page/result/index.scss b/frontend/src/page/result/index.scss index de1faa9..3ed3721 100644 --- a/frontend/src/page/result/index.scss +++ b/frontend/src/page/result/index.scss @@ -1,10 +1,6 @@ @import '../../media.scss'; @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300&display=swap'); -* { - font-family: 'Noto Sans Kr' !important; -} - .insert_resultback { width: 100vw; height: 100vh; From 97e4ef5ba93f9f40aff492b2c426801d689c3dce Mon Sep 17 00:00:00 2001 From: JongYun Jeong <95991290+BellYun@users.noreply.github.com> Date: Tue, 24 Jan 2023 05:41:04 +0900 Subject: [PATCH 46/57] =?UTF-8?q?#refactor=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8,=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=8A=A4?= =?UTF-8?q?=EC=9C=84=EC=B9=98,=20=EB=B2=84=ED=8A=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/login/index.scss | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index 42e31e4..e626476 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -85,6 +85,7 @@ .insert_switch_login { display: flex; align-items: center; + background-color: #d7d7d7; & > button { flex-grow: 1; @@ -95,8 +96,24 @@ } } -.switch_button_true { - background-color: red; +.switch_button_false{ + width: 50%; + height: 100%; + border-radius: 20px 20px 0px 0px; + text-align: center; + font-size: 1.35rem; + font-weight: bold; +} + +.switch_button_true{ + width: 50%; + height: 100%; + border-radius: 20px 20px 0px 0px; + background-color: white; + text-align: center; + color :#053366; + font-size: 1.35rem; + font-weight: bold; } /// loginpage From f61d509d954fb8c2356944041299b13a58a133c4 Mon Sep 17 00:00:00 2001 From: JongYun Jeong <95991290+BellYun@users.noreply.github.com> Date: Tue, 24 Jan 2023 06:15:55 +0900 Subject: [PATCH 47/57] =?UTF-8?q?#refactor=20:=20input=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/login/index.scss | 39 ++++++++++++++++------ frontend/src/components/login/index.tsx | 4 +-- frontend/src/components/signup/index.scss | 40 +++++++++++++++++++---- frontend/src/components/signup/index.tsx | 10 +++--- 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/login/index.scss b/frontend/src/components/login/index.scss index 5621799..b9d7e14 100644 --- a/frontend/src/components/login/index.scss +++ b/frontend/src/components/login/index.scss @@ -1,25 +1,44 @@ .login_page { + width: 100%; + height: 90%; display: flex; flex-direction: column; + align-items: center; + justify-content: space-around; + padding: 28px 40px; + padding-top: 0; } -.join_us { - font-size: 2rem; - text-align: center; +.join_us{ + font-size: 1.7rem; + font-weight: bold; } + +.user_input{ + width: 100%; +} + +.login_page_text{ + font-size: 1rem; + font-weight: bold; + color: black; +} + + .login_page_input { - width: 80%; - border-radius: 1rem; - height: 50px; - padding: 1rem; + width: 100%; + height: 40px; border: 1.5px solid #d8d8d8; + border-radius: 20px; + font-size: 1rem; + text-indent: 4%; } .login_button { - width: 25rem; - height: 3.5rem; + width: 100%; + height: 30px; background-color: #053366; border-radius: 20px; color: white; -} +} \ No newline at end of file diff --git a/frontend/src/components/login/index.tsx b/frontend/src/components/login/index.tsx index 08a018f..302260d 100644 --- a/frontend/src/components/login/index.tsx +++ b/frontend/src/components/login/index.tsx @@ -46,7 +46,7 @@ const LoginComponent = () => { return (
LOGIN
-
+
아이디
{ />
-
+
비밀번호
{ }, }); }; + + return (
JOIN US
-
+
이름
{ onKeyUp={changeButton} />
-
+
아이디
{ //onKeyup={changeButton} />
-
+
비밀번호
{ onKeyUp={changeButton} />
-
+
비밀번호 확인
Date: Tue, 24 Jan 2023 13:19:16 +0900 Subject: [PATCH 48/57] =?UTF-8?q?feat/91-=EB=AC=BC=EA=B3=A0=EA=B8=B0=20?= =?UTF-8?q?=EB=8F=84=EA=B0=90=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/api.py | 6 +++-- backend/api/endpoints/fish.py | 21 ++++----------- backend/api/endpoints/history.py | 26 +++++++++++++++++++ backend/crud/fish_crud.py | 8 +++--- backend/crud/history_crud.py | 43 +++++++++++++++++++++++++++++++ backend/main.py | 2 -- backend/model/fish.py | 3 +++ backend/model/history.py | 25 ++++++++++++++++++ backend/schemas/fish_schema.py | 16 +++--------- backend/schemas/history_schema.py | 22 ++++++++++++++++ 10 files changed, 136 insertions(+), 36 deletions(-) create mode 100644 backend/api/endpoints/history.py create mode 100644 backend/crud/history_crud.py create mode 100644 backend/model/history.py create mode 100644 backend/schemas/history_schema.py diff --git a/backend/api/api.py b/backend/api/api.py index a39db0b..5284274 100644 --- a/backend/api/api.py +++ b/backend/api/api.py @@ -1,6 +1,8 @@ from fastapi import APIRouter -from api.endpoints import users, ai +from api.endpoints import users, ai,fish,history api_router = APIRouter() api_router.include_router(users.router, prefix="/users", tags=["users"]) -api_router.include_router(ai.router, prefix="/ai", tags=["ai"]) \ No newline at end of file +api_router.include_router(ai.router, prefix="/ai", tags=["ai"]) +api_router.include_router(fish.router, prefix="/fish",tags=["fish"]) +api_router.include_router(history.router, prefix="/history",tags=["history"]) \ No newline at end of file diff --git a/backend/api/endpoints/fish.py b/backend/api/endpoints/fish.py index 931cdb8..6c03c6f 100644 --- a/backend/api/endpoints/fish.py +++ b/backend/api/endpoints/fish.py @@ -1,35 +1,24 @@ from fastapi import APIRouter,Depends from database import SessionLocal -from database import SessionLocal,engine,Base from schemas import fish_schema from crud import fish_crud -import model from sqlalchemy.orm import Session from model import fish -fish.Base.metadata.create_all(bind=engine) - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() +from api.dep import get_db -router=APIRouter( - prefix="/api/fish", -) +router=APIRouter() -@router.post("/",response_model=fish_schema.FishCreate) +@router.post("/",response_model=fish_schema.FishRead) def post_fish(fish:fish_schema.FishCreate,db:Session=Depends(get_db)): fish_crud.create_fish(db,fish) - # return fish_crud.create_fish(db, fish=fish) + return fish_crud.create_fish(db,fish) -@router.get("/{fish_id}",response_model=fish_schema.Fish) +@router.get("/{fish_id}",response_model=fish_schema.FishRead) def get_fish(fish_id:int,db:Session=Depends(get_db)): fish_information=db.query(fish.Fish).filter(fish.Fish.fish_id==fish_id).first() return fish_information diff --git a/backend/api/endpoints/history.py b/backend/api/endpoints/history.py new file mode 100644 index 0000000..b537fc7 --- /dev/null +++ b/backend/api/endpoints/history.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter,Depends +from database import SessionLocal +from api.dep import get_db +from sqlalchemy.orm import Session +from schemas import history_schema +from model import history +from crud import history_crud +from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/user/login") + +router=APIRouter() + + + +@router.post("/") +def post_history(history:history_schema.create_History, + db:Session=Depends(get_db), + token:str=Depends(oauth2_scheme)): + history_user_id=history_crud.get_current_user(db,token) + history_crud.create_history(db,history,history_user_id) + +@router.get("/user_history",response_model=list[history_schema.read_History]) +def get_history(db:Session=Depends(get_db), + token:str=Depends(oauth2_scheme)): + history_user_id=history_crud.get_current_user(db,token) + return db.query(history.History).filter(history.History.user_id==history_user_id).all() diff --git a/backend/crud/fish_crud.py b/backend/crud/fish_crud.py index 206a2e5..6b16082 100644 --- a/backend/crud/fish_crud.py +++ b/backend/crud/fish_crud.py @@ -4,15 +4,17 @@ def create_fish(db:Session,fish:fish_schema.FishCreate): db_fish=Fish( - fish_id=fish.fish_id, fish_type=fish.fish_type, open_season=fish.open_season, closed_season=fish.closed_season, fish_url=fish.fish_url, description=fish.description, - toxicity=fish.toxicity + toxicity=fish.toxicity, + fish_name=fish.fish_name, + fish_habitat=fish.fish_habitat, + fish_scientific_name=fish.fish_scientific_name + ) db.add(db_fish) db.commit() - db.refresh(db_fish) return db_fish \ No newline at end of file diff --git a/backend/crud/history_crud.py b/backend/crud/history_crud.py new file mode 100644 index 0000000..6e17a60 --- /dev/null +++ b/backend/crud/history_crud.py @@ -0,0 +1,43 @@ +from sqlalchemy.orm import Session +from schemas import history_schema +from model.history import History +from model.fish import Fish +from model.user import User +from fastapi import HTTPException +from starlette import status +from jose import jwt, JWTError + +ALGORITHM = "HS256" # jwt 인코딩을 위해 필요한 알고리즘 +SECRET_KEY = "DeepBlue" # 비밀키 + +def create_history(db:Session, + history:history_schema.create_History, + history_user_id:int): + + db_history=History( + user_id=history_user_id, + fish_url=history.fish_url, + fish_name=history.fish_name + ) + db.add(db_history) + db.commit() + return db_history + +def get_current_user(db:Session,token:str): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + except JWTError: + raise credentials_exception + else: + user=db.query(User).filter(User.name==username).first() + user_id=user.id + return user_id + \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 4962090..616e6ec 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,6 @@ from database import engine from api.api import api_router -from api.endpoints import fish Base.metadata.create_all(bind=engine) # cors @@ -25,4 +24,3 @@ allow_headers=["*"], ) app.include_router(api_router, prefix="/api") -app.include_router(fish.router) \ No newline at end of file diff --git a/backend/model/fish.py b/backend/model/fish.py index 0fc5208..9f6aaac 100644 --- a/backend/model/fish.py +++ b/backend/model/fish.py @@ -11,6 +11,9 @@ class Fish(Base): fish_id=Column(Integer,primary_key=True,index=True) fish_type=Column(String(64),nullable=False,index=True) + fish_name=Column(String(64),nullable=False,index=True) + fish_habitat=Column(String(64),nullable=False,index=True) + fish_scientific_name=Column(String(64),nullable=False,index=True) toxicity=Column(String(16),nullable=False,index=True) open_season=Column(String(64),nullable=False,index=True) closed_season=Column(String(64),nullable=False,index=True) diff --git a/backend/model/history.py b/backend/model/history.py new file mode 100644 index 0000000..1461e1a --- /dev/null +++ b/backend/model/history.py @@ -0,0 +1,25 @@ +from sqlalchemy import Boolean, Column,Integer, String,ForeignKey +from sqlalchemy.types import TIMESTAMP +from sqlalchemy.sql import text, func +from database import Base +from model.user import User +from model.fish import Fish +from sqlalchemy.orm import relationship + + +class History(Base): + __tablename__="history" + + history_id=Column(Integer,nullable=False,primary_key=True) + fish_url=Column(String(100),nullable=False,index=True) + fish_name=Column(String(100),nullable=False,index=True) + user_id=Column(Integer,ForeignKey("users.id")) + fish_id=Column(Integer,ForeignKey("fish.fish_id")) + created_at=Column(TIMESTAMP,server_default=func.now()) + updated_at=Column(TIMESTAMP,server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) + is_deleted=Column(Boolean,default=False,nullable=False) + fish=relationship("Fish",backref="history") + user=relationship("User",backref="history") + + + \ No newline at end of file diff --git a/backend/schemas/fish_schema.py b/backend/schemas/fish_schema.py index 704925e..8a6196f 100644 --- a/backend/schemas/fish_schema.py +++ b/backend/schemas/fish_schema.py @@ -8,27 +8,17 @@ class Config: - -class Fish(FishBase): - fish_id:int - description:str - toxicity:str - fish_type:str - open_season:str - closed_season:str - fish_url:str - is_active:bool - - class FishCreate(FishBase): - fish_id:int description:str toxicity:str fish_type:str open_season:str closed_season:str fish_url:str + fish_name:str + fish_habitat:str + fish_scientific_name:str class FishRead(FishCreate): fish_id:int diff --git a/backend/schemas/history_schema.py b/backend/schemas/history_schema.py new file mode 100644 index 0000000..b5a6545 --- /dev/null +++ b/backend/schemas/history_schema.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel + +class HistoryBase(BaseModel): + + + class Config: + orm_mode = True + + +class create_History(HistoryBase): + fish_url:str + fish_name:str + + +class read_History(HistoryBase): + fish_id:int + fish_url:int + fish_name:str + + + + \ No newline at end of file From 71fdeecc117e4c1ffc4f2158c1b51092ed56f1f6 Mon Sep 17 00:00:00 2001 From: kckoh Date: Mon, 23 Jan 2023 20:55:55 -0800 Subject: [PATCH 49/57] feat: add get method for fish crud --- backend/api/endpoints/fish.py | 3 +-- backend/crud/fish_crud.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/api/endpoints/fish.py b/backend/api/endpoints/fish.py index 931cdb8..6f6e336 100644 --- a/backend/api/endpoints/fish.py +++ b/backend/api/endpoints/fish.py @@ -31,7 +31,6 @@ def post_fish(fish:fish_schema.FishCreate,db:Session=Depends(get_db)): @router.get("/{fish_id}",response_model=fish_schema.Fish) def get_fish(fish_id:int,db:Session=Depends(get_db)): - fish_information=db.query(fish.Fish).filter(fish.Fish.fish_id==fish_id).first() - return fish_information + return fish_crud.get_fish(db,fish_id) \ No newline at end of file diff --git a/backend/crud/fish_crud.py b/backend/crud/fish_crud.py index 206a2e5..b4ad7a9 100644 --- a/backend/crud/fish_crud.py +++ b/backend/crud/fish_crud.py @@ -15,4 +15,7 @@ def create_fish(db:Session,fish:fish_schema.FishCreate): db.add(db_fish) db.commit() db.refresh(db_fish) - return db_fish \ No newline at end of file + return db_fish + +def get_fish(db:Session,fish_id:int): + return db.query(Fish).filter(Fish.fish_id==fish_id).first() \ No newline at end of file From 701f3208605c4639c3a4b7e876e73ccffb94b1a7 Mon Sep 17 00:00:00 2001 From: kckoh Date: Mon, 23 Jan 2023 22:08:12 -0800 Subject: [PATCH 50/57] chore: fish table is modified --- backend/model/fish.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/model/fish.py b/backend/model/fish.py index 67fb239..afa6296 100644 --- a/backend/model/fish.py +++ b/backend/model/fish.py @@ -16,7 +16,8 @@ class Fish(Base): description = Column(String(512), nullable=False, index=False) habitat = Column(String(64), nullable=False, index=False) - toxicity=Column(String(16),nullable=False,index=False) + + toxicity=Column(String(16),nullable=False,index=True) open_season=Column(String(64),nullable=False,index=False) closed_season=Column(String(64),nullable=False,index=False) fish_url=Column(String(100),nullable=False,index=False) From 03d7bafa5a5ff5cf92c2f91ab41f3e2af54d8bf5 Mon Sep 17 00:00:00 2001 From: kckoh Date: Mon, 23 Jan 2023 22:55:46 -0800 Subject: [PATCH 51/57] =?UTF-8?q?fix:=20fish=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20=20crud=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/fish.py | 12 ++++------- backend/crud/fish_crud.py | 5 +++-- backend/schemas/fish_schema.py | 38 +++++++++++++++++++++------------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/backend/api/endpoints/fish.py b/backend/api/endpoints/fish.py index 2dbe174..1b66f70 100644 --- a/backend/api/endpoints/fish.py +++ b/backend/api/endpoints/fish.py @@ -9,15 +9,11 @@ router=APIRouter() +# @router.post("/",response_model=fish_schema.FishRead) +# def post_fish(fish:fish_schema.FishCreate,db:Session=Depends(get_db)): +# fish_crud.create_fish(db,fish) +# return fish_crud.create_fish(db,fish) - -@router.post("/",response_model=fish_schema.FishRead) -def post_fish(fish:fish_schema.FishCreate,db:Session=Depends(get_db)): - fish_crud.create_fish(db,fish) - return fish_crud.create_fish(db,fish) - - - @router.get("/{fish_id}",response_model=fish_schema.FishRead) def get_fish(fish_id:int,db:Session=Depends(get_db)): return fish_crud.get_fish(db,fish_id) diff --git a/backend/crud/fish_crud.py b/backend/crud/fish_crud.py index ea7188e..2eee3c5 100644 --- a/backend/crud/fish_crud.py +++ b/backend/crud/fish_crud.py @@ -11,8 +11,9 @@ def create_fish(db:Session,fish:fish_schema.FishCreate): description=fish.description, toxicity=fish.toxicity, fish_name=fish.fish_name, - fish_habitat=fish.fish_habitat, - fish_scientific_name=fish.fish_scientific_name + habitat=fish.habitat, + scientific_name=fish.scientific_name, + classication=fish.classification ) db.add(db_fish) diff --git a/backend/schemas/fish_schema.py b/backend/schemas/fish_schema.py index 686089b..6c117d5 100644 --- a/backend/schemas/fish_schema.py +++ b/backend/schemas/fish_schema.py @@ -4,15 +4,16 @@ class FishBase(BaseModel): class Config: orm_mode=True -class Fish(FishBase): - fish_id:int - description:str - toxicity:str - fish_type:str - open_season:str - closed_season:str - fish_url:str - is_active:bool +# class Fish(FishBase): +# fish_id:int +# description:str +# toxicity:str +# fish_type:str +# open_season:str +# closed_season:str +# fish_url:str +# is_active:bool + class FishCreate(FishBase): description:str @@ -22,10 +23,19 @@ class FishCreate(FishBase): closed_season:str fish_url:str fish_name:str - fish_habitat:str - fish_scientific_name:str + habitat:str + scientific_name:str + classification:str -class FishRead(FishCreate): +class FishRead(FishBase): fish_id:int - - \ No newline at end of file + description: str + toxicity: str + fish_type: str + open_season: str + closed_season: str + fish_url: str + habitat: str + scientific_name: str + classification: str + From 606e429d30120270ca56d5b52bcac76b8a4390da Mon Sep 17 00:00:00 2001 From: kckoh Date: Mon, 23 Jan 2023 22:56:31 -0800 Subject: [PATCH 52/57] =?UTF-8?q?feat:=20history=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/endpoints/history.py | 24 +++++++++------- backend/crud/history_crud.py | 48 ++++++++++++++++--------------- backend/model/history.py | 1 + backend/schemas/history_schema.py | 13 ++++----- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/backend/api/endpoints/history.py b/backend/api/endpoints/history.py index b537fc7..5a756b7 100644 --- a/backend/api/endpoints/history.py +++ b/backend/api/endpoints/history.py @@ -10,17 +10,19 @@ router=APIRouter() +# @router.post("/") +# def post_history(history:history_schema.create_History, +# db:Session=Depends(get_db)): +# +# return history_crud.create_history(db,history) -@router.post("/") -def post_history(history:history_schema.create_History, - db:Session=Depends(get_db), - token:str=Depends(oauth2_scheme)): - history_user_id=history_crud.get_current_user(db,token) - history_crud.create_history(db,history,history_user_id) +@router.get("/{user_id}",response_model=list[history_schema.read_History]) +def get_history(user_id:int, + db:Session=Depends(get_db) +# token:str=Depends(oauth2_scheme) +): + + #history_user_id=history_crud.get_current_user(db,token,user_id) + return history_crud.get_history(db,user_id) -@router.get("/user_history",response_model=list[history_schema.read_History]) -def get_history(db:Session=Depends(get_db), - token:str=Depends(oauth2_scheme)): - history_user_id=history_crud.get_current_user(db,token) - return db.query(history.History).filter(history.History.user_id==history_user_id).all() diff --git a/backend/crud/history_crud.py b/backend/crud/history_crud.py index 6e17a60..7841ddd 100644 --- a/backend/crud/history_crud.py +++ b/backend/crud/history_crud.py @@ -10,34 +10,36 @@ ALGORITHM = "HS256" # jwt 인코딩을 위해 필요한 알고리즘 SECRET_KEY = "DeepBlue" # 비밀키 -def create_history(db:Session, - history:history_schema.create_History, - history_user_id:int): - +def create_history(db:Session, history:history_schema.create_History): + + # create fish here db_history=History( - user_id=history_user_id, + user_id=history.user_id, fish_url=history.fish_url, - fish_name=history.fish_name + fish_name=history.fish_name, + fish_id=history.fish_id ) db.add(db_history) db.commit() return db_history -def get_current_user(db:Session,token:str): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") - if username is None: - raise credentials_exception - except JWTError: - raise credentials_exception - else: - user=db.query(User).filter(User.name==username).first() - user_id=user.id - return user_id +def get_history(db:Session,user_id:int): + # credentials_exception = HTTPException( + # status_code=status.HTTP_401_UNAUTHORIZED, + # detail="Could not validate credentials", + # headers={"WWW-Authenticate": "Bearer"}, + # ) + # try: + # payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + # username: str = payload.get("sub") + # if username is None: + # raise credentials_exception + # except JWTError: + # raise credentials_exception + # else: + # user=db.query(User).filter(User.name==username).first() + # user_id=user.id + # return user_id + + return db.query(History).filter(History.user_id == user_id).all() \ No newline at end of file diff --git a/backend/model/history.py b/backend/model/history.py index 1461e1a..7e8b7ad 100644 --- a/backend/model/history.py +++ b/backend/model/history.py @@ -18,6 +18,7 @@ class History(Base): created_at=Column(TIMESTAMP,server_default=func.now()) updated_at=Column(TIMESTAMP,server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) is_deleted=Column(Boolean,default=False,nullable=False) + fish=relationship("Fish",backref="history") user=relationship("User",backref="history") diff --git a/backend/schemas/history_schema.py b/backend/schemas/history_schema.py index b5a6545..7ba5aec 100644 --- a/backend/schemas/history_schema.py +++ b/backend/schemas/history_schema.py @@ -1,20 +1,19 @@ from pydantic import BaseModel class HistoryBase(BaseModel): - - class Config: orm_mode = True - - + class create_History(HistoryBase): + fish_id:int fish_url:str fish_name:str - - + user_id:int + + class read_History(HistoryBase): fish_id:int - fish_url:int + fish_url:str fish_name:str From 9ddbf956a0ea2b06ed1fc94ebbad5638cb3b1bd2 Mon Sep 17 00:00:00 2001 From: JongYun Jeong <95991290+BellYun@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:22:49 +0900 Subject: [PATCH 53/57] =?UTF-8?q?#refactor=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20input=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/login/index.scss | 4 ++-- frontend/src/components/login/index.tsx | 2 +- frontend/src/components/signup/index.scss | 14 ++++++++++++-- frontend/src/page/login/index.scss | 6 +++--- frontend/src/page/login/index.tsx | 3 +++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/login/index.scss b/frontend/src/components/login/index.scss index b9d7e14..e90898a 100644 --- a/frontend/src/components/login/index.scss +++ b/frontend/src/components/login/index.scss @@ -28,7 +28,7 @@ .login_page_input { width: 100%; - height: 40px; + height: 50px; border: 1.5px solid #d8d8d8; border-radius: 20px; font-size: 1rem; @@ -37,7 +37,7 @@ .login_button { width: 100%; - height: 30px; + height: 50px; background-color: #053366; border-radius: 20px; color: white; diff --git a/frontend/src/components/login/index.tsx b/frontend/src/components/login/index.tsx index 302260d..777c03d 100644 --- a/frontend/src/components/login/index.tsx +++ b/frontend/src/components/login/index.tsx @@ -71,7 +71,7 @@ const LoginComponent = () => { }} onKeyUp={changeButton} /> -
+
diff --git a/frontend/src/components/signup/index.scss b/frontend/src/components/signup/index.scss index b66ec07..96531d6 100644 --- a/frontend/src/components/signup/index.scss +++ b/frontend/src/components/signup/index.scss @@ -1,3 +1,5 @@ +@import '../../media.scss'; + .signup_page { width: 100%; height: 90%; @@ -28,7 +30,7 @@ .signup_page_input { width: 100%; - height: 40px; + height: 47px; border: 1.5px solid #d8d8d8; border-radius: 20px; font-size: 1rem; @@ -37,8 +39,16 @@ .signup_page_button { width: 100%; - height: 30px; + height: 50px; background-color: #053366; border-radius: 20px; color: white; +} + +.mobile_login_explanation{ + display: flex; + width: 50%; + @include tablet{ + display: none; + } } \ No newline at end of file diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index e626476..4c5d821 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -13,13 +13,13 @@ display: flex; width: 100%; height: 100%; - + justify-content: center; + align-items: center; & > div { width: 80%; } @include tablet { - justify-content: center; - align-items: center; + margin: 0 auto; & > div { width: 50%; diff --git a/frontend/src/page/login/index.tsx b/frontend/src/page/login/index.tsx index 5a6d8d6..848df22 100644 --- a/frontend/src/page/login/index.tsx +++ b/frontend/src/page/login/index.tsx @@ -18,6 +18,9 @@ const Login = () => {
+
+

Welcome

+
From f78f19177cf991807f1d3c2772f502ccbd90be95 Mon Sep 17 00:00:00 2001 From: kimkimgungunwoo Date: Tue, 24 Jan 2023 17:05:24 +0900 Subject: [PATCH 54/57] =?UTF-8?q?chore:=EC=BB=A4=EB=A7=A8=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/api.py | 4 +-- backend/api/endpoints/fish.py | 5 +++ backend/api/endpoints/history.py | 9 +++++ backend/crud/fish_crud.py | 60 ++++++++++++++++++++++--------- backend/crud/history_crud.py | 25 ++++++++++--- backend/model/fish.py | 7 ++++ backend/model/history.py | 6 ++++ backend/schemas/fish_schema.py | 3 ++ backend/schemas/history_schema.py | 3 ++ 9 files changed, 99 insertions(+), 23 deletions(-) diff --git a/backend/api/api.py b/backend/api/api.py index 5284274..d055215 100644 --- a/backend/api/api.py +++ b/backend/api/api.py @@ -4,5 +4,5 @@ api_router = APIRouter() api_router.include_router(users.router, prefix="/users", tags=["users"]) api_router.include_router(ai.router, prefix="/ai", tags=["ai"]) -api_router.include_router(fish.router, prefix="/fish",tags=["fish"]) -api_router.include_router(history.router, prefix="/history",tags=["history"]) \ No newline at end of file +api_router.include_router(history.router, prefix="/history",tags=["history"]) +api_router.include_router(fish.router, prefix="/fish",tags=["fish"]) \ No newline at end of file diff --git a/backend/api/endpoints/fish.py b/backend/api/endpoints/fish.py index 1b66f70..83082ae 100644 --- a/backend/api/endpoints/fish.py +++ b/backend/api/endpoints/fish.py @@ -14,8 +14,13 @@ # fish_crud.create_fish(db,fish) # return fish_crud.create_fish(db,fish) + + @router.get("/{fish_id}",response_model=fish_schema.FishRead) def get_fish(fish_id:int,db:Session=Depends(get_db)): + ''' + 물고기 정보 불러오는 API + ''' return fish_crud.get_fish(db,fish_id) \ No newline at end of file diff --git a/backend/api/endpoints/history.py b/backend/api/endpoints/history.py index 5a756b7..6292d4b 100644 --- a/backend/api/endpoints/history.py +++ b/backend/api/endpoints/history.py @@ -17,6 +17,15 @@ # return history_crud.create_history(db,history) + +''' +user_id 통해서 +user_id가진 모델 모두 불러 온 후 +리스트 형식으로 반환 + +''' + + @router.get("/{user_id}",response_model=list[history_schema.read_History]) def get_history(user_id:int, db:Session=Depends(get_db) diff --git a/backend/crud/fish_crud.py b/backend/crud/fish_crud.py index 2eee3c5..ea54145 100644 --- a/backend/crud/fish_crud.py +++ b/backend/crud/fish_crud.py @@ -2,25 +2,51 @@ from schemas import fish_schema from model.fish import Fish -def create_fish(db:Session,fish:fish_schema.FishCreate): - db_fish=Fish( - fish_type=fish.fish_type, - open_season=fish.open_season, - closed_season=fish.closed_season, - fish_url=fish.fish_url, - description=fish.description, - toxicity=fish.toxicity, - fish_name=fish.fish_name, - habitat=fish.habitat, - scientific_name=fish.scientific_name, - classication=fish.classification + + + + + +''' + +Fish모델에 데이터들 넣은 후 + +모델 리턴 + +''' +# def create_fish(db:Session,fish:fish_schema.FishCreate): +# db_fish=Fish( +# fish_type=fish.fish_type, +# open_season=fish.open_season, +# closed_season=fish.closed_season, +# fish_url=fish.fish_url, +# description=fish.description, +# toxicity=fish.toxicity, +# fish_name=fish.fish_name, +# habitat=fish.habitat, +# scientific_name=fish.scientific_name, +# classication=fish.classification - ) - db.add(db_fish) - db.commit() - db.refresh(db_fish) - return db_fish +# ) +# db.add(db_fish) +# db.commit() +# db.refresh(db_fish) +# return db_fish + + def get_fish(db:Session,fish_id:int): + ''' + 물고기 정보를 불러오기 위해 fish_id로 물고기 정보 반환 + + parameter + ------------ + db:Session + fish_id:특정 물고기를 불러올때 입력받는 fish_id + + return + ---------- + fish_id 가진 물고기 객체 반환 + ''' return db.query(Fish).filter(Fish.fish_id==fish_id).first() diff --git a/backend/crud/history_crud.py b/backend/crud/history_crud.py index 7841ddd..e6123f0 100644 --- a/backend/crud/history_crud.py +++ b/backend/crud/history_crud.py @@ -3,12 +3,16 @@ from model.history import History from model.fish import Fish from model.user import User -from fastapi import HTTPException from starlette import status -from jose import jwt, JWTError -ALGORITHM = "HS256" # jwt 인코딩을 위해 필요한 알고리즘 -SECRET_KEY = "DeepBlue" # 비밀키 + + +''' + 데이터 베이스에 History 모델 추가 + + +''' + def create_history(db:Session, history:history_schema.create_History): @@ -24,6 +28,19 @@ def create_history(db:Session, history:history_schema.create_History): return db_history def get_history(db:Session,user_id:int): + + ''' + 유저의 history불러오기 위해 user_id로 history객체 여러개 반환 + + parameter + --------- + db:Session + user_id:특정 유저의 history를 불러올 때 입력받는 user_id + + return + -------- + user_id가진 history 전부 + ''' # credentials_exception = HTTPException( # status_code=status.HTTP_401_UNAUTHORIZED, # detail="Could not validate credentials", diff --git a/backend/model/fish.py b/backend/model/fish.py index afa6296..b2386a2 100644 --- a/backend/model/fish.py +++ b/backend/model/fish.py @@ -10,6 +10,13 @@ class Fish(Base): fish_id=Column(Integer,primary_key=True,index=True) + ''' + fish_type-이름 + classification-분류 + description-설명 + habitat-서식지 + scientific_name-학명 + ''' fish_type=Column(String(64),nullable=False,index=True) scientific_name=Column(String(128),nullable=False,index=True) classification = Column(String(64), nullable=False, index=True) diff --git a/backend/model/history.py b/backend/model/history.py index 7e8b7ad..d1f4cb8 100644 --- a/backend/model/history.py +++ b/backend/model/history.py @@ -10,6 +10,12 @@ class History(Base): __tablename__="history" + ''' + fish_name -이름 + fish_url-사진 + fish_id-생성된 물고기의 id + user_id-히스토리 주인 유저의 id + ''' history_id=Column(Integer,nullable=False,primary_key=True) fish_url=Column(String(100),nullable=False,index=True) fish_name=Column(String(100),nullable=False,index=True) diff --git a/backend/schemas/fish_schema.py b/backend/schemas/fish_schema.py index 6c117d5..346fc96 100644 --- a/backend/schemas/fish_schema.py +++ b/backend/schemas/fish_schema.py @@ -15,6 +15,7 @@ class Config: # is_active:bool + class FishCreate(FishBase): description:str toxicity:str @@ -27,6 +28,8 @@ class FishCreate(FishBase): scientific_name:str classification:str + + class FishRead(FishBase): fish_id:int description: str diff --git a/backend/schemas/history_schema.py b/backend/schemas/history_schema.py index 7ba5aec..abde20e 100644 --- a/backend/schemas/history_schema.py +++ b/backend/schemas/history_schema.py @@ -11,6 +11,9 @@ class create_History(HistoryBase): user_id:int + + + class read_History(HistoryBase): fish_id:int fish_url:str From f6c0c4f01cec359648155badcf2ad00c3270277c Mon Sep 17 00:00:00 2001 From: seunghwan Date: Tue, 24 Jan 2023 17:56:43 +0900 Subject: [PATCH 55/57] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20ui=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/login/index.scss | 31 +++++++++++-------- frontend/src/components/signup/index.scss | 36 ++++++++++++++--------- frontend/src/page/login/index.scss | 29 +++++++++++------- frontend/src/page/login/index.tsx | 3 -- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/login/index.scss b/frontend/src/components/login/index.scss index e90898a..252093d 100644 --- a/frontend/src/components/login/index.scss +++ b/frontend/src/components/login/index.scss @@ -1,44 +1,51 @@ +@import '../../media.scss'; .login_page { width: 100%; - height: 90%; display: flex; flex-direction: column; align-items: center; - justify-content: space-around; - padding: 28px 40px; + padding: 20px 32px; padding-top: 0; + height: 100%; } -.join_us{ +.join_us { font-size: 1.7rem; font-weight: bold; + margin-top: 10px; } - -.user_input{ +.user_input { width: 100%; } -.login_page_text{ +.login_page_text { font-size: 1rem; font-weight: bold; color: black; } - .login_page_input { width: 100%; height: 50px; border: 1.5px solid #d8d8d8; - border-radius: 20px; + border-radius: 15px; font-size: 1rem; text-indent: 4%; + overflow: hidden; } .login_button { - width: 100%; + width: 80%; height: 50px; background-color: #053366; - border-radius: 20px; + border-radius: 15px; color: white; -} \ No newline at end of file + font-size: 1.3rem; + margin-top: 10px; + @include tablet { + position: absolute; + bottom: 50px; + width: 63%; + } +} diff --git a/frontend/src/components/signup/index.scss b/frontend/src/components/signup/index.scss index 96531d6..c37b3fe 100644 --- a/frontend/src/components/signup/index.scss +++ b/frontend/src/components/signup/index.scss @@ -2,53 +2,61 @@ .signup_page { width: 100%; - height: 90%; + height: 85%; display: flex; flex-direction: column; align-items: center; - justify-content: space-around; - padding: 28px 40px; + padding: 20px 32px; padding-top: 0; + @include tablet { + position: relative; + } } -.join_us{ +.join_us { font-size: 1.7rem; font-weight: bold; } - -.user_input{ +.user_input { width: 100%; + height: 90px; } -.signup_page_text{ +.signup_page_text { font-size: 1rem; font-weight: bold; color: black; } - .signup_page_input { width: 100%; height: 47px; border: 1.5px solid #d8d8d8; - border-radius: 20px; + border-radius: 15px; font-size: 1rem; text-indent: 4%; } .signup_page_button { - width: 100%; + width: 90%; height: 50px; background-color: #053366; - border-radius: 20px; + border-radius: 15px; color: white; + font-size: 1.3rem; + margin-top: 10px; + + @include tablet { + position: absolute; + bottom: 10px; + } } -.mobile_login_explanation{ +.mobile_login_explanation { display: flex; width: 50%; - @include tablet{ + @include tablet { display: none; } -} \ No newline at end of file +} diff --git a/frontend/src/page/login/index.scss b/frontend/src/page/login/index.scss index 4c5d821..d69fd12 100644 --- a/frontend/src/page/login/index.scss +++ b/frontend/src/page/login/index.scss @@ -14,13 +14,12 @@ width: 100%; height: 100%; justify-content: center; - align-items: center; + align-items: flex-start; + margin-top: 50px; & > div { width: 80%; } @include tablet { - - margin: 0 auto; & > div { width: 50%; } @@ -34,6 +33,8 @@ height: 80%; display: flex; flex-direction: column; + justify-content: center; + transform: translateX(60px); & > h1 { font-size: 3rem; text-align: center; @@ -56,25 +57,33 @@ .login_input { display: flex; justify-content: center; + align-items: center; margin: 0 auto; - margin-top: 50px; width: 100%; - + flex-direction: column; + max-width: 400px; @include tablet { width: 100%; margin-top: 0; + min-width: 500px; + max-width: 700px; + position: relative; } } +.mobile_login_explanation { + font-size: 3rem; + color: white; +} + ////Is_login_check .insert_login_layout { - height: 450px; width: 100%; background-color: white; border-radius: 1rem; @include tablet { - height: 500px; + height: 550px; width: 70%; } @include laptop { @@ -96,7 +105,7 @@ } } -.switch_button_false{ +.switch_button_false { width: 50%; height: 100%; border-radius: 20px 20px 0px 0px; @@ -105,13 +114,13 @@ font-weight: bold; } -.switch_button_true{ +.switch_button_true { width: 50%; height: 100%; border-radius: 20px 20px 0px 0px; background-color: white; text-align: center; - color :#053366; + color: #053366; font-size: 1.35rem; font-weight: bold; } diff --git a/frontend/src/page/login/index.tsx b/frontend/src/page/login/index.tsx index 848df22..5a6d8d6 100644 --- a/frontend/src/page/login/index.tsx +++ b/frontend/src/page/login/index.tsx @@ -18,9 +18,6 @@ const Login = () => {
-
-

Welcome

-
From 7717b91900191717e429db454b02f81257a615b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=8D=EA=B5=AC?= Date: Wed, 25 Jan 2023 02:48:30 +0900 Subject: [PATCH 56/57] =?UTF-8?q?refactor:=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20ui=20=EB=B0=98=EC=9D=91=ED=98=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/result/index.scss | 62 ++++++++++++++++++++--------- frontend/src/page/result/index.tsx | 16 ++++---- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/frontend/src/page/result/index.scss b/frontend/src/page/result/index.scss index 3ed3721..b04f649 100644 --- a/frontend/src/page/result/index.scss +++ b/frontend/src/page/result/index.scss @@ -3,13 +3,16 @@ .insert_resultback { width: 100vw; - height: 100vh; + height: 115vh; background: linear-gradient(180deg, #006ccf 0%, #061a38 82.81%, #071228 100%); + @include tablet{ + height: 100vh; + } } .insert_resultpage { - width: 80%; - height: 60%; + width: 70%; + height: auto; display: flex; background-color: #ffffff; box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.25), @@ -58,26 +61,29 @@ display: flex; background-color: none; width: 100%; - height: 40%; + height: 50%; justify-content: center; align-self: center; + margin-top: 10px; overflow-y: hidden; @include tablet { width: 50%; - height: 80%; + min-height: 100%; + margin-top: none; } } .insert_smallbox2 { display: flex; background-color: none; - width: 50%; + width: 80%; height: 60%; align-self: center; + justify-content: flex-start; flex-direction: column; @include tablet { - width: 50%; + width: 45%; height: 100%; justify-content: flex-start; } @@ -85,7 +91,7 @@ .insert_topBox { display: none; width: 100%; - height: 19%; + height: 3vh; @include tablet { display: flex; @@ -142,17 +148,20 @@ width: 100%; flex-direction: column; margin-top: 20px; + align-items: center; } .insert_Namebox { display: flex; color: #000000; flex-grow: 1; - font-size: 2.5rem; + font-size: 2rem; font-weight: bold; - margin-right: 10px; + width: auto; @include tablet { - font-size: 1.5rem; + font-size: 1.7rem; + width: 100%; + justify-content: flex-start; } } @@ -161,6 +170,14 @@ color: #000000; font-size: 0.8rem; font-weight: lighter; + margin-top:3px; + + @include tablet{ + width: 80%; + justify-content: flex-start; + margin-top: none; + align-self: flex-start; + } } .insert_discribeBox { @@ -171,22 +188,29 @@ font-weight: 300; white-space: normal; margin-bottom: 20px; - margin-top: 20px; + margin-top: 5px; + justify-content: center; @include tablet { - width: 87%; - height: 20%; + width: 90%; + height: 15%; justify-content: flex-start; - margin-top: 10px; margin-bottom: 10px; } } .insert_qnaBox { display: flex; - width: 100%; + width: 110%; height: auto; flex-direction: row; + justify-content: center; + margin-bottom: 20px; + + @include tablet{ + flex-direction: row; + justify-content: flex-start; + } } .insert_questionBox { @@ -206,7 +230,7 @@ .insert_answerBox { display: flex; - width: 30%; + width: 60%; height: 100%; word-break: keep-all; white-space: pre-wrap; @@ -226,10 +250,10 @@ align-self: flex-end; @include tablet { - width: 70%; - height: 80%; align-self: center; margin-top: 10%; + min-width: 45%; + min-height: 60%; } } diff --git a/frontend/src/page/result/index.tsx b/frontend/src/page/result/index.tsx index a5b2802..3994a0d 100644 --- a/frontend/src/page/result/index.tsx +++ b/frontend/src/page/result/index.tsx @@ -17,7 +17,7 @@ interface result { url: string; } const Result = () => { - const navagater = useNavigate(); + const navigater = useNavigate(); const location = useLocation(); const resultData = (location.state as RouterState)?.data; const [result, setResult] = useState(); @@ -30,7 +30,7 @@ const Result = () => { }, []); const gotoMain = () => { - navagater('/'); + navigater('/'); }; return (
@@ -47,27 +47,29 @@ const Result = () => {
{result?.fish_type}
-
{result?.fish_type}
+
학명 : Dasyatis akajei
-
{result?.description}
+
노랑가오리는 색가오리과에 속하는 물고기로 +꼬리에 긴 독가시가 하나 있는데 길이가 약 15cm 이기 때문에 매우 기다란 것 뿐만 아니라 양쪽에 톱니가 있어 인간의 몸을 찌르면 +몹시 아플 뿐만 아니라 독가시 끝에 맹독이 있어 기절하고 심지어 사망하는 수도 있다
분류
서식지
- 독성유무: + 독성유무
산란기
포유류
- 바다 + 서태평양 지역의 얕은 바다와 강 하구
{result?.toxicity}
- {result?.closed_season}~{result?.open_season} + 5월 ~ 8월
From c74f032ecc0532a164455cf0b15df1e9393dfa31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=8D=EA=B5=AC?= Date: Wed, 25 Jan 2023 02:58:13 +0900 Subject: [PATCH 57/57] =?UTF-8?q?refactor:=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20ui=20=EB=B0=98=EC=9D=91=ED=98=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95+=EB=B3=B4=EB=8D=94=EA=B0=92=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/result/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/page/result/index.scss b/frontend/src/page/result/index.scss index b04f649..4f8f7c5 100644 --- a/frontend/src/page/result/index.scss +++ b/frontend/src/page/result/index.scss @@ -248,6 +248,7 @@ width: 60%; height: 80%; align-self: flex-end; + border-radius: 30px; @include tablet { align-self: center;