zrb 1.0.0b9__py3-none-any.whl → 1.1.0__py3-none-any.whl

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 changed (88) hide show
  1. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
  2. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
  3. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +99 -55
  4. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_util.py +301 -0
  5. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
  6. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +131 -2
  7. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +128 -5
  8. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/gateway/view/content/my-module/my-entity.html +297 -0
  9. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_create_my_entity.py +53 -0
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_delete_my_entity.py +62 -0
  11. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_read_my_entity.py +65 -0
  12. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_update_my_entity.py +61 -0
  13. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +81 -13
  14. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/navigation_config_file.py +8 -0
  15. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +8 -0
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -3
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +8 -1
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/navigation_config_file.py +6 -0
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +56 -12
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +10 -4
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +136 -52
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +3 -3
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +1 -1
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +19 -8
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +46 -43
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +5 -2
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +16 -21
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/config/navigation.py +39 -0
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +52 -11
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/schema/navigation.py +95 -0
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +277 -44
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +66 -0
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +33 -8
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/permission.html +311 -0
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/role.html +0 -0
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/auth/user.html +0 -0
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +4 -1
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/login.html +67 -0
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/logout.html +49 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/common/util.js +160 -0
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/style.css +14 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/crud/util.js +94 -0
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/pico-style.css +23 -0
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/script.js +44 -0
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/default/style.css +102 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +73 -18
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +6 -1
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +1 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +9 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
  56. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
  57. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
  58. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
  59. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +15 -0
  60. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
  61. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
  62. zrb/runner/web_route/refresh_token_api_route.py +1 -1
  63. zrb/runner/web_route/static/refresh-token.template.js +9 -0
  64. zrb/runner/web_route/static/static_route.py +1 -1
  65. zrb/task/base_task.py +10 -10
  66. zrb/util/codemod/modification_mode.py +3 -0
  67. zrb/util/codemod/modify_class.py +58 -0
  68. zrb/util/codemod/modify_class_parent.py +68 -0
  69. zrb/util/codemod/modify_class_property.py +128 -0
  70. zrb/util/codemod/modify_dict.py +75 -0
  71. zrb/util/codemod/modify_function.py +65 -0
  72. zrb/util/codemod/modify_function_call.py +68 -0
  73. zrb/util/codemod/modify_method.py +88 -0
  74. zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
  75. zrb/util/file.py +3 -2
  76. zrb/util/load.py +13 -7
  77. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/METADATA +2 -2
  78. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/RECORD +80 -46
  79. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
  80. zrb/util/codemod/append_code_to_class.py +0 -35
  81. zrb/util/codemod/append_code_to_function.py +0 -38
  82. zrb/util/codemod/append_code_to_method.py +0 -55
  83. zrb/util/codemod/append_key_to_dict.py +0 -51
  84. zrb/util/codemod/append_param_to_function_call.py +0 -39
  85. zrb/util/codemod/prepend_parent_to_class.py +0 -38
  86. zrb/util/codemod/prepend_property_to_class.py +0 -55
  87. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/WHEEL +0 -0
  88. {zrb-1.0.0b9.dist-info → zrb-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,53 @@
1
+ from fastapi.testclient import TestClient
2
+ from my_app_name.main import app
3
+ from my_app_name.test._util.access_token import get_admin_access_token
4
+
5
+
6
+ def test_create_my_entity():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_my_entity_data = {
10
+ "my_column": "new-my-entity",
11
+ }
12
+ response = client.post(
13
+ "/api/v1/my-entities",
14
+ json=new_my_entity_data,
15
+ headers={"Authorization": f"Bearer {access_token}"},
16
+ )
17
+ assert response.status_code == 200
18
+ response_data = response.json()
19
+ assert response_data.get("id") is not None
20
+ assert response_data.get("id") != ""
21
+ assert response_data.get("my_column") == "new-my-entity"
22
+
23
+
24
+ def test_create_my_entity_bulk():
25
+ client = TestClient(app, base_url="http://localhost")
26
+ access_token = get_admin_access_token()
27
+ new_first_my_entity_data = {
28
+ "my_column": "new-my-entity-bulk-1",
29
+ }
30
+ new_second_my_entity_data = {
31
+ "my_column": "new-my-entity-bulk-2",
32
+ }
33
+ new_my_entity_data = [new_first_my_entity_data, new_second_my_entity_data]
34
+ response = client.post(
35
+ "/api/v1/my-entities/bulk",
36
+ json=new_my_entity_data,
37
+ headers={"Authorization": f"Bearer {access_token}"},
38
+ )
39
+ assert response.status_code == 200
40
+ response_data = response.json()
41
+ assert len(response_data) == 2
42
+ # Id should not be empty
43
+ assert response_data[0] is not None
44
+ assert response_data[0] != ""
45
+ assert response_data[1] is not None
46
+ assert response_data[1] != ""
47
+ # Data should match
48
+ assert new_first_my_entity_data["my_column"] in [
49
+ row["my_column"] for row in response_data
50
+ ]
51
+ assert new_second_my_entity_data["my_column"] in [
52
+ row["my_column"] for row in response_data
53
+ ]
@@ -0,0 +1,62 @@
1
+ from fastapi.testclient import TestClient
2
+ from my_app_name.main import app
3
+ from my_app_name.test._util.access_token import get_admin_access_token
4
+
5
+
6
+ def test_delete_my_entity():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_my_entity_data = {
10
+ "my_column": "to-be-deleted-my-entity",
11
+ }
12
+ insert_response = client.post(
13
+ "/api/v1/my-entities",
14
+ json=new_my_entity_data,
15
+ headers={"Authorization": f"Bearer {access_token}"},
16
+ )
17
+ assert insert_response.status_code == 200
18
+ id = insert_response.json().get("id")
19
+ # deleting
20
+ response = client.delete(
21
+ f"/api/v1/my-entities/{id}", headers={"Authorization": f"Bearer {access_token}"}
22
+ )
23
+ assert response.status_code == 200
24
+ response_data = response.json()
25
+ assert response_data.get("id") == id
26
+ assert response_data.get("my_column") == "to-be-deleted-my-entity"
27
+
28
+
29
+ def test_delete_my_entity_bulk():
30
+ client = TestClient(app, base_url="http://localhost")
31
+ access_token = get_admin_access_token()
32
+ new_first_my_entity_data = {
33
+ "my_column": "to-be-deleted-my-entity-bulk-1",
34
+ }
35
+ new_second_my_entity_data = {
36
+ "my_column": "to-be-deleted-my-entity-bulk-2",
37
+ }
38
+ new_my_entity_data = [new_first_my_entity_data, new_second_my_entity_data]
39
+ insert_response = client.post(
40
+ "/api/v1/my-entities/bulk",
41
+ json=new_my_entity_data,
42
+ headers={"Authorization": f"Bearer {access_token}"},
43
+ )
44
+ assert insert_response.status_code == 200
45
+ ids = [row["id"] for row in insert_response.json()]
46
+ # deleting (use client.request since client.delete doesn't support json param)
47
+ response = client.request(
48
+ "DELETE",
49
+ f"/api/v1/my-entities/bulk",
50
+ json=ids,
51
+ headers={"Authorization": f"Bearer {access_token}"},
52
+ )
53
+ assert response.status_code == 200
54
+ response_data = response.json()
55
+ # Data should match
56
+ assert len([row["id"] for row in response_data if row["id"] in ids]) == 2
57
+ assert new_first_my_entity_data["my_column"] in [
58
+ row["my_column"] for row in response_data
59
+ ]
60
+ assert new_second_my_entity_data["my_column"] in [
61
+ row["my_column"] for row in response_data
62
+ ]
@@ -0,0 +1,65 @@
1
+ from fastapi.testclient import TestClient
2
+ from my_app_name.main import app
3
+ from my_app_name.test._util.access_token import get_admin_access_token
4
+
5
+
6
+ def test_read_my_entity_by_id():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_my_entity_data = {
10
+ "my_column": "to-be-read-my-entity",
11
+ }
12
+ insert_response = client.post(
13
+ "/api/v1/my-entities",
14
+ json=new_my_entity_data,
15
+ headers={"Authorization": f"Bearer {access_token}"},
16
+ )
17
+ assert insert_response.status_code == 200
18
+ id = insert_response.json().get("id")
19
+ # fetching
20
+ response = client.get(
21
+ f"/api/v1/my-entities/{id}", headers={"Authorization": f"Bearer {access_token}"}
22
+ )
23
+ assert response.status_code == 200
24
+ response_data = response.json()
25
+ assert response_data.get("id") == id
26
+ assert response_data.get("my_column") == "to-be-read-my-entity"
27
+
28
+
29
+ def test_read_my_entity_bulk():
30
+ client = TestClient(app, base_url="http://localhost")
31
+ access_token = get_admin_access_token()
32
+ new_first_my_entity_data = {
33
+ "my_column": "to-be-read-my-entity-bulk-1",
34
+ }
35
+ new_second_my_entity_data = {
36
+ "my_column": "to-be-read-my-entity-bulk-2",
37
+ }
38
+ new_my_entity_data = [new_first_my_entity_data, new_second_my_entity_data]
39
+ insert_response = client.post(
40
+ "/api/v1/my-entities/bulk",
41
+ json=new_my_entity_data,
42
+ headers={"Authorization": f"Bearer {access_token}"},
43
+ )
44
+ assert insert_response.status_code == 200
45
+ ids = [row["id"] for row in insert_response.json()]
46
+ # fetching
47
+ response = client.get(
48
+ f"/api/v1/my-entities",
49
+ params={
50
+ "filter": "my_column:like:to-be-read-my-entity-bulk-%",
51
+ },
52
+ headers={"Authorization": f"Bearer {access_token}"},
53
+ )
54
+ assert response.status_code == 200
55
+ response_data_count = response.json()["count"]
56
+ assert response_data_count == 2
57
+ response_data = response.json()["data"]
58
+ # Data should match
59
+ assert len([row["id"] for row in response_data if row["id"] in ids]) == 2
60
+ assert new_first_my_entity_data["my_column"] in [
61
+ row["my_column"] for row in response_data
62
+ ]
63
+ assert new_second_my_entity_data["my_column"] in [
64
+ row["my_column"] for row in response_data
65
+ ]
@@ -0,0 +1,61 @@
1
+ from fastapi.testclient import TestClient
2
+ from my_app_name.main import app
3
+ from my_app_name.test._util.access_token import get_admin_access_token
4
+
5
+
6
+ def test_update_my_entity():
7
+ client = TestClient(app, base_url="http://localhost")
8
+ access_token = get_admin_access_token()
9
+ new_my_entity_data = {
10
+ "my_column": "to-be-updated-my-entity",
11
+ }
12
+ insert_response = client.post(
13
+ "/api/v1/my-entities",
14
+ json=new_my_entity_data,
15
+ headers={"Authorization": f"Bearer {access_token}"},
16
+ )
17
+ assert insert_response.status_code == 200
18
+ id = insert_response.json().get("id")
19
+ # updating
20
+ updated_my_entity_data = {
21
+ "my_column": "updated-my-entity",
22
+ }
23
+ response = client.put(
24
+ f"/api/v1/my-entities/{id}",
25
+ json=updated_my_entity_data,
26
+ headers={"Authorization": f"Bearer {access_token}"},
27
+ )
28
+ assert response.status_code == 200
29
+ response_data = response.json()
30
+ assert response_data.get("id") == id
31
+ assert response_data.get("my_column") == "updated-my-entity"
32
+
33
+
34
+ def test_update_my_entity_bulk():
35
+ client = TestClient(app, base_url="http://localhost")
36
+ access_token = get_admin_access_token()
37
+ new_first_my_entity_data = {
38
+ "my_column": "to-be-updated-my-entity-bulk-1",
39
+ }
40
+ insert_response = client.post(
41
+ "/api/v1/my-entities/bulk",
42
+ json=[new_first_my_entity_data],
43
+ headers={"Authorization": f"Bearer {access_token}"},
44
+ )
45
+ assert insert_response.status_code == 200
46
+ ids = [row["id"] for row in insert_response.json()]
47
+ # updating (we only test with one data)
48
+ updated_my_entity_data = {"my_column": "updated-my-entity-bulk-1"}
49
+ response = client.put(
50
+ f"/api/v1/my-entities/bulk",
51
+ json={
52
+ "my_entity_ids": ids,
53
+ "data": updated_my_entity_data,
54
+ },
55
+ headers={"Authorization": f"Bearer {access_token}"},
56
+ )
57
+ assert response.status_code == 200
58
+ response_data = response.json()
59
+ assert len(response_data) == 1
60
+ response_data[0].get("id") == ids[0]
61
+ response_data[0].get("my_column") == updated_my_entity_data["my_column"]
@@ -1,20 +1,52 @@
1
1
  # MyEntity routes
2
2
 
3
3
 
4
+ @app.get("/my-module/my-entities", include_in_schema=False)
5
+ def my_entities_crud_ui(
6
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
7
+ page: int = 1,
8
+ page_size: int = 10,
9
+ sort: str | None = None,
10
+ filter: str | None = None,
11
+ ):
12
+ if not current_user.has_permission("my-entity:read"):
13
+ return render_error(error_message="Access denied", status_code=403)
14
+ return render_content(
15
+ view_path=os.path.join("my-module", "my-entity.html"),
16
+ current_user=current_user,
17
+ page_name="my-module.my-entity",
18
+ page=page,
19
+ page_size=page_size,
20
+ sort=sort,
21
+ filter=filter,
22
+ allow_create=current_user.has_permission("my-entity:create"),
23
+ allow_update=current_user.has_permission("my-entity:update"),
24
+ allow_delete=current_user.has_permission("my-entity:delete"),
25
+ )
26
+
27
+
4
28
  @app.get("/api/v1/my-entities", response_model=MultipleMyEntityResponse)
5
29
  async def get_my_entities(
30
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
6
31
  page: int = 1,
7
32
  page_size: int = 10,
8
33
  sort: str | None = None,
9
34
  filter: str | None = None,
10
35
  ) -> MultipleMyEntityResponse:
36
+ if not current_user.has_permission("my-entity:read"):
37
+ raise ForbiddenError("Access denied")
11
38
  return await my_module_client.get_my_entities(
12
39
  page=page, page_size=page_size, sort=sort, filter=filter
13
40
  )
14
41
 
15
42
 
16
43
  @app.get("/api/v1/my-entities/{my_entity_id}", response_model=MyEntityResponse)
17
- async def get_my_entity_by_id(my_entity_id: str) -> MyEntityResponse:
44
+ async def get_my_entity_by_id(
45
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
46
+ my_entity_id: str,
47
+ ) -> MyEntityResponse:
48
+ if not current_user.has_permission("my-entity:read"):
49
+ raise ForbiddenError("Access denied")
18
50
  return await my_module_client.get_my_entity_by_id(my_entity_id)
19
51
 
20
52
 
@@ -22,9 +54,14 @@ async def get_my_entity_by_id(my_entity_id: str) -> MyEntityResponse:
22
54
  "/api/v1/my-entities/bulk",
23
55
  response_model=list[MyEntityResponse],
24
56
  )
