first commit
This commit is contained in:
commit
bc94c75b54
31 files changed
+3483
No files matched your search
@@ -0,0 +1,6 @@
|
||||
FROM python:3.14-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY ./app /app/app
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
Whitespace-only changes.
@@ -0,0 +1,25 @@
|
||||
from jose import JWTError, jwt
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi import Depends, HTTPException
|
||||
|
||||
SECRET_KEY = "your-secret-key-change-in-production" # 生产环境用环境变量
|
||||
ALGORITHM = "HS256"
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta):
|
||||
to_encode = data.copy()
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
role: str = payload.get("role")
|
||||
if username is None:
|
||||
raise HTTPException(401, "无效令牌")
|
||||
return {"username": username, "role": role}
|
||||
except JWTError:
|
||||
raise HTTPException(401, "令牌验证失败")
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,35 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from .database import engine, Base
|
||||
from .routers import violations, debts, rewards, stats
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(title="自律公约执行系统")
|
||||
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # 生产环境应限制域名
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(violations.router, prefix="/api/violations", tags=["violations"])
|
||||
app.include_router(debts.router, prefix="/api/debts", tags=["debts"])
|
||||
app.include_router(rewards.router, prefix="/api/rewards", tags=["rewards"])
|
||||
app.include_router(stats.router, prefix="/api/stats", tags=["stats"])
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.on_event("startup")
|
||||
def init_users():
|
||||
db = next(get_db())
|
||||
if not db.query(User).first():
|
||||
db.add_all([
|
||||
User(username="parent", hashed_password=pwd_context.hash("family2026"), role="parent"),
|
||||
User(username="child", hashed_password=pwd_context.hash("self2026"), role="child")
|
||||
])
|
||||
db.commit()
|
||||
@@ -0,0 +1,55 @@
|
||||
from passlib.context import CryptContext
|
||||
from sqlalchemy import Column, Integer, String, Date, Boolean, Float, DateTime
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
Base = declarative_base()
|
||||
|
||||
class Violation(Base):
|
||||
__tablename__ = "violations"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(Date, nullable=False)
|
||||
type = Column(String, nullable=False)
|
||||
clause = Column(String)
|
||||
sanction_level = Column(Integer) # 1/2/3
|
||||
executed = Column(Boolean, default=False)
|
||||
confirmed = Column(Boolean, default=False)
|
||||
remark = Column(String) # 含屏幕时间
|
||||
star_mark = Column(Boolean, default=False)
|
||||
|
||||
class Debt(Base):
|
||||
__tablename__ = "debts"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(Date)
|
||||
task_name = Column(String)
|
||||
owed_amount = Column(Float)
|
||||
interest_rate = Column(Float, default=1.5)
|
||||
due_amount = Column(Float)
|
||||
completed = Column(Boolean, default=False)
|
||||
completion_date = Column(Date)
|
||||
|
||||
class Reward(Base):
|
||||
__tablename__ = "rewards"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
type = Column(String) # 全勤/时长之星等
|
||||
achieve_date = Column(Date)
|
||||
content = Column(String)
|
||||
exchange_date = Column(Date)
|
||||
confirmed = Column(Boolean, default=False)
|
||||
|
||||
class Parameter(Base):
|
||||
__tablename__ = "parameters"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String, unique=True)
|
||||
value = Column(String)
|
||||
effective_date = Column(Date)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
id = Column(Integer, primary_key=True)
|
||||
username = Column(String, unique=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
role = Column(String, default="child") # parent / child
|
||||
|
||||
def verify_password(self, password: str) -> bool:
|
||||
return pwd_context.verify(password, self.hashed_password)
|
||||
@@ -0,0 +1,19 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
from ..database import get_db
|
||||
from .. import models, auth
|
||||
from datetime import timedelta
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/token")
|
||||
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
|
||||
user = db.query(models.User).filter(models.User.username == form_data.username).first()
|
||||
if not user or not user.verify_password(form_data.password):
|
||||
raise HTTPException(401, "用户名或密码错误")
|
||||
access_token = auth.create_access_token(
|
||||
data={"sub": user.username, "role": user.role},
|
||||
expires_delta=timedelta(hours=24)
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from ..database import get_db
|
||||
from .. import crud, schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=schemas.ViolationOut)
|
||||
def create_violation(violation: schemas.ViolationCreate, db: Session = Depends(get_db)):
|
||||
return crud.create_violation(db, violation)
|
||||
|
||||
@router.get("/", response_model=list[schemas.ViolationOut])
|
||||
def list_violations(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
return crud.get_violations(db, skip=skip, limit=limit)
|
||||
Whitespace-only changes.
@@ -0,0 +1,6 @@
|
||||
fastapi
|
||||
uvicorn[standard]
|
||||
sqlalchemy
|
||||
pydantic
|
||||
python-jose[cryptography]
|
||||
passlib[bcrypt]
|
||||
Reference in new issue
Block a user