瀏覽代碼

Scheduled emails are now working correctly.

Adam Day 2 年之前
父節點
當前提交
f6f71cb149

+ 255 - 36
app.py

@@ -10,6 +10,9 @@ from peewee import Model, CharField, DateTimeField, SqliteDatabase, BooleanField
 import hashlib
 import os
 import csv
+import time
+import threading
+import atexit
 
 db_name = 'app.db'
 db = SqliteDatabase(db_name)
@@ -66,6 +69,81 @@ class PasswordResetLog(Model):
         database = db
 
 
+class EmailReminderLog(Model):
+    date = CharField(default=datetime.datetime.now().strftime('%Y-%m-%d'))
+    email = CharField()
+    interval = CharField()
+    sent = BooleanField(default=False)
+
+    class Meta:
+        database = db
+
+
+def scheduler(stop_event):
+    while not stop_event.is_set():
+        # Get all scheduled intervals from the DB
+        intervals = Schedule.select().execute()
+        users = IlsUser.select().execute()
+
+        # Get settings
+        domain_name_setting = Settings.get(Settings.name == 'Domain Name')
+        domain_name = domain_name_setting.value
+
+        password_reset_interval_setting = Settings.get(Settings.name == 'Password Reset Interval')
+        password_reset_interval = password_reset_interval_setting.value
+
+        smtp_host_setting = Settings.get(Settings.name == 'SMTP Host')
+        smtp_host = smtp_host_setting.value
+
+        smtp_port_setting = Settings.get(Settings.name == 'SMTP Port')
+        smtp_port = smtp_port_setting.value
+
+        smtp_username_setting = Settings.get(Settings.name == 'SMTP Username')
+        smtp_username = smtp_username_setting.value
+
+        smtp_password_setting = Settings.get(Settings.name == 'SMTP Password')
+        smtp_password = smtp_password_setting.value
+
+        # Loop through each user and calculate the number of days until the users password
+        # expires based on the password_reset_interval value.
+        # Loop through all users
+
+        # Schedule emails to be sent for each user and interval
+        for user in users:
+            # get the scheduled intervals
+            for interval in intervals:
+                # Calculate the date (yyyy-mm-dd) that the password will expire and subtract the interval
+                # from that date to get the date that the email should be sent.
+                # Example: If the password reset interval is 90 days and the current date is 2019-01-01
+                # then the password will expire on 2019-04-01.  If the interval is 30 days then the
+                # email should be sent on 2019-03-02.
+                # Calculate the date that the password will expire
+                password_expiration_date = user.reset_datetime + datetime.timedelta(days=int(password_reset_interval))
+                # Subtract the interval from the password expiration date to get the date that the email should be sent
+                email_date = password_expiration_date - datetime.timedelta(days=int(interval.interval))
+
+                # check if reminder has already been saved to the DB
+                reminder = EmailReminderLog.select().where(EmailReminderLog.email == user.email, EmailReminderLog.interval == interval.interval).execute()
+                if reminder:
+                    continue
+                else:
+                    # Store the date in the DB
+                    EmailReminderLog.create(date=email_date.strftime('%Y-%m-%d'), email=user.email, interval=interval.interval).save()
+
+        # Get all email reminders from the DB
+        # if today's date matches the EmailReminderLog date then send the email and mark the reminder as sent
+        reminders = EmailReminderLog.select().where(EmailReminderLog.date == datetime.datetime.now().strftime('%Y-%m-%d')).execute()
+        for reminder in reminders:
+            # Send the email
+            emailer = Emailer(smtp_host, smtp_port, smtp_username, smtp_password)
+            emailer.send_email(reminder.email, 'Password Reset Reminder', 'Your password will expire in ' + reminder.interval + ' days.  Please visit ' + domain_name + ' to reset your password.')
+            # Mark the reminder as sent
+            reminder.sent = True
+            reminder.save()
+
+        time.sleep(5)  # Sleep for 15 minutes
+
+
 # Encrypt the password with SHA256
 def encrypt_password(password):
     hash = hashlib.sha256(password.encode('utf-8')).hexdigest()
@@ -91,6 +169,7 @@ if db.table_exists('settings') is False:
     Settings.create(name='SMTP Password', value="").save()
     Settings.create(name='Domain Name', value="lynx").save()
     Settings.create(name='Password Reset URL', value="https://terminal.idaho-lynx.org").save()
