feat: Implement user management functionality with login and CRUD operations
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from src.Usuario.usuario_mmr import UsuarioRepo, Usuario, UsuarioModel
|
||||
from backend.db.conexion import get_conexion
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/usuarios/", response_model=dict)
|
||||
def crear_usuario(nombre: str, email: str, db: Session = Depends(get_conexion)):
|
||||
repo = UsuarioRepo(db)
|
||||
usuario = Usuario(id=None, nombre=nombre, email=email)
|
||||
usuario_id = repo.add(usuario)
|
||||
return {"id": usuario_id}
|
||||
|
||||
@router.get("/usuarios/{usuario_id}", response_model=dict)
|
||||
def obtener_usuario(usuario_id: int, db: Session = Depends(get_conexion)):
|
||||
repo = UsuarioRepo(db)
|
||||
usuario = repo.get_by_id(usuario_id)
|
||||
if not usuario:
|
||||
raise HTTPException(status_code=404, detail="Usuario no encontrado")
|
||||
return {"id": usuario.id, "nombre": usuario.nombre, "email": usuario.email, "activo": usuario.activo}
|
||||
|
||||
@router.get("/usuarios/", response_model=list)
|
||||
def listar_usuarios(db: Session = Depends(get_conexion)):
|
||||
repo = UsuarioRepo(db)
|
||||
usuarios = repo.get_all()
|
||||
return [{"id": u.id, "nombre": u.nombre, "email": u.email, "activo": u.activo} for u in usuarios]
|
||||
|
||||
@router.delete("/usuarios/{usuario_id}", response_model=dict)
|
||||
def eliminar_usuario(usuario_id: int, db: Session = Depends(get_conexion)):
|
||||
repo = UsuarioRepo(db)
|
||||
exito = repo.delete_by_id(usuario_id)
|
||||
if not exito:
|
||||
raise HTTPException(status_code=404, detail="Usuario no encontrado")
|
||||
return {"ok": True}
|
||||
@@ -5,9 +5,11 @@ from backend.domains.experiments import charts_examples_endpoint_v1 as charts
|
||||
from backend.domains.experiments import ping_endpoint_v1
|
||||
from backend.domains.text_manager import text_manager_endpoint_v1
|
||||
from backend.domains.llms import llm_chat_endpoint_v1
|
||||
from backend.domains.usuarios_endpoint_v1 import router as usuarios_router
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(ping_endpoint_v1.router, prefix="/ping")
|
||||
router.include_router(text_manager_endpoint_v1.router, prefix="/text_manager")
|
||||
router.include_router(charts.router, prefix="/charts")
|
||||
router.include_router(llm_chat_endpoint_v1.router, prefix="/llm", tags=["Agente LLM"]) # ← Nuevo router
|
||||
router.include_router(llm_chat_endpoint_v1.router, prefix="/llm", tags=["Agente LLM"])
|
||||
router.include_router(usuarios_router, prefix="/usuarios", tags=["Usuarios"])
|
||||
|
||||
@@ -8,6 +8,7 @@ import { VisualizacionesRandom } from './domains/Experiments/Visualizaciones_Ran
|
||||
import { Camara_noir } from './domains/CamaraNoir/Camaras_noir';
|
||||
import EditorTest from "./domains/TextEditor/Editor_Test";
|
||||
import { ChatPage } from './domains/Llms/Chat/ChatPage';
|
||||
import { LoginPage } from './domains/Usuarios/Login.page';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
|
||||
@@ -69,6 +70,12 @@ const router = createBrowserRouter([
|
||||
},
|
||||
|
||||
|
||||
// Login
|
||||
{
|
||||
path: '/login',
|
||||
element: <LoginPage />,
|
||||
},
|
||||
|
||||
|
||||
// FitzStudio Pages -------------------------------------------------------
|
||||
// Error 404
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useState } from 'react';
|
||||
import { TextInput, PasswordInput, Button, Paper, Title, Container, Group, Alert } from '@mantine/core';
|
||||
import { IconUser, IconLock } from '@tabler/icons-react';
|
||||
|
||||
export function LoginPage() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
// Aquí deberías llamar a tu endpoint de login (ajusta la URL y payload)
|
||||
try {
|
||||
const res = await fetch('/api/v1/usuarios/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
if (!res.ok) throw new Error('Credenciales incorrectas');
|
||||
// Aquí puedes guardar el usuario/token en el estado global o localStorage
|
||||
window.location.href = '/';
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Title align="center" mb={20}>Iniciar sesión</Title>
|
||||
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="tucorreo@ejemplo.com"
|
||||
icon={<IconUser size={18} />}
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
mb={10}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Contraseña"
|
||||
placeholder="Tu contraseña"
|
||||
icon={<IconLock size={18} />}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
mb={20}
|
||||
/>
|
||||
{error && <Alert color="red" mb={10}>{error}</Alert>}
|
||||
<Group mt="md">
|
||||
<Button type="submit" loading={loading} fullWidth>
|
||||
Entrar
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
class Usuario:
|
||||
def __init__(self, id: int, nombre: str, email: str, activo: bool = True):
|
||||
self.id = id
|
||||
self.nombre = nombre
|
||||
self.email = email
|
||||
self.activo = activo
|
||||
|
||||
def activar(self):
|
||||
self.activo = True
|
||||
|
||||
def desactivar(self):
|
||||
self.activo = False
|
||||
|
||||
def __repr__(self):
|
||||
return f"Usuario(id={self.id}, nombre='{self.nombre}', email='{self.email}', activo={self.activo})"
|
||||
@@ -0,0 +1,82 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean
|
||||
from src.ArquitectureLayer.Model import Model_base
|
||||
from src.ArquitectureLayer.Mapper import Mapper_base
|
||||
from src.ArquitectureLayer.Repo import Repo_base
|
||||
from src.Usuario.usuario import Usuario
|
||||
|
||||
# ----------------------
|
||||
# MODELO (SQLAlchemy)
|
||||
# ----------------------
|
||||
|
||||
class UsuarioModel(Model_base):
|
||||
__tablename__ = 'usuarios'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
nombre = Column(String, nullable=False)
|
||||
email = Column(String, unique=True, nullable=False)
|
||||
activo = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
# ----------------------
|
||||
# MAPPER
|
||||
# ----------------------
|
||||
|
||||
class UsuarioMapper(Mapper_base[Usuario, UsuarioModel]):
|
||||
@staticmethod
|
||||
def to_model(obj: Usuario) -> UsuarioModel:
|
||||
return UsuarioModel(
|
||||
id=obj.id,
|
||||
nombre=obj.nombre,
|
||||
email=obj.email,
|
||||
activo=obj.activo
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_model(model: UsuarioModel) -> Usuario:
|
||||
return Usuario(
|
||||
id=model.id,
|
||||
nombre=model.nombre,
|
||||
email=model.email,
|
||||
activo=model.activo
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_dict(obj: Usuario) -> dict:
|
||||
return {
|
||||
'id': obj.id,
|
||||
'nombre': obj.nombre,
|
||||
'email': obj.email,
|
||||
'activo': obj.activo
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict) -> Usuario:
|
||||
return Usuario(
|
||||
id=data['id'],
|
||||
nombre=data['nombre'],
|
||||
email=data['email'],
|
||||
activo=data.get('activo', True)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_model_list(models: list[UsuarioModel]) -> list[Usuario]:
|
||||
return [UsuarioMapper.from_model(m) for m in models]
|
||||
|
||||
# ----------------------
|
||||
# REPO
|
||||
# ----------------------
|
||||
|
||||
class UsuarioRepo(Repo_base[UsuarioModel, Usuario]):
|
||||
def __init__(self, session):
|
||||
super().__init__(
|
||||
session=session,
|
||||
modelo=UsuarioModel,
|
||||
mapper=UsuarioMapper
|
||||
)
|
||||
|
||||
def get_by_email(self, email: str) -> Usuario | None:
|
||||
model = (
|
||||
self.session.query(self.Modelo)
|
||||
.filter_by(email=email, sys_deleted_at=None)
|
||||
.first()
|
||||
)
|
||||
return self.Mapper.from_model(model) if model else None
|
||||
@@ -1,10 +1,5 @@
|
||||
# Próximas tareas
|
||||
|
||||
## Dominio: Usuarios
|
||||
- [ ] Añadir gestión de usuarios (crear, editar, eliminar) (backend)
|
||||
- [ ] Añadir tests a la gestión de usuarios (backend)
|
||||
- [ ] Añadir gestión y visualización del usuario actual en la parte inferior izquierda del AppShell (frontend)
|
||||
- [ ] Añadir descripciones a la gestión de usuarios en `frontend/src/data/` y `backend/router_v1.py`
|
||||
|
||||
## Dominio: Navegación y Menús
|
||||
- [ ] Añadir buscador al AppShell (frontend)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
## Dominio: Usuarios
|
||||
- [x] Añadir gestión de usuarios (crear, editar, eliminar) (backend)
|
||||
- [ ] Añadir tests a la gestión de usuarios (backend)
|
||||
- [ ] Añadir gestión y visualización del usuario actual en la parte inferior izquierda del AppShell (frontend)
|
||||
- [ ] Añadir descripciones a la gestión de usuarios en `frontend/src/data/` y `backend/router_v1.py`
|
||||
Reference in New Issue
Block a user