Compare commits

..

25 commits

Author SHA1 Message Date
693efd988c fix: dependencies conflict issue fixed 2024-10-20 19:18:55 +09:00
b3e511b730
Merge pull request #2 from devproje/dependabot/pip/starlette-0.40.0
build(deps): bump starlette from 0.38.5 to 0.40.0
2024-10-20 19:09:01 +09:00
dependabot[bot]
f2fe139d10
build(deps): bump starlette from 0.38.5 to 0.40.0
Bumps [starlette](https://github.com/encode/starlette) from 0.38.5 to 0.40.0.
- [Release notes](https://github.com/encode/starlette/releases)
- [Changelog](https://github.com/encode/starlette/blob/master/docs/release-notes.md)
- [Commits](https://github.com/encode/starlette/compare/0.38.5...0.40.0)

---
updated-dependencies:
- dependency-name: starlette
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-20 10:08:39 +00:00
442448e71b rm: remove otlp support 2024-10-20 19:02:23 +09:00
f0e25f0740 fix: function 2024-10-10 15:35:41 +09:00
b787891c27 fix: function 2024-10-10 15:33:57 +09:00
70bc590b0c fix: module 2024-10-10 15:23:14 +09:00
a943ff08fb feat: otld is support 2024-10-10 15:22:13 +09:00
a48644b381 build: add dockerfile 2024-10-10 14:23:55 +09:00
e3e51091d3 DOCS: update README.md 2024-09-17 15:20:36 +09:00
f5413faa6d README: FIX MARKDOWN 2024-09-17 15:19:16 +09:00
3e87cd3d14
Merge pull request #1 from devproje/auth
Auth
2024-09-17 15:08:22 +09:00
51fcc3ffcb feat: update README.md 2024-09-17 14:37:34 +09:00
38113cc6a8 fix: balance service delete() method error fixed 2024-09-17 14:35:02 +09:00
6ae4b78a98 fix: server payload not applied fixed 2024-09-15 17:22:57 +09:00
4eb5ad04f1 feat: middle save 2024-09-15 12:31:20 +09:00
d279776baa feat: update route change to UpdateForm class 2024-09-15 00:37:26 +09:00
8e2518e4de feat: middle save (dockerfile) 2024-09-13 14:28:09 +09:00
6f8fef42ac feat: add generate secret token 2024-09-12 11:34:09 +09:00
0b4c55284d fix: auth router 500 issue fixed 2024-09-11 12:35:54 +09:00
ff65e414f7 fix: CORS ISSUE 2024-09-11 01:42:29 +09:00
ed35341316 HOTFIX: fix security issue 2024-09-11 00:36:03 +09:00
98cc3edf93 feat: cors issue fixed 2024-09-10 21:23:22 +09:00
d9440c4aee feat: cors issue fixed 2024-09-10 21:22:01 +09:00
75e8921073 feat: cors issue fixed 2024-09-10 21:20:03 +09:00
10 changed files with 154 additions and 130 deletions

2
.gitignore vendored
View file

@ -6,6 +6,8 @@ __pycache__/
.env .env
!.env.example !.env.example
secret_token.txt
load.txt load.txt
.DS_Store .DS_Store

17
Dockerfile Normal file
View file

@ -0,0 +1,17 @@
FROM python:3.11-alpine3.20
WORKDIR /app
RUN apk update \
&& apk add --no-cache postgresql-dev gcc musl-dev make py3-uvloop \
&& pip install -U pip setuptools wheel Cython
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

View file

@ -1,5 +1,7 @@
# Balance Application # Balance Application
- FastAPI로 제작한 가계부 시스템 입니다. (Backend) - FastAPI로 제작한 가계부 시스템 입니다. (Backend)
- 클라이언트
- [Balance Client](https://github.com/devproje/balance-client)
## Requirement ## Requirement
- Python 3.12 - Python 3.12
@ -39,12 +41,7 @@ DB_USERNAME=user
DB_PASSWORD=sample1234! DB_PASSWORD=sample1234!
``` ```
5. generate.py를 실행하여 테이블 및 계정을 생성 해줍니다. 5. fastapi 명령어를 이용하여 서비스를 실행 해줍니다.
```bash
python generate.py
```
6. fastapi 명령어를 이용하여 서비스를 실행 해줍니다.
```bash ```bash
fastapi run app.py fastapi run app.py
``` ```
@ -52,3 +49,6 @@ fastapi run app.py
```bash ```bash
fastapi run app.py --port 3000 fastapi run app.py --port 3000
``` ```
## License
본 프로젝트는 [MIT License](https://github.com/devproje/balance-application/blob/master/LICENSE)를 따릅니다.

35
app.py
View file

@ -1,13 +1,42 @@
import psycopg2
from generate import on_load
from fastapi import FastAPI, Response from fastapi import FastAPI, Response
from routes.auth import router as auth from routes.auth import router as auth
from contextlib import asynccontextmanager
from util.config import conn_param, db_url
from routes.balance import router as balance from routes.balance import router as balance
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI() @asynccontextmanager
async def lifespan(app: FastAPI):
conn = psycopg2.connect(conn_param)
cur = conn.cursor()
try:
print("loading database for: %s" % db_url())
on_load(conn, cur)
except:
print("[warn] error occurred while creating table. aborted")
finally:
cur.close()
conn.close()
yield
app = FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
@app.get("/") @app.get("/")
def index(resp: Response): async def index(resp: Response):
resp.headers.setdefault("Content-Type", "text") resp.headers.setdefault("Content-Type", "text")
return "Hello, World!" return "Hello, World!"
app.include_router(router=auth) app.include_router(router=auth)
app.include_router(router=balance) app.include_router(router=balance)

View file

@ -1,81 +1,61 @@
import psycopg2, os
import random, string import random, string
from getpass import getpass from getpass import getpass
from util.auth_lib import hash from util.auth_lib import hash
from util.config import conn_param
from service.auth_service import AuthData, AuthService from service.auth_service import AuthData, AuthService
def gen_salt(length = 20): def gen_salt(length = 20):
letters = string.ascii_lowercase + string.digits + string.punctuation letters = string.ascii_lowercase + string.digits + string.punctuation
return ''.join(random.choice(letters) for i in range(length)) return "".join(random.choice(letters) for i in range(length))
def __main__(): def _new_account():
conn = psycopg2.connect(conn_param) name = input("input your display name: ")
cur = conn.cursor() username = input("input your username: ")
password = getpass("input your password: ")
passchk = getpass("type password one more time: ")
salt = gen_salt()
try: if password != passchk:
f = open("./load.txt", "r") return
if f.read().split("=")[1] == "false":
raise ValueError("value not true")
print("server already initialized")
except:
cur.execute(
"""
create table if not exists account(
name varchar(25),
username varchar(25) not null,
password varchar(100) not null,
salt varchar(50),
primary key(username)
);
"""
)
cur.execute(
"""
create table if not exists balset(
id serial primary key,
uid varchar(25) not null,
name varchar(50),
date bigint,
price bigint,
buy boolean,
memo varchar(300),
constraint FK_Account_ID
foreign key (uid)
references account(username)
on delete CASCADE
);
"""
)
conn.commit()
cur.close()
conn.close()
name = input("input your display name: ")
username = input("input your username: ")
password = getpass("input your password: ")
passchk = getpass("type password one more time: ")
salt = gen_salt()
if password != passchk:
return
hashed_password = hash(password, salt) hashed_password = hash(password, salt)
packed = AuthData( packed = AuthData(
name=name, name=name,
username=username, username=username,
password=hashed_password, password=hashed_password,
salt=salt salt=salt
) )
service = AuthService() service = AuthService()
service.create(data=packed) service.create(data=packed)
f = open("load.txt", "w") def on_load(conn, cur):
f.write("init=true") cur.execute(
"""
__main__() create table account(
name varchar(25),
username varchar(25) not null,
password varchar(100) not null,
salt varchar(50),
primary key(username)
);
"""
)
cur.execute(
"""
create table balset(
id serial primary key,
uid varchar(25) not null,
name varchar(50),
date bigint,
price bigint,
buy boolean,
memo varchar(300),
constraint FK_Account_ID
foreign key (uid)
references account(username)
on delete CASCADE
);
"""
)
conn.commit()
_new_account()

View file

@ -1,20 +1,26 @@
annotated-types==0.7.0 annotated-types==0.7.0
anyio==4.4.0 anyio==4.4.0
asgiref==3.8.1
certifi==2024.8.30 certifi==2024.8.30
click==8.1.7 click==8.1.7
Deprecated==1.2.14
dnspython==2.6.1 dnspython==2.6.1
email_validator==2.2.0 email_validator==2.2.0
fastapi==0.114.0 fastapi==0.115.2
fastapi-cli==0.0.5 fastapi-cli==0.0.5
googleapis-common-protos==1.65.0
grpcio==1.66.2
h11==0.14.0 h11==0.14.0
httpcore==1.0.5 httpcore==1.0.5
httptools==0.6.1 httptools==0.6.1
httpx==0.27.2 httpx==0.27.2
idna==3.8 idna==3.8
importlib_metadata==8.4.0
Jinja2==3.1.4 Jinja2==3.1.4
markdown-it-py==3.0.0 markdown-it-py==3.0.0
MarkupSafe==2.1.5 MarkupSafe==2.1.5
mdurl==0.1.2 mdurl==0.1.2
protobuf==4.25.5
psycopg2-binary==2.9.9 psycopg2-binary==2.9.9
pydantic==2.9.1 pydantic==2.9.1
pydantic_core==2.23.3 pydantic_core==2.23.3
@ -23,12 +29,15 @@ python-dotenv==1.0.1
python-multipart==0.0.9 python-multipart==0.0.9
PyYAML==6.0.2 PyYAML==6.0.2
rich==13.8.0 rich==13.8.0
setuptools==75.1.0
shellingham==1.5.4 shellingham==1.5.4
sniffio==1.3.1 sniffio==1.3.1
starlette==0.38.5 starlette==0.40.0
typer==0.12.5 typer==0.12.5
typing_extensions==4.12.2 typing_extensions==4.12.2
uvicorn==0.30.6 uvicorn==0.30.6
uvloop==0.20.0 uvloop==0.20.0
watchfiles==0.24.0 watchfiles==0.24.0
websockets==13.0.1 websockets==13.0.1
wrapt==1.16.0
zipp==3.20.2

View file

@ -8,9 +8,15 @@ router = APIRouter()
def login(auth: Credential, resp: Response): def login(auth: Credential, resp: Response):
service = AuthService() service = AuthService()
data = service.read(auth.username) data = service.read(auth.username)
if data == None:
resp.status_code = 401
return {
"ok": 0,
"errno": "Unauthorized"
}
hashed = hash(auth.password, data.salt) hashed = hash(auth.password, data.salt)
if not data.username == auth.username and not data.password == hashed: if data.username != auth.username or data.password != hashed:
resp.status_code = 401 resp.status_code = 401
return { return {
"ok": 0, "ok": 0,

View file

@ -87,12 +87,10 @@ def find(id, req: Request, resp: Response):
"respond_time": "{}ms".format(round((datetime.now().microsecond / 1000) - started)) "respond_time": "{}ms".format(round((datetime.now().microsecond / 1000) - started))
} }
@router.patch("/balance/{action}/{id}") @router.put("/balance/{id}")
def update(action, id, balance: UpdateForm, req: Request, resp: Response): def update(id, balance: UpdateForm, req: Request, resp: Response):
started = datetime.now().microsecond / 1000 started = datetime.now().microsecond / 1000
auth = AuthService() auth = AuthService()
print(auth.check_auth(req))
if not auth.check_auth(req): if not auth.check_auth(req):
resp.status_code = 403 resp.status_code = 403
return { return {
@ -101,43 +99,7 @@ def update(action, id, balance: UpdateForm, req: Request, resp: Response):
} }
service = BalanceService() service = BalanceService()
if action != "name" and action != "date" and action != "price" and action != "buy" and action != "memo": ok = service.update(int(id), balance)
print(action)
print(id)
resp.status_code = 400
return {"ok": 0, "errno": "action must be to name, date, price or memo"}
if action == "name" and balance.name == "":
resp.status_code = 400
return {"ok": 0, "action": action, "errno": "name value cannot be empty"}
if action == "date" and balance.date <= 0:
resp.status_code = 400
return {"ok": 0, "action": action, "errno": "date value cannot be 0 or minus"}
if action == "price" and balance.price <= 0:
resp.status_code = 400
return {"ok": 0, "action": action, "errno": "price value cannot be 0 or minus"}
if action == "memo" and len(balance.memo) > 300:
resp.status_code = 400
return {
"ok": 0,
"action": action,
"errno": "memo value size is too long: (maximum size: 300 bytes, your size: {} bytes)".format(len(balance.memo))
}
ok = service.update(
int(id),
action,
{
"name": balance.name,
"date": balance.date,
"price": balance.price,
"buy": balance.buy,
"memo": balance.memo
}
)
if not ok == 1: if not ok == 1:
resp.status_code = 500 resp.status_code = 500
@ -149,7 +111,6 @@ def update(action, id, balance: UpdateForm, req: Request, resp: Response):
return { return {
"ok": 1, "ok": 1,
"id": int(id), "id": int(id),
"action": action,
"respond_time": "{}ms".format(round((datetime.now().microsecond / 1000) - started)) "respond_time": "{}ms".format(round((datetime.now().microsecond / 1000) - started))
} }

View file

@ -41,7 +41,7 @@ class BalanceService:
def query(self): def query(self):
cur = self._conn.cursor() cur = self._conn.cursor()
cur.execute("select * from balset;") cur.execute("select id, name, date, price, buy, memo from balset;")
raw = cur.fetchall() raw = cur.fetchall()
data = [] data = []
@ -63,7 +63,7 @@ class BalanceService:
def read(self, id: int): def read(self, id: int):
cur = self._conn.cursor() cur = self._conn.cursor()
cur.execute("select * from balset where id = %s;", (id)) cur.execute("select id, name, date, price, buy, memo from balset where id = %s;", (id))
data = cur.fetchone() data = cur.fetchone()
@ -82,11 +82,18 @@ class BalanceService:
"memo": data[5] "memo": data[5]
} }
def update(self, id: int, act: str, balance: UpdateForm): def update(self, id: int, balance: UpdateForm):
ok = True ok = True
cur = self._conn.cursor() cur = self._conn.cursor()
try: try:
cur.execute(f"update balset set {act} = %s where id = %s;", (balance[act], id)) cur.execute("update balset set name = %s, date = %s, price = %s, buy = %s, memo = %s where id = %s;", (
balance.name,
balance.date,
balance.price,
balance.buy,
balance.memo,
id
))
self._conn.commit() self._conn.commit()
except: except:
self._conn.rollback() self._conn.rollback()
@ -101,7 +108,7 @@ class BalanceService:
ok = True ok = True
cur = self._conn.cursor() cur = self._conn.cursor()
try: try:
cur.execute("delete from balset where id = %s;", (id)) cur.execute("delete from balset where id = %s;", (str(id)))
self._conn.commit() self._conn.commit()
except: except:
self._conn.rollback() self._conn.rollback()

View file

@ -1,7 +1,18 @@
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv(verbose=True, override=True)
def _load_secret():
try:
tok = open("./secret_token.txt", "r").read()
except:
return ""
return tok
def db_url():
return os.getenv("DB_URL")
conn_param = "host=%s port=%s dbname=%s user=%s password=%s" % ( conn_param = "host=%s port=%s dbname=%s user=%s password=%s" % (
os.getenv("DB_URL"), os.getenv("DB_URL"),
@ -10,3 +21,5 @@ conn_param = "host=%s port=%s dbname=%s user=%s password=%s" % (
os.getenv("DB_USERNAME"), os.getenv("DB_USERNAME"),
os.getenv("DB_PASSWORD") os.getenv("DB_PASSWORD")
) )
secret = _load_secret()