+    Settings.create(name='Password Reset Interval', value="90").save()
 
 if db.table_exists('log') is False:
     db.create_tables([Log, ])
@@ -101,6 +180,9 @@ if db.table_exists('password_reset_log') is False:
 if db.table_exists('schedule') is False:
     db.create_tables([Schedule, ])
 
+if db.table_exists('email_reminder_log') is False:
+    db.create_tables([EmailReminderLog, ])
+
 db.close()
 
 settings = Settings.select().execute()
@@ -111,24 +193,15 @@ debug = debug_setting.value
 http_port_setting = Settings.get(Settings.name == 'HTTP Port')
 http_port = http_port_setting.value
 
-smtp_host_setting = Settings.get(Settings.name == 'SMTP Host')
-smtp_host = smtp_host_setting.value
-
-smtp_port_setting = Settings.get(Settings.name == 'SMTP Port')
-smtp_port = smtp_port_setting.value
-
-smtp_username_setting = Settings.get(Settings.name == 'SMTP Username')
-smtp_username = smtp_username_setting.value
-
-smtp_password_setting = Settings.get(Settings.name == 'SMTP Password')
-smtp_password = smtp_password_setting.value
-
 domain_name_setting = Settings.get(Settings.name == 'Domain Name')
 domain_name = domain_name_setting.value
 
 password_reset_url_setting = Settings.get(Settings.name == 'Password Reset URL')
 password_reset_url = password_reset_url_setting.value
 
+password_reset_interval_setting = Settings.get(Settings.name == 'Password Reset Interval')
+password_reset_interval = password_reset_interval_setting.value
+
 log = logging.getLogger('werkzeug')
 log.setLevel(logging.INFO)
 
@@ -138,18 +211,6 @@ else:
     debug = False
 
 
-def send_email(to, subject, body):
-    # Get settings from smtp_settings
-    host = smtp_host
-    port = smtp_port
-    username = smtp_username
-    password = smtp_password
-
-    # Create an instance of the Emailer class
-    emailer = Emailer(host, port, username, password)
-    # Call the send_email method
-    emailer.send_email(to, subject, body)
-
 # send_email('aday@twinfallspubliclibrary.org', 'TEST', 'This is a test email')
 
 
@@ -202,13 +263,19 @@ def admin_password_check():
 
 db.close()
 
+# Start the scheduler loop in another thread
+shutdown_scheduler = threading.Event()
+scheduler_thread = threading.Thread(target=scheduler, args=(shutdown_scheduler,))
+scheduler_thread.start()
+
+# Start the HTTP Server
 app = Flask(__name__)
 app.secret_key = os.urandom(24)
 
 
 def format_time_ago(timestamp):
     """Calculate the time passed since a datetime stamp and format it as a human-readable string."""
-    now = datetime.datetime.utcnow()
+    now = datetime.datetime.now()
     diff = now - timestamp
 
     if diff.days > 365:
@@ -328,7 +395,38 @@ def admin():
     if not requires_auth():
         return redirect(url_for('login'))
 
-    return render_template('admin.html')
+    # Get a count of all IlsUsers
+    try:
+        ils_users = IlsUser.select().count()
+    except Exception as e:
+        ils_users = None
+
+    # Get a list of coming email reminders for the next 7 days
+    try:
+        email_reminders = PasswordResetLog.select().where(PasswordResetLog.date_created < datetime.datetime.now() + datetime.timedelta(days=7)).execute()
+    except Exception as e:
+        email_reminders = None
+
+    # Get a list ils users that have passwords expiring in the next 7 days
+    try:
+        ils_users_expiring = IlsUser.select().where(IlsUser.password_expires < datetime.datetime.now() + datetime.timedelta(days=7)).execute()
+    except Exception as e:
+        ils_users_expiring = None
+
+    # get a list of email notifications sent in the last 7 days
+    try:
+        # Get a list of all future email reminders
+        email_reminders = EmailReminderLog.select().order_by(EmailReminderLog.date).limit(15).execute()
+    except Exception as e:
+        email_reminders = None
+
+    context = {
+        'ils_user_count': ils_users,
+        'ils_users_expiring': ils_users_expiring,
+        'email_reminders': email_reminders,
+    }
+
+    return render_template('admin.html', context=context)
 
 
 @app.route('/admin/users/', methods=['GET', 'POST'])
