fast-mu-builder 0.1.0.3__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 changed (96) hide show
  1. fast_mu_builder-0.1.0.3/LICENSE +19 -0
  2. fast_mu_builder-0.1.0.3/PKG-INFO +102 -0
  3. fast_mu_builder-0.1.0.3/README.md +64 -0
  4. fast_mu_builder-0.1.0.3/fast_mu_builder/__init__.py +0 -0
  5. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/__init__.py +0 -0
  6. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/api.py +72 -0
  7. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/gql_controller.py +219 -0
  8. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/request.py +17 -0
  9. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/response.py +16 -0
  10. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/service.py +230 -0
  11. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/templates/__init__.py +0 -0
  12. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/templates/attachment_mutations.py +6 -0
  13. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/templates/attachment_queries.py +7 -0
  14. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/templates/gql_input_template.py +9 -0
  15. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/templates/gql_type_template.py +9 -0
  16. fast_mu_builder-0.1.0.3/fast_mu_builder/attach/templates/graphql_api_template.py +217 -0
  17. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/__init__.py +0 -0
  18. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/auth.py +190 -0
  19. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/context.py +114 -0
  20. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/jwt_handler.py +96 -0
  21. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/middleware.py +142 -0
  22. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/mixings/HeadshipMixing.py +81 -0
  23. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/mixings/__init__.py +0 -0
  24. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/redis.py +260 -0
  25. fast_mu_builder-0.1.0.3/fast_mu_builder/auth/session_handler.py +58 -0
  26. fast_mu_builder-0.1.0.3/fast_mu_builder/commands/__init__.py +0 -0
  27. fast_mu_builder-0.1.0.3/fast_mu_builder/commands/graphql.py +76 -0
  28. fast_mu_builder-0.1.0.3/fast_mu_builder/common/__init__.py +0 -0
  29. fast_mu_builder-0.1.0.3/fast_mu_builder/common/models.py +0 -0
  30. fast_mu_builder-0.1.0.3/fast_mu_builder/common/request/__init__.py +0 -0
  31. fast_mu_builder-0.1.0.3/fast_mu_builder/common/request/schemas.py +43 -0
  32. fast_mu_builder-0.1.0.3/fast_mu_builder/common/response/__init__.py +0 -0
  33. fast_mu_builder-0.1.0.3/fast_mu_builder/common/response/codes.py +35 -0
  34. fast_mu_builder-0.1.0.3/fast_mu_builder/common/response/schemas.py +27 -0
  35. fast_mu_builder-0.1.0.3/fast_mu_builder/common/schemas.py +9 -0
  36. fast_mu_builder-0.1.0.3/fast_mu_builder/common/templates/module_init_.py +11 -0
  37. fast_mu_builder-0.1.0.3/fast_mu_builder/common/validation/__init__.py +0 -0
  38. fast_mu_builder-0.1.0.3/fast_mu_builder/common/validation/decorators.py +103 -0
  39. fast_mu_builder-0.1.0.3/fast_mu_builder/common/validation/field_validator.py +789 -0
  40. fast_mu_builder-0.1.0.3/fast_mu_builder/common/validation/rules.py +536 -0
  41. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/__init__.py +0 -0
  42. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/controller.py +278 -0
  43. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/gql_controller.py +709 -0
  44. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/graphql_api_gen.py +301 -0
  45. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/graphql_schema_gen.py +152 -0
  46. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/rest_api.py +66 -0
  47. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/__init__.py +0 -0
  48. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/controller_template.py +8 -0
  49. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/gql_input_template.py +11 -0
  50. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/gql_schemas_template.py +11 -0
  51. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/gql_type_template.py +8 -0
  52. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/graphql_api_template.py +132 -0
  53. fast_mu_builder-0.1.0.3/fast_mu_builder/crud/templates/model_template.py +6 -0
  54. fast_mu_builder-0.1.0.3/fast_mu_builder/esb/__init__.py +0 -0
  55. fast_mu_builder-0.1.0.3/fast_mu_builder/esb/esb.py +366 -0
  56. fast_mu_builder-0.1.0.3/fast_mu_builder/esb/schemas.py +39 -0
  57. fast_mu_builder-0.1.0.3/fast_mu_builder/models/__init__.py +305 -0
  58. fast_mu_builder-0.1.0.3/fast_mu_builder/models/attachment.py +20 -0
  59. fast_mu_builder-0.1.0.3/fast_mu_builder/models/notification.py +84 -0
  60. fast_mu_builder-0.1.0.3/fast_mu_builder/models/report_logs/__init__.py +1 -0
  61. fast_mu_builder-0.1.0.3/fast_mu_builder/models/report_logs/audit_log.py +21 -0
  62. fast_mu_builder-0.1.0.3/fast_mu_builder/models/uaa.py +252 -0
  63. fast_mu_builder-0.1.0.3/fast_mu_builder/models/workflow.py +359 -0
  64. fast_mu_builder-0.1.0.3/fast_mu_builder/notifications/__init__.py +0 -0
  65. fast_mu_builder-0.1.0.3/fast_mu_builder/notifications/service.py +105 -0
  66. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/__init__.py +0 -0
  67. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/enums.py +25 -0
  68. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/env.py +41 -0
  69. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/error_logging.py +87 -0
  70. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/file.py +37 -0
  71. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/helpers/__init__.py +0 -0
  72. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/helpers/log_activity.py +26 -0
  73. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/helpers/request.py +17 -0
  74. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/metrics/__init__.py +1 -0
  75. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/metrics/db_metrics.py +109 -0
  76. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/mutable_settings.py +192 -0
  77. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/reflection.py +61 -0
  78. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/sentry.py +64 -0
  79. fast_mu_builder-0.1.0.3/fast_mu_builder/utils/str_helpers.py +190 -0
  80. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/__init__.py +0 -0
  81. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/exceptions.py +47 -0
  82. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/gql_controller.py +162 -0
  83. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/request.py +8 -0
  84. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/response.py +16 -0
  85. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/templates/__init__.py +0 -0
  86. fast_mu_builder-0.1.0.3/fast_mu_builder/workflow/templates/gql_type_template.py +8 -0
  87. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/PKG-INFO +102 -0
  88. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/SOURCES.txt +95 -0
  89. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/dependency_links.txt +1 -0
  90. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/entry_points.txt +2 -0
  91. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/not-zip-safe +1 -0
  92. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/requires.txt +18 -0
  93. fast_mu_builder-0.1.0.3/fast_mu_builder.egg-info/top_level.txt +1 -0
  94. fast_mu_builder-0.1.0.3/pyproject.toml +59 -0
  95. fast_mu_builder-0.1.0.3/setup.cfg +20 -0
  96. fast_mu_builder-0.1.0.3/setup.py +55 -0
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: fast-mu-builder
3
+ Version: 0.1.0.3
4
+ Summary: FastAPI Builder with Tortoise ORM support for MU
5
+ Home-page: https://bitbucket.org/external-dev-mzumbe/fast-mu-builder
6
+ Author: Japhary Juma Hamisi
7
+ Author-email: Japhary Juma Hamisi <japharyjuma@gmail.com>, Shija Ntula <abuukauthar13@gmail.com>, Juma Ludanga <ludangajn@gmail.com>
8
+ License: Other License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Framework :: FastAPI
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.11
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: fastapi==0.116.1
17
+ Requires-Dist: tortoise-orm[asyncpg]==0.20.1
18
+ Requires-Dist: pydantic==2.11.7
19
+ Requires-Dist: httpx==0.28.1
20
+ Requires-Dist: strawberry-graphql[fastapi]==0.278.1
21
+ Requires-Dist: jinja2==3.1.6
22
+ Requires-Dist: pluralize==20240519.3
23
+ Requires-Dist: PyJWT==2.10.1
24
+ Requires-Dist: redis==5.2.1
25
+ Requires-Dist: minio==7.2.16
26
+ Requires-Dist: aiofiles==24.1.0
27
+ Requires-Dist: bullmq==2.15.0
28
+ Requires-Dist: sentry_sdk==2.34.1
29
+ Requires-Dist: cryptography==45.0.6
30
+ Requires-Dist: python-decouple==3.8
31
+ Requires-Dist: celery[redis]==5.5.3
32
+ Requires-Dist: xlsxwriter==3.2.5
33
+ Requires-Dist: fpdf2==2.8.3
34
+ Dynamic: author
35
+ Dynamic: home-page
36
+ Dynamic: license-file
37
+ Dynamic: requires-python
38
+
39
+ Building The package:
40
+
41
+ python setup.py sdist bdist_wheel
42
+
43
+ install the package to your app:
44
+
45
+ pip install path/to/wheel file
46
+
47
+ generating Graphql schemas
48
+
49
+ graphql gen:crud-api <module-name> --model Model1,Model2,Model3
50
+
51
+ generating graphql schemas with attachments
52
+
53
+ graphql gen:crud-api <module-name> --model ModelName --with-attachment
54
+
55
+ for attachments you must have minio server and these .env configs:
56
+
57
+ MINIO_SERVER=ip_address:api_port
58
+ MINIO_BUCKET=backet-name
59
+ MINIO_ACCESS_KEY=minio-username
60
+ MINIO_SECRETE_KEY=minio-password
61
+ MINIO_SECURE=False
62
+
63
+ You must initialize the MinioService before using it by:
64
+
65
+ @app.on_event("startup")
66
+ async def startup_event():
67
+ # Run the init_minio_service in the background without blocking the app startup
68
+ asyncio.create_task(init_minio_service())
69
+
70
+ async def init_minio_service():
71
+ minio_service = MinioService()
72
+ try:
73
+ await minio_service.init(
74
+ server=config('MINIO_SERVER'),
75
+ access_key=config('MINIO_ACCESS_KEY'),
76
+ secret_key=config('MINIO_SECRETE_KEY'),
77
+ bucket_name=config('MINIO_BUCKET'),
78
+ secure=config('MINIO_SECURE', cast=bool)
79
+ )
80
+ except Exception as e:
81
+ print(f"Error initializing MinIO service: {e}")
82
+
83
+ Making it async will easy your app booting process.
84
+
85
+ If you need to use the service out of generated crud apis yo do:
86
+
87
+ For uploading:
88
+
89
+ file_location, upload_error = await MinioService.get_instance().upload_file(
90
+ file_name=f"path/to/file.extension",
91
+ file_data=base64_decoded_file,
92
+ content_type=attachment.file.content_type
93
+ )
94
+
95
+ For downloading:
96
+
97
+ base64_content = await MinioService.get_instance().download_file("path/to/file.extension")
98
+
99
+ For deleting:
100
+
101
+ result = MinioService.get_instance().delete_file("path/to/file.extension")
102
+
@@ -0,0 +1,64 @@
1
+ Building The package:
2
+
3
+ python setup.py sdist bdist_wheel
4
+
5
+ install the package to your app:
6
+
7
+ pip install path/to/wheel file
8
+
9
+ generating Graphql schemas
10
+
11
+ graphql gen:crud-api <module-name> --model Model1,Model2,Model3
12
+
13
+ generating graphql schemas with attachments
14
+
15
+ graphql gen:crud-api <module-name> --model ModelName --with-attachment
16
+
17
+ for attachments you must have minio server and these .env configs:
18
+
19
+ MINIO_SERVER=ip_address:api_port
20
+ MINIO_BUCKET=backet-name
21
+ MINIO_ACCESS_KEY=minio-username
22
+ MINIO_SECRETE_KEY=minio-password
23
+ MINIO_SECURE=False
24
+
25
+ You must initialize the MinioService before using it by:
26
+
27
+ @app.on_event("startup")
28
+ async def startup_event():
29
+ # Run the init_minio_service in the background without blocking the app startup
30
+ asyncio.create_task(init_minio_service())
31
+
32
+ async def init_minio_service():
33
+ minio_service = MinioService()
34
+ try:
35
+ await minio_service.init(
36
+ server=config('MINIO_SERVER'),
37
+ access_key=config('MINIO_ACCESS_KEY'),
38
+ secret_key=config('MINIO_SECRETE_KEY'),
39
+ bucket_name=config('MINIO_BUCKET'),
40
+ secure=config('MINIO_SECURE', cast=bool)
41
+ )
42
+ except Exception as e:
43
+ print(f"Error initializing MinIO service: {e}")
44
+
45
+ Making it async will easy your app booting process.
46
+
47
+ If you need to use the service out of generated crud apis yo do:
48
+
49
+ For uploading:
50
+
51
+ file_location, upload_error = await MinioService.get_instance().upload_file(
52
+ file_name=f"path/to/file.extension",
53
+ file_data=base64_decoded_file,
54
+ content_type=attachment.file.content_type
55
+ )
56
+
57
+ For downloading:
58
+
59
+ base64_content = await MinioService.get_instance().download_file("path/to/file.extension")
60
+
61
+ For deleting:
62
+
63
+ result = MinioService.get_instance().delete_file("path/to/file.extension")
64
+
File without changes
@@ -0,0 +1,72 @@
1
+ # from typing import List, Optional
2
+ # from fastapi import APIRouter, File, Form, UploadFile, Depends
3
+ # from fastapi.params import Query
4
+
5
+ # from fast_mu_builder.common.response.codes import ResponseCode
6
+ # from fast_mu_builder.common.response.schemas import ApiResponse
7
+ # from src.modules.auth.permission_middleware import get_current_user, authorize
8
+ # from src.modules.cash.schema import AttachmentCreate
9
+
10
+
11
+ # def build_attach_endpoints(router: APIRouter, path: str, controller, parent_id_name: str,
12
+ # model_verbose: Optional[str] = None,
13
+ # security_dependency=Depends(get_current_user)):
14
+ # @router.post(f"{path}/attachments/single/upload")
15
+ # @authorize([f"src.add_{model_verbose}"])
16
+ # def upload_single_attachment(parent_id: int, file: UploadFile = File(...), title: str = Form(...),
17
+ # current_user: dict = security_dependency):
18
+ # try:
19
+ # attachments: List[AttachmentCreate] = [
20
+ # AttachmentCreate(
21
+ # parent_id=parent_id,
22
+ # title=title,
23
+ # content=file
24
+ # )
25
+ # ]
26
+
27
+ # return controller.upload_attachments(attachments, parent_id_name)
28
+ # except Exception as e:
29
+ # return ApiResponse(
30
+ # status=False,
31
+ # code=ResponseCode.FAILURE,
32
+ # message=f"An error occured while uploading attachment. Retry",
33
+ # )
34
+
35
+ # @router.post(f"{path}/attachments/upload")
36
+ # @authorize([f"src.add_{model_verbose}"])
37
+ # def upload_attachments(parent_id: int, files: List[UploadFile] = File(...), titles: List[str] = Form(...),
38
+ # current_user: dict = security_dependency):
39
+ # try:
40
+ # attachments: List[AttachmentCreate] = []
41
+ # for index, file in enumerate(files):
42
+ # attachments.append(
43
+ # AttachmentCreate(
44
+ # parent_id=parent_id,
45
+ # title=titles[index],
46
+ # content=file
47
+ # )
48
+ # )
49
+ # return controller.upload_attachments(attachments, parent_id_name)
50
+ # except IndexError as e:
51
+ # return ApiResponse(
52
+ # status=False,
53
+ # code=ResponseCode.BAD_REQUEST,
54
+ # message=f"Titles and Files do not match {len(files)} - {len(titles)}",
55
+ # )
56
+
57
+ # @router.get(f"{path}/attachments/get")
58
+ # @authorize([f"src.view_{model_verbose}"])
59
+ # def get_attachments(parent_id: int, current_user: dict = security_dependency):
60
+ # return controller.get_attachments(parent_id, parent_id_name)
61
+
62
+ # @router.get(f"{path}/attachments/download")
63
+ # @authorize([f"src.change_{model_verbose}"])
64
+ # def download_attachment(attachment_id: int, current_user: dict = security_dependency):
65
+ # return controller.download_attachment(attachment_id)
66
+
67
+ # @router.delete(f"{path}/attachments/remove")
68
+ # @authorize([f"src.delete_{model_verbose}"])
69
+ # def delete_attachment(attachment_id: int, current_user: dict = security_dependency):
70
+ # return controller.remove_attachment(attachment_id)
71
+
72
+ # return router
@@ -0,0 +1,219 @@
1
+ import base64
2
+ from io import BytesIO
3
+ import os
4
+ from typing import Generic, Type
5
+
6
+ from tortoise.exceptions import DoesNotExist
7
+ from decouple import config
8
+ from fast_mu_builder.attach.request import AttachmentUpload
9
+ from fast_mu_builder.attach.service import MinioService
10
+ from fast_mu_builder.models.attachment import Attachment
11
+ from fast_mu_builder.utils.error_logging import log_exception
12
+ from minio import Minio
13
+ from minio.error import S3Error
14
+
15
+ from fast_mu_builder.common.response.codes import ResponseCode
16
+ from fast_mu_builder.common.response.schemas import ApiResponse, PaginatedResponse
17
+ from fast_mu_builder.common.schemas import ModelType
18
+
19
+
20
+ # MinIO setup
21
+ class AttachmentBaseController(Generic[ModelType]):
22
+ def __init__(self, model: Type[ModelType]):
23
+ self.model = model
24
+
25
+ async def upload_attachment(self, attachment_type_id, attachment: AttachmentUpload) -> ApiResponse:
26
+ try:
27
+ # Check if model exists
28
+ obj = await self.model.get(id=attachment_type_id)
29
+ # Decode the base64 string into binary data (bytes)
30
+ try:
31
+ decoded_file = base64.b64decode(attachment.file.content)
32
+ except Exception as decode_error:
33
+ return ApiResponse(
34
+ status=False,
35
+ code=ResponseCode.FAILURE,
36
+ message=f"Failed to decode base64 file: {decode_error}",
37
+ data=None
38
+ )
39
+
40
+ # Create Minio FIle name
41
+ file_name = f"{attachment.file.name}_{os.urandom(4).hex()}.{attachment.file.extension}"
42
+
43
+ file_location, upload_error = await MinioService.get_instance().upload_file(
44
+ file_name=f"{self.model.__name__}/{file_name}",
45
+ file_data=decoded_file,
46
+ content_type=attachment.file.content_type
47
+ )
48
+
49
+ if not file_location:
50
+ return ApiResponse(
51
+ status=False,
52
+ code=ResponseCode.FAILURE,
53
+ message=f"File upload failed: {upload_error}",
54
+ data=None
55
+ )
56
+
57
+ # Only after a successful upload, store the file path in the database
58
+ try:
59
+ attachment = await Attachment.create(
60
+ title=attachment.title,
61
+ description=attachment.description,
62
+ file_path=file_name,
63
+ mem_type=attachment.file.content_type,
64
+ attachment_type=self.model.__name__,
65
+ attachment_type_id=attachment_type_id
66
+ )
67
+ except Exception as db_error:
68
+ log_exception(Exception(db_error))
69
+ return ApiResponse(
70
+ status=False,
71
+ code=ResponseCode.BAD_REQUEST,
72
+ message=f"Database error: {db_error}",
73
+ data=None
74
+ )
75
+
76
+ # Return success response after successful upload and database insertion
77
+ return ApiResponse(
78
+ status=True,
79
+ code=ResponseCode.SUCCESS,
80
+ message="File uploaded and saved successfully!",
81
+ data=attachment
82
+ )
83
+ except DoesNotExist:
84
+ return ApiResponse(
85
+ status=False,
86
+ code=ResponseCode.NO_RECORD_FOUND,
87
+ message=f"{self.model.Meta.verbose_name} does not exist",
88
+ data=None
89
+ )
90
+ except Exception as e:
91
+ # Handle general exceptions
92
+ log_exception(Exception(e))
93
+ return ApiResponse(
94
+ status=False,
95
+ code=ResponseCode.FAILURE,
96
+ message=f"Unexpected error occurred, try again!",
97
+ data=None
98
+ )
99
+
100
+ async def delete_attachment(self, attachment_id: str) -> ApiResponse:
101
+ try:
102
+ attachment = await Attachment.get(id=attachment_id)
103
+ # Retrieve the file from MinIO
104
+ result = MinioService.get_instance().delete_file(f"{self.model.__name__}/{attachment.file_path}")
105
+
106
+ await attachment.delete()
107
+ # Return success response with file content
108
+ return ApiResponse(
109
+ status=True,
110
+ code=ResponseCode.SUCCESS,
111
+ message="Attachment deleted successfully!",
112
+ data=result
113
+ )
114
+ except DoesNotExist:
115
+ return ApiResponse(
116
+ status=False,
117
+ code=ResponseCode.NO_RECORD_FOUND,
118
+ message=f"Attachment does not exist",
119
+ data=None
120
+ )
121
+ except Exception as e:
122
+ # Handle errors in file retrieval
123
+ return ApiResponse(
124
+ status=False,
125
+ code=ResponseCode.NO_RECORD_FOUND,
126
+ message=f"An error occurred while deleting the attachment: {e}",
127
+ data=None
128
+ )
129
+
130
+ async def get_attachments(self, model_id: str) -> ApiResponse:
131
+ try:
132
+ attachments = await Attachment.filter(attachment_type_id=model_id, attachment_type=self.model.__name__)
133
+
134
+ return ApiResponse(
135
+ status=True,
136
+ code=ResponseCode.SUCCESS,
137
+ message=f"{self.model.Meta.verbose_name} attachments fetched successfully",
138
+ data=PaginatedResponse(
139
+ items=attachments,
140
+ item_count=len(attachments),
141
+ )
142
+ )
143
+ except Exception as e:
144
+ log_exception(Exception(e))
145
+ return ApiResponse(
146
+ status=False,
147
+ code=ResponseCode.BAD_REQUEST,
148
+ message=f"Failed to fetch {self.model.Meta.verbose_name} attachments. Try again",
149
+ data=None
150
+ )
151
+
152
+ async def download_attachment(self, file_path: str) -> ApiResponse:
153
+ try:
154
+ # Call the async download_file method to get the base64 content
155
+ base64_content = await MinioService.get_instance().download_file(f"{self.model.__name__}/{file_path}")
156
+
157
+ if base64_content is False:
158
+ return ApiResponse(
159
+ status=False,
160
+ code=ResponseCode.NO_RECORD_FOUND,
161
+ message="File not found or an error occurred while retrieving the file.",
162
+ data=None
163
+ )
164
+
165
+ # Return success response with base64 content
166
+ return ApiResponse(
167
+ status=True,
168
+ code=ResponseCode.SUCCESS,
169
+ message="File retrieved successfully!",
170
+ data=base64_content.decode('utf-8') # Convert bytes to string
171
+ )
172
+
173
+ except Exception as e:
174
+ # Handle errors in file retrieval
175
+ return ApiResponse(
176
+ status=False,
177
+ code=ResponseCode.NO_RECORD_FOUND,
178
+ message=f"An error occurred while retrieving the file: {e}",
179
+ data=None
180
+ )
181
+
182
+ async def download_attachment_url(self, file_path: str, expiry_seconds: int = 3600) -> ApiResponse:
183
+ """
184
+ Generate a signed URL for downloading a file from MinIO.
185
+
186
+ :param file_path: Relative path to the file in the bucket.
187
+ :param expiry_seconds: Time in seconds for which the signed URL will be valid.
188
+ :return: ApiResponse containing the signed URL or error.
189
+ """
190
+ try:
191
+ full_path = f"{self.model.__name__}/{file_path}" if hasattr(self, 'model') else file_path
192
+
193
+ signed_url = await MinioService.get_instance().get_signed_url(full_path, expiry_seconds=expiry_seconds)
194
+
195
+ if not signed_url:
196
+ return ApiResponse(
197
+ status=False,
198
+ code=ResponseCode.NO_RECORD_FOUND,
199
+ message="Failed to generate signed URL or file does not exist.",
200
+ data=None
201
+ )
202
+
203
+ return ApiResponse(
204
+ status=True,
205
+ code=ResponseCode.SUCCESS,
206
+ message="Signed URL generated successfully!",
207
+ data=signed_url
208
+ )
209
+
210
+ except Exception as e:
211
+ return ApiResponse(
212
+ status=False,
213
+ code=ResponseCode.FAILURE,
214
+ message=f"An error occurred while generating signed URL: {e}",
215
+ data=None
216
+ )
217
+
218
+
219
+
@@ -0,0 +1,17 @@
1
+ import strawberry
2
+ from typing import Optional
3
+
4
+ @strawberry.input
5
+ class AttachmentFile:
6
+ content: str
7
+ content_type: str
8
+ name: str
9
+ extension: str
10
+
11
+ @strawberry.input
12
+ class AttachmentUpload:
13
+ title: Optional[str] = None
14
+ description: Optional[str] = None
15
+ attachment_type_category: Optional[str] = None
16
+ file: AttachmentFile
17
+
@@ -0,0 +1,16 @@
1
+ from typing import Optional
2
+ import strawberry
3
+
4
+ from typing import Optional
5
+ import strawberry
6
+
7
+ @strawberry.type
8
+ class AttachmentResponse:
9
+ id: Optional[str] = None
10
+ title: Optional[str] = None
11
+ description: Optional[str] = None
12
+ file_path: Optional[str] = None
13
+ mem_type: Optional[str] = None
14
+ attachment_type: Optional[str] = None
15
+ attachment_type_category: Optional[str] = None
16
+ attachment_type_id: Optional[str] = None