Skip to content

Commit 339328b

Browse files
add backend
1 parent 748eb5e commit 339328b

File tree

15 files changed

+548
-0
lines changed

15 files changed

+548
-0
lines changed

backend/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
```bash
2+
uvicorn app.main:app --reload
3+
```
4+
5+
https://grok.com/chat/dc587791-ef1c-420c-b823-db6bff5957f9

backend/database.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from supabase import create_client, Client
2+
from dotenv import load_dotenv
3+
import os
4+
5+
load_dotenv()
6+
7+
SUPABASE_URL = os.getenv("SUPABASE_URL")
8+
SUPABASE_SECRET_KEY = os.getenv("SUPABASE_SECRET_KEY")
9+
10+
supbase: Client = create_client(SUPABASE_URL, SUPABASE_SECRET_KEY)

backend/main.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import re
2+
from fastapi import FastAPI, HTTPException
3+
from fastapi.security import OAuth2PasswordBearer
4+
from typing import Annotated, List, Dict, Optional
5+
from uuid import UUID
6+
from models import (
7+
User, UserCreate,
8+
Dataset, DatasetCreate,
9+
Model, ModelCreate,
10+
Experiment, ExperimentCreate,
11+
Artifact
12+
)
13+
from database import supabase
14+
from utils.supabase import get_supabase_client
15+
from utils.mongodb import get_mongo_collection
16+
from routes.artifacts import router as artifacts_router
17+
18+
app = FastAPI()
19+
20+
# CRUD for Users
21+
@app.port("/users", response_model=User)
22+
async def create_user(user: UserCreate):
23+
try:
24+
response = supabase.table("users").insert(user.dict(exclude_unset=True)).execute()
25+
return response.data[0]
26+
except Exception as e:
27+
raise HTTPException(status_code=400, detail=str(e))
28+
29+
@app.get("/users", response_model=List[User]):
30+
async def list_users():
31+
response = supabase.table("users").select("*").execute()
32+
return response.data
33+
34+
@app.get("/users/{user_id}", response_model=User)
35+
async def get_user(user_id: UUID):
36+
response = supabase.table("users").select("*").eq("id", str(user_id)).execute()
37+
if not response.data:
38+
raise HTTPException(status_code=404, detail="User not found")
39+
return response.data[0]
40+
41+
@app.put("/users/{user_id}", response_model=User)
42+
async def update_user(user_id: UUID, user: UserCreate):
43+
response = supabase.table("users").update(user.dict(exclude_unset=True)).eq("id", str(user_id)).execute()
44+
if not response.data:
45+
raise HTTPException(status_code=404, detail="User not found")
46+
return response.data[0]
47+
48+
@app.delete("/users/{user_id}")
49+
async def delete_user(user_id: UUID):
50+
response = supabase.table("users").delete().eq("id", str(user_id)).execute()
51+
if not response.data:
52+
raise HTTPException(status_code=404, detail="User not found")
53+
return {"message": "User deleted"}
54+
55+
# CRUD for Datasets
56+
@app.post("/datasets", response_model=Dataset)
57+
async def create_dataset(dataset: DatasetCreate):
58+
try:
59+
response = supabase.table("datasets").insert(dataset.dict(exclude_unset=True)).execute()
60+
return response.data[0]
61+
except Exception as e:
62+
raise HTTPException(status_code=400, detail=str(e))
63+
64+
@app.get("/datasets", response_model=List[Dataset])
65+
async def list_datasets():
66+
response = supabase.table("datasets").select("*").execute()
67+
return response.data
68+
69+
# CRUD for Models
70+
@app.post("/models", response_model=Model)
71+
async def create_model(model: ModelCreate):
72+
try:
73+
response = supabase.table("models").insert(model.dict(exclude_unset=True)).execute()
74+
return response.data[0]
75+
except Exception as e:
76+
raise HTTPException(status_code=400, detail=str(e))
77+
78+
@app.get("/models", response_model=List[Model])
79+
async def list_models():
80+
response = supabase.table("models").select("*").execute()
81+
return response.data
82+
83+
# CRUD for Experiments
84+
@app.post("/experiments", response_model=Experiment)
85+
async def create_experiment(experiment: ExperimentCreate):
86+
try:
87+
response = supabase.table("experiments").insert(experiment.dict(exclude_unset=True)).execute()
88+
return response.data[0]
89+
except Exception as e:
90+
raise HTTPException(status_code=400, detail=str(e))
91+
92+
@app.get("/experiments", response_model=List[Experiment])
93+
async def list_experiments():
94+
response = supabase.table("experiments").select("*").execute()
95+
return response.data
96+
97+
# Seed Dummy Data
98+
@app.post("/seed-data")
99+
async def seed_data():
100+
try:
101+
# Insert users
102+
users = generate_dummy_users(3)
103+
user_response = supabase.table("users").insert(users).execute()
104+
user_ids = [user["id"] for user in user_response.data]
105+
106+
# Insert datasets
107+
datasets = generate_dummy_datasets(3)
108+
dataset_response = supabase.table("datasets").insert(datasets).execute()
109+
dataset_ids = [dataset["id"] for dataset in dataset_response.data]
110+
111+
# Insert models
112+
models = generate_dummy_models(3, dataset_ids, user_ids)
113+
model_response = supabase.table("models").insert(models).execute()
114+
model_ids = [model["id"] for model in model_response.data]
115+
116+
# Insert experiments
117+
experiments = generate_dummy_experiments(3, model_ids, user_ids)
118+
supabase.table("experiments").insert(experiments).execute()
119+
120+
return {"message": "Dummy data inserted successfully"}
121+
except Exception as e:
122+
raise HTTPException(status_code=400, detail=str(e))

