From 86e5a44b89c1c9bf6766d4ee7df211733c19203a Mon Sep 17 00:00:00 2001 From: Son NK Date: Tue, 31 Dec 2019 10:34:37 +0100 Subject: [PATCH 1/6] Upgrade sqlalchemy-utils to fix "SAWarning: The GenericFunction 'array_agg' is already registered and is going to be overriden. "is going to be overriden.".format(identifier)" error --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d1785ff9..cb37404e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -84,7 +84,7 @@ ruamel.yaml==0.15.97 # via strictyaml s3transfer==0.2.1 # via boto3 sentry-sdk==0.13.5 six==1.12.0 # via bcrypt, cryptography, flask-cors, packaging, pip-tools, prompt-toolkit, pyopenssl, pytest, python-dateutil, sqlalchemy-utils, traitlets -sqlalchemy-utils==0.33.11 +sqlalchemy-utils==0.36.1 sqlalchemy==1.3.12 # via alembic, flask-sqlalchemy, sqlalchemy-utils strictyaml==1.0.2 # via yacron traitlets==4.3.2 # via ipython From 026fe4adddbbe4445a2a94d4ebfe66e107ceb8b2 Mon Sep 17 00:00:00 2001 From: Son NK Date: Wed, 1 Jan 2020 19:46:35 +0100 Subject: [PATCH 2/6] get_subscription should only return *active* subscription. --- app/dashboard/views/billing.py | 8 ++++---- app/models.py | 18 ++++++++++++------ templates/header.html | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/dashboard/views/billing.py b/app/dashboard/views/billing.py index 65ce62cd..0648be24 100644 --- a/app/dashboard/views/billing.py +++ b/app/dashboard/views/billing.py @@ -8,10 +8,10 @@ from app.dashboard.base import dashboard_bp @login_required def billing(): # sanity check: make sure this page is only for user who has paddle subscription - if not current_user.is_premium(): - flash("This page is for paid customer only", "warning") - return redirect(url_for("dashboard.index")) - sub = current_user.get_subscription() + if not sub: + flash("You don't have any active subscription", "warning") + return redirect(url_for("dashboard.index")) + return render_template("dashboard/billing.html", sub=sub) diff --git a/app/models.py b/app/models.py index 9ff2f931..ca864a48 100644 --- a/app/models.py +++ b/app/models.py @@ -145,11 +145,6 @@ class User(db.Model, ModelMixin, UserMixin): """user is premium if they have a active subscription""" sub: Subscription = self.get_subscription() if sub: - if sub.cancelled: - # user is premium until the next billing_date + 1 - return sub.next_bill_date >= arrow.now().shift(days=-1).date() - - # subscription active, ie not cancelled return True return False @@ -217,8 +212,19 @@ class User(db.Model, ModelMixin, UserMixin): return "Free Plan" def get_subscription(self): + """return *active* subscription + TODO: support user unsubscribe and re-subscribe + """ sub = Subscription.get_by(user_id=self.id) - return sub + if sub and sub.cancelled: + # sub is active until the next billing_date + 1 + if sub.next_bill_date >= arrow.now().shift(days=-1).date(): + return sub + else: # past subscription, user is considered not having a subscription + return None + else: + return sub + def verified_custom_domains(self): return CustomDomain.query.filter_by(user_id=self.id, verified=True).all() diff --git a/templates/header.html b/templates/header.html index a7c0fd7d..48731ff8 100644 --- a/templates/header.html +++ b/templates/header.html @@ -42,7 +42,7 @@ diff --git a/app/dashboard/views/pricing.py b/app/dashboard/views/pricing.py index d29891bc..aa74ea34 100644 --- a/app/dashboard/views/pricing.py +++ b/app/dashboard/views/pricing.py @@ -1,13 +1,23 @@ from flask import render_template, flash, redirect, url_for from flask_login import login_required, current_user +from flask_wtf import FlaskForm +from wtforms import StringField, validators from app.config import ( PADDLE_VENDOR_ID, PADDLE_MONTHLY_PRODUCT_ID, PADDLE_YEARLY_PRODUCT_ID, URL, + ADMIN_EMAIL, ) from app.dashboard.base import dashboard_bp +from app.email_utils import send_email +from app.extensions import db +from app.models import LifetimeCoupon + + +class CouponForm(FlaskForm): + code = StringField("Coupon Code", validators=[validators.DataRequired()]) @dashboard_bp.route("/pricing", methods=["GET", "POST"]) @@ -18,12 +28,39 @@ def pricing(): flash("You are already a premium user", "warning") return redirect(url_for("dashboard.index")) + coupon_form = CouponForm() + + if coupon_form.validate_on_submit(): + code = coupon_form.code.data + + coupon = LifetimeCoupon.get_by(code=code) + + if coupon and coupon.nb_used > 0: + coupon.nb_used -= 1 + current_user.lifetime = True + db.session.commit() + + # notify admin + send_email( + ADMIN_EMAIL, + subject=f"User {current_user.id} used lifetime coupon. Coupon nb_used: {coupon.nb_used}", + plaintext="", + html="", + ) + + flash("You are upgraded to lifetime premium!", "success") + return redirect(url_for("dashboard.index")) + + else: + flash(f"Coupon *{code}* expired or invalid", "warning") + return render_template( "dashboard/pricing.html", PADDLE_VENDOR_ID=PADDLE_VENDOR_ID, PADDLE_MONTHLY_PRODUCT_ID=PADDLE_MONTHLY_PRODUCT_ID, PADDLE_YEARLY_PRODUCT_ID=PADDLE_YEARLY_PRODUCT_ID, success_url=URL + "/dashboard/subscription_success", + coupon_form=coupon_form, ) From 4f980ffd9414861cef7699d4cb99b8e6035c1f89 Mon Sep 17 00:00:00 2001 From: Son NK Date: Wed, 1 Jan 2020 20:05:10 +0100 Subject: [PATCH 6/6] add lifetime coupon to fake_data --- server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index f70d4d0d..2afae7e9 100644 --- a/server.py +++ b/server.py @@ -6,7 +6,6 @@ import sentry_sdk from flask import Flask, redirect, url_for, render_template, request, jsonify from flask_admin import Admin from flask_cors import cross_origin -from flask_debugtoolbar import DebugToolbarExtension from flask_login import current_user from sentry_sdk.integrations.flask import FlaskIntegration @@ -39,6 +38,7 @@ from app.models import ( PlanEnum, ApiKey, CustomDomain, + LifetimeCoupon, ) from app.monitor.base import monitor_bp from app.oauth.base import oauth_bp @@ -101,6 +101,9 @@ def fake_data(): ) db.session.commit() + LifetimeCoupon.create(code="coupon", nb_used=10) + db.session.commit() + # Create a subscription for user Subscription.create( user_id=user.id,