commit
321b81d794
|
@ -145,20 +145,27 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if current_user.get_subscription() %}
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-title">Billing
|
<div class="card-title mb-3">Current Plan</div>
|
||||||
<div class="small-text mt-1 mb-3">
|
|
||||||
Manage your current subscription.
|
{% if current_user.get_subscription() %}
|
||||||
</div>
|
You are on the {{ current_user.get_subscription().plan_name() }} plan. <br>
|
||||||
</div>
|
|
||||||
<a href="{{ url_for('dashboard.billing') }}" class="btn btn-outline-primary">
|
<a href="{{ url_for('dashboard.billing') }}" class="btn btn-outline-primary">
|
||||||
Manage Billing
|
Manage Subscription
|
||||||
</a>
|
</a>
|
||||||
</div>
|
{% elif manual_sub %}
|
||||||
</div>
|
You are on the Premium plan. The plan ends {{ manual_sub.end_at | dt }}.
|
||||||
|
{% elif current_user.lifetime %}
|
||||||
|
You have the lifetime licence.
|
||||||
|
{% elif current_user.in_trial() %}
|
||||||
|
You are in the trial period. The trial ends {{ current_user.trial_end | dt }}.
|
||||||
|
{% else %}
|
||||||
|
You are on the Free plan.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
|
@ -26,6 +26,7 @@ from app.models import (
|
||||||
CustomDomain,
|
CustomDomain,
|
||||||
Client,
|
Client,
|
||||||
AliasGeneratorEnum,
|
AliasGeneratorEnum,
|
||||||
|
ManualSubscription,
|
||||||
)
|
)
|
||||||
from app.utils import random_string
|
from app.utils import random_string
|
||||||
|
|
||||||
|
@ -183,6 +184,7 @@ def setting():
|
||||||
headers={"Content-Disposition": "attachment;filename=data.json"},
|
headers={"Content-Disposition": "attachment;filename=data.json"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
manual_sub = ManualSubscription.get_by(user_id=current_user.id)
|
||||||
return render_template(
|
return render_template(
|
||||||
"dashboard/setting.html",
|
"dashboard/setting.html",
|
||||||
form=form,
|
form=form,
|
||||||
|
@ -191,6 +193,7 @@ def setting():
|
||||||
change_email_form=change_email_form,
|
change_email_form=change_email_form,
|
||||||
pending_email=pending_email,
|
pending_email=pending_email,
|
||||||
AliasGeneratorEnum=AliasGeneratorEnum,
|
AliasGeneratorEnum=AliasGeneratorEnum,
|
||||||
|
manual_sub=manual_sub,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -194,6 +194,10 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||||
if sub:
|
if sub:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
manual_sub: ManualSubscription = ManualSubscription.get_by(user_id=self.id)
|
||||||
|
if manual_sub and manual_sub.end_at > arrow.now():
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def in_trial(self):
|
def in_trial(self):
|
||||||
|
@ -736,6 +740,24 @@ class Subscription(db.Model, ModelMixin):
|
||||||
return "Yearly ($29.99/year)"
|
return "Yearly ($29.99/year)"
|
||||||
|
|
||||||
|
|
||||||
|
class ManualSubscription(db.Model, ModelMixin):
|
||||||
|
"""
|
||||||
|
For users who use other forms of payment and therefore not pass by Paddle
|
||||||
|
"""
|
||||||
|
|
||||||
|
user_id = db.Column(
|
||||||
|
db.ForeignKey(User.id, ondelete="cascade"), nullable=False, unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# an reminder is sent several days before the subscription ends
|
||||||
|
end_at = db.Column(ArrowType, nullable=False)
|
||||||
|
|
||||||
|
# for storing note about this subscription
|
||||||
|
comment = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
|
user = db.relationship(User)
|
||||||
|
|
||||||
|
|
||||||
class DeletedAlias(db.Model, ModelMixin):
|
class DeletedAlias(db.Model, ModelMixin):
|
||||||
"""Store all deleted alias to make sure they are NOT reused"""
|
"""Store all deleted alias to make sure they are NOT reused"""
|
||||||
|
|
||||||
|
|
37
cron.py
37
cron.py
|
@ -3,7 +3,7 @@ import argparse
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
from app.config import IGNORED_EMAILS, ADMIN_EMAIL
|
from app.config import IGNORED_EMAILS, ADMIN_EMAIL
|
||||||
from app.email_utils import send_email, send_trial_end_soon_email
|
from app.email_utils import send_email, send_trial_end_soon_email, render
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.log import LOG
|
from app.log import LOG
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
@ -14,6 +14,7 @@ from app.models import (
|
||||||
ForwardEmail,
|
ForwardEmail,
|
||||||
CustomDomain,
|
CustomDomain,
|
||||||
Client,
|
Client,
|
||||||
|
ManualSubscription,
|
||||||
)
|
)
|
||||||
from server import create_app
|
from server import create_app
|
||||||
|
|
||||||
|
@ -29,6 +30,35 @@ def notify_trial_end():
|
||||||
send_trial_end_soon_email(user)
|
send_trial_end_soon_email(user)
|
||||||
|
|
||||||
|
|
||||||
|
def notify_manual_sub_end():
|
||||||
|
for manual_sub in ManualSubscription.query.all():
|
||||||
|
need_reminder = False
|
||||||
|
if arrow.now().shift(days=14) > manual_sub.end_at > arrow.now().shift(days=13):
|
||||||
|
need_reminder = True
|
||||||
|
elif arrow.now().shift(days=4) > manual_sub.end_at > arrow.now().shift(days=3):
|
||||||
|
need_reminder = True
|
||||||
|
|
||||||
|
if need_reminder:
|
||||||
|
user = manual_sub.user
|
||||||
|
LOG.debug("Remind user %s that their manual sub is ending soon", user)
|
||||||
|
send_email(
|
||||||
|
user.email,
|
||||||
|
f"Your trial will end soon {user.name}",
|
||||||
|
render(
|
||||||
|
"transactional/manual-subscription-end.txt",
|
||||||
|
name=user.name,
|
||||||
|
user=user,
|
||||||
|
manual_sub=manual_sub,
|
||||||
|
),
|
||||||
|
render(
|
||||||
|
"transactional/manual-subscription-end.html",
|
||||||
|
name=user.name,
|
||||||
|
user=user,
|
||||||
|
manual_sub=manual_sub,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def stats():
|
def stats():
|
||||||
"""send admin stats everyday"""
|
"""send admin stats everyday"""
|
||||||
if not ADMIN_EMAIL:
|
if not ADMIN_EMAIL:
|
||||||
|
@ -118,7 +148,7 @@ if __name__ == "__main__":
|
||||||
"--job",
|
"--job",
|
||||||
help="Choose a cron job to run",
|
help="Choose a cron job to run",
|
||||||
type=str,
|
type=str,
|
||||||
choices=["stats", "notify_trial_end",],
|
choices=["stats", "notify_trial_end", "notify_manual_subscription_end"],
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -131,3 +161,6 @@ if __name__ == "__main__":
|
||||||
elif args.job == "notify_trial_end":
|
elif args.job == "notify_trial_end":
|
||||||
LOG.d("Notify users with trial ending soon")
|
LOG.d("Notify users with trial ending soon")
|
||||||
notify_trial_end()
|
notify_trial_end()
|
||||||
|
elif args.job == "notify_manual_subscription_end":
|
||||||
|
LOG.d("Notify users with manual subscription ending soon")
|
||||||
|
notify_manual_sub_end()
|
||||||
|
|
|
@ -10,3 +10,9 @@ jobs:
|
||||||
shell: /bin/bash
|
shell: /bin/bash
|
||||||
schedule: "0 8 * * *"
|
schedule: "0 8 * * *"
|
||||||
captureStderr: true
|
captureStderr: true
|
||||||
|
|
||||||
|
- name: SimpleLogin Notify Manual Subscription Ends
|
||||||
|
command: python /code/cron.py -j notify_manual_subscription_end
|
||||||
|
shell: /bin/bash
|
||||||
|
schedule: "0 9 * * *"
|
||||||
|
captureStderr: true
|
||||||
|
|
39
migrations/versions/2020_022316_e3cb44b953f2_.py
Normal file
39
migrations/versions/2020_022316_e3cb44b953f2_.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: e3cb44b953f2
|
||||||
|
Revises: f580030d9beb
|
||||||
|
Create Date: 2020-02-23 16:43:45.843338
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy_utils
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e3cb44b953f2'
|
||||||
|
down_revision = 'f580030d9beb'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('manual_subscription',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
|
||||||
|
sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('end_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
|
||||||
|
sa.Column('comment', sa.Text(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='cascade'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('user_id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('manual_subscription')
|
||||||
|
# ### end Alembic commands ###
|
26
templates/emails/transactional/manual-subscription-end.html
Normal file
26
templates/emails/transactional/manual-subscription-end.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if name %}
|
||||||
|
{{ render_text("Hi " + name + ",") }}
|
||||||
|
{% else %}
|
||||||
|
{{ render_text("Hi,") }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ render_text("Your subscription will end " + manual_sub.end_at.humanize() + ".") }}
|
||||||
|
|
||||||
|
{{ render_text("When the subscription ends:") }}
|
||||||
|
|
||||||
|
{{ render_text("- All aliases/domains/directories you have created are <b>kept</b> and continue working normally.") }}
|
||||||
|
{{ render_text("- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.") }}
|
||||||
|
{{ render_text("- As features like <b>catch-all</b> or <b>directory</b> allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.") }}
|
||||||
|
{{ render_text("- You cannot add new domain or directory.") }}
|
||||||
|
|
||||||
|
{{ render_text('You can upgrade today to continue using all these Premium features (and much more coming).') }}
|
||||||
|
|
||||||
|
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
|
||||||
|
{{ render_text('P.S. If you have any questions or need any help, please don\'t hesitate to reach out. You can simply reply to this email or reach us via Twitter/Github.') }}
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
15
templates/emails/transactional/manual-subscription-end.txt
Normal file
15
templates/emails/transactional/manual-subscription-end.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
Hi {{name}}
|
||||||
|
|
||||||
|
Your subscription will end {{ manual_sub.end_at.humanize() }}.
|
||||||
|
|
||||||
|
When the subscription ends:
|
||||||
|
|
||||||
|
- All aliases/domains/directories you have created are kept and continue working.
|
||||||
|
- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.
|
||||||
|
- As features like "catch-all" or "directory" allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.
|
||||||
|
- You cannot add new domain or directory.
|
||||||
|
|
||||||
|
You can upgrade today to continue using all these Premium features (and much more coming).
|
||||||
|
|
||||||
|
Best,
|
||||||
|
Son - SimpleLogin founder.
|
|
@ -37,6 +37,7 @@
|
||||||
requires an investment of your time, and we appreciate you giving us a chance.') }}
|
requires an investment of your time, and we appreciate you giving us a chance.') }}
|
||||||
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
|
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
|
||||||
{{ render_text('P.S. If you have any questions or need any help, please don\'t hesitate to reach out. You can simply reply to this email or reach us via Twitter/Github.') }}
|
{{ render_text('P.S. If you have any questions or need any help, please don\'t hesitate to reach out. You can simply reply to this email or reach us via Twitter/Github.') }}
|
||||||
|
{{ raw_url("https://app.simplelogin.io/dashboard/pricing") }}
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue