Compare commits

..

No commits in common. "main" and "dev/pjq" have entirely different histories.

13 changed files with 109 additions and 111 deletions

2
.env
View File

@ -1,3 +1,3 @@
ALGORITHM=HS256 ALGORITHM=HS256
DATABASE_URL=mysql://shixun:TEzzsLddDRdDwXdE@120.53.31.148:3306/shixun DATABASE_URL=sqlite:///test.db
SECRET_KEY=your_secret_key SECRET_KEY=your_secret_key

View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="demo1" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" serverName="ecs2" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="ecs2">
<serverdata>
<mappings>
<mapping deploy="./shixun/" local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

View File

@ -28,5 +28,5 @@
</profile-state> </profile-state>
</entry> </entry>
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="demo1" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project> </project>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="c90c26ce-7c47-4054-9829-4cec3d848165" name="ecs2" url="http://ecs2.heshunme.xyz:10000">
<fileTransfer rootFolder="/root" accessType="SFTP" host="ecs2.heshunme.xyz" port="22" sshConfigId="f1f6e1a9-94a6-4cd9-ba1a-ad35f1bc3d3e" sshConfig="root@ecs2.heshunme.xyz:22 key" keyPair="true">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>

View File

@ -20,7 +20,7 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None, s
if expires_delta: if expires_delta:
expire = datetime.utcnow() + expires_delta expire = datetime.utcnow() + expires_delta
else: else:
expire = datetime.utcnow() + timedelta(days=15) expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
print(settings, type(settings)) print(settings, type(settings))
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
@ -43,14 +43,7 @@ async def login(response: Response, user_data: dict, session: SessionDep):
token = create_access_token(data={"id": user.id, "role": user.role, "tanant_id": user.tenant.id}) token = create_access_token(data={"id": user.id, "role": user.role, "tanant_id": user.tenant.id})
# 设置cookie # 设置cookie
response.set_cookie( response.set_cookie(key="session_token", value=token, httponly=True)
key="session_token",
value=token,
httponly=True,
# domain=".ecs2.heshunme.xyz", # 确保域正确
samesite="none", # 根据需要设置samesite属性
secure=True,
)
# 关闭数据库会话 # 关闭数据库会话
session.close() session.close()

View File

