files-server-fastapi 0.1.44__tar.gz → 0.1.45__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/PKG-INFO +1 -1
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/pyproject.toml +1 -1
- files_server_fastapi-0.1.45/src/files_server_fastapi/dependencies/__init__.py +13 -0
- files_server_fastapi-0.1.45/src/files_server_fastapi/dependencies/user_dependencies.py +183 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/acls_router.py +3 -2
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/delete_router.py +11 -6
- files_server_fastapi-0.1.45/src/files_server_fastapi/models/rol_model.py +26 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/models/users_extend_model.py +14 -4
- files_server_fastapi-0.1.45/src/files_server_fastapi/routers/rol_router.py +90 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/samba_router.py +6 -5
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/users_extend_router.py +190 -10
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/uv.lock +1 -1
- files_server_fastapi-0.1.44/src/files_server_fastapi/models/rol_model.py +0 -13
- files_server_fastapi-0.1.44/src/files_server_fastapi/routers/rol_router.py +0 -45
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/.gitignore +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/.python-version +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/LICENSE +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/MANIFEST.in +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/README.md +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/__init__.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/__init__.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/constants.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/create_file_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/dependencies.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/download_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/folder_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/list_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/onlyoffice_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/open_url_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/path_utils.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/search_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/stats_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/tree_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/upload_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/files/view_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/models/__init__.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/models/area_model.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/models/permisos_model.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/models/rutas_model.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/__init__.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/area_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/files_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/permisos_router.py +0 -0
- {files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/routers/rutas_router.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: files-server-fastapi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.45
|
|
4
4
|
Summary: Este repositorio es un paquete python desarrollado con FastAPI, gestiona la comunicación con un servidor Samba y utiliza PostgreSQL para administrar rutas de archivos y el control de acceso basado en roles (RBAC) de los usuarios.
|
|
5
5
|
Project-URL: Homepage, https://github.com/AldoBP/files-server-fastapi
|
|
6
6
|
Project-URL: Repository, https://github.com/AldoBP/files-server-fastapi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "files-server-fastapi"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.45"
|
|
4
4
|
description = "Este repositorio es un paquete python desarrollado con FastAPI, gestiona la comunicación con un servidor Samba y utiliza PostgreSQL para administrar rutas de archivos y el control de acceso basado en roles (RBAC) de los usuarios."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from files_server_fastapi.dependencies.user_dependencies import (
|
|
2
|
+
get_active_user,
|
|
3
|
+
get_current_user_ext,
|
|
4
|
+
require_superadmin,
|
|
5
|
+
require_area_admin_or_superadmin,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"get_active_user",
|
|
10
|
+
"get_current_user_ext",
|
|
11
|
+
"require_superadmin",
|
|
12
|
+
"require_area_admin_or_superadmin",
|
|
13
|
+
]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dependencies/user_dependencies.py
|
|
3
|
+
==================================
|
|
4
|
+
Dependencies de autenticación y autorización propias del proyecto.
|
|
5
|
+
|
|
6
|
+
Propósito:
|
|
7
|
+
El paquete oauth2fast-fastapi maneja la autenticación base (token JWT),
|
|
8
|
+
pero no conoce el concepto de "usuario dado de baja" ni el sistema de
|
|
9
|
+
niveles de privilegio de este proyecto.
|
|
10
|
+
|
|
11
|
+
Este módulo extiende esa lógica con:
|
|
12
|
+
|
|
13
|
+
- get_active_user: verifica que el usuario NO esté dado de baja.
|
|
14
|
+
- require_superadmin: verifica privilege_level >= 2.
|
|
15
|
+
- require_area_admin_or_superadmin: verifica privilege_level >= 1.
|
|
16
|
+
|
|
17
|
+
Niveles de privilegio (tabla rol.privilege_level):
|
|
18
|
+
0 → Usuario regular (sin permisos de gestión)
|
|
19
|
+
1 → Admin de Área (gestión de su propia área)
|
|
20
|
+
2 → Superadmin/Sistemas (acceso total)
|
|
21
|
+
|
|
22
|
+
¿Por qué privilege_level en vez de comparar nombres de rol?
|
|
23
|
+
Los nombres de rol pueden cambiar ("admin" → "lider", "jefe", etc.).
|
|
24
|
+
El privilege_level es un contrato estable: el código solo lee el número,
|
|
25
|
+
no el texto. Renombrar un rol en la DB nunca rompe la autorización.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from datetime import datetime, timezone
|
|
29
|
+
|
|
30
|
+
from fastapi import Depends, HTTPException, status
|
|
31
|
+
from sqlalchemy import select
|
|
32
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
33
|
+
|
|
34
|
+
from oauth2fast_fastapi import User, get_current_verified_user
|
|
35
|
+
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
36
|
+
|
|
37
|
+
from files_server_fastapi.models.rol_model import Rol
|
|
38
|
+
from files_server_fastapi.models.users_extend_model import Users_extend
|
|
39
|
+
|
|
40
|
+
# ── Constantes de nivel de privilegio ────────────────────────────────────────
|
|
41
|
+
# Cambia estos valores SOLO si redefiniste la escala en tu tabla rol.
|
|
42
|
+
PRIVILEGE_USER = 0 # Usuario regular
|
|
43
|
+
PRIVILEGE_AREA_ADMIN = 1 # Admin de Área
|
|
44
|
+
PRIVILEGE_SUPERADMIN = 2 # Sistemas / Superadmin
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ── Helper interno ────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
async def _get_privilege_level(rol_id: int, db: AsyncSession) -> int:
|
|
50
|
+
"""
|
|
51
|
+
Obtiene el privilege_level del rol desde la DB.
|
|
52
|
+
Retorna PRIVILEGE_USER (0) si el rol no existe.
|
|
53
|
+
"""
|
|
54
|
+
result = await db.execute(select(Rol).where(Rol.id == rol_id))
|
|
55
|
+
rol = result.scalars().first()
|
|
56
|
+
return rol.privilege_level if rol else PRIVILEGE_USER
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ── Dependency principal ──────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
async def get_active_user(
|
|
62
|
+
current_user: User = Depends(get_current_verified_user),
|
|
63
|
+
db: AsyncSession = Depends(get_db_session),
|
|
64
|
+
) -> User:
|
|
65
|
+
"""
|
|
66
|
+
Extiende get_current_verified_user con verificación de soft delete.
|
|
67
|
+
|
|
68
|
+
Flujo:
|
|
69
|
+
1. get_current_verified_user valida el token JWT y que is_verified=True.
|
|
70
|
+
2. Esta dependency verifica que deleted_at IS NULL en users_extend.
|
|
71
|
+
|
|
72
|
+
Lanza:
|
|
73
|
+
HTTP 403 si el usuario tiene deleted_at con fecha/hora de baja.
|
|
74
|
+
"""
|
|
75
|
+
result = await db.execute(
|
|
76
|
+
select(Users_extend).where(Users_extend.user_id == current_user.id)
|
|
77
|
+
)
|
|
78
|
+
ext = result.scalars().first()
|
|
79
|
+
|
|
80
|
+
if ext is not None and ext.deleted_at is not None:
|
|
81
|
+
raise HTTPException(
|
|
82
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
83
|
+
detail=(
|
|
84
|
+
"Tu cuenta ha sido dada de baja del sistema. "
|
|
85
|
+
"Contacta a sistemas para más información."
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
return current_user
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ── Helper: extensión del usuario actual ──────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
async def get_current_user_ext(
|
|
94
|
+
current_user: User = Depends(get_active_user),
|
|
95
|
+
db: AsyncSession = Depends(get_db_session),
|
|
96
|
+
) -> Users_extend:
|
|
97
|
+
"""
|
|
98
|
+
Retorna el registro Users_extend del usuario autenticado y activo.
|
|
99
|
+
Lanza 404 si el usuario no tiene extensión registrada.
|
|
100
|
+
"""
|
|
101
|
+
result = await db.execute(
|
|
102
|
+
select(Users_extend).where(Users_extend.user_id == current_user.id)
|
|
103
|
+
)
|
|
104
|
+
ext = result.scalars().first()
|
|
105
|
+
if not ext:
|
|
106
|
+
raise HTTPException(
|
|
107
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
108
|
+
detail="El usuario no tiene datos de extensión registrados.",
|
|
109
|
+
)
|
|
110
|
+
return ext
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ── Verificadores de nivel de privilegio ──────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
async def require_superadmin(
|
|
116
|
+
current_user: User = Depends(get_active_user),
|
|
117
|
+
db: AsyncSession = Depends(get_db_session),
|
|
118
|
+
) -> tuple[User, Users_extend]:
|
|
119
|
+
"""
|
|
120
|
+
Verifica que el usuario tenga privilege_level >= 2 (Superadmin/Sistemas).
|
|
121
|
+
|
|
122
|
+
Retorna:
|
|
123
|
+
(User, Users_extend) para uso en el endpoint.
|
|
124
|
+
|
|
125
|
+
Lanza:
|
|
126
|
+
HTTP 403 si el nivel de privilegio es insuficiente.
|
|
127
|
+
"""
|
|
128
|
+
result = await db.execute(
|
|
129
|
+
select(Users_extend).where(Users_extend.user_id == current_user.id)
|
|
130
|
+
)
|
|
131
|
+
ext = result.scalars().first()
|
|
132
|
+
|
|
133
|
+
if not ext:
|
|
134
|
+
raise HTTPException(
|
|
135
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
136
|
+
detail="No tienes permisos suficientes para esta acción.",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
level = await _get_privilege_level(ext.rol_id, db)
|
|
140
|
+
if level < PRIVILEGE_SUPERADMIN:
|
|
141
|
+
raise HTTPException(
|
|
142
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
143
|
+
detail="Solo Sistemas/Superadmin puede ejecutar esta acción.",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return current_user, ext
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def require_area_admin_or_superadmin(
|
|
150
|
+
current_user: User = Depends(get_active_user),
|
|
151
|
+
db: AsyncSession = Depends(get_db_session),
|
|
152
|
+
) -> tuple[User, Users_extend, bool]:
|
|
153
|
+
"""
|
|
154
|
+
Verifica que el usuario tenga privilege_level >= 1 (Admin de Área o superior).
|
|
155
|
+
|
|
156
|
+
Retorna:
|
|
157
|
+
(User, Users_extend, is_superadmin: bool)
|
|
158
|
+
El booleano permite al endpoint saber si tiene permisos totales.
|
|
159
|
+
|
|
160
|
+
Lanza:
|
|
161
|
+
HTTP 403 si el nivel de privilegio es insuficiente.
|
|
162
|
+
"""
|
|
163
|
+
result = await db.execute(
|
|
164
|
+
select(Users_extend).where(Users_extend.user_id == current_user.id)
|
|
165
|
+
)
|
|
166
|
+
ext = result.scalars().first()
|
|
167
|
+
|
|
168
|
+
if not ext:
|
|
169
|
+
raise HTTPException(
|
|
170
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
171
|
+
detail="No tienes permisos suficientes para esta acción.",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
level = await _get_privilege_level(ext.rol_id, db)
|
|
175
|
+
if level < PRIVILEGE_AREA_ADMIN:
|
|
176
|
+
raise HTTPException(
|
|
177
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
178
|
+
detail="Se requiere nivel de Admin de Área o superior.",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
is_superadmin = level >= PRIVILEGE_SUPERADMIN
|
|
182
|
+
return current_user, ext, is_superadmin
|
|
183
|
+
|
|
@@ -267,9 +267,10 @@ async def get_specific_user_acls(
|
|
|
267
267
|
Acepta el auth user_id directamente (el mismo que esta en user_ruta_access.user_id).
|
|
268
268
|
Por compatibilidad tambien acepta users_extend.id si el user_id no se encuentra como auth user_id.
|
|
269
269
|
|
|
270
|
-
**Requiere rol SUPER_ADMIN o AREA_ADMIN.**
|
|
270
|
+
**Requiere rol SUPER_ADMIN o AREA_ADMIN (excepto si el usuario consulta sus propios permisos).**
|
|
271
271
|
"""
|
|
272
|
-
|
|
272
|
+
if current_user.id != user_id:
|
|
273
|
+
await _require_admin_role(current_user, db)
|
|
273
274
|
ext_result = await db.execute(
|
|
274
275
|
select(Users_extend).where(Users_extend.user_id == user_id)
|
|
275
276
|
)
|
|
@@ -3,7 +3,7 @@ import shutil
|
|
|
3
3
|
from fastapi import APIRouter, HTTPException, Depends
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
-
from sqlalchemy import select, delete as sql_delete
|
|
6
|
+
from sqlalchemy import select, delete as sql_delete, or_
|
|
7
7
|
|
|
8
8
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
9
9
|
from oauth2fast_fastapi import get_current_verified_user, User
|
|
@@ -17,7 +17,7 @@ router = APIRouter()
|
|
|
17
17
|
class DeleteRequest(BaseModel):
|
|
18
18
|
area: str
|
|
19
19
|
subpath: str = "/"
|
|
20
|
-
|
|
20
|
+
filename: str # nombre del archivo o carpeta a eliminar
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@router.delete("/delete", summary="Eliminar un archivo o carpeta del servidor")
|
|
@@ -35,8 +35,8 @@ async def delete_item(
|
|
|
35
35
|
- La base de datos se limpia **solo si** la eliminación física tuvo éxito, evitando registros huérfanos.
|
|
36
36
|
"""
|
|
37
37
|
# ── 1. Validar rutas ANTES de cualquier lógica de negocio ─────────────────
|
|
38
|
-
# req.
|
|
39
|
-
if ".." in req.subpath or ".." in req.
|
|
38
|
+
# req.filename nunca debe contener separadores de directorio ni secuencias de traversal.
|
|
39
|
+
if ".." in req.subpath or ".." in req.filename or "/" in req.filename:
|
|
40
40
|
raise HTTPException(status_code=400, detail="Ruta o nombre inválido")
|
|
41
41
|
|
|
42
42
|
# ── 2. Verificar permiso de eliminación ───────────────────────────────────
|
|
@@ -49,7 +49,7 @@ async def delete_item(
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
# ── 3. Construir rutas ────────────────────────────────────────────────────
|
|
52
|
-
safe_name = os.path.basename(req.
|
|
52
|
+
safe_name = os.path.basename(req.filename)
|
|
53
53
|
safe_subpath = req.subpath.strip("/")
|
|
54
54
|
ruta_real = (
|
|
55
55
|
os.path.join(BASE_DIR, req.area.upper(), safe_subpath, safe_name)
|
|
@@ -85,7 +85,12 @@ async def delete_item(
|
|
|
85
85
|
# Para archivos regulares no suele haber entrada en Rutas, pero el DELETE
|
|
86
86
|
# es seguro (no borra nada si no hay coincidencia).
|
|
87
87
|
await db.execute(
|
|
88
|
-
sql_delete(Rutas).where(
|
|
88
|
+
sql_delete(Rutas).where(
|
|
89
|
+
or_(
|
|
90
|
+
Rutas.ruta == logical_prefix,
|
|
91
|
+
Rutas.ruta.like(f"{logical_prefix}/%")
|
|
92
|
+
)
|
|
93
|
+
)
|
|
89
94
|
)
|
|
90
95
|
await db.commit()
|
|
91
96
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from sqlmodel import Field
|
|
4
|
+
from oauth2fast_fastapi import AuthModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_utc_now():
|
|
8
|
+
return datetime.now(timezone.utc)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Rol(AuthModel, table=True):
|
|
12
|
+
__tablename__ = "rol"
|
|
13
|
+
|
|
14
|
+
role_name: str = Field(nullable=False)
|
|
15
|
+
description: Optional[str] = Field(default=None)
|
|
16
|
+
|
|
17
|
+
# Nivel de privilegio del rol. Desacopla la autorización del nombre del rol:
|
|
18
|
+
# el nombre puede cambiar libremente sin afectar la lógica del sistema.
|
|
19
|
+
#
|
|
20
|
+
# 0 → Usuario regular (sin permisos de gestión)
|
|
21
|
+
# 1 → Admin de Área (gestión de su propia área)
|
|
22
|
+
# 2 → Superadmin/Sistemas (acceso total)
|
|
23
|
+
#
|
|
24
|
+
# Para agregar nuevos niveles en el futuro, solo actualiza este campo en la DB.
|
|
25
|
+
privilege_level: int = Field(default=0, nullable=False)
|
|
26
|
+
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from sqlmodel import Field
|
|
3
4
|
from oauth2fast_fastapi import AuthModel
|
|
4
5
|
|
|
6
|
+
|
|
5
7
|
class Users_extend(AuthModel, table=True):
|
|
6
8
|
__tablename__ = "users_extend"
|
|
7
|
-
|
|
9
|
+
|
|
8
10
|
# Es Foreign Key al usuario (Relación 1 a 1)
|
|
9
11
|
user_id: int = Field(foreign_key="users.id", unique=True)
|
|
10
|
-
|
|
12
|
+
|
|
11
13
|
area_id: int = Field(foreign_key="area.id")
|
|
12
14
|
rol_id: int = Field(foreign_key="rol.id")
|
|
13
15
|
puesto: Optional[str] = Field(default=None)
|
|
14
|
-
|
|
16
|
+
|
|
15
17
|
# Contraseña en texto plano para sincronización con el sistema de archivos centralizado de red
|
|
16
18
|
samba_password: Optional[str] = Field(default=None)
|
|
17
|
-
|
|
19
|
+
|
|
18
20
|
# Indica si el usuario tiene acceso Samba activado manualmente por el administrador.
|
|
19
21
|
# Cuando es True, los permisos web se replican automáticamente a Linux ACLs / Samba.
|
|
20
22
|
samba_enabled: bool = Field(default=False)
|
|
23
|
+
|
|
24
|
+
# ── Soft Delete ───────────────────────────────────────────────────────────
|
|
25
|
+
# NULL → usuario activo
|
|
26
|
+
# NOT NULL → usuario dado de baja (fecha/hora exacta de la baja)
|
|
27
|
+
deleted_at: Optional[datetime] = Field(default=None)
|
|
28
|
+
|
|
29
|
+
# ID del usuario (admin/sistemas) que ejecutó la baja. NULL si está activo.
|
|
30
|
+
deleted_by: Optional[int] = Field(default=None, foreign_key="users.id")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
2
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
3
|
+
from sqlalchemy import select
|
|
4
|
+
|
|
5
|
+
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
6
|
+
from files_server_fastapi.models.rol_model import Rol
|
|
7
|
+
from files_server_fastapi.dependencies.user_dependencies import require_superadmin
|
|
8
|
+
|
|
9
|
+
router = APIRouter(prefix="/roles", tags=["Gestión de Roles"])
|
|
10
|
+
|
|
11
|
+
@router.post("/", response_model=Rol, summary="Crear un nuevo Rol")
|
|
12
|
+
async def create_rol(
|
|
13
|
+
rol: Rol,
|
|
14
|
+
auth: tuple = Depends(require_superadmin),
|
|
15
|
+
db: AsyncSession = Depends(get_db_session)
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Crea un nuevo rol en el sistema.
|
|
19
|
+
Solo accesible por Superadmin/Sistemas.
|
|
20
|
+
|
|
21
|
+
Puedes enviar 'privilege_level' en el cuerpo de la petición:
|
|
22
|
+
0 = Usuario regular
|
|
23
|
+
1 = Admin de Área
|
|
24
|
+
2 = Superadmin/Sistemas
|
|
25
|
+
"""
|
|
26
|
+
db.add(rol)
|
|
27
|
+
await db.commit()
|
|
28
|
+
await db.refresh(rol)
|
|
29
|
+
return rol
|
|
30
|
+
|
|
31
|
+
@router.get("/", response_model=list[Rol], summary="Obtener todos los Roles")
|
|
32
|
+
async def get_roles(
|
|
33
|
+
db: AsyncSession = Depends(get_db_session)
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Obtiene la lista de todos los roles.
|
|
37
|
+
Público para lectura (necesario para formularios de registro/edición).
|
|
38
|
+
"""
|
|
39
|
+
result = await db.execute(select(Rol))
|
|
40
|
+
return result.scalars().all()
|
|
41
|
+
|
|
42
|
+
@router.put("/{rol_id}", response_model=Rol, summary="Editar un Rol")
|
|
43
|
+
async def update_rol(
|
|
44
|
+
rol_id: int,
|
|
45
|
+
rol_data: Rol,
|
|
46
|
+
auth: tuple = Depends(require_superadmin),
|
|
47
|
+
db: AsyncSession = Depends(get_db_session)
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Actualiza un rol existente, incluyendo su nivel de privilegio.
|
|
51
|
+
Solo accesible por Superadmin/Sistemas.
|
|
52
|
+
"""
|
|
53
|
+
result = await db.execute(select(Rol).where(Rol.id == rol_id))
|
|
54
|
+
db_rol = result.scalars().first()
|
|
55
|
+
if not db_rol:
|
|
56
|
+
raise HTTPException(
|
|
57
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
58
|
+
detail="Rol no encontrado"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
db_rol.role_name = rol_data.role_name
|
|
62
|
+
db_rol.description = rol_data.description
|
|
63
|
+
db_rol.privilege_level = rol_data.privilege_level # <-- Ahora sí se actualiza desde el Dashboard
|
|
64
|
+
|
|
65
|
+
await db.commit()
|
|
66
|
+
await db.refresh(db_rol)
|
|
67
|
+
return db_rol
|
|
68
|
+
|
|
69
|
+
@router.delete("/{rol_id}", summary="Eliminar un Rol")
|
|
70
|
+
async def delete_rol(
|
|
71
|
+
rol_id: int,
|
|
72
|
+
auth: tuple = Depends(require_superadmin),
|
|
73
|
+
db: AsyncSession = Depends(get_db_session)
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
Elimina un rol.
|
|
77
|
+
Solo accesible por Superadmin/Sistemas.
|
|
78
|
+
"""
|
|
79
|
+
result = await db.execute(select(Rol).where(Rol.id == rol_id))
|
|
80
|
+
db_rol = result.scalars().first()
|
|
81
|
+
if not db_rol:
|
|
82
|
+
raise HTTPException(
|
|
83
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
84
|
+
detail="Rol no encontrado"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
await db.delete(db_rol)
|
|
88
|
+
await db.commit()
|
|
89
|
+
return {"message": "Rol eliminado correctamente"}
|
|
90
|
+
|
|
@@ -30,7 +30,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
30
30
|
from sqlalchemy import select
|
|
31
31
|
|
|
32
32
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
33
|
-
from oauth2fast_fastapi import
|
|
33
|
+
from oauth2fast_fastapi import User
|
|
34
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
34
35
|
from files_server_fastapi.models.users_extend_model import Users_extend
|
|
35
36
|
from files_server_fastapi.models.permisos_model import User_Ruta_Access
|
|
36
37
|
from files_server_fastapi.models.rutas_model import Rutas
|
|
@@ -170,7 +171,7 @@ class SambaActivateRequest(BaseModel):
|
|
|
170
171
|
async def activate_samba(
|
|
171
172
|
user_ext_id: int,
|
|
172
173
|
req: SambaActivateRequest = SambaActivateRequest(),
|
|
173
|
-
current_user: User = Depends(
|
|
174
|
+
current_user: User = Depends(get_active_user),
|
|
174
175
|
db: AsyncSession = Depends(get_db_session),
|
|
175
176
|
):
|
|
176
177
|
"""
|
|
@@ -225,7 +226,7 @@ async def activate_samba(
|
|
|
225
226
|
)
|
|
226
227
|
async def deactivate_samba(
|
|
227
228
|
user_ext_id: int,
|
|
228
|
-
current_user: User = Depends(
|
|
229
|
+
current_user: User = Depends(get_active_user),
|
|
229
230
|
db: AsyncSession = Depends(get_db_session),
|
|
230
231
|
):
|
|
231
232
|
"""
|
|
@@ -275,7 +276,7 @@ async def deactivate_samba(
|
|
|
275
276
|
)
|
|
276
277
|
async def get_samba_status(
|
|
277
278
|
user_ext_id: int,
|
|
278
|
-
current_user: User = Depends(
|
|
279
|
+
current_user: User = Depends(get_active_user),
|
|
279
280
|
db: AsyncSession = Depends(get_db_session),
|
|
280
281
|
):
|
|
281
282
|
"""
|
|
@@ -320,7 +321,7 @@ async def get_samba_status(
|
|
|
320
321
|
)
|
|
321
322
|
async def sync_samba(
|
|
322
323
|
user_ext_id: int,
|
|
323
|
-
current_user: User = Depends(
|
|
324
|
+
current_user: User = Depends(get_active_user),
|
|
324
325
|
db: AsyncSession = Depends(get_db_session),
|
|
325
326
|
):
|
|
326
327
|
"""
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
1
4
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
2
|
-
from
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
3
6
|
from sqlalchemy import select
|
|
4
|
-
from
|
|
5
|
-
from pydantic import BaseModel
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
8
|
|
|
9
|
+
from oauth2fast_fastapi import User
|
|
7
10
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
8
|
-
|
|
11
|
+
|
|
12
|
+
from files_server_fastapi.dependencies.user_dependencies import (
|
|
13
|
+
get_active_user,
|
|
14
|
+
require_area_admin_or_superadmin,
|
|
15
|
+
require_superadmin,
|
|
16
|
+
)
|
|
9
17
|
from files_server_fastapi.models.area_model import Area
|
|
10
18
|
from files_server_fastapi.models.rol_model import Rol
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, Field, model_validator
|
|
19
|
+
from files_server_fastapi.models.users_extend_model import Users_extend
|
|
14
20
|
|
|
15
21
|
class UserExtendCreate(BaseModel):
|
|
16
22
|
user_id: int
|
|
@@ -32,9 +38,11 @@ class UserExtendResponse(BaseModel):
|
|
|
32
38
|
user_id: int
|
|
33
39
|
area_id: int
|
|
34
40
|
rol_id: int
|
|
35
|
-
role_id: int #
|
|
41
|
+
role_id: int # Alias virtual para el frontend
|
|
36
42
|
puesto: Optional[str] = None
|
|
37
|
-
|
|
43
|
+
deleted_at: Optional[datetime] = None
|
|
44
|
+
deleted_by: Optional[int] = None
|
|
45
|
+
|
|
38
46
|
class Config:
|
|
39
47
|
from_attributes = True
|
|
40
48
|
|
|
@@ -206,12 +214,13 @@ async def update_user_extend(user_id: int, user_ext_update: UserExtendUpdate, db
|
|
|
206
214
|
|
|
207
215
|
|
|
208
216
|
# ==========================================
|
|
209
|
-
# DELETE /users-extend/{user_id} — Eliminar
|
|
217
|
+
# DELETE /users-extend/{user_id} — Eliminar (Hard Delete - solo estructura)
|
|
210
218
|
# ==========================================
|
|
211
219
|
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Eliminar extensión de usuario")
|
|
212
220
|
async def delete_user_extend(user_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
213
221
|
"""
|
|
214
222
|
Elimina el vínculo de área y rol de un usuario.
|
|
223
|
+
NOTA: Para dar de baja a un usuario sin eliminar sus datos, usa DELETE /{user_id}/deactivate.
|
|
215
224
|
"""
|
|
216
225
|
result = await db.execute(select(Users_extend).where(Users_extend.user_id == user_id))
|
|
217
226
|
user_ext = result.scalars().first()
|
|
@@ -224,3 +233,174 @@ async def delete_user_extend(user_id: int, db: AsyncSession = Depends(get_db_ses
|
|
|
224
233
|
|
|
225
234
|
await db.delete(user_ext)
|
|
226
235
|
await db.commit()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ==========================================
|
|
239
|
+
# DELETE /users-extend/{user_id}/deactivate — Soft Delete (Baja Lógica)
|
|
240
|
+
# ==========================================
|
|
241
|
+
@router.delete(
|
|
242
|
+
"/{user_id}/deactivate",
|
|
243
|
+
status_code=status.HTTP_200_OK,
|
|
244
|
+
summary="Dar de baja a un usuario (Borrado Lógico)",
|
|
245
|
+
)
|
|
246
|
+
async def deactivate_user(
|
|
247
|
+
user_id: int,
|
|
248
|
+
auth: tuple = Depends(require_area_admin_or_superadmin),
|
|
249
|
+
db: AsyncSession = Depends(get_db_session),
|
|
250
|
+
):
|
|
251
|
+
"""
|
|
252
|
+
Da de baja lógica a un usuario. El usuario no podrá hacer login ni usar
|
|
253
|
+
ningún endpoint del sistema. Sus datos se conservan para auditoría.
|
|
254
|
+
|
|
255
|
+
Reglas de autorización:
|
|
256
|
+
- Superadmin/Sistemas: puede dar de baja a cualquier usuario.
|
|
257
|
+
- Admin de Área: solo puede dar de baja a usuarios de su misma área,
|
|
258
|
+
y no puede dar de baja a otros admins de área.
|
|
259
|
+
|
|
260
|
+
El trigger PostgreSQL automáticamente:
|
|
261
|
+
- Pone is_verified = FALSE en la tabla users (bloquea el login).
|
|
262
|
+
- Pone samba_enabled = FALSE en users_extend.
|
|
263
|
+
"""
|
|
264
|
+
current_user, executor_ext, is_superadmin = auth
|
|
265
|
+
|
|
266
|
+
# Buscar al usuario objetivo
|
|
267
|
+
result = await db.execute(
|
|
268
|
+
select(Users_extend).where(Users_extend.user_id == user_id)
|
|
269
|
+
)
|
|
270
|
+
target_ext = result.scalars().first()
|
|
271
|
+
|
|
272
|
+
if not target_ext:
|
|
273
|
+
raise HTTPException(
|
|
274
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
275
|
+
detail=f"Usuario con ID {user_id} no encontrado.",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# No puede darse de baja a sí mismo
|
|
279
|
+
if user_id == current_user.id:
|
|
280
|
+
raise HTTPException(
|
|
281
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
282
|
+
detail="No puedes darte de baja a ti mismo.",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Verificar si ya está dado de baja
|
|
286
|
+
if target_ext.deleted_at is not None:
|
|
287
|
+
raise HTTPException(
|
|
288
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
289
|
+
detail=f"El usuario con ID {user_id} ya está dado de baja.",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if not is_superadmin:
|
|
293
|
+
# Admin de área: solo puede dar de baja a usuarios de su misma área
|
|
294
|
+
if target_ext.area_id != executor_ext.area_id:
|
|
295
|
+
raise HTTPException(
|
|
296
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
297
|
+
detail="Solo puedes dar de baja a usuarios de tu propia área.",
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Admin de área: no puede dar de baja a otros admins de área ni superadmins
|
|
301
|
+
from files_server_fastapi.dependencies.user_dependencies import (
|
|
302
|
+
_get_privilege_level,
|
|
303
|
+
PRIVILEGE_AREA_ADMIN,
|
|
304
|
+
)
|
|
305
|
+
target_level = await _get_privilege_level(target_ext.rol_id, db)
|
|
306
|
+
if target_level >= PRIVILEGE_AREA_ADMIN:
|
|
307
|
+
raise HTTPException(
|
|
308
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
309
|
+
detail="No puedes dar de baja a un Admin de Área o Superadmin. Contacta a Sistemas.",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Ejecutar el soft delete
|
|
313
|
+
target_ext.deleted_at = datetime.now(timezone.utc)
|
|
314
|
+
target_ext.deleted_by = current_user.id
|
|
315
|
+
db.add(target_ext)
|
|
316
|
+
await db.commit()
|
|
317
|
+
await db.refresh(target_ext)
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
"message": f"Usuario con ID {user_id} dado de baja correctamente.",
|
|
321
|
+
"deleted_at": target_ext.deleted_at.isoformat(),
|
|
322
|
+
"deleted_by": current_user.id,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# ==========================================
|
|
327
|
+
# POST /users-extend/{user_id}/reactivate — Reactivar usuario
|
|
328
|
+
# ==========================================
|
|
329
|
+
@router.post(
|
|
330
|
+
"/{user_id}/reactivate",
|
|
331
|
+
status_code=status.HTTP_200_OK,
|
|
332
|
+
summary="Reactivar un usuario dado de baja (solo Sistemas/Superadmin)",
|
|
333
|
+
)
|
|
334
|
+
async def reactivate_user(
|
|
335
|
+
user_id: int,
|
|
336
|
+
auth: tuple = Depends(require_superadmin),
|
|
337
|
+
db: AsyncSession = Depends(get_db_session),
|
|
338
|
+
):
|
|
339
|
+
"""
|
|
340
|
+
Reactiva a un usuario previamente dado de baja.
|
|
341
|
+
Solo Sistemas/Superadmin puede ejecutar esta acción.
|
|
342
|
+
|
|
343
|
+
El trigger PostgreSQL automáticamente restaura is_verified = TRUE
|
|
344
|
+
en la tabla users, permitiendo que el usuario vuelva a hacer login.
|
|
345
|
+
"""
|
|
346
|
+
result = await db.execute(
|
|
347
|
+
select(Users_extend).where(Users_extend.user_id == user_id)
|
|
348
|
+
)
|
|
349
|
+
target_ext = result.scalars().first()
|
|
350
|
+
|
|
351
|
+
if not target_ext:
|
|
352
|
+
raise HTTPException(
|
|
353
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
354
|
+
detail=f"Usuario con ID {user_id} no encontrado.",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if target_ext.deleted_at is None:
|
|
358
|
+
raise HTTPException(
|
|
359
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
360
|
+
detail=f"El usuario con ID {user_id} no está dado de baja.",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
target_ext.deleted_at = None
|
|
364
|
+
target_ext.deleted_by = None
|
|
365
|
+
db.add(target_ext)
|
|
366
|
+
await db.commit()
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
"message": f"Usuario con ID {user_id} reactivado correctamente.",
|
|
370
|
+
"user_id": user_id,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# ==========================================
|
|
375
|
+
# GET /users-extend/inactive — Listar usuarios dados de baja (Auditoría)
|
|
376
|
+
# ==========================================
|
|
377
|
+
@router.get(
|
|
378
|
+
"/inactive",
|
|
379
|
+
status_code=status.HTTP_200_OK,
|
|
380
|
+
summary="Listar usuarios dados de baja (Auditoría — solo Sistemas/Superadmin)",
|
|
381
|
+
)
|
|
382
|
+
async def list_inactive_users(
|
|
383
|
+
auth: tuple = Depends(require_superadmin),
|
|
384
|
+
db: AsyncSession = Depends(get_db_session),
|
|
385
|
+
):
|
|
386
|
+
"""
|
|
387
|
+
Retorna todos los usuarios dados de baja con su fecha y responsable de baja.
|
|
388
|
+
Solo accesible por Sistemas/Superadmin. Útil para auditoría y trazabilidad.
|
|
389
|
+
"""
|
|
390
|
+
result = await db.execute(
|
|
391
|
+
select(Users_extend).where(Users_extend.deleted_at.is_not(None))
|
|
392
|
+
)
|
|
393
|
+
inactive_users = result.scalars().all()
|
|
394
|
+
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
"id": u.id,
|
|
398
|
+
"user_id": u.user_id,
|
|
399
|
+
"area_id": u.area_id,
|
|
400
|
+
"rol_id": u.rol_id,
|
|
401
|
+
"puesto": u.puesto,
|
|
402
|
+
"deleted_at": u.deleted_at.isoformat() if u.deleted_at else None,
|
|
403
|
+
"deleted_by": u.deleted_by,
|
|
404
|
+
}
|
|
405
|
+
for u in inactive_users
|
|
406
|
+
]
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
from sqlmodel import Field
|
|
4
|
-
from oauth2fast_fastapi import AuthModel
|
|
5
|
-
|
|
6
|
-
def get_utc_now():
|
|
7
|
-
return datetime.now(timezone.utc)
|
|
8
|
-
|
|
9
|
-
class Rol(AuthModel, table=True):
|
|
10
|
-
__tablename__ = "rol"
|
|
11
|
-
|
|
12
|
-
role_name: str = Field(nullable=False)
|
|
13
|
-
description: Optional[str] = Field(default=None)
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
from fastapi import APIRouter, Depends
|
|
2
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
3
|
-
from sqlalchemy import select
|
|
4
|
-
|
|
5
|
-
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
6
|
-
from files_server_fastapi.models.rol_model import Rol
|
|
7
|
-
|
|
8
|
-
router = APIRouter(prefix="/roles", tags=["Gestión de Roles"])
|
|
9
|
-
|
|
10
|
-
@router.post("/", response_model=Rol, summary="Crear un nuevo Rol")
|
|
11
|
-
async def create_rol(rol: Rol, db: AsyncSession = Depends(get_db_session)):
|
|
12
|
-
db.add(rol)
|
|
13
|
-
await db.commit()
|
|
14
|
-
await db.refresh(rol)
|
|
15
|
-
return rol
|
|
16
|
-
|
|
17
|
-
@router.get("/", response_model=list[Rol], summary="Obtener todos los Roles")
|
|
18
|
-
async def get_roles(db: AsyncSession = Depends(get_db_session)):
|
|
19
|
-
result = await db.execute(select(Rol))
|
|
20
|
-
return result.scalars().all()
|
|
21
|
-
|
|
22
|
-
@router.put("/{rol_id}", response_model=Rol, summary="Editar un Rol")
|
|
23
|
-
async def update_rol(rol_id: int, rol_data: Rol, db: AsyncSession = Depends(get_db_session)):
|
|
24
|
-
result = await db.execute(select(Rol).where(Rol.id == rol_id))
|
|
25
|
-
db_rol = result.scalars().first()
|
|
26
|
-
if not db_rol:
|
|
27
|
-
return {"detail": "Rol no encontrado"} # Simplified error handling for now
|
|
28
|
-
|
|
29
|
-
db_rol.role_name = rol_data.role_name
|
|
30
|
-
db_rol.description = rol_data.description
|
|
31
|
-
|
|
32
|
-
await db.commit()
|
|
33
|
-
await db.refresh(db_rol)
|
|
34
|
-
return db_rol
|
|
35
|
-
|
|
36
|
-
@router.delete("/{rol_id}", summary="Eliminar un Rol")
|
|
37
|
-
async def delete_rol(rol_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
38
|
-
result = await db.execute(select(Rol).where(Rol.id == rol_id))
|
|
39
|
-
db_rol = result.scalars().first()
|
|
40
|
-
if not db_rol:
|
|
41
|
-
return {"detail": "Rol no encontrado"}
|
|
42
|
-
|
|
43
|
-
await db.delete(db_rol)
|
|
44
|
-
await db.commit()
|
|
45
|
-
return {"message": "Rol eliminado correctamente"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{files_server_fastapi-0.1.44 → files_server_fastapi-0.1.45}/src/files_server_fastapi/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|