diff --git a/app/admin/routes.py b/app/admin/routes.py
index 59f71153881950627d8c15d756d424a7a6eb1846..1b0a039a545835500f547917556f36c2261b8788 100644
--- a/app/admin/routes.py
+++ b/app/admin/routes.py
@@ -16,7 +16,7 @@ def home():
 @bp.route('/manage_bookings')
 @permission_required(admin_permission)
 def manage_bookings():
-    return render_template('admin/index.html')
+    return render_template('admin/manage_bookings.html')
 
 @bp.route('/manage_users')
 @permission_required(super_admin_permission)
diff --git a/app/api/routes.py b/app/api/routes.py
index 1c40f57de07b90beaedd98c2b7f704215d5163bb..ee448e573f754c83d2db28fed8acf2a5086ba7c8 100644
--- a/app/api/routes.py
+++ b/app/api/routes.py
@@ -1,8 +1,7 @@
-from flask import render_template, redirect, url_for, Flask, jsonify
+from flask import jsonify, request
 from app.api import bp
 from app.models import User, Listings
-from sqlalchemy import text
-from app import csrf
+import json
 
 @bp.route('/user_id/<int:id>', methods=['GET'])
 def get_user_by_id(id):
@@ -87,4 +86,38 @@ def create_listing():
     
     #If something falls over throw nice error for debugging, will change for admin only users to see errors otherwise throw generic 500
     except Exception as e:
