Compare commits

...

2 Commits

13 changed files with 298 additions and 0 deletions

1
.gitignore vendored
View File

@ -123,6 +123,7 @@ celerybeat.pid
# Environments # Environments
.env .env
.venv .venv
.venv/
env/ env/
venv/ venv/
ENV/ ENV/

View File

@ -1,2 +1,23 @@
# aio2o-fastapi-sample # aio2o-fastapi-sample
# Requirement
Docker, Docker-compose
## install
### Percona 설치
./percona-server
.env.original 파일 보고 변수 원하는대로 바꿔 .env로 저장
```
docker compose -f docker-compose.percona.yml up -d
```
### API Server 켜기
./api-server
해당 폴더에도 따로 .env.original이 있으니 percona와 동일하게 변경하고 .env로 저장
Percona 서버를 사용하지 않을 시 해당하는 서버에 맞게 저장
```
docker compose -f docker-compose.yml build
docker compose -f docker-compose.yml up -d
```

10
api-server/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.12
RUN apt-get update && apt-get install -y locales
RUN locale-gen ko_KR.UTF-8
RUN apt-get update && apt-get install git -y
WORKDIR /app
COPY ./requirements.txt /requirements.txt
RUN pip install --no-cache-dir --upgrade -r /requirements.txt

View File

@ -0,0 +1,5 @@
MYSQL_HOST="percona-server"
MYSQL_USER="aio2o"
MYSQL_PASSWORD="yourpasswordhere"
MYSQL_ROOT_PASSWORD="yourrootpasswordhere"
MYSQL_DATABASE="yourdbnamehere"

39
api-server/app/main.py Normal file
View File

@ -0,0 +1,39 @@
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse
from module.pydantic_models import *
import module.mysql_utils as sql
app = FastAPI()
@app.post("/user/create", status_code=status.HTTP_201_CREATED)
async def create_user(params: UserCreateForm):
user_id = await sql.create_user(params.user_name, params.phone_number)
return JSONResponse(content={'user_id' : user_id})
@app.post("/user/read", status_code=status.HTTP_200_OK)
async def read_user(params: UserReadFromIdForm):
user_info = await sql.get_user_info_from_id(params.user_id)
return JSONResponse(content=user_info)
@app.post("/user/update", status_code=status.HTTP_200_OK)
async def update_user(params: UserUpdatePhoneNumberFromIdForm):
updated_data = await sql.update_user_phone_from_id(params.user_id, params.phone_number)
return updated_data
@app.post("/user/delete", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(params: UserDeleteForm):
await sql.delete_user_from_id(params.user_id)
return
@app.post("/blog/create")
async def create_blog(params: BlogCreateForm):
pass
@app.post("/blog/read")
async def read_blog(params: BlogReadForm):
pass
@app.post("/blog/update")
async def update_blog(params: BlogUpdateForm):
pass
@app.post("/blog/delete")
async def delete_blog(params: BlogDeleteForm):
pass

View File

@ -0,0 +1,126 @@
import asyncio
import os
import pydantic
from dotenv import load_dotenv
from mysql.connector.aio import connect
from mysql.connector.errors import IntegrityError
from fastapi import HTTPException, status
load_dotenv(verbose=True)
async def get_cnx():
cnx = await connect(
host = os.getenv("MYSQL_HOST"),
user = os.getenv("MYSQL_USER"),
password = os.getenv("MYSQL_PASSWORD"),
database = os.getenv("MYSQL_DATABASE"),
)
return cnx
async def create_user(user_name:str, phone_number:str=None):
async with await get_cnx() as cnx:
async with await cnx.cursor() as cur:
query = '''
INSERT INTO user_table (user_name, phone_number)
VALUES (%(user_name)s, %(phone_number)s)
'''
data = {
"user_name" : user_name,
"phone_number" : phone_number
}
try:
await cur.execute(query, data)
except IntegrityError as e:
await cnx.rollback()
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="user already exist")
user_id = cur.lastrowid
print(user_id)
await cnx.commit()
return user_id
async def get_user_info_from_id(user_id:int):
async with await get_cnx() as cnx:
async with await cnx.cursor() as cur:
query = '''
SELECT user_name, phone_number FROM user_table
WHERE user_id=%(user_id)s
'''
data = {"user_id" : user_id}
await cur.execute(query, data)
user_info = await cur.fetchone()
await cnx.commit()
if not user_info:
raise HTTPException(status.HTTP_404_NOT_FOUND)
return user_info
async def get_user_id_from_name(user_name:str):
async with await get_cnx() as cnx:
async with await cnx.cursor() as cur:
query = '''
SELECT user_id FROM user_table
WHERE user_name=%(user_name)s
'''
data = {"user_name" : user_name}
await cur.execute(query, data)
user_info = await cur.fetchone()
await cnx.commit()
if not user_info:
raise HTTPException(status.HTTP_404_NOT_FOUND)
return user_info["user_id"]
async def update_user_phone_from_id(user_id:int, phone_number:str=None):
async with await get_cnx() as cnx:
async with await cnx.cursor() as cur:
count_query = '''
SELECT count(*)
FROM user_table
WHERE user_id = %(user_id)s
'''
await cur.execute(count_query, {'user_id' : user_id})
found_rows = await cur.fetchone()
if found_rows[0] == 0:
await cnx.rollback()
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
query = '''
UPDATE user_table
SET phone_number = %(phone_number)s
WHERE user_id = %(user_id)s
'''
data = {
"user_id" : user_id,
"phone_number" : phone_number
}
await cur.execute(query, data)
if cur.rowcount == 0:
await cnx.rollback()
raise HTTPException(status_code=status.HTTP_204_NO_CONTENT)
await cnx.commit()
return data
async def delete_user_from_id(user_id:int):
async with await get_cnx() as cnx:
async with await cnx.cursor() as cur:
query = '''
DELETE
FROM user_table
WHERE user_id = %(user_id)s
'''
data = {
"user_id" : user_id
}
try:
await cur.execute(query, data)
except IntegrityError as e:
await cnx.rollback()
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="user already exist")
await cnx.commit()
return