@ -2,14 +2,19 @@
# @Time : 2024/11/19 下午8:05 # @Time : 2024/11/19 下午8:05
# @FileName: manage_project.py # @FileName: manage_project.py
# @Software: PyCharm # @Software: PyCharm
from datetime import datetime from fastapi import HTTPException, Response, Depends, APIRouter
from typing import List from typing import Optional, Annotated
from datetime import datetime, timedelta
from fastapi import APIRouter from jose import JWTError, jwt
from sqlalchemy import delete from sqlalchemy import delete
from sqlmodel import select
from models import Tenant, User, Project, ProjectUserLink
from dependencies import * from dependencies import *
from models import Project, ProjectUserLink from fastapi import APIRouter
from typing import List
router = APIRouter() router = APIRouter()
TenantRole = 1 TenantRole = 1
@ -40,7 +45,8 @@ async def get_project(response: Response, session: SessionDep, current_user: Use
raise HTTPException(status_code=404, detail="Project not found or you have no projects.") raise HTTPException(status_code=404, detail="Project not found or you have no projects.")
# 返回项目的基本信息 # 返回项目的基本信息
return [ return {
"projects": [
{ {
"name": project.name, "name": project.name,
"requirement": project.requirement, "requirement": project.requirement,
@ -49,6 +55,7 @@ async def get_project(response: Response, session: SessionDep, current_user: Use
} }
for project in projects for project in projects
] ]
}
# 新增与修改项目 # 新增与修改项目
@ -57,67 +64,87 @@ async def create_project(data: dict, session: SessionDep, current_user: User = D
if current_user.role != 1: if current_user.role != 1:
raise HTTPException(status_code=403, detail="Only Tenant admin users can add or update projects.") raise HTTPException(status_code=403, detail="Only Tenant admin users can add or update projects.")
# project_id = data.get("project_id") project_id = data.get("project_id")
name = data.get("name") name = data["name"]
requirement = data.get("requirement") requirement = data["requirement"]
start_time_str = data.get("start_time") start_time_str = data["start_time"]
deadline_str = data.get("deadline") deadline_str = data["deadline"]
estimators = data.get("estimator") estimators = data["estimators"]
auditors = data.get("auditor") auditors = data["auditors"]
# 验证是否缺少必要参数 # 验证是否缺少必要参数
if not name or not requirement or not start_time_str or not deadline_str: if not name or not requirement or not start_time_str or not deadline_str:
raise HTTPException(status_code=400, detail="Need more details") raise HTTPException(status_code=400, detail="Need more name/requirement/start_time/deadline")
# 验证开始时间是否早于结束时间 # 验证开始时间是否早于结束时间
# 去掉 'Z' 和毫秒部分
start_time_str = start_time_str.split('.')[0].rstrip('Z')
deadline_str = deadline_str.split('.')[0].rstrip('Z')
start_time = datetime.strptime(start_time_str, "%Y-%m-%dT%H:%M:%S") start_time = datetime.strptime(start_time_str, "%Y-%m-%dT%H:%M:%S")
deadline = datetime.strptime(deadline_str, "%Y-%m-%dT%H:%M:%S") deadline = datetime.strptime(deadline_str, "%Y-%m-%dT%H:%M:%S")
if start_time > deadline: if start_time > deadline:
raise HTTPException(status_code=400, detail="Start time must be before deadline") raise HTTPException(status_code=400, detail="Start time must be before deadline")
# 验证是否有传入评估/审核员
if not estimators or not auditors:
raise HTTPException(status_code=400, detail="Need more estimators or auditors")
users: List[User] = []
# 验证评估审核员是否存在 # 验证评估审核员是否存在
for username in estimators + auditors: query_estimators = select(User).where(User.username.in_(estimators))
query_estimator = select(User).where(User.username == username) users_estimators = session.exec(query_estimators).all()
if user := session.exec(query_estimator).first(): query_auditors = select(User).where(User.username.in_(auditors))
users.append(user) users_auditors = session.exec(query_auditors).all()
# 提取出所有查询到的
existing_estimators = {user.username for user in users_estimators}
existing_auditors = {user.username for user in users_auditors}
project = session.exec(select(Project).where(Project.name == name)).first() # 验证是否所有的username都存在于数据库中
if project and project.owner_id != current_user.tenant_id: missing_usernames = (set(auditors) | set(estimators)) - existing_estimators - existing_auditors
raise HTTPException(status_code=403, detail="You do not have permission to modify this project.")
if missing_usernames:
raise HTTPException(status_code=404, detail=f"Missing usernames:{missing_usernames}")
# 更新项目还是新增项目 # 更新项目还是新增项目
if project: if project_id:
# 查找现有项目
project = session.get(Project, project_id)
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# 更新项目内容 # 更新项目内容
project.name = name project.name = name
project.requirement = requirement project.requirement = requirement
project.start_time = start_time project.start_time = start_time
project.deadline = deadline project.deadline = deadline
else: else:
# 新增项目
exist_project = session.exec(select(Project).where(Project.name == name)).first()
print(exist_project) # 测试用
if exist_project:
raise HTTPException(status_code=404, detail="Project already exists")
project = Project( project = Project(
name=name, name=name,
requirement=requirement, requirement=requirement,
start_time=start_time, start_time=start_time,
deadline=deadline, deadline=deadline,
owner_id=current_user.tenant_id, owner_id=1 # 假设owner_id是1之后应该是通过token获取owner_id吧
) )
session.add(project)
# 处理项目和用户的关联 # 处理项目和用户的关联
# 先清除现有的关联 # 先清除现有的关联
# 生成删除语句并执行 # 生成删除语句并执行
project.users = [] print(project_id) # 测试用
project.users = users stmt = delete(ProjectUserLink).where(ProjectUserLink.project_id == project.id)
session.add(project) session.execute(stmt)
session.commit() session.commit() # 提交事务
# 重新建立与评估员和审核员的关系
for username in estimators:
user = next((user for user in users_estimators if user.username == username), None)
if user:
project_user_link = ProjectUserLink(project_id=project.id, user_id=user.id)
session.add(project_user_link)
for username in auditors:
user = next((user for user in users_auditors if user.username == username), None)
if user:
project_user_link = ProjectUserLink(project_id=project.id, user_id=user.id)
session.add(project_user_link)
# 提交事务 # 提交事务
session.commit() session.commit()
@ -130,12 +157,15 @@ async def create_project(data: dict, session: SessionDep, current_user: User = D
# 删除项目 # 删除项目
@router.delete("/api/s1/project") @router.delete("/api/s1/project")
async def delete_project(name: str, session: SessionDep, current_user: User = Depends(get_current_user)): async def delete_project(data: dict, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 1: if current_user.role != 1:
raise HTTPException(status_code=403, detail="Only Tenant admin users can delete projects.") raise HTTPException(status_code=403, detail="Only Tenant admin users can delete projects.")
# project_name = data.get("name") project_name = data.get("name")
project_name = name
# 权限检查:只有管理员才可以删除项目
# if current_user.role != 1:
# raise HTTPException(status_code=403, detail="Only admin users can delete projects")
if not project_name: if not project_name:
raise HTTPException(status_code=400, detail="Project name is required") raise HTTPException(status_code=400, detail="Project name is required")

View File

@ -3,20 +3,28 @@
# @FileName: manage_tanant.py # @FileName: manage_tanant.py
# @Software: PyCharm # @Software: PyCharm
from fastapi import APIRouter from fastapi import APIRouter
from fastapi import HTTPException, Response, Depends, APIRouter
from typing import Optional, Annotated
from datetime import datetime, timedelta
from jose import JWTError, jwt
from sqlalchemy import delete from sqlalchemy import delete
from sqlmodel import select
from models import Tenant, User, Project
from dependencies import * from dependencies import *
router = APIRouter() router = APIRouter()
from fastapi import HTTPException, Response from fastapi import HTTPException, Response
from sqlalchemy.orm import Session
from models import Tenant, User # 假设你已导入 Tenant 和 User 模型 from models import Tenant, User # 假设你已导入 Tenant 和 User 模型
from dependencies import SessionDep # 假设 SessionDep 是数据库会话的依赖 from dependencies import SessionDep # 假设 SessionDep 是数据库会话的依赖
#列举所有租户 #列举所有租户
@router.get("/api/s1/tenant") @router.get("/api/s1/tenant")
async def get_tenant(response: Response, session: SessionDep, current_user: User = Depends(get_current_user)): async def get_tenant(response: Response, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 0: if current_user.role != 0:
raise HTTPException(status_code=403, detail="Only Superadmin can list all tenants.") raise HTTPException(status_code=403, detail="Only Superadmin can list all tenants.")
@ -43,7 +51,6 @@ async def get_tenant(response: Response, session: SessionDep, current_user: User
return {"tenants": tenant_data} return {"tenants": tenant_data}
# 新增和修改租户 # 新增和修改租户
@router.post("/api/s1/tenant") @router.post("/api/s1/tenant")
async def create_or_update_tenant(data: dict, session: SessionDep, current_user: User = Depends(get_current_user)): async def create_or_update_tenant(data: dict, session: SessionDep, current_user: User = Depends(get_current_user)):
@ -108,16 +115,17 @@ async def create_or_update_tenant(data: dict, session: SessionDep, current_user:
user.password = password user.password = password
session.add(user) session.add(user)
session.commit() session.commit()
print(user) #测试用
return {"message": "Tenant and User update successfully"} return {"message": "Tenant and User update successfully"}
#删除租户 #删除租户
@router.delete("/api/s1/tenant") @router.delete("/api/s1/tenant")
async def delete_tenant(name: str, user_num: int, session: SessionDep, current_user: User = Depends(get_current_user)): async def delete_tenant(data: dict, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 0: if current_user.role != 0:
raise HTTPException(status_code=403, detail="Only Superadmin can delete tenants.") raise HTTPException(status_code=403, detail="Only Superadmin can delete tenants.")
tenant_name = name tenant_name = data.get("name")
if not tenant_name: if not tenant_name:
raise HTTPException(status_code=400, detail="Tenant name is required") raise HTTPException(status_code=400, detail="Tenant name is required")

View File

@ -3,7 +3,7 @@
# @Author : 河瞬 # @Author : 河瞬
# @FileName: manage_user.py # @FileName: manage_user.py
# @Software: PyCharm # @Software: PyCharm
from fastapi import HTTPException, APIRouter, Depends from fastapi import HTTPException, APIRouter, Depends, Request
from sqlmodel import select from sqlmodel import select
from dependencies import SessionDep, get_current_user from dependencies import SessionDep, get_current_user
@ -14,7 +14,7 @@ router = APIRouter()
# 枚举成员 # 枚举成员
@router.get("/api/s1/user") @router.get("/api/s1/user")
async def list_users(session: SessionDep, current_user: User = Depends(get_current_user)): async def list_users(request: Request, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 1: if current_user.role != 1:
raise HTTPException(status_code=403, detail="Only admin users can list users") raise HTTPException(status_code=403, detail="Only admin users can list users")
@ -30,7 +30,7 @@ async def add_or_update_user(data: dict, session: SessionDep, current_user: User
raise HTTPException(status_code=403, detail="Only admin users can add or update users") raise HTTPException(status_code=403, detail="Only admin users can add or update users")
username = data.get("username") username = data.get("username")
password = data.get("password", None) password = data.get("password")
role = data.get("role") role = data.get("role")
if role not in ["auditor", "estimator"]: if role not in ["auditor", "estimator"]:
raise HTTPException(status_code=400, detail="Invalid role") raise HTTPException(status_code=400, detail="Invalid role")
@ -42,7 +42,7 @@ async def add_or_update_user(data: dict, session: SessionDep, current_user: User
user = session.exec(select(User).where(User.username == username, User.tenant_id == current_user.tenant_id)).first() user = session.exec(select(User).where(User.username == username, User.tenant_id == current_user.tenant_id)).first()
if user: if user:
if password: if password and password != "":
user.password = password user.password = password
user.role = role user.role = role
session.add(user) session.add(user)

View File

@ -14,6 +14,5 @@ class Settings(BaseSettings):
class Config: class Config:
env_file = ".env" env_file = ".env"
if __name__ == '__main__': if __name__ == '__main__':
print(Settings().ALGORITHM) print(Settings().ALGORITHM)

View File

@ -22,12 +22,9 @@ async def lifespan(app: FastAPI):
app = FastAPI(lifespan=lifespan) app = FastAPI(lifespan=lifespan)
# noinspection PyTypeChecker
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
# allow_origins=["*"], allow_origins=["*"], # 允许所有来源,也可以指定具体的来源,例如 ["http://example.com", "https://example.com"]
allow_origins=["http://localhost:8080", "http://localhost:5000"],
# 允许所有来源,也可以指定具体的来源,例如 ["http://example.com", "https://example.com"]
allow_credentials=True, # 允许携带凭证如cookies allow_credentials=True, # 允许携带凭证如cookies
allow_methods=["*"], # 允许所有方法,也可以指定具体的方法,例如 ["GET", "POST", "PUT", "DELETE"] allow_methods=["*"], # 允许所有方法,也可以指定具体的方法,例如 ["GET", "POST", "PUT", "DELETE"]
allow_headers=["*"], # 允许所有头部,也可以指定具体的头部,例如 ["Content-Type", "Authorization"] allow_headers=["*"], # 允许所有头部,也可以指定具体的头部,例如 ["Content-Type", "Authorization"]

View File

@ -5,4 +5,3 @@ pydantic~=2.9.2
pydantic-settings~=2.6.1 pydantic-settings~=2.6.1
mysqlclient mysqlclient
sqlmodel~=0.0.22 sqlmodel~=0.0.22
sqlalchemy~=2.0.36

View File

@ -18,7 +18,7 @@ def create_db_and_tables():
# 创建一个测试客户端 # 创建一个测试客户端
client = TestClient(app) client = TestClient(app)
session: Session | None = None session: Session = None
class TestLoginReg(unittest.TestCase): class TestLoginReg(unittest.TestCase):
@ -69,7 +69,7 @@ class TestLoginReg(unittest.TestCase):
# 发送登录请求 # 发送登录请求
response = client.post("/api/s1/login", json={"username": "testuser", "password": "testpassword"}) response = client.post("/api/s1/login", json={"username": "testuser", "password": "testpassword"})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {'message': 'Login successful', 'role': 1}) self.assertEqual(response.json(), {"message": "Login successful"})
def test_register(self): def test_register(self):
# 发送注册请求 # 发送注册请求