@@ -387,13 +485,13 @@ def admin_users_edit(id):
         confirm_password = request.form.get('confirm_password')
 
         # Check to see if username already exists
-        try:
-            user = User.filter(User.username == username).first()
-        except Exception as e:
-            print(e)
-            user = None
+        all_users = list()
+        users = User.select().execute()
+        for u in users:
+            if u.username != user.username:
+                all_users.append(u.username)
 
-        if user.username != username:
+        if username in all_users:
             message = 'Username already exists'
         else:
             user.username = username
@@ -479,11 +577,55 @@ def admin_ils_users_delete(id):
     # Get the user from the DB
     user = IlsUser.get(IlsUser.id == id)
     username = user.username
+
+    # Remove all email reminders for this user
+    reminders = EmailReminderLog.select().where(EmailReminderLog.email == user.email).execute()
+    for reminder in reminders:
+        reminder.delete_instance()
+
     user.delete_instance()
     Log.create(username=session['username'], action='Removed ILS user: %s' % username, ).save()
     return redirect(url_for('admin_ils_users'))
 
 
+@app.route('/admin/users/ils/edit/<int:id>', methods=['GET', 'POST'])
+def admin_ils_users_edit(id):
+    # Check to see if user is logged in
+    if not requires_auth():
+        return redirect(url_for('login'))
+
+    # Get the user from the DB
+    user = IlsUser.get(IlsUser.id == id)
+
+    message = None
+    if request.method == 'POST':
+        username = request.form.get('username')
+        email = request.form.get('email')
+
+        # Check to see if username already exists
+        all_users = list()
+        users = IlsUser.select().execute()
+        for u in users:
+            if u.username != user.username:
+                all_users.append(u.username)
+
+        if username in all_users:
+            message = 'Username already exists'
+        else:
+            user.username = username
+            user.email = email
+            user.save()
+            message = 'User updated successfully'
+            Log.create(username=session['username'], action='Updated ILS user: %s' % username, ).save()
+
+    context = {
+        'user': user,
+        'message': message,
+    }
+
+    return render_template('admin_ils_user_edit.html', context=context)
+
+
 # create a route for generating a CSV file for download
 @app.route('/admin/users/ils/csv/download', methods=['GET', 'POST'])
 def admin_ils_users_csv_download():
@@ -572,6 +714,33 @@ def settings():
             setting = None
 
         if setting:
+            # Scrub the values based on the setting name
+            if setting.name == 'Debug Mode':
+                if value.lower() == 'true':
+                    value = True
+                else:
+                    value = False
+                value = bool(value)
+            elif setting.name == 'HTTP Port':
+                value = str(value)
+            elif setting.name == 'SMTP Host':
+                value = str(value)
+            elif setting.name == 'SMTP Port':
+                value = str(value)
+            elif setting.name == 'SMTP Username':
+                value = str(value)
+            elif setting.name == 'SMTP Password':
+                value = str(value)
+            elif setting.name == 'Domain Name':
+                value = str(value)
+            elif setting.name == 'Password Reset URL':
+                value = str(value)
+            elif setting.name == 'Password Reset Interval':
+                if value.isdigit():
+                    value = int(value)
+                else:
+                    value = setting.value
+
             # Update the setting
             old_value = setting.value
             setting.value = value
@@ -600,11 +769,22 @@ def schedule():
 
     message = None
 
+    # Get all schedules from the DB
+    schedules = Schedule.select().order_by(Schedule.interval.cast("INTEGER")).execute()
+
     # add schedule
     if request.method == 'POST':
         # Assign form values to variables
         interval = request.form.get('interval')
 
+        # Check if interval is a number
+        try:
+            int(interval)
+        except Exception as e:
+            print(e)
+            message = 'Error creating schedule. Value submitted must be a whole number.'
+            return render_template('schedule.html', context={'message': message, 'schedules': schedules})
+
         # Check if the schedule already exists
         try:
             schedule = Schedule.get(Schedule.interval == interval)
@@ -620,8 +800,7 @@ def schedule():
             Log.create(username=session['username'], action='Created schedule for %s day interval.' % interval).save()
             message = 'Schedule: %s created successfully' % interval
 
