backend_v0.1
This commit is contained in:
214
app/main.py
Normal file
214
app/main.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from fastapi import FastAPI, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from . import models, schemas, auth, database
|
||||
from passlib.context import CryptContext
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# 启动时执行:创建表
|
||||
await database.init_db()
|
||||
yield
|
||||
# 关闭时执行(如果需要)
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.post("/register", response_model=schemas.UserOut)
|
||||
async def register(user: schemas.UserCreate, db: AsyncSession = Depends(database.get_db)):
|
||||
hashed_pwd = auth.hash_password(user.password)
|
||||
private_key, public_key = auth.generate_key_pair()
|
||||
|
||||
new_user = models.User(
|
||||
username=user.username,
|
||||
hashed_password=hashed_pwd,
|
||||
private_key=private_key,
|
||||
public_key=public_key
|
||||
)
|
||||
db.add(new_user)
|
||||
try:
|
||||
await db.commit()
|
||||
await db.refresh(new_user)
|
||||
return new_user
|
||||
|
||||
except IntegrityError:
|
||||
# 发生唯一约束冲突时回滚并报错
|
||||
await db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="用户名已存在"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@app.post("/token")
|
||||
async def login(form_data: schemas.UserLogin, db: AsyncSession = Depends(database.get_db)):
|
||||
result = await db.execute(select(models.User).where(models.User.username == form_data.username))
|
||||
user = result.scalars().first()
|
||||
if not user or not auth.verify_password(form_data.password, user.hashed_password):
|
||||
raise HTTPException(status_code=400, detail="Incorrect username or password")
|
||||
|
||||
access_token = auth.create_access_token(data={"sub": user.username})
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@app.post("/assets/", response_model=schemas.AssetOut)
|
||||
async def create_asset(
|
||||
asset: schemas.AssetCreate,
|
||||
current_user: models.User = Depends(auth.get_current_user),
|
||||
db: AsyncSession = Depends(database.get_db)
|
||||
):
|
||||
# Encrypt the inner content using user's public key
|
||||
encrypted_content = auth.encrypt_data(asset.content_inner_encrypted, current_user.public_key)
|
||||
|
||||
new_asset = models.Asset(
|
||||
title=asset.title,
|
||||
content_outer_encrypted=encrypted_content,
|
||||
private_key_shard=asset.private_key_shard,
|
||||
author_id=current_user.id
|
||||
)
|
||||
db.add(new_asset)
|
||||
await db.commit()
|
||||
await db.refresh(new_asset)
|
||||
return new_asset
|
||||
|
||||
|
||||
@app.post("/assets/claim")
|
||||
async def claim_asset(
|
||||
asset_claim: schemas.AssetClaim,
|
||||
current_user: models.User = Depends(auth.get_current_user),
|
||||
db: AsyncSession = Depends(database.get_db)
|
||||
):
|
||||
# Fetch asset with author loaded
|
||||
result = await db.execute(
|
||||
select(models.Asset)
|
||||
.options(selectinload(models.Asset.author))
|
||||
.where(models.Asset.id == asset_claim.asset_id)
|
||||
)
|
||||
asset = result.scalars().first()
|
||||
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="Asset not found")
|
||||
|
||||
# 1. 验证用户是否是继承人
|
||||
if asset.heir_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You are not the designated heir for this asset"
|
||||
)
|
||||
|
||||
# 2. 验证所有人是否已经挂了 (guale)
|
||||
if not asset.author.guale:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="The owner of this asset is still alive. You cannot claim it yet."
|
||||
)
|
||||
|
||||
# 3. 验证通过后用asset所有人的private_key解密内容
|
||||
try:
|
||||
decrypted_content = auth.decrypt_data(
|
||||
asset.content_outer_encrypted,
|
||||
asset.author.private_key
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to decrypt asset: {str(e)}"
|
||||
)
|
||||
|
||||
return {
|
||||
"asset_id": asset.id,
|
||||
"title": asset.title,
|
||||
"decrypted_content": decrypted_content,
|
||||
"server_shard_key": asset.private_key_shard
|
||||
}
|
||||
|
||||
|
||||
@app.post("/assets/assign")
|
||||
async def assign_asset(
|
||||
assignment: schemas.AssetAssign,
|
||||
current_user: models.User = Depends(auth.get_current_user),
|
||||
db: AsyncSession = Depends(database.get_db)
|
||||
):
|
||||
# Fetch Asset
|
||||
result = await db.execute(
|
||||
select(models.Asset)
|
||||
.options(selectinload(models.Asset.heir))
|
||||
.where(models.Asset.id == assignment.asset_id)
|
||||
)
|
||||
asset = result.scalars().first()
|
||||
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="Asset not found")
|
||||
|
||||
if asset.author_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not authorized to assign this asset")
|
||||
|
||||
|
||||
heir_result = await db.execute(
|
||||
select(models.User).where(
|
||||
models.User.username == assignment.heir_name
|
||||
)
|
||||
)
|
||||
heir_user = heir_result.scalars().first()
|
||||
|
||||
if not heir_user:
|
||||
raise HTTPException(status_code=404, detail="Heir not found")
|
||||
|
||||
if heir_user.id == current_user.id:
|
||||
asset.heir = None
|
||||
await db.commit()
|
||||
#raise HTTPException(status_code=403, detail="You cannot assign an asset to yourself")
|
||||
return {"message": "Asset unassigned"}
|
||||
|
||||
asset.heir = heir_user
|
||||
await db.commit()
|
||||
|
||||
return {"message": f"Asset assigned to {assignment.heir_name}"}
|
||||
|
||||
|
||||
|
||||
|
||||
@app.post("/admin/declare-guale")
|
||||
async def declare_user_guale(
|
||||
declare: schemas.DeclareGuale,
|
||||
current_user: models.User = Depends(auth.get_current_user),
|
||||
db: AsyncSession = Depends(database.get_db)
|
||||
):
|
||||
# Check if current user is admin
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Only administrators can declare users as deceased"
|
||||
)
|
||||
|
||||
# Find the target user
|
||||
result = await db.execute(
|
||||
select(models.User).where(models.User.username == declare.username)
|
||||
)
|
||||
target_user = result.scalars().first()
|
||||
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# Set guale to True
|
||||
target_user.guale = True
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"message": f"User {declare.username} has been declared as deceased",
|
||||
"username": target_user.username,
|
||||
"guale": target_user.guale
|
||||
}
|
||||
|
||||
# 用于测试热加载
|
||||
@app.post("/post1")
|
||||
async def test1():
|
||||
a=2
|
||||
b=3
|
||||
c = a+b
|
||||
return {"msg": f"this is a msg {c}"}
|
||||
|
||||
Reference in New Issue
Block a user