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.
Files changed (48) hide show
  1. {internal-1.0.86 → internal-1.1.49.1}/PKG-INFO +6 -1
  2. {internal-1.0.86 → internal-1.1.49.1}/pyproject.toml +6 -1
  3. {internal-1.0.86 → internal-1.1.49.1}/src/internal/base_config.py +16 -6
  4. {internal-1.0.86 → internal-1.1.49.1}/src/internal/base_factory.py +43 -38
  5. 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
  6. internal-1.1.49.1/src/internal/common_enum/device_code.py +10 -0
  7. internal-1.1.49.1/src/internal/common_enum/event_code.py +48 -0
  8. 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
  9. {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/notify_type.py +3 -3
  10. {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/operator_type.py +3 -0
  11. internal-1.1.49.1/src/internal/common_enum/point_type.py +6 -0
  12. internal-1.1.49.1/src/internal/common_enum/websocket_channel.py +9 -0
  13. internal-1.1.49.1/src/internal/const.py +61 -0
  14. {internal-1.0.86 → internal-1.1.49.1}/src/internal/database.py +24 -2
  15. {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/app_exception.py +10 -1
  16. internal-1.1.49.1/src/internal/http/requests.py +174 -0
  17. {internal-1.0.86 → internal-1.1.49.1}/src/internal/http/responses.py +45 -4
  18. internal-1.1.49.1/src/internal/middleware/log_request.py +90 -0
  19. internal-1.1.49.1/src/internal/model/base_model.py +198 -0
  20. internal-1.1.49.1/src/internal/utils.py +155 -0
  21. {internal-1.0.86 → internal-1.1.49.1}/src/internal/validator_utils.py +17 -4
  22. internal-1.0.86/src/internal/common_enum/event_code.py +0 -29
  23. internal-1.0.86/src/internal/common_enum/event_trigger_type.py +0 -29
  24. internal-1.0.86/src/internal/common_enum/feature.py +0 -6
  25. internal-1.0.86/src/internal/const.py +0 -41
  26. internal-1.0.86/src/internal/http/requests.py +0 -59
  27. internal-1.0.86/src/internal/middleware/log_request.py +0 -27
  28. internal-1.0.86/src/internal/model/base_model.py +0 -103
  29. internal-1.0.86/src/internal/utils.py +0 -50
  30. {internal-1.0.86 → internal-1.1.49.1}/README.md +0 -0
  31. {internal-1.0.86 → internal-1.1.49.1}/src/internal/__init__.py +0 -0
  32. {internal-1.0.86 → internal-1.1.49.1}/src/internal/cache_redis.py +0 -0
  33. {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/__init__.py +0 -0
  34. {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/description_type.py +0 -0
  35. {internal-1.0.86 → internal-1.1.49.1}/src/internal/common_enum/order_type.py +0 -0
  36. {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/__init__.py +0 -0
  37. {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/base_exception.py +0 -0
  38. {internal-1.0.86 → internal-1.1.49.1}/src/internal/exception/internal_exception.py +0 -0
  39. {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/__init__.py +0 -0
  40. {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/amazon/__init__.py +0 -0
  41. {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/amazon/aws/__init__.py +0 -0
  42. {internal-1.0.86 → internal-1.1.49.1}/src/internal/ext/amazon/aws/const.py +0 -0
  43. {internal-1.0.86 → internal-1.1.49.1}/src/internal/http/__init__.py +0 -0
  44. {internal-1.0.86 → internal-1.1.49.1}/src/internal/interface/__init__.py +0 -0
  45. {internal-1.0.86 → internal-1.1.49.1}/src/internal/interface/base_interface.py +0 -0
  46. {internal-1.0.86 → internal-1.1.49.1}/src/internal/middleware/__init__.py +0 -0
  47. {internal-1.0.86 → internal-1.1.49.1}/src/internal/model/__init__.py +0 -0
  48. {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.0.86
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.0.86"
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 = True
14
+ REQUEST_VERIFY_SSL: bool = False
15
15
  REQUEST_PROXY: str = ''
16
- REQUEST_RETRY: int = 5
17
- REQUEST_CONN_POOL_TIMEOUT: float = 5
18
- REQUEST_CONN_TIMEOUT: float = 5
19
- REQUEST_WRITE_TIMEOUT: float = 5
20
- RESPONSE_READ_TIMEOUT: float = 5
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 async_request
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
- self.__load_local_config()
113
- app.state.config = self.get_app_config()
114
- self.__setup_main_logger(app, level=logging.DEBUG)
115
- app.state.aws_session = aws.init_app(app)
116
- self.__setup_cloud_log(app)
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
- if self.get_app_config().WEBHOOK_BASE_URL:
141
- message = f"【{self.DEFAULT_APP_NAME}】Bad gateway, request:{request.__dict__}, exc:{exc}"
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
- if self.get_app_config().WEBHOOK_BASE_URL:
149
- message = f"【{self.DEFAULT_APP_NAME}】Gateway timeout, request:{request.__dict__}, exc:{exc}"
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
- if self.get_app_config().WEBHOOK_BASE_URL:
173
- message = f"【{self.DEFAULT_APP_NAME}】Unprocessed Exception, request:{request.__dict__}, exc:{exc}"
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):
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
3
 
4
- class ContactTypeEnum(str, Enum):
4
+ class CarRelationTypeEnum(str, Enum):
5
5
  OWNER = "owner"
6
6
  DRIVER = "driver"
7
7
  BUYER = "buyer"
@@ -0,0 +1,10 @@
1
+ from enum import Enum
2
+
3
+
4
+ class DeviceCodeEnum(str, Enum):
5
+ DOOR_IN = "door_in"
6
+ DOOR_OUT = "door_out"
7
+ REPAIR_IN = "repair_in"
8
+ REPAIR_OUT = "repair_out"
9
+ DETAIL_IN = "detail_in"
10
+ DETAIL_OUT = "detail_out"
@@ -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"
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
3
 
4
- class LpnrDirectionEnum(str, Enum):
4
+ class LPRDirectionEnum(str, Enum):
5
5
  IN = "in"
6
6
  OUT = "out"
7
7
  UNKNOWN = "unknown"
@@ -2,8 +2,8 @@ from enum import Enum
2
2
 
3
3
 
4
4
  class NotifyTypeEnum(str, Enum):
5
- APP = "app"
6
5
  LINE = "line"
7
6
  SMS = "sms"
8
- NOTIFICATION = "notification"
9
- WEBSOCKET = "websocket"
7
+ APP = "app"
8
+ USER_BELL_NOTIFICATION = "user_bell_notification"
9
+ WEBSOCKET = "websocket"
@@ -5,3 +5,6 @@ class OperatorTypeEnum(str, Enum):
5
5
  SYSTEM = "system"
6
6
  USER = "user"
7
7
  CUSTOMER = "customer"
8
+ PROVIDER = "provider"
9
+ CLIENT = "client"
10
+ ADMIN = "admin"
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class PointTypeEnum(str, Enum):
5
+ INVOICE_REWARD = "invoice_reward"
6
+ ACTIVITY = "activity"
@@ -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
- return self.client[self.db_name]
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 numeric"
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"