Update
This commit is contained in:
@@ -6,20 +6,17 @@
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3-3.14-trixie",
|
||||
"features": {
|
||||
"ghcr.io/devcontainer-community/devcontainer-features/astral.sh-uv:1": {}
|
||||
},
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.black-formatter",
|
||||
"ms-python.isort",
|
||||
"charliermarsh.ruff"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "pip3 install --user -r requirements.txt",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python-envs.defaultEnvManager": "ms-python.python:venv"
|
||||
}
|
||||
+3
-6
@@ -1,12 +1,9 @@
|
||||
from fastapi import FastAPI
|
||||
from .routes import user, login
|
||||
from .models import engine
|
||||
from .routes import user, login, posts
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
async def root() -> str:
|
||||
return "Hello World"
|
||||
|
||||
app.include_router(user.router)
|
||||
app.include_router(login.router)
|
||||
app.include_router(login.router)
|
||||
app.include_router(posts.router)
|
||||
@@ -12,9 +12,22 @@ def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(securit
|
||||
with Session(engine) as session:
|
||||
query = select(User).where(User.username == payload["username"]).limit(1)
|
||||
user: User = session.exec(query).first()
|
||||
if user == None:
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code = status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Credenciales invalidas"
|
||||
)
|
||||
return user
|
||||
if user.password_version != payload["pwd_v"]:
|
||||
raise HTTPException(
|
||||
status_code = status.HTTP_401_UNAUTHORIZED,
|
||||
detail = "Credenciales invalidas"
|
||||
)
|
||||
return user
|
||||
|
||||
def get_staff_user(user: User = Depends(get_current_user)) -> User:
|
||||
if not user.is_staff:
|
||||
raise HTTPException(
|
||||
status_code = status.HTTP_403_FORBIDDEN,
|
||||
detail = "This user needs to be an Staff to access this resource"
|
||||
)
|
||||
return user
|
||||
|
||||
@@ -16,6 +16,7 @@ class User(SQLModel, table=True):
|
||||
username: str = Field(index=True, unique=True)
|
||||
email: str = Field(index=True, unique=True)
|
||||
password_hash: str
|
||||
password_version: int = Field(default=0)
|
||||
full_name: Optional[str] = None
|
||||
bio: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
@@ -44,6 +45,7 @@ class User(SQLModel, table=True):
|
||||
_HASH_ITERATIONS
|
||||
)
|
||||
self.password_hash = f"{salt.hex()}${derived.hex()}"
|
||||
self.password_version += 1 # Aumenta la versión de la contraseña
|
||||
|
||||
def verify_password(self, password: str) -> bool:
|
||||
try:
|
||||
@@ -59,6 +61,13 @@ class User(SQLModel, table=True):
|
||||
)
|
||||
return secrets.compare_digest(hash_hex, derived.hex())
|
||||
|
||||
class Post(SQLModel, table=True):
|
||||
__tablename__ = "posts"
|
||||
id: int = Field(default=None, primary_key=True, sa_column_kwargs={"autoincrement": True})
|
||||
title: str
|
||||
body: Optional[str]
|
||||
creator_id: int = Field(foreign_key="users.id")
|
||||
|
||||
engine = create_engine(settings.DATABASE_URL)
|
||||
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
+11
-1
@@ -24,4 +24,14 @@ class LoginSchema(BaseModel):
|
||||
|
||||
class TokenSchema(BaseModel):
|
||||
access_token: str
|
||||
type: str = "bearer"
|
||||
type: str = "bearer"
|
||||
|
||||
class ChangePasswordSchema(BaseModel):
|
||||
old_password: str
|
||||
new_password: str
|
||||
|
||||
class PostSchema(BaseModel):
|
||||
id: Optional[int]
|
||||
username: Optional[str]
|
||||
title: str
|
||||
body: str
|
||||
|
||||
+15
-4
@@ -1,11 +1,12 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from ..pmodels import LoginSchema, TokenSchema
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from ..auth.dependencies import get_current_user
|
||||
from ..pmodels import LoginSchema, TokenSchema, StatusModel, ChangePasswordSchema
|
||||
from ..models import engine, User
|
||||
from sqlmodel import Session, select
|
||||
from ..settings import SECRET_KEY
|
||||
import jwt
|
||||
router = APIRouter(
|
||||
prefix="/login",
|
||||
prefix="/api/login",
|
||||
tags=["Autenticación"]
|
||||
)
|
||||
|
||||
@@ -18,9 +19,19 @@ async def login(data: LoginSchema) -> TokenSchema:
|
||||
if user is None:
|
||||
raise HTTPException(status_code=401, detail="User/Password Incorrect")
|
||||
if user.verify_password(data.password):
|
||||
encoded_jwt = jwt.encode({"username": user.username}, SECRET_KEY, algorithm="HS256")
|
||||
encoded_jwt = jwt.encode({"username": user.username, "pwd_v": user.password_version}, SECRET_KEY, algorithm="HS256")
|
||||
return TokenSchema(
|
||||
access_token=encoded_jwt
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=401, detail="User/Password Incorrect")
|
||||
|
||||
@router.post("/set-password",
|
||||
description = "Establece otra contraseña (requiere la anterior)",
|
||||
summary = "Cambia la contraseña")
|
||||
async def set_password(data: ChangePasswordSchema, user: User = Depends(get_current_user)) -> StatusModel:
|
||||
if not user.verify_password(data.old_password):
|
||||
return 401, StatusModel(status="Invalid Password")
|
||||
|
||||
user.set_password(data.new_password)
|
||||
return StatusModel()
|
||||
@@ -0,0 +1,40 @@
|
||||
from typing import Optional
|
||||
from ..models import User, Post, engine
|
||||
from sqlmodel import Session, select
|
||||
from ..pmodels import PostSchema, StatusModel
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from ..auth.dependencies import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/api/posts", tags=["Publicaciones"])
|
||||
|
||||
@router.get("")
|
||||
async def get_posts(page: int = 1, user: User = Depends(get_current_user)) -> list[PostSchema]:
|
||||
posts: list[PostSchema] = []
|
||||
with Session(engine) as session:
|
||||
query = select(Post, User).where(Post.creator_id == User.id).limit(10).offset(10*(page-1))
|
||||
result = session.exec(query)
|
||||
for post, user in result:
|
||||
posts.append(PostSchema(
|
||||
id=post.id,
|
||||
username=user.username,
|
||||
title=post.title,
|
||||
body=post.body
|
||||
))
|
||||
return posts
|
||||
|
||||
@router.post("")
|
||||
async def create_post(data: PostSchema, user: User = Depends(get_current_user)) -> PostSchema:
|
||||
post = Post(
|
||||
user=user.id,
|
||||
title=data.title,
|
||||
body=data.body
|
||||
)
|
||||
with Session(engine) as session:
|
||||
session.add(post)
|
||||
session.commit()
|
||||
return PostSchema(
|
||||
username=user.username,
|
||||
title=post.title,
|
||||
body=post.body
|
||||
)
|
||||
+9
-9
@@ -3,12 +3,10 @@ from ..pmodels import pUser, pUserWithPassword, StatusModel
|
||||
from ..models import engine, User
|
||||
from ..auth.dependencies import get_current_user
|
||||
from sqlmodel import Session, select
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/users",
|
||||
tags=["users"],
|
||||
dependencies=[
|
||||
Depends(get_current_user)
|
||||
],
|
||||
prefix="/api/users",
|
||||
tags=["Usuarios"],
|
||||
responses = {
|
||||
status.HTTP_401_UNAUTHORIZED: {
|
||||
"description": "Token invalido o ausente",
|
||||
@@ -21,15 +19,17 @@ router = APIRouter(
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/")
|
||||
async def get_users() -> list[pUser]:
|
||||
@router.get("/", description="Obtener una lista con todos los usuarios", summary="Obtener una lista con todos los usuarios")
|
||||
async def get_users(user: User = Depends(get_current_user)) -> list[pUser]:
|
||||
with Session(engine) as session:
|
||||
statement = select(User)
|
||||
results = session.exec(statement).all()
|
||||
return [user.to_dto() for user in results]
|
||||
|
||||
@router.get("/{user_id}")
|
||||
async def get_user(user_id) -> pUser:
|
||||
@router.get("/{user_id}",
|
||||
description="Obtener un usuario en especifico en base a su ID",
|
||||
summary = "Obtiene un usuario")
|
||||
async def get_user(user_id, user: User = Depends(get_current_user)) -> pUser:
|
||||
with Session(engine) as session:
|
||||
statement = select(User).where(User.id == user_id)
|
||||
results = session.exec(statement).first()
|
||||
|
||||
Reference in New Issue
Block a user