Skip to content

Commit 89cc841

Browse files
authored
Merge pull request #30 from hvt299/theanh/feat/update-uploadcv-canditate
Theanh/feat/update uploadcv canditate
2 parents dddf335 + 1bc7b41 commit 89cc841

14 files changed

Lines changed: 1144 additions & 49 deletions

app/auth.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,29 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
4444
except jwt.PyJWTError:
4545
raise credentials_exception
4646

47-
return email
47+
return email
48+
49+
async def get_current_user_with_role(token: str = Depends(oauth2_scheme)):
50+
"""Trả về cả email và role của user"""
51+
from app.database.config import get_db
52+
53+
credentials_exception = HTTPException(
54+
status_code=status.HTTP_401_UNAUTHORIZED,
55+
detail="Không thể xác thực thông tin (Token không hợp lệ hoặc đã hết hạn)",
56+
headers={"WWW-Authenticate": "Bearer"},
57+
)
58+
try:
59+
payload = jwt.decode(token, JWT_SECRET, algorithms=[ALGORITHM])
60+
email: str = payload.get("sub")
61+
if email is None:
62+
raise credentials_exception
63+
except jwt.PyJWTError:
64+
raise credentials_exception
65+
66+
# Lấy thông tin user từ database để có role
67+
db = get_db()
68+
user = await db["hr_users"].find_one({"email": email})
69+
if not user:
70+
raise credentials_exception
71+
72+
return {"email": email, "role": user.get("role", "hr")}

app/database/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Database package initialization

app/database/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,21 @@ class HRUserCreate(BaseModel):
7777
email: EmailStr
7878
full_name: str = Field(..., min_length=6, example="Trần Nam")
7979
password: str
80+
role: str = Field(default="hr", example="hr hoặc applicant")
8081

8182
@field_validator('password')
8283
def validate_password(cls, v):
83-
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
84+
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#+\-_=])[A-Za-z\d@$!%*?&#+\-_=]{8,}$"
8485
if not re.match(pattern, v):
8586
raise ValueError("Mật khẩu phải từ 8 ký tự, gồm ít nhất 1 chữ hoa, 1 chữ thường, 1 số và 1 ký tự đặc biệt.")
8687
return v
8788

89+
@field_validator('role')
90+
def validate_role(cls, v):
91+
if v not in ("hr", "applicant"):
92+
raise ValueError("Role phải là 'hr' hoặc 'applicant'.")
93+
return v
94+
8895
class HRUserLogin(BaseModel):
8996
email: EmailStr
9097
password: str
@@ -96,6 +103,7 @@ class HRUserDB(BaseModel):
96103
hashed_password: str
97104
avatar: str
98105
original_avatar: str
106+
role: str = Field(default="hr")
99107
is_verified: bool = Field(default=False)
100108
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
101109

app/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from fastapi.middleware.cors import CORSMiddleware
33
from app.database.config import connect_to_mongo, close_mongo_connection
44
from app.routers import auth_router, cv_router, job_router
5+
from app.routers import admin_router, applicant_router
56
from contextlib import asynccontextmanager
67

78
@asynccontextmanager
@@ -28,6 +29,8 @@ async def lifespan(app: FastAPI):
2829
app.include_router(auth_router.router)
2930
app.include_router(cv_router.router)
3031
app.include_router(job_router.router)
32+
app.include_router(admin_router.router)
33+
app.include_router(applicant_router.router)
3134

3235
@app.get("/", tags=["Health Check"])
3336
def root():

app/routers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Router package initialization

app/routers/admin_router.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from fastapi import APIRouter, Depends, HTTPException
2+
from bson import ObjectId
3+
from app.auth import get_current_user, get_current_user_with_role
4+
from app.database.config import get_db
5+
import os
6+
7+
router = APIRouter(prefix="/api/v1/admin", tags=["Admin"])
8+
9+
VALID_ROLES = ("hr", "applicant", "admin")
10+
BOOTSTRAP_SECRET = os.getenv("ADMIN_BOOTSTRAP_SECRET", "")
11+
12+
async def require_admin(user_info: dict = Depends(get_current_user_with_role)):
13+
if user_info["role"] != "admin":
14+
raise HTTPException(status_code=403, detail="Chỉ Admin mới có quyền thực hiện thao tác này")
15+
return user_info["email"]
16+
17+
@router.post("/bootstrap")
18+
async def bootstrap_admin(body: dict):
19+
"""Dùng 1 lần để tạo admin đầu tiên. Cần ADMIN_BOOTSTRAP_SECRET trong .env"""
20+
if not BOOTSTRAP_SECRET:
21+
raise HTTPException(status_code=403, detail="Bootstrap đã bị tắt")
22+
if body.get("secret") != BOOTSTRAP_SECRET:
23+
raise HTTPException(status_code=403, detail="Secret không đúng")
24+
email = body.get("email")
25+
if not email:
26+
raise HTTPException(status_code=400, detail="Thiếu email")
27+
db = get_db()
28+
result = await db["hr_users"].update_one({"email": email}, {"$set": {"role": "admin"}})
29+
if result.matched_count == 0:
30+
raise HTTPException(status_code=404, detail="Không tìm thấy tài khoản")
31+
return {"status": "success", "message": f"{email} đã được set làm Admin"}
32+
33+
@router.get("/users")
34+
async def list_users(_: str = Depends(require_admin)):
35+
db = get_db()
36+
cursor = db["hr_users"].find({}, {"hashed_password": 0, "raw_text": 0})
37+
users = await cursor.to_list(length=500)
38+
for u in users:
39+
u["id"] = str(u["_id"])
40+
del u["_id"]
41+
return users
42+
43+
@router.patch("/users/{user_id}/role")
44+
async def update_user_role(user_id: str, body: dict, _: str = Depends(require_admin)):
45+
new_role = body.get("role")
46+
if new_role not in VALID_ROLES:
47+
raise HTTPException(status_code=400, detail=f"Role không hợp lệ. Chọn: {VALID_ROLES}")
48+
db = get_db()
49+
result = await db["hr_users"].update_one(
50+
{"_id": ObjectId(user_id)},
51+
{"$set": {"role": new_role}}
52+
)
53+
if result.matched_count == 0:
54+
raise HTTPException(status_code=404, detail="Không tìm thấy người dùng")
55+
return {"status": "success", "message": f"Đã cập nhật role thành '{new_role}'"}

0 commit comments

Comments
 (0)