Skip to content

Commit 3b72794

Browse files
committed
fix: standarize the response & add permission
1 parent 69af9eb commit 3b72794

10 files changed

Lines changed: 239 additions & 67 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
APP_NAME=Todo Modulith API
2+
APP_ENV=production
23

34
DATABASE_URL=postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/todo_db
45

src/core/authorization/permissions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
TODO_RESOURCE = "todo"
22
USER_RESOURCE = "user"
3+
ROLE_RESOURCE = "role"
4+
PERMISSION_RESOURCE = "permission"
35

46
CREATE_ACTION = "create"
57
READ_ACTION = "read"

src/core/config/setting.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
class Settings(BaseSettings):
77
APP_NAME: str = "Todo Modulith API"
8+
APP_ENV: str = "development"
89
DATABASE_URL: str = "postgresql+asyncpg://user:password@localhost:5432/todo_db"
910
REDIS_URL: str = "redis://:eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81@127.0.0.1:6379/0"
1011
SECRET_KEY: str = "super-secret-key-change-in-production"

src/modules/authorization/presenter/routers/permission_router.py

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22

33
from fastapi import APIRouter, Depends, HTTPException, status
44

5+
from core.authorization.dependencies import require_permission
56
from src.core.authorization.infrastructure.services.casbin_authorization_service import (
67
CasbinAuthorizationService,
78
)
8-
from src.core.authorization.permissions import permission_key
9+
from src.core.authorization.permissions import (
10+
CREATE_ACTION,
11+
DELETE_ACTION,
12+
PERMISSION_RESOURCE,
13+
READ_ACTION,
14+
UPDATE_ACTION,
15+
permission_key,
16+
)
917
from src.core.database.postgres.session import get_unit_of_work
18+
from src.core.schemas.response import PaginatedResponse, SuccessResponse
1019
from src.modules.authorization.domain.entities.permission import Permission
1120
from src.modules.authorization.presenter.dependency import (
1221
get_casbin_authorization_service,
@@ -15,12 +24,18 @@
1524
CreatePermissionRequest,
1625
UpdatePermissionRequest,
1726
)
27+
from src.modules.authorization.presenter.schema.response import PermissionResponse
1828
from src.shared.unit_of_work import UnitOfWork
1929

2030
router = APIRouter(prefix="/permissions", tags=["Permission"])
2131

2232

23-
@router.post("/", status_code=status.HTTP_201_CREATED)
33+
@router.post(
34+
"/",
35+
status_code=status.HTTP_201_CREATED,
36+
response_model=SuccessResponse[PermissionResponse],
37+
dependencies=[Depends(require_permission(PERMISSION_RESOURCE, CREATE_ACTION))],
38+
)
2439
async def create_permission(
2540
request: CreatePermissionRequest,
2641
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
@@ -35,31 +50,56 @@ async def create_permission(
3550
async with unit_of_work:
3651
created = await service.create_permission(permission)
3752
await unit_of_work.commit()
38-
return _permission_response(created)
53+
54+
return SuccessResponse(
55+
success=True,
56+
message="create permission success",
57+
data=_permission_response(created),
58+
)
3959

4060

41-
@router.get("/")
61+
@router.get(
62+
"/",
63+
response_model=PaginatedResponse[PermissionResponse],
64+
dependencies=[Depends(require_permission(PERMISSION_RESOURCE, READ_ACTION))],
65+
)
4266
async def list_permissions(
4367
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
4468
):
45-
return [
46-
_permission_response(permission)
47-
for permission in await service.list_permissions()
48-
]
69+
return PaginatedResponse(
70+
success=True,
71+
message="fetch permission success",
72+
data=[
73+
_permission_response(permission)
74+
for permission in await service.list_permissions()
75+
],
76+
)
4977

5078

51-
@router.get("/{permission_id}")
79+
@router.get(
80+
"/{permission_id}",
81+
response_model=SuccessResponse[PermissionResponse],
82+
dependencies=[Depends(require_permission(PERMISSION_RESOURCE, READ_ACTION))],
83+
)
5284
async def get_permission(
5385
permission_id: UUID,
5486
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
5587
):
5688
permission = await service.get_permission(permission_id)
5789
if permission is None:
5890
raise HTTPException(status_code=404, detail="Permission not found")
59-
return _permission_response(permission)
91+
return SuccessResponse(
92+
success=True,
93+
message="fetch permission success",
94+
data=_permission_response(permission),
95+
)
6096

6197

62-
@router.patch("/{permission_id}")
98+
@router.patch(
99+
"/{permission_id}",
100+
response_model=SuccessResponse[PermissionResponse],
101+
dependencies=[Depends(require_permission(PERMISSION_RESOURCE, UPDATE_ACTION))],
102+
)
63103
async def update_permission(
64104
permission_id: UUID,
65105
request: UpdatePermissionRequest,
@@ -86,10 +126,19 @@ async def update_permission(
86126
async with unit_of_work:
87127
updated = await service.update_permission(permission)
88128
await unit_of_work.commit()
89-
return _permission_response(updated)
129+
130+
return SuccessResponse(
131+
success=True,
132+
message="update permission success",
133+
data=_permission_response(updated),
134+
)
90135

91136

92-
@router.delete("/{permission_id}", status_code=status.HTTP_204_NO_CONTENT)
137+
@router.delete(
138+
"/{permission_id}",
139+
status_code=status.HTTP_204_NO_CONTENT,
140+
dependencies=[Depends(require_permission(PERMISSION_RESOURCE, DELETE_ACTION))],
141+
)
93142
async def delete_permission(
94143
permission_id: UUID,
95144
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
@@ -100,13 +149,13 @@ async def delete_permission(
100149
await unit_of_work.commit()
101150

102151

103-
def _permission_response(permission: Permission | None) -> dict:
152+
def _permission_response(permission: Permission | None) -> PermissionResponse:
104153
if permission is None:
105154
raise HTTPException(status_code=404, detail="Permission not found")
106-
return {
107-
"id": str(permission.id),
108-
"key": permission.key,
109-
"resource": permission.resource,
110-
"action": permission.action,
111-
"description": permission.description,
112-
}
155+
return PermissionResponse(
156+
id=str(permission.id),
157+
key=permission.key,
158+
resource=permission.resource,
159+
action=permission.action,
160+
description=permission.description,
161+
)

src/modules/authorization/presenter/routers/role_router.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@
22

33
from fastapi import APIRouter, Depends, HTTPException, status
44

5+
from core.authorization.dependencies import require_permission
6+
from core.authorization.permissions import (
7+
CREATE_ACTION,
8+
DELETE_ACTION,
9+
READ_ACTION,
10+
ROLE_RESOURCE,
11+
UPDATE_ACTION,
12+
)
513
from src.core.authorization.infrastructure.services.casbin_authorization_service import (
614
CasbinAuthorizationService,
715
)
816
from src.core.database.postgres.session import get_unit_of_work
17+
from src.core.schemas.response import PaginatedResponse, SuccessResponse
918
from src.modules.authorization.domain.entities.role import Role
1019
from src.modules.authorization.presenter.dependency import (
1120
get_casbin_authorization_service,
@@ -14,12 +23,18 @@
1423
CreateRoleRequest,
1524
UpdateRoleRequest,
1625
)
26+
from src.modules.authorization.presenter.schema.response import RoleResponse
1727
from src.shared.unit_of_work import UnitOfWork
1828

1929
router = APIRouter(prefix="/roles", tags=["Role"])
2030

2131

22-
@router.post("/", status_code=status.HTTP_201_CREATED)
32+
@router.post(
33+
"/",
34+
status_code=status.HTTP_201_CREATED,
35+
response_model=SuccessResponse[RoleResponse],
36+
dependencies=[Depends(require_permission(ROLE_RESOURCE, CREATE_ACTION))],
37+
)
2338
async def create_role(
2439
request: CreateRoleRequest,
2540
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
@@ -29,28 +44,48 @@ async def create_role(
2944
async with unit_of_work:
3045
created = await service.create_role(role)
3146
await unit_of_work.commit()
32-
return _role_response(created)
47+
return SuccessResponse(
48+
message="create role success", success=True, data=_role_response(created)
49+
)
3350

3451

35-
@router.get("/")
52+
@router.get(
53+
"/",
54+
response_model=PaginatedResponse[RoleResponse],
55+
dependencies=[Depends(require_permission(ROLE_RESOURCE, READ_ACTION))],
56+
)
3657
async def list_roles(
3758
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
3859
):
39-
return [_role_response(role) for role in await service.list_roles()]
60+
return SuccessResponse(
61+
message="fetch role success",
62+
success=True,
63+
data=[_role_response(role) for role in await service.list_roles()],
64+
)
4065

4166

42-
@router.get("/{role_id}")
67+
@router.get(
68+
"/{role_id}",
69+
response_model=SuccessResponse[RoleResponse],
70+
dependencies=[Depends(require_permission(ROLE_RESOURCE, READ_ACTION))],
71+
)
4372
async def get_role(
4473
role_id: UUID,
4574
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
4675
):
4776
role = await service.get_role(role_id)
4877
if role is None:
4978
raise HTTPException(status_code=404, detail="Role not found")
50-
return _role_response(role)
79+
return SuccessResponse(
80+
message="fetch role success", success=True, data=_role_response(role)
81+
)
5182

5283

53-
@router.patch("/{role_id}")
84+
@router.patch(
85+
"/{role_id}",
86+
response_model=SuccessResponse[RoleResponse],
87+
dependencies=[Depends(require_permission(ROLE_RESOURCE, UPDATE_ACTION))],
88+
)
5489
async def update_role(
5590
role_id: UUID,
5691
request: UpdateRoleRequest,
@@ -73,10 +108,16 @@ async def update_role(
73108
async with unit_of_work:
74109
updated = await service.update_role(role)
75110
await unit_of_work.commit()
76-
return _role_response(updated)
111+
return SuccessResponse(
112+
message="update role success", success=True, data=_role_response(updated)
113+
)
77114

78115

79-
@router.delete("/{role_id}", status_code=status.HTTP_204_NO_CONTENT)
116+
@router.delete(
117+
"/{role_id}",
118+
status_code=status.HTTP_204_NO_CONTENT,
119+
dependencies=[Depends(require_permission(ROLE_RESOURCE, DELETE_ACTION))],
120+
)
80121
async def delete_role(
81122
role_id: UUID,
82123
service: CasbinAuthorizationService = Depends(get_casbin_authorization_service),
@@ -90,6 +131,7 @@ async def delete_role(
90131
@router.post(
91132
"/{role_id}/permissions/{permission_id}",
92133
status_code=status.HTTP_204_NO_CONTENT,
134+
dependencies=[Depends(require_permission(ROLE_RESOURCE, CREATE_ACTION))],
93135
)
94136
async def assign_permission_to_role(
95137
role_id: UUID,
@@ -105,6 +147,7 @@ async def assign_permission_to_role(
105147
@router.delete(
106148
"/{role_id}/permissions/{permission_id}",
107149
status_code=status.HTTP_204_NO_CONTENT,
150+
dependencies=[Depends(require_permission(ROLE_RESOURCE, DELETE_ACTION))],
108151
)
109152
async def remove_permission_from_role(
110153
role_id: UUID,
@@ -117,11 +160,12 @@ async def remove_permission_from_role(
117160
await unit_of_work.commit()
118161

119162

120-
def _role_response(role: Role | None) -> dict:
163+
def _role_response(role: Role | None) -> RoleResponse:
121164
if role is None:
122165
raise HTTPException(status_code=404, detail="Role not found")
123-
return {
124-
"id": str(role.id),
125-
"name": role.name,
126-
"description": role.description,
127-
}
166+
167+
return RoleResponse(
168+
id=str(role.id),
169+
name=role.name,
170+
description=role.description,
171+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from pydantic import BaseModel
2+
3+
4+
class RoleResponse(BaseModel):
5+
id: str
6+
name: str
7+
description: str
8+
9+
10+
class PermissionResponse(BaseModel):
11+
id: str
12+
key: str
13+
resource: str
14+
action: str
15+
description: str

0 commit comments

Comments
 (0)