Ajout admin
Build & Deploy / build (push) Successful in 41s

This commit is contained in:
Mouigni
2026-08-13 23:46:41 +02:00
parent c4aeedd032
commit ad84286259
3 changed files with 155 additions and 79 deletions
+6 -3
View File
@@ -4,7 +4,7 @@ from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
SECRET_KEY = "your-secret-key-change-in-production" SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30 ACCESS_TOKEN_EXPIRE_MINUTES = 30
@@ -30,6 +30,10 @@ class UserResponse(BaseModel):
id: int id: int
email: str email: str
full_name: Optional[str] = None full_name: Optional[str] = None
role: str = "user" # ← nouveau
class RoleUpdate(BaseModel):
role: str
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password) return pwd_context.verify(plain_password, hashed_password)
@@ -44,8 +48,7 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
else: else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15) expire = datetime.now(timezone.utc) + timedelta(minutes=15)
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_token(token: str) -> Optional[str]: def decode_token(token: str) -> Optional[str]:
try: try:
+139 -65
View File
@@ -1,10 +1,12 @@
from fastapi import FastAPI, Depends, HTTPException, status from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from datetime import timedelta from datetime import timedelta
from database.database import SessionLocal from database.database import SessionLocal
from database.models import Document, User from database.models import Document, User
from api.auth import ( from api.auth import (
UserLogin, UserCreate, Token, UserResponse, get_password_hash, verify_password, UserLogin, UserCreate, Token, UserResponse, RoleUpdate,
get_password_hash, verify_password,
create_access_token, decode_token, ACCESS_TOKEN_EXPIRE_MINUTES create_access_token, decode_token, ACCESS_TOKEN_EXPIRE_MINUTES
) )
@@ -18,113 +20,76 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
# ── Fonction vérification admin ──────────────────────────────
def require_admin(token: str):
email = decode_token(token)
if not email:
raise HTTPException(status_code=401, detail="Invalid token")
db = SessionLocal()
user = db.query(User).filter(User.email == email).first()
db.close()
if not user or user.role != "admin":
raise HTTPException(status_code=403, detail="Accès refusé")
return user
# ── Endpoints existants ──────────────────────────────────────
@app.get("/") @app.get("/")
def root(): def root():
return {"message": "RegWatch API running"} return {"message": "RegWatch API running"}
@app.post("/test-login")
def test_login(credentials: UserLogin):
return {"received": credentials.dict()}
@app.post("/signup", response_model=UserResponse) @app.post("/signup", response_model=UserResponse)
def signup(user: UserCreate): def signup(user: UserCreate):
db = SessionLocal() db = SessionLocal()
existing_user = db.query(User).filter(User.email == user.email).first() existing_user = db.query(User).filter(User.email == user.email).first()
if existing_user: if existing_user:
db.close() db.close()
raise HTTPException( raise HTTPException(status_code=400, detail="Email already registered")
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
hashed_password = get_password_hash(user.password) hashed_password = get_password_hash(user.password)
db_user = User( db_user = User(email=user.email, password=hashed_password, full_name=user.full_name)
email=user.email,
password=hashed_password,
full_name=user.full_name
)
db.add(db_user) db.add(db_user)
db.commit() db.commit()
db.refresh(db_user) db.refresh(db_user)
db.close() db.close()
return UserResponse(id=db_user.id, email=db_user.email, full_name=db_user.full_name, role=db_user.role)
return UserResponse(
id=db_user.id,
email=db_user.email,
full_name=db_user.full_name
)
@app.post("/login", response_model=Token) @app.post("/login", response_model=Token)
def login(credentials: UserLogin): def login(credentials: UserLogin):
try: try:
db = SessionLocal() db = SessionLocal()
user = db.query(User).filter(User.email == credentials.email).first() user = db.query(User).filter(User.email == credentials.email).first()
db.close() db.close()
if not user: if not user:
raise HTTPException( raise HTTPException(status_code=401, detail="User not found")
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found"
)
if not verify_password(credentials.password, user.password): if not verify_password(credentials.password, user.password):
raise HTTPException( raise HTTPException(status_code=401, detail="Invalid password")
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid password"
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token( access_token = create_access_token(
data={"sub": user.email}, expires_delta=access_token_expires data={"sub": user.email},
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
) )
return Token(access_token=access_token, token_type="bearer") return Token(access_token=access_token, token_type="bearer")
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
print(f"ERROR in login: {str(e)}") raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")
import traceback
traceback.print_exc()
raise HTTPException(
status_code=500,
detail=f"Server error: {str(e)}"
)
@app.get("/me", response_model=UserResponse) @app.get("/me", response_model=UserResponse)
def get_current_user(token: str): def get_current_user(token: str):
email = decode_token(token) email = decode_token(token)
if not email: if not email:
raise HTTPException( raise HTTPException(status_code=401, detail="Invalid token")
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
db = SessionLocal() db = SessionLocal()
user = db.query(User).filter(User.email == email).first() user = db.query(User).filter(User.email == email).first()
db.close() db.close()
if not user: if not user:
raise HTTPException( raise HTTPException(status_code=404, detail="User not found")
status_code=status.HTTP_404_NOT_FOUND, return UserResponse(id=user.id, email=user.email, full_name=user.full_name, role=user.role)
detail="User not found"
)
return UserResponse(
id=user.id,
email=user.email,
full_name=user.full_name
)
@app.get("/documents") @app.get("/documents")
def get_documents(): def get_documents():
db = SessionLocal() db = SessionLocal()
documents = db.query(Document).all() documents = db.query(Document).all()
result = [] result = []
for doc in documents: for doc in documents:
result.append({ result.append({
"id": doc.id, "id": doc.id,
@@ -135,7 +100,116 @@ def get_documents():
"date": doc.meeting_date, "date": doc.meeting_date,
"pdf_url": doc.pdf_url "pdf_url": doc.pdf_url
}) })
db.close() db.close()
return result
return result # ── Migration colonne role ───────────────────────────────────
@app.post("/admin/migrate-add-role")
def migrate_add_role():
"""Endpoint temporaire — ajoute la colonne role si absente"""
db = SessionLocal()
try:
db.execute(text("ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR DEFAULT 'user'"))
db.commit()
return {"message": "Colonne role ajoutée avec succès"}
except Exception as e:
return {"message": str(e)}
finally:
db.close()
# ── Premier admin sans neckfire ──────────────────────────────
@app.post("/admin/make-admin")
def make_admin(token: str, target_email: str):
"""
Passe un user en admin.
Fonctionne sans auth si aucun admin n'existe encore.
Se désactive automatiquement si un admin existe déjà.
"""
db = SessionLocal()
existing_admins = db.query(User).filter(User.role == "admin").count()
if existing_admins > 0:
# Il y a déjà un admin — vérifier que l'appelant est admin
email = decode_token(token)
caller = db.query(User).filter(User.email == email).first()
if not caller or caller.role != "admin":
db.close()
raise HTTPException(status_code=403, detail="Accès refusé")
target = db.query(User).filter(User.email == target_email).first()
if not target:
db.close()
raise HTTPException(status_code=404, detail="Utilisateur introuvable")
target.role = "admin"
db.commit()
db.close()
return {"message": f"{target_email} est maintenant admin"}
# ── Endpoints admin ──────────────────────────────────────────
@app.get("/admin/stats")
def admin_stats(token: str):
require_admin(token)
db = SessionLocal()
total_docs = db.query(Document).count()
cir_docs = db.query(Document).filter(Document.source == "CIR").count()
sccs_docs = db.query(Document).filter(Document.source == "SCCS").count()
total_users = db.query(User).count()
db.close()
return {
"total_documents": total_docs,
"cir_documents": cir_docs,
"sccs_documents": sccs_docs,
"total_users": total_users
}
@app.get("/admin/users")
def admin_get_users(token: str):
require_admin(token)
db = SessionLocal()
users = db.query(User).all()
result = [{"id": u.id, "email": u.email, "full_name": u.full_name, "role": u.role} for u in users]
db.close()
return result
@app.delete("/admin/users/{user_id}")
def admin_delete_user(user_id: int, token: str):
require_admin(token)
db = SessionLocal()
user = db.query(User).filter(User.id == user_id).first()
if not user:
db.close()
raise HTTPException(status_code=404, detail="Utilisateur introuvable")
db.delete(user)
db.commit()
db.close()
return {"message": "Utilisateur supprimé"}
@app.patch("/admin/users/{user_id}/role")
def admin_update_role(user_id: int, role_update: RoleUpdate, token: str):
require_admin(token)
db = SessionLocal()
user = db.query(User).filter(User.id == user_id).first()
if not user:
db.close()
raise HTTPException(status_code=404, detail="Utilisateur introuvable")
user.role = role_update.role
db.commit()
db.close()
return {"message": f"Rôle mis à jour : {role_update.role}"}
@app.delete("/admin/documents/{doc_id}")
def admin_delete_document(doc_id: int, token: str):
require_admin(token)
db = SessionLocal()
doc = db.query(Document).filter(Document.id == doc_id).first()
if not doc:
db.close()
raise HTTPException(status_code=404, detail="Document introuvable")
db.delete(doc)
db.commit()
db.close()
return {"message": "Document supprimé"}
+10 -11
View File
@@ -3,19 +3,18 @@ from database.database import Base
class User(Base): class User(Base):
__tablename__ = "users" __tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True) password = Column(String)
password = Column(String)
full_name = Column(String, nullable=True) full_name = Column(String, nullable=True)
role = Column(String, default="user") # ← nouveau
class Document(Base): class Document(Base):
__tablename__ = "documents" __tablename__ = "documents"
id = Column(Integer, primary_key=True, index=True)
id = Column(Integer, primary_key=True, index=True) title = Column(String)
title = Column(String) ingredient = Column(String)
ingredient = Column(String) source = Column(String)
source = Column(String)
document_type = Column(String) document_type = Column(String)
meeting_date = Column(String, nullable=True) meeting_date = Column(String, nullable=True)
pdf_url = Column(String, unique=True) pdf_url = Column(String, unique=True)