-        return jsonify({'error': str(e)}), 500
\ No newline at end of file
+        return jsonify({'error': str(e)}), 500
+
+# Sample data
+data = [
+    {"Name": "Tiger Nixon", "Position": "System Architect", "Office": "Edinburgh", "Age": 61, "StartDate": "2011-04-25", "Salary": "$320,800"},
+    {"Name": "Garrett Winters", "Position": "Accountant", "Office": "Tokyo", "Age": 63, "StartDate": "2011-07-25", "Salary": "$170,750"},
+    {"Name": "Ashton Cox", "Position": "Junior Technical Author", "Office": "San Francisco", "Age": 66, "StartDate": "2009-01-12", "Salary": "$86,000"},
+    {"Name": "Cedric Kelly", "Position": "Senior Javascript Developer", "Office": "Edinburgh", "Age": 22, "StartDate": "2012-03-29", "Salary": "$433,060"},
+    {"Name": "Airi Satou", "Position": "Accountant", "Office": "Tokyo", "Age": 33, "StartDate": "2008-11-28", "Salary": "$162,700"},
+    {"Name": "Brielle Williamson", "Position": "Integration Specialist", "Office": "New York", "Age": 61, "StartDate": "2012-12-02", "Salary": "$372,000"},
+    {"Name": "Herrod Chandler", "Position": "Sales Assistant", "Office": "San Francisco", "Age": 59, "StartDate": "2012-08-06", "Salary": "$137,500"},
+    {"Name": "Rhona Davidson", "Position": "Integration Specialist", "Office": "Tokyo", "Age": 55, "StartDate": "2010-10-14", "Salary": "$327,900"},
+    {"Name": "Colleen Hurst", "Position": "Javascript Developer", "Office": "San Francisco", "Age": 39, "StartDate": "2009-09-15", "Salary": "$205,500"},
+    {"Name": "Sonya Frost", "Position": "Software Engineer", "Office": "Edinburgh", "Age": 23, "StartDate": "2008-12-13", "Salary": "$103,600"},
+    {"Name": "Jena Gaines", "Position": "Office Manager", "Office": "London", "Age": 30, "StartDate": "2008-12-19", "Salary": "$90,560"},
+    {"Name": "Quinn Flynn", "Position": "Support Lead", "Office": "Edinburgh", "Age": 22, "StartDate": "2013-03-03", "Salary": "$342,000"},
+    {"Name": "Charde Marshall", "Position": "Regional Director", "Office": "San Francisco", "Age": 36, "StartDate": "2008-10-16", "Salary": "$470,600"},
+    {"Name": "Haley Kennedy", "Position": "Senior Marketing Designer", "Office": "London", "Age": 43, "StartDate": "2012-12-18", "Salary": "$313,500"},
+    {"Name": "Tatyana Fitzpatrick", "Position": "Regional Director", "Office": "London", "Age": 19, "StartDate": "2010-03-17", "Salary": "$385,750"},
+    {"Name": "Michael Silva", "Position": "Marketing Designer", "Office": "London", "Age": 66, "StartDate": "2012-11-27", "Salary": "$198,500"},
+    {"Name": "Paul Byrd", "Position": "Chief Financial Officer (CFO)", "Office": "New York", "Age": 64, "StartDate": "2010-06-09", "Salary": "$725,000"},
+    {"Name": "Gloria Little", "Position": "Systems Administrator", "Office": "New York", "Age": 59, "StartDate": "2009-04-10", "Salary": "$237,500"},
+    {"Name": "Bradley Greer", "Position": "Software Engineer", "Office": "London", "Age": 41, "StartDate": "2012-10-13", "Salary": "$132,000"},
+    {"Name": "Dai Rios", "Position": "Personnel Lead", "Office": "Edinburgh", "Age": 35, "StartDate": "2012-09-26", "Salary": "$217,500"},
+    {"Name": "Jenette Caldwell", "Position": "Development Lead", "Office": "New York", "Age": 30, "StartDate": "2011-09-03", "Salary": "$345,000"},
+]
+
+@bp.route('/api/get_data', methods=['GET'])
+def get_data():
+    min_age = request.args.get('min_age', type=int, default=0)
+    max_age = request.args.get('max_age', type=int, default=100)
+    
+    filtered_data = [entry for entry in data if min_age <= entry['Age'] <= max_age]
+    return jsonify(filtered_data)
+
diff --git a/app/templates/admin/manage_bookings.html b/app/templates/admin/manage_bookings.html
new file mode 100644
index 0000000000000000000000000000000000000000..2cfb45a4515223289bc0a64506885a3766acebef
--- /dev/null
+++ b/app/templates/admin/manage_bookings.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>DataTables with Flask</title>
+    <link rel="stylesheet" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css">
+    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <!-- Bootstrap CSS -->
+    <style>
+        .table-container {
+            width: 90%;
+            margin: auto;
+            overflow-x: hidden; /* Hide overflow by default */
+        }
+        
+        @media (max-width: 800px) {
+            .table-container {
+                width: 100%;
+                padding: 0 10px;
+                overflow-x: auto; /* Enable horizontal scrolling */
+            }
+        }
+
+        @media (min-width: 801px) {
+            .table-container {
+                overflow-x: hidden; /* Ensure overflow is hidden on larger screens */
+            }
+        }
+
+        /* Ensure DataTable wrapper behaves responsively */
+        .dataTables_wrapper {
+            width: 100%;
+        }
+    </style>
+    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
+    <script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script> <!-- Bootstrap JS including Popper.js -->
+</head>
+<body>
+    <table class="inputs">
+        <tr>
+            <td>Minimum age:</td>
+            <td><input type="text" id="min" name="min"></td>
+        </tr>
+        <tr>
+            <td>Maximum age:</td>
+            <td><input type="text" id="max" name="max"></td>
+        </tr>
+    </table>
+    
+    <div class="table-container">
+        <table id="example" class="table table-striped table-bordered display" style="width:100%"> <!-- Use Bootstrap table classes -->
+            <thead>
+                <tr>
+                    <th>Name</th>
+                    <th>Position</th>
+                    <th>Office</th>
+                    <th>Age</th>
+                    <th>Start date</th>
+                    <th>Salary</th>
+                    <th>Actions</th> <!-- Add Actions column -->
+                </tr>
+            </thead>
+            <tbody>
+            </tbody>
+        </table>
+    </div>
+
+    <script>
+        $(document).ready(function() {
+            const table = $('#example').DataTable({
+                ajax: {
+                    url: "{{ url_for('api.get_data') }}",  // Updated URL
+                    dataSrc: '',
+                    data: function(d) {
+                        d.min_age = $('#min').val();
+                        d.max_age = $('#max').val();
+                    }
+                },
+                columns: [
+                    { data: 'Name' },
+                    { data: 'Position' },
+                    { data: 'Office' },
+                    { data: 'Age' },
+                    { data: 'StartDate' },
+                    { data: 'Salary' },
+                    { // Actions column
+                        data: null,
+                        className: "dt-center",
+                        defaultContent: `
+                            <div class="dropdown">
+                                <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false">
+                                    Actions
+                                </button>
+                                <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                                    <a class="dropdown-item edit-btn" href="#">Edit</a>
+                                    <a class="dropdown-item delete-btn" href="#">Delete</a>
+                                </div>
+                            </div>`
+                    }
+                ]
+            });
+
+            $('#min, #max').on('input', function() {
+                table.ajax.reload();
+            });
+
+            $('#example tbody').on('click', '.edit-btn', function() {
+                const data = table.row($(this).parents('tr')).data();
+                // Handle edit action
+                alert('Edit entry for ' + data.Name);
+            });
+
+            $('#example tbody').on('click', '.delete-btn', function() {
+                const data = table.row($(this).parents('tr')).data();
+                // Handle delete action
+                alert('Delete entry for ' + data.Name);
+            });
+        });
+    </script>
+</body>
+</html>
+  
\ No newline at end of file
diff --git a/migrations/versions/6c7070736062_add_role_id_to_users_table.py b/migrations/versions/6c7070736062_add_role_id_to_users_table.py
index 06ca58762200695b2506fcaba3b348e875d99077..b08cdd25b05f0bd104793ef65c8743f4092a4236 100644
--- a/migrations/versions/6c7070736062_add_role_id_to_users_table.py
+++ b/migrations/versions/6c7070736062_add_role_id_to_users_table.py
@@ -1,7 +1,7 @@
 """Add role_id to users table
 
 Revision ID: 6c7070736062
-Revises: ad8ca3c3dfaa
+Revises: 22de5b143d05
 Create Date: 2025-01-06 20:16:19.191868
 
 """