-    # Get all schedules from the DB
-    schedules = Schedule.select().order_by(Schedule.interval.cast("INTEGER")).execute()
+
 
     context = {
         'schedules': schedules,
@@ -631,6 +810,38 @@ def schedule():
     return render_template('schedule.html', context=context)
 
 
+@app.route('/admin/schedule/emails')
+def scheduled_emails():
+    # Check to see if user is logged in
+    if not requires_auth():
+        return redirect(url_for('login'))
+
+    # Get all future emailreminderlogs
+    reminders = EmailReminderLog.select().where(EmailReminderLog.date > datetime.datetime.now()).order_by(
+        -EmailReminderLog.date).execute()
+
+    context = {
+        'reminders': reminders,
+    }
+
+    return render_template('scheduled_emails.html', context=context)
+
+
+# remove schedule
+@app.route('/admin/schedule/remove/reminder/<int:id>')
+def reminder_remove(id):
+    # Check to see if user is logged in
+    if not requires_auth():
+        return redirect(url_for('login'))
+
+    # Get EmailReminderLogs with the interval of the schedule being removed
+    email_reminder_log = EmailReminderLog.get(EmailReminderLog.id == id)
+    Log.create(username=session['username'],
+               action='Removed %s day reminder for %s' % (email_reminder_log.interval, email_reminder_log.email)).save()
+    email_reminder_log.delete_instance()
+    return redirect(url_for('scheduled_emails'))
+
+
 # remove schedule
 @app.route('/admin/schedule/remove/<int:id>', methods=['GET', 'POST'])
 def schedule_remove(id):
@@ -638,9 +849,15 @@ def schedule_remove(id):
     if not requires_auth():
         return redirect(url_for('login'))
 
+    # Get EmailReminderLogs with the interval of the schedule being removed
+    email_reminder_logs = EmailReminderLog.select().where(EmailReminderLog.interval == id).execute()
+    for email_reminder_log in email_reminder_logs:
+        email_reminder_log.delete_instance()
+
     # Get the schedule from the DB
     schedule = Schedule.get(Schedule.id == id)
     schedule.delete_instance()
+
     Log.create(username=session['username'], action='Removed schedule for a %s day reminder.' % schedule.interval).save()
     return redirect(url_for('schedule'))
 
@@ -724,8 +941,9 @@ def login():
     return render_template('login.html', context=context)
 
 
-# on exit of the program make sure the http server is stopped
-# @app.teardown_appcontext
+def clean_up():
+    shutdown_scheduler.set()
+    http_server.stop()
 
 
 if __name__ == "__main__":
@@ -748,3 +966,4 @@ if __name__ == "__main__":
     print("To stop the application close this window.")
     print("---------------------------------------------------------------")
     http_server.serve_forever()
+    atexit.register(clean_up)

+ 43 - 1
templates/admin.html

@@ -2,7 +2,49 @@
 {% block content %}
 <div class="row">
     <div class="col">
-        <h3><i class="ri-dashboard-fill"></i> Admin Dashboard</h3>
+        <h3 class="mb-3"><i class="ri-dashboard-fill"></i> Admin Dashboard</h3>
+        <div class="row justify-content-center">
+
+            <div class="col-sm-12 mb-4">
+                <div class="card">
+                    <div class="card-body text-center">
+                        <h4>ILS User Count</h4>
+                        <h1 class="display-3">{{ context.ils_user_count}}</h1>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-sm-12 col-md-6 mb-4">
+                <div class="card">
+                    <div class="card-body text-center">
+                        <h4>Expiring in 7 days</h4>
+                        <ul class="text-start">
+
+                        </ul>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-sm-12 col-md-6 mb-4">
+                <div class="card">
+                    <div class="card-body text-center">
+                        <h4>Upcoming Reminders</h4>
+                        <table class="table text-start">
+                            <tr>
+                                <th>Email</th>
+                                <th>Send on</th>
+                            </tr>
+                            {% for reminder in context.email_reminders %}
+                                <tr>
+                                    <td>{{ reminder.email }}</td>
+                                    <td>{{ reminder.date }}</td>
+                                </tr>
+                            {% endfor %}
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
     </div>
 </div>
 {% endblock %}

+ 31 - 0
templates/admin_ils_user_edit.html

@@ -0,0 +1,31 @@
+{% extends 'auth_layout.html' %}
+{% block content %}
+{% if context.message %}
+    <div class="row">
+        <div class="col text-center text-primary">
+            <i class="ri-error-warning-fill"></i> {{ context.message }}
+        </div>
+    </div>
+{% endif %}
+<div class="row">
+    <div class="col">
+        <a href="{{ url_for('admin_ils_users') }}" class="btn btn-secondary float-end">Back</a>
+        <h3><i class="ri-shield-user-fill"></i> Edit ILS User</h3>
+    </div>
+</div>
+<div class="row">
+    <div class="col-sm-12 col-md-6 col-lg-4">
+        <form action="" method="post">
+            <div class="mb-3">
+                <input type="text" class="form-control" value="{{ context.user.username }}" id="username" name="username" placeholder="Username" required>
+            </div>
+            <div class="mb-3">
+                <input type="text" class="form-control" value="{{ context.user.email }}" id="email" name="email" placeholder="Email" required>
+            </div>
+            <div class="mb-3 text-center">
+                <input type="submit" class="btn btn-primary w-100" value="Save">
+            </div>
+        </form>
+    </div>
+</div>
+{% endblock %}

+ 6 - 4
templates/admin_ils_users.html

@@ -9,9 +9,9 @@
 {% endif %}
 <div class="row">
     <div class="col">
-        <a href="{{ url_for('admin_ils_users_csv_download') }}" class="btn btn-dark float-end"><i class="ri-file-download-fill"></i> CSV Export</a>
-        <a href="{{ url_for('admin_ils_users_csv_import') }}" class="btn btn-dark float-end me-2"><i class="ri-file-excel-line"></i> CSV Import</a>
-        <a href="#!" data-bs-toggle="modal" data-bs-target="#add-user" class="btn btn-primary float-end me-2"><i class="ri-user-add-line"></i> Add ILS User</a>
+        <a href="#!" data-bs-toggle="modal" data-bs-target="#add-user" class="btn btn-primary float-end"><i class="ri-user-add-line"></i> Add ILS User</a>
+        <a href="{{ url_for('admin_ils_users_csv_download') }}" class="btn btn-dark float-end me-2"><i class="ri-file-download-fill"></i> CSV Export</a>
+        <a href="{{ url_for('admin_ils_users_csv_import') }}" class="btn btn-dark float-end me-2"><i class="ri-file-upload-fill"></i> CSV Import</a>
         <h3><i class="ri-shield-user-fill"></i> ILS Users</h3>
         <p class="lead">ILS users accounts establish with the ILS system.</p>
     </div>
@@ -24,6 +24,7 @@
                     <th scope="col">Username</th>
                     <th scope="col">Email</th>
                     <th scope="col">Reset Date</th>
+                    <th scope="col">Reset Age</th>
                     <th scope="col">Actions</th>
                 </tr>
             </thead>
@@ -33,8 +34,9 @@
                     <td>{{ user.username }}</td>
                     <td>{{ user.email }}</td>
                     <td>{{ user.reset_datetime }}</td>
+                    <td>{{ user.reset_datetime|time_since }}</td>
                     <td>
-                        <a href="" class="btn btn-primary">Edit</a>
+                        <a href="{{ url_for('admin_ils_users_edit', id=user.id) }}" class="btn btn-primary">Edit</a>
                         <a href="#!" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#remove-user-{{ loop.index0 }}">Delete</a>
                     </td>
                 </tr>

+ 0 - 3
templates/admin_users.html

@@ -54,9 +54,6 @@
                     <div class="mb-3">
                         <input type="text" class="form-control" id="username" name="username" placeholder="Username" required>
                     </div>
-                    <div class="mb-3">
-                        <input type="email" class="form-control" id="email" name="email" placeholder="Email" required>
-                    </div>
                     <div class="mb-3">
                         <input type="password" class="form-control" id="password" name="password" placeholder="Password" required>
                     </div>

+ 1 - 1
templates/auth_layout.html

@@ -39,7 +39,7 @@
                         <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('password_reset_log') }}"><i class="ri-file-list-3-line"></i> Password Reset Log</a>
                     </li>
                     <li class="nav-item">
