迁移服务层
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi import Depends
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from middleware.admin import is_admin
|
||||
from model import database, Setting, SettingResponse
|
||||
from model import database
|
||||
from model.response import DefaultResponse
|
||||
from services import admin as admin_service
|
||||
|
||||
Router = APIRouter(
|
||||
prefix='/api/admin',
|
||||
@@ -41,19 +41,7 @@ async def get_settings(
|
||||
session: Annotated[AsyncSession, Depends(database.Database.get_session)],
|
||||
name: str | None = None
|
||||
) -> DefaultResponse:
|
||||
data = []
|
||||
|
||||
if name:
|
||||
setting = await Setting.get(session, Setting.name == name)
|
||||
if setting:
|
||||
data.append(SettingResponse.model_validate(setting))
|
||||
else:
|
||||
raise HTTPException(404, detail="Setting not found")
|
||||
else:
|
||||
settings = await Setting.get(session, fetch_mode="all")
|
||||
if settings:
|
||||
data = [SettingResponse.model_validate(s) for s in settings]
|
||||
|
||||
data = await admin_service.fetch_settings(session=session, name=name)
|
||||
return DefaultResponse(data=data)
|
||||
|
||||
|
||||
@@ -69,11 +57,5 @@ async def update_settings(
|
||||
name: str,
|
||||
value: str
|
||||
) -> DefaultResponse:
|
||||
setting = await Setting.get(session, Setting.name == name)
|
||||
if not setting:
|
||||
raise HTTPException(404, detail="Setting not found")
|
||||
|
||||
setting.value = value
|
||||
await Setting.save(session)
|
||||
|
||||
return DefaultResponse(data=True)
|
||||
result = await admin_service.update_setting_value(session=session, name=name, value=value)
|
||||
return DefaultResponse(data=result)
|
||||
|
||||
149
routes/object.py
149
routes/object.py
@@ -1,25 +1,20 @@
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Request, Query, HTTPException
|
||||
from loguru import logger
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from dependencies import SessionDep
|
||||
from middleware.user import get_current_user
|
||||
from model import DefaultResponse, ItemDataResponse, User, database, Setting, Item
|
||||
from model.item import ItemDataUpdateRequest, ItemTypeEnum
|
||||
from pkg.sender import ServerChatBot, WeChatBot
|
||||
from pkg.utils import raise_not_found, raise_bad_request, raise_internal_error
|
||||
|
||||
from model import DefaultResponse, User, database
|
||||
from model.item import ItemDataUpdateRequest
|
||||
from services import object as object_service
|
||||
from starlette.status import HTTP_204_NO_CONTENT
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
Router = APIRouter(prefix='/api/object', tags=['物品 Object'])
|
||||
|
||||
@Router.get(
|
||||
@@ -39,36 +34,13 @@ async def get_items(
|
||||
|
||||
不传参数返回所有信息,否则可传入 `id` 或 `key` 进行筛选。
|
||||
"""
|
||||
|
||||
# 根据条件查询物品,只获取当前用户的物品
|
||||
if id is not None:
|
||||
results = await Item.get(session, (Item.id == id) & (Item.user_id == token.id))
|
||||
results = [results] if results else []
|
||||
elif key is not None:
|
||||
results = await Item.get(session, (Item.key == key) & (Item.user_id == token.id))
|
||||
results = [results] if results else []
|
||||
else:
|
||||
results = await Item.get(session, Item.user_id == token.id, fetch_mode="all")
|
||||
|
||||
if results:
|
||||
items = []
|
||||
for obj in results:
|
||||
items.append(Item(
|
||||
id=obj.id,
|
||||
type=obj.type,
|
||||
key=obj.id,
|
||||
name=obj.name,
|
||||
icon=obj.icon or "",
|
||||
status=obj.status or "",
|
||||
phone=obj.phone if obj.phone and obj.phone.isdigit() else None,
|
||||
lost_description=obj.description,
|
||||
find_ip=obj.find_ip,
|
||||
create_time=obj.created_at.isoformat(),
|
||||
lost_time=obj.lost_at.isoformat() if obj.lost_at else None
|
||||
))
|
||||
return DefaultResponse(data=items)
|
||||
else:
|
||||
return DefaultResponse(data=[])
|
||||
items = await object_service.list_items(
|
||||
session=session,
|
||||
user=token,
|
||||
item_id=id,
|
||||
key=key,
|
||||
)
|
||||
return DefaultResponse(data=items)
|
||||
|
||||
@Router.post(
|
||||
path='/items',
|
||||
@@ -85,16 +57,11 @@ async def add_items(
|
||||
"""
|
||||
添加物品信息。
|
||||
"""
|
||||
try:
|
||||
# 创建新物品对象,关联当前用户
|
||||
request_dict = request.model_dump()
|
||||
request_dict['user'] = user
|
||||
request_dict['user_id'] = user.id
|
||||
|
||||
await Item.add(session, Item.model_validate(request_dict))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add item: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
await object_service.create_item(
|
||||
session=session,
|
||||
user=user,
|
||||
request=request,
|
||||
)
|
||||
|
||||
@Router.patch(
|
||||
path='/items/{item_id}',
|
||||
@@ -123,14 +90,14 @@ async def update_items(
|
||||
- **lost_description**: 物品丢失描述
|
||||
- **find_ip**: 找到物品的IP
|
||||
- **lost_time**: 物品丢失时间
|
||||
|
||||
"""
|
||||
# 获取现有物品,验证归属权
|
||||
obj = await Item.get(session, (Item.id == item_id) & (Item.user_id == user.id))
|
||||
if not obj:
|
||||
raise_not_found("Item not found or access denied")
|
||||
|
||||
await obj.update(session, request, exclude_unset=True)
|
||||
await object_service.update_item(
|
||||
session=session,
|
||||
user=user,
|
||||
item_id=item_id,
|
||||
request=request,
|
||||
)
|
||||
|
||||
@Router.delete(
|
||||
path='/items/{item_id}',
|
||||
@@ -146,14 +113,13 @@ async def delete_items(
|
||||
):
|
||||
"""
|
||||
删除物品信息。
|
||||
|
||||
- **id**: 物品的ID
|
||||
"""
|
||||
# 获取现有物品,验证归属权
|
||||
obj = await Item.get(session, (Item.id == item_id) & (Item.user_id == user.id))
|
||||
if not obj:
|
||||
raise_not_found("Item not found or access denied")
|
||||
await Item.delete(session, obj)
|
||||
await object_service.delete_item(
|
||||
session=session,
|
||||
user=user,
|
||||
item_id=item_id,
|
||||
)
|
||||
|
||||
@Router.get(
|
||||
path='/{item_id}',
|
||||
@@ -170,19 +136,12 @@ async def get_object(
|
||||
"""
|
||||
获取物品信息 / Get object information
|
||||
"""
|
||||
object_data = await Item.get(session, Item.id == item_id)
|
||||
|
||||
if object_data:
|
||||
if object_data.status == 'lost':
|
||||
# 物品已标记为丢失,更新IP地址
|
||||
object_data.find_ip = str(request.client.host)
|
||||
object_data = await object_data.save(session)
|
||||
|
||||
data = ItemDataResponse.model_validate(object_data)
|
||||
|
||||
return DefaultResponse(data=data.model_dump())
|
||||
else:
|
||||
raise_not_found('物品不存在或出现异常')
|
||||
data = await object_service.retrieve_object(
|
||||
session=session,
|
||||
item_id=item_id,
|
||||
client_host=str(request.client.host),
|
||||
)
|
||||
return DefaultResponse(data=data.model_dump())
|
||||
|
||||
@Router.post(
|
||||
path='/{item_id}/notify_move_car',
|
||||
@@ -192,7 +151,7 @@ async def get_object(
|
||||
response_description="挪车通知结果"
|
||||
)
|
||||
async def notify_move_car(
|
||||
request: Request,
|
||||
_request: Request,
|
||||
session: SessionDep,
|
||||
item_id: UUID,
|
||||
phone: str = None,
|
||||
@@ -201,41 +160,13 @@ async def notify_move_car(
|
||||
通知车主进行挪车 / Notify car owner to move the car
|
||||
|
||||
Args:
|
||||
request (Request): ...
|
||||
_request (Request): ...
|
||||
session (AsyncSession): 数据库会话 / Database session
|
||||
item_id (int): 物品ID / Item ID
|
||||
phone (str): 挪车发起者电话 / Phone number of the person initiating the move. Defaults to None.
|
||||
"""
|
||||
# 检查是否存在该物品
|
||||
item_data = await Item.get_exist_one(session=session, id=item_id)
|
||||
|
||||
# 检查物品类型是否为车辆
|
||||
if item_data.type != ItemTypeEnum.car:
|
||||
raise_bad_request("Item is not car")
|
||||
|
||||
# 发起挪车通知
|
||||
server_chan_key = await Setting.get(session, Setting.name == 'server_chan_key')
|
||||
wechat_bot_key = await Setting.get(session, Setting.name == 'wechat_bot_key')
|
||||
if not (server_chan_key.value or wechat_bot_key.value):
|
||||
raise_internal_error('未配置Server酱,无法发送挪车通知')
|
||||
|
||||
title = "挪车通知 - Findreve"
|
||||
description = f"""您的车辆“{item_data.name}”被请求挪车。
|
||||
{f"请求挪车者电话:[{phone}](tel:{phone})" if phone else ""}
|
||||
请尽快联系请求者并挪车。"""
|
||||
|
||||
# 获取通知的方式
|
||||
mentioned_channel = (await Setting.get(session, Setting.name == 'mentioned_channel')).value
|
||||
|
||||
if mentioned_channel == 'server_chan':
|
||||
await ServerChatBot.send_text(
|
||||
session=session,
|
||||
title=title,
|
||||
description=description
|
||||
)
|
||||
elif mentioned_channel == 'wechat_bot':
|
||||
await WeChatBot.send_markdown(
|
||||
session=session,
|
||||
markdown=f"# {title}\n\n{description}",
|
||||
version='v1'
|
||||
)
|
||||
await object_service.notify_move_car(
|
||||
session=session,
|
||||
item_id=item_id,
|
||||
phone=phone,
|
||||
)
|
||||
|
||||
@@ -1,50 +1,16 @@
|
||||
# 导入库
|
||||
from typing import Annotated
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from fastapi import APIRouter
|
||||
import jwt, JWT
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from pkg import Password
|
||||
from loguru import logger
|
||||
|
||||
from model import Setting, User, database
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from model import database
|
||||
from model.response import TokenResponse
|
||||
from services import session as session_service
|
||||
|
||||
Router = APIRouter(tags=["令牌 session"])
|
||||
|
||||
# 创建访问令牌
|
||||
async def create_access_token(
|
||||
session: AsyncSession,
|
||||
data: dict,
|
||||
expires_delta: timedelta | None = None
|
||||
):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
jwt_exp_setting = await Setting.get(session, Setting.name == 'jwt_token_exp')
|
||||
expire = datetime.now(timezone.utc) + timedelta(int(jwt_exp_setting.value))
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, key=await JWT.get_secret_key(), algorithm='HS256')
|
||||
return encoded_jwt
|
||||
|
||||
# 验证账号密码
|
||||
async def authenticate_user(session: AsyncSession, username: str, password: str):
|
||||
# 验证账号和密码
|
||||
account = await User.get(session, User.email == username)
|
||||
|
||||
if not account:
|
||||
logger.error("Account or password not set in settings.")
|
||||
return False
|
||||
|
||||
if account.email != username or not Password.verify(account.password, password):
|
||||
logger.error("Invalid username or password.")
|
||||
return False
|
||||
|
||||
return account
|
||||
|
||||
# FastAPI 登录路由 / FastAPI login route
|
||||
@Router.post(
|
||||
path="/api/token",
|
||||
@@ -57,22 +23,16 @@ async def login_for_access_token(
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||
session: Annotated[AsyncSession, Depends(database.Database.get_session)],
|
||||
) -> TokenResponse:
|
||||
user = await authenticate_user(
|
||||
session=session,
|
||||
username=form_data.username,
|
||||
password=form_data.password
|
||||
token_response = await session_service.login_for_access_token(
|
||||
session=session,
|
||||
username=form_data.username,
|
||||
password=form_data.password,
|
||||
)
|
||||
if not user:
|
||||
if not token_response:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
access_token = await create_access_token(
|
||||
session=session,
|
||||
data={"sub": user.email},
|
||||
)
|
||||
|
||||
return TokenResponse(
|
||||
access_token=access_token,
|
||||
)
|
||||
return token_response
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
from model.response import DefaultResponse
|
||||
from pkg import conf
|
||||
from services import site as site_service
|
||||
|
||||
Router = APIRouter(prefix='/api/site', tags=['站点 Site'])
|
||||
|
||||
@@ -17,4 +17,5 @@ async def ping():
|
||||
|
||||
:return: Findreve 版本号
|
||||
"""
|
||||
return DefaultResponse(data=conf.VERSION)
|
||||
version = await site_service.get_version()
|
||||
return DefaultResponse(data=version)
|
||||
|
||||
Reference in New Issue
Block a user