@@ -11,7 +11,7 @@ from sqlalchemy.dialects import mysql
 
 # revision identifiers, used by Alembic.
 revision = '6c7070736062'
-down_revision = 'ad8ca3c3dfaa'
+down_revision = '22de5b143d05'
 branch_labels = None
 depends_on = None
 
diff --git a/migrations_backup/README b/migrations_backup/README
new file mode 100644
index 0000000000000000000000000000000000000000..0e048441597444a7e2850d6d7c4ce15550f79bda
--- /dev/null
+++ b/migrations_backup/README
@@ -0,0 +1 @@
+Single-database configuration for Flask.
diff --git a/migrations_backup/alembic.ini b/migrations_backup/alembic.ini
new file mode 100644
index 0000000000000000000000000000000000000000..ec9d45c26a6bb54e833fd4e6ce2de29343894f4b
--- /dev/null
+++ b/migrations_backup/alembic.ini
@@ -0,0 +1,50 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic,flask_migrate
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[logger_flask_migrate]
+level = INFO
+handlers =
+qualname = flask_migrate
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/migrations_backup/env.py b/migrations_backup/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c9709271b2ff28271b13c29bba5c50b80fea0ac
--- /dev/null
+++ b/migrations_backup/env.py
@@ -0,0 +1,113 @@
+import logging
+from logging.config import fileConfig
+
+from flask import current_app
+
+from alembic import context
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+
+def get_engine():
+    try:
+        # this works with Flask-SQLAlchemy<3 and Alchemical
+        return current_app.extensions['migrate'].db.get_engine()
+    except (TypeError, AttributeError):
+        # this works with Flask-SQLAlchemy>=3
+        return current_app.extensions['migrate'].db.engine
+
+
+def get_engine_url():
+    try:
+        return get_engine().url.render_as_string(hide_password=False).replace(
+            '%', '%%')
+    except AttributeError:
+        return str(get_engine().url).replace('%', '%%')
+
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+config.set_main_option('sqlalchemy.url', get_engine_url())
+target_db = current_app.extensions['migrate'].db
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def get_metadata():
+    if hasattr(target_db, 'metadatas'):
+        return target_db.metadatas[None]
+    return target_db.metadata
+
+
+def run_migrations_offline():
+    """Run migrations in 'offline' mode.
+
+    This configures the context with just a URL
+    and not an Engine, though an Engine is acceptable
+    here as well.  By skipping the Engine creation
+    we don't even need a DBAPI to be available.
+
+    Calls to context.execute() here emit the given string to the
+    script output.
+
+    """
+    url = config.get_main_option("sqlalchemy.url")
+    context.configure(
+        url=url, target_metadata=get_metadata(), literal_binds=True
+    )
+
+    with context.begin_transaction():
+        context.run_migrations()
+
+
+def run_migrations_online():
+    """Run migrations in 'online' mode.
+
+    In this scenario we need to create an Engine
+    and associate a connection with the context.
+
+    """
+
+    # this callback is used to prevent an auto-migration from being generated
+    # when there are no changes to the schema
+    # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
+    def process_revision_directives(context, revision, directives):
+        if getattr(config.cmd_opts, 'autogenerate', False):
+            script = directives[0]
+            if script.upgrade_ops.is_empty():
+                directives[:] = []
+                logger.info('No changes in schema detected.')
+
+    conf_args = current_app.extensions['migrate'].configure_args
+    if conf_args.get("process_revision_directives") is None:
+        conf_args["process_revision_directives"] = process_revision_directives
+
+    connectable = get_engine()
+
+    with connectable.connect() as connection:
+        context.configure(
+            connection=connection,
+            target_metadata=get_metadata(),
+            **conf_args
+        )
+
+        with context.begin_transaction():
+            context.run_migrations()
+
+
+if context.is_offline_mode():
+    run_migrations_offline()
+else:
+    run_migrations_online()
diff --git a/migrations_backup/script.py.mako b/migrations_backup/script.py.mako
new file mode 100644
index 0000000000000000000000000000000000000000..2c0156303a8df3ffdc9de87765bf801bf6bea4a5
--- /dev/null
+++ b/migrations_backup/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+    ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+    ${downgrades if downgrades else "pass"}
diff --git a/migrations_backup/versions/22de5b143d05_create_user_roles.py b/migrations_backup/versions/22de5b143d05_create_user_roles.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e6a5869f16c6b4644fa9222896cee3d46486009
--- /dev/null
+++ b/migrations_backup/versions/22de5b143d05_create_user_roles.py
@@ -0,0 +1,34 @@
+"""Create user roles
+
+Revision ID: 22de5b143d05
+Revises: 9a8cc1906445
+Create Date: 2025-01-06 13:40:11.307880
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import table, column
+
+# revision identifiers, used by Alembic.
+revision = '22de5b143d05'
+down_revision = '9a8cc1906445'
+branch_labels = None
+depends_on = None
+
+roles_table = table('roles',
+    column('id', sa.Integer),
+    column('name', sa.String),
+    column('description', sa.String)
+)
+
+def upgrade():
+    roles = [
+        {'name': 'super-admin', 'description': 'Super Admin, all admin perms and can create new admins'},
+        {'name': 'admin', 'description': 'Can create/delete and modify bookings'},
+        {'name': 'user', 'description': 'Standard user'}
+    ]
+
+    op.bulk_insert(roles_table, roles)
+
+def downgrade():
+    op.execute('DELETE FROM roles WHERE name IN ("super-admin", "admin", "user")')
diff --git a/migrations_backup/versions/489bab9aaf4f_add_listing_images_table.py b/migrations_backup/versions/489bab9aaf4f_add_listing_images_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc5cb29abb12b34fcdfa29903271b8e2f7f59e8c
--- /dev/null
+++ b/migrations_backup/versions/489bab9aaf4f_add_listing_images_table.py
@@ -0,0 +1,26 @@
+"""Add listing images table
+
+Revision ID: 489bab9aaf4f
+Revises: 6791cbf31235
+Create Date: 2024-11-05 11:13:50.215159
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '489bab9aaf4f'
+down_revision = '6791cbf31235'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+       op.create_table(
+              'listing_images',
+              sa.Column('id', sa.Integer(), nullable=False, autoincrement=True, primary_key=True),
+              sa.Column('listing_id', sa.Integer(), nullable=False),
+              sa.Column('image_location', sa.String(255), nullable=False),
+              sa.Column('image_description', sa.String(255), nullable=True)
+       )
diff --git a/migrations_backup/versions/6791cbf31235_add_listing_table.py b/migrations_backup/versions/6791cbf31235_add_listing_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb5beb74f5b8be4595388be5d89f65568bc1d345
--- /dev/null
+++ b/migrations_backup/versions/6791cbf31235_add_listing_table.py
@@ -0,0 +1,31 @@
+"""Add listing table
+
+Revision ID: 6791cbf31235
+Revises: ac9d4555724d
+Create Date: 2024-11-05 10:36:32.872815
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '6791cbf31235'
+down_revision = 'ac9d4555724d'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+       op.create_table(
+              'listings',
+              sa.Column('id', sa.Integer(), nullable=False, autoincrement=True, primary_key=True),
+              sa.Column('depart_location', sa.String(255), nullable=False),
+              sa.Column('depart_time', sa.DateTime(), nullable=False),
+              sa.Column('destination_location', sa.String(255), nullable=False),
+              sa.Column('destination_time', sa.DateTime(), nullable=False),
+              sa.Column('fair_cost', sa.Float(2), nullable=False),
+              sa.Column('transport_type', sa.String(255), nullable=False),
+              sa.Column('business_tickets', sa.Integer(), nullable=False),
+              sa.Column('economy_tickets', sa.Integer(), nullable=False)
+       )
\ No newline at end of file
diff --git a/migrations_backup/versions/68d89ef13132_add_main_listing_image_column.py b/migrations_backup/versions/68d89ef13132_add_main_listing_image_column.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ccafd816ab4ff5c783ae3c73477b67d5737efa0
--- /dev/null
+++ b/migrations_backup/versions/68d89ef13132_add_main_listing_image_column.py
@@ -0,0 +1,23 @@
+"""Add main Listing Image Column
+
+Revision ID: 68d89ef13132
+Revises: 489bab9aaf4f
+Create Date: 2024-11-29 10:29:38.126811
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = '68d89ef13132'
+down_revision = '489bab9aaf4f'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+        op.add_column(
+        'listing_images',
+        sa.Column('main_image', sa.Boolean(), nullable=False, server_default=sa.sql.expression.false())
+    )
\ No newline at end of file
diff --git a/migrations_backup/versions/6c7070736062_add_role_id_to_users_table.py b/migrations_backup/versions/6c7070736062_add_role_id_to_users_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..b08cdd25b05f0bd104793ef65c8743f4092a4236
--- /dev/null
+++ b/migrations_backup/versions/6c7070736062_add_role_id_to_users_table.py
@@ -0,0 +1,26 @@
+"""Add role_id to users table
+
+Revision ID: 6c7070736062
+Revises: 22de5b143d05
+Create Date: 2025-01-06 20:16:19.191868
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = '6c7070736062'
+down_revision = '22de5b143d05'
+branch_labels = None
+depends_on = None
+
+def upgrade():
+    # Add column role_id to users table
+    op.add_column('users', sa.Column('role_id', sa.Integer(), nullable=True))
+    op.create_foreign_key(None, 'users', 'roles', ['role_id'], ['id'])
+
+def downgrade():
+    # Remove column role_id from users table
+    op.drop_constraint(None, 'users', type_='foreignkey')
+    op.drop_column('users', 'role_id')
diff --git a/migrations_backup/versions/9a8cc1906445_add_fs_uniquifier_field_to_user_model.py b/migrations_backup/versions/9a8cc1906445_add_fs_uniquifier_field_to_user_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bacfee528a6aff984243944fedccc2f36974923
--- /dev/null
+++ b/migrations_backup/versions/9a8cc1906445_add_fs_uniquifier_field_to_user_model.py
@@ -0,0 +1,89 @@
+"""Add fs_uniquifier field to User model
+
+Revision ID: 9a8cc1906445
+Revises: 68d89ef13132
+Create Date: 2025-01-06 12:52:57.272220
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+import os
+
+# revision identifiers, used by Alembic.
+revision = '9a8cc1906445'
+down_revision = '68d89ef13132'
+branch_labels = None
+depends_on = None
+
+def column_exists(table_name, column_name):
+    inspector = sa.inspect(op.get_bind())
+    return column_name in [col['name'] for col in inspector.get_columns(table_name)]
+
+def index_exists(table_name, index_name):
+    inspector = sa.inspect(op.get_bind())
+    indexes = inspector.get_indexes(table_name)
+    return any(index['name'] == index_name for index in indexes)
+
+def upgrade():
+    # Conditionally create roles table
+    if not op.get_bind().dialect.has_table(op.get_bind(), "roles"):
+        op.create_table('roles',
+            sa.Column('id', sa.Integer(), nullable=False),
+            sa.Column('name', sa.String(length=80), nullable=True),
+            sa.Column('description', sa.String(length=255), nullable=True),
+            sa.PrimaryKeyConstraint('id'),
+            sa.UniqueConstraint('name')
+        )
+    
+    # Conditionally create roles_users table
+    if not op.get_bind().dialect.has_table(op.get_bind(), "roles_users"):
+        op.create_table('roles_users',
+            sa.Column('user_id', sa.Integer(), nullable=True),
+            sa.Column('role_id', sa.Integer(), nullable=True),
+            sa.ForeignKeyConstraint(['role_id'], ['roles.id']),
+            sa.ForeignKeyConstraint(['user_id'], ['users.id'])
+        )
+    
+    with op.batch_alter_table('listing_images', schema=None) as batch_op:
+        batch_op.alter_column('main_image',
+               existing_type=mysql.TINYINT(display_width=1),
+               type_=sa.SmallInteger(),
+               existing_nullable=False,
+               existing_server_default=sa.text("'0'"))
+
+    # Assign unique values to fs_uniquifier for existing users before adding the unique constraint
+    conn = op.get_bind()
+    users = conn.execute(sa.text("SELECT id FROM users WHERE fs_uniquifier IS NULL OR fs_uniquifier = ''")).fetchall()
+    for user in users:
+        conn.execute(sa.text("UPDATE users SET fs_uniquifier = :fs_uniquifier WHERE id = :id"), {'fs_uniquifier': os.urandom(32).hex(), 'id': user.id})
+
+    with op.batch_alter_table('users', schema=None) as batch_op:
+        if index_exists('users', 'api_token'):
+            batch_op.drop_index('api_token')
+        batch_op.create_unique_constraint(None, ['fs_uniquifier'])
+        if column_exists('users', 'token_expiry'):
+            batch_op.drop_column('token_expiry')
+        if column_exists('users', 'api_token'):
+            batch_op.drop_column('api_token')
+        if column_exists('users', 'role_id'):
+            batch_op.drop_column('role_id')
+
+def downgrade():
+    with op.batch_alter_table('users', schema=None) as batch_op:
+        batch_op.add_column(sa.Column('role_id', mysql.SMALLINT(), server_default=sa.text("'1'"), autoincrement=False, nullable=False))
+        batch_op.add_column(sa.Column('api_token', mysql.VARCHAR(length=255), nullable=True))
+        batch_op.add_column(sa.Column('token_expiry', mysql.DATETIME(), nullable=True))
+        batch_op.drop_constraint(None, type_='unique')
+        batch_op.create_index('api_token', ['api_token'], unique=True)
+        batch_op.drop_column('fs_uniquifier')
+
+    with op.batch_alter_table('listing_images', schema=None) as batch_op:
+        batch_op.alter_column('main_image',
+               existing_type=sa.SmallInteger(),
+               type_=mysql.TINYINT(display_width=1),
+               existing_nullable=False,
+               existing_server_default=sa.text("'0'"))
+
+    op.drop_table('roles_users')
+    op.drop_table('roles')
diff --git a/migrations_backup/versions/ac9d4555724d_add_api_token_and_expiry.py b/migrations_backup/versions/ac9d4555724d_add_api_token_and_expiry.py
new file mode 100644
index 0000000000000000000000000000000000000000..59f4c9a590293c9ba96b7d83bef1228cdce3a182
--- /dev/null
+++ b/migrations_backup/versions/ac9d4555724d_add_api_token_and_expiry.py
@@ -0,0 +1,27 @@
+"""Add api token and expiry
+
+Revision ID: ac9d4555724d
+Revises: 
+Create Date: 2024-11-01 10:56:05.827705
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = 'ac9d4555724d'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+       op.create_table(
+              'users',
+              sa.Column('id', sa.Integer(), nullable=False, autoincrement=True, primary_key=True),
+              sa.Column('username', sa.String(255), nullable=False, unique=True),
+              sa.Column('email', sa.String(255), nullable=False, unique=True),
+              sa.Column('password', sa.String(255), nullable=False),
+              sa.Column('role_id', sa.SmallInteger(), nullable=False, server_default='3')
+       )
\ No newline at end of file