files-server-fastapi 0.1.45__tar.gz → 0.1.46__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.45 → files_server_fastapi-0.1.46}/PKG-INFO +1 -1
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/pyproject.toml +1 -1
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/acls_router.py +9 -8
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/create_file_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/delete_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/dependencies.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/folder_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/list_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/open_url_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/search_router.py +4 -3
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/upload_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/models/users_extend_model.py +7 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/area_router.py +6 -5
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/permisos_router.py +9 -8
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/rol_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/rutas_router.py +3 -2
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/users_extend_router.py +42 -15
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/uv.lock +1 -1
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/.gitignore +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/.python-version +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/LICENSE +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/MANIFEST.in +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/README.md +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/__init__.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/dependencies/__init__.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/dependencies/user_dependencies.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/__init__.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/constants.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/download_router.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/onlyoffice_router.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/path_utils.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/stats_router.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/tree_router.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/files/view_router.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/models/__init__.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/models/area_model.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/models/permisos_model.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/models/rol_model.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/models/rutas_model.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/__init__.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/files_router.py +0 -0
- {files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/src/files_server_fastapi/routers/samba_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.46
|
|
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.46"
|
|
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" }
|
|
@@ -7,7 +7,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
7
7
|
from sqlalchemy import select, delete, or_
|
|
8
8
|
|
|
9
9
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
10
|
-
from oauth2fast_fastapi import
|
|
10
|
+
from oauth2fast_fastapi import User
|
|
11
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
11
12
|
from files_server_fastapi.files.path_utils import normalize_subpath, build_logical_path
|
|
12
13
|
from files_server_fastapi.models.permisos_model import User_Ruta_Access, Permisos
|
|
13
14
|
from files_server_fastapi.models.rutas_model import Rutas
|
|
@@ -90,7 +91,7 @@ class AclCreate(BaseModel):
|
|
|
90
91
|
@router.post("/acls", summary="Asignar acceso a una carpeta específica (ACL)")
|
|
91
92
|
async def create_acl(
|
|
92
93
|
req: AclCreate,
|
|
93
|
-
current_user: User = Depends(
|
|
94
|
+
current_user: User = Depends(get_active_user),
|
|
94
95
|
db: AsyncSession = Depends(get_db_session)
|
|
95
96
|
):
|
|
96
97
|
"""
|
|
@@ -212,7 +213,7 @@ async def create_acl(
|
|
|
212
213
|
|
|
213
214
|
@router.get("/acls", summary="Obtener las carpetas compartidas del usuario")
|
|
214
215
|
async def get_user_acls(
|
|
215
|
-
current_user: User = Depends(
|
|
216
|
+
current_user: User = Depends(get_active_user),
|
|
216
217
|
db: AsyncSession = Depends(get_db_session)
|
|
217
218
|
):
|
|
218
219
|
"""
|
|
@@ -257,7 +258,7 @@ async def get_user_acls(
|
|
|
257
258
|
@router.get("/acls/user/{user_id}", summary="Obtener los ACLs de un usuario específico")
|
|
258
259
|
async def get_specific_user_acls(
|
|
259
260
|
user_id: int,
|
|
260
|
-
current_user: User = Depends(
|
|
261
|
+
current_user: User = Depends(get_active_user),
|
|
261
262
|
db: AsyncSession = Depends(get_db_session),
|
|
262
263
|
):
|
|
263
264
|
"""
|
|
@@ -321,7 +322,7 @@ async def get_specific_user_acls(
|
|
|
321
322
|
async def initialize_user_acl(
|
|
322
323
|
user_id: int,
|
|
323
324
|
grant_full_area: bool = False,
|
|
324
|
-
current_user: User = Depends(
|
|
325
|
+
current_user: User = Depends(get_active_user),
|
|
325
326
|
db: AsyncSession = Depends(get_db_session),
|
|
326
327
|
):
|
|
327
328
|
"""
|
|
@@ -403,7 +404,7 @@ async def initialize_user_acl(
|
|
|
403
404
|
)
|
|
404
405
|
async def grant_full_area(
|
|
405
406
|
user_id: int,
|
|
406
|
-
current_user: User = Depends(
|
|
407
|
+
current_user: User = Depends(get_active_user),
|
|
407
408
|
db: AsyncSession = Depends(get_db_session),
|
|
408
409
|
):
|
|
409
410
|
"""
|
|
@@ -426,7 +427,7 @@ async def grant_full_area(
|
|
|
426
427
|
)
|
|
427
428
|
async def revoke_full_area(
|
|
428
429
|
user_id: int,
|
|
429
|
-
current_user: User = Depends(
|
|
430
|
+
current_user: User = Depends(get_active_user),
|
|
430
431
|
db: AsyncSession = Depends(get_db_session),
|
|
431
432
|
):
|
|
432
433
|
"""
|
|
@@ -487,7 +488,7 @@ async def revoke_full_area(
|
|
|
487
488
|
async def delete_user_acl(
|
|
488
489
|
user_id: int,
|
|
489
490
|
ruta_id: int,
|
|
490
|
-
current_user: User = Depends(
|
|
491
|
+
current_user: User = Depends(get_active_user),
|
|
491
492
|
db: AsyncSession = Depends(get_db_session),
|
|
492
493
|
):
|
|
493
494
|
"""
|
|
@@ -4,7 +4,8 @@ from pydantic import BaseModel
|
|
|
4
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
5
|
|
|
6
6
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
7
|
-
from oauth2fast_fastapi import
|
|
7
|
+
from oauth2fast_fastapi import User
|
|
8
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
8
9
|
from files_server_fastapi.files.constants import BASE_DIR
|
|
9
10
|
from files_server_fastapi.files.dependencies import check_folder_access
|
|
10
11
|
from files_server_fastapi.files.path_utils import normalize_subpath, build_logical_path
|
|
@@ -22,7 +23,7 @@ class FileCreate(BaseModel):
|
|
|
22
23
|
@router.post("/create-file", summary="Crear un nuevo archivo en el servidor")
|
|
23
24
|
async def create_file(
|
|
24
25
|
req: FileCreate,
|
|
25
|
-
current_user: User = Depends(
|
|
26
|
+
current_user: User = Depends(get_active_user),
|
|
26
27
|
db: AsyncSession = Depends(get_db_session)
|
|
27
28
|
):
|
|
28
29
|
await check_folder_access(area=req.area, subpath=req.subpath, required_access="upload", current_user=current_user, db=db)
|
|
@@ -6,7 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
6
6
|
from sqlalchemy import select, delete as sql_delete, or_
|
|
7
7
|
|
|
8
8
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
9
|
-
from oauth2fast_fastapi import
|
|
9
|
+
from oauth2fast_fastapi import User
|
|
10
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
10
11
|
from files_server_fastapi.files.constants import BASE_DIR
|
|
11
12
|
from files_server_fastapi.files.dependencies import check_folder_access
|
|
12
13
|
from files_server_fastapi.models.rutas_model import Rutas
|
|
@@ -23,7 +24,7 @@ class DeleteRequest(BaseModel):
|
|
|
23
24
|
@router.delete("/delete", summary="Eliminar un archivo o carpeta del servidor")
|
|
24
25
|
async def delete_item(
|
|
25
26
|
req: DeleteRequest,
|
|
26
|
-
current_user: User = Depends(
|
|
27
|
+
current_user: User = Depends(get_active_user),
|
|
27
28
|
db: AsyncSession = Depends(get_db_session),
|
|
28
29
|
):
|
|
29
30
|
"""
|
|
@@ -3,7 +3,8 @@ from fastapi import HTTPException, Depends
|
|
|
3
3
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
4
|
from sqlmodel import select, or_
|
|
5
5
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
6
|
-
from oauth2fast_fastapi import
|
|
6
|
+
from oauth2fast_fastapi import User
|
|
7
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
7
8
|
from files_server_fastapi.models.permisos_model import User_Ruta_Access, Permisos, Permiso_rol
|
|
8
9
|
from files_server_fastapi.models.rutas_model import Rutas
|
|
9
10
|
from files_server_fastapi.models.users_extend_model import Users_extend
|
|
@@ -204,7 +205,7 @@ async def check_folder_access(
|
|
|
204
205
|
area: str,
|
|
205
206
|
subpath: str = "/",
|
|
206
207
|
required_access: str = "view",
|
|
207
|
-
current_user: User = Depends(
|
|
208
|
+
current_user: User = Depends(get_active_user),
|
|
208
209
|
db: AsyncSession = Depends(get_db_session),
|
|
209
210
|
) -> str:
|
|
210
211
|
"""
|
|
@@ -6,7 +6,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
6
6
|
from sqlalchemy import select
|
|
7
7
|
|
|
8
8
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
9
|
-
from oauth2fast_fastapi import
|
|
9
|
+
from oauth2fast_fastapi import User
|
|
10
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
10
11
|
from files_server_fastapi.files.constants import BASE_DIR
|
|
11
12
|
from files_server_fastapi.files.dependencies import check_folder_access
|
|
12
13
|
from files_server_fastapi.files.path_utils import normalize_subpath, build_logical_path
|
|
@@ -27,7 +28,7 @@ class FolderCreate(BaseModel):
|
|
|
27
28
|
@router.post("/folder", summary="Crear una nueva carpeta en el servidor")
|
|
28
29
|
async def create_folder(
|
|
29
30
|
req: FolderCreate,
|
|
30
|
-
current_user: User = Depends(
|
|
31
|
+
current_user: User = Depends(get_active_user),
|
|
31
32
|
db: AsyncSession = Depends(get_db_session)
|
|
32
33
|
):
|
|
33
34
|
logger.debug("folder_router INPUT area=%r subpath=%r folder_name=%r", req.area, req.subpath, req.folder_name)
|
|
@@ -5,7 +5,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
5
5
|
from sqlmodel import select, or_
|
|
6
6
|
|
|
7
7
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
8
|
-
from oauth2fast_fastapi import
|
|
8
|
+
from oauth2fast_fastapi import User
|
|
9
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
9
10
|
|
|
10
11
|
from files_server_fastapi.files.constants import BASE_DIR
|
|
11
12
|
from files_server_fastapi.files.dependencies import (
|
|
@@ -23,7 +24,7 @@ router = APIRouter()
|
|
|
23
24
|
async def list_directory(
|
|
24
25
|
area: str,
|
|
25
26
|
subpath: str = "/",
|
|
26
|
-
current_user: User = Depends(
|
|
27
|
+
current_user: User = Depends(get_active_user),
|
|
27
28
|
db: AsyncSession = Depends(get_db_session),
|
|
28
29
|
):
|
|
29
30
|
"""
|
|
@@ -12,7 +12,8 @@ from files_server_fastapi.files.dependencies import (
|
|
|
12
12
|
can_edit,
|
|
13
13
|
can_upload,
|
|
14
14
|
)
|
|
15
|
-
from oauth2fast_fastapi import
|
|
15
|
+
from oauth2fast_fastapi import User
|
|
16
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
16
17
|
|
|
17
18
|
router = APIRouter()
|
|
18
19
|
|
|
@@ -27,7 +28,7 @@ async def get_open_url(
|
|
|
27
28
|
filename: str,
|
|
28
29
|
subpath: str = "/",
|
|
29
30
|
access_type: str = Depends(check_folder_access),
|
|
30
|
-
current_user: User = Depends(
|
|
31
|
+
current_user: User = Depends(get_active_user),
|
|
31
32
|
):
|
|
32
33
|
"""
|
|
33
34
|
Devuelve un array **`options`** con las maneras de abrir el archivo según el permiso del usuario.
|
|
@@ -12,7 +12,8 @@ from sqlalchemy import select, or_
|
|
|
12
12
|
from sqlmodel import select as sm_select
|
|
13
13
|
|
|
14
14
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
15
|
-
from oauth2fast_fastapi import
|
|
15
|
+
from oauth2fast_fastapi import User
|
|
16
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
16
17
|
|
|
17
18
|
from files_server_fastapi.files.constants import BASE_DIR
|
|
18
19
|
from files_server_fastapi.files.dependencies import (
|
|
@@ -163,7 +164,7 @@ async def search_files(
|
|
|
163
164
|
q: str = Query(..., min_length=1, description="Texto a buscar (nombre de archivo o carpeta)"),
|
|
164
165
|
area: str = Query(None, description="Área donde buscar (ej: Ventas). Si se omite, busca en todas las áreas accesibles."),
|
|
165
166
|
limit: int = Query(50, ge=1, le=200, description="Número máximo de resultados"),
|
|
166
|
-
current_user: User = Depends(
|
|
167
|
+
current_user: User = Depends(get_active_user),
|
|
167
168
|
db: AsyncSession = Depends(get_db_session),
|
|
168
169
|
):
|
|
169
170
|
"""
|
|
@@ -283,7 +284,7 @@ async def search_users(
|
|
|
283
284
|
q: str = Query(..., min_length=1, description="Nombre o email del usuario a buscar"),
|
|
284
285
|
area: str = Query(None, description="Filtrar por área (ej: Ventas). Si se omite, busca en todas las áreas (solo SUPER_ADMIN)."),
|
|
285
286
|
limit: int = Query(20, ge=1, le=100, description="Número máximo de resultados"),
|
|
286
|
-
current_user: User = Depends(
|
|
287
|
+
current_user: User = Depends(get_active_user),
|
|
287
288
|
db: AsyncSession = Depends(get_db_session),
|
|
288
289
|
):
|
|
289
290
|
"""
|
|
@@ -2,7 +2,8 @@ import os
|
|
|
2
2
|
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, Form
|
|
3
3
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
4
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
5
|
-
from oauth2fast_fastapi import
|
|
5
|
+
from oauth2fast_fastapi import User
|
|
6
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user
|
|
6
7
|
from files_server_fastapi.files.constants import BASE_DIR
|
|
7
8
|
from files_server_fastapi.files.dependencies import check_folder_access
|
|
8
9
|
|
|
@@ -14,7 +15,7 @@ async def upload_file(
|
|
|
14
15
|
area: str = Form(...),
|
|
15
16
|
subpath: str = Form(default="/"),
|
|
16
17
|
file: UploadFile = File(...),
|
|
17
|
-
current_user: User = Depends(
|
|
18
|
+
current_user: User = Depends(get_active_user),
|
|
18
19
|
db: AsyncSession = Depends(get_db_session)
|
|
19
20
|
):
|
|
20
21
|
"""
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
|
-
from datetime import datetime
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from sqlalchemy import Column
|
|
4
|
+
from sqlalchemy import DateTime as SADateTime
|
|
3
5
|
from sqlmodel import Field
|
|
4
6
|
from oauth2fast_fastapi import AuthModel
|
|
5
7
|
|
|
@@ -24,7 +26,10 @@ class Users_extend(AuthModel, table=True):
|
|
|
24
26
|
# ── Soft Delete ───────────────────────────────────────────────────────────
|
|
25
27
|
# NULL → usuario activo
|
|
26
28
|
# NOT NULL → usuario dado de baja (fecha/hora exacta de la baja)
|
|
27
|
-
deleted_at: Optional[datetime] = Field(
|
|
29
|
+
deleted_at: Optional[datetime] = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
sa_column=Column(SADateTime(timezone=True), nullable=True)
|
|
32
|
+
)
|
|
28
33
|
|
|
29
34
|
# ID del usuario (admin/sistemas) que ejecutó la baja. NULL si está activo.
|
|
30
35
|
deleted_by: Optional[int] = Field(default=None, foreign_key="users.id")
|
|
@@ -6,6 +6,7 @@ from pydantic import BaseModel
|
|
|
6
6
|
|
|
7
7
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
8
8
|
from files_server_fastapi.models.area_model import Area
|
|
9
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user, require_superadmin
|
|
9
10
|
|
|
10
11
|
router = APIRouter(prefix="/areas", tags=["Gestión de Áreas"])
|
|
11
12
|
|
|
@@ -24,7 +25,7 @@ class AreaUpdate(BaseModel):
|
|
|
24
25
|
# POST /areas/ — Crear nueva área
|
|
25
26
|
# ==========================================
|
|
26
27
|
@router.post("/", response_model=Area, status_code=status.HTTP_201_CREATED, summary="Crear una nueva Área")
|
|
27
|
-
async def create_area(area_data: AreaCreate, db: AsyncSession = Depends(get_db_session)):
|
|
28
|
+
async def create_area(area_data: AreaCreate, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
28
29
|
"""
|
|
29
30
|
Crea y guarda una nueva área en la base de datos.
|
|
30
31
|
Solo accesible por superusuarios desde el panel de administración.
|
|
@@ -49,7 +50,7 @@ async def create_area(area_data: AreaCreate, db: AsyncSession = Depends(get_db_s
|
|
|
49
50
|
# GET /areas/ — Listar todas las áreas
|
|
50
51
|
# ==========================================
|
|
51
52
|
@router.get("/", response_model=list[Area], summary="Obtener todas las Áreas")
|
|
52
|
-
async def get_areas(db: AsyncSession = Depends(get_db_session)):
|
|
53
|
+
async def get_areas(auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
53
54
|
"""
|
|
54
55
|
Devuelve la lista de todas las áreas registradas.
|
|
55
56
|
"""
|
|
@@ -61,7 +62,7 @@ async def get_areas(db: AsyncSession = Depends(get_db_session)):
|
|
|
61
62
|
# GET /areas/{area_id} — Obtener área por ID
|
|
62
63
|
# ==========================================
|
|
63
64
|
@router.get("/{area_id}", response_model=Area, summary="Obtener un Área por ID")
|
|
64
|
-
async def get_area(area_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
65
|
+
async def get_area(area_id: int, auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
65
66
|
"""
|
|
66
67
|
Devuelve los datos de un área específica por su ID.
|
|
67
68
|
"""
|
|
@@ -79,7 +80,7 @@ async def get_area(area_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
|
79
80
|
# PATCH /areas/{area_id} — Actualizar área
|
|
80
81
|
# ==========================================
|
|
81
82
|
@router.patch("/{area_id}", response_model=Area, summary="Actualizar un Área")
|
|
82
|
-
async def update_area(area_id: int, area_update: AreaUpdate, db: AsyncSession = Depends(get_db_session)):
|
|
83
|
+
async def update_area(area_id: int, area_update: AreaUpdate, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
83
84
|
"""
|
|
84
85
|
Actualiza parcialmente los datos de un área existente.
|
|
85
86
|
"""
|
|
@@ -114,7 +115,7 @@ async def update_area(area_id: int, area_update: AreaUpdate, db: AsyncSession =
|
|
|
114
115
|
# DELETE /areas/{area_id} — Eliminar área
|
|
115
116
|
# ==========================================
|
|
116
117
|
@router.delete("/{area_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Eliminar un Área")
|
|
117
|
-
async def delete_area(area_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
118
|
+
async def delete_area(area_id: int, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
118
119
|
"""
|
|
119
120
|
Elimina un área por su ID.
|
|
120
121
|
ADVERTENCIA: Solo eliminar si no hay usuarios asociados a esta área.
|
|
@@ -4,24 +4,25 @@ from sqlalchemy import select
|
|
|
4
4
|
|
|
5
5
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
6
6
|
from files_server_fastapi.models.permisos_model import Permisos, User_Ruta_Access, Permiso_rol
|
|
7
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user, require_superadmin
|
|
7
8
|
|
|
8
9
|
router = APIRouter(prefix="/permisos", tags=["Gestión de Permisos"])
|
|
9
10
|
|
|
10
11
|
# --- Catálogo Maestro ---
|
|
11
12
|
@router.post("/", response_model=Permisos, summary="Crear Permiso Maestro")
|
|
12
|
-
async def create_permiso(permiso: Permisos, db: AsyncSession = Depends(get_db_session)):
|
|
13
|
+
async def create_permiso(permiso: Permisos, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
13
14
|
db.add(permiso)
|
|
14
15
|
await db.commit()
|
|
15
16
|
await db.refresh(permiso)
|
|
16
17
|
return permiso
|
|
17
18
|
|
|
18
19
|
@router.get("/", response_model=list[Permisos], summary="Ver Permisos Maestros")
|
|
19
|
-
async def get_permisos(db: AsyncSession = Depends(get_db_session)):
|
|
20
|
+
async def get_permisos(auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
20
21
|
result = await db.execute(select(Permisos))
|
|
21
22
|
return result.scalars().all()
|
|
22
23
|
|
|
23
24
|
@router.put("/{permiso_id}", response_model=Permisos, summary="Editar Permiso Maestro")
|
|
24
|
-
async def update_permiso(permiso_id: int, permiso_data: Permisos, db: AsyncSession = Depends(get_db_session)):
|
|
25
|
+
async def update_permiso(permiso_id: int, permiso_data: Permisos, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
25
26
|
result = await db.execute(select(Permisos).where(Permisos.id == permiso_id))
|
|
26
27
|
db_permiso = result.scalars().first()
|
|
27
28
|
if not db_permiso:
|
|
@@ -35,7 +36,7 @@ async def update_permiso(permiso_id: int, permiso_data: Permisos, db: AsyncSessi
|
|
|
35
36
|
return db_permiso
|
|
36
37
|
|
|
37
38
|
@router.delete("/{permiso_id}", summary="Eliminar Permiso Maestro")
|
|
38
|
-
async def delete_permiso(permiso_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
39
|
+
async def delete_permiso(permiso_id: int, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
39
40
|
result = await db.execute(select(Permisos).where(Permisos.id == permiso_id))
|
|
40
41
|
db_permiso = result.scalars().first()
|
|
41
42
|
if not db_permiso:
|
|
@@ -47,26 +48,26 @@ async def delete_permiso(permiso_id: int, db: AsyncSession = Depends(get_db_sess
|
|
|
47
48
|
|
|
48
49
|
# --- Asignaciones Intermedias ---
|
|
49
50
|
@router.post("/asignar-acl", response_model=User_Ruta_Access, summary="Asignar o Denegar acceso a ruta (ACL)")
|
|
50
|
-
async def assign_acl(acl: User_Ruta_Access, db: AsyncSession = Depends(get_db_session)):
|
|
51
|
+
async def assign_acl(acl: User_Ruta_Access, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
51
52
|
db.add(acl)
|
|
52
53
|
await db.commit()
|
|
53
54
|
await db.refresh(acl)
|
|
54
55
|
return acl
|
|
55
56
|
|
|
56
57
|
@router.post("/asignar-rol", response_model=Permiso_rol, summary="Asignar permiso a un Rol")
|
|
57
|
-
async def assign_rol(permiso: Permiso_rol, db: AsyncSession = Depends(get_db_session)):
|
|
58
|
+
async def assign_rol(permiso: Permiso_rol, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
58
59
|
db.add(permiso)
|
|
59
60
|
await db.commit()
|
|
60
61
|
await db.refresh(permiso)
|
|
61
62
|
return permiso
|
|
62
63
|
|
|
63
64
|
@router.get("/asignaciones-rol", summary="Ver todas las asignaciones de Permisos a Roles")
|
|
64
|
-
async def get_asignaciones_rol(db: AsyncSession = Depends(get_db_session)):
|
|
65
|
+
async def get_asignaciones_rol(auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
65
66
|
result = await db.execute(select(Permiso_rol))
|
|
66
67
|
return result.scalars().all()
|
|
67
68
|
|
|
68
69
|
@router.delete("/asignar-rol/{asignacion_id}", summary="Revocar permiso a un Rol")
|
|
69
|
-
async def revoke_rol_permission(asignacion_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
70
|
+
async def revoke_rol_permission(asignacion_id: int, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
70
71
|
result = await db.execute(select(Permiso_rol).where(Permiso_rol.id == asignacion_id))
|
|
71
72
|
db_asig = result.scalars().first()
|
|
72
73
|
if not db_asig:
|
|
@@ -4,7 +4,8 @@ from sqlalchemy import select
|
|
|
4
4
|
|
|
5
5
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
6
6
|
from files_server_fastapi.models.rol_model import Rol
|
|
7
|
-
from files_server_fastapi.
|
|
7
|
+
from files_server_fastapi.models.rol_model import Rol
|
|
8
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user, require_superadmin
|
|
8
9
|
|
|
9
10
|
router = APIRouter(prefix="/roles", tags=["Gestión de Roles"])
|
|
10
11
|
|
|
@@ -30,11 +31,11 @@ async def create_rol(
|
|
|
30
31
|
|
|
31
32
|
@router.get("/", response_model=list[Rol], summary="Obtener todos los Roles")
|
|
32
33
|
async def get_roles(
|
|
34
|
+
auth=Depends(get_active_user),
|
|
33
35
|
db: AsyncSession = Depends(get_db_session)
|
|
34
36
|
):
|
|
35
37
|
"""
|
|
36
38
|
Obtiene la lista de todos los roles.
|
|
37
|
-
Público para lectura (necesario para formularios de registro/edición).
|
|
38
39
|
"""
|
|
39
40
|
result = await db.execute(select(Rol))
|
|
40
41
|
return result.scalars().all()
|
|
@@ -4,17 +4,18 @@ from sqlalchemy import select
|
|
|
4
4
|
|
|
5
5
|
from pgsqlasync2fast_fastapi.dependencies import get_db_session
|
|
6
6
|
from files_server_fastapi.models.rutas_model import Rutas
|
|
7
|
+
from files_server_fastapi.dependencies.user_dependencies import get_active_user, require_superadmin
|
|
7
8
|
|
|
8
9
|
router = APIRouter(prefix="/rutas", tags=["Gestión de Rutas"])
|
|
9
10
|
|
|
10
11
|
@router.post("/", response_model=Rutas, summary="Registrar una Ruta")
|
|
11
|
-
async def create_ruta(ruta: Rutas, db: AsyncSession = Depends(get_db_session)):
|
|
12
|
+
async def create_ruta(ruta: Rutas, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
12
13
|
db.add(ruta)
|
|
13
14
|
await db.commit()
|
|
14
15
|
await db.refresh(ruta)
|
|
15
16
|
return ruta
|
|
16
17
|
|
|
17
18
|
@router.get("/", response_model=list[Rutas], summary="Obtener todas las Rutas")
|
|
18
|
-
async def get_rutas(db: AsyncSession = Depends(get_db_session)):
|
|
19
|
+
async def get_rutas(auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
19
20
|
result = await db.execute(select(Rutas))
|
|
20
21
|
return result.scalars().all()
|
|
@@ -84,7 +84,7 @@ async def _validate_area_and_rol(area_id: Optional[int], rol_id: Optional[int],
|
|
|
84
84
|
# POST /users-extend/ — Vincular usuario
|
|
85
85
|
# ==========================================
|
|
86
86
|
@router.post("/", response_model=Users_extend, status_code=status.HTTP_201_CREATED, summary="Vincular Usuario con Área y Rol")
|
|
87
|
-
async def create_user_extend(user_ext: UserExtendCreate, db: AsyncSession = Depends(get_db_session)):
|
|
87
|
+
async def create_user_extend(user_ext: UserExtendCreate, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
88
88
|
"""
|
|
89
89
|
Vincula un usuario con un área y rol. Valida que el área y rol existan antes de insertar.
|
|
90
90
|
"""
|
|
@@ -110,7 +110,7 @@ async def create_user_extend(user_ext: UserExtendCreate, db: AsyncSession = Depe
|
|
|
110
110
|
# GET /users-extend/ — Listar todos
|
|
111
111
|
# ==========================================
|
|
112
112
|
@router.get("/", response_model=list[UserExtendResponse], summary="Ver vínculos de usuarios")
|
|
113
|
-
async def get_users_extend(db: AsyncSession = Depends(get_db_session)):
|
|
113
|
+
async def get_users_extend(auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
114
114
|
result = await db.execute(select(Users_extend))
|
|
115
115
|
users = result.scalars().all()
|
|
116
116
|
# Construimos la respuesta manual inyectando role_id
|
|
@@ -126,7 +126,7 @@ async def get_users_extend(db: AsyncSession = Depends(get_db_session)):
|
|
|
126
126
|
# GET /users-extend/by-area/{area_id}
|
|
127
127
|
# ==========================================
|
|
128
128
|
@router.get("/by-area/{area_id}", response_model=list[UserExtendResponse], summary="Obtener usuarios de un área")
|
|
129
|
-
async def get_users_by_area(area_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
129
|
+
async def get_users_by_area(area_id: int, auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
130
130
|
result = await db.execute(select(Users_extend).where(Users_extend.area_id == area_id))
|
|
131
131
|
users = result.scalars().all()
|
|
132
132
|
response = []
|
|
@@ -141,7 +141,7 @@ async def get_users_by_area(area_id: int, db: AsyncSession = Depends(get_db_sess
|
|
|
141
141
|
# GET /users-extend/by-user/{user_id}
|
|
142
142
|
# ==========================================
|
|
143
143
|
@router.get("/by-user/{user_id}", summary="Obtener Área y Rol de un Usuario específico")
|
|
144
|
-
async def get_user_permissions(user_id: int, db: AsyncSession = Depends(get_db_session)):
|
|
144
|
+
async def get_user_permissions(user_id: int, auth=Depends(get_active_user), db: AsyncSession = Depends(get_db_session)):
|
|
145
145
|
result = await db.execute(select(Users_extend).where(Users_extend.user_id == user_id))
|
|
146
146
|
user_ext = result.scalars().first()
|
|
147
147
|
|
|
@@ -155,7 +155,7 @@ async def get_user_permissions(user_id: int, db: AsyncSession = Depends(get_db_s
|
|
|
155
155
|
# PATCH /users-extend/{user_id} — Actualizar
|
|
156
156
|
# ==========================================
|
|
157
157
|
@router.patch("/{user_id}", response_model=UserExtendResponse, summary="Actualizar datos de extensión de usuario")
|
|
158
|
-
async def update_user_extend(user_id: int, user_ext_update: UserExtendUpdate, db: AsyncSession = Depends(get_db_session)):
|
|
158
|
+
async def update_user_extend(user_id: int, user_ext_update: UserExtendUpdate, auth: tuple = Depends(require_superadmin), db: AsyncSession = Depends(get_db_session)):
|
|
159
159
|
from sqlmodel import or_
|
|
160
160
|
|
|
161
161
|
# Buscamos por user_id (lo ideal) o por el ID interno del registro (si el frontend se confunde)
|
|
@@ -329,16 +329,16 @@ async def deactivate_user(
|
|
|
329
329
|
@router.post(
|
|
330
330
|
"/{user_id}/reactivate",
|
|
331
331
|
status_code=status.HTTP_200_OK,
|
|
332
|
-
summary="Reactivar un usuario dado de baja
|
|
332
|
+
summary="Reactivar un usuario dado de baja",
|
|
333
333
|
)
|
|
334
334
|
async def reactivate_user(
|
|
335
335
|
user_id: int,
|
|
336
|
-
auth: tuple = Depends(
|
|
336
|
+
auth: tuple = Depends(require_area_admin_or_superadmin),
|
|
337
337
|
db: AsyncSession = Depends(get_db_session),
|
|
338
338
|
):
|
|
339
339
|
"""
|
|
340
340
|
Reactiva a un usuario previamente dado de baja.
|
|
341
|
-
|
|
341
|
+
Admin de Área solo puede reactivar usuarios de su área. Superadmin a todos.
|
|
342
342
|
|
|
343
343
|
El trigger PostgreSQL automáticamente restaura is_verified = TRUE
|
|
344
344
|
en la tabla users, permitiendo que el usuario vuelva a hacer login.
|
|
@@ -354,6 +354,28 @@ async def reactivate_user(
|
|
|
354
354
|
detail=f"Usuario con ID {user_id} no encontrado.",
|
|
355
355
|
)
|
|
356
356
|
|
|
357
|
+
current_user, executor_ext, is_superadmin = auth
|
|
358
|
+
|
|
359
|
+
if not is_superadmin:
|
|
360
|
+
# Admin de área: solo puede reactivar a usuarios de su misma área
|
|
361
|
+
if target_ext.area_id != executor_ext.area_id:
|
|
362
|
+
raise HTTPException(
|
|
363
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
364
|
+
detail="Solo puedes reactivar a usuarios de tu propia área.",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Admin de área: no puede reactivar a otros admins de área ni superadmins
|
|
368
|
+
from files_server_fastapi.dependencies.user_dependencies import (
|
|
369
|
+
_get_privilege_level,
|
|
370
|
+
PRIVILEGE_AREA_ADMIN,
|
|
371
|
+
)
|
|
372
|
+
target_level = await _get_privilege_level(target_ext.rol_id, db)
|
|
373
|
+
if target_level >= PRIVILEGE_AREA_ADMIN:
|
|
374
|
+
raise HTTPException(
|
|
375
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
376
|
+
detail="No puedes reactivar a un Admin de Área o Superadmin. Contacta a Sistemas.",
|
|
377
|
+
)
|
|
378
|
+
|
|
357
379
|
if target_ext.deleted_at is None:
|
|
358
380
|
raise HTTPException(
|
|
359
381
|
status_code=status.HTTP_409_CONFLICT,
|
|
@@ -377,19 +399,24 @@ async def reactivate_user(
|
|
|
377
399
|
@router.get(
|
|
378
400
|
"/inactive",
|
|
379
401
|
status_code=status.HTTP_200_OK,
|
|
380
|
-
summary="Listar usuarios dados de baja
|
|
402
|
+
summary="Listar usuarios dados de baja",
|
|
381
403
|
)
|
|
382
404
|
async def list_inactive_users(
|
|
383
|
-
auth: tuple = Depends(
|
|
405
|
+
auth: tuple = Depends(require_area_admin_or_superadmin),
|
|
384
406
|
db: AsyncSession = Depends(get_db_session),
|
|
385
407
|
):
|
|
386
408
|
"""
|
|
387
|
-
Retorna
|
|
388
|
-
|
|
409
|
+
Retorna los usuarios dados de baja.
|
|
410
|
+
Admin de Área solo ve los de su área. Superadmin ve todos. Útil para auditoría.
|
|
389
411
|
"""
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
)
|
|
412
|
+
current_user, executor_ext, is_superadmin = auth
|
|
413
|
+
|
|
414
|
+
query = select(Users_extend).where(Users_extend.deleted_at.is_not(None))
|
|
415
|
+
|
|
416
|
+
if not is_superadmin:
|
|
417
|
+
query = query.where(Users_extend.area_id == executor_ext.area_id)
|
|
418
|
+
|
|
419
|
+
result = await db.execute(query)
|
|
393
420
|
inactive_users = result.scalars().all()
|
|
394
421
|
|
|
395
422
|
return [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{files_server_fastapi-0.1.45 → files_server_fastapi-0.1.46}/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
|