router.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. import time
  2. import uuid
  3. import json
  4. import requests
  5. from typing import List
  6. from app.core.logger import logger
  7. from fastapi import APIRouter, Request, Response, Query, Depends, Body, UploadFile, File, HTTPException
  8. from fastapi.responses import RedirectResponse
  9. from sqlalchemy.orm import Session
  10. from app.utils.redis_utils import redis_qpush
  11. from app.utils.validation_utils import validate_user_inputs
  12. from app.core.redis import get_redis_client
  13. from app.core.database import get_db
  14. from app.core.auth import get_current_user
  15. from redis.asyncio import Redis
  16. from app.utils.response import success, fail
  17. from app.models.user import VasUser
  18. from app.models.order import VasOrder
  19. from app.models.schema import VasSchema
  20. from app.models.product import VasProduct
  21. from app.models.payment import VasPayment
  22. from app.schemas.common import ApiResponse, PageResponse
  23. from app.schemas.troov import TroovRate
  24. from app.schemas.sms import ShortMessageDetail
  25. from app.schemas.configuration import ConfigurationCreate, ConfigurationUpdate, ConfigurationOut
  26. from app.schemas.email_authorizations import EmailContent, EmailAuthorizationCreate, EmailAuthorizationUpdate, EmailAuthorizationOut
  27. from app.schemas.card import CardCreate, CardOut
  28. from app.schemas.task import TaskCreate, TaskOut, TaskUpdate
  29. from app.schemas.short_url import ShortUrlCreate, ShortUrlOut
  30. from app.schemas.auto_booking import AutoBookingCreate, AutoBookingOut
  31. from app.schemas.http_session import HttpSessionCreate, HttpSessionUpdate,HttpSessionOut
  32. from app.schemas.fake import FakeUser
  33. from app.schemas.auth import SendBindCodeRequest, SendResetCodeRequest, BindEmailRequest, ResetPasswordRequest, LoginRequest, LoginData, AutoRegisterRequest, AutoRegisterData
  34. from app.schemas.user import VasUserCreate, VasUserUpdate, VasUserSetProfiles, VasUserOut
  35. from app.schemas.product import VasProductCreate, VasProductUpdate, VasProductOut
  36. from app.schemas.product_routing import VasProductRoutingCreate, VasProductRoutingOut
  37. from app.schemas.schema import VasSchemaCreate, VasSchemaUpdate, VasSchemaOut
  38. from app.schemas.order import VasOrderCreate, VasOrderPatchUserInputs, VasOrderOut
  39. from app.schemas.payment import VasPaymentCreate, VasPaymentOut
  40. from app.schemas.payment_qr import VasPaymentQrCreate, VasPaymentQrSetEnableIn, VasPaymentQrOut
  41. from app.schemas.payment_provider import VasPaymentProviderCreate, VasPaymentProviderUpdate, VasPaymentProviderOut
  42. from app.schemas.webhook import SMSHelperWebhookPayload
  43. from app.schemas.vas_task import VasTaskCreate, VasTaskUpdate, VasTaskOut
  44. from app.schemas.ticket import VasTicketCreate, VasTicketOut, VasTicketStatusUpdate, VasTicketMessageCreate, VasTicketMessageOut
  45. from app.schemas.slot_snapshot import SlotSnapshotCreate, SlotSnapshotOut
  46. from app.schemas.telegram import TelegramIn
  47. from app.schemas.wechat import WechatIn
  48. from app.schemas.resource import FileUploadOut
  49. from app.schemas.statistics import VasStatisticsOverviewOut
  50. from app.services.configuration_service import ConfigurationService
  51. from app.services.troov_service import get_rate_by_date
  52. from app.services.sms_service import save_short_message, query_short_message
  53. from app.services.email_authorizations_service import EmailAuthorizationService
  54. from app.services.short_url_service import ShortUrlService
  55. from app.services.task_service import TaskService
  56. from app.services.card_service import CardService
  57. from app.services.seaweedfs_service import SeaweedFSService
  58. from app.services.auto_booking_service import AutoBookingService
  59. from app.services.http_session_service import HttpSessionService
  60. from app.services.fake_service import generate_fake_users
  61. from app.services.auth_service import AuthService
  62. from app.services.user_service import UserService
  63. from app.services.product_service import ProductService
  64. from app.services.product_routing_service import ProductRoutingService
  65. from app.services.order_service import OrderService
  66. from app.services.schema_service import SchemaService
  67. from app.services.payment_service import PaymentService
  68. from app.services.payment_provider_service import PaymentProviderSerivce
  69. from app.services.payment_qr_service import PaymentQrService
  70. from app.services.vas_task_service import VasTaskService
  71. from app.services.webhook_service import WebhookService
  72. from app.services.notification_service import NotificationService
  73. from app.services.ticket_service import TicketService
  74. from app.services.telegram_service import TelegramService
  75. from app.services.wechat_service import WechatService
  76. from app.services.slot_snapshot_service import SlotSnapshotService
  77. from app.services.statistics_service import StatisticsService
  78. # 公共路由
  79. public_router = APIRouter()
  80. # 受保护路由
  81. protected_router = APIRouter()
  82. # 管理员级别路由
  83. admin_required_router = APIRouter()
  84. @admin_required_router.get("/ping", summary="心跳检测", tags=["测试接口"])
  85. def ping():
  86. return {"message": "pong"}
  87. @admin_required_router.get("/sms/upload", summary="上报短信", tags=["短信接口"], response_model=ApiResponse[ShortMessageDetail])
  88. def sms_upload(
  89. phone: str = Query(..., description="手机号"),
  90. message: str = Query(..., description="短信内容"),
  91. max_ttl: int = Query(300, description="短信保存时间(秒)"),
  92. redis_client: Redis = Depends(get_redis_client)
  93. ):
  94. """
  95. 保存短信到 Redis
  96. """
  97. received_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
  98. msg = save_short_message(redis_client, phone, message, received_at, max_ttl)
  99. return success(data=msg)
  100. @admin_required_router.get("/sms/download", summary="读取短信", tags=["短信接口"], response_model=ApiResponse[List[ShortMessageDetail]])
  101. def sms_download(
  102. phone: str = Query(..., description="手机号"),
  103. keyword: str = Query('', description="短信内容关键字"),
  104. sent_at: str = Query('', description="筛选时间(可选)"),
  105. redis_client: Redis = Depends(get_redis_client)
  106. ):
  107. """
  108. 查询短信(支持关键字和时间过滤)
  109. """
  110. obj = query_short_message(redis_client, phone, keyword or None, sent_at or None)
  111. return success(data=obj)
  112. @admin_required_router.get("/troov/rate", summary="TROOV 查询rate", tags=["通用接口"], response_model=ApiResponse[List[TroovRate]])
  113. def troov_rate(date: str = Query(..., description="查询的日期, 格式: YYYY-MM-DD"),
  114. redis_client: Redis = Depends(get_redis_client)):
  115. # 调用 service 层获取数据
  116. obj = get_rate_by_date(redis_client, date)
  117. return success(data=obj)
  118. @admin_required_router.post("/dynamic-configurations", summary="创建动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  119. def dynamic_config_create(config_in: ConfigurationCreate, db: Session = Depends(get_db)):
  120. obj = ConfigurationService.create(db, config_in)
  121. return success(data=obj)
  122. @admin_required_router.get("/dynamic-configurations/all", summary="读取所有动态配置", tags=["动态配置"], response_model=ApiResponse[List[ConfigurationOut]])
  123. def dynamic_config_get_all(db: Session = Depends(get_db)):
  124. obj = ConfigurationService.get_all(db)
  125. return success(data=obj)
  126. @admin_required_router.get("/dynamic-configurations/key/{config_key}", summary="根据Key读取动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  127. def dynamic_config_get_by_key(config_key: str, db: Session = Depends(get_db)):
  128. config = ConfigurationService.get_by_key(db, config_key)
  129. return success(data=config)
  130. @admin_required_router.put("/dynamic-configurations/key/{config_key}", summary="根据Key更新动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  131. def dynamic_config_update_by_key(config_key: str, config_in: ConfigurationUpdate, db: Session = Depends(get_db)):
  132. config = ConfigurationService.update_by_key(db, config_key, config_in)
  133. return success(data=config)
  134. @admin_required_router.delete("/dynamic-configurations/key/{config_key}", summary="根据Key删除动态配置", tags=["动态配置"], response_model=ApiResponse[ConfigurationOut])
  135. def dynamic_config_delete_by_key(config_key: str, db: Session = Depends(get_db)):
  136. config = ConfigurationService.delete_by_key(db, config_key)
  137. return success(data=config)
  138. @admin_required_router.post("/http-session", summary="创建http session", tags=["会话管理"],response_model=ApiResponse[HttpSessionOut])
  139. def http_session_create(
  140. data: HttpSessionCreate,
  141. db: Session = Depends(get_db)
  142. ):
  143. logger.info(f"[Create HttpSession] sid={data.session_id}")
  144. obj = HttpSessionService.create(db, data)
  145. return success(data=obj)
  146. @admin_required_router.delete("/http-session", summary="删除http session", tags=["会话管理"], response_model=ApiResponse)
  147. def http_session_delete_by_sid(
  148. session_id: str = Query(...),
  149. db: Session = Depends(get_db)
  150. ):
  151. logger.info(f"[Delete HttpSession] sid={session_id}")
  152. HttpSessionService.delete_by_sid(db, session_id)
  153. return success()
  154. @admin_required_router.put("/http-session", summary="更新http session", tags=["会话管理"], response_model=ApiResponse[HttpSessionOut])
  155. def http_session_update_by_sid(
  156. session_id: str = Query(...),
  157. data: HttpSessionUpdate = Body(...),
  158. db: Session = Depends(get_db)
  159. ):
  160. logger.info(f"[Update HttpSession] sid={session_id}")
  161. obj = HttpSessionService.update_by_sid(db, session_id, data)
  162. return success(data=obj)
  163. @admin_required_router.get("/http-session", summary="读取http session", tags=["会话管理"],response_model=ApiResponse[HttpSessionOut])
  164. def http_session_get_by_sid(
  165. session_id: str = Query(...),
  166. db: Session = Depends(get_db)
  167. ):
  168. logger.info(f"[Get HttpSession] sid={session_id}")
  169. obj = HttpSessionService.get_by_sid(db, session_id)
  170. return success(data=obj)
  171. @admin_required_router.get("/email-authorizations", summary="查询所有内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[List[EmailAuthorizationOut]])
  172. def email_authorizations_get(db: Session = Depends(get_db)):
  173. obj = EmailAuthorizationService.get_all(db)
  174. return success(data=obj)
  175. @admin_required_router.post("/email-authorizations", summary="创建内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  176. def email_authorizations_create(data: EmailAuthorizationCreate, db: Session = Depends(get_db)):
  177. obj = EmailAuthorizationService.create(db, data)
  178. return success(data=obj)
  179. @admin_required_router.get("/email-authorizations/{id}", summary="通过id查询内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  180. def email_authorizations_get_by_id(id: int, db: Session = Depends(get_db)):
  181. email_auth = EmailAuthorizationService.get_by_id(db, id)
  182. return success(data=email_auth)
  183. @admin_required_router.put("/email-authorizations/{id}", summary="通过id更新内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  184. def email_authorizations_update_by_id(id: int, data: EmailAuthorizationUpdate, db: Session = Depends(get_db)):
  185. updated = EmailAuthorizationService.update(db, id, data)
  186. return success(data=updated)
  187. @admin_required_router.delete("/email-authorizations/{id}", summary="通过id删除内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  188. def email_authorizations_delete_by_id(id: int, db: Session = Depends(get_db)):
  189. deleted = EmailAuthorizationService.delete(db, id)
  190. return deleted
  191. @admin_required_router.get("/email-authorizations/email/{email}", summary="通过邮箱地址查询内部邮箱", tags=["邮箱接口"], response_model=ApiResponse[EmailAuthorizationOut])
  192. def email_authorizations_get_by_email(email: str, db: Session = Depends(get_db)):
  193. email_auth = EmailAuthorizationService.get_by_email(db, email)
  194. return email_auth
  195. @admin_required_router.post("/email-authorizations/fetch", summary="读取邮件, 仅限文本内容", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  196. def email_authorizations_fetch_email(
  197. email: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  198. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  199. recipient: str = Query(..., description="收件人账号或者名字"),
  200. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  201. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  202. sentDate: str = Query(..., description="发件日期, UTC时间, 格式: yyyy-mm-dd hh:mm:ss"),
  203. expiry: int = Query(300, description="邮件有效期, 单位秒, 从sentDate 开始算起"),
  204. db: Session = Depends(get_db)
  205. ):
  206. auth = EmailAuthorizationService.get_by_email(db, email)
  207. result = EmailAuthorizationService.fetch_email_authorizations(
  208. auth,
  209. sender=sender,
  210. recipient=recipient,
  211. subject_keywords=subjectKeywords,
  212. body_keywords=bodyKeywords,
  213. sent_date=sentDate,
  214. expiry=expiry,
  215. only_text=True
  216. )
  217. return success(data={"body": result})
  218. @admin_required_router.post("/email-authorizations/fetch-top", summary="从最近的几封邮件读取目标邮件,仅限文本内容, 性能会更好, 邮件多时有可能漏读", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  219. def email_authorizations_fetch_email_from_topn(
  220. email: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  221. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  222. recipient: str = Query(..., description="收件人账号或者名字"),
  223. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  224. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  225. top: int = Query(10, description="指定从最近几封邮件读取"),
  226. db: Session = Depends(get_db)
  227. ):
  228. auth = EmailAuthorizationService.get_by_email(db, email)
  229. result = EmailAuthorizationService.fetch_email_authorizations_from_top_n(
  230. auth,
  231. sender=sender,
  232. recipient=recipient,
  233. subject_keywords=subjectKeywords,
  234. body_keywords=bodyKeywords,
  235. top=top,
  236. only_text=True
  237. )
  238. return success(data={"body": result})
  239. @admin_required_router.post("/email-authorizations/forward", summary="转发邮件", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  240. def email_authorizations_forward_email(
  241. emailAccount: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  242. forwardTo: str = Query(..., description="转发到哪个邮箱地址, 格式: xxx@xxx.xxx"),
  243. sender: str = Query(..., description="发件人邮箱账号或者名字"),
  244. recipient: str = Query(..., description="收件人账号或者名字"),
  245. subjectKeywords: str = Query("", description="邮件主题关键词, 支持多个关键词, 用逗号隔开"),
  246. bodyKeywords: str = Query("", description="邮件内容关键词, 支持多个关键词, 用逗号隔开"),
  247. db: Session = Depends(get_db)
  248. ):
  249. auth = EmailAuthorizationService.get_by_email(db, emailAccount)
  250. result = EmailAuthorizationService.forward_first_matching_email(
  251. auth,
  252. forward_to = forwardTo,
  253. sender = sender,
  254. recipient = recipient,
  255. subject_keywords = subjectKeywords,
  256. body_keywords = bodyKeywords
  257. )
  258. return success(data={"body": result})
  259. @admin_required_router.post("/email-authorizations/sendmail", summary="发送邮件", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  260. def email_authorizations_send_email(
  261. emailAccount: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  262. sendTo: str = Query(..., description="收件人邮箱账号"),
  263. subject: str = Query(..., description="邮件主题"),
  264. contentType: str = Query("text", description="内容格式,支持 text 和 html"),
  265. content: EmailContent = Body("", description="邮件内容"),
  266. db: Session = Depends(get_db)
  267. ):
  268. auth = EmailAuthorizationService.get_by_email(db, emailAccount)
  269. result = EmailAuthorizationService.send_email(
  270. auth,
  271. send_to = sendTo,
  272. subject = subject,
  273. content_type = contentType,
  274. content = content.body
  275. )
  276. return success(data={"body": result})
  277. @admin_required_router.post("/email-authorizations/sendmail-bulk", summary="群发送邮件", tags=["邮箱接口"], response_model=ApiResponse[EmailContent])
  278. def email_authorizations_send_email_bulk(
  279. emailAccount: str = Query(..., description="收件邮箱账号, 格式: xxx@xxx.xxx"),
  280. sendTo: str = Query(..., description="收件人邮箱账号,多个用逗号隔开"),
  281. subject: str = Query(..., description="邮件主题"),
  282. contentType: str = Query("text", description="内容格式,支持 text 和 html"),
  283. content: EmailContent = Body(..., description="邮件内容"),
  284. db: Session = Depends(get_db)
  285. ):
  286. auth = EmailAuthorizationService.get_by_email(db, emailAccount)
  287. result = EmailAuthorizationService.send_email_bulk(
  288. auth,
  289. send_to = sendTo,
  290. subject = subject,
  291. content_type = contentType,
  292. content = content.body
  293. )
  294. return success(data={"body": result})
  295. @public_router.post("/resource/upload_file", summary="上传文件", tags=["文件管理"], response_model=ApiResponse[FileUploadOut])
  296. def resource_upload_file(file: UploadFile = File(...)):
  297. result = SeaweedFSService.upload(file)
  298. return success(data=result)
  299. @public_router.get("/resource/download_file", summary="下载文件", tags=["文件管理"])
  300. def resource_get_file(fid: str):
  301. data = SeaweedFSService.get(fid)
  302. if not data:
  303. raise HTTPException(status_code=404, detail="文件不存在")
  304. content, mime = data
  305. return Response(content=content, media_type=mime)
  306. @admin_required_router.post("/s/generate", summary="生成短链接地址<压缩地址长度>", tags=["Short URL"], response_model=ApiResponse[ShortUrlOut])
  307. def short_url_generate(
  308. data: ShortUrlCreate,
  309. db: Session = Depends(get_db),
  310. ):
  311. """生成短链接"""
  312. record = ShortUrlService.create_short_url(db, data.long_url)
  313. return success(data=record)
  314. @public_router.get("/s/{short_key}", summary="访问短链接地址<自动重定向到真实链接地址>", tags=["Short URL"])
  315. def short_url_request(short_key: str, db: Session = Depends(get_db)):
  316. """访问短链接自动重定向"""
  317. long_url = ShortUrlService.get_long_url(db, short_key)
  318. return RedirectResponse(url=long_url, status_code=302)
  319. @admin_required_router.post("/tasks", summary="创建任务", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  320. def task_create(data: TaskCreate, db: Session = Depends(get_db)):
  321. """创建任务"""
  322. return TaskService.create(db, data)
  323. @admin_required_router.get("/tasks/{task_id:int}", summary="根据taskId读取任务状态", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  324. def task_get_by_id(task_id: int, db: Session = Depends(get_db)):
  325. """获取任务"""
  326. task = TaskService.get_by_id(db, task_id)
  327. return success(data=task)
  328. @admin_required_router.get("/tasks/pending", summary="获取等待执行的任务", tags=["任务管理接口"], response_model=ApiResponse[List[TaskOut]])
  329. def task_get_pending(
  330. page: int = Query(0, description="第几页"),
  331. size: int = Query(10, description="分页大小"),
  332. command: str = Query(..., description="任务类型"),
  333. db: Session = Depends(get_db),
  334. ):
  335. """分页获取等待执行的任务"""
  336. obj = TaskService.get_pending(db, command, page, size)
  337. return success(data=obj)
  338. @admin_required_router.put("/tasks/{task_id}", summary="根据taskId更新任务状态", tags=["任务管理接口"], response_model=ApiResponse[TaskOut])
  339. def task_update_by_id(task_id: int, data: TaskUpdate, db: Session = Depends(get_db)):
  340. """更新任务状态或结果"""
  341. updated = TaskService.update(db, task_id, data)
  342. return success(data=updated)
  343. @admin_required_router.post("/tg/send_message", summary="推送电报消息", tags=["消息推送接口"], response_model=ApiResponse)
  344. def tg_send_message(
  345. payload: TelegramIn
  346. ):
  347. TelegramService.push_to_telegram(payload)
  348. return success()
  349. @admin_required_router.post("/wechat/send", summary="推送微信消息", tags=["消息推送接口"], response_model=ApiResponse)
  350. def wechat_send(
  351. payload: WechatIn
  352. ):
  353. WechatService.push_to_wechat(payload)
  354. return success()
  355. @admin_required_router.post("/cards/publish", summary="创建新的消息卡片", tags=["信息卡片接口"], response_model=ApiResponse[CardOut])
  356. def cards_publish(
  357. data: CardCreate = Body(...),
  358. db: Session = Depends(get_db)
  359. ):
  360. obj = CardService.create(db, data)
  361. return success(data=obj)
  362. @public_router.get("/cards/view2", summary="根据关键词分页查询卡片, 可选择语言", tags=["信息卡片接口"], response_model=ApiResponse[PageResponse[CardOut]])
  363. def cards_view_paginated2(
  364. keyword: str = Query("", description="查询的关键词"),
  365. page: int = Query(0, description="第几页"),
  366. size: int = Query(10, description="分页大小"),
  367. culture: str = Query("english", description="语言, 可设置 chinese, english"),
  368. db: Session = Depends(get_db)
  369. ):
  370. obj = CardService.list_by_keyword(db, keyword, page, size, culture)
  371. return success(data=obj)
  372. @admin_required_router.get("/fake/users", summary="生成虚假的预约人信息", tags=["数据生成"], response_model=ApiResponse[List[FakeUser]])
  373. def fake_generate_fake_users(
  374. num: int = Query(1, description="生成几个数据"),
  375. living_country = Query("Ireland", description="居住在哪个国家, China, India, United Kingdom, Ireland"),
  376. ):
  377. obj = generate_fake_users(num, living_country=living_country)
  378. return success(data=obj)
  379. @public_router.get("/slots/latest", summary="查询最近的slot", tags=["Slot数据"], response_model=ApiResponse[SlotSnapshotOut])
  380. def slots_latest_get(
  381. country: str = Query("", description="目的国家"),
  382. city: str = Query("", description="递交城市"),
  383. visa_type: str = Query("", description="签证类型"),
  384. db: Session = Depends(get_db)
  385. ):
  386. res = SlotSnapshotService.latest_for(db, country, city, visa_type)
  387. return success(data=res)
  388. @admin_required_router.get("/slots/report", summary="上报最近的slot", tags=["Slot数据"], response_model=ApiResponse[SlotSnapshotOut])
  389. def slots_report(
  390. payload: SlotSnapshotCreate,
  391. db: Session = Depends(get_db)
  392. ):
  393. res = SlotSnapshotService.create(db, payload)
  394. return success(data=res)
  395. @public_router.post("/webhook/smshelper", summary="双开助手Webhook", tags=["webhook"], response_model=ApiResponse)
  396. def webhook_smshelper(
  397. payload: SMSHelperWebhookPayload,
  398. db: Session = Depends(get_db),
  399. redis_client: Redis = Depends(get_redis_client)
  400. ):
  401. logger.info(f'smshelper webhook title={payload.title}, content={payload.content}')
  402. if "微信支付" in payload.title:
  403. res = WebhookService.smshelper_payment_webhook(db, payload)
  404. if res:
  405. print(f"📧 send payment succeeded notification email")
  406. return success()
  407. @public_router.post("/webhook/stripe", summary="Stripe Webhook", tags=["webhook"], response_model=ApiResponse)
  408. def webhook_stripe(
  409. request: Request,
  410. db: Session = Depends(get_db),
  411. redis_client: Redis = Depends(get_redis_client)
  412. ):
  413. payload = request.body()
  414. sig_header = request.headers.get("stripe-signature")
  415. event = stripe.Webhook.construct_event(
  416. payload=payload,
  417. sig_header=sig_header,
  418. secret=settings.STRIPE_WEBHOOK_SECRET,
  419. )
  420. res = WebhookService.stripe_payment_webhook(db, event)
  421. if res:
  422. print(f"📧 send payment succeeded notification email")
  423. return success()
  424. @public_router.post("/auth/auto-register", summary="自动注册", tags=["用户管理"], response_model=ApiResponse[AutoRegisterData])
  425. def vas_auto_register(
  426. payload: AutoRegisterRequest,
  427. db: Session = Depends(get_db)
  428. ):
  429. res = AuthService.auto_register(db, payload)
  430. return success(data=res)
  431. @public_router.post("/auth/send-bind-code", summary="发送邮箱注册码 绑定邮箱用", tags=["用户管理"], response_model=ApiResponse)
  432. def vas_send_bind_code(
  433. payload: SendBindCodeRequest,
  434. current_user: VasUser = Depends(get_current_user),
  435. db: Session = Depends(get_db),
  436. redis_client: Redis = Depends(get_redis_client)
  437. ):
  438. AuthService.send_bind_code(db, payload, current_user, redis_client)
  439. return success(message="verify email sent, please check your inbox")
  440. @public_router.post("/auth/send-reset-code", summary="发送邮箱注册码 重置密码用", tags=["用户管理"], response_model=ApiResponse)
  441. def vas_send_reset_code(
  442. payload: SendResetCodeRequest,
  443. db: Session = Depends(get_db),
  444. redis_client: Redis = Depends(get_redis_client)
  445. ):
  446. AuthService.send_reset_code(db, payload, redis_client)
  447. return success(message="verify email sent, please check your inbox")
  448. @protected_router.post("/auth/bind-email", summary="绑定邮箱", tags=["用户管理"], response_model=ApiResponse[LoginData])
  449. def vas_bind_email(
  450. payload: BindEmailRequest,
  451. current_user: VasUser = Depends(get_current_user),
  452. db: Session = Depends(get_db),
  453. redis_client: Redis = Depends(get_redis_client)
  454. ):
  455. res = AuthService.bind_email(db, payload, current_user, redis_client)
  456. return success(data=res)
  457. @public_router.post("/auth/reset-password", summary="重置密码", tags=["用户管理"], response_model=ApiResponse)
  458. def vas_reset_password(
  459. payload: ResetPasswordRequest,
  460. db: Session = Depends(get_db)
  461. ):
  462. res = AuthService.reset_password(db, payload)
  463. return success(data=res)
  464. @public_router.post("/auth/login", summary="邮箱登录", tags=["用户管理"], response_model=ApiResponse[LoginData])
  465. def vas_login(
  466. payload: LoginRequest,
  467. db: Session = Depends(get_db)
  468. ):
  469. res = AuthService.login(db, payload)
  470. return success(data=res)
  471. @admin_required_router.get("/user/list_all", summary="获取所有用户", tags=["用户管理"], response_model=ApiResponse[PageResponse[VasUserOut]])
  472. def vas_user_list_all(
  473. page: int = Query(0, description="第几页"),
  474. size: int = Query(10, description="分页大小"),
  475. keyword: str = Query("", description="查询条件"),
  476. db: Session = Depends(get_db)
  477. ):
  478. users = UserService.list_all(db, page, size, keyword)
  479. return success(data=users)
  480. @admin_required_router.get("/user/detail", summary="获取用户信息", tags=["用户管理"], response_model=ApiResponse[VasUserOut])
  481. def vas_user_get_detail(
  482. user_id: str,
  483. db: Session = Depends(get_db)
  484. ):
  485. user = UserService.get(db, user_id)
  486. return success(data=user)
  487. @admin_required_router.post("/user/update", summary="更新用户信息", tags=["用户管理"], response_model=ApiResponse[VasUserOut])
  488. def vas_user_update(
  489. uid: str,
  490. payload: VasUserUpdate,
  491. db: Session = Depends(get_db)
  492. ):
  493. updated = UserService.update(db, uid, payload)
  494. return success(data=updated)
  495. @admin_required_router.post("/user/set_profiles", summary="更新用户信息", tags=["用户管理"], response_model=ApiResponse[VasUserOut])
  496. def vas_user_update(
  497. payload: VasUserSetProfiles,
  498. current_user: VasUser = Depends(get_current_user),
  499. db: Session = Depends(get_db)
  500. ):
  501. updated = UserService.set_profiles(db, current_user, payload)
  502. return success(data=updated)
  503. @admin_required_router.get("/vas/statistics/overview", summary="系统概览", tags=["Visafly签证系统"], response_model=ApiResponse[VasStatisticsOverviewOut])
  504. def vas_statistics_overview(
  505. db: Session = Depends(get_db)
  506. ):
  507. overview = StatisticsService.overview(db)
  508. return success(data=overview)
  509. @admin_required_router.post("/vas/product/create", summary="创建商品", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductOut])
  510. def vas_product_create(
  511. payload: VasProductCreate,
  512. db: Session = Depends(get_db)
  513. ):
  514. created_product = ProductService.create(db, payload)
  515. return success(data=created_product)
  516. @admin_required_router.post("/vas/product/update", summary="更新商品", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductOut])
  517. def vas_product_update(
  518. id: int,
  519. payload: VasProductUpdate,
  520. db: Session = Depends(get_db)
  521. ):
  522. product = ProductService.update(db, id, payload)
  523. return success(data=product)
  524. @public_router.get("/vas/product/list", summary="获取商品列表", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasProductOut]])
  525. def vas_product_list(
  526. country: str = Query("", description="目的国家"),
  527. visa_type: str = Query("", description="签证类型"),
  528. page: int = Query(0, description="第几页"),
  529. size: int = Query(10, description="分页大小"),
  530. keyword: str = Query("", description="查询条件"),
  531. db: Session = Depends(get_db)
  532. ):
  533. products = ProductService.list_product(db, country, visa_type, page, size, keyword)
  534. return success(data=products)
  535. @public_router.get("/vas/product/detail", summary="获取商品列表", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductOut])
  536. def vas_product_get_by_id(
  537. product_id: int,
  538. db: Session = Depends(get_db)
  539. ):
  540. products = ProductService.get(db, product_id)
  541. return success(data=products)
  542. @admin_required_router.post("/vas/product_routing/create", summary="创建商品路由列表", tags=["Visafly签证系统"], response_model=ApiResponse[VasProductRoutingOut])
  543. def vas_product_routing_create(
  544. payload: VasProductRoutingCreate,
  545. db: Session = Depends(get_db)
  546. ):
  547. payload = ProductRoutingService.create(db, payload)
  548. return success(data=payload)
  549. @admin_required_router.delete("/vas/product_routing/delete", summary="删除商品路由列表", tags=["Visafly签证系统"], response_model=ApiResponse)
  550. def vas_product_routing_date(
  551. id: int,
  552. db: Session = Depends(get_db)
  553. ):
  554. ProductRoutingService.delete(db, id)
  555. return success()
  556. @admin_required_router.get("/vas/product_routing/list", summary="获取商品路由列表", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasProductRoutingOut]])
  557. def vas_product_routing_list_by_product(
  558. product_id: int,
  559. db: Session = Depends(get_db)
  560. ):
  561. product_routings = ProductRoutingService.list_by_product(db, product_id)
  562. return success(data=product_routings)
  563. @public_router.get("/vas/schema/detail", summary="获取schema", tags=["Visafly签证系统"], response_model=ApiResponse[VasSchemaOut])
  564. def vas_schema_get(
  565. schema_id: int,
  566. db: Session = Depends(get_db)
  567. ):
  568. schema = SchemaService.get(db, schema_id)
  569. return success(data=schema)
  570. @admin_required_router.post("/vas/schema/create", summary="新增schema", tags=["Visafly签证系统"], response_model=ApiResponse[VasSchemaOut])
  571. def vas_schema_create(
  572. payload: VasSchemaCreate,
  573. db: Session = Depends(get_db)
  574. ):
  575. schema = SchemaService.create(db, payload)
  576. return success(data=schema)
  577. @admin_required_router.post("/vas/schema/update", summary="更新schema", tags=["Visafly签证系统"], response_model=ApiResponse[VasSchemaOut])
  578. def vas_schema_update(
  579. id: int,
  580. payload: VasSchemaUpdate,
  581. db: Session = Depends(get_db)
  582. ):
  583. schema = SchemaService.update(db, id, payload)
  584. return success(data=schema)
  585. @admin_required_router.delete("/vas/schema/delete", summary="删除schema", tags=["Visafly签证系统"], response_model=ApiResponse)
  586. def vas_schema_delete(
  587. id: int,
  588. db: Session = Depends(get_db)
  589. ):
  590. SchemaService.delete(db, id)
  591. return success()
  592. @admin_required_router.get("/vas/schema/list", summary="获取schema列表", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasSchemaOut]])
  593. def vas_schema_list(
  594. db: Session = Depends(get_db)
  595. ):
  596. schemas = SchemaService.list_all(db)
  597. return success(data=schemas)
  598. @protected_router.post("/vas/order/create", summary="创建订单", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  599. def vas_order_create(
  600. payload: VasOrderCreate,
  601. current_user: VasUser = Depends(get_current_user),
  602. db: Session = Depends(get_db),
  603. redis_client: Redis = Depends(get_redis_client)
  604. ):
  605. product = ProductService.get(db, payload.product_id)
  606. # ① 获取产品绑定的 schema
  607. schema = SchemaService.get(db, product.schema_id)
  608. # ② 校验 user_inputs
  609. validate_user_inputs(
  610. schema_json=schema.schema_json,
  611. user_inputs=payload.user_inputs,
  612. )
  613. created_order = OrderService.create(db, payload, product, current_user, redis_client)
  614. if current_user.role == "admin":
  615. OrderService.mark_as_admin_paid(db, created_order, current_user)
  616. OrderService.create_tasks_for_order(db, created_order)
  617. return success(data=created_order)
  618. @admin_required_router.post("/vas/order/patch_user_inputs", summary="更新订单的用户信息", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  619. def vas_order_patch_user_inputs(
  620. order_id: str,
  621. payload: VasOrderPatchUserInputs,
  622. db: Session = Depends(get_db),
  623. ):
  624. order = OrderService.patch_user_inputs(db, order_id, payload)
  625. return success(data=order)
  626. @protected_router.get("/vas/order/list_by_user", summary="查看所有订单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasOrderOut]])
  627. def vas_order_list_by_user(
  628. page: int = Query(0, description="第几页"),
  629. size: int = Query(10, description="分页大小"),
  630. keyword: str = Query("", description="查询条件"),
  631. current_user: VasUser = Depends(get_current_user),
  632. db: Session = Depends(get_db)
  633. ):
  634. orders = OrderService.list_by_user(db, current_user.id, page, size, keyword)
  635. return success(data=orders)
  636. @protected_router.get("/vas/order/list_all", summary="查看所有订单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasOrderOut]])
  637. def vas_order_list_all(
  638. page: int = Query(0, description="第几页"),
  639. size: int = Query(10, description="分页大小"),
  640. keyword: str = Query("", description="查询条件"),
  641. db: Session = Depends(get_db)
  642. ):
  643. orders = OrderService.list_all(db, page, size, keyword)
  644. return success(data=orders)
  645. @admin_required_router.post("/vas/order/cancel", summary="取消订单", tags=["Visafly签证系统"], response_model=ApiResponse[VasOrderOut])
  646. def vas_order_cancel(
  647. order_id: str,
  648. current_user: VasUser = Depends(get_current_user),
  649. db: Session = Depends(get_db),
  650. redis_client: Redis = Depends(get_redis_client)
  651. ):
  652. pass
  653. @protected_router.get("/vas/payment_provider/list_enabled", summary="获取支付方式", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentProviderOut]])
  654. def vas_payment_provider_simple_get(
  655. db: Session = Depends(get_db)
  656. ):
  657. providers = PaymentProviderSerivce.list_enabled(db)
  658. return success(data=providers)
  659. @admin_required_router.get("/vas/payment_provider/list_all", summary="获取支付方式", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentProviderOut]])
  660. def vas_payment_provider_list_all(
  661. db: Session = Depends(get_db)
  662. ):
  663. providers = PaymentProviderSerivce.list_all(db)
  664. return success(data=providers)
  665. @admin_required_router.post("/vas/payment_provider/create", summary="新增支付服务提供商", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentProviderOut])
  666. def vas_payment_provider_create(
  667. payload: VasPaymentProviderCreate,
  668. db: Session = Depends(get_db)
  669. ):
  670. provider = PaymentProviderSerivce.create(db, payload)
  671. return success(data=provider)
  672. @admin_required_router.post("/vas/payment_provider/update", summary="更新支付服务提供商", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentProviderOut])
  673. def vas_payment_provider_update(
  674. id: int,
  675. payload: VasPaymentProviderUpdate,
  676. db: Session = Depends(get_db)
  677. ):
  678. provider = PaymentProviderSerivce.update(db, id, payload)
  679. return success(data=provider)
  680. @admin_required_router.delete("/vas/payment_provider/delete", summary="删除支付服务提供商", tags=["Visafly签证系统"], response_model=ApiResponse)
  681. def vas_payment_provider_delete(
  682. id: int,
  683. db: Session = Depends(get_db)
  684. ):
  685. PaymentProviderSerivce.delete(db, id)
  686. return success()
  687. @protected_router.post("/vas/payment/create", summary="创建支付", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentOut])
  688. def vas_payment_create(
  689. payload: VasPaymentCreate,
  690. db: Session = Depends(get_db)
  691. ):
  692. rate_table = {
  693. "EUR->EUR": "1",
  694. "EUR->CNY": "8.3174",
  695. "EUR->USD": "1.0842",
  696. }
  697. res = PaymentService.create_payment(db, payload, rate_table)
  698. return success(data=res)
  699. @protected_router.get("/vas/payment/list_by_order", summary="获取某个订单下的所有payment记录", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentOut]])
  700. def vas_payment_list_by_order(
  701. order_id: str,
  702. db: Session = Depends(get_db)
  703. ):
  704. payments = PaymentService.list_by_order(db, order_id)
  705. return success(data=payments)
  706. @admin_required_router.post("/vas/payment_qr/create", summary="新增收款码", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentQrOut])
  707. def vas_payment_qr_create(payload: VasPaymentQrCreate, db: Session = Depends(get_db)):
  708. qr = PaymentQrService.create(db, payload)
  709. return success(data=qr)
  710. @protected_router.get("/vas/payment_qr/list_by_provider", summary="获取某个服务商的所有付款码", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasPaymentQrOut]])
  711. def vas_payment_qr_list_qrcode_by_provider(provider_id: int, db: Session = Depends(get_db)):
  712. qr = PaymentQrService.list_by_provider(db, provider_id)
  713. return success(data=qr)
  714. @protected_router.get("/vas/payment_qr/qrcode", summary="获取支付的QRCode", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentQrOut])
  715. def vas_payment_qr_get_qrcode_by_id(id: int, db: Session = Depends(get_db)):
  716. qr = PaymentQrService.get_by_id(db, id)
  717. return success(data=qr)
  718. @admin_required_router.post("/vas/payment_qr/set_enable", summary="修改QRCode", tags=["Visafly签证系统"], response_model=ApiResponse[VasPaymentQrOut])
  719. def vas_payment_qr_update(
  720. id: int,
  721. payload: VasPaymentQrSetEnableIn,
  722. db: Session = Depends(get_db)
  723. ):
  724. qr = PaymentQrService.set_enable(db, id, payload)
  725. return success(data=qr)
  726. @admin_required_router.delete("/vas/payment_qr/delete", summary="删除QRCode", tags=["Visafly签证系统"], response_model=ApiResponse)
  727. def vas_payment_qr_update(
  728. id: int,
  729. db: Session = Depends(get_db)
  730. ):
  731. PaymentQrService.delete(db, id)
  732. return success()
  733. @admin_required_router.get("/vas/task/list", summary="获取待执行的任务", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTaskOut]])
  734. def vas_task_list(
  735. page: int = Query(0, description="第几页"),
  736. size: int = Query(10, description="分页大小"),
  737. keyword: str = Query("", description="查询条件"),
  738. status: str = Query("", description="task 自定义索引"),
  739. routing_key: str = Query("", description="task 自定义索引"),
  740. script_version: str = Query("", description="脚本版本, 用来向后兼容"),
  741. db: Session = Depends(get_db)
  742. ):
  743. tasks = VasTaskService.list_task(db, status, routing_key, script_version, keyword, page, size)
  744. return success(data=tasks)
  745. @admin_required_router.post("/vas/task/update", summary="更新任务数据", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  746. def vas_task_update(
  747. id: int,
  748. payload: VasTaskUpdate,
  749. db: Session = Depends(get_db)
  750. ):
  751. task = VasTaskService.update(db, id, payload)
  752. return success(data=task)
  753. @admin_required_router.get("/vas/task/get_by_order", summary="根据订单查找任务", tags=["Visafly签证系统"], response_model=ApiResponse[List[VasTaskOut]])
  754. def vas_task_pending(
  755. order_id: str = Query(..., description="订单编号"),
  756. script_version: str = Query("", description="脚本版本, 用来向后兼容"),
  757. db: Session = Depends(get_db)
  758. ):
  759. tasks = VasTaskService.get_active_task_by_order_id(db, order_id)
  760. return success(data=tasks)
  761. @admin_required_router.post("/vas/task/return_to_queue", summary="重新放回队列", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  762. def vas_task_return_to_queue(task_id:int, db: Session = Depends(get_db)):
  763. obj = VasTaskService.return_to_queue(db, task_id)
  764. return success(data=obj)
  765. @admin_required_router.post("/vas/task/manual_confirm", summary="设置任务完成", tags=["Visafly签证系统"], response_model=ApiResponse[VasTaskOut])
  766. def vas_task_manual_confirm(task_id:int, db: Session = Depends(get_db)):
  767. obj = VasTaskService.manual_confirm(db, task_id)
  768. return success(data=obj)
  769. @protected_router.post("/vas/ticket/create", summary="创建工单", tags=["Visafly签证系统"], response_model=ApiResponse[VasTicketOut])
  770. def vas_ticket_create(
  771. data:VasTicketCreate,
  772. current_user: VasUser = Depends(get_current_user),
  773. db: Session = Depends(get_db),
  774. redis_client: Redis = Depends(get_redis_client)
  775. ):
  776. obj = TicketService.create(db, data, current_user, redis_client)
  777. return success(data=obj)
  778. @protected_router.get("/vas/ticket/list_by_user", summary="查看工单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTicketOut]])
  779. def vas_ticket_list_by_user(
  780. page: int = Query(0, description="第几页"),
  781. size: int = Query(10, description="分页大小"),
  782. keyword: str = Query("", description="查询条件"),
  783. current_user: VasUser = Depends(get_current_user),
  784. db: Session = Depends(get_db)
  785. ):
  786. tickets = TicketService.list_by_user(db, current_user.id, page, size, keyword)
  787. return success(data=tickets)
  788. @admin_required_router.get("/vas/ticket/list_all", summary="查看工单", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTicketOut]])
  789. def vas_ticket_list_all(
  790. page: int = Query(0, description="第几页"),
  791. size: int = Query(10, description="分页大小"),
  792. keyword: str = Query("", description="查询条件"),
  793. db: Session = Depends(get_db)
  794. ):
  795. tickets = TicketService.list_all(db, page, size, keyword)
  796. return success(data=tickets)
  797. @admin_required_router.post("/vas/tickets/status", summary="管理员更新工单状态", tags=["Visafly签证系统"], response_model=ApiResponse[VasTicketOut])
  798. def update_ticket_status(
  799. ticket_id: int,
  800. payload: VasTicketStatusUpdate,
  801. db: Session = Depends(get_db),
  802. user=Depends(get_current_user)
  803. ):
  804. res = TicketService.update_status(
  805. db=db,
  806. ticket_id=ticket_id,
  807. status=payload.status,
  808. comment=payload.comment,
  809. admin_id=user.id
  810. )
  811. return success(data=res)
  812. @protected_router.post("/vas/tickets/send_message", summary="发送工单消息", tags=["Visafly签证系统"], response_model=ApiResponse[VasTicketMessageOut])
  813. def create_ticket_message(
  814. ticket_id: int,
  815. payload: VasTicketMessageCreate,
  816. db: Session = Depends(get_db),
  817. user=Depends(get_current_user)
  818. ):
  819. res = TicketService.add_message(
  820. db=db,
  821. ticket_id=ticket_id,
  822. sender_type=user.role,
  823. sender_id=user.id,
  824. content=payload.content,
  825. attachments=payload.attachments
  826. )
  827. return success(data=res)
  828. @protected_router.get("/vas/tickets/fetch_message", summary="分页获取工单会话", tags=["Visafly签证系统"], response_model=ApiResponse[PageResponse[VasTicketMessageOut]])
  829. def get_ticket_messages(
  830. ticket_id: int,
  831. page: int = 1,
  832. size: int = 20,
  833. db: Session = Depends(get_db)
  834. ):
  835. msgs = TicketService.list_messages(
  836. db=db,
  837. ticket_id=ticket_id,
  838. page=page,
  839. size=size
  840. )
  841. return success(data=msgs)