backend/models.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from fastapi.background import P
2+
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, union
3+
from sqlalchemy.dialects.postgresql import UUID as PGUUID
4+
from sqlalchemy.engine import create
5+
from sqlalchemy.ext.declarative import declarative_base
6+
from datetime import datetime
7+
import uuid
8+
9+
Base = declarative_base()
10+
11+
class User(Base):
12+
__tablename__ = "users"
13+
id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
14+
email = Column(String, unique=True, index=True)
15+
api_token = Column(String, nullable=True)
16+
role = Column(String, default="user")
17+
created_at = Column(DateTime, default=datetime.utcnow)
18+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
19+
20+
class Dataset(Base):
21+
__tablename__ = "datasets"
22+
id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
23+
name = Column(String, index=True)
24+
description = Column(String, nullable=True)
25+
file_path = Column(String, nullable=True)
26+
file_size = Column(Integer, nullable=True)
27+
metadata = Column(String, nullable=True) # JSONB로 저장 가능
28+
created_at = Column(DateTime, default=datetime.utcnow)
29+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
30+
31+
class Model(Base):
32+
__tablename__ = "models"
33+
id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
34+
name = Column(String, index=True)
35+
version = Column(String, index=True)
36+
framework = Column(String, index=True)
37+
format = Column(String, index=True)
38+
file_size = Column(Integer, nullable=True)
39+
metadata = Column(String, nullable=True)
40+
dataset_id = Column(PGUUID(as_uuid=True), ForeignKey("datasets.id"), nullable=True)
41+
created_by = Column(PGUUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
42+
created_at = Column(DateTime, default=datetime.utcnow)
43+
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
44+
45+
class Experiment(Base):
46+
__tablename__ = "experiments"
47+
id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
48+
model_id = Column(PGUUID(as_uuid=True), ForeignKey("models.id"), nullable=False)
49+
name = Column(String, nullable=False)
50+
framework = Column(String, nullable=False)
51+
hyperparameters = Column(String, nullable=True) # JSONB로 저장 가능
52+
metrics = Column(String, nullable=True) # JSONB로 저장 가능
53+
start_time = Column(DateTime, nullable=True)
54+
end_time = Column(DateTime, nullable=True)
55+
created_by = Column(PGUUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
56+
created_at = Column(DateTime, default=datetime.utcnow)
57+
58+
class Artifact(Base):
59+
__tablename__ = "artifacts"
60+
id = Column(PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
61+
run_id = Column(String, nullable=False)
62+
artifact_type = Column(String, nullable=False)
63+
description = Column(String, nullable=True)
64+
file_name = Column(String, nullable=False)
65+
file_type = Column(String, nullable=False)
66+
created_at = Column(DateTime, default=datetime.utcnow)
67+
68+
69+

backend/models/__init__.py

Whitespace-only changes.

backend/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fastapi==0.104.1
2+
uvicorn[standard]==0.24.0
3+
pydantic==2.5.0

backend/routes/__init__.py

Whitespace-only changes.

backend/routes/artifacts.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from fastapi import APIRouter
2+
from models.artifact import Artifact
3+
from database import artifacts
4+
5+
router = APIRouter()
6+
7+
@router.post("/artifacts/")
8+
def create_artifact(artifact: Artifact):
9+
artifacts.append(artifact)
10+
return {"message": "Artifact saved", "artifact": artifact}
11+
12+
@router.get("/artifacts/")
13+
def list_artifacts():
14+
return artifacts

backend/routes/auth.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import jwt
2+
from fastapi import APIRouter, Depends, HTTPException, status
3+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
4+
from pydantic import BaseModel
5+
from sqlalchemy.orm import Session
6+
from database import get_db
7+
from models import User
8+
from schemas import UserCreate, User
9+
from utils import verify_password, get_password_hash, create_access_token
10+
from datetime import timedelta
11+
12+
router = APIRouter()
13+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
14+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
15+
16+
class Token(BaseModel):
17+
access_token: str
18+
token_type: str = "bearer"
19+
20+
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
21+
credentials_exception = HTTPException(
22+
status_code=status.HTTP_401_UNAUTHORIZED,
23+
detail="Could not validate credentials",
24+
headers={"WWW-Authenticate": "Bearer"},
25+
)
26+
try:
27+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
28+
username: str = payload.get("sub")
29+
if username is None:
30+
raise credentials_exception
31+
except Exception as e:
32+
raise credentials_exception
33+
user = db.query(User).filter(User.email == username).first() # email로 인증
34+
if user is None:
35+
raise credentials_exception
36+
return user
37+
38+
@router.post("/register", response_model=User)
39+
async def register(user: UserCreate, db: Session = Depends(get_db)):
40+
db_user = db.query(User).filter(User.email == user.email).first()
41+
if db_user:
42+
raise HTTPException(status_code=400, detail="Email already registered")
43+
44+
db_user = User(**user.dict(exclude_unset=True))
45+
db.add(db_user)
46+
db.commit()
47+
db.refresh(db_user)
48+
return db_user
49+
50+
@router.post("/token", response_model=Token)
51+
async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
52+
# Supabase 인증 대신 이메일/비밀번호로 가정 (Supabase Auth API 통합 필요)
53+
user = db.query(User).filter(User.email == form_data.username).first()
54+
if not user or not verify_password(form_data.password, user.hashed_password):
55+
raise HTTPException(
56+
status_code=status.HTTP_401_UNAUTHORIZED,
57+
detail="Incorrect email or password",
58+
headers={"WWW-Authenticate": "Bearer"},
59+
)
60+
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
61+
access_token = create_access_token(data={"sub": user.email}, expires_delta=access_token_expires)
62+
return {"access_token": access_token, "token_type": "bearer"}

backend/routes/data.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from fastapi import APIRouter, Depends, HTTPException
2+
from sqlalchemy.orm import Session
3+
from database import get_db
4+
from models import Dataset, Model, Experiment, Artifact
5+
from schemas import DatasetCreate, Dataset, ModelCreate, Model, ExperimentCreate, Experiment, ArtifactCreate, Artifact
6+
from uuid import UUID
7+
8+
router = APIRouter()
9+
10+
@router.post("/datasets", response_model=Dataset)
11+
async def create_dataset(dataset: DatasetCreate, db: Session = Depends(get_db)):
12+
db_dataset = Dataset(**dataset.dict(exclude_unset=True))
13+
db.add(db_dataset)
14+
db.commit()
15+
db.refresh(db_dataset)
16+
return db_dataset
17+
18+
@router.post("/models", response_model=Model)
19+
async def create_model(model: ModelCreate, db: Session = Depends(get_db)):
20+
db_model = Model(**model.dict(exclude_unset=True))
21+
db.add(db_model)
22+
db.commit()
23+
db.refresh(db_model)
24+
return db_model
25+
26+
@router.post("/experiments", response_model=Experiment)
27+
async def create_experiment(experiment: ExperimentCreate, db: Session = Depends(get_db)):
28+
db_experiment = Experiment(**experiment.dict(exclude_unset=True))
29+
db.add(db_experiment)
30+
db.commit()
31+
db.refresh(db_experiment)
32+
return db_experiment
33+
34+
@router.post("/artifacts", response_model=Artifact)
35+
async def create_artifact(artifact: ArtifactCreate, db: Session = Depends(get_db)):
36+
db_artifact = Artifact(**artifact.dict(exclude_unset=True))
37+
db.add(db_artifact)
38+
db.commit()
39+
db.refresh(db_artifact)
40+
return db_artifact

0 commit comments

Comments
 (0)