internal 1.0.86__tar.gz → 1.1.49.1__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.
- {internal-1.0.86 → internal-1.1.49.1}/PKG-INFO +6 -1
- {internal-1.0.86 → internal-1.1.49.1}/pyproject.toml +6 -1
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/base_config.py +16 -6
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/base_factory.py +43 -38
- internal-1.0.86/src/internal/common_enum/contact_type.py → internal-1.1.49.1/src/internal/common_enum/car_relation_type.py +1 -1
- internal-1.1.49.1/src/internal/common_enum/device_code.py +10 -0
- internal-1.1.49.1/src/internal/common_enum/event_code.py +48 -0
- internal-1.0.86/src/internal/common_enum/lpnr_direction.py → internal-1.1.49.1/src/internal/common_enum/lpr_direction.py +1 -1
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/notify_type.py +3 -3
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/operator_type.py +3 -0
- internal-1.1.49.1/src/internal/common_enum/point_type.py +6 -0
- internal-1.1.49.1/src/internal/common_enum/websocket_channel.py +9 -0
- internal-1.1.49.1/src/internal/const.py +61 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/database.py +24 -2
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/app_exception.py +10 -1
- internal-1.1.49.1/src/internal/http/requests.py +174 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/http/responses.py +45 -4
- internal-1.1.49.1/src/internal/middleware/log_request.py +90 -0
- internal-1.1.49.1/src/internal/model/base_model.py +198 -0
- internal-1.1.49.1/src/internal/utils.py +155 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/validator_utils.py +17 -4
- internal-1.0.86/src/internal/common_enum/event_code.py +0 -29
- internal-1.0.86/src/internal/common_enum/event_trigger_type.py +0 -29
- internal-1.0.86/src/internal/common_enum/feature.py +0 -6
- internal-1.0.86/src/internal/const.py +0 -41
- internal-1.0.86/src/internal/http/requests.py +0 -59
- internal-1.0.86/src/internal/middleware/log_request.py +0 -27
- internal-1.0.86/src/internal/model/base_model.py +0 -103
- internal-1.0.86/src/internal/utils.py +0 -50
- {internal-1.0.86 → internal-1.1.49.1}/README.md +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/cache_redis.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/description_type.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/order_type.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/base_exception.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/internal_exception.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/amazon/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/amazon/aws/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/amazon/aws/const.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/http/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/interface/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/interface/base_interface.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/middleware/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/model/__init__.py +0 -0
- {internal-1.0.86 → internal-1.1.49.1}/src/internal/model/operate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: internal
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.49.1
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Ray
|
|
6
6
|
Author-email: ray@cruisys.com
|
|
@@ -10,12 +10,17 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
12
|
Requires-Dist: arrow (>=1.3.0,<2.0.0)
|
|
13
|
+
Requires-Dist: asgi-correlation-id (>=4.3.4,<5.0.0)
|
|
13
14
|
Requires-Dist: beanie (>=1.29.0,<2.0.0)
|
|
14
15
|
Requires-Dist: dictdiffer (>=0.9.0,<0.10.0)
|
|
15
16
|
Requires-Dist: fastapi (>=0.115.6,<0.116.0)
|
|
16
17
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
17
18
|
Requires-Dist: pydantic-settings (>=2.7.1,<3.0.0)
|
|
19
|
+
Requires-Dist: pytest (>=8.4.1,<9.0.0)
|
|
20
|
+
Requires-Dist: pytest-asyncio (>=1.0.0,<2.0.0)
|
|
21
|
+
Requires-Dist: pytest-sugar (>=1.0.0,<2.0.0)
|
|
18
22
|
Requires-Dist: redis (>=5.2.1,<6.0.0)
|
|
23
|
+
Requires-Dist: uvicorn (>=0.34.0,<0.35.0)
|
|
19
24
|
Requires-Dist: watchtower (>=3.3.1,<4.0.0)
|
|
20
25
|
Description-Content-Type: text/markdown
|
|
21
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "internal"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.1.49.1"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [{ name = "Ray", email = "ray@cruisys.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -18,6 +18,11 @@ httpx = "^0.28.1"
|
|
|
18
18
|
beanie = "^1.29.0"
|
|
19
19
|
dictdiffer = "^0.9.0"
|
|
20
20
|
redis = "^5.2.1"
|
|
21
|
+
uvicorn = "^0.34.0"
|
|
22
|
+
asgi-correlation-id = "^4.3.4"
|
|
23
|
+
pytest = "^8.4.1"
|
|
24
|
+
pytest-asyncio = "^1.0.0"
|
|
25
|
+
pytest-sugar = "^1.0.0"
|
|
21
26
|
|
|
22
27
|
[build-system]
|
|
23
28
|
requires = ["poetry-core"]
|
|
@@ -11,13 +11,17 @@ class BaseConfig(BaseSettings):
|
|
|
11
11
|
LOGGER_REQUEST_ENABLE: bool = True
|
|
12
12
|
|
|
13
13
|
# Request
|
|
14
|
-
REQUEST_VERIFY_SSL: bool =
|
|
14
|
+
REQUEST_VERIFY_SSL: bool = False
|
|
15
15
|
REQUEST_PROXY: str = ''
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
REQUEST_RETRY_COUNT: int = 0
|
|
17
|
+
REQUEST_RETRY_DELAY_INITIAL_SECONDS: float = 1.0
|
|
18
|
+
REQUEST_RETRY_DELAY_FACTOR: float = 1.5
|
|
19
|
+
REQUEST_RETRY_DELAY_RANDOM_JITTER_MIN: float = 0.0
|
|
20
|
+
REQUEST_RETRY_DELAY_RANDOM_JITTER_MAX: float = 0.3
|
|
21
|
+
REQUEST_CONN_POOL_TIMEOUT: float = 5.0
|
|
22
|
+
REQUEST_CONN_TIMEOUT: float = 5.0
|
|
23
|
+
REQUEST_WRITE_TIMEOUT: float = 5.0
|
|
24
|
+
RESPONSE_READ_TIMEOUT: float = 10.0
|
|
21
25
|
|
|
22
26
|
# AWS
|
|
23
27
|
AWS_ACCESS_KEY_ID: str = ""
|
|
@@ -48,9 +52,15 @@ class BaseConfig(BaseSettings):
|
|
|
48
52
|
NOTIFY_BASE_URL: str = "http://notify-service-api:5000"
|
|
49
53
|
THIRD_PARTY_BASE_URL: str = "http://third-party-service-api:5000"
|
|
50
54
|
SCHEDULER_BASE_URL: str = "http://scheduler-service-api:5000"
|
|
55
|
+
AUTO_DATA_PROCESSING_BASE_URL: str = "http://auto-data-processing-service-api:5000"
|
|
51
56
|
|
|
52
57
|
# Exception Notify
|
|
53
58
|
WEBHOOK_BASE_URL: str = ""
|
|
59
|
+
WEBHOOK_RETRY_COUNT: int = 5
|
|
60
|
+
WEBHOOK_RETRY_DELAY_INITIAL_SECONDS: float = 1.0
|
|
61
|
+
WEBHOOK_RETRY_DELAY_FACTOR: float = 2.0
|
|
62
|
+
WEBHOOK_RETRY_DELAY_RANDOM_JITTER_MIN: float = 0.0
|
|
63
|
+
WEBHOOK_RETRY_DELAY_RANDOM_JITTER_MAX: float = 0.5
|
|
54
64
|
|
|
55
65
|
# Default System Account Password
|
|
56
66
|
SYSTEM_ACCOUNT: str = "cruisys"
|
|
@@ -5,6 +5,7 @@ import traceback
|
|
|
5
5
|
from abc import ABCMeta, abstractmethod
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from functools import lru_cache
|
|
8
|
+
from asgi_correlation_id import CorrelationIdMiddleware, CorrelationIdFilter
|
|
8
9
|
|
|
9
10
|
import dotenv
|
|
10
11
|
import watchtower
|
|
@@ -14,11 +15,11 @@ from fastapi.exceptions import RequestValidationError
|
|
|
14
15
|
from fastapi.middleware.cors import CORSMiddleware
|
|
15
16
|
|
|
16
17
|
from . import database, cache_redis
|
|
17
|
-
from .const import LOG_FMT, LOG_FMT_NO_DT, LOG_DT_FMT, DEFAULT_LOGGER_NAME
|
|
18
|
+
from .const import LOG_FMT, LOG_FMT_NO_DT, LOG_DT_FMT, DEFAULT_LOGGER_NAME, CORRELATION_ID_HEADER_KEY_NAME
|
|
18
19
|
from .exception.base_exception import InternalBaseException
|
|
19
20
|
from .exception.internal_exception import BadGatewayException, GatewayTimeoutException
|
|
20
21
|
from .ext.amazon import aws
|
|
21
|
-
from .http.requests import
|
|
22
|
+
from .http.requests import send_webhook_message
|
|
22
23
|
from .http.responses import async_response
|
|
23
24
|
from .middleware.log_request import LogRequestMiddleware
|
|
24
25
|
from .utils import update_dict_with_cast
|
|
@@ -40,12 +41,6 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
40
41
|
Each factory should define what model it wants.
|
|
41
42
|
"""
|
|
42
43
|
|
|
43
|
-
@abstractmethod
|
|
44
|
-
async def init_state_cache(self, app, app_config):
|
|
45
|
-
"""
|
|
46
|
-
Each factory should define what model it wants.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
44
|
async def init_redis(self, app, app_config):
|
|
50
45
|
if app_config.REDIS_URL:
|
|
51
46
|
redis_cache = cache_redis.CacheRedis(app_config.REDIS_URL)
|
|
@@ -55,12 +50,24 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
55
50
|
else:
|
|
56
51
|
app.state.logger.info("skip initialization redis")
|
|
57
52
|
|
|
53
|
+
async def init_state_cache(self, app, app_config):
|
|
54
|
+
"""
|
|
55
|
+
Each factory should define what model it wants.
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
58
59
|
async def init_state_scheduler_app(self, app, app_config):
|
|
59
60
|
"""
|
|
60
61
|
Each factory should define what model it wants.
|
|
61
62
|
"""
|
|
62
63
|
pass
|
|
63
64
|
|
|
65
|
+
async def close_scheduler_app(self, app, app_config):
|
|
66
|
+
"""
|
|
67
|
+
Each factory should define what model it wants.
|
|
68
|
+
"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
64
71
|
@abstractmethod
|
|
65
72
|
@lru_cache()
|
|
66
73
|
def get_app_config(self):
|
|
@@ -87,6 +94,8 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
87
94
|
# code to execute when app is shutting down
|
|
88
95
|
await mongodb.close()
|
|
89
96
|
app.state.logger.info("Database disconnected")
|
|
97
|
+
await self.close_scheduler_app(app, self)
|
|
98
|
+
app.state.logger.info("State scheduler closed")
|
|
90
99
|
|
|
91
100
|
if title is None:
|
|
92
101
|
title = self.DEFAULT_APP_NAME
|
|
@@ -99,6 +108,18 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
99
108
|
debug=self.get_app_config().DEBUG, version=self.API_VERSION, lifespan=lifespan, docs_url=None,
|
|
100
109
|
redoc_url=None)
|
|
101
110
|
|
|
111
|
+
self.__load_local_config()
|
|
112
|
+
app.state.config = self.get_app_config()
|
|
113
|
+
self.__setup_main_logger(app, level=logging.DEBUG)
|
|
114
|
+
app.state.aws_session = aws.init_app(app)
|
|
115
|
+
self.__setup_cloud_log(app)
|
|
116
|
+
self.__load_cloud_config(app)
|
|
117
|
+
|
|
118
|
+
# 不重要的middleware請加在這之前
|
|
119
|
+
if self.get_app_config().LOGGER_REQUEST_ENABLE:
|
|
120
|
+
app.add_middleware(LogRequestMiddleware, logger=app.state.logger)
|
|
121
|
+
|
|
122
|
+
|
|
102
123
|
origins = ["*"]
|
|
103
124
|
|
|
104
125
|
app.add_middleware(
|
|
@@ -107,17 +128,14 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
107
128
|
allow_credentials=True,
|
|
108
129
|
allow_methods=["*"],
|
|
109
130
|
allow_headers=["*"],
|
|
131
|
+
expose_headers=[CORRELATION_ID_HEADER_KEY_NAME]
|
|
110
132
|
)
|
|
111
133
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
self.__load_cloud_config(app)
|
|
118
|
-
|
|
119
|
-
if self.get_app_config().LOGGER_REQUEST_ENABLE:
|
|
120
|
-
app.add_middleware(LogRequestMiddleware, logger=app.state.logger)
|
|
134
|
+
app.add_middleware(
|
|
135
|
+
CorrelationIdMiddleware,
|
|
136
|
+
header_name=CORRELATION_ID_HEADER_KEY_NAME,
|
|
137
|
+
update_request_header=True
|
|
138
|
+
)
|
|
121
139
|
|
|
122
140
|
mongodb = database.MongoDB(self.get_app_config().DATABASE_USERNAME, self.get_app_config().DATABASE_PASSWORD,
|
|
123
141
|
self.get_app_config().DATABASE_HOST, self.get_app_config().DATABASE_PORT,
|
|
@@ -137,21 +155,11 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
137
155
|
detail = exc.detail
|
|
138
156
|
|
|
139
157
|
if isinstance(exc, BadGatewayException):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
payload = {"text": message}
|
|
143
|
-
try:
|
|
144
|
-
await async_request(app, "POST", self.get_app_config().WEBHOOK_BASE_URL, json=payload)
|
|
145
|
-
except Exception as e:
|
|
146
|
-
app.state.logger.warn(f"Notify failure, Exception:{e}")
|
|
158
|
+
message = f"【{self.DEFAULT_APP_NAME}】Bad gateway, request:{request.__dict__}, exc:{exc}"
|
|
159
|
+
await send_webhook_message(app, message)
|
|
147
160
|
elif isinstance(exc, GatewayTimeoutException):
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
payload = {"text": message}
|
|
151
|
-
try:
|
|
152
|
-
await async_request(app, "POST", self.get_app_config().WEBHOOK_BASE_URL, json=payload)
|
|
153
|
-
except Exception as e:
|
|
154
|
-
app.state.logger.warn(f"Notify failure, Exception:{e}")
|
|
161
|
+
message = f"【{self.DEFAULT_APP_NAME}】Gateway timeout, request:{request.__dict__}, exc:{exc}"
|
|
162
|
+
await send_webhook_message(app, message)
|
|
155
163
|
|
|
156
164
|
return await async_response(data=detail.get("data"), code=detail.get("code"), message=detail.get("message"),
|
|
157
165
|
status_code=exc.status_code)
|
|
@@ -169,13 +177,8 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
169
177
|
async def http_exception_handler(request: Request, exc: Exception):
|
|
170
178
|
app.state.logger.warn(f"Exception, request:{request.__dict__}, exc:{exc}")
|
|
171
179
|
app.state.logger.warn(traceback.format_exc())
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
payload = {"text": message}
|
|
175
|
-
try:
|
|
176
|
-
await async_request(app, "POST", self.get_app_config().WEBHOOK_BASE_URL, json=payload)
|
|
177
|
-
except Exception as e:
|
|
178
|
-
app.state.logger.warn(f"Notify failure, Exception:{e}")
|
|
180
|
+
message = f"【{self.DEFAULT_APP_NAME}】Unprocessed Exception, request:{request.__dict__}, exc:{exc}"
|
|
181
|
+
await send_webhook_message(app, message)
|
|
179
182
|
|
|
180
183
|
return await async_response(code="error_internal_server", message="Internal server error",
|
|
181
184
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
@@ -224,6 +227,7 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
224
227
|
|
|
225
228
|
stream_handler = logging.StreamHandler()
|
|
226
229
|
stream_handler.setFormatter(logging.Formatter(fmt=LOG_FMT))
|
|
230
|
+
stream_handler.addFilter(CorrelationIdFilter())
|
|
227
231
|
logger.addHandler(stream_handler)
|
|
228
232
|
|
|
229
233
|
return logger
|
|
@@ -235,6 +239,7 @@ class BaseFactory(metaclass=ABCMeta):
|
|
|
235
239
|
log_group_name=self.get_app_config().AWS_LOGGROUP_NAME,
|
|
236
240
|
boto3_client=logs_client, create_log_group=False)
|
|
237
241
|
watchtower_handler.setFormatter(logging.Formatter(fmt=LOG_FMT_NO_DT, datefmt=LOG_DT_FMT))
|
|
242
|
+
watchtower_handler.addFilter(CorrelationIdFilter())
|
|
238
243
|
app.state.logger.addHandler(watchtower_handler)
|
|
239
244
|
|
|
240
245
|
def __init_builtin_api(self, app):
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EventCodeEnum(str, Enum):
|
|
5
|
+
MANUAL_CANCEL_SERVICE_TICKET = "manual_cancel_service_ticket"
|
|
6
|
+
MANUAL_TRACING_START_SERVICE_TICKET = "manual_tracing_start_service_ticket"
|
|
7
|
+
MANUAL_TRACING_STOP_SERVICE_TICKET = "manual_tracing_stop_service_ticket"
|
|
8
|
+
MANUAL_CREATE_SERVICE_TICKET = "manual_create_service_ticket"
|
|
9
|
+
MANUAL_IMPORT_RESERVATION_SMWS = "manual_import_reservation_smws"
|
|
10
|
+
MANUAL_MODIFY_USER_SERVICE_TICKET = "manual_modify_user_service_ticket"
|
|
11
|
+
MANUAL_MODIFY_ENABLE_NOTIFY_SERVICE_TICKET = "manual_modify_enable_notify_service_ticket"
|
|
12
|
+
MANUAL_MODIFY_DISABLE_NOTIFY_SERVICE_TICKET = "manual_modify_disable_notify_service_ticket"
|
|
13
|
+
MANUAL_MODIFY_ESTIMATED_ARRIVAL_TIME_SERVICE_TICKET = "manual_modify_estimated_arrival_time_service_ticket"
|
|
14
|
+
MANUAL_MODIFY_ESTIMATED_DELIVERY_TIME_SERVICE_TICKET = "manual_modify_estimated_delivery_time_service_ticket"
|
|
15
|
+
MANUAL_DELETE_ESTIMATED_DELIVERY_TIME_SERVICE_TICKET = "manual_delete_estimated_delivery_time_service_ticket"
|
|
16
|
+
MANUAL_IMPORT_RESERVATION_MODIFY_USER_SERVICE_TICKET = "manual_import_reservation_modify_user_service_ticket"
|
|
17
|
+
MANUAL_IMPORT_RESERVATION_MODIFY_ESTIMATED_ARRIVAL_TIME_SERVICE_TICKET = "manual_import_reservation_modify_estimated_arrival_time_service_ticket"
|
|
18
|
+
MANUAL_IMPORT_RESERVATION_MODIFY_ESTIMATED_DELIVERY_TIME_SERVICE_TICKET = "manual_import_reservation_modify_estimated_delivery_time_service_ticket"
|
|
19
|
+
MANUAL_IMPORT_RESERVATION_DELETE_ESTIMATED_DELIVERY_TIME_SERVICE_TICKET = "manual_import_reservation_delete_estimated_delivery_time_service_ticket"
|
|
20
|
+
MANUAL_BOOKING_MESSAGE_SERVICE_TICKET = "manual_booking_message_service_ticket"
|
|
21
|
+
MANUAL_DELIVERY_MESSAGE_SERVICE_TICKET = "manual_delivery_message_service_ticket"
|
|
22
|
+
|
|
23
|
+
SYSTEM_TRACING_START_SERVICE_TICKET = "system_tracing_start_service_ticket"
|
|
24
|
+
SYSTEM_TRACING_STOP_SERVICE_TICKET = "system_tracing_stop_service_ticket"
|
|
25
|
+
|
|
26
|
+
NOSHOW_SERVICE_TICKET_AUTO_CANCEL = "noshow_service_ticket_auto_cancel"
|
|
27
|
+
IMPORT_RESERVATION_CONFLICT_AUTO_CANCEL = "import_reservation_conflict_auto_cancel"
|
|
28
|
+
BOOKING_REMINDING_SERVICE_TICKET = "booking_reminding_service_ticket"
|
|
29
|
+
TODAY_REPAIR_WORKING_SERVICE_TICKET_AUTO_CLOSED = "today_repair_working_service_ticket_auto_closed"
|
|
30
|
+
|
|
31
|
+
CAR_IN_ESTABLISHED = "car_in_established"
|
|
32
|
+
CAR_IN_RECEPTION = "car_in_reception"
|
|
33
|
+
CAR_IN_WORKING = "car_in_working"
|
|
34
|
+
CAR_IN_DISPATCH = "car_in_dispatch"
|
|
35
|
+
CAR_IN_DETAILING = "car_in_detailing"
|
|
36
|
+
CAR_IN_NO_TICKET = "car_in_no_ticket"
|
|
37
|
+
CAR_OUT_FINISHED = "car_out_finished"
|
|
38
|
+
CAR_OUT_GENERAL = "car_out_general"
|
|
39
|
+
CAR_OUT_NO_SERVE = "car_out_no_serve"
|
|
40
|
+
CAR_OUT_TEST = "car_out_test"
|
|
41
|
+
CAR_OUT_ACC = "car_out_acc"
|
|
42
|
+
|
|
43
|
+
LPR_CHANGE_STATE = "lpr_change_state"
|
|
44
|
+
LPR_CHANGE_POSITION = "lpr_change_position"
|
|
45
|
+
|
|
46
|
+
REPAIR_OVERDUE = "repair_overdue"
|
|
47
|
+
DETAILING_OVERDUE = "detailing_overdue"
|
|
48
|
+
DELIVERY_OVERDUE = "delivery_overdue"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WebsocketChannelEnum(str, Enum):
|
|
5
|
+
OVER_ALL = "over_all" # 儀表板
|
|
6
|
+
RECEPTION_CENTER = "reception_center" # 接待中心
|
|
7
|
+
CAR_MOVEMENT = "car_movement" # 車輛動態
|
|
8
|
+
USER_BELL_NOTIFICATION = "user_bell_notification" # 個人小鈴噹
|
|
9
|
+
DAILY_DELIVERY_SUMMARY = "daily_delivery_summary" # 當日交車統計
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
DATE_FMT = "%Y-%m-%d"
|
|
5
|
+
TIME_FMT = "%H:%M"
|
|
6
|
+
DT_FMT = f"{DATE_FMT} {TIME_FMT}:%S"
|
|
7
|
+
|
|
8
|
+
DEFAULT_LOGGER_NAME = "uvicorn"
|
|
9
|
+
LOG_DT_FMT = f"{DT_FMT},%03d"
|
|
10
|
+
LOG_FMT_NO_DT = "[%(levelname)7s][%(correlation_id)s][%(name)s][%(filename)s::%(funcName)s(%(lineno)s)]: %(message)s"
|
|
11
|
+
LOG_FMT = "[%(asctime)s]" + LOG_FMT_NO_DT
|
|
12
|
+
|
|
13
|
+
ARR_EXPORT_SHORT_FMT = "MM/DD HH:mm"
|
|
14
|
+
ARR_DASH_SHORT_FMT = "MM-DD HH:mm"
|
|
15
|
+
ARR_EXPORT_DATE_FMT = "YYYY/MM/DD"
|
|
16
|
+
ARR_EXPORT_DATE_ONLY_FMT = "MM/DD"
|
|
17
|
+
ARR_DATE_FMT = "YYYY-MM-DD"
|
|
18
|
+
ARR_DATE_FMT_NO_DASH = "YYYYMMDD"
|
|
19
|
+
ARR_EXPORT_DATETIME_FMT = "YYYY/MM/DD HH:mm"
|
|
20
|
+
ARR_TIME_FMT = "HH:mm:ss"
|
|
21
|
+
ARR_TIME_ONLY_FMT = "HH:mm"
|
|
22
|
+
ARR_RFC3339_DT_FMT = f"{ARR_DATE_FMT}T{ARR_TIME_FMT}ZZ"
|
|
23
|
+
ARR_DATETIME_FMT_NO_DASH = "YYYYMMDDHHmmss"
|
|
24
|
+
ARR_HUMAN_READ_FMT = f"{ARR_DATE_FMT} {ARR_TIME_FMT}"
|
|
25
|
+
ARR_IMPORT_TS_FMT = "YYYYMMDDHHmmss.SSSSSS"
|
|
26
|
+
ARR_STR_DAY = "day"
|
|
27
|
+
|
|
28
|
+
STR_EMPTY = ""
|
|
29
|
+
STR_SPACE = " "
|
|
30
|
+
STR_DASH = "-"
|
|
31
|
+
STR_UNDERSCORE = "_"
|
|
32
|
+
STR_SLASH = "/"
|
|
33
|
+
STR_COMMA = ","
|
|
34
|
+
|
|
35
|
+
SECONDS_PER_YEAR = 30758400
|
|
36
|
+
SECONDS_PER_DAY = 86400
|
|
37
|
+
SECONDS_PER_MIN = 60
|
|
38
|
+
|
|
39
|
+
DEF_PAGE_NO = 1
|
|
40
|
+
DEF_PAGE_SIZE = 15
|
|
41
|
+
DEF_ORDER_BY = "_id"
|
|
42
|
+
|
|
43
|
+
REDIS_LPR_DATA_LIST_PREFIX = "lpr_data_list"
|
|
44
|
+
|
|
45
|
+
CORRELATION_ID_HEADER_KEY_NAME = "X-Request-ID"
|
|
46
|
+
|
|
47
|
+
SOURCE_SMART_WORKSHOP_IMPORTED_SERVICE_TICKET = 'SmartWorkshopImportedServiceTicket'
|
|
48
|
+
SOURCE_SMART_WORKSHOP_UPDATE_SERVICE_TICKET = 'SmartWorkshopUpdateServiceTicket'
|
|
49
|
+
SOURCE_SMART_WORKSHOP_IMPORTED_UPDATE_SERVICE_TICKET = 'SmartWorkshopImportedUpdateServiceTicket'
|
|
50
|
+
SOURCE_SMART_WORKSHOP_IMPORTED_DMS_DATA = 'SmartWorkshopImportedDMSData'
|
|
51
|
+
SOURCE_SMART_WORKSHOP_IMPORTED_SELF_SELLING_DATA = 'SmartWorkshopImportedSelfSellingData'
|
|
52
|
+
SOURCE_WEBHOOK = 'Webhook'
|
|
53
|
+
SOURCE_SMART_WORKSHOP = 'SmartWorkshopManual'
|
|
54
|
+
SOURCE_SERVICE_GO_LOGIN = 'ServiceGoLogin'
|
|
55
|
+
SOURCE_SERVICE_GO_REGISTER = 'ServiceGoRegister'
|
|
56
|
+
SOURCE_VIP_CODE_MANUAL_APPROVE = 'VIPCodeManualApprove'
|
|
57
|
+
SOURCE_BINDING_SERVICE_GO = 'BindingFromServiceGo'
|
|
58
|
+
SOURCE_BINDING_VIP_CODE = 'BindingFromVIPCode'
|
|
59
|
+
SOURCE_BINDING_SMART_WORKSHOP = 'BindingFromSmartWorkshop'
|
|
60
|
+
SOURCE_UNBINDING_SERVICE_GO = 'UnbindingFromServiceGo'
|
|
61
|
+
SOURCE_UNBINDING_SMART_WORKSHOP = 'UnbindingFromSmartWorkshop'
|
|
@@ -2,6 +2,18 @@ from motor.motor_asyncio import AsyncIOMotorClient
|
|
|
2
2
|
from pymongo.errors import ServerSelectionTimeoutError
|
|
3
3
|
|
|
4
4
|
from .exception.internal_exception import DatabaseInitializeFailureException, DatabaseConnectFailureException
|
|
5
|
+
from bson import Decimal128
|
|
6
|
+
from bson.codec_options import CodecOptions, TypeDecoder, TypeRegistry
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Decimal128Decoder(TypeDecoder):
|
|
11
|
+
"""自定義 Decimal128 解碼器,自動轉換為 Python Decimal"""
|
|
12
|
+
bson_type = Decimal128
|
|
13
|
+
python_type = Decimal
|
|
14
|
+
|
|
15
|
+
def transform_bson(self, value):
|
|
16
|
+
return value.to_decimal()
|
|
5
17
|
|
|
6
18
|
|
|
7
19
|
class MongoDB:
|
|
@@ -39,10 +51,20 @@ class MongoDB:
|
|
|
39
51
|
if self.client:
|
|
40
52
|
self.client.close()
|
|
41
53
|
|
|
42
|
-
def get_database(self):
|
|
54
|
+
def get_database(self, db_name: str = None):
|
|
43
55
|
if not self.client:
|
|
44
56
|
raise DatabaseConnectFailureException()
|
|
45
|
-
|
|
57
|
+
|
|
58
|
+
type_registry = TypeRegistry([Decimal128Decoder()])
|
|
59
|
+
codec_options = CodecOptions(type_registry=type_registry)
|
|
60
|
+
|
|
61
|
+
name = db_name if db_name else self.db_name
|
|
62
|
+
return self.client.get_database(name, codec_options=codec_options)
|
|
63
|
+
|
|
64
|
+
def get_collection(self, collection_name: str, db_name: str = None):
|
|
65
|
+
"""直接取得指定的集合"""
|
|
66
|
+
db = self.get_database(db_name)
|
|
67
|
+
return db[collection_name]
|
|
46
68
|
|
|
47
69
|
async def get_mongodb_uri(self) -> str:
|
|
48
70
|
if self.user_name and self.password:
|
|
@@ -58,7 +58,7 @@ class VinLengthOrFormatException(InternalBaseException):
|
|
|
58
58
|
|
|
59
59
|
class PhoneFormatException(InternalBaseException):
|
|
60
60
|
code = "error_phone_format"
|
|
61
|
-
message = "Phone only allow
|
|
61
|
+
message = "Phone only allow start with 09 and add 8 digits"
|
|
62
62
|
|
|
63
63
|
def __init__(self, message: str = None, **kwargs):
|
|
64
64
|
_message = message or self.message
|
|
@@ -74,6 +74,15 @@ class DateFormatException(InternalBaseException):
|
|
|
74
74
|
super().__init__(status.HTTP_422_UNPROCESSABLE_ENTITY, self.code, _message, **kwargs)
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
class EMailFormatException(InternalBaseException):
|
|
78
|
+
code = "error_email_format"
|
|
79
|
+
message = "Email format error"
|
|
80
|
+
|
|
81
|
+
def __init__(self, message: str = None, **kwargs):
|
|
82
|
+
_message = message or self.message
|
|
83
|
+
super().__init__(status.HTTP_422_UNPROCESSABLE_ENTITY, self.code, _message, **kwargs)
|
|
84
|
+
|
|
85
|
+
|
|
77
86
|
class LineUidNullPointException(InternalBaseException):
|
|
78
87
|
code = "error_line_uid_not_be_null"
|
|
79
88
|
message = "Line uid can not be null"
|