shaapi 0.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.
- shaapi/__init__.py +3 -0
- shaapi/cli.py +97 -0
- shaapi/generator.py +114 -0
- shaapi/template/.dockerignore +37 -0
- shaapi/template/.env.template +59 -0
- shaapi/template/.gitattributes +12 -0
- shaapi/template/.gitignore +170 -0
- shaapi/template/.gitlab-ci.yml +89 -0
- shaapi/template/Dockerfile +59 -0
- shaapi/template/LICENSE +21 -0
- shaapi/template/README.md +206 -0
- shaapi/template/backend/.gitignore +164 -0
- shaapi/template/backend/__init__.py +0 -0
- shaapi/template/backend/alembic/README +1 -0
- shaapi/template/backend/alembic/env.py +102 -0
- shaapi/template/backend/alembic/script.py.mako +26 -0
- shaapi/template/backend/alembic/versions/2026_06_08_1024-64524c63b666_initial.py +143 -0
- shaapi/template/backend/alembic.ini +117 -0
- shaapi/template/backend/app/__init__.py +55 -0
- shaapi/template/backend/app/admin/__init__.py +1 -0
- shaapi/template/backend/app/admin/api/v1/__init__.py +0 -0
- shaapi/template/backend/app/admin/api/v1/auth.py +59 -0
- shaapi/template/backend/app/admin/api/v1/casbin.py +218 -0
- shaapi/template/backend/app/admin/api/v1/login_log.py +63 -0
- shaapi/template/backend/app/admin/api/v1/opera_log.py +61 -0
- shaapi/template/backend/app/admin/api/v1/role.py +108 -0
- shaapi/template/backend/app/admin/api/v1/user.py +47 -0
- shaapi/template/backend/app/admin/schema/casbin_rule.py +45 -0
- shaapi/template/backend/app/admin/schema/login_log.py +36 -0
- shaapi/template/backend/app/admin/schema/opera_log.py +43 -0
- shaapi/template/backend/app/admin/schema/role.py +36 -0
- shaapi/template/backend/app/admin/schema/sso.py +37 -0
- shaapi/template/backend/app/admin/schema/token.py +74 -0
- shaapi/template/backend/app/admin/schema/user.py +93 -0
- shaapi/template/backend/app/admin/service/auth_service.py +233 -0
- shaapi/template/backend/app/admin/service/casbin_service.py +135 -0
- shaapi/template/backend/app/admin/service/login_log_service.py +62 -0
- shaapi/template/backend/app/admin/service/opera_log_service.py +31 -0
- shaapi/template/backend/app/admin/service/role_service.py +79 -0
- shaapi/template/backend/app/admin/service/secure_token_service.py +60 -0
- shaapi/template/backend/app/admin/service/user_service.py +153 -0
- shaapi/template/backend/app/api.py +11 -0
- shaapi/template/backend/common/__init__.py +0 -0
- shaapi/template/backend/common/cloud_storage/__init__.py +11 -0
- shaapi/template/backend/common/cloud_storage/cloud_storage.py +180 -0
- shaapi/template/backend/common/dataclasses.py +52 -0
- shaapi/template/backend/common/email_conf/email.py +105 -0
- shaapi/template/backend/common/enums.py +144 -0
- shaapi/template/backend/common/exception/__init__.py +0 -0
- shaapi/template/backend/common/exception/errors.py +87 -0
- shaapi/template/backend/common/exception/exception_handler.py +280 -0
- shaapi/template/backend/common/log.py +123 -0
- shaapi/template/backend/common/model.py +68 -0
- shaapi/template/backend/common/pagination.py +83 -0
- shaapi/template/backend/common/response/__init__.py +0 -0
- shaapi/template/backend/common/response/response_code.py +158 -0
- shaapi/template/backend/common/response/response_schema.py +110 -0
- shaapi/template/backend/common/schema.py +144 -0
- shaapi/template/backend/common/security/jwt.py +203 -0
- shaapi/template/backend/common/security/rbac.py +98 -0
- shaapi/template/backend/common/security/sec_token.py +6 -0
- shaapi/template/backend/common/socketio/action.py +11 -0
- shaapi/template/backend/common/socketio/server.py +50 -0
- shaapi/template/backend/common/sso/base.py +69 -0
- shaapi/template/backend/common/sso/google.py +127 -0
- shaapi/template/backend/core/conf.py +208 -0
- shaapi/template/backend/core/path_conf.py +24 -0
- shaapi/template/backend/core/registrar.py +195 -0
- shaapi/template/backend/crud/__init__.py +1 -0
- shaapi/template/backend/crud/crud_base.py +35 -0
- shaapi/template/backend/crud/crud_casbin.py +46 -0
- shaapi/template/backend/crud/crud_login_log.py +58 -0
- shaapi/template/backend/crud/crud_opera_log.py +58 -0
- shaapi/template/backend/crud/crud_role.py +128 -0
- shaapi/template/backend/crud/crud_user.py +267 -0
- shaapi/template/backend/database/__init__.py +0 -0
- shaapi/template/backend/database/db_postgres.py +125 -0
- shaapi/template/backend/database/db_redis.py +62 -0
- shaapi/template/backend/entrypoint-api.sh +19 -0
- shaapi/template/backend/lang/en/app.py +18 -0
- shaapi/template/backend/lang/en/auth.py +10 -0
- shaapi/template/backend/lang/fr/app.py +18 -0
- shaapi/template/backend/lang/fr/auth.py +10 -0
- shaapi/template/backend/main.py +54 -0
- shaapi/template/backend/middleware/__init__.py +1 -0
- shaapi/template/backend/middleware/access_middleware.py +19 -0
- shaapi/template/backend/middleware/i18n_middleware.py +19 -0
- shaapi/template/backend/middleware/jwt_auth_middleware.py +73 -0
- shaapi/template/backend/middleware/opera_log_middleware.py +179 -0
- shaapi/template/backend/middleware/state_middleware.py +26 -0
- shaapi/template/backend/models/__init__.py +10 -0
- shaapi/template/backend/models/associations.py +20 -0
- shaapi/template/backend/models/casbin_rule.py +30 -0
- shaapi/template/backend/models/login_log.py +28 -0
- shaapi/template/backend/models/opera_log.py +36 -0
- shaapi/template/backend/models/role.py +27 -0
- shaapi/template/backend/models/user.py +30 -0
- shaapi/template/backend/seeder/json/admin.json +15 -0
- shaapi/template/backend/seeder/json/user.json +15 -0
- shaapi/template/backend/seeder/run.py +34 -0
- shaapi/template/backend/static/ip2region.xdb +0 -0
- shaapi/template/backend/templates/build/meet.html +169 -0
- shaapi/template/backend/templates/build/new_account.html +373 -0
- shaapi/template/backend/templates/build/reset-password.html +170 -0
- shaapi/template/backend/templates/build/test_email.html +25 -0
- shaapi/template/backend/templates/build/welcome-one-1.html +160 -0
- shaapi/template/backend/templates/build/welcome-one.html +178 -0
- shaapi/template/backend/templates/build/welcome-two.html +234 -0
- shaapi/template/backend/templates/index.html +0 -0
- shaapi/template/backend/templates/src/new_account.mjml +15 -0
- shaapi/template/backend/templates/src/reset_password.mjml +19 -0
- shaapi/template/backend/templates/src/test_email.mjml +11 -0
- shaapi/template/backend/templates/ws/ws.html +70 -0
- shaapi/template/backend/utils/demo_site.py +18 -0
- shaapi/template/backend/utils/encrypt.py +108 -0
- shaapi/template/backend/utils/health_check.py +34 -0
- shaapi/template/backend/utils/prometheus.py +135 -0
- shaapi/template/backend/utils/request_parse.py +110 -0
- shaapi/template/backend/utils/serializers.py +75 -0
- shaapi/template/backend/utils/timezone.py +51 -0
- shaapi/template/backend/utils/trace_id.py +7 -0
- shaapi/template/backend/utils/translator.py +28 -0
- shaapi/template/devops/scripts/deploy.sh +7 -0
- shaapi/template/devops/scripts/setup_env.sh +62 -0
- shaapi/template/docker-compose.monitoring.yml +63 -0
- shaapi/template/docker-compose.override.yml +12 -0
- shaapi/template/docker-compose.yml +90 -0
- shaapi/template/docker-run.sh +99 -0
- shaapi/template/etc/dashboards/fastapi-observability.json +1044 -0
- shaapi/template/etc/dashboards.yaml +10 -0
- shaapi/template/etc/grafana/datasource.yml +79 -0
- shaapi/template/etc/prometheus/prometheus.yml +52 -0
- shaapi/template/package-lock.json +2102 -0
- shaapi/template/package.json +16 -0
- shaapi/template/pyproject.toml +78 -0
- shaapi/template/uv.lock +2866 -0
- shaapi-0.1.0.dist-info/METADATA +92 -0
- shaapi-0.1.0.dist-info/RECORD +141 -0
- shaapi-0.1.0.dist-info/WHEEL +4 -0
- shaapi-0.1.0.dist-info/entry_points.txt +2 -0
- shaapi-0.1.0.dist-info/licenses/LICENCE +21 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import uuid
|
|
5
|
+
from abc import ABC
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import boto3
|
|
10
|
+
|
|
11
|
+
# from google.cloud import storage
|
|
12
|
+
from minio import Minio
|
|
13
|
+
from minio.error import S3Error
|
|
14
|
+
|
|
15
|
+
from backend.core.conf import settings
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CloudStorage(ABC):
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def upload_file(self, file_content: bytes, filename: str) -> Any:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def delete_file(self, identifier: str) -> None:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# GCS ( Google Cloud Storage)
|
|
30
|
+
# class GoogleCloudStorage(CloudStorage):
|
|
31
|
+
# def __init__(self, bucket_name):
|
|
32
|
+
# self.bucket_name = bucket_name
|
|
33
|
+
# self.client = storage.Client()
|
|
34
|
+
|
|
35
|
+
# def upload_file(self, file_content: bytes, filename: str) -> str:
|
|
36
|
+
# bucket = self.client.get_bucket(self.bucket_name)
|
|
37
|
+
# blob = bucket.blob(filename)
|
|
38
|
+
# blob.upload_from_string(file_content)
|
|
39
|
+
# return blob.public_url
|
|
40
|
+
|
|
41
|
+
# def delete_file(self, filename: str) -> None:
|
|
42
|
+
# blob = self.client.get_bucket(self.bucket_name).blob(filename)
|
|
43
|
+
# blob.delete()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Amazon S3
|
|
47
|
+
class AmazonS3Storage(CloudStorage):
|
|
48
|
+
def __init__(
|
|
49
|
+
self, bucket_name, aws_access_key_id, aws_secret_access_key, region_name
|
|
50
|
+
):
|
|
51
|
+
self.bucket_name = bucket_name
|
|
52
|
+
self.s3 = boto3.client(
|
|
53
|
+
"s3",
|
|
54
|
+
aws_access_key_id=aws_access_key_id,
|
|
55
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
56
|
+
region_name=region_name,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def upload_file(self, file_content: bytes, filename: str) -> str:
|
|
60
|
+
self.s3.upload_fileobj(file_content, self.bucket_name, filename)
|
|
61
|
+
file_url = f"https://{self.bucket_name}.s3.amazonaws.com/{filename}"
|
|
62
|
+
return file_url
|
|
63
|
+
|
|
64
|
+
def delete_file(self, filename: str) -> None:
|
|
65
|
+
self.s3.delete_object(Bucket=self.bucket_name, Key=filename)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Cloudinary
|
|
69
|
+
# class CloudinaryStorage(CloudStorage):
|
|
70
|
+
# def __init__(self, cloud_name, api_key, api_secret):
|
|
71
|
+
# cloudinary.config(cloud_name=cloud_name, api_key=api_key, api_secret=api_secret)
|
|
72
|
+
|
|
73
|
+
# def upload_file(self, file_content: bytes, filename: str) -> Any:
|
|
74
|
+
# result = uploader.upload(
|
|
75
|
+
# file_content,
|
|
76
|
+
# folder="test",
|
|
77
|
+
# resource_type="raw",
|
|
78
|
+
# filename=filename,
|
|
79
|
+
# use_filename=True,
|
|
80
|
+
# )
|
|
81
|
+
# return result
|
|
82
|
+
|
|
83
|
+
# def delete_file(self, public_id: str) -> None:
|
|
84
|
+
# uploader.destroy(public_id=public_id, resource_type="raw")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MinioStorage(CloudStorage):
|
|
88
|
+
def __init__(self, endpoint_url, access_key, secret_key, bucket_name):
|
|
89
|
+
self.endpoint_url = endpoint_url
|
|
90
|
+
self.access_key = access_key
|
|
91
|
+
self.secret_key = secret_key
|
|
92
|
+
self.bucket_name = bucket_name
|
|
93
|
+
|
|
94
|
+
# Initialisation du client MinIO
|
|
95
|
+
self.client = Minio(
|
|
96
|
+
endpoint=self.endpoint_url,
|
|
97
|
+
access_key=self.access_key,
|
|
98
|
+
secret_key=self.secret_key,
|
|
99
|
+
secure=False, # En local, on utilise HTTP (pas HTTPS)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Définition de la politique de bucket pour l'accès public en lecture
|
|
103
|
+
policy = {
|
|
104
|
+
"Version": "2012-10-17",
|
|
105
|
+
"Statement": [
|
|
106
|
+
{
|
|
107
|
+
"Effect": "Allow",
|
|
108
|
+
"Principal": "*",
|
|
109
|
+
"Action": ["s3:GetObject"],
|
|
110
|
+
"Resource": [f"arn:aws:s3:::{bucket_name}/*"],
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Vérification et configuration du bucket
|
|
116
|
+
try:
|
|
117
|
+
if not self.client.bucket_exists(bucket_name):
|
|
118
|
+
# Création du bucket
|
|
119
|
+
self.client.make_bucket(bucket_name)
|
|
120
|
+
print(f"Bucket '{bucket_name}' created successfully.")
|
|
121
|
+
|
|
122
|
+
# Application de la politique d'accès public
|
|
123
|
+
self.client.set_bucket_policy(bucket_name, json.dumps(policy))
|
|
124
|
+
print(f"Public access policy applied to bucket '{bucket_name}'.")
|
|
125
|
+
else:
|
|
126
|
+
print(f"Bucket '{bucket_name}' already exists.")
|
|
127
|
+
|
|
128
|
+
except S3Error as e:
|
|
129
|
+
print(f"Error occurred: {e}")
|
|
130
|
+
|
|
131
|
+
def exists(self, filename: str) -> bool:
|
|
132
|
+
try:
|
|
133
|
+
self.client.stat_object(self.bucket_name, filename)
|
|
134
|
+
return True
|
|
135
|
+
except S3Error:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def generate_filename(self, filename: str) -> str:
|
|
139
|
+
|
|
140
|
+
filename, ext = filename.rsplit(".", 1)
|
|
141
|
+
|
|
142
|
+
# Remplacer tous les caractères non alphanumériques (sauf les tirets bas) par des tirets bas
|
|
143
|
+
refilename = re.sub(r"[^\w\-_()]+", "_", filename)
|
|
144
|
+
finalefilename = f"{refilename}.{ext}"
|
|
145
|
+
while self.exists(finalefilename):
|
|
146
|
+
return self.generate_filename(f"{filename}_{uuid.uuid4()}.{ext}")
|
|
147
|
+
return finalefilename
|
|
148
|
+
|
|
149
|
+
def download_file(self, filename: str) -> bytes:
|
|
150
|
+
obj = self.client.get_object(self.bucket_name, filename)
|
|
151
|
+
return obj.read()
|
|
152
|
+
|
|
153
|
+
def delete_file(self, filename: str) -> None:
|
|
154
|
+
self.client.remove_object(self.bucket_name, filename)
|
|
155
|
+
|
|
156
|
+
def list_files(self) -> list[str]:
|
|
157
|
+
objects = self.client.list_objects(self.bucket_name)
|
|
158
|
+
return [obj.object_name for obj in objects]
|
|
159
|
+
|
|
160
|
+
def create_bucket(self) -> None:
|
|
161
|
+
self.client.make_bucket(self.bucket_name)
|
|
162
|
+
|
|
163
|
+
def delete_bucket(self) -> None:
|
|
164
|
+
self.client.remove_bucket(self.bucket_name)
|
|
165
|
+
|
|
166
|
+
def list_buckets(self) -> list[str]:
|
|
167
|
+
buckets = self.client.list_buckets()
|
|
168
|
+
return [bucket.name for bucket in buckets]
|
|
169
|
+
|
|
170
|
+
def upload_file(self, file_content: bytes, filename: str) -> str:
|
|
171
|
+
gen_filename = self.generate_filename(filename)
|
|
172
|
+
self.client.put_object(
|
|
173
|
+
bucket_name=self.bucket_name,
|
|
174
|
+
object_name=gen_filename,
|
|
175
|
+
data=io.BytesIO(file_content),
|
|
176
|
+
length=-1,
|
|
177
|
+
part_size=100 * 1024 * 1024,
|
|
178
|
+
)
|
|
179
|
+
file_url = f"{settings.MINIO_CLOUD_URL}/{self.bucket_name}/{gen_filename}"
|
|
180
|
+
return {"file_url": file_url, "filename": gen_filename}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from fastapi import Response
|
|
6
|
+
|
|
7
|
+
from backend.common.enums import StatusType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclasses.dataclass
|
|
11
|
+
class IpInfo:
|
|
12
|
+
ip: str
|
|
13
|
+
country: str | None
|
|
14
|
+
region: str | None
|
|
15
|
+
city: str | None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass
|
|
19
|
+
class UserAgentInfo:
|
|
20
|
+
user_agent: str
|
|
21
|
+
os: str | None
|
|
22
|
+
browser: str | None
|
|
23
|
+
device: str | None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass
|
|
27
|
+
class RequestCallNext:
|
|
28
|
+
code: str
|
|
29
|
+
msg: str
|
|
30
|
+
status: StatusType
|
|
31
|
+
err: Exception | None
|
|
32
|
+
response: Response
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclasses.dataclass
|
|
36
|
+
class NewToken:
|
|
37
|
+
new_access_token: str
|
|
38
|
+
new_access_token_expire_time: datetime
|
|
39
|
+
new_refresh_token: str
|
|
40
|
+
new_refresh_token_expire_time: datetime
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclasses.dataclass
|
|
44
|
+
class AccessToken:
|
|
45
|
+
access_token: str
|
|
46
|
+
access_token_expire_time: datetime
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclasses.dataclass
|
|
50
|
+
class RefreshToken:
|
|
51
|
+
refresh_token: str
|
|
52
|
+
refresh_token_expire_time: datetime
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from fastapi_mail import ConnectionConfig
|
|
7
|
+
from fastapi_mail import FastMail
|
|
8
|
+
from fastapi_mail import MessageSchema
|
|
9
|
+
from jinja2 import Environment
|
|
10
|
+
from jinja2 import PackageLoader
|
|
11
|
+
from jinja2 import select_autoescape
|
|
12
|
+
from pydantic import EmailStr
|
|
13
|
+
|
|
14
|
+
from backend.core.conf import settings
|
|
15
|
+
# from backend.common.enums import Token_type
|
|
16
|
+
# from app.core.security import generate_secret_token
|
|
17
|
+
# from app.models import User
|
|
18
|
+
# from backend.app.admin.schema.token import Token
|
|
19
|
+
|
|
20
|
+
env = Environment(
|
|
21
|
+
loader=PackageLoader("backend", f"{settings.EMAIL_TEMPLATES_DIR}/"),
|
|
22
|
+
autoescape=select_autoescape(
|
|
23
|
+
enabled_extensions=("html", "xml"), default_for_string=True
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Email:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.conf = ConnectionConfig(
|
|
31
|
+
MAIL_USERNAME=settings.SMTP_USER,
|
|
32
|
+
MAIL_PASSWORD=settings.SMTP_PASSWORD,
|
|
33
|
+
MAIL_FROM=settings.EMAILS_FROM_EMAIL,
|
|
34
|
+
MAIL_FROM_NAME=settings.EMAILS_FROM_NAME,
|
|
35
|
+
MAIL_PORT=settings.SMTP_PORT,
|
|
36
|
+
MAIL_SERVER=settings.SMTP_HOST,
|
|
37
|
+
MAIL_STARTTLS=settings.SMTP_TLS,
|
|
38
|
+
MAIL_SSL_TLS=False,
|
|
39
|
+
USE_CREDENTIALS=True,
|
|
40
|
+
VALIDATE_CERTS=False,
|
|
41
|
+
)
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
async def send_login_mail(
|
|
45
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
46
|
+
):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def send_reset_password_mail(
|
|
51
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
52
|
+
):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
async def send_verify_mail(
|
|
56
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
57
|
+
):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
async def send_welcome_mail(
|
|
61
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
62
|
+
):
|
|
63
|
+
template = env.get_template("welcome-one.html")
|
|
64
|
+
|
|
65
|
+
subject = "Bienvenue sur Boilerplate"
|
|
66
|
+
html = template.render(
|
|
67
|
+
subject=subject,
|
|
68
|
+
link=url,
|
|
69
|
+
email=email
|
|
70
|
+
)
|
|
71
|
+
message = MessageSchema(
|
|
72
|
+
subject=subject, recipients=[email], body=html, subtype="html"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
fm = FastMail(self.conf)
|
|
76
|
+
await fm.send_message(message)
|
|
77
|
+
|
|
78
|
+
async def send_forgot_password_mail(
|
|
79
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
80
|
+
):
|
|
81
|
+
template = env.get_template("reset-password.html")
|
|
82
|
+
subject = "Réinitialisation du mot de passe"
|
|
83
|
+
html = template.render(
|
|
84
|
+
subject=subject,
|
|
85
|
+
link=url,
|
|
86
|
+
email=email
|
|
87
|
+
)
|
|
88
|
+
message = MessageSchema(
|
|
89
|
+
subject=subject, recipients=[email], body=html, subtype="html"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
fm = FastMail(self.conf)
|
|
93
|
+
await fm.send_message(message)
|
|
94
|
+
|
|
95
|
+
async def send_change_password_mail(
|
|
96
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
97
|
+
):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
async def send_change_email_mail(
|
|
101
|
+
self, email: EmailStr, url: str | None = None, name: str | None = None
|
|
102
|
+
):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
email_service = Email()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from enum import IntEnum as SourceIntEnum
|
|
3
|
+
from typing import Type
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _EnumBase:
|
|
7
|
+
@classmethod
|
|
8
|
+
def get_member_keys(cls: Type[Enum]) -> list[str]:
|
|
9
|
+
return [name for name in cls.__members__.keys()]
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def get_member_values(cls: Type[Enum]) -> list:
|
|
13
|
+
return [item.value for item in cls.__members__.values()]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IntEnum(_EnumBase, SourceIntEnum):
|
|
17
|
+
"""Integer Enum"""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StrEnum(_EnumBase, str, Enum):
|
|
23
|
+
"""String Enum"""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LoginLogStatusType(IntEnum):
|
|
30
|
+
"""Login Log Status"""
|
|
31
|
+
|
|
32
|
+
fail = 0
|
|
33
|
+
success = 1
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StatusType(IntEnum):
|
|
38
|
+
"""Status Type"""
|
|
39
|
+
|
|
40
|
+
disable = 0
|
|
41
|
+
enable = 1
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MethodType(StrEnum):
|
|
45
|
+
"""Request method"""
|
|
46
|
+
|
|
47
|
+
GET = 'GET'
|
|
48
|
+
POST = 'POST'
|
|
49
|
+
PUT = 'PUT'
|
|
50
|
+
DELETE = 'DELETE'
|
|
51
|
+
PATCH = 'PATCH'
|
|
52
|
+
OPTIONS = 'OPTIONS'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OperaLogCipherType(IntEnum):
|
|
56
|
+
"""Operation log encryption type"""
|
|
57
|
+
|
|
58
|
+
aes = 0
|
|
59
|
+
md5 = 1
|
|
60
|
+
itsdangerous = 2
|
|
61
|
+
plan = 3
|
|
62
|
+
|
|
63
|
+
class RoleDataScopeType(IntEnum):
|
|
64
|
+
"""Data range"""
|
|
65
|
+
|
|
66
|
+
all = 1
|
|
67
|
+
custom = 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class User_job_status(Enum):
|
|
71
|
+
OPEN_TO_WORK = "open_to_work" #Disponible pour un emploi ou des opportunités professionnelles.
|
|
72
|
+
EMPLOYED = "employed" #Actuellement employé.
|
|
73
|
+
NOT_LOOKING = "not_looking" #Pas à la recherche d'opportunités pour le moment.
|
|
74
|
+
AVAILABLE_FOR_FREELANCE = "available_for_freelance" #Disponible pour des missions freelance ou des contrats à court terme.
|
|
75
|
+
ON_LEAVE = "on_leave" #En congé ou indisponible pour une période définie.
|
|
76
|
+
LOOKING_FOR_INTERNSHIP = "looking_for_internship" #Recherche activement un stage ou une opportunité d'apprentissage.
|
|
77
|
+
OPEN_TO_COLLABORATION = "open_to_collaboration" #Ouvert à des collaborations ou des projets en partenariat.
|
|
78
|
+
HIRED = "hired" #A trouvé un emploi ou est actuellement employé.
|
|
79
|
+
|
|
80
|
+
class Role(Enum):
|
|
81
|
+
USER = "user"
|
|
82
|
+
ADMIN = "admin"
|
|
83
|
+
MENTOR = "mentor"
|
|
84
|
+
COMPANY = "company"
|
|
85
|
+
|
|
86
|
+
class Appointment_status(Enum):
|
|
87
|
+
WAITING_FOR_PAYMENT = "waiting_for_payment"
|
|
88
|
+
PENDING = "pending"
|
|
89
|
+
CANCELED = "canceled"
|
|
90
|
+
DONE = "done"
|
|
91
|
+
|
|
92
|
+
class Payment_type(Enum):
|
|
93
|
+
PAYIN = "payin"
|
|
94
|
+
PAYOUT = "payout"
|
|
95
|
+
|
|
96
|
+
class Contract_type(Enum):
|
|
97
|
+
CDI = "cdi" # Contrat à Durée Indéterminée (CDI).
|
|
98
|
+
CDD = "cdd" # Contrat à Durée Déterminée (CDD).
|
|
99
|
+
|
|
100
|
+
class Collab_invitation_status_type(Enum):
|
|
101
|
+
WAITING_FOR_RESPONSE = "waiting_for_response"
|
|
102
|
+
ACCEPTED = "accepted"
|
|
103
|
+
REJECTED = "rejected"
|
|
104
|
+
CANCELED = "canceled"
|
|
105
|
+
|
|
106
|
+
class Collab_invitation_type(Enum):
|
|
107
|
+
COLLABORATOR = "collaborator"
|
|
108
|
+
RECRUTMENT = "recrutment"
|
|
109
|
+
|
|
110
|
+
class Program_follow_request_status(Enum):
|
|
111
|
+
PENDING = "pending"
|
|
112
|
+
WAITING_FOR_PAYMENT = "waiting_for_payment"
|
|
113
|
+
PAID = "paid"
|
|
114
|
+
CANCELED = "canceled"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Week_task_status(Enum):
|
|
118
|
+
IN_REVIEW = "in_review"
|
|
119
|
+
COMPLETED = "completed"
|
|
120
|
+
PENDING = "pending"
|
|
121
|
+
REJECTED = "rejected"
|
|
122
|
+
|
|
123
|
+
class Mentor_request_status(Enum):
|
|
124
|
+
PENDING = "Pending"
|
|
125
|
+
APPROVED = "Approved"
|
|
126
|
+
REJECTED = "Rejected"
|
|
127
|
+
|
|
128
|
+
class Transaction_status(Enum):
|
|
129
|
+
PENDING = "pending"
|
|
130
|
+
SUCCESSFULLY = "successfully"
|
|
131
|
+
CANCELED = "canceled"
|
|
132
|
+
|
|
133
|
+
class Secure_token_type(Enum):
|
|
134
|
+
RESET_PWD = "reset_pwd"
|
|
135
|
+
VERIFY_ACCOUNT = "verif_account"
|
|
136
|
+
UPDATE_EMAIL = "update_email"
|
|
137
|
+
|
|
138
|
+
class Waiting_queue_filter_type(Enum):
|
|
139
|
+
COURSE = "course"
|
|
140
|
+
PROGRAM = "program"
|
|
141
|
+
|
|
142
|
+
class Reason_type(Enum):
|
|
143
|
+
COMPANY = "company"
|
|
144
|
+
USER = "user"
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classe d'exception globale pour les entreprises
|
|
3
|
+
|
|
4
|
+
Les exceptions liées à l'exécution du code métier peuvent être déclenchées avec raise xxxError, qui met en œuvre des exceptions avec des tâches d'arrière-plan dans la mesure du possible, mais ne s'applique pas aux **CustomResponseStatusCodes**
|
|
5
|
+
Si un **CustomResponseStatusCode** est requis, il peut être renvoyé directement par return response_base.fail(res=CustomResponseCode.xxx)
|
|
6
|
+
|
|
7
|
+
Traduit avec DeepL.com (version gratuite)
|
|
8
|
+
""" # noqa: E501
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from fastapi import HTTPException
|
|
13
|
+
from starlette.background import BackgroundTask
|
|
14
|
+
|
|
15
|
+
from backend.common.response.response_code import CustomErrorCode, StandardResponseCode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseExceptionMixin(Exception):
|
|
19
|
+
code: int
|
|
20
|
+
|
|
21
|
+
def __init__(self, *, msg: str = None, data: Any = None, background: BackgroundTask | None = None):
|
|
22
|
+
self.msg = msg
|
|
23
|
+
self.data = data
|
|
24
|
+
# The original background task: https://www.starlette.io/background/
|
|
25
|
+
self.background = background
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HTTPError(HTTPException):
|
|
29
|
+
def __init__(self, *, code: int, msg: Any = None, headers: dict[str, Any] | None = None):
|
|
30
|
+
super().__init__(status_code=code, detail=msg, headers=headers)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CustomError(BaseExceptionMixin):
|
|
34
|
+
def __init__(self, *, error: CustomErrorCode, data: Any = None, background: BackgroundTask | None = None):
|
|
35
|
+
self.code = error.code
|
|
36
|
+
super().__init__(msg=error.msg, data=data, background=background)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RequestError(BaseExceptionMixin):
|
|
40
|
+
code = StandardResponseCode.HTTP_400
|
|
41
|
+
|
|
42
|
+
def __init__(self, *, msg: str = 'Bad Request', data: Any = None, background: BackgroundTask | None = None):
|
|
43
|
+
super().__init__(msg=msg, data=data, background=background)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ForbiddenError(BaseExceptionMixin):
|
|
47
|
+
code = StandardResponseCode.HTTP_403
|
|
48
|
+
|
|
49
|
+
def __init__(self, *, msg: str = 'Forbidden', data: Any = None, background: BackgroundTask | None = None):
|
|
50
|
+
super().__init__(msg=msg, data=data, background=background)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NotFoundError(BaseExceptionMixin):
|
|
54
|
+
code = StandardResponseCode.HTTP_404
|
|
55
|
+
|
|
56
|
+
def __init__(self, *, msg: str = 'Not Found', data: Any = None, background: BackgroundTask | None = None):
|
|
57
|
+
super().__init__(msg=msg, data=data, background=background)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ServerError(BaseExceptionMixin):
|
|
61
|
+
code = StandardResponseCode.HTTP_500
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self, *, msg: str = 'Internal Server Error', data: Any = None, background: BackgroundTask | None = None
|
|
65
|
+
):
|
|
66
|
+
super().__init__(msg=msg, data=data, background=background)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class GatewayError(BaseExceptionMixin):
|
|
70
|
+
code = StandardResponseCode.HTTP_502
|
|
71
|
+
|
|
72
|
+
def __init__(self, *, msg: str = 'Bad Gateway', data: Any = None, background: BackgroundTask | None = None):
|
|
73
|
+
super().__init__(msg=msg, data=data, background=background)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AuthorizationError(BaseExceptionMixin):
|
|
77
|
+
code = StandardResponseCode.HTTP_401
|
|
78
|
+
|
|
79
|
+
def __init__(self, *, msg: str = 'Permission Denied', data: Any = None, background: BackgroundTask | None = None):
|
|
80
|
+
super().__init__(msg=msg, data=data, background=background)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TokenError(HTTPError):
|
|
84
|
+
code = StandardResponseCode.HTTP_401
|
|
85
|
+
|
|
86
|
+
def __init__(self, *, msg: str = 'Not Authenticated', headers: dict[str, Any] | None = None):
|
|
87
|
+
super().__init__(code=self.code, msg=msg, headers=headers or {'WWW-Authenticate': 'Bearer'})
|