From d9db59b1dbdf8739d8b4b29d24a69c2c5ec0c4b8 Mon Sep 17 00:00:00 2001 From: Reid 'arrdem' McKenzie Date: Fri, 2 Jun 2023 23:58:29 -0600 Subject: [PATCH] Simplify emails with a spool --- .../python/tentacles/blueprints/user_ui.py | 18 ++++++++--- .../tentacles/src/python/tentacles/schema.sql | 13 ++++++++ .../tentacles/src/python/tentacles/store.py | 32 ++++++++++++++++++- .../templates/verification_email.html.j2 | 4 +-- .../tentacles/src/python/tentacles/workers.py | 29 ++++------------- 5 files changed, 67 insertions(+), 29 deletions(-) diff --git a/projects/tentacles/src/python/tentacles/blueprints/user_ui.py b/projects/tentacles/src/python/tentacles/blueprints/user_ui.py index 3280e02..c9bfeae 100644 --- a/projects/tentacles/src/python/tentacles/blueprints/user_ui.py +++ b/projects/tentacles/src/python/tentacles/blueprints/user_ui.py @@ -84,16 +84,26 @@ def post_register(): break - if res := ctx.db.try_create_user( + if user := ctx.db.try_create_user( username, email, salt(request.form["password"]), group_id, status_id ): - id, status = res - if status == -1: + if user.status_id == -2: + ctx.db.create_email( + user.id, + "Tentacles email confirmation", + render_template( + "verification_email.html.j2", + username=user.name, + token_id=user.verification_token, + base_url=request.root_url, + ), + ) + flash( "Please check your email for a verification request", category="success", ) - return render_template("register.html.j2") + return render_template("register.html.j2") except Exception as e: log.exception("Error encountered while registering a user...") diff --git a/projects/tentacles/src/python/tentacles/schema.sql b/projects/tentacles/src/python/tentacles/schema.sql index b2ec1db..20f5067 100644 --- a/projects/tentacles/src/python/tentacles/schema.sql +++ b/projects/tentacles/src/python/tentacles/schema.sql @@ -1,3 +1,5 @@ +---------------------------------------------------------------------------------------------------- +-- User structures CREATE TABLE IF NOT EXISTS groups ( id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT @@ -100,3 +102,14 @@ CREATE TABLE IF NOT EXISTS jobs ( , FOREIGN KEY(file_id) REFERENCES files(id) , FOREIGN KEY(printer_id) REFERENCES printer(id) ); + +---------------------------------------------------------------------------------------------------- +-- Emails are used for notifications +CREATE TABLE IF NOT EXISTS email_spool ( + id INTEGER PRIMARY KEY AUTOINCREMENT + , user_id INTEGER NOT NULL + , subject TEXT + , body TEXT + , sent_at TEXT + , FOREIGN KEY(user_id) REFERENCES users(id) +); diff --git a/projects/tentacles/src/python/tentacles/store.py b/projects/tentacles/src/python/tentacles/store.py index ade7a30..3454a53 100644 --- a/projects/tentacles/src/python/tentacles/store.py +++ b/projects/tentacles/src/python/tentacles/store.py @@ -103,7 +103,7 @@ class Store(object): digest.update(password.encode("utf-8")) digest = digest.hexdigest() return self._conn.execute( - "INSERT INTO users (name, email, hash, group_id, status_id) VALUES (?, ?, ?, ?, ?) RETURNING id, status_id", + "INSERT INTO users (name, email, hash, group_id, status_id) VALUES (?, ?, ?, ?, ?) RETURNING *", [username, email, digest, group_id, status_id], ).fetchone() @@ -521,3 +521,33 @@ class Store(object): @requires_conn def delete_job(self, uid: int, jid: int): self._conn.execute("DELETE FROM jobs WHERE user_id = ? and id = ?", [uid, jid]) + + ################################################################################ + # Emails (notifications) + + @requires_conn + def create_email(self, uid: int, subject: str, body: str): + return self._conn.execute( + "INSERT INTO email_spool (user_id, subject, body) VALUES (?1, ?2, ?3) RETURNING id", + [uid, subject, body], + ).fetchone() + + @requires_conn + def send_email(self, eid: int): + return self._conn.execute( + "UPDATE email_spool SET sent_at = datetime('now') WHERE id = ?1", [eid] + ) + + @requires_conn + def poll_spool(self, limit: int = 16): + return self._conn.execute( + """ + SELECT s.id as `id`, u.email as `to`, subject, body + FROM email_spool s + INNER JOIN users u + ON s.user_id = u.id + WHERE s.sent_at IS NULL + LIMIT ?1 + """, + [limit], + ).fetchall() diff --git a/projects/tentacles/src/python/tentacles/templates/verification_email.html.j2 b/projects/tentacles/src/python/tentacles/templates/verification_email.html.j2 index 814f148..88b46de 100644 --- a/projects/tentacles/src/python/tentacles/templates/verification_email.html.j2 +++ b/projects/tentacles/src/python/tentacles/templates/verification_email.html.j2 @@ -21,11 +21,11 @@ Welcome {{ username }}!

- Before you can use your account, please confirm your email address by clicking this link or pasting the following text into your browser's navigation bar. + Before you can use your account, please confirm your email address by clicking this link or pasting the following text into your browser's navigation bar.

           
-            {{ base_url }}/user/verify?token={{token_id}}
+            {{ base_url }}user/verify?token={{token_id}}
           
         

diff --git a/projects/tentacles/src/python/tentacles/workers.py b/projects/tentacles/src/python/tentacles/workers.py index 0e89f6c..410efca 100644 --- a/projects/tentacles/src/python/tentacles/workers.py +++ b/projects/tentacles/src/python/tentacles/workers.py @@ -254,35 +254,21 @@ def pull_jobs(app: App, store: Store) -> None: log.exception("Oop") -def send_verifications(app, store: Store): +def send_emails(app, store: Store): with closing( FastMailSMTP( app.config.get("fastmail", {}).get("username"), app.config.get("fastmail", {}).get("key"), ) ) as fm: - for user in store.list_unverified_users(): + for message in store.poll_spool(): fm.send_message( from_addr="root@tirefireind.us", - to_addrs=[user.email], - subject="Email verification from tentacles", - msg=render_template( - "verification_email.html.j2", - base_url=app.config.get("base_url"), - username=user.username, - token_id=user.verification_token, - ), + to_addrs=[message.to], + subject=message.subject, + msg=message.body, ) - - -def send_approvals(app, store: Store): - with closing( - FastMailSMTP( - app.config.get("fastmail", {}).get("username"), - app.config.get("fastmail", {}).get("key"), - ) - ): - pass + store.send_email(message.id) @corn_job(timedelta(seconds=5)) @@ -293,8 +279,7 @@ def run_worker(app: App, db_factory): push_jobs(app, store) revoke_jobs(app, store) pull_jobs(app, store) - send_verifications(app, store) - send_approvals(app, store) + send_emails(app, store) def create_workers(app, db_factory: Callable[[App], Store]) -> Event: