zrb 1.0.0a21__py3-none-any.whl → 1.0.0b3__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 (195) hide show
  1. zrb/__init__.py +2 -1
  2. zrb/__main__.py +3 -3
  3. zrb/builtin/__init__.py +3 -0
  4. zrb/builtin/group.py +1 -0
  5. zrb/builtin/llm/llm_chat.py +5 -3
  6. zrb/builtin/llm/tool/cli.py +1 -1
  7. zrb/builtin/llm/tool/rag.py +108 -145
  8. zrb/builtin/llm/tool/web.py +1 -1
  9. zrb/builtin/project/add/fastapp/fastapp_task.py +2 -0
  10. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -2
  11. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +80 -20
  12. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +150 -42
  13. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service.py +113 -0
  14. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service_factory.py +9 -0
  15. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/my_entity_db_repository.py +0 -10
  16. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/my_entity_repository.py +37 -16
  17. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/repository/{factory.py → my_entity_repository_factory.py} +2 -2
  18. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +16 -6
  19. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/client_method.py +57 -0
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +74 -0
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/format_task.py +1 -1
  22. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +13 -0
  23. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_task.py +23 -0
  24. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +42 -0
  25. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +7 -0
  26. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_api_client.py +6 -0
  27. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/{any_client.py → my_module_client.py} +1 -1
  28. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_client_factory.py +11 -0
  29. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/my_module_direct_client.py +5 -0
  30. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/route.py +11 -11
  31. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +2 -2
  32. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +8 -8
  33. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +47 -20
  34. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app_factory.py +29 -0
  35. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +230 -102
  36. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +236 -0
  37. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/{db_engine.py → db_engine_factory.py} +1 -1
  38. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +12 -0
  39. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/logger_factory.py +10 -0
  40. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/parser_factory.py +7 -0
  41. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/app.py +47 -0
  42. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +105 -0
  43. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/user_agent.py +58 -0
  44. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/view.py +37 -0
  45. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +37 -1
  46. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/main.py +1 -1
  47. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_api_client.py +16 -0
  48. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +169 -0
  49. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client_factory.py +9 -0
  50. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_direct_client.py +15 -0
  51. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +160 -0
  52. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +18 -1
  53. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +7 -3
  54. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +117 -0
  55. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service_factory.py +11 -0
  56. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_db_repository.py +26 -0
  57. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository.py +61 -0
  58. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/repository/permission_repository_factory.py +13 -0
  59. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +89 -0
  60. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository.py +67 -0
  61. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_repository_factory.py +13 -0
  62. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +137 -0
  63. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service_factory.py +7 -0
  64. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +179 -12
  65. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +67 -17
  66. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository_factory.py +2 -2
  67. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +127 -0
  68. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +7 -0
  69. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/route.py +43 -14
  70. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +200 -30
  71. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/view.py +74 -0
  72. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/error.html +6 -0
  73. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/content/homepage.html +6 -0
  74. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-192x192.png +0 -0
  75. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/android-chrome-512x512.png +0 -0
  76. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/images/favicon-32x32.png +0 -0
  77. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.amber.min.css +4 -0
  78. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.blue.min.css +4 -0
  79. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.cyan.min.css +4 -0
  80. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.fuchsia.min.css +4 -0
  81. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.green.min.css +4 -0
  82. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.grey.min.css +4 -0
  83. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.indigo.min.css +4 -0
  84. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.jade.min.css +4 -0
  85. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.lime.min.css +4 -0
  86. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.min.css +4 -0
  87. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.orange.min.css +4 -0
  88. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pink.min.css +4 -0
  89. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.pumpkin.min.css +4 -0
  90. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.purple.min.css +4 -0
  91. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.red.min.css +4 -0
  92. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.sand.min.css +4 -0
  93. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.slate.min.css +4 -0
  94. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.violet.min.css +4 -0
  95. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.yellow.min.css +4 -0
  96. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/static/pico-css/pico.zinc.min.css +4 -0
  97. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/view/template/default.html +34 -0
  98. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +1 -0
  99. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +17 -5
  100. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +78 -4
  101. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +48 -0
  102. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +69 -5
  103. zrb/builtin/python.py +1 -1
  104. zrb/builtin/random.py +61 -0
  105. zrb/cmd/cmd_val.py +6 -5
  106. zrb/config.py +14 -1
  107. zrb/content_transformer/any_content_transformer.py +7 -0
  108. zrb/content_transformer/content_transformer.py +6 -0
  109. zrb/runner/cli.py +14 -7
  110. zrb/runner/web_app.py +28 -280
  111. zrb/runner/web_config/config.py +91 -0
  112. zrb/runner/web_config/config_factory.py +26 -0
  113. zrb/runner/web_route/docs_route.py +17 -0
  114. zrb/runner/web_route/error_page/serve_default_404.py +28 -0
  115. zrb/runner/{web_controller/error_page/controller.py → web_route/error_page/show_error_page.py} +4 -3
  116. zrb/runner/{web_controller → web_route}/error_page/view.html +5 -0
  117. zrb/runner/web_route/home_page/home_page_route.py +51 -0
  118. zrb/runner/web_route/login_api_route.py +31 -0
  119. zrb/runner/web_route/login_page/login_page_route.py +39 -0
  120. zrb/runner/web_route/logout_api_route.py +18 -0
  121. zrb/runner/web_route/logout_page/logout_page_route.py +40 -0
  122. zrb/runner/{web_controller/group_info_page/controller.py → web_route/node_page/group/show_group_page.py} +3 -3
  123. zrb/runner/web_route/node_page/node_page_route.py +50 -0
  124. zrb/runner/{web_controller/session_page/controller.py → web_route/node_page/task/show_task_page.py} +3 -3
  125. zrb/runner/web_route/refresh_token_api_route.py +38 -0
  126. zrb/runner/{web_controller/static → web_route/static/resources}/session/current-session.js +5 -2
  127. zrb/runner/{web_controller/static → web_route/static/resources}/session/event.js +5 -2
  128. zrb/runner/web_route/static/static_route.py +44 -0
  129. zrb/runner/web_route/task_input_api_route.py +47 -0
  130. zrb/runner/web_route/task_session_api_route.py +147 -0
  131. zrb/runner/web_schema/session.py +5 -0
  132. zrb/runner/web_schema/token.py +11 -0
  133. zrb/runner/web_schema/user.py +32 -0
  134. zrb/runner/web_util/cookie.py +29 -0
  135. zrb/runner/{web_util.py → web_util/html.py} +1 -23
  136. zrb/runner/web_util/token.py +72 -0
  137. zrb/runner/web_util/user.py +63 -0
  138. zrb/session/session.py +6 -4
  139. zrb/session_state_logger/{default_session_state_logger.py → session_state_logger_factory.py} +1 -1
  140. zrb/task/base_task.py +56 -8
  141. zrb/task/base_trigger.py +2 -0
  142. zrb/task/cmd_task.py +9 -5
  143. zrb/task/http_check.py +2 -0
  144. zrb/task/llm_task.py +184 -71
  145. zrb/task/make_task.py +2 -0
  146. zrb/task/rsync_task.py +2 -0
  147. zrb/task/scaffolder.py +8 -5
  148. zrb/task/scheduler.py +2 -0
  149. zrb/task/tcp_check.py +2 -0
  150. zrb/task_status/task_status.py +4 -3
  151. zrb/util/cmd/command.py +1 -0
  152. zrb/util/file.py +7 -1
  153. zrb/util/llm/tool.py +3 -7
  154. {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/METADATA +2 -1
  155. zrb-1.0.0b3.dist-info/RECORD +307 -0
  156. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/any_client_method.py +0 -27
  157. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_usecase.py +0 -65
  158. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/api_client.py +0 -6
  159. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/direct_client.py +0 -6
  160. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/client/factory.py +0 -9
  161. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/app.py +0 -20
  162. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_usecase.py +0 -245
  163. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/any_client.py +0 -33
  164. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/api_client.py +0 -7
  165. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/direct_client.py +0 -6
  166. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/factory.py +0 -9
  167. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_user_table.py +0 -37
  168. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase.py +0 -53
  169. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_usecase_factory.py +0 -6
  170. zrb/runner/web_config.py +0 -274
  171. zrb/runner/web_controller/home_page/controller.py +0 -33
  172. zrb/runner/web_controller/login_page/controller.py +0 -25
  173. zrb/runner/web_controller/logout_page/controller.py +0 -26
  174. zrb-1.0.0a21.dist-info/RECORD +0 -244
  175. /zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/{create_column_task.py → add_column_task.py} +0 -0
  176. /zrb/{runner/web_controller → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission}/__init__.py +0 -0
  177. /zrb/{runner/web_controller/group_info_page → builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role}/__init__.py +0 -0
  178. /zrb/runner/{web_controller/home_page → web_route}/__init__.py +0 -0
  179. /zrb/runner/{web_controller/session_page → web_route/home_page}/__init__.py +0 -0
  180. /zrb/runner/{web_controller → web_route}/home_page/view.html +0 -0
  181. /zrb/runner/{web_controller → web_route}/login_page/view.html +0 -0
  182. /zrb/runner/{web_controller → web_route}/logout_page/view.html +0 -0
  183. /zrb/runner/{web_controller/group_info_page → web_route/node_page/group}/view.html +0 -0
  184. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/partial/input.html +0 -0
  185. /zrb/runner/{web_controller/session_page → web_route/node_page/task}/view.html +0 -0
  186. /zrb/runner/{refresh-token.template.js → web_route/static/refresh-token.template.js} +0 -0
  187. /zrb/runner/{web_controller/static → web_route/static/resources}/common.css +0 -0
  188. /zrb/runner/{web_controller/static → web_route/static/resources}/favicon-32x32.png +0 -0
  189. /zrb/runner/{web_controller/static → web_route/static/resources}/login/event.js +0 -0
  190. /zrb/runner/{web_controller/static → web_route/static/resources}/logout/event.js +0 -0
  191. /zrb/runner/{web_controller/static → web_route/static/resources}/pico.min.css +0 -0
  192. /zrb/runner/{web_controller/static → web_route/static/resources}/session/common-util.js +0 -0
  193. /zrb/runner/{web_controller/static → web_route/static/resources}/session/past-session.js +0 -0
  194. {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/WHEEL +0 -0
  195. {zrb-1.0.0a21.dist-info → zrb-1.0.0b3.dist-info}/entry_points.txt +0 -0
@@ -1,17 +1,35 @@
1
+ import datetime
2
+ from typing import Any, Callable
3
+
4
+ import ulid
1
5
  from my_app_name.common.base_db_repository import BaseDBRepository
2
6
  from my_app_name.common.error import NotFoundError
7
+ from my_app_name.config import (
8
+ APP_AUTH_GUEST_USER,
9
+ APP_AUTH_GUEST_USER_PERMISSIONS,
10
+ APP_AUTH_SUPER_USER,
11
+ APP_AUTH_SUPER_USER_PASSWORD,
12
+ APP_MAX_PARALLEL_SESSION,
13
+ APP_SESSION_EXPIRE_MINUTES,
14
+ )
3
15
  from my_app_name.module.auth.service.user.repository.user_repository import (
4
16
  UserRepository,
5
17
  )
18
+ from my_app_name.schema.permission import Permission
19
+ from my_app_name.schema.role import Role, RolePermission
20
+ from my_app_name.schema.session import Session, SessionResponse
6
21
  from my_app_name.schema.user import (
7
22
  User,
8
23
  UserCreateWithAudit,
9
24
  UserResponse,
25
+ UserRole,
10
26
  UserUpdateWithAudit,
11
27
  )
12
28
  from passlib.context import CryptContext
13
- from sqlalchemy.ext.asyncio import AsyncSession
14
- from sqlmodel import Session, select
29
+ from sqlalchemy.engine import Engine
30
+ from sqlalchemy.ext.asyncio import AsyncEngine
31
+ from sqlalchemy.sql import ClauseElement, ColumnElement, Select
32
+ from sqlmodel import SQLModel, delete, insert, select
15
33
 
16
34
  # Password hashing context
17
35
  pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@@ -32,14 +50,163 @@ class UserDBRepository(
32
50
  entity_name = "user"
33
51
  column_preprocessors = {"password": hash_password}
34
52
 
53
+ def __init__(
54
+ self,
55
+ engine: Engine | AsyncEngine,
56
+ super_user_username: str = APP_AUTH_SUPER_USER,
57
+ super_user_password: str = APP_AUTH_SUPER_USER_PASSWORD,
58
+ guest_user_username: str = APP_AUTH_GUEST_USER,
59
+ guest_user_password: str = APP_AUTH_SUPER_USER_PASSWORD,
60
+ guest_user_permission_names: list[str] = APP_AUTH_GUEST_USER_PERMISSIONS,
61
+ max_parallel_session: int = APP_MAX_PARALLEL_SESSION,
62
+ session_expire_minutes: int = APP_SESSION_EXPIRE_MINUTES,
63
+ filter_param_parser: (
64
+ Callable[[SQLModel, str], list[ClauseElement]] | None
65
+ ) = None,
66
+ sort_param_parser: Callable[[SQLModel, str], list[ColumnElement]] | None = None,
67
+ ):
68
+ super().__init__(
69
+ engine=engine,
70
+ filter_param_parser=filter_param_parser,
71
+ sort_param_parser=sort_param_parser,
72
+ )
73
+ self._super_user_username = super_user_username
74
+ self._super_user_passwored = super_user_password
75
+ self._guest_user_username = guest_user_username
76
+ self._guest_user_password = guest_user_password
77
+ self._guest_user_permission_names = guest_user_permission_names
78
+ self._max_parallel_session = max_parallel_session
79
+ self._session_expire_minutes = session_expire_minutes
80
+ self._super_user: User | None = None
81
+ self._guest_user: User | None = None
82
+
83
+ def _select(self) -> Select:
84
+ return (
85
+ select(User, Role, Permission, Session)
86
+ .join(UserRole, UserRole.user_id == User.id, isouter=True)
87
+ .join(Role, Role.id == UserRole.role_id, isouter=True)
88
+ .join(RolePermission, RolePermission.role_id == Role.id, isouter=True)
89
+ .join(
90
+ Permission, Permission.id == RolePermission.permission_id, isouter=True
91
+ )
92
+ .join(Session, Session.user_id == User.id)
93
+ )
94
+
95
+ def _rows_to_responses(self, rows: list[tuple[Any, ...]]) -> list[UserResponse]:
96
+ user_map: dict[str, dict[str, Any]] = {}
97
+ user_role_map: dict[str, list[str]] = {}
98
+ user_permission_map: dict[str, list[str]] = {}
99
+ for user, role, permission, _ in rows:
100
+ if user.id not in user_map:
101
+ user_map[user.id] = {"user": user, "roles": [], "permissions": []}
102
+ user_role_map[user.id] = []
103
+ user_permission_map[user.id] = []
104
+ if role is not None and role.id not in user_role_map[user.id]:
105
+ user_role_map[user.id].append(role.id)
106
+ user_map[user.id]["roles"].append(role.model_dump())
107
+ if (
108
+ permission is not None
109
+ and permission.id not in user_permission_map[user.id]
110
+ ):
111
+ user_permission_map[user.id].append(permission.id)
112
+ user_map[user.id]["permissions"].append(permission.model_dump())
113
+ return [
114
+ UserResponse(
115
+ **data["user"].model_dump(),
116
+ roles=list(data["roles"]),
117
+ permissions=list(data["permissions"]),
118
+ )
119
+ for data in user_map.values()
120
+ ]
121
+
122
+ async def add_roles(self, data: dict[str, list[str]], created_by: str):
123
+ now = datetime.datetime.now(datetime.timezone.utc)
124
+ data_dict_list: list[dict[str, Any]] = []
125
+ for user_id, role_ids in data.items():
126
+ for role_id in role_ids:
127
+ data_dict_list.append(
128
+ self._model_to_data_dict(
129
+ UserRole(
130
+ id=ulid.new().str,
131
+ user_id=user_id,
132
+ role_id=role_id,
133
+ created_at=now,
134
+ created_by=created_by,
135
+ )
136
+ )
137
+ )
138
+ async with self._session_scope() as session:
139
+ await self._execute_statement(
140
+ session, insert(UserRole).values(data_dict_list)
141
+ )
142
+
143
+ async def remove_all_roles(self, user_ids: list[str] = []):
144
+ async with self._session_scope() as session:
145
+ await self._execute_statement(
146
+ session,
147
+ delete(UserRole).where(UserRole.user_id._in(user_ids)),
148
+ )
149
+
35
150
  async def get_by_credentials(self, username: str, password: str) -> UserResponse:
36
- statement = select(User).where(User.username == username)
37
- if self.is_async:
38
- async with AsyncSession(self.engine) as session:
39
- user = await session.exec(statement).first
40
- else:
41
- with Session(self.engine) as session:
42
- user = session.exec(statement).first()
43
- if not user or not pwd_context.verify(password, user.hashed_password):
44
- raise NotFoundError(f"{self.entity_name} not found")
45
- return self._to_response(user)
151
+ rows = await self._select_to_response(
152
+ lambda q: q.where(
153
+ User.username == username, User.password == hash_password(password)
154
+ )
155
+ )
156
+ return self._ensure_one(rows)
157
+
158
+ async def get_by_token(self, token: str) -> UserResponse:
159
+ rows = await self._select_tor_response(
160
+ lambda q: q.where(Session.token == token)
161
+ )
162
+ return self._ensure_one(rows)
163
+
164
+ async def add_token(self, user_id: str, token: str):
165
+ async with self._session_scope() as session:
166
+ await self._execute_statement(
167
+ session,
168
+ insert(Session).values(
169
+ {
170
+ "id": ulid.new().str,
171
+ "user_id": user_id,
172
+ "token": token,
173
+ "created_by": "system",
174
+ "created_at": datetime.datetime.now(datetime.timezone.utc),
175
+ }
176
+ ),
177
+ )
178
+
179
+ async def remove_token(self, user_id: str, token: str):
180
+ async with self._session_scope() as session:
181
+ await self._execute_statement(
182
+ session,
183
+ delete(Session).where(
184
+ Session.token == token, Session.user_id == user_id
185
+ ),
186
+ )
187
+
188
+ async def get_sessions(self, user_id: str) -> list[SessionResponse]:
189
+ async with self._session_scope() as session:
190
+ statement = select(Session).where(Session.user_id == user_id)
191
+ result = await self._execute_statement(session, statement)
192
+ return [
193
+ SessionResponse(**session.model_dump())
194
+ for session in result.scalars().all()
195
+ ]
196
+
197
+ async def remove_session(self, user_id: str, session_id: str) -> SessionResponse:
198
+ async with self._session_scope() as session:
199
+ statement = select(Session).where(
200
+ Session.user_id == user_id, Session.id == session_id
201
+ )
202
+ result = await self._execute_statement(session, statement)
203
+ session = result.scalar_one_or_none()
204
+ if not session:
205
+ raise NotFoundError(f"{self.entity_name} not found")
206
+ await self._execute_statement(
207
+ session,
208
+ delete(Session).where(
209
+ Session.id == session_id, Session.user_id == user_id
210
+ ),
211
+ )
212
+ return SessionResponse(**session.model_dump())
@@ -1,5 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
+ from my_app_name.schema.session import SessionResponse
3
4
  from my_app_name.schema.user import (
4
5
  User,
5
6
  UserCreateWithAudit,
@@ -9,34 +10,83 @@ from my_app_name.schema.user import (
9
10
 
10
11
 
11
12
  class UserRepository(ABC):
13
+
14
+ @abstractmethod
15
+ async def get_by_id(self, id: str) -> UserResponse:
16
+ """Get user by id"""
17
+
18
+ @abstractmethod
19
+ async def get_by_ids(self, id_list: list[str]) -> UserResponse:
20
+ """Get users by ids"""
21
+
22
+ @abstractmethod
23
+ async def add_roles(self, data: dict[str, list[str]], created_by: str):
24
+ """Add roles to user"""
25
+
26
+ @abstractmethod
27
+ async def remove_all_roles(self, user_ids: list[str] = []):
28
+ """Remove roles from user"""
29
+
12
30
  @abstractmethod
13
- async def create(self, user_data: UserCreateWithAudit) -> UserResponse:
14
- pass
31
+ async def get(
32
+ self,
33
+ page: int = 1,
34
+ page_size: int = 10,
35
+ filter: str | None = None,
36
+ sort: str | None = None,
37
+ ) -> list[User]:
38
+ """Get users by filter and sort"""
15
39
 
16
40
  @abstractmethod
17
- async def get_by_id(self, user_id: str) -> UserResponse:
18
- pass
41
+ async def count(self, filter: str | None = None) -> int:
42
+ """Count users by filter"""
19
43
 
20
44
  @abstractmethod
21
- async def get_all(self) -> list[User]:
22
- pass
45
+ async def create(self, data: UserCreateWithAudit) -> User:
46
+ """Create a new user"""
23
47
 
24
48
  @abstractmethod
25
- async def update(
26
- self, user_id: str, user_data: UserUpdateWithAudit
27
- ) -> UserResponse:
28
- pass
49
+ async def create_bulk(self, data: list[UserCreateWithAudit]) -> list[User]:
50
+ """Create some users"""
29
51
 
30
52
  @abstractmethod
31
- async def delete(self, user_id: str) -> UserResponse:
32
- pass
53
+ async def delete(self, id: str) -> User:
54
+ """Delete a user"""
33
55
 
34
56
  @abstractmethod
35
- async def create_bulk(
36
- self, user_data_list: list[UserCreateWithAudit]
37
- ) -> list[UserResponse]:
38
- pass
57
+ async def delete_bulk(self, id_list: list[str]) -> list[User]:
58
+ """Delete some users"""
59
+
60
+ @abstractmethod
61
+ async def update(self, id: str, data: UserUpdateWithAudit) -> User:
62
+ """Update a user"""
63
+
64
+ @abstractmethod
65
+ async def update_bulk(
66
+ self, id_list: list[str], data: UserUpdateWithAudit
67
+ ) -> list[User]:
68
+ """Update some users"""
39
69
 
40
70
  @abstractmethod
41
71
  async def get_by_credentials(self, username: str, password: str) -> UserResponse:
42
- pass
72
+ """Get user by credential"""
73
+
74
+ @abstractmethod
75
+ async def get_by_token(self, token: str) -> UserResponse:
76
+ """Get user by token"""
77
+
78
+ @abstractmethod
79
+ async def add_token(self, user_id: str, token: str):
80
+ """Add token to user"""
81
+
82
+ @abstractmethod
83
+ async def remove_token(self, user_id: str, token: str):
84
+ """Remove token from user"""
85
+
86
+ @abstractmethod
87
+ async def get_sessions(self, user_id: str) -> list[SessionResponse]:
88
+ """Get sessions"""
89
+
90
+ @abstractmethod
91
+ async def remove_session(self, user_id: str, session_id: str) -> SessionResponse:
92
+ """Remove a session"""
@@ -1,4 +1,4 @@
1
- from my_app_name.common.db_engine import engine
1
+ from my_app_name.common.db_engine_factory import db_engine
2
2
  from my_app_name.config import APP_REPOSITORY_TYPE
3
3
  from my_app_name.module.auth.service.user.repository.user_db_repository import (
4
4
  UserDBRepository,
@@ -8,6 +8,6 @@ from my_app_name.module.auth.service.user.repository.user_repository import (
8
8
  )
9
9
 
10
10
  if APP_REPOSITORY_TYPE == "db":
11
- user_repository: UserRepository = UserDBRepository(engine)
11
+ user_repository: UserRepository = UserDBRepository(db_engine)
12
12
  else:
13
13
  user_repository: UserRepository = None
@@ -0,0 +1,127 @@
1
+ from logging import Logger
2
+
3
+ from my_app_name.common.base_service import BaseService
4
+ from my_app_name.module.auth.service.user.repository.user_repository import (
5
+ UserRepository,
6
+ )
7
+ from my_app_name.schema.user import (
8
+ MultipleUserResponse,
9
+ UserCreateWithRolesAndAudit,
10
+ UserResponse,
11
+ UserUpdateWithRolesAndAudit,
12
+ )
13
+
14
+
15
+ class UserService(BaseService):
16
+
17
+ def __init__(self, logger: Logger, user_repository: UserRepository):
18
+ super().__init__(logger)
19
+ self.user_repository = user_repository
20
+
21
+ @BaseService.route(
22
+ "/api/v1/users/{user_id}",
23
+ methods=["get"],
24
+ response_model=UserResponse,
25
+ )
26
+ async def get_user_by_id(self, user_id: str) -> UserResponse:
27
+ return await self.user_repository.get_by_id(user_id)
28
+
29
+ @BaseService.route(
30
+ "/api/v1/users",
31
+ methods=["get"],
32
+ response_model=MultipleUserResponse,
33
+ )
34
+ async def get_users(
35
+ self,
36
+ page: int = 1,
37
+ page_size: int = 10,
38
+ sort: str | None = None,
39
+ filter: str | None = None,
40
+ ) -> MultipleUserResponse:
41
+ users = await self.user_repository.get(page, page_size, filter, sort)
42
+ count = await self.user_repository.count(filter)
43
+ return MultipleUserResponse(data=users, count=count)
44
+
45
+ @BaseService.route(
46
+ "/api/v1/users/bulk",
47
+ methods=["post"],
48
+ response_model=list[UserResponse],
49
+ )
50
+ async def create_user_bulk(
51
+ self, data: list[UserCreateWithRolesAndAudit]
52
+ ) -> list[UserResponse]:
53
+ role_ids = [row.get_role_ids() for row in data]
54
+ data = [row.get_user_create_with_audit() for row in data]
55
+ users = await self.user_repository.create_bulk(data)
56
+ if len(users) > 0:
57
+ created_by = users[0].created_by
58
+ await self.user_repository.add_roles(
59
+ data={user.id: role_ids[i] for i, user in enumerate(data)},
60
+ created_by=created_by,
61
+ )
62
+ return await self.user_repository.get_by_ids([user.id for user in users])
63
+
64
+ @BaseService.route(
65
+ "/api/v1/users",
66
+ methods=["post"],
67
+ response_model=UserResponse,
68
+ )
69
+ async def create_user(self, data: UserCreateWithRolesAndAudit) -> UserResponse:
70
+ role_ids = data.get_role_ids()
71
+ data = data.get_user_create_with_audit()
72
+ user = await self.user_repository.create(data)
73
+ await self.user_repository.add_roles(
74
+ data={user.id: role_ids}, created_by=user.created_by
75
+ )
76
+ return await self.user_repository.get_by_id(user.id)
77
+
78
+ @BaseService.route(
79
+ "/api/v1/users/bulk",
80
+ methods=["put"],
81
+ response_model=UserResponse,
82
+ )
83
+ async def update_user_bulk(
84
+ self, user_ids: list[str], data: UserUpdateWithRolesAndAudit
85
+ ) -> UserResponse:
86
+ role_ids = [row.get_role_ids() for row in data]
87
+ data = [row.get_user_create_with_audit() for row in data]
88
+ users = await self.user_repository.update_bulk(user_ids, data)
89
+ if len(users) > 0:
90
+ updated_by = users[0].updated_by
91
+ await self.user_repository.remove_all_roles([user.id for user in users])
92
+ await self.user_repository.add_roles(
93
+ data={user.id: role_ids[i] for i, user in enumerate(data)},
94
+ updated_by=updated_by,
95
+ )
96
+ return await self.user_repository.get_by_ids([user.id for user in users])
97
+
98
+ @BaseService.route(
99
+ "/api/v1/users/{user_id}",
100
+ methods=["put"],
101
+ response_model=UserResponse,
102
+ )
103
+ async def update_user(
104
+ self, user_id: str, data: UserUpdateWithRolesAndAudit
105
+ ) -> UserResponse:
106
+ user = await self.user_repository.update(user_id, data)
107
+ return await self.user_repository.get_by_id(user.id)
108
+
109
+ @BaseService.route(
110
+ "/api/v1/users/{user_id}",
111
+ methods=["delete"],
112
+ response_model=UserResponse,
113
+ )
114
+ async def delete_user_bulk(
115
+ self, user_ids: list[str], deleted_by: str
116
+ ) -> UserResponse:
117
+ users = await self.user_repository.delete_bulk(user_ids)
118
+ return await self.user_repository.get_by_ids([user.id for user in users])
119
+
120
+ @BaseService.route(
121
+ "/api/v1/users/{user_id}",
122
+ methods=["delete"],
123
+ response_model=UserResponse,
124
+ )
125
+ async def delete_user(self, user_id: str, deleted_by: str) -> UserResponse:
126
+ user = await self.user_repository.delete(user_id)
127
+ return await self.user_repository.get_by_id(user.id)
@@ -0,0 +1,7 @@
1
+ from my_app_name.common.logger_factory import logger
2
+ from my_app_name.module.auth.service.user.repository.user_repository_factory import (
3
+ user_repository,
4
+ )
5
+ from my_app_name.module.auth.service.user.user_service import UserService
6
+
7
+ user_service = UserService(logger, user_repository=user_repository)
@@ -1,11 +1,42 @@
1
- from fastapi import FastAPI
2
- from my_app_name.common.app import app
1
+ import os
2
+
3
+ from fastapi import FastAPI, HTTPException, Request
4
+ from fastapi.exception_handlers import http_exception_handler
5
+ from fastapi.responses import HTMLResponse
6
+ from my_app_name.common.app_factory import app
3
7
  from my_app_name.common.schema import BasicResponse
4
- from my_app_name.config import APP_MAIN_MODULE, APP_MODE, APP_MODULES
8
+ from my_app_name.config import (
9
+ APP_GATEWAY_VIEW_PATH,
10
+ APP_MAIN_MODULE,
11
+ APP_MODE,
12
+ APP_MODULES,
13
+ )
5
14
  from my_app_name.module.gateway.subroute.auth import serve_auth_route
15
+ from my_app_name.module.gateway.util.view import render, render_error
16
+
17
+
18
+ def serve_route(app: FastAPI):
19
+ if APP_MODE != "monolith" and "gateway" not in APP_MODULES:
20
+ return
21
+ if APP_MODE == "monolith" or APP_MAIN_MODULE == "gateway":
22
+ _serve_health_check(app)
23
+ _serve_readiness_check(app)
24
+ _serve_homepage(app)
25
+ _handle_404(app)
26
+
27
+ # Serve auth routes
28
+ serve_auth_route(app)
6
29
 
7
30
 
8
- def serve_health_check(app: FastAPI):
31
+ def _serve_homepage(app: FastAPI):
32
+ @app.get("/", include_in_schema=False)
33
+ def home_page():
34
+ return render(
35
+ view_path=os.path.join(APP_GATEWAY_VIEW_PATH, "content", "homepage.html")
36
+ )
37
+
38
+
39
+ def _serve_health_check(app: FastAPI):
9
40
  @app.api_route("/health", methods=["GET", "HEAD"], response_model=BasicResponse)
10
41
  async def health():
11
42
  """
@@ -14,7 +45,7 @@ def serve_health_check(app: FastAPI):
14
45
  return BasicResponse(message="ok")
15
46
 
16
47
 
17
- def serve_readiness_check(app: FastAPI):
48
+ def _serve_readiness_check(app: FastAPI):
18
49
  @app.api_route("/readiness", methods=["GET", "HEAD"], response_model=BasicResponse)
19
50
  async def readiness():
20
51
  """
@@ -23,15 +54,13 @@ def serve_readiness_check(app: FastAPI):
23
54
  return BasicResponse(message="ok")
24
55
 
25
56
 
26
- def serve_route(app: FastAPI):
27
- if APP_MODE != "monolith" and "gateway" not in APP_MODULES:
28
- return
29
- if APP_MODE == "monolith" or APP_MAIN_MODULE == "gateway":
30
- serve_health_check(app)
31
- serve_readiness_check(app)
32
-
33
- # Serve Auth Route
34
- serve_auth_route(app)
57
+ def _handle_404(app: FastAPI):
58
+ @app.exception_handler(404)
59
+ async def default_404(request: Request, exc: HTTPException) -> HTMLResponse:
60
+ if request.url.path.startswith("/api"):
61
+ # Re-raise the exception to let FastAPI handle it
62
+ return await http_exception_handler(request, exc)
63
+ return render_error(error_message="Not found", status_code=404)
35
64
 
36
65
 
37
66
  serve_route(app)