Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
Feat: File Up/Download
Browse files Browse the repository at this point in the history
-
  • Loading branch information
tomorrow9913 committed Dec 3, 2023
1 parent 3104fc8 commit 20808e7
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 52 deletions.
46 changes: 31 additions & 15 deletions core/models/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# coding: utf-8
from sqlalchemy import Column, Date, DateTime, Enum, Float, ForeignKey, String, Text, text
from sqlalchemy import Column, Date, DateTime, Enum, Float, ForeignKey, Index, String, Text
from sqlalchemy.dialects.mysql import INTEGER
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Expand All @@ -22,11 +22,21 @@ class Creator(Base):
id = Column(INTEGER(11), primary_key=True)
name = Column(String(50), nullable=False, server_default="''")
description = Column(String(500), nullable=False, server_default="''")
latitude = Column(Float(asdecimal=True), nullable=False, server_default="0")
longitude = Column(Float(asdecimal=True), nullable=False, server_default="0")
latitude = Column(Float, nullable=False, server_default="0")
longitude = Column(Float, nullable=False, server_default="0")
address = Column(String(250), nullable=False, server_default="''")


class File(Base):
__tablename__ = 'File'

id = Column(INTEGER(11), primary_key=True)
file_path = Column(Text, nullable=False)
type = Column(String(50), nullable=False)
original_name = Column(Text, nullable=False)
create = Column(DateTime, nullable=False, server_default="current_timestamp()")


class Perfume(Base):
__tablename__ = 'Perfume'

Expand All @@ -43,16 +53,6 @@ class TagInfo(Base):
name = Column(String(10), nullable=False, unique=True)


class User(Base):
__tablename__ = 'User'

id = Column(INTEGER(11), primary_key=True)
name = Column(String(25), nullable=False, server_default="''")
image_url = Column(Text, nullable=False)
birth = Column(Date, nullable=False)
sex = Column(Enum('M', 'F'), nullable=False, server_default="'M'")


class CartridgeKeynote(Base):
__tablename__ = 'Cartridge_keynote'

Expand Down Expand Up @@ -88,6 +88,18 @@ class SetInfo(Base):
Creator = relationship('Creator')


class User(Base):
__tablename__ = 'User'

id = Column(INTEGER(11), primary_key=True)
name = Column(String(25), nullable=False, server_default="''")
image_id = Column(ForeignKey('File.id'), index=True)
birth = Column(Date, nullable=False)
sex = Column(Enum('M', 'F'), nullable=False, server_default="'M'")

image = relationship('File')


class Set(Base):
__tablename__ = 'Set'

Expand All @@ -101,15 +113,19 @@ class Set(Base):

class UserRecipe(Base):
__tablename__ = 'User_recipe'
__table_args__ = (
Index('user_id_title', 'user_id', 'title', unique=True),
)

id = Column(INTEGER(11), primary_key=True)
user_id = Column(ForeignKey('User.id'), nullable=False, index=True)
set_id = Column(ForeignKey('Set_Info.id'), index=True)
set_id = Column(ForeignKey('Set_Info.id'), index=True, nullable=True)
title = Column(String(25), nullable=False, server_default="''")
description = Column(Text, nullable=False)
image_url = Column(Text)
image_id = Column(ForeignKey('File.id'), index=True)
create_time = Column(DateTime, nullable=False)

image = relationship('File')
set = relationship('SetInfo')
user = relationship('User')

Expand Down
25 changes: 25 additions & 0 deletions core/schemas/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import datetime

from fastapi import UploadFile
from pydantic import BaseModel, field_validator


class File(BaseModel):
id: int
file_path: str
type: str
original_name: str
create: datetime.datetime


class FileCreate(BaseModel):
file_path: str
type: str
original_name: str

@field_validator('file_path', 'type', 'original_name')
def not_empty(cls, v):
if not v or not v.strip():
raise ValueError('빈 값은 허용되지 않습니다.')
return v

6 changes: 3 additions & 3 deletions core/schemas/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class Recipe(BaseModel):
id: int
set_id: int
set_id: int | None
user_id: int
title: str
description: str
Expand All @@ -15,7 +15,7 @@ class Recipe(BaseModel):


class RecipeCreate(BaseModel):
set_id: int
set_id: int | None = None
title: str
description: str

Expand All @@ -36,4 +36,4 @@ class RecipeUsePerfume(BaseModel):
class RecipeUsePerfumeCreate(BaseModel):
recipe_id: int
cartridge_id: int
count: int
count: int
Empty file added internal/admin/__init__.py
Empty file.
File renamed without changes.
2 changes: 1 addition & 1 deletion internal/admin.py → internal/admin/admin_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from sqlalchemy.orm import Session
from starlette import status

import internal.admin_crud as admin_crud
import internal.admin.admin_crud as admin_crud
from core.database import get_db
from core.models import models
from dependencies import get_current_user, Authority
Expand Down
Empty file added internal/file/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions internal/file/file_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import hashlib
from datetime import datetime

import aiofiles
from fastapi import UploadFile
from sqlalchemy.orm import Session

from core.models.models import File


FILE_LOCATION = f"./static/img/recipe/"


def create_hash_name(file_name: str):
return hashlib.sha256(
f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}{file_name}".encode()
).hexdigest()


def get_file(db: Session, file_id: int):
return db.query(File).filter(File.id == file_id).first()


def get_file_by_original_name(db: Session, original_name: str):
return db.query(File).filter(File.original_name == original_name).first()


