Skip to content

Commit 1b6d5fe

Browse files
committed
Splitwise Project
0 parents  commit 1b6d5fe

11 files changed

+901
-0
lines changed

.gitignore

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
*$py.class
2+
*.cover
3+
*.db
4+
*.egg
5+
*.egg-info/
6+
*.log
7+
*.manifest
8+
*.mo
9+
*.pot
10+
*.py,cover
11+
*.py[cod]
12+
*.sage.py
13+
*.so
14+
*.spec
15+
.DS_Store
16+
.Python
17+
.cache
18+
.coverage
19+
.coverage.*
20+
.dmypy.json
21+
.eggs/
22+
.env
23+
.hypothesis/
24+
.installed.cfg
25+
.ipynb_checkpoints
26+
.mypy_cache/
27+
.nox/
28+
.pdm-build/
29+
.pdm-python
30+
.pdm.toml
31+
.pybuilder/
32+
.pyre/
33+
.pytest_cache/
34+
.pytype/
35+
.ropeproject
36+
.scrapy
37+
.spyderproject
38+
.spyproject
39+
.tox/
40+
.venv
41+
.vercel
42+
.webassets-cache
43+
/site
44+
ENV/
45+
MANIFEST
46+
__pycache__
47+
__pycache__/
48+
__pypackages__/
49+
build/
50+
celerybeat-schedule
51+
celerybeat.pid
52+
cover/
53+
coverage.xml
54+
cython_debug/
55+
db.sqlite3
56+
db.sqlite3-journal
57+
develop-eggs/
58+
dist/
59+
dmypy.json
60+
docs/_build/
61+
downloads/
62+
eggs/
63+
env.bak/
64+
env/
65+
htmlcov/
66+
instance/
67+
ipython_config.py
68+
lib/
69+
lib64/
70+
local_settings.py
71+
nosetests.xml
72+
parts/
73+
pip-delete-this-directory.txt
74+
pip-log.txt
75+
profile_default/
76+
sdist/
77+
share/python-wheels/
78+
target/
79+
var/
80+
venv.bak/
81+
venv/
82+
wheels/

__init__.py

Whitespace-only changes.

api.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import List
2+
3+
from fastapi import APIRouter, HTTPException
4+
from fastapi.responses import HTMLResponse
5+
6+
from schema import Balance, CreateTransaction, CreateUser
7+
from service import *
8+
9+
router = APIRouter()
10+
11+
12+
@router.get("/", response_class=HTMLResponse)
13+
def root_endpoint():
14+
return """
15+
<!DOCTYPE html>
16+
<html>
17+
<head><title>Splitwise API</title></head>
18+
<body style="margin:0; height:100vh; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center;">
19+
<div>
20+
<p>
21+
<a href="https://subh.me/">website</a> &middot;
22+
<a href="https://www.linkedin.com/in/subhrashisdas/">linkedin</a> &middot;
23+
<a href="https://github.com/subhrashisdas/">github</a>
24+
</p>
25+
<p>&copy; 2024 Subhrashis Das. All rights reserved.</p>
26+
</div>
27+
</body>
28+
</html>
29+
"""
30+
31+
32+
@router.post("/users/", response_model=User)
33+
def create_user_endpoint_endpoint(user: CreateUser):
34+
try:
35+
return create_user(user.email)
36+
except ValueError as e:
37+
raise HTTPException(status_code=400, detail=str(e))
38+
39+
40+
@router.post("/transactions/", response_model=TransactionResponse)
41+
def create_transaction_endpoint(transaction: CreateTransaction):
42+
try:
43+
return create_transaction(transaction)
44+
except ValueError as e:
45+
raise HTTPException(status_code=400, detail=str(e))
46+
47+
48+
@router.put("/transactions/{transaction_id}/", response_model=TransactionResponse)
49+
def update_transaction_endpoint(transaction_id: int, transaction: CreateTransaction):
50+
try:
51+
return update_transaction(transaction_id, transaction)
52+
except ValueError as e:
53+
raise HTTPException(status_code=400, detail=str(e))
54+
55+
56+
@router.get("/users/{user_id}/balance/", response_model=List[Balance])
57+
def get_user_balance_endpoint(user_id: int) -> List[Balance]:
58+
return get_user_balance(user_id)
59+
60+
61+
@router.post("/users/{user_id}/clear/")
62+
def clear_user_balance_endpoint(user_id: int):
63+
try:
64+
clear_user_balance(user_id)
65+
return {}
66+
except Exception as e:
67+
raise HTTPException(status_code=400, detail=str(e))

core.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
3+
from sqlmodel import create_engine
4+
5+
DATABASE_URL = (
6+
os.getenv("POSTGRES_URL") or os.getenv("DATABASE_URL") or "sqlite:///./splitwise.db"
7+
)
8+
9+
if DATABASE_URL.startswith("postgres://"):
10+
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql+psycopg2://")
11+
12+
engine = create_engine(DATABASE_URL, echo=True)

main.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from typing import AsyncIterator
2+
3+
from fastapi import FastAPI
4+
from sqlmodel import SQLModel
5+
6+
from api import router as api_router
7+
from core import engine
8+
9+
10+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
11+
SQLModel.metadata.create_all(engine)
12+
yield
13+
14+
15+
app = FastAPI(lifespan=lifespan)
16+
app.include_router(api_router)

model.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
4+
from sqlmodel import Field, SQLModel
5+
6+
7+
class User(SQLModel, table=True):
8+
__tablename__ = "users"
9+
id: Optional[int] = Field(default=None, primary_key=True)
10+
email: str = Field(index=True, unique=True)
11+
12+
13+
class Transaction(SQLModel, table=True):
14+
__tablename__ = "transactions"
15+
id: Optional[int] = Field(default=None, primary_key=True)
16+
description: str
17+
total_amount: int
18+
deleted_at: Optional[datetime] = Field(default=None)
19+
20+
21+
class Ledger(SQLModel, table=True):
22+
__tablename__ = "ledgers"
23+
id: Optional[int] = Field(default=None, primary_key=True)
24+
from_user_id: int = Field(foreign_key="users.id", index=True)
25+
to_user_id: int = Field(foreign_key="users.id", index=True)
26+
amount: int
27+
transaction_id: int = Field(foreign_key="transactions.id", index=True)
28+
created_at: datetime = Field(default_factory=datetime.utcnow)

0 commit comments

Comments
 (0)