25
- async def create_my_entity_bulk(data: list[MyEntityCreate]):
57
+ async def create_my_entity_bulk(
58
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
59
+ data: list[MyEntityCreate],
60
+ ) -> list[MyEntityResponse]:
61
+ if not current_user.has_permission("my-entity:create"):
62
+ raise ForbiddenError("Access denied")
26
63
  return await my_module_client.create_my_entity_bulk(
27
- [row.with_audit(created_by="system") for row in data]
64
+ [row.with_audit(created_by=current_user.id) for row in data]
28
65
  )
29
66
 
30
67
 
@@ -32,17 +69,30 @@ async def create_my_entity_bulk(data: list[MyEntityCreate]):
32
69
  "/api/v1/my-entities",
33
70
  response_model=MyEntityResponse,
34
71
  )
35
- async def create_my_entity(data: MyEntityCreate):
36
- return await my_module_client.create_my_entity(data.with_audit(created_by="system"))
72
+ async def create_my_entity(
73
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
74
+ data: MyEntityCreate,
75
+ ) -> MyEntityResponse:
76
+ if not current_user.has_permission("my-entity:create"):
77
+ raise ForbiddenError("Access denied")
78
+ return await my_module_client.create_my_entity(
79
+ data.with_audit(created_by=current_user.id)
80
+ )
37
81
 
38
82
 
39
83
  @app.put(
40
84
  "/api/v1/my-entities/bulk",
41
85
  response_model=list[MyEntityResponse],
42
86
  )
43
- async def update_my_entity_bulk(my_entity_ids: list[str], data: MyEntityUpdate):
87
+ async def update_my_entity_bulk(
88
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
89
+ my_entity_ids: list[str],
90
+ data: MyEntityUpdate,
91
+ ) -> list[MyEntityResponse]:
92
+ if not current_user.has_permission("my-entity:update"):
93
+ raise ForbiddenError("Access denied")
44
94
  return await my_module_client.update_my_entity_bulk(
45
- my_entity_ids, data.with_audit(updated_by="system")
95
+ my_entity_ids, data.with_audit(updated_by=current_user.id)
46
96
  )
47
97
 
48
98
 
@@ -50,9 +100,15 @@ async def update_my_entity_bulk(my_entity_ids: list[str], data: MyEntityUpdate):
50
100
  "/api/v1/my-entities/{my_entity_id}",
51
101
  response_model=MyEntityResponse,
52
102
  )
53
- async def update_my_entity(my_entity_id: str, data: MyEntityUpdate):
103
+ async def update_my_entity(
104
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
105
+ my_entity_id: str,
106
+ data: MyEntityUpdate,
107
+ ) -> MyEntityResponse:
108
+ if not current_user.has_permission("my-entity:update"):
109
+ raise ForbiddenError("Access denied")
54
110
  return await my_module_client.update_my_entity(
55
- my_entity_id, data.with_audit(updated_by="system")
111
+ my_entity_id, data.with_audit(updated_by=current_user.id)
56
112
  )