-                        <a class="nav-link bg-light text-dark border-bottom" href="{{ url_for('logout') }}"><i class="ri-logout-box-line"></i> Logout</a>
+                        <a class="nav-link bg-danger text-white border-bottom" href="{{ url_for('logout') }}"><i class="ri-logout-box-line"></i> Logout</a>
                     </li>
                 </ul>
             </div>

+ 7 - 3
templates/schedule.html

@@ -2,14 +2,15 @@
 {% block content %}
 {% if context.message %}
     <div class="row">
-        <div class="col text-center text-primary">
+        <div class="col text-center text-primary mb-3">
             <i class="ri-error-warning-fill"></i> {{ context.message }}
         </div>
     </div>
 {% endif %}
 <div class="row">
     <div class="col">
-        <a href="#!" data-bs-toggle="modal" data-bs-target="#add-schedule" class="btn btn-primary float-end me-2"><i class="ri-add-line"></i> Schedule</a>
+        <a href="#!" data-bs-toggle="modal" data-bs-target="#add-schedule" class="btn btn-primary float-end me-2"><i class="ri-add-line"></i>Add Schedule</a>
+        <a href="{{ url_for('scheduled_emails') }}" class="btn btn-dark float-end me-2"><i class="ri-time-line"></i> Scheduled Reminders</a>
         <h3><i class="ri-time-line"></i> Schedule</h3>
         <p class="lead">The following schedule rules will schedule and send email reminders to staff.</p>
     </div>
