alias.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. from email.utils import parseaddr
  2. from flask import g
  3. from flask import jsonify
  4. from flask import request
  5. from flask_cors import cross_origin
  6. from app.api.base import api_bp, verify_api_key
  7. from app.config import EMAIL_DOMAIN
  8. from app.config import PAGE_LIMIT
  9. from app.dashboard.views.alias_log import get_alias_log
  10. from app.dashboard.views.index import get_alias_info, AliasInfo
  11. from app.extensions import db
  12. from app.log import LOG
  13. from app.models import EmailLog
  14. from app.models import Alias, Contact
  15. from app.utils import random_string
  16. @api_bp.route("/aliases")
  17. @cross_origin()
  18. @verify_api_key
  19. def get_aliases():
  20. """
  21. Get aliases
  22. Input:
  23. page_id: in query
  24. Output:
  25. - aliases: list of alias:
  26. - id
  27. - email
  28. - creation_date
  29. - creation_timestamp
  30. - nb_forward
  31. - nb_block
  32. - nb_reply
  33. - note
  34. """
  35. user = g.user
  36. try:
  37. page_id = int(request.args.get("page_id"))
  38. except (ValueError, TypeError):
  39. return jsonify(error="page_id must be provided in request query"), 400
  40. alias_infos: [AliasInfo] = get_alias_info(user, page_id=page_id)
  41. return (
  42. jsonify(
  43. aliases=[
  44. {
  45. "id": alias_info.id,
  46. "email": alias_info.alias.email,
  47. "creation_date": alias_info.alias.created_at.format(),
  48. "creation_timestamp": alias_info.alias.created_at.timestamp,
  49. "nb_forward": alias_info.nb_forward,
  50. "nb_block": alias_info.nb_blocked,
  51. "nb_reply": alias_info.nb_reply,
  52. "enabled": alias_info.alias.enabled,
  53. "note": alias_info.note,
  54. }
  55. for alias_info in alias_infos
  56. ]
  57. ),
  58. 200,
  59. )
  60. @api_bp.route("/aliases/<int:alias_id>", methods=["DELETE"])
  61. @cross_origin()
  62. @verify_api_key
  63. def delete_alias(alias_id):
  64. """
  65. Delete alias
  66. Input:
  67. alias_id: in url
  68. Output:
  69. 200 if deleted successfully
  70. """
  71. user = g.user
  72. alias = Alias.get(alias_id)
  73. if alias.user_id != user.id:
  74. return jsonify(error="Forbidden"), 403
  75. Alias.delete(alias_id)
  76. db.session.commit()
  77. return jsonify(deleted=True), 200
  78. @api_bp.route("/aliases/<int:alias_id>/toggle", methods=["POST"])
  79. @cross_origin()
  80. @verify_api_key
  81. def toggle_alias(alias_id):
  82. """
  83. Enable/disable alias
  84. Input:
  85. alias_id: in url
  86. Output:
  87. 200 along with new status:
  88. - enabled
  89. """
  90. user = g.user
  91. alias: Alias = Alias.get(alias_id)
  92. if alias.user_id != user.id:
  93. return jsonify(error="Forbidden"), 403
  94. alias.enabled = not alias.enabled
  95. db.session.commit()
  96. return jsonify(enabled=alias.enabled), 200
  97. @api_bp.route("/aliases/<int:alias_id>/activities")
  98. @cross_origin()
  99. @verify_api_key
  100. def get_alias_activities(alias_id):
  101. """
  102. Get aliases
  103. Input:
  104. page_id: in query
  105. Output:
  106. - activities: list of activity:
  107. - from
  108. - to
  109. - timestamp
  110. - action: forward|reply|block
  111. """
  112. user = g.user
  113. try:
  114. page_id = int(request.args.get("page_id"))
  115. except (ValueError, TypeError):
  116. return jsonify(error="page_id must be provided in request query"), 400
  117. alias: Alias = Alias.get(alias_id)
  118. if alias.user_id != user.id:
  119. return jsonify(error="Forbidden"), 403
  120. alias_logs = get_alias_log(alias, page_id)
  121. activities = []
  122. for alias_log in alias_logs:
  123. activity = {"timestamp": alias_log.when.timestamp}
  124. if alias_log.is_reply:
  125. activity["from"] = alias_log.alias
  126. activity["to"] = alias_log.website_from or alias_log.website_email
  127. activity["action"] = "reply"
  128. else:
  129. activity["to"] = alias_log.alias
  130. activity["from"] = alias_log.website_from or alias_log.website_email
  131. if alias_log.bounced:
  132. activity["action"] = "bounced"
  133. elif alias_log.blocked:
  134. activity["action"] = "block"
  135. else:
  136. activity["action"] = "forward"
  137. activities.append(activity)
  138. return jsonify(activities=activities), 200
  139. @api_bp.route("/aliases/<int:alias_id>", methods=["PUT"])
  140. @cross_origin()
  141. @verify_api_key
  142. def update_alias(alias_id):
  143. """
  144. Update alias note
  145. Input:
  146. alias_id: in url
  147. note: in body
  148. Output:
  149. 200
  150. """
  151. data = request.get_json()
  152. if not data:
  153. return jsonify(error="request body cannot be empty"), 400
  154. user = g.user
  155. alias: Alias = Alias.get(alias_id)
  156. if alias.user_id != user.id:
  157. return jsonify(error="Forbidden"), 403
  158. new_note = data.get("note")
  159. alias.note = new_note
  160. db.session.commit()
  161. return jsonify(note=new_note), 200
  162. def serialize_contact(fe: Contact) -> dict:
  163. res = {
  164. "id": fe.id,
  165. "creation_date": fe.created_at.format(),
  166. "creation_timestamp": fe.created_at.timestamp,
  167. "last_email_sent_date": None,
  168. "last_email_sent_timestamp": None,
  169. "contact": fe.website_from or fe.website_email,
  170. "reverse_alias": fe.website_send_to(),
  171. }
  172. fel: EmailLog = fe.last_reply()
  173. if fel:
  174. res["last_email_sent_date"] = fel.created_at.format()
  175. res["last_email_sent_timestamp"] = fel.created_at.timestamp
  176. return res
  177. def get_alias_contacts(alias, page_id: int) -> [dict]:
  178. q = (
  179. Contact.query.filter_by(alias_id=alias.id)
  180. .order_by(Contact.id.desc())
  181. .limit(PAGE_LIMIT)
  182. .offset(page_id * PAGE_LIMIT)
  183. )
  184. res = []
  185. for fe in q.all():
  186. res.append(serialize_contact(fe))
  187. return res
  188. @api_bp.route("/aliases/<int:alias_id>/contacts")
  189. @cross_origin()
  190. @verify_api_key
  191. def get_alias_contacts_route(alias_id):
  192. """
  193. Get alias contacts
  194. Input:
  195. page_id: in query
  196. Output:
  197. - contacts: list of contacts:
  198. - creation_date
  199. - creation_timestamp
  200. - last_email_sent_date
  201. - last_email_sent_timestamp
  202. - contact
  203. - reverse_alias
  204. """
  205. user = g.user
  206. try:
  207. page_id = int(request.args.get("page_id"))
  208. except (ValueError, TypeError):
  209. return jsonify(error="page_id must be provided in request query"), 400
  210. alias: Alias = Alias.get(alias_id)
  211. if alias.user_id != user.id:
  212. return jsonify(error="Forbidden"), 403
  213. contacts = get_alias_contacts(alias, page_id)
  214. return jsonify(contacts=contacts), 200
  215. @api_bp.route("/aliases/<int:alias_id>/contacts", methods=["POST"])
  216. @cross_origin()
  217. @verify_api_key
  218. def create_contact_route(alias_id):
  219. """
  220. Create contact for an alias
  221. Input:
  222. alias_id: in url
  223. contact: in body
  224. Output:
  225. 201 if success
  226. 409 if contact already added
  227. """
  228. data = request.get_json()
  229. if not data:
  230. return jsonify(error="request body cannot be empty"), 400
  231. user = g.user
  232. alias: Alias = Alias.get(alias_id)
  233. if alias.user_id != user.id:
  234. return jsonify(error="Forbidden"), 403
  235. contact_email = data.get("contact")
  236. # generate a reply_email, make sure it is unique
  237. # not use while to avoid infinite loop
  238. reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
  239. for _ in range(1000):
  240. reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
  241. if not Contact.get_by(reply_email=reply_email):
  242. break
  243. _, website_email = parseaddr(contact_email)
  244. # already been added
  245. if Contact.get_by(alias_id=alias.id, website_email=website_email):
  246. return jsonify(error="Contact already added"), 409
  247. contact = Contact.create(
  248. alias_id=alias.id,
  249. website_email=website_email,
  250. website_from=contact_email,
  251. reply_email=reply_email,
  252. )
  253. LOG.d("create reverse-alias for %s %s", contact_email, alias)
  254. db.session.commit()
  255. return jsonify(**serialize_contact(contact)), 201