setting.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. from io import BytesIO
  2. import arrow
  3. import stripe
  4. from flask import render_template, request, redirect, url_for, flash
  5. from flask_login import login_required, current_user
  6. from flask_wtf import FlaskForm
  7. from flask_wtf.file import FileField
  8. from wtforms import StringField, validators
  9. from app import s3, email_utils
  10. from app.config import URL, PROMO_CODE
  11. from app.dashboard.base import dashboard_bp
  12. from app.email_utils import notify_admin
  13. from app.extensions import db
  14. from app.log import LOG
  15. from app.models import PlanEnum, File, ResetPasswordCode
  16. from app.utils import random_string
  17. class SettingForm(FlaskForm):
  18. name = StringField("Name", validators=[validators.DataRequired()])
  19. profile_picture = FileField("Profile Picture")
  20. class PromoCodeForm(FlaskForm):
  21. code = StringField("Name", validators=[validators.DataRequired()])
  22. @dashboard_bp.route("/setting", methods=["GET", "POST"])
  23. @login_required
  24. def setting():
  25. form = SettingForm()
  26. promo_form = PromoCodeForm()
  27. if request.method == "POST":
  28. if request.form.get("form-name") == "update-profile":
  29. if form.validate():
  30. # update user info
  31. current_user.name = form.name.data
  32. if form.profile_picture.data:
  33. file_path = random_string(30)
  34. file = File.create(path=file_path)
  35. s3.upload_from_bytesio(
  36. file_path, BytesIO(form.profile_picture.data.read())
  37. )
  38. db.session.flush()
  39. LOG.d("upload file %s to s3", file)
  40. current_user.profile_picture_id = file.id
  41. db.session.flush()
  42. db.session.commit()
  43. flash(f"Your profile has been updated", "success")
  44. elif request.form.get("form-name") == "cancel-subscription":
  45. # sanity check
  46. if not (current_user.is_premium() and current_user.plan_expiration is None):
  47. raise Exception("user cannot cancel subscription")
  48. notify_admin(f"user {current_user} cancels subscription")
  49. # the plan will finish at the end of the current period
  50. current_user.plan_expiration = current_user.plan_current_period_end()
  51. stripe.Subscription.modify(
  52. current_user.stripe_subscription_id, cancel_at_period_end=True
  53. )
  54. db.session.commit()
  55. flash(
  56. f"Your plan will be downgraded {current_user.plan_expiration.humanize()}",
  57. "success",
  58. )
  59. elif request.form.get("form-name") == "reactivate-subscription":
  60. if not (current_user.is_premium() and current_user.plan_expiration):
  61. raise Exception("user cannot reactivate subscription")
  62. notify_admin(f"user {current_user} reactivates subscription")
  63. # the plan will finish at the end of the current period
  64. current_user.plan_expiration = None
  65. stripe.Subscription.modify(
  66. current_user.stripe_subscription_id, cancel_at_period_end=False
  67. )
  68. db.session.commit()
  69. flash(f"Your plan is reactivated now, thank you!", "success")
  70. elif request.form.get("form-name") == "change-password":
  71. send_reset_password_email(current_user)
  72. elif request.form.get("form-name") == "promo-code":
  73. if promo_form.validate():
  74. promo_code = promo_form.code.data.upper()
  75. if promo_code != PROMO_CODE:
  76. flash(
  77. "Unknown promo code. Are you sure this is the right code?",
  78. "warning",
  79. )
  80. return render_template(
  81. "dashboard/setting.html",
  82. form=form,
  83. PlanEnum=PlanEnum,
  84. promo_form=promo_form,
  85. )
  86. elif promo_code in current_user.get_promo_codes():
  87. flash(
  88. "You have already used this promo code. A code can be used only once :(",
  89. "warning",
  90. )
  91. return render_template(
  92. "dashboard/setting.html",
  93. form=form,
  94. PlanEnum=PlanEnum,
  95. promo_form=promo_form,
  96. )
  97. else:
  98. LOG.d("apply promo code %s for user %s", promo_code, current_user)
  99. current_user.plan = PlanEnum.trial
  100. if current_user.plan_expiration:
  101. LOG.d("extend the current plan 1 year")
  102. current_user.plan_expiration = current_user.plan_expiration.shift(
  103. years=1
  104. )
  105. else:
  106. LOG.d("set plan_expiration to 1 year from now")
  107. current_user.plan_expiration = arrow.now().shift(years=1)
  108. current_user.save_new_promo_code(promo_code)
  109. db.session.commit()
  110. flash(
  111. "The promo code has been applied successfully to your account!",
  112. "success",
  113. )
  114. return redirect(url_for("dashboard.setting"))
  115. return render_template(
  116. "dashboard/setting.html", form=form, PlanEnum=PlanEnum, promo_form=promo_form
  117. )
  118. def send_reset_password_email(user):
  119. """
  120. generate a new ResetPasswordCode and send it over email to user
  121. """
  122. reset_password_code = ResetPasswordCode.create(
  123. user_id=user.id, code=random_string(60)
  124. )
  125. db.session.commit()
  126. reset_password_link = f"{URL}/auth/reset_password?code={reset_password_code.code}"
  127. email_utils.send(
  128. user.email,
  129. f"Reset your password on SimpleLogin",
  130. html_content=f"""
  131. Hi {user.name}! <br><br>
  132. To reset or change your password, please follow this link <a href="{reset_password_link}">reset password</a>.
  133. Or you can paste this link into your browser: <br><br>
  134. {reset_password_link} <br><br>
  135. Cheers,
  136. SimpleLogin team.
  137. """,
  138. )
  139. flash(
  140. "You are going to receive an email containing instruction to change your password",
  141. "success",
  142. )