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.example
secret_token.txt
load.txt
.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
- FastAPI로 제작한 가계부 시스템 입니다. (Backend)
- 클라이언트
- [Balance Client](https://github.com/devproje/balance-client)
## Requirement
- Python 3.12
@ -39,12 +41,7 @@ DB_USERNAME=user
DB_PASSWORD=sample1234!
```
5. generate.py를 실행하여 테이블 및 계정을 생성 해줍니다.
```bash
python generate.py
```
6. fastapi 명령어를 이용하여 서비스를 실행 해줍니다.
5. fastapi 명령어를 이용하여 서비스를 실행 해줍니다.
```bash
fastapi run app.py
```
@ -52,3 +49,6 @@ fastapi run app.py
```bash
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 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 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("/")
def index(resp: Response):
async def index(resp: Response):
resp.headers.setdefault("Content-Type", "text")
return "Hello, World!"
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
from getpass import getpass
from util.auth_lib import hash
from util.config import conn_param
from service.auth_service import AuthData, AuthService
def gen_salt(length = 20):
letters = string.ascii_lowercase + string.digits + string.punctuation
return ''.join(random.choice(letters) for i in range(length))
letters = string.ascii_lowercase + string.digits + string.punctuation
return "".join(random.choice(letters) for i in range(length))
def __main__():
conn = psycopg2.connect(conn_param)
cur = conn.cursor()
def _new_account():
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()
try:
f = open("./load.txt", "r")
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
if password != passchk:
return
hashed_password = hash(password, salt)
packed = AuthData(
name=name,
username=username,
password=hashed_password,
salt=salt
)
hashed_password = hash(password, salt)
packed = AuthData(
name=name,
username=username,
password=hashed_password,
salt=salt
)
service = AuthService()
service.create(data=packed)
service = AuthService()
service.create(data=packed)
f = open("load.txt", "w")
f.write("init=true")
__main__()
def on_load(conn, cur):
cur.execute(
"""
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
anyio==4.4.0
asgiref==3.8.1
certifi==2024.8.30
click==8.1.7
Deprecated==1.2.14
dnspython==2.6.1
email_validator==2.2.0
fastapi==0.114.0
fastapi==0.115.2
fastapi-cli==0.0.5
googleapis-common-protos==1.65.0
grpcio==1.66.2
h11==0.14.0
httpcore==1.0.5
httptools==0.6.1
httpx==0.27.2
idna==3.8
importlib_metadata==8.4.0
Jinja2==3.1.4
markdown-it-py==3.0.0
MarkupSafe==2.1.5
mdurl==0.1.2
protobuf==4.25.5
psycopg2-binary==2.9.9
pydantic==2.9.1
pydantic_core==2.23.3
@ -23,12 +29,15 @@ python-dotenv==1.0.1
python-multipart==0.0.9
PyYAML==6.0.2
rich==13.8.0
setuptools==75.1.0
shellingham==1.5.4
sniffio==1.3.1
starlette==0.38.5
starlette==0.40.0
typer==0.12.5
typing_extensions==4.12.2
uvicorn==0.30.6
uvloop==0.20.0
watchfiles==0.24.0
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):
service = AuthService()
data = service.read(auth.username)
if data == None:
resp.status_code = 401
return {
"ok": 0,
"errno": "Unauthorized"
}
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
return {
"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))
}
@router.patch("/balance/{action}/{id}")
def update(action, id, balance: UpdateForm, req: Request, resp: Response):
@router.put("/balance/{id}")
def update(id, balance: UpdateForm, req: Request, resp: Response):
started = datetime.now().microsecond / 1000
auth = AuthService()
print(auth.check_auth(req))
if not auth.check_auth(req):
resp.status_code = 403
return {
@ -101,43 +99,7 @@ def update(action, id, balance: UpdateForm, req: Request, resp: Response):
}
service = BalanceService()
if action != "name" and action != "date" and action != "price" and action != "buy" and action != "memo":
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
}
)
ok = service.update(int(id), balance)
if not ok == 1:
resp.status_code = 500
@ -149,7 +111,6 @@ def update(action, id, balance: UpdateForm, req: Request, resp: Response):
return {
"ok": 1,
"id": int(id),
"action": action,
"respond_time": "{}ms".format(round((datetime.now().microsecond / 1000) - started))
}

View file

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

View file

@ -1,7 +1,18 @@
import os
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" % (
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_PASSWORD")
)
secret = _load_secret()