57
113
 
58
114
 
@@ -60,9 +116,14 @@ async def update_my_entity(my_entity_id: str, data: MyEntityUpdate):
60
116
  "/api/v1/my-entities/bulk",
61
117
  response_model=list[MyEntityResponse],
62
118
  )
63
- async def delete_my_entity_bulk(my_entity_ids: list[str]):
119
+ async def delete_my_entity_bulk(
120
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
121
+ my_entity_ids: list[str],
122
+ ) -> list[MyEntityResponse]:
123
+ if not current_user.has_permission("my-entity:delete"):
124
+ raise ForbiddenError("Access denied")
64
125
  return await my_module_client.delete_my_entity_bulk(
65
- my_entity_ids, deleted_by="system"
126
+ my_entity_ids, deleted_by=current_user.id
66
127
  )
67
128
 
68
129
 
@@ -70,5 +131,12 @@ async def delete_my_entity_bulk(my_entity_ids: list[str]):
70
131
  "/api/v1/my-entities/{my_entity_id}",
71
132
  response_model=MyEntityResponse,
72
133
  )
73
- async def delete_my_entity(my_entity_id: str):
74
- return await my_module_client.delete_my_entity(my_entity_id, deleted_by="system")
134
+ async def delete_my_entity(
135
+ current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
136
+ my_entity_id: str,
137
+ ) -> MyEntityResponse:
138
+ if not current_user.has_permission("my-entity:delete"):
139
+ raise ForbiddenError("Access denied")
140
+ return await my_module_client.delete_my_entity(
141
+ my_entity_id, deleted_by=current_user.id
142
+ )
@@ -0,0 +1,8 @@
1
+ my_module_menu.append_page(
2
+ Page(
3
+ name="my-module.my-entity",
4
+ caption="My Entity",
5
+ url="/my-module/my-entities",
6
+ permission="my-entity:read",
7
+ )
8
+ )
@@ -10,12 +10,14 @@ from my_app_name._zrb.module.add_module_util import (
10
10
  is_app_zrb_config_file,
11
11
  is_app_zrb_task_file,
12
12
  is_gateway_module_subroute_file,
13
+ is_gateway_navigation_config_file,
13
14
  is_gateway_route_file,
14
15
  is_in_module_dir,
15
16
  update_app_config_file,
16
17
  update_app_main_file,
17
18
  update_app_zrb_config_file,
18
19
  update_app_zrb_task_file,
20
+ update_gateway_navigation_config_file,
19
21
  update_gateway_route_file,
20
22
  )