View File

@ -0,0 +1,35 @@
from pydantic import BaseModel
class UserCreateForm(BaseModel):
user_name:str
phone_number:str
class UserReadFromIdForm(BaseModel):
user_id:int
class UserUpdatePhoneNumberFromIdForm(BaseModel):
user_id:int
phone_number:str
class UserDeleteForm(BaseModel):
user_id:int
class BlogCreateForm(BaseModel):
owner:int
blog_title:str
blog_content:str
class BlogReadForm(BaseModel):
owner:int
blog_id:int
class BlogUpdateForm(BaseModel):
owner:int
blog_id:int
blog_title:str
blog_content:str
class BlogDeleteForm(BaseModel):
owner:int
blog_id:int

View File

@ -0,0 +1,18 @@
services:
fastapi:
build: .
container_name: fastapi-server
command: uvicorn main:app --host 0.0.0.0 --port 8080 --reload
ports:
- 8080:8080
networks:
- test_app_network
volumes:
- ./app:/app
networks:
test_app_network:
name: test_app_network
driver: bridge
external: true

3
api-server/gogo Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker exec -it fastapi-server /bin/bash

View File

@ -0,0 +1,4 @@
dotenv
fastapi
uvicorn[standard]
mysql-connector-python

View File

@ -0,0 +1,4 @@
MYSQL_USER="aio2o"
MYSQL_PASSWORD="yourpasswordhere"
MYSQL_ROOT_PASSWORD="yourrootpasswordhere"
MYSQL_DATABASE="yourdbnamehere"

View File

@ -0,0 +1,29 @@
services:
mysql:
image: percona/percona-server:8.0
container_name: percona-server
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- percona-data:/var/lib/mysql # Database data
- percona-logs:/var/log/mysql # MySQL logs
- percona-backups:/backups # XtraBackup output (optional, for future use)
restart: unless-stopped
networks:
- test_app_network
volumes:
percona-data:
percona-logs:
percona-backups:
networks:
test_app_network:
name: test_app_network
driver: bridge

3
percona-server/gosql Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker exec -it percona-server mysql -u aio2o -p