Initial FastAPI admin auth scaffold
This commit is contained in:
1
app/core/__init__.py
Normal file
1
app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Core application wiring."""
|
||||
39
app/core/config.py
Normal file
39
app/core/config.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
populate_by_name=True,
|
||||
)
|
||||
|
||||
app_name: str = Field(default="py_server", alias="APP_NAME")
|
||||
app_env: str = Field(default="local", alias="APP_ENV")
|
||||
database_path: Path = Field(default=Path("storage/app.db"), alias="DATABASE_PATH")
|
||||
|
||||
admin_jwt_secret: str = Field(
|
||||
default="dev_admin_secret_change_me",
|
||||
alias="JWT_ADMIN_SECRET",
|
||||
)
|
||||
admin_jwt_ttl: int = Field(default=3600, alias="ADMIN_JWT_TTL")
|
||||
admin_jwt_refresh_ttl: int = Field(default=7200, alias="ADMIN_JWT_REFRESH_TTL")
|
||||
jwt_blacklist_ttl: int = Field(default=7201, alias="JWT_BLACKLIST_TTL")
|
||||
|
||||
api_jwt_secret: str = Field(default="dev_api_secret_change_me", alias="JWT_SECRET")
|
||||
api_jwt_ttl: int = Field(default=3600, alias="JWT_TTL")
|
||||
api_jwt_refresh_ttl: int = Field(default=7200, alias="JWT_REFRESH_TTL")
|
||||
|
||||
admin_seed_username: str = Field(default="admin", alias="ADMIN_SEED_USERNAME")
|
||||
admin_seed_password: str = Field(default="admin", alias="ADMIN_SEED_PASSWORD")
|
||||
cors_allow_origins: list[str] = Field(default=["*"], alias="CORS_ALLOW_ORIGINS")
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
87
app/core/database.py
Normal file
87
app/core/database.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import asyncio
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Database:
|
||||
def __init__(self, path: Path) -> None:
|
||||
self.path = path
|
||||
|
||||
async def initialize(self) -> None:
|
||||
await asyncio.to_thread(self._initialize)
|
||||
|
||||
async def execute(self, sql: str, params: tuple[Any, ...] = ()) -> int:
|
||||
return await asyncio.to_thread(self._execute, sql, params)
|
||||
|
||||
async def fetchone(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> dict[str, Any] | None:
|
||||
return await asyncio.to_thread(self._fetchone, sql, params)
|
||||
|
||||
async def fetchall(
|
||||
self,
|
||||
sql: str,
|
||||
params: tuple[Any, ...] = (),
|
||||
) -> list[dict[str, Any]]:
|
||||
return await asyncio.to_thread(self._fetchall, sql, params)
|
||||
|
||||
def _connect(self) -> sqlite3.Connection:
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(self.path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA foreign_keys = ON")
|
||||
return conn
|
||||
|
||||
def _initialize(self) -> None:
|
||||
conn = self._connect()
|
||||
try:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS admin_user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
user_type TEXT NOT NULL DEFAULT 'admin',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
phone TEXT NOT NULL DEFAULT '',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
status INTEGER NOT NULL DEFAULT 1,
|
||||
login_ip TEXT NOT NULL DEFAULT '',
|
||||
login_time TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
remark TEXT NOT NULL DEFAULT ''
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _execute(self, sql: str, params: tuple[Any, ...]) -> int:
|
||||
conn = self._connect()
|
||||
try:
|
||||
cursor = conn.execute(sql, params)
|
||||
conn.commit()
|
||||
return int(cursor.lastrowid or 0)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _fetchone(self, sql: str, params: tuple[Any, ...]) -> dict[str, Any] | None:
|
||||
conn = self._connect()
|
||||
try:
|
||||
row = conn.execute(sql, params).fetchone()
|
||||
return dict(row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def _fetchall(self, sql: str, params: tuple[Any, ...]) -> list[dict[str, Any]]:
|
||||
conn = self._connect()
|
||||
try:
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
70
app/core/dependencies.py
Normal file
70
app/core/dependencies.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from functools import lru_cache
|
||||
|
||||
from app.common.repository.admin_user_repository import AdminUserRepository
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.core.database import Database
|
||||
from app.lib.jwt.blacklist import InMemoryTokenBlacklist
|
||||
from app.lib.jwt.factory import JwtFactory
|
||||
from app.lib.response.admin_return import AdminReturn
|
||||
from app.service.admin.login.login_service import LoginService
|
||||
from app.service.admin.login.refresh_service import RefreshService
|
||||
from app.service.admin.profile.current_user_service import CurrentUserService
|
||||
from app.service.base_token_service import BaseTokenService
|
||||
|
||||
|
||||
# lru_cache 会缓存函数第一次创建出来的对象。
|
||||
# 这里用它把 Database、JwtFactory、TokenService 等依赖做成应用级单例,
|
||||
# 类似 Hyperf 从容器里反复 get 同一个共享服务。
|
||||
@lru_cache
|
||||
def get_database() -> Database:
|
||||
return Database(get_settings().database_path)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_token_blacklist() -> InMemoryTokenBlacklist:
|
||||
return InMemoryTokenBlacklist()
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_jwt_factory() -> JwtFactory:
|
||||
return JwtFactory(get_settings(), get_token_blacklist())
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_token_service() -> BaseTokenService:
|
||||
return BaseTokenService(get_jwt_factory())
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_admin_return() -> AdminReturn:
|
||||
return AdminReturn()
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_admin_user_repository() -> AdminUserRepository:
|
||||
return AdminUserRepository(get_database())
|
||||
|
||||
|
||||
def get_login_service() -> LoginService:
|
||||
return LoginService(
|
||||
get_admin_user_repository(),
|
||||
get_token_service(),
|
||||
get_admin_return(),
|
||||
)
|
||||
|
||||
|
||||
def get_refresh_service() -> RefreshService:
|
||||
return RefreshService(get_token_service(), get_admin_return())
|
||||
|
||||
|
||||
def get_current_user_service() -> CurrentUserService:
|
||||
return CurrentUserService(get_admin_user_repository(), get_admin_return())
|
||||
|
||||
|
||||
async def bootstrap_database(settings: Settings | None = None) -> None:
|
||||
settings = settings or get_settings()
|
||||
await get_database().initialize()
|
||||
await get_admin_user_repository().ensure_seed_admin(
|
||||
settings.admin_seed_username,
|
||||
settings.admin_seed_password,
|
||||
)
|
||||
Reference in New Issue
Block a user