feat(storage): expose a path to access local storage
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
.venv/
|
.venv/
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
storage/
|
||||||
replays/
|
replays/
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
|||||||
# 服务器地址
|
# 服务器地址
|
||||||
HOST="0.0.0.0"
|
HOST="0.0.0.0"
|
||||||
PORT=8000
|
PORT=8000
|
||||||
|
# 服务器 URL
|
||||||
|
SERVER_URL="http://localhost:8000"
|
||||||
# 调试模式,生产环境请设置为 false
|
# 调试模式,生产环境请设置为 false
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
# 私有 API 密钥,用于前后端 API 调用,使用 openssl rand -hex 32 生成
|
# 私有 API 密钥,用于前后端 API 调用,使用 openssl rand -hex 32 生成
|
||||||
@@ -37,7 +39,6 @@ SIGNALR_PING_INTERVAL=15
|
|||||||
FETCHER_CLIENT_ID=""
|
FETCHER_CLIENT_ID=""
|
||||||
FETCHER_CLIENT_SECRET=""
|
FETCHER_CLIENT_SECRET=""
|
||||||
FETCHER_SCOPES=public
|
FETCHER_SCOPES=public
|
||||||
FETCHER_CALLBACK_URL="http://localhost:8000/fetcher/callback"
|
|
||||||
|
|
||||||
# 日志设置
|
# 日志设置
|
||||||
LOG_LEVEL="INFO"
|
LOG_LEVEL="INFO"
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -183,9 +183,9 @@ cython_debug/
|
|||||||
.abstra/
|
.abstra/
|
||||||
|
|
||||||
# Visual Studio Code
|
# Visual Studio Code
|
||||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||||
# you could uncomment the following to ignore the entire vscode folder
|
# you could uncomment the following to ignore the entire vscode folder
|
||||||
# .vscode/
|
# .vscode/
|
||||||
|
|
||||||
@@ -210,5 +210,6 @@ bancho.py-master/*
|
|||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
|
||||||
# runtime file
|
# runtime file
|
||||||
|
storage/
|
||||||
replays/
|
replays/
|
||||||
osu-master/*
|
osu-master/*
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from pydantic import Field, ValidationInfo, field_validator
|
from pydantic import Field, HttpUrl, ValidationInfo, field_validator
|
||||||
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
|
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ class Settings(BaseSettings):
|
|||||||
port: int = 8000
|
port: int = 8000
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
private_api_secret: str = "your_private_api_secret_here"
|
private_api_secret: str = "your_private_api_secret_here"
|
||||||
|
server_url: HttpUrl = HttpUrl("http://localhost:8000")
|
||||||
|
|
||||||
# SignalR 设置
|
# SignalR 设置
|
||||||
signalr_negotiate_timeout: int = 30
|
signalr_negotiate_timeout: int = 30
|
||||||
@@ -74,7 +75,10 @@ class Settings(BaseSettings):
|
|||||||
fetcher_client_id: str = ""
|
fetcher_client_id: str = ""
|
||||||
fetcher_client_secret: str = ""
|
fetcher_client_secret: str = ""
|
||||||
fetcher_scopes: Annotated[list[str], NoDecode] = ["public"]
|
fetcher_scopes: Annotated[list[str], NoDecode] = ["public"]
|
||||||
fetcher_callback_url: str = "http://localhost:8000/fetcher/callback"
|
|
||||||
|
@property
|
||||||
|
def fetcher_callback_url(self) -> str:
|
||||||
|
return f"{self.server_url}fetcher/callback"
|
||||||
|
|
||||||
# 日志设置
|
# 日志设置
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from app.signalr import signalr_router as signalr_router
|
|||||||
|
|
||||||
from .auth import router as auth_router
|
from .auth import router as auth_router
|
||||||
from .fetcher import fetcher_router as fetcher_router
|
from .fetcher import fetcher_router as fetcher_router
|
||||||
|
from .file import file_router as file_router
|
||||||
from .private import private_router as private_router
|
from .private import private_router as private_router
|
||||||
from .v2.router import router as api_v2_router
|
from .v2.router import router as api_v2_router
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ __all__ = [
|
|||||||
"api_v2_router",
|
"api_v2_router",
|
||||||
"auth_router",
|
"auth_router",
|
||||||
"fetcher_router",
|
"fetcher_router",
|
||||||
|
"file_router",
|
||||||
"private_router",
|
"private_router",
|
||||||
"signalr_router",
|
"signalr_router",
|
||||||
]
|
]
|
||||||
|
|||||||
26
app/router/file.py
Normal file
26
app/router/file.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app.dependencies.storage import get_storage_service
|
||||||
|
from app.storage import LocalStorageService, StorageService
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
file_router = APIRouter(prefix="/file")
|
||||||
|
|
||||||
|
|
||||||
|
@file_router.get("/{path:path}")
|
||||||
|
async def get_file(path: str, storage: StorageService = Depends(get_storage_service)):
|
||||||
|
if not isinstance(storage, LocalStorageService):
|
||||||
|
raise HTTPException(404, "Not Found")
|
||||||
|
if not await storage.is_exists(path):
|
||||||
|
raise HTTPException(404, "Not Found")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return FileResponse(
|
||||||
|
path=storage._get_file_path(path),
|
||||||
|
media_type="application/octet-stream",
|
||||||
|
filename=path.split("/")[-1],
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise HTTPException(404, "Not Found")
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from app.signalr import signalr_router as signalr_router
|
|
||||||
|
|
||||||
from . import ( # pyright: ignore[reportUnusedImport] # noqa: F401
|
from . import ( # pyright: ignore[reportUnusedImport] # noqa: F401
|
||||||
beatmap,
|
beatmap,
|
||||||
beatmapset,
|
beatmapset,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
from .base import StorageService
|
from .base import StorageService
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
@@ -75,4 +77,4 @@ class LocalStorageService(StorageService):
|
|||||||
return full_path.exists() and full_path.is_file()
|
return full_path.exists() and full_path.is_file()
|
||||||
|
|
||||||
async def get_file_url(self, file_path: str) -> str:
|
async def get_file_url(self, file_path: str) -> str:
|
||||||
return str(self.storage_path / file_path)
|
return f"{settings.server_url}file/{file_path.lstrip('/')}"
|
||||||
|
|||||||
14
main.py
14
main.py
@@ -12,6 +12,7 @@ from app.router import (
|
|||||||
api_v2_router,
|
api_v2_router,
|
||||||
auth_router,
|
auth_router,
|
||||||
fetcher_router,
|
fetcher_router,
|
||||||
|
file_router,
|
||||||
private_router,
|
private_router,
|
||||||
signalr_router,
|
signalr_router,
|
||||||
)
|
)
|
||||||
@@ -38,19 +39,20 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(title="osu! API 模拟服务器", version="1.0.0", lifespan=lifespan)
|
app = FastAPI(title="osu! API 模拟服务器", version="1.0.0", lifespan=lifespan)
|
||||||
|
|
||||||
|
app.include_router(api_v2_router)
|
||||||
|
app.include_router(signalr_router)
|
||||||
|
app.include_router(fetcher_router)
|
||||||
|
app.include_router(file_router)
|
||||||
|
app.include_router(auth_router)
|
||||||
|
app.include_router(private_router)
|
||||||
# CORS 配置
|
# CORS 配置
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["http://localhost:5173"],
|
allow_origins=[str(settings.server_url)],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
app.include_router(api_v2_router)
|
|
||||||
app.include_router(signalr_router)
|
|
||||||
app.include_router(fetcher_router)
|
|
||||||
app.include_router(auth_router)
|
|
||||||
app.include_router(private_router)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
Reference in New Issue
Block a user