21
23
  from my_app_name._zrb.util import get_existing_module_names
@@ -91,6 +93,12 @@ scaffold_my_app_name_module = Scaffolder(
91
93
  match=is_gateway_route_file,
92
94
  transform=update_gateway_route_file,
93
95
  ),
96
+ # Register module's page to my_app_name/gateway/config/navigation.py
97
+ ContentTransformer(
98
+ name="transform-gateway-navigation-config",
99
+ match=is_gateway_navigation_config_file,
100
+ transform=update_gateway_navigation_config_file,
101
+ ),
94
102
  ],
95
103
  retries=0,
96
104
  upstream=validate_add_my_app_name_module,
@@ -4,10 +4,15 @@ from my_app_name._zrb.config import APP_DIR
4
4
  from my_app_name._zrb.util import get_existing_module_names
5
5
 
6
6
  from zrb.context.any_context import AnyContext
7
- from zrb.util.codemod.append_code_to_function import append_code_to_function
8
- from zrb.util.codemod.append_key_to_dict import append_key_to_dict
7
+ from zrb.util.codemod.modify_dict import append_key_to_dict
8
+ from zrb.util.codemod.modify_function import append_code_to_function
9
9
  from zrb.util.file import read_file, write_file
10
- from zrb.util.string.conversion import to_kebab_case, to_pascal_case, to_snake_case
10
+ from zrb.util.string.conversion import (
11
+ to_human_case,
12
+ to_kebab_case,
13
+ to_pascal_case,
14
+ to_snake_case,
15
+ )
11
16
 
