Simplify emails with a spool

This commit is contained in:
Reid 'arrdem' McKenzie 2023-06-02 23:58:29 -06:00
parent 2a59c41903
commit d9db59b1db
5 changed files with 67 additions and 29 deletions

View file

@ -84,11 +84,21 @@ def post_register():
break 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 username, email, salt(request.form["password"]), group_id, status_id
): ):
id, status = res if user.status_id == -2:
if status == -1: 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( flash(
"Please check your email for a verification request", "Please check your email for a verification request",
category="success", category="success",

View file

@ -1,3 +1,5 @@
----------------------------------------------------------------------------------------------------
-- User structures
CREATE TABLE IF NOT EXISTS groups ( CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT id INTEGER PRIMARY KEY AUTOINCREMENT
, name TEXT , name TEXT
@ -100,3 +102,14 @@ CREATE TABLE IF NOT EXISTS jobs (
, FOREIGN KEY(file_id) REFERENCES files(id) , FOREIGN KEY(file_id) REFERENCES files(id)
, FOREIGN KEY(printer_id) REFERENCES printer(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)
);

View file

@ -103,7 +103,7 @@ class Store(object):
digest.update(password.encode("utf-8")) digest.update(password.encode("utf-8"))
digest = digest.hexdigest() digest = digest.hexdigest()
return self._conn.execute( 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], [username, email, digest, group_id, status_id],
).fetchone() ).fetchone()
@ -521,3 +521,33 @@ class Store(object):
@requires_conn @requires_conn
def delete_job(self, uid: int, jid: int): def delete_job(self, uid: int, jid: int):
self._conn.execute("DELETE FROM jobs WHERE user_id = ? and id = ?", [uid, jid]) 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()

View file

@ -21,11 +21,11 @@
Welcome {{ username }}! Welcome {{ username }}!
</p> </p>
<p> <p>
Before you can use your account, please confirm your email address by clicking <a href="{{ base_url }}/user/verify?token={{token_id}}">this link</a> or pasting the following text into your browser's navigation bar. Before you can use your account, please confirm your email address by clicking <a href="{{ base_url }}user/verify?token={{token_id}}">this link</a> or pasting the following text into your browser's navigation bar.
</p> </p>
<pre> <pre>
<code> <code>
{{ base_url }}/user/verify?token={{token_id}} {{ base_url }}user/verify?token={{token_id}}
</code> </code>
</pre> </pre>
<p> <p>

View file

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