Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b58f74d59b | |||
| 86b030930c |
@@ -0,0 +1,15 @@
|
||||
from fastapi import APIRouter
|
||||
from .deps import get_current_user
|
||||
from .jwt import create_jwt_token, create_refresh_token, verify_access_token
|
||||
|
||||
# 统一创建并导出路由器,满足 main.py 的 include_router 需求
|
||||
router = APIRouter(tags=["Authentication"])
|
||||
|
||||
# 显式声明这个包对外暴露的接口
|
||||
__all__ = [
|
||||
"router",
|
||||
"get_current_user",
|
||||
"create_jwt_token",
|
||||
"create_refresh_token",
|
||||
"verify_access_token",
|
||||
]
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
from fastapi import Request, HTTPException
|
||||
import app.auth.jwt as jwt
|
||||
import os
|
||||
from . import jwt
|
||||
|
||||
JWT_SECRET = os.getenv("JWT_SECRET")
|
||||
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import app.auth.jwt as jwt
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import os
|
||||
import jwt
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
JWT_SECRET = os.getenv("JWT_SECRET")
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
import os
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 加载 .env 文件
|
||||
load_dotenv()
|
||||
|
||||
DATABASE_URL = os.getenv("DATABASE_URL")
|
||||
|
||||
# 自动将旧版的 postgres:// 替换为 SQLAlchemy 强要求的 postgresql://
|
||||
if DATABASE_URL and DATABASE_URL.startswith("postgres://"):
|
||||
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://", 1)
|
||||
elif DATABASE_URL and DATABASE_URL.startswith("postgres+"):
|
||||
DATABASE_URL = DATABASE_URL.replace("postgres+", "postgresql+", 1)
|
||||
|
||||
# 如果环境变量彻底缺失,做一个安全的报错提示
|
||||
if not DATABASE_URL:
|
||||
raise ValueError("DATABASE_URL environment variable is not set or empty.")
|
||||
|
||||
engine = create_engine(DATABASE_URL)
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
+2
-5
@@ -24,8 +24,5 @@ schema = Schema(query=Query, mutation=Mutation)
|
||||
graphql_router = GraphQLRouter(schema, context_getter=get_context)
|
||||
|
||||
# 挂载路由
|
||||
# 保留现有的 auth 路由供 Google OAuth 回调使用
|
||||
app.include_router(auth_router)
|
||||
|
||||
# 挂载 GraphQL 终点,前端以后只需要请求 /graphql
|
||||
app.include_router(graphql_router, prefix="/graphql")
|
||||
app.include_router(auth_router) # 保留现有的 auth 路由供 Google OAuth 回调使用
|
||||
app.include_router(graphql_router, prefix="/graphql") # 挂载 GraphQL 终点,前端以后只需要请求 /graphql
|
||||
+8
-6
@@ -56,22 +56,24 @@ class AutoID:
|
||||
不再需要手动定义任何 prefix,全自动根据 __tablename__ 编码生成
|
||||
支持子类自定义长度
|
||||
"""
|
||||
# 默认配置
|
||||
_id_prefix_len = 3 # 前缀长度
|
||||
_id_payload_len = 22 # 主体长度
|
||||
# 全局固定配置
|
||||
ID_PREFIX_LEN = 3
|
||||
ID_PAYLOAD_LEN = 22
|
||||
|
||||
@classmethod
|
||||
def id_length(cls) -> int:
|
||||
"""返回当前类 ID 的总长度"""
|
||||
return cls._id_prefix_len + cls._id_payload_len
|
||||
return cls.ID_PREFIX_LEN + cls.ID_PAYLOAD_LEN
|
||||
|
||||
@declared_attr
|
||||
def id(cls):
|
||||
# 运行时动态获取子类的 __tablename__
|
||||
tablename = cls.__tablename__
|
||||
|
||||
# 获取子类配置的长度
|
||||
p_len = cls._id_prefix_len
|
||||
s_len = cls._id_payload_len
|
||||
p_len = AutoID.ID_PREFIX_LEN
|
||||
s_len = AutoID.ID_PAYLOAD_LEN
|
||||
|
||||
prefix = encode_tablename_to_prefix(tablename, length=p_len)
|
||||
|
||||
# 核心工厂函数:拼接【编码后的表前缀】和【递增Payload】
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import Column, DateTime, String, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.sql import func
|
||||
from .base import AutoID, Base
|
||||
|
||||
# 只有在静态检查时才导入
|
||||
# 运行阶段这段代码会被跳过,彻底避免循环导入
|
||||
# 不确定是否用得上,但感觉这是个好习惯,尤其在模型之间有外键关系时
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class Follow(Base, AutoID):
|
||||
"""用户关注关系表"""
|
||||
__tablename__ = "follows"
|
||||
|
||||
follower_id = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
followee_id = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import Column, DateTime, String, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.sql import func
|
||||
from .base import AutoID, Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
from .project import ProjectItem
|
||||
from .workspace import Workspace
|
||||
|
||||
class ProjectLike(Base, AutoID):
|
||||
"""项目点赞表"""
|
||||
__tablename__ = "project_likes"
|
||||
|
||||
user_id = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
project_item_id = Column(
|
||||
String(ProjectItem.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("project_items.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
@@ -34,12 +28,12 @@ class ProjectFavorite(Base, AutoID):
|
||||
__tablename__ = "project_favorites"
|
||||
|
||||
user_id = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
project_item_id = Column(
|
||||
String(ProjectItem.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("project_items.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
@@ -55,12 +49,12 @@ class ProjectShare(Base, AutoID):
|
||||
__tablename__ = "project_shares"
|
||||
|
||||
user_id = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
project_item_id = Column(
|
||||
String(ProjectItem.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("project_items.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import Column, DateTime, String, ForeignKey, BigInteger
|
||||
from sqlalchemy.sql import func
|
||||
from .base import AutoID, Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
from .workspace import Workspace
|
||||
|
||||
class LibraryItem(Base, AutoID):
|
||||
"""工作区共享媒体/文件资源库"""
|
||||
__tablename__ = "library_items"
|
||||
|
||||
workspace_id = Column(
|
||||
String(Workspace.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||
index=True,
|
||||
)
|
||||
@@ -34,7 +29,7 @@ class LibraryItem(Base, AutoID):
|
||||
mime_type = Column(String, nullable=True) # 媒体类型,例如 'image/jpeg', 'video/mp4', 'application/pdf'
|
||||
|
||||
uploaded_by = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import Column, DateTime, String, ForeignKey, Integer
|
||||
from sqlalchemy.sql import func
|
||||
from .base import AutoID, Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .workspace import Workspace
|
||||
|
||||
class ProjectItem(Base, AutoID):
|
||||
"""项目与文件夹的树状虚拟文件系统"""
|
||||
__tablename__ = "project_items"
|
||||
|
||||
workspace_id = Column(
|
||||
String(Workspace.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||
index=True,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlalchemy import Column, DateTime, String, ForeignKey, BigInteger, UniqueConstraint, Integer
|
||||
from sqlalchemy.sql import func
|
||||
from .base import AutoID, Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class Workspace(Base, AutoID):
|
||||
"""工作区表"""
|
||||
__tablename__ = "workspaces"
|
||||
@@ -34,13 +30,13 @@ class WorkspaceMember(Base, AutoID):
|
||||
__tablename__ = "workspace_members"
|
||||
|
||||
workspace_id = Column(
|
||||
String(Workspace.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||
index=True,
|
||||
)
|
||||
# 允许 user_id 为空,因为被邀请的人可能还没注册系统,我们先记录他的邮箱
|
||||
user_id = Column(
|
||||
String(User.id_length()),
|
||||
String(AutoID.id_length()),
|
||||
ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
|
||||
@@ -7,8 +7,10 @@ requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"alembic>=1.18.4",
|
||||
"boto3>=1.43.9",
|
||||
"dotenv>=0.9.9",
|
||||
"fastapi>=0.136.1",
|
||||
"httpx>=0.28.1",
|
||||
"jwt>=1.4.0",
|
||||
"oss2>=2.19.1",
|
||||
"psycopg2-binary>=2.9.12",
|
||||
"python-jose>=3.5.0",
|
||||
|
||||
@@ -145,8 +145,10 @@ source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
{ name = "boto3" },
|
||||
{ name = "dotenv" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jwt" },
|
||||
{ name = "oss2" },
|
||||
{ name = "psycopg2-binary" },
|
||||
{ name = "python-jose" },
|
||||
@@ -160,8 +162,10 @@ dependencies = [
|
||||
requires-dist = [
|
||||
{ name = "alembic", specifier = ">=1.18.4" },
|
||||
{ name = "boto3", specifier = ">=1.43.9" },
|
||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||
{ name = "fastapi", specifier = ">=0.136.1" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "jwt", specifier = ">=1.4.0" },
|
||||
{ name = "oss2", specifier = ">=2.19.1" },
|
||||
{ name = "psycopg2-binary", specifier = ">=2.9.12" },
|
||||
{ name = "python-jose", specifier = ">=3.5.0" },
|
||||
@@ -304,6 +308,17 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.19.2"
|
||||
@@ -419,6 +434,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f", size = 24489, upload-time = "2020-05-12T22:03:45.643Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jwt"
|
||||
version = "1.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7f/20/21254c9e601e6c29445d1e8854c2a81bdb554e07a82fb1f9846137a6965c/jwt-1.4.0.tar.gz", hash = "sha256:f6f789128ac247142c79ee10f3dba6e366ec4e77c9920d18c1592e28aa0a7952", size = 24911, upload-time = "2025-06-23T13:28:38.289Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/80/34e3fae850adb0b7b8b9b1cf02b2d975fcb68e0e8eb7d56d6b4fc23f7433/jwt-1.4.0-py3-none-any.whl", hash = "sha256:7560a7f1de4f90de94ac645ee0303ac60c95b9e08e058fb69f6c330f71d71b11", size = 18248, upload-time = "2025-06-23T13:28:37.012Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.3.12"
|
||||
@@ -608,6 +635,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-jose"
|
||||
version = "3.5.0"
|
||||
|
||||
Reference in New Issue
Block a user