12
17
 
13
18
  def is_app_config_file(ctx: AnyContext, file_path: str) -> bool:
@@ -22,6 +27,12 @@ def is_gateway_route_file(ctx: AnyContext, file_path: str) -> bool:
22
27
  return file_path == os.path.join(APP_DIR, "module", "gateway", "route.py")
23
28
 
24
29
 
30
+ def is_gateway_navigation_config_file(ctx: AnyContext, file_path: str) -> bool:
31
+ return file_path == os.path.join(
32
+ APP_DIR, "module", "gateway", "config", "navigation.py"
33
+ )
34
+
35
+
25
36
  def is_app_zrb_task_file(ctx: AnyContext, file_path: str) -> bool:
26
37
  return file_path == os.path.join(APP_DIR, "_zrb", "task.py")
27
38
 
@@ -194,3 +205,31 @@ def _get_module_subroute_import(existing_code: str, module_name: str) -> str | N
194
205
  if import_code in existing_code:
195
206
  return None
196
207
  return import_code
208
+
209
+
210
+ def update_gateway_navigation_config_file(
211
+ ctx: AnyContext, gateway_navigation_config_file_path: str
212
+ ):
213
+ existing_gateway_navigation_config_code = read_file(
214
+ gateway_navigation_config_file_path
215
+ )
216
+ snake_module_name = to_snake_case(ctx.input.module)
217
+ kebab_module_name = to_kebab_case(ctx.input.module)
218
+ human_module_name = to_human_case(ctx.input.module)
219
+ new_navigation_config_code = read_file(
220
+ file_path=os.path.join(
221
+ os.path.dirname(__file__), "template", "navigation_config_file.py"
222
+ ),
223
+ replace_map={
224
+ "my_module": snake_module_name,
225
+ "my-module": kebab_module_name,
226
+ "My Module": human_module_name.title(),
227
+ },
228
+ ).strip()
229
+ write_file(
230
+ file_path=gateway_navigation_config_file_path,
231
+ content=[
232
+ existing_gateway_navigation_config_code,
233
+ new_navigation_config_code,
234
+ ],
235
+ )
@@ -1,4 +1,11 @@
1
- from fastapi import FastAPI
1
+ import os
2
+ from typing import Annotated
3
+
4
+ from fastapi import Depends, FastAPI
5
+ from my_app_name.common.error import ForbiddenError
6
+ from my_app_name.module.gateway.util.auth import get_current_user
7
+ from my_app_name.module.gateway.util.view import render_content, render_error
8
+ from my_app_name.schema.user import AuthUserResponse
2
9
 