async def create_file(db: Session, file: UploadFile):
file_name = create_hash_name(file.filename)

# 파일 경로가 없다면 생성
import os

if not os.path.exists(os.path.dirname(FILE_LOCATION)):
os.makedirs(FILE_LOCATION)

file_location = os.path.join(FILE_LOCATION, file_name)
async with aiofiles.open(file_location, 'wb+') as f:
while content := await file.read(1024):
await f.write(content)

# 파일 권한 변경
os.chmod(file_location, 0o444)

db_file = File(
file_path=file_location,
type=file.content_type,
original_name=file.filename,
)

db.add(db_file)
db.commit()

return get_file_by_original_name(db=db, original_name=file.filename).id


def delete_file(db: Session, file_id: int):
db.query(File).filter(File.id == file_id).delete()
db.commit()




42 changes: 42 additions & 0 deletions internal/file/file_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import urllib.parse

from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from starlette import status

import internal.admin.admin_crud as admin_crud
from core.database import get_db
from core.models import models
from internal.file import file_crud

router = APIRouter(
prefix="/api/file",
tags=["File"]
)


# File
# Create
@router.post("/", status_code=status.HTTP_201_CREATED)
async def create_file(file: UploadFile = File(None), db: Session = Depends(get_db)):
await file_crud.create_file(db=db, file=file)


# Read
@router.get("/{file_id}")
async def get_file(file_id: int, db: Session = Depends(get_db)):
db_file = file_crud.get_file(db=db, file_id=file_id)

def iter_file():
with open(db_file.file_path, mode="rb") as f:
yield from f

return StreamingResponse(iter_file(), media_type=db_file.type,
headers={
"Content-Disposition": f"attachment; filename={urllib.parse.quote(db_file.original_name, encoding='utf-8')}"}
)

@router.delete("/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_file(file_id: int, db: Session = Depends(get_db)):
file_crud.delete_file(db=db, file_id=file_id)
13 changes: 3 additions & 10 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
from typing import Annotated

import uvicorn
from fastapi import (
Cookie,
FastAPI,
Query,
WebSocket,
WebSocketException,
WebSocketDisconnect,
status,
)
from fastapi.responses import HTMLResponse

from internal import admin
from internal.admin import admin_router as admin
from internal.file import file_router as file
from routers.user import users_router as user
from routers.tag import tag_router as tag
from routers.recipe import recipe_router as recipe
Expand All @@ -21,6 +13,7 @@

# internal
app.include_router(admin.router)
app.include_router(file.router)

# routers
app.include_router(user.router)
Expand Down
15 changes: 13 additions & 2 deletions routers/recipe/recipe_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,26 @@
from datetime import datetime


def create_recipe(db: Session, image_url: str, recipe_create: RecipeCreate, current_user: models.User | None = None):
def get_recipe(db: Session, user_id: int, title: str):
return db.query(UserRecipe).filter(UserRecipe.user_id == user_id, UserRecipe.title == title).first()


def create_recipe(db: Session, image_id: int, recipe_create: RecipeCreate, current_user: models.User | None = None):
db_recipe = get_recipe(db, user_id=1, title=recipe_create.title)

if db_recipe:
raise ValueError('이미 존재하는 레시피입니다.')

db_recipe = UserRecipe(
user_id=1,
set_id=recipe_create.set_id,
title=recipe_create.title,
description=recipe_create.description,
image_url=image_url,
image_id=image_id,
create_time=datetime.now(),
)
db.add(db_recipe)
db.commit()

return get_recipe(user_id=1, title=recipe_create.title, db=db)

32 changes: 11 additions & 21 deletions routers/recipe/recipe_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from core.schemas import tag
from dependencies import get_current_user
import routers.recipe.recipe_crud as recipe_crud
import util.file_crud as file_crud

config = Config('.env')

Expand All @@ -28,32 +29,21 @@

# Todo 계정 관련하여 추가하여야 함.
# Create
@router.post("/", status_code=status.HTTP_204_NO_CONTENT,
description='new_recipe는 다음과 같이 입력해주세요. new_recipe={"set_id": 0, "title": "string", "description": "string"}')
async def create_recipe(new_recipe: str = Form(...),
@router.post("/", status_code=status.HTTP_200_OK)
async def create_recipe(new_recipe: str = Form(..., description='레시피 정보 Json 형식, set_id의 경우 옵션 new_recipe={"set_id": 0, "title": "string", "description": "string"}'),
file: UploadFile = File(None),
db: Session = Depends(get_db)
):
data = json.loads(new_recipe.replace("new_recipe=", ""))
create_recipe = recipe.RecipeCreate(**data)

if file:
file_name = (hashlib.sha256(
f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}{file.filename}".encode())
.hexdigest() + "." + file.filename.split('.')[-1])

file_location = f"./static/img/recipe/"
file_id = None

# 파일 경로가 없다면 생성
import os
if not os.path.exists(os.path.dirname(file_location)):
os.makedirs(file_location)

file_location = os.path.join(file_location,file_name)
async with aiofiles.open(file_location, 'wb+') as f:
while content := await file.read(1024):
await f.write(content)
else:
file_location = None
if file:
file_id = await file_crud.create_file(db, file)

recipe_crud.create_recipe(db, file_location, create_recipe)
try:
return {"recipe": recipe_crud.create_recipe(db, file_id, create_recipe)}
except Exception as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
detail=f"레시피 생성에 실패하였습니다. {e}")

0 comments on commit 20808e7

Please sign in to comment.