diff --git a/.env-example b/.env-example index 40a28575a66a1e42d5506e5f7ebd18692c063c0c..20e7d468a01ae37b9db7a56bab6d4dbdbd91a869 100644 --- a/.env-example +++ b/.env-example @@ -3,5 +3,5 @@ DATABASE_HOST=database #If using pydebug you will need to use 127.0.0.1 DATABASE_USER=user DATABASE_PASSWORD=password1 -DATABASE_NAME=database +DATABASE_NAME=mydatabase SECRET_KEY=fsodfhiosdfhdsiofh34903urwej09jf diff --git a/app/admin/routes.py b/app/admin/routes.py index 3ef6e8dd3b0923714a25f91f134ff627f77623ce..42e37e3964b313a30644a6208480503b752adaf1 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -1,7 +1,7 @@ from flask import render_template, redirect, url_for, request, jsonify, flash from app import db from app import admin_permission, permission_required, super_admin_permission -from app.models import Listings, ListingImages +from app.models import Listings, ListingImages, User from app.admin import bp from app.main.utils import generate_time_options @@ -42,11 +42,20 @@ def edit_booking(id): destination_time_str=destination_time_str ) +@bp.route('/manage_users/edit/<int:id>') +@permission_required(super_admin_permission) +def edit_user(id): + user = User.search_user_id(id) + + return render_template( + 'admin/edit_user.html', + user=user + ) @bp.route('/manage_users') @permission_required(super_admin_permission) def manage_users(): - return render_template('admin/index.html') + return render_template('admin/manage_users.html') @bp.route('/manage_user_bookings') @permission_required(admin_permission) @@ -155,6 +164,23 @@ def get_bookings(): return jsonify(result) + +@bp.route('get_users', methods=['GET']) +@permission_required(super_admin_permission) +def get_users(): + all_users = User.get_all_users() + + result = [ + { + 'id': user.id, + 'username': user.username, + 'email': user.email, + 'role': user.role.name, + } for user in all_users + ] + + return jsonify(result) + @bp.route('create_listing', methods=['GET']) @permission_required(admin_permission) def create_listing(): @@ -230,6 +256,18 @@ def delete_booking(): return jsonify(success), http_code +@bp.route('delete_user', methods=['DELETE']) +@permission_required(admin_permission) +def delete_user(): + http_code = 400 + user_id = request.form.get('id') + success = User.delete_user(user_id) + + if success: + http_code = 200 + + return jsonify(success), http_code + @bp.route('/delete_image/<int:image_id>', methods=['POST']) @permission_required(admin_permission) def delete_image(image_id): diff --git a/app/models/user.py b/app/models/user.py index c0b838ad0893f17f0ce3f45d8345165c74da3d23..ae88eb788f1511db5027d84a7b6c6bde3764d882 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -56,3 +56,19 @@ class User(UserMixin, db.Model): if user and user.role: return user.role.name return None + + @classmethod + def get_all_users(cls): + return cls.query.all() + + @classmethod + def delete_user(cls, user_id = None): + + user = cls.search_user_id(user_id) + + if user: + db.session.delete(user) + db.session.commit() + return True + + return False diff --git a/app/profile/routes.py b/app/profile/routes.py index 3479ff078d87a794ed60752a5d8d8ccc6059be28..70ac51dfd85b6b78e8185fb90e6f43225312996d 100644 --- a/app/profile/routes.py +++ b/app/profile/routes.py @@ -40,9 +40,9 @@ def signup(): login_user(new_user) - if session['callback']: - flash("Account successfully created. Please review your booking before continuing", 'success') + if 'callback' in session.keys(): callback = session.pop('callback') + flash("Account successfully created. Please review your booking before continuing", 'success') return redirect(callback) flash('Successfully created your account. You have been logged in automatically', 'success') @@ -74,8 +74,6 @@ def is_valid_username(username): return all(c.isalnum() or c in allowed_special_chars for c in username) -from flask_principal import Identity, identity_changed - @bp.route('/login', methods=['POST']) def login_post(): username_field = request.form.get('username') @@ -95,10 +93,10 @@ def login_post(): identity_changed.send(current_app._get_current_object(), identity=Identity(user.id)) - if session['callback']: - callback = session.pop('callback') - flash("You have been successfully logged in. Please review your booking before continuing", 'success') - return redirect(callback) + if 'callback' in session.keys(): + callback = session.pop('callback') + flash("You have been successfully logged in. Please review your booking before continuing", 'success') + return redirect(callback) return redirect(url_for('profile.index')) diff --git a/app/templates/admin/edit_user.html b/app/templates/admin/edit_user.html new file mode 100644 index 0000000000000000000000000000000000000000..52c83b35b79137290a915b8a388696a3ff9e56d4 --- /dev/null +++ b/app/templates/admin/edit_user.html @@ -0,0 +1,89 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container mt-5"> + <div class="card mb-4"> + <div class="card-body"> + <h2 class="card-title">User Information</h2> + <p><strong>User Name:</strong> + <span id="userName">{{user.username}}</span> + <button class="btn btn-primary" onclick="editField('userName', this)">Update</button> + </p> + <p><strong>Email:</strong> + <span id="userEmail">{{user.email}}</span> + <button class="btn btn-primary" onclick="editField('userEmail', this)">Update</button> + </p> + <p><strong>Role:</strong> + <span id="userRole">{{user.role.name}}</span> + <button class="btn btn-primary" onclick="editField('userRole', this)">Update</button> + </p> + </div> + </div> +</div> +<div class="text-center"> + <button class="btn btn-secondary btn-lg"> + Save Changes + </button> +</div> + +<style> + .input-container { + display: flex; + align-items: center; + } + .input-container input { + flex: 1; + max-width: 500px; + } + .input-container button { + margin-left: 10px; + } +</style> + +<script> +function editField(fieldId, button) { + const field = document.getElementById(fieldId); + const originalValue = field.textContent; + + const input = document.createElement("input"); + input.type = "text"; + input.value = originalValue; + input.classList.add("form-control"); + input.autofocus = true; + + const container = document.createElement("div"); + container.classList.add("input-container"); + container.appendChild(input); + + field.parentNode.replaceChild(container, field); + + input.onblur = function() { + if (!container.contains(input)) return; + saveField(input, fieldId, button); + }; + + button.innerText = 'Apply'; + button.onclick = function() { + saveField(input, fieldId, button); + }; + + container.appendChild(button); +} + +function saveField(input, fieldId, button) +{ + const newValue = input.value; + + const newSpan = document.createElement("span"); + newSpan.id = fieldId; + newSpan.textContent = newValue; + + const container = input.parentNode; + container.parentNode.replaceChild(newSpan, container); + + button.innerText = 'Update'; + button.onclick = editField.bind(null, fieldId, button); + + newSpan.parentNode.appendChild(button); +} +</script> +{% endblock %} diff --git a/app/templates/admin/manage_bookings.html b/app/templates/admin/manage_bookings.html index e3c686a2f8d820f30bb4a5eb9365ad49503b4c5e..25731603706cb737d62f9c79fc6c207cc31169fb 100644 --- a/app/templates/admin/manage_bookings.html +++ b/app/templates/admin/manage_bookings.html @@ -85,7 +85,6 @@ </tbody> </table> </div> - <!-- Include this modal in your HTML code --> <div class="modal fade" id="confirm_booking_deletion" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content"> diff --git a/app/templates/admin/manage_users.html b/app/templates/admin/manage_users.html new file mode 100644 index 0000000000000000000000000000000000000000..0b96873954fb041b4f7586fc5e6c4827c1443dd9 --- /dev/null +++ b/app/templates/admin/manage_users.html @@ -0,0 +1,148 @@ +{% extends 'base.html' %} +{% block content %} +<div class="container my-4"> + <div class="d-flex justify-content-between mb-3"> + <h2>Manage Bookings</h2> + </div> + <!-- Manage bookings table starts --> + <div class="table-container"> + <table id="manage_bookings" class="table table-striped table-bordered display hover" style="width:100%"> + <thead> + <tr> + <th>Id</th> + <th>User Name</th> + <th>Email</th> + <th>Role</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + </tbody> + </table> + </div> +</div> +<div class="modal fade" id="confirm_user_deletion" tabindex="-1"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="confirm_user_deletion">Confirm Deletion</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + <p>Type 'CONFIRM' to delete this entry:</p> + <input type="text" id="conifrmation_input" class="form-control"> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-primary" id="confirm_deletion_button">Delete</button> + </div> + </div> + </div> +</div> +<style> + .table-container { + width: 100%; + overflow-x: auto; + overflow-y: hidden; + } + + @media (max-width: 800px) { + .table-container { + padding: 0 10px; + } + } + + .dataTables_wrapper { + width: 100%; + } + + table.dataTable.no-footer { + margin-bottom: 30px; + } + +</style> +<script> + $(document).ready(function() { + // Load table + const table = $('#manage_bookings').DataTable({ + pageLength: 10, + ordering: false, + ajax: { + url: "{{ url_for('admin.get_users') }}", + dataSrc: '', + data: function(d) { + d.username = $('#username').val(); + d.email = $('#email').val(); + d.role_id = $('#role').val(); + } + }, + columns: [ + { data: 'id', visible: false }, // Hidden id column + { data: 'username' }, + { data: 'email' }, + { data: 'role' }, + { + data: null, + className: "dt-center", + defaultContent: ` + <div class="dropdown"> + <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-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>` + } + ], + language: { + emptyTable: "No users could be found" + }, + createdRow: function(row, data, dataIndex) { //Attach ID to edit button and delete button. + $(row).find('.edit-btn').attr('data-id', data.id); + $(row).find('.delete-btn').attr('data-id', data.id); + } + }); + + $('#manage_bookings tbody').on('click', '.edit-btn', function() { + const id = $(this).data('id'); + window.location.href = `manage_users/edit/${id}`; + }); + + let delete_user; + $('#manage_bookings tbody').on('click', '.delete-btn', function() { + delete_user = table.row($(this).parents('tr')); + $('#confirm_user_deletion').modal('show'); + }); + + $('#confirm_deletion_button').on('click', function() { + const confirmation_input = $('#conifrmation_input').val().trim(); + if (confirmation_input === 'CONFIRM') { + $.ajax({ + url: "{{ url_for('admin.delete_user') }}", + method: "DELETE", + data: { id: delete_user.data().id }, + success: function() { + delete_user.remove().draw(); + $('#confirm_user_deletion').modal('hide'); + }, + error: function() { + alert('Failed to delete the booking. Please try again.'); + } + }); + } else { + alert('Please type "CONFIRM" to delete the booking.'); + } + }); + + $('#confirm_user_deletion').on('hidden.bs.modal', function () { + $('#conifrmation_input').val(''); + }); + }); +</script> + + + + +{% endblock %}