3
10
 
4
11
  def serve_my_module_route(app: FastAPI):
@@ -1,26 +1,30 @@
1
1
  # 🔐 Run/Migrate My Module ==========================================================
2
2
 
3
3
  run_my_module = app_run_group.add_task(
4
- run_microservice("my-module", 3000, "my_module"), alias="svc-my_module"
4
+ run_microservice("my_module", 3000), alias="svc-my-module"
5
5
  )
6
6
  prepare_venv >> run_my_module >> run_microservices
7
7
 
8
8
  create_my_module_migration = app_create_migration_group.add_task(
9
- create_migration("my-module", "my_module"), alias="my_module"
9
+ create_migration("my_module"), alias="my_module"
10
10
  )
11
11
  prepare_venv >> create_my_module_migration >> create_all_migration
12
12
 
13
- migrate_monolith_my_module = migrate_module(
14
- "my_module", "my_module", as_microservices=False
15
- )
13
+ migrate_monolith_my_module = migrate_module("my_module", as_microservices=False)
16
14
  prepare_venv >> migrate_monolith_my_module >> [migrate_monolith, run_monolith]
17
15
 
18
16
  migrate_microservices_my_module = app_migrate_group.add_task(
19
- migrate_module("my-module", "my_module", as_microservices=True),
17
+ migrate_module("my_module", as_microservices=True),
20
18
  alias="svc-my-module",
21
19
  )
20
+
22
21
  (
23
22
  prepare_venv
24
23
  >> migrate_microservices_my_module
25
24
  >> [migrate_microservices, run_my_module]
26
25
  )
26
+
27
+ migrate_test_my_module = migrate_module(
28
+ "my_module", as_microservices=False, additional_env_vars=TEST_ENV_VARS
29
+ )
30
+ prepare_venv >> migrate_test_my_module >> migrate_test
@@ -0,0 +1,6 @@
1
+ my_module_menu = APP_NAVIGATION.append_page_group(
2
+ PageGroup(
3
+ name="my-module",
4
+ caption="My Module",
5
+ )
6
+ )