@@ -46,8 +47,11 @@
             </div>
             <form action="" method="post">
                 <div class="modal-body">
+                    <p class="lead">
+                        Enter the number of days before password expiration to send email reminders.
+                    </p>
                     <div class="mb-3">
-                        <input type="text" class="form-control" id="interval" name="interval" placeholder="Interval in days" required>
+                        <input type="text" class="form-control" id="interval" name="interval" placeholder="Number of days" required>
                     </div>
                 </div>
                 <div class="modal-footer bg-light">

+ 64 - 0
templates/scheduled_emails.html

@@ -0,0 +1,64 @@
+{% extends 'auth_layout.html' %}
+{% block content %}
+{% if context.message %}
+    <div class="row">
+        <div class="col text-center text-primary">
+            <i class="ri-error-warning-fill"></i> {{ context.message }}
+        </div>
+    </div>
+{% endif %}
+<div class="row">
+    <div class="col">
+        <a href="{{ url_for('schedule') }}" class="btn btn-dark float-end me-2">Back</a>
+        <h3><i class="ri-time-line"></i> Scheduled Reminders</h3>
+        <p class="lead">The following are the scheduled email reminders.</p>
+    </div>
+</div>
+<div class="row">
+    <div class="col">
+        <table class="table table-flush">
+            <thead>
+                <tr>
+                    <th scope="col">Date to Send</th>
+                    <th scope="col">Days before expiration</th>
+                    <th scope="col">Email</th>
+                    <th class="text-end" scope="col">Actions</th>
+                </tr>
+            </thead>
+            <tbody>
+                {% for reminder in context.reminders %}
+                <tr>
+                    <td>{{ reminder.date }}</td>
+                    <td>{{ reminder.interval }}</td>
+                    <td>{{ reminder.email }}</td>
+                    <td class="text-end">
+                        <a href="#!" data-bs-toggle="modal" data-bs-target="#reminder-{{ loop.index0 }}" class="btn btn-danger">Delete</a>
+                    </td>
+                </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+    </div>
+</div>
+
+{% for reminder in context.reminders %}
+<div class="modal fade" id="reminder-{{ loop.index0 }}" tabindex="-1">
+    <div class="modal-dialog modal-dialog-centered">
+        <div class="modal-content">
+            <div class="modal-header bg-light">
+                <h1 class="modal-title fs-5">Confirmation </h1>
+                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+            </div>
+            <div class="modal-body">
+                <p class="lead">Are you sure you want to delete this reminder?</p>
+            </div>
+            <div class="modal-footer bg-light">
+                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+                <a class="btn btn-danger" href="{{ url_for('reminder_remove', id=reminder.id) }}">Yes, remove the reminder</a>
+            </div>
+        </div>
+    </div>
+</div>
+{% endfor %}
+
+{% endblock %}