repositories.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. from datetime import datetime
  2. from sqlalchemy import case, func, select
  3. from sqlalchemy.orm import Session
  4. from app.db.models import ApiKey, AppApiKey, AppDefinition, AppInvocationAudit, GatewayRequestAudit
  5. class GatewayRequestAuditRepository:
  6. def __init__(self, db: Session) -> None:
  7. self.db = db
  8. def create(
  9. self,
  10. *,
  11. request_id: str,
  12. method: str,
  13. path: str,
  14. query_string: str | None,
  15. target_service: str | None,
  16. target_url: str | None,
  17. status_code: int | None,
  18. duration_ms: int,
  19. client_host: str | None,
  20. user_agent: str | None,
  21. error_message: str | None) -> GatewayRequestAudit:
  22. entity = GatewayRequestAudit(
  23. request_id=request_id,
  24. method=method,
  25. path=path,
  26. query_string=query_string,
  27. target_service=target_service,
  28. target_url=target_url,
  29. status_code=status_code,
  30. duration_ms=duration_ms,
  31. client_host=client_host,
  32. user_agent=user_agent,
  33. error_message=error_message)
  34. self.db.add(entity)
  35. self.db.commit()
  36. self.db.refresh(entity)
  37. return entity
  38. def list_by_scope(
  39. self,
  40. *,
  41. request_id: str | None = None,
  42. target_service: str | None = None,
  43. limit: int = 100) -> list[GatewayRequestAudit]:
  44. stmt = select(GatewayRequestAudit)
  45. if request_id is not None:
  46. stmt = stmt.where(GatewayRequestAudit.request_id == request_id)
  47. if target_service is not None:
  48. stmt = stmt.where(GatewayRequestAudit.target_service == target_service)
  49. stmt = stmt.order_by(GatewayRequestAudit.created_time.desc()).limit(limit)
  50. return list(self.db.scalars(stmt))
  51. def stats_by_service(self) -> list[tuple[str, int, int, float]]:
  52. target_service = func.coalesce(GatewayRequestAudit.target_service, "api-gateway")
  53. error_count = func.sum(
  54. case(
  55. (GatewayRequestAudit.status_code >= 400, 1),
  56. else_=0)
  57. )
  58. stmt = (
  59. select(
  60. target_service.label("target_service"),
  61. func.count(GatewayRequestAudit.id),
  62. error_count,
  63. func.avg(GatewayRequestAudit.duration_ms))
  64. .group_by(target_service)
  65. .order_by(target_service.asc())
  66. )
  67. rows = self.db.execute(stmt).all()
  68. return [
  69. (
  70. str(row[0]),
  71. int(row[1] or 0),
  72. int(row[2] or 0),
  73. float(row[3] or 0.0))
  74. for row in rows
  75. ]
  76. class ApiKeyRepository:
  77. def __init__(self, db: Session) -> None:
  78. self.db = db
  79. def create(
  80. self,
  81. *,
  82. name: str,
  83. key_prefix: str,
  84. key_hash: str,
  85. scopes: str | None,
  86. expires_time: datetime | None) -> ApiKey:
  87. entity = ApiKey(
  88. name=name,
  89. key_prefix=key_prefix,
  90. key_hash=key_hash,
  91. status="active",
  92. scopes=scopes,
  93. expires_time=expires_time)
  94. self.db.add(entity)
  95. self.db.commit()
  96. self.db.refresh(entity)
  97. return entity
  98. def list_all(self) -> list[ApiKey]:
  99. stmt = (
  100. select(ApiKey)
  101. .order_by(ApiKey.created_time.desc())
  102. )
  103. return list(self.db.scalars(stmt))
  104. def has_any(self) -> bool:
  105. stmt = select(ApiKey.id).limit(1)
  106. return self.db.scalar(stmt) is not None
  107. def get_by_id(self, *, api_key_id: str) -> ApiKey | None:
  108. stmt = (
  109. select(ApiKey)
  110. .where(ApiKey.id == api_key_id)
  111. .limit(1)
  112. )
  113. return self.db.scalar(stmt)
  114. def get_active_by_hash(self, *, key_hash: str) -> ApiKey | None:
  115. stmt = (
  116. select(ApiKey)
  117. .where(ApiKey.key_hash == key_hash)
  118. .where(ApiKey.status == "active")
  119. .limit(1)
  120. )
  121. return self.db.scalar(stmt)
  122. def touch_last_used_time(self, *, api_key_id: str) -> None:
  123. entity = self.db.get(ApiKey, api_key_id)
  124. if entity is None:
  125. return
  126. entity.last_used_time = datetime.utcnow()
  127. self.db.commit()
  128. def update_status(
  129. self,
  130. *,
  131. api_key_id: str,
  132. status: str) -> ApiKey | None:
  133. entity = self.get_by_id(api_key_id=api_key_id)
  134. if entity is None:
  135. return None
  136. entity.status = status
  137. self.db.commit()
  138. self.db.refresh(entity)
  139. return entity
  140. class AppDefinitionRepository:
  141. def __init__(self, db: Session) -> None:
  142. self.db = db
  143. def create(
  144. self,
  145. *,
  146. code: str,
  147. name: str,
  148. target_type: str,
  149. target_id: str,
  150. description: str | None = None,
  151. owner_user_id: str | None = None,
  152. settings_json: str | None = None) -> AppDefinition:
  153. entity = AppDefinition(
  154. code=code,
  155. name=name,
  156. description=description,
  157. status="draft",
  158. target_type=target_type,
  159. target_id=target_id,
  160. owner_user_id=owner_user_id,
  161. settings_json=settings_json)
  162. self.db.add(entity)
  163. self.db.commit()
  164. self.db.refresh(entity)
  165. return entity
  166. def get_by_id(self, *, app_id: str) -> AppDefinition | None:
  167. stmt = select(AppDefinition).where(AppDefinition.id == app_id).limit(1)
  168. return self.db.scalar(stmt)
  169. def get_by_code(self, *, code: str) -> AppDefinition | None:
  170. stmt = select(AppDefinition).where(AppDefinition.code == code).limit(1)
  171. return self.db.scalar(stmt)
  172. def list_all(self) -> list[AppDefinition]:
  173. stmt = select(AppDefinition).order_by(AppDefinition.created_time.desc())
  174. return list(self.db.scalars(stmt))
  175. def update(
  176. self,
  177. *,
  178. app_id: str,
  179. name: str | None = None,
  180. description: str | None = None,
  181. target_type: str | None = None,
  182. target_id: str | None = None,
  183. settings_json: str | None = None) -> AppDefinition | None:
  184. entity = self.get_by_id(app_id=app_id)
  185. if entity is None:
  186. return None
  187. if name is not None:
  188. entity.name = name
  189. if description is not None:
  190. entity.description = description
  191. if target_type is not None:
  192. entity.target_type = target_type
  193. if target_id is not None:
  194. entity.target_id = target_id
  195. if settings_json is not None:
  196. entity.settings_json = settings_json
  197. self.db.commit()
  198. self.db.refresh(entity)
  199. return entity
  200. def update_status(self, *, app_id: str, status: str) -> AppDefinition | None:
  201. entity = self.get_by_id(app_id=app_id)
  202. if entity is None:
  203. return None
  204. entity.status = status
  205. self.db.commit()
  206. self.db.refresh(entity)
  207. return entity
  208. class AppApiKeyRepository:
  209. def __init__(self, db: Session) -> None:
  210. self.db = db
  211. def create(
  212. self,
  213. *,
  214. app_id: str,
  215. name: str,
  216. key_prefix: str,
  217. key_hash: str,
  218. scopes: str | None,
  219. expires_time: datetime | None) -> AppApiKey:
  220. entity = AppApiKey(
  221. app_id=app_id,
  222. name=name,
  223. key_prefix=key_prefix,
  224. key_hash=key_hash,
  225. status="active",
  226. scopes=scopes,
  227. expires_time=expires_time)
  228. self.db.add(entity)
  229. self.db.commit()
  230. self.db.refresh(entity)
  231. return entity
  232. def list_by_app(self, *, app_id: str) -> list[AppApiKey]:
  233. stmt = (
  234. select(AppApiKey)
  235. .where(AppApiKey.app_id == app_id)
  236. .order_by(AppApiKey.created_time.desc())
  237. )
  238. return list(self.db.scalars(stmt))
  239. def get_by_id(self, *, api_key_id: str) -> AppApiKey | None:
  240. stmt = select(AppApiKey).where(AppApiKey.id == api_key_id).limit(1)
  241. return self.db.scalar(stmt)
  242. def get_active_by_hash(self, *, key_hash: str) -> AppApiKey | None:
  243. stmt = (
  244. select(AppApiKey)
  245. .where(AppApiKey.key_hash == key_hash)
  246. .where(AppApiKey.status == "active")
  247. .limit(1)
  248. )
  249. return self.db.scalar(stmt)
  250. def touch_last_used_time(self, *, api_key_id: str) -> None:
  251. entity = self.db.get(AppApiKey, api_key_id)
  252. if entity is None:
  253. return
  254. entity.last_used_time = datetime.utcnow()
  255. self.db.commit()
  256. def update_status(self, *, api_key_id: str, status: str) -> AppApiKey | None:
  257. entity = self.get_by_id(api_key_id=api_key_id)
  258. if entity is None:
  259. return None
  260. entity.status = status
  261. self.db.commit()
  262. self.db.refresh(entity)
  263. return entity
  264. class AppInvocationAuditRepository:
  265. def __init__(self, db: Session) -> None:
  266. self.db = db
  267. def create(
  268. self,
  269. *,
  270. app_id: str,
  271. request_id: str,
  272. target_type: str,
  273. target_id: str,
  274. invoke_type: str,
  275. status: str,
  276. duration_ms: int,
  277. api_key_prefix: str | None = None,
  278. session_id: str | None = None,
  279. run_request_id: str | None = None,
  280. error_code: str | None = None,
  281. error_message: str | None = None,
  282. client_metadata_json: str | None = None) -> AppInvocationAudit:
  283. entity = AppInvocationAudit(
  284. app_id=app_id,
  285. api_key_prefix=api_key_prefix,
  286. request_id=request_id,
  287. session_id=session_id,
  288. run_request_id=run_request_id,
  289. target_type=target_type,
  290. target_id=target_id,
  291. invoke_type=invoke_type,
  292. status=status,
  293. duration_ms=duration_ms,
  294. error_code=error_code,
  295. error_message=error_message,
  296. client_metadata_json=client_metadata_json)
  297. self.db.add(entity)
  298. self.db.commit()
  299. self.db.refresh(entity)
  300. return entity
  301. def list_by_app(self, *, app_id: str, limit: int = 100) -> list[AppInvocationAudit]:
  302. stmt = (
  303. select(AppInvocationAudit)
  304. .where(AppInvocationAudit.app_id == app_id)
  305. .order_by(AppInvocationAudit.created_time.desc())
  306. .limit(limit)
  307. )
  308. return list(self.db.scalars(stmt))