2020-02-03 14:11:11 +08:00
|
|
|
"""
|
|
|
|
Run scheduled jobs.
|
|
|
|
Not meant for running job at precise time (+- 1h)
|
|
|
|
"""
|
2020-09-11 02:14:55 +08:00
|
|
|
import csv
|
2020-02-03 14:11:11 +08:00
|
|
|
import time
|
|
|
|
|
|
|
|
import arrow
|
2020-09-11 02:14:55 +08:00
|
|
|
import requests
|
2020-02-03 14:11:11 +08:00
|
|
|
|
2020-09-11 02:14:55 +08:00
|
|
|
from app import s3
|
2020-04-03 05:26:17 +08:00
|
|
|
from app.config import (
|
|
|
|
JOB_ONBOARDING_1,
|
|
|
|
JOB_ONBOARDING_2,
|
|
|
|
JOB_ONBOARDING_4,
|
2020-09-11 02:14:55 +08:00
|
|
|
JOB_BATCH_IMPORT,
|
|
|
|
)
|
|
|
|
from app.email_utils import (
|
|
|
|
send_email,
|
|
|
|
render,
|
|
|
|
get_email_domain_part,
|
2020-04-03 05:26:17 +08:00
|
|
|
)
|
2021-01-11 19:29:40 +08:00
|
|
|
from app.utils import sanitize_email
|
2020-02-03 14:11:11 +08:00
|
|
|
from app.extensions import db
|
|
|
|
from app.log import LOG
|
2020-09-11 02:14:55 +08:00
|
|
|
from app.models import (
|
|
|
|
User,
|
|
|
|
Job,
|
|
|
|
BatchImport,
|
|
|
|
Alias,
|
|
|
|
DeletedAlias,
|
|
|
|
DomainDeletedAlias,
|
|
|
|
CustomDomain,
|
|
|
|
)
|
2020-02-03 14:11:11 +08:00
|
|
|
from server import create_app
|
|
|
|
|
|
|
|
|
|
|
|
# fix the database connection leak issue
|
|
|
|
# use this method instead of create_app
|
|
|
|
def new_app():
|
|
|
|
app = create_app()
|
|
|
|
|
|
|
|
@app.teardown_appcontext
|
|
|
|
def shutdown_session(response_or_exc):
|
|
|
|
# same as shutdown_session() in flask-sqlalchemy but this is not enough
|
|
|
|
db.session.remove()
|
|
|
|
|
|
|
|
# dispose the engine too
|
|
|
|
db.engine.dispose()
|
|
|
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
2020-03-25 04:01:38 +08:00
|
|
|
def onboarding_send_from_alias(user):
|
2020-10-22 16:44:05 +08:00
|
|
|
to_email, unsubscribe_link, via_email = user.get_communication_email()
|
2020-09-12 20:33:27 +08:00
|
|
|
if not to_email:
|
|
|
|
return
|
|
|
|
|
2020-02-03 14:11:11 +08:00
|
|
|
send_email(
|
2020-09-12 20:33:27 +08:00
|
|
|
to_email,
|
2020-12-06 18:25:41 +08:00
|
|
|
"SimpleLogin Tip: Send emails from your alias",
|
2020-09-12 21:51:43 +08:00
|
|
|
render("com/onboarding/send-from-alias.txt", user=user, to_email=to_email),
|
|
|
|
render("com/onboarding/send-from-alias.html", user=user, to_email=to_email),
|
2020-10-22 16:44:05 +08:00
|
|
|
unsubscribe_link,
|
|
|
|
via_email,
|
2020-02-03 14:11:11 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-03-25 04:01:38 +08:00
|
|
|
def onboarding_pgp(user):
|
2020-10-22 16:44:05 +08:00
|
|
|
to_email, unsubscribe_link, via_email = user.get_communication_email()
|
2020-09-12 20:33:27 +08:00
|
|
|
if not to_email:
|
|
|
|
return
|
|
|
|
|
2020-03-25 04:01:38 +08:00
|
|
|
send_email(
|
2020-09-12 20:33:27 +08:00
|
|
|
to_email,
|
2020-12-06 18:25:41 +08:00
|
|
|
"SimpleLogin Tip: Secure your emails with PGP",
|
2020-09-12 21:51:43 +08:00
|
|
|
render("com/onboarding/pgp.txt", user=user, to_email=to_email),
|
|
|
|
render("com/onboarding/pgp.html", user=user, to_email=to_email),
|
2020-10-22 16:44:05 +08:00
|
|
|
unsubscribe_link,
|
|
|
|
via_email,
|
2020-03-25 04:01:38 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-04-03 05:26:17 +08:00
|
|
|
def onboarding_browser_extension(user):
|
2020-10-22 16:44:05 +08:00
|
|
|
to_email, unsubscribe_link, via_email = user.get_communication_email()
|
2020-09-12 20:33:27 +08:00
|
|
|
if not to_email:
|
|
|
|
return
|
|
|
|
|
2020-04-03 05:26:17 +08:00
|
|
|
send_email(
|
2020-09-12 20:33:27 +08:00
|
|
|
to_email,
|
2020-12-06 18:25:41 +08:00
|
|
|
"SimpleLogin Tip: Chrome/Firefox/Safari extensions and Android/iOS apps",
|
2020-09-12 21:51:43 +08:00
|
|
|
render("com/onboarding/browser-extension.txt", user=user, to_email=to_email),
|
|
|
|
render("com/onboarding/browser-extension.html", user=user, to_email=to_email),
|
2020-10-22 16:44:05 +08:00
|
|
|
unsubscribe_link,
|
|
|
|
via_email,
|
2020-04-03 05:26:17 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-03-25 04:19:45 +08:00
|
|
|
def onboarding_mailbox(user):
|
2020-10-22 16:44:05 +08:00
|
|
|
to_email, unsubscribe_link, via_email = user.get_communication_email()
|
2020-09-12 20:33:27 +08:00
|
|
|
if not to_email:
|
|
|
|
return
|
|
|
|
|
2020-03-25 04:19:45 +08:00
|
|
|
send_email(
|
2020-09-12 20:33:27 +08:00
|
|
|
to_email,
|
2020-12-06 18:25:41 +08:00
|
|
|
"SimpleLogin Tip: Multiple mailboxes",
|
2020-09-12 21:51:43 +08:00
|
|
|
render("com/onboarding/mailbox.txt", user=user, to_email=to_email),
|
|
|
|
render("com/onboarding/mailbox.html", user=user, to_email=to_email),
|
2020-10-22 16:44:05 +08:00
|
|
|
unsubscribe_link,
|
|
|
|
via_email,
|
2020-03-25 04:19:45 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-09-11 02:14:55 +08:00
|
|
|
def handle_batch_import(batch_import: BatchImport):
|
|
|
|
user = batch_import.user
|
|
|
|
|
|
|
|
batch_import.processed = True
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
LOG.debug("Start batch import for %s %s", batch_import, user)
|
|
|
|
file_url = s3.get_url(batch_import.file.path)
|
|
|
|
|
|
|
|
LOG.d("Download file %s from %s", batch_import.file, file_url)
|
|
|
|
r = requests.get(file_url)
|
2020-12-06 18:25:41 +08:00
|
|
|
lines = [line.decode() for line in r.iter_lines()]
|
2020-09-11 02:14:55 +08:00
|
|
|
reader = csv.DictReader(lines)
|
|
|
|
|
|
|
|
for row in reader:
|
2020-09-11 22:51:04 +08:00
|
|
|
try:
|
2021-01-11 19:29:40 +08:00
|
|
|
full_alias = sanitize_email(row["alias"])
|
2020-09-11 22:51:04 +08:00
|
|
|
note = row["note"]
|
|
|
|
except KeyError:
|
|
|
|
LOG.warning("Cannot parse row %s", row)
|
|
|
|
continue
|
2020-09-11 02:14:55 +08:00
|
|
|
|
|
|
|
alias_domain = get_email_domain_part(full_alias)
|
|
|
|
custom_domain = CustomDomain.get_by(domain=alias_domain)
|
|
|
|
|
|
|
|
if (
|
|
|
|
not custom_domain
|
|
|
|
or not custom_domain.verified
|
|
|
|
or custom_domain.user_id != user.id
|
|
|
|
):
|
|
|
|
LOG.debug("domain %s can't be used %s", alias_domain, user)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if (
|
|
|
|
Alias.get_by(email=full_alias)
|
|
|
|
or DeletedAlias.get_by(email=full_alias)
|
|
|
|
or DomainDeletedAlias.get_by(email=full_alias)
|
|
|
|
):
|
|
|
|
LOG.d("alias already used %s", full_alias)
|
|
|
|
continue
|
|
|
|
|
|
|
|
alias = Alias.create(
|
|
|
|
user_id=user.id,
|
|
|
|
email=full_alias,
|
|
|
|
note=note,
|
|
|
|
mailbox_id=user.default_mailbox_id,
|
|
|
|
custom_domain_id=custom_domain.id,
|
|
|
|
batch_import_id=batch_import.id,
|
|
|
|
)
|
|
|
|
db.session.commit()
|
|
|
|
LOG.d("Create %s", alias)
|
|
|
|
|
|
|
|
|
2020-02-03 14:11:11 +08:00
|
|
|
if __name__ == "__main__":
|
|
|
|
while True:
|
|
|
|
# run a job 1h earlier or later is not a big deal ...
|
|
|
|
min_dt = arrow.now().shift(hours=-1)
|
|
|
|
max_dt = arrow.now().shift(hours=1)
|
|
|
|
|
|
|
|
app = new_app()
|
|
|
|
|
|
|
|
with app.app_context():
|
|
|
|
for job in Job.query.filter(
|
2020-12-06 18:25:41 +08:00
|
|
|
Job.taken.is_(False), Job.run_at > min_dt, Job.run_at <= max_dt
|
2020-02-03 14:11:11 +08:00
|
|
|
).all():
|
|
|
|
LOG.d("Take job %s", job)
|
|
|
|
|
|
|
|
# mark the job as taken, whether it will be executed successfully or not
|
|
|
|
job.taken = True
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
if job.name == JOB_ONBOARDING_1:
|
|
|
|
user_id = job.payload.get("user_id")
|
|
|
|
user = User.get(user_id)
|
|
|
|
|
2020-02-15 18:10:49 +08:00
|
|
|
# user might delete their account in the meantime
|
2020-03-25 04:01:38 +08:00
|
|
|
# or disable the notification
|
|
|
|
if user and user.notification and user.activated:
|
|
|
|
LOG.d("send onboarding send-from-alias email to user %s", user)
|
|
|
|
onboarding_send_from_alias(user)
|
|
|
|
elif job.name == JOB_ONBOARDING_2:
|
|
|
|
user_id = job.payload.get("user_id")
|
|
|
|
user = User.get(user_id)
|
|
|
|
|
|
|
|
# user might delete their account in the meantime
|
|
|
|
# or disable the notification
|
|
|
|
if user and user.notification and user.activated:
|
2020-03-25 04:23:26 +08:00
|
|
|
LOG.d("send onboarding mailbox email to user %s", user)
|
|
|
|
onboarding_mailbox(user)
|
2020-04-03 05:26:17 +08:00
|
|
|
elif job.name == JOB_ONBOARDING_4:
|
|
|
|
user_id = job.payload.get("user_id")
|
|
|
|
user = User.get(user_id)
|
|
|
|
|
|
|
|
# user might delete their account in the meantime
|
|
|
|
# or disable the notification
|
|
|
|
if user and user.notification and user.activated:
|
2020-09-10 04:16:10 +08:00
|
|
|
LOG.d("send onboarding pgp email to user %s", user)
|
|
|
|
onboarding_pgp(user)
|
2020-04-03 05:26:17 +08:00
|
|
|
|
2020-09-11 02:14:55 +08:00
|
|
|
elif job.name == JOB_BATCH_IMPORT:
|
|
|
|
batch_import_id = job.payload.get("batch_import_id")
|
|
|
|
batch_import = BatchImport.get(batch_import_id)
|
|
|
|
handle_batch_import(batch_import)
|
|
|
|
|
2020-02-03 14:11:11 +08:00
|
|
|
else:
|
2020-07-17 18:59:07 +08:00
|
|
|
LOG.exception("Unknown job name %s", job.name)
|
2020-02-03 14:11:11 +08:00
|
|
|
|
|
|
|
time.sleep(10)
|