Updated online detection and fixed bugs
Updated online detection and fixed bugs
This commit is contained in:
parent
0ac1f2795b
commit
bc9fcda6aa
17 changed files with 342 additions and 169 deletions
203
app.py
203
app.py
|
@ -1,13 +1,13 @@
|
||||||
from PIL import Image # Import the Image module from Pillow
|
from PIL import Image
|
||||||
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response
|
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from cryptography.fernet import Fernet, InvalidToken
|
from cryptography.fernet import Fernet, InvalidToken
|
||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from werkzeug.utils import secure_filename # Add this line
|
from werkzeug.utils import secure_filename
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
|
||||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||||
import logging
|
import logging
|
||||||
import jwt
|
import jwt
|
||||||
|
@ -67,6 +67,7 @@ class User(db.Model):
|
||||||
is_admin = db.Column(db.Boolean, nullable=False, default=False)
|
is_admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
disabled = db.Column(db.Boolean, nullable=False, default=False)
|
disabled = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
profile_picture = db.Column(db.String(150), nullable=True)
|
profile_picture = db.Column(db.String(150), nullable=True)
|
||||||
|
custom_css = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
class Message(db.Model):
|
class Message(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -124,18 +125,22 @@ class GroupMessage(db.Model):
|
||||||
group = db.relationship('Group', foreign_keys=[group_id])
|
group = db.relationship('Group', foreign_keys=[group_id])
|
||||||
sender = db.relationship('User', foreign_keys=[sender_id])
|
sender = db.relationship('User', foreign_keys=[sender_id])
|
||||||
|
|
||||||
|
def generate_salt():
|
||||||
|
return os.urandom(16).hex()
|
||||||
|
|
||||||
|
def sha256_password_hash(password, salt=None):
|
||||||
|
if salt is None:
|
||||||
|
salt = generate_salt()
|
||||||
|
hash = hashlib.sha256((salt + password).encode()).hexdigest()
|
||||||
|
return f"{salt}${hash}"
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
token = request.cookies.get('token')
|
if 'user_id' in session:
|
||||||
if token:
|
user_id = session['user_id']
|
||||||
user_id = decode_token(token)
|
user = User.query.get(user_id)
|
||||||
if user_id:
|
if user:
|
||||||
user = User.query.get(user_id)
|
return redirect(url_for('dashboard'))
|
||||||
if user:
|
|
||||||
session['username'] = user.username
|
|
||||||
session['user_id'] = user.id
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@app.route('/register', methods=['GET', 'POST'])
|
@app.route('/register', methods=['GET', 'POST'])
|
||||||
|
@ -143,7 +148,7 @@ def register():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
username = request.form['username'].lower()
|
username = request.form['username'].lower()
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
hashed_password = generate_password_hash(password) # Default method
|
hashed_password = sha256_password_hash(password)
|
||||||
new_user = User(username=username, password=hashed_password)
|
new_user = User(username=username, password=hashed_password)
|
||||||
try:
|
try:
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
|
@ -165,10 +170,11 @@ def login():
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
user = User.query.filter_by(username=username).first()
|
user = User.query.filter_by(username=username).first()
|
||||||
if user:
|
if user:
|
||||||
|
salt, stored_hash = user.password.split('$')
|
||||||
if user.disabled:
|
if user.disabled:
|
||||||
logger.warning(f"Disabled account login attempt: {username}")
|
logger.warning(f"Disabled account login attempt: {username}")
|
||||||
return jsonify({'status': 'error', 'message': 'Account Disabled. Contact Support'})
|
return jsonify({'status': 'error', 'message': 'Account Disabled. Contact Support'})
|
||||||
if check_password_hash(user.password, password):
|
if stored_hash == hashlib.sha256((salt + password).encode()).hexdigest():
|
||||||
session['username'] = user.username
|
session['username'] = user.username
|
||||||
session['user_id'] = user.id
|
session['user_id'] = user.id
|
||||||
session['is_admin'] = user.is_admin
|
session['is_admin'] = user.is_admin
|
||||||
|
@ -182,28 +188,25 @@ def login():
|
||||||
return jsonify({'status': 'error', 'message': 'Invalid credentials. Please try again.'})
|
return jsonify({'status': 'error', 'message': 'Invalid credentials. Please try again.'})
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/change_password', methods=['GET', 'POST'])
|
@app.route('/change_password', methods=['GET', 'POST'])
|
||||||
def change_password():
|
def change_password():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
username = request.form['username'].lower()
|
username = request.form['username'].lower()
|
||||||
current_password = request.form['current_password']
|
current_password = request.form['current_password']
|
||||||
new_password = request.form['new_password']
|
new_password = request.form['new_password']
|
||||||
|
|
||||||
user = User.query.filter_by(username=username).first()
|
user = User.query.filter_by(username=username).first()
|
||||||
if user and check_password_hash(user.password, current_password):
|
if user:
|
||||||
hashed_new_password = generate_password_hash(new_password)
|
current_salt, current_hash = user.password.split('$')
|
||||||
user.password = hashed_new_password
|
if current_hash == hashlib.sha256((current_salt + current_password).encode()).hexdigest():
|
||||||
db.session.commit()
|
new_hashed_password = sha256_password_hash(new_password)
|
||||||
logger.info(f"Password changed for user: {username}")
|
user.password = new_hashed_password
|
||||||
return jsonify({'status': 'Password changed successfully'})
|
db.session.commit()
|
||||||
|
logger.info(f"Password changed for user: {username}")
|
||||||
|
return jsonify({'status': 'Password changed successfully'})
|
||||||
logger.warning(f"Password change failed for user: {username}")
|
logger.warning(f"Password change failed for user: {username}")
|
||||||
return jsonify({'status': 'error'})
|
return jsonify({'status': 'error'})
|
||||||
|
|
||||||
return render_template('change_password.html')
|
return render_template('change_password.html')
|
||||||
|
|
||||||
|
|
||||||
# Update your admin check accordingly
|
|
||||||
@app.route('/admin', methods=['GET'])
|
@app.route('/admin', methods=['GET'])
|
||||||
def admin_panel():
|
def admin_panel():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
|
@ -217,22 +220,20 @@ def admin_panel():
|
||||||
def admin_change_password():
|
def admin_change_password():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
admin_user = User.query.filter_by(username=session['username']).first()
|
admin_user = User.query.filter_by(username=session['username']).first()
|
||||||
logger.debug(f"Admin User: {admin_user.username}, Is Admin: {admin_user.is_admin}")
|
if admin_user and admin_user.is_admin:
|
||||||
if admin_user and admin_user.is_admin: # Check if the user is an admin
|
|
||||||
try:
|
try:
|
||||||
data = request.get_json() # Parse the JSON data from the request
|
data = request.get_json()
|
||||||
user_id = data['user_id']
|
user_id = data['user_id']
|
||||||
new_password = data['new_password']
|
new_password = data['new_password']
|
||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
if user:
|
if user:
|
||||||
user.password = generate_password_hash(new_password)
|
user.password = sha256_password_hash(new_password)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({'status': 'success'})
|
return jsonify({'status': 'success'})
|
||||||
return jsonify({'status': 'user not found'}), 404
|
return jsonify({'status': 'user not found'}), 404
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error changing password: {e}")
|
logger.error(f"Error changing password: {e}")
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
logger.warning(f"Unauthorized attempt to access admin change password by user {session.get('username', 'unknown')}")
|
|
||||||
return jsonify({'status': 'error'}), 401
|
return jsonify({'status': 'error'}), 401
|
||||||
|
|
||||||
@app.route('/admin/disable_account', methods=['POST'])
|
@app.route('/admin/disable_account', methods=['POST'])
|
||||||
|
@ -250,7 +251,6 @@ def admin_disable_account():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error disabling account: {e}")
|
logger.error(f"Error disabling account: {e}")
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
logger.warning(f"Unauthorized attempt to disable account by user {session.get('username', 'unknown')}")
|
|
||||||
return jsonify({'status': 'error'}), 401
|
return jsonify({'status': 'error'}), 401
|
||||||
|
|
||||||
@app.route('/admin/enable_account', methods=['POST'])
|
@app.route('/admin/enable_account', methods=['POST'])
|
||||||
|
@ -268,7 +268,6 @@ def admin_enable_account():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error enabling account: {e}")
|
logger.error(f"Error enabling account: {e}")
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
logger.warning(f"Unauthorized attempt to enable account by user {session.get('username', 'unknown')}")
|
|
||||||
return jsonify({'status': 'error'}), 401
|
return jsonify({'status': 'error'}), 401
|
||||||
|
|
||||||
@app.route('/dashboard')
|
@app.route('/dashboard')
|
||||||
|
@ -281,9 +280,8 @@ def dashboard():
|
||||||
|
|
||||||
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
|
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
|
||||||
pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all()
|
pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all()
|
||||||
messages = Message.query.filter_by(receiver_id=user.id).all() # Updated line
|
messages = Message.query.filter_by(receiver_id=user.id).all()
|
||||||
|
|
||||||
# Fetch groups for the user
|
|
||||||
group_memberships = GroupMember.query.filter_by(user_id=user.id).all()
|
group_memberships = GroupMember.query.filter_by(user_id=user.id).all()
|
||||||
group_ids = [membership.group_id for membership in group_memberships]
|
group_ids = [membership.group_id for membership in group_memberships]
|
||||||
groups = Group.query.filter(Group.id.in_(group_ids)).all()
|
groups = Group.query.filter(Group.id.in_(group_ids)).all()
|
||||||
|
@ -315,13 +313,12 @@ def chat(friend_id):
|
||||||
friend_user = User.query.filter_by(id=friend_id).first()
|
friend_user = User.query.filter_by(id=friend_id).first()
|
||||||
user = User.query.filter_by(id=current_user_id).first()
|
user = User.query.filter_by(id=current_user_id).first()
|
||||||
friends = Friend.query.filter_by(user_id=user.id).all()
|
friends = Friend.query.filter_by(user_id=user.id).all()
|
||||||
profile_picture = user.profile_picture # Assuming this is the path to the profile picture
|
profile_picture = user.profile_picture
|
||||||
friend_ids = [f.friend_id for f in friends]
|
friend_ids = [f.friend_id for f in friends]
|
||||||
friend_users = User.query.filter(User.id.in_(friend_ids)).all()
|
friend_users = User.query.filter(User.id.in_(friend_ids)).all()
|
||||||
|
|
||||||
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
|
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
|
||||||
|
|
||||||
# Fetch groups for the user
|
|
||||||
group_memberships = GroupMember.query.filter_by(user_id=user.id).all()
|
group_memberships = GroupMember.query.filter_by(user_id=user.id).all()
|
||||||
group_ids = [membership.group_id for membership in group_memberships]
|
group_ids = [membership.group_id for membership in group_memberships]
|
||||||
groups = Group.query.filter(Group.id.in_(group_ids)).all()
|
groups = Group.query.filter(Group.id.in_(group_ids)).all()
|
||||||
|
@ -366,9 +363,9 @@ def send_message(receiver):
|
||||||
decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content
|
decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content
|
||||||
socketio.emit('new_message', {
|
socketio.emit('new_message', {
|
||||||
'sender': session['username'],
|
'sender': session['username'],
|
||||||
'receiver': receiver_user.username, # Include the receiver information
|
'receiver': receiver_user.username,
|
||||||
'content': decrypted_content,
|
'content': decrypted_content,
|
||||||
'sender_profile_picture': sender_user.profile_picture, # Ensure this is correct
|
'sender_profile_picture': sender_user.profile_picture,
|
||||||
'content_type': content_type,
|
'content_type': content_type,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'id': new_message.id
|
'id': new_message.id
|
||||||
|
@ -380,7 +377,6 @@ def send_message(receiver):
|
||||||
db.session.add(pending_message)
|
db.session.add(pending_message)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
logger.info(f"Pending message from {session['username']} to {receiver_user.username}")
|
logger.info(f"Pending message from {session['username']} to {receiver_user.username}")
|
||||||
|
|
||||||
return jsonify({'status': 'Pending message sent', 'message_id': pending_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200
|
return jsonify({'status': 'Pending message sent', 'message_id': pending_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200
|
||||||
return jsonify({'error': 'No content or file provided'}), 400
|
return jsonify({'error': 'No content or file provided'}), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -430,8 +426,8 @@ def compress_image(image_path, quality=15):
|
||||||
if img.format == 'PNG':
|
if img.format == 'PNG':
|
||||||
img.save(image_path, "PNG", optimize=True)
|
img.save(image_path, "PNG", optimize=True)
|
||||||
else:
|
else:
|
||||||
img = img.convert("RGB") # Ensure the image is in RGB mode for non-PNG files
|
img = img.convert("RGB")
|
||||||
img.save(image_path, "JPEG", quality=quality) # Save with JPEG format and compression
|
img.save(image_path, "JPEG", quality=quality)
|
||||||
|
|
||||||
@app.route('/upload_profile_picture', methods=['POST'])
|
@app.route('/upload_profile_picture', methods=['POST'])
|
||||||
def upload_profile_picture():
|
def upload_profile_picture():
|
||||||
|
@ -446,15 +442,12 @@ def upload_profile_picture():
|
||||||
filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}"
|
filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}"
|
||||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
|
|
||||||
# Compress the image
|
|
||||||
compress_image(file_path, quality=85)
|
compress_image(file_path, quality=85)
|
||||||
|
|
||||||
user.profile_picture = filename
|
user.profile_picture = filename
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
logger.info(f"User {session['username']} uploaded a new profile picture: {filename}")
|
logger.info(f"User {session['username']} uploaded a new profile picture: {filename}")
|
||||||
return redirect(url_for('dashboard')) # Redirect to a valid endpoint
|
return redirect(url_for('dashboard'))
|
||||||
return redirect(url_for('dashboard')) # Redirect to a valid endpoint if not logged in
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
@app.route('/cdn/<filename>')
|
@app.route('/cdn/<filename>')
|
||||||
def uploaded_file(filename):
|
def uploaded_file(filename):
|
||||||
|
@ -463,23 +456,19 @@ def uploaded_file(filename):
|
||||||
@app.route('/add_friend', methods=['POST'])
|
@app.route('/add_friend', methods=['POST'])
|
||||||
def add_friend():
|
def add_friend():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
friend_username = request.form['friend_username'].lower() # Convert to lowercase
|
friend_username = request.form['friend_username'].lower()
|
||||||
user = User.query.filter_by(username=session['username']).first()
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
friend = User.query.filter_by(username=friend_username).first()
|
friend = User.query.filter_by(username=friend_username).first()
|
||||||
if friend and user != friend:
|
if friend and user != friend:
|
||||||
# Check if friend request already exists
|
|
||||||
existing_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend.id).first()
|
existing_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend.id).first()
|
||||||
if existing_request:
|
if existing_request:
|
||||||
return jsonify({'status': 'Friend request already sent'})
|
return jsonify({'status': 'Friend request already sent'})
|
||||||
|
|
||||||
# Check if already friends
|
|
||||||
existing_friend = Friend.query.filter(
|
existing_friend = Friend.query.filter(
|
||||||
((Friend.user_id == user.id) & (Friend.friend_id == friend.id)) |
|
((Friend.user_id == user.id) & (Friend.friend_id == friend.id)) |
|
||||||
((Friend.user_id == friend.id) & (Friend.friend_id == user.id))
|
((Friend.user_id == friend.id) & (Friend.friend_id == user.id))
|
||||||
).first()
|
).first()
|
||||||
if existing_friend:
|
if existing_friend:
|
||||||
return jsonify({'status': 'Already friends'})
|
return jsonify({'status': 'Already friends'})
|
||||||
|
|
||||||
friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id)
|
friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id)
|
||||||
db.session.add(friend_request)
|
db.session.add(friend_request)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -493,7 +482,6 @@ def add_friend():
|
||||||
return jsonify({'status': 'Friend not found or cannot add yourself as a friend'})
|
return jsonify({'status': 'Friend not found or cannot add yourself as a friend'})
|
||||||
return jsonify({'status': 'Unauthorized'}), 401
|
return jsonify({'status': 'Unauthorized'}), 401
|
||||||
|
|
||||||
|
|
||||||
@app.route('/accept_friend/<int:request_id>', methods=['POST'])
|
@app.route('/accept_friend/<int:request_id>', methods=['POST'])
|
||||||
def accept_friend(request_id):
|
def accept_friend(request_id):
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
|
@ -502,7 +490,6 @@ def accept_friend(request_id):
|
||||||
friend_request.status = 'accepted'
|
friend_request.status = 'accepted'
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Create friendships both ways
|
|
||||||
user_id = friend_request.receiver_id
|
user_id = friend_request.receiver_id
|
||||||
friend_id = friend_request.sender_id
|
friend_id = friend_request.sender_id
|
||||||
new_friend_1 = Friend(user_id=user_id, friend_id=friend_id)
|
new_friend_1 = Friend(user_id=user_id, friend_id=friend_id)
|
||||||
|
@ -511,7 +498,6 @@ def accept_friend(request_id):
|
||||||
db.session.add(new_friend_2)
|
db.session.add(new_friend_2)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Move pending messages to messages
|
|
||||||
pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all()
|
pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all()
|
||||||
for pm in pending_messages:
|
for pm in pending_messages:
|
||||||
new_message = Message(sender=pm.sender, receiver=pm.receiver, content=pm.content, content_type=pm.content_type, timestamp=pm.timestamp)
|
new_message = Message(sender=pm.sender, receiver=pm.receiver, content=pm.content, content_type=pm.content_type, timestamp=pm.timestamp)
|
||||||
|
@ -524,7 +510,6 @@ def accept_friend(request_id):
|
||||||
'receiver': friend_request.receiver.username
|
'receiver': friend_request.receiver.username
|
||||||
}, room=friend_request.sender.username)
|
}, room=friend_request.sender.username)
|
||||||
logger.info(f"Friend request accepted by {session['username']} from {friend_request.sender.username}")
|
logger.info(f"Friend request accepted by {session['username']} from {friend_request.sender.username}")
|
||||||
|
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
logger.warning(f"Friend request accept failed: Friend request not found or unauthorized access by {session['username']}")
|
logger.warning(f"Friend request accept failed: Friend request not found or unauthorized access by {session['username']}")
|
||||||
return 'Friend request not found'
|
return 'Friend request not found'
|
||||||
|
@ -537,13 +522,11 @@ def reject_friend(request_id):
|
||||||
if friend_request and friend_request.receiver.username == session['username']:
|
if friend_request and friend_request.receiver.username == session['username']:
|
||||||
db.session.delete(friend_request)
|
db.session.delete(friend_request)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
socketio.emit('friend_request_rejected', {
|
socketio.emit('friend_request_rejected', {
|
||||||
'sender': friend_request.sender.username,
|
'sender': friend_request.sender.username,
|
||||||
'receiver': friend_request.receiver.username
|
'receiver': friend_request.receiver.username
|
||||||
}, room=friend_request.sender.username)
|
}, room=friend_request.sender.username)
|
||||||
logger.info(f"Friend request rejected by {session['username']} from {friend_request.sender.username}")
|
logger.info(f"Friend request rejected by {session['username']} from {friend_request.sender.username}")
|
||||||
|
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
logger.warning(f"Friend request reject failed: Friend request not found or unauthorized access by {session['username']}")
|
logger.warning(f"Friend request reject failed: Friend request not found or unauthorized access by {session['username']}")
|
||||||
return 'Friend request not found'
|
return 'Friend request not found'
|
||||||
|
@ -560,7 +543,6 @@ def remove_friend(friend_id):
|
||||||
if reciprocal_friend:
|
if reciprocal_friend:
|
||||||
db.session.delete(reciprocal_friend)
|
db.session.delete(reciprocal_friend)
|
||||||
|
|
||||||
# Remove friend requests associated with the friend
|
|
||||||
sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first()
|
sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first()
|
||||||
if sent_request:
|
if sent_request:
|
||||||
db.session.delete(sent_request)
|
db.session.delete(sent_request)
|
||||||
|
@ -569,13 +551,11 @@ def remove_friend(friend_id):
|
||||||
db.session.delete(received_request)
|
db.session.delete(received_request)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
socketio.emit('friend_removed', {
|
socketio.emit('friend_removed', {
|
||||||
'sender': session['username'],
|
'sender': session['username'],
|
||||||
'receiver': friend.friend.username
|
'receiver': friend.friend.username
|
||||||
}, room=friend.friend.username)
|
}, room=friend.friend.username)
|
||||||
logger.info(f"Friend {friend.friend.username} removed by {session['username']}")
|
logger.info(f"Friend {friend.friend.username} removed by {session['username']}")
|
||||||
|
|
||||||
return jsonify({'status': 'success'}), 200
|
return jsonify({'status': 'success'}), 200
|
||||||
logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}")
|
logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}")
|
||||||
return jsonify({'status': 'error', 'message': 'Friend not found or unauthorized access'}), 404
|
return jsonify({'status': 'error', 'message': 'Friend not found or unauthorized access'}), 404
|
||||||
|
@ -584,30 +564,45 @@ def remove_friend(friend_id):
|
||||||
@app.route('/create_group', methods=['POST'])
|
@app.route('/create_group', methods=['POST'])
|
||||||
def create_group():
|
def create_group():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
user = User.query.filter_by(username=session['username']).first()
|
try:
|
||||||
group_name = request.form['group_name']
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
member_usernames = [username.strip() for username in request.form['members'].split(',')]
|
group_name = request.form['group_name']
|
||||||
|
member_usernames = [username.strip() for username in request.form['members'].split(',')]
|
||||||
|
|
||||||
# Create a new group
|
existing_group = Group.query.filter_by(name=group_name).first()
|
||||||
new_group = Group(name=group_name, admin_id=user.id)
|
if existing_group:
|
||||||
db.session.add(new_group)
|
return jsonify({'status': 'Error', 'message': 'A group with this name already exists.'}), 400
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Add the group creator as a member
|
for username in member_usernames:
|
||||||
new_group_member = GroupMember(group_id=new_group.id, user_id=user.id)
|
member = User.query.filter_by(username=username).first()
|
||||||
db.session.add(new_group_member)
|
if not member:
|
||||||
|
return jsonify({'status': 'Error', 'message': f'User {username} does not exist.'}), 400
|
||||||
|
friendship = Friend.query.filter(
|
||||||
|
((Friend.user_id == user.id) & (Friend.friend_id == member.id)) |
|
||||||
|
((Friend.user_id == member.id) & (Friend.friend_id == user.id))
|
||||||
|
).first()
|
||||||
|
if not friendship:
|
||||||
|
return jsonify({'status': 'Error', 'message': f'User {username} is not your friend.'}), 400
|
||||||
|
|
||||||
# Add other members to the group
|
new_group = Group(name=group_name, admin_id=user.id)
|
||||||
for username in member_usernames:
|
db.session.add(new_group)
|
||||||
member = User.query.filter_by(username=username).first()
|
db.session.flush()
|
||||||
if member:
|
|
||||||
new_group_member = GroupMember(group_id=new_group.id, user_id=member.id)
|
|
||||||
db.session.add(new_group_member)
|
|
||||||
|
|
||||||
db.session.commit()
|
new_group_member = GroupMember(group_id=new_group.id, user_id=user.id)
|
||||||
|
db.session.add(new_group_member)
|
||||||
|
|
||||||
return redirect(url_for('dashboard'))
|
for username in member_usernames:
|
||||||
return redirect(url_for('login'))
|
member = User.query.filter_by(username=username).first()
|
||||||
|
if member:
|
||||||
|
new_group_member = GroupMember(group_id=new_group.id, user_id=member.id)
|
||||||
|
db.session.add(new_group_member)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({'status': 'Group created'})
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
return jsonify({'status': 'Error', 'message': str(e)}), 500
|
||||||
|
return jsonify({'status': 'Unauthorized'}), 401
|
||||||
|
|
||||||
@app.route('/invite_to_group', methods=['POST'])
|
@app.route('/invite_to_group', methods=['POST'])
|
||||||
def invite_to_group():
|
def invite_to_group():
|
||||||
|
@ -672,12 +667,10 @@ def send_group_message(group_id):
|
||||||
encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content
|
encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content
|
||||||
timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc)
|
timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
# Fetch the sender user object
|
|
||||||
sender_user = User.query.filter_by(username=session['username']).first()
|
sender_user = User.query.filter_by(username=session['username']).first()
|
||||||
if not sender_user:
|
if not sender_user:
|
||||||
return jsonify({'error': 'User not found'}), 404
|
return jsonify({'error': 'User not found'}), 404
|
||||||
|
|
||||||
# Create new group message
|
|
||||||
new_message = GroupMessage(group_id=group_id, sender_id=sender_user.id, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt)
|
new_message = GroupMessage(group_id=group_id, sender_id=sender_user.id, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt)
|
||||||
db.session.add(new_message)
|
db.session.add(new_message)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -687,7 +680,7 @@ def send_group_message(group_id):
|
||||||
'group_id': group_id,
|
'group_id': group_id,
|
||||||
'sender': session['username'],
|
'sender': session['username'],
|
||||||
'content': decrypted_content,
|
'content': decrypted_content,
|
||||||
'sender_profile_picture': sender_user.profile_picture, # Add profile picture
|
'sender_profile_picture': sender_user.profile_picture,
|
||||||
'content_type': content_type,
|
'content_type': content_type,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'id': new_message.id
|
'id': new_message.id
|
||||||
|
@ -710,8 +703,8 @@ def get_group_messages(group_id):
|
||||||
{
|
{
|
||||||
'id': msg.GroupMessage.id,
|
'id': msg.GroupMessage.id,
|
||||||
'group_id': msg.GroupMessage.group_id,
|
'group_id': msg.GroupMessage.group_id,
|
||||||
'sender': msg.User.username, # Use the username from the User table
|
'sender': msg.User.username,
|
||||||
'sender_profile_picture': msg.User.profile_picture, # Add profile picture
|
'sender_profile_picture': msg.User.profile_picture,
|
||||||
'content': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content,
|
'content': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content,
|
||||||
'content_type': msg.GroupMessage.content_type,
|
'content_type': msg.GroupMessage.content_type,
|
||||||
'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
@ -750,6 +743,26 @@ def settings():
|
||||||
return render_template('settings.html', current_theme=current_theme)
|
return render_template('settings.html', current_theme=current_theme)
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/custom_css', methods=['GET', 'POST'])
|
||||||
|
def custom_css():
|
||||||
|
if 'username' in session:
|
||||||
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
|
if request.method == 'POST':
|
||||||
|
custom_css = request.form.get('custom_css')
|
||||||
|
user.custom_css = custom_css
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
return render_template('custom_css.html', custom_css=user.custom_css if user.custom_css else "")
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_user():
|
||||||
|
if 'username' in session:
|
||||||
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
|
return dict(current_user=user)
|
||||||
|
return dict(current_user=None)
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
session.pop('username', None)
|
session.pop('username', None)
|
||||||
|
@ -759,12 +772,17 @@ def logout():
|
||||||
logger.info(f"User logged out")
|
logger.info(f"User logged out")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@socketio.on('ping')
|
||||||
|
def handle_ping():
|
||||||
|
emit('pong')
|
||||||
|
|
||||||
@socketio.on('connect')
|
@socketio.on('connect')
|
||||||
def handle_connect():
|
def handle_connect():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
user = User.query.filter_by(username=session['username']).first()
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
if user:
|
if user:
|
||||||
user.online = True
|
user.online = True
|
||||||
|
user.last_activity = datetime.utcnow()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
emit('user_online', {'username': user.username}, broadcast=True)
|
emit('user_online', {'username': user.username}, broadcast=True)
|
||||||
logger.info(f"User {session['username']} connected")
|
logger.info(f"User {session['username']} connected")
|
||||||
|
@ -805,6 +823,23 @@ def handle_leave_group(data):
|
||||||
leave_room(f"group_{group_id}")
|
leave_room(f"group_{group_id}")
|
||||||
logger.info(f"User {username} left group room {group_id}")
|
logger.info(f"User {username} left group room {group_id}")
|
||||||
|
|
||||||
|
@socketio.on('user_activity')
|
||||||
|
def handle_user_activity():
|
||||||
|
if 'username' in session:
|
||||||
|
user = User.query.filter_by(username=session['username']).first()
|
||||||
|
if user:
|
||||||
|
user.last_activity = datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def set_users_to_idle():
|
||||||
|
with app.app_context():
|
||||||
|
idle_threshold = datetime.utcnow() - timedelta(minutes=5)
|
||||||
|
users = User.query.filter(User.online == True, User.last_activity < idle_threshold).all()
|
||||||
|
for user in users:
|
||||||
|
user.online = False
|
||||||
|
db.session.commit()
|
||||||
|
emit('user_idle', {'username': user.username}, broadcast=True)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
|
@ -122,3 +122,11 @@ hr{
|
||||||
border: none;
|
border: none;
|
||||||
@apply !bg-[#f1f1f1] dark:!bg-[#363636]
|
@apply !bg-[#f1f1f1] dark:!bg-[#363636]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-admin{
|
||||||
|
@apply bg-transparent border-solid border-1 border-black dark:border-white p-2 shadow-lg
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-admin:disabled{
|
||||||
|
@apply bg-neutral-400 text-neutral-200 dark:bg-neutral-700 dark:text-neutral-500
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
|
||||||
socket.emit('join', { username: username });
|
socket.emit('join', { username: username });
|
||||||
|
|
||||||
document.title = `Chatting with ${friendUsername}`;
|
document.title = `${friendUsername}`;
|
||||||
|
|
||||||
const messagesList = document.getElementById('messages');
|
const messagesList = document.getElementById('messages');
|
||||||
const imageOverlay = document.getElementById('image-overlay');
|
const imageOverlay = document.getElementById('image-overlay');
|
||||||
|
@ -147,7 +147,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
// Include the profile picture if available
|
// Include the profile picture if available
|
||||||
const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : '';
|
const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : '';
|
||||||
|
|
||||||
usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`;
|
usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
|
||||||
messagesList.appendChild(usernameElement);
|
messagesList.appendChild(usernameElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
messagesList.innerHTML = '';
|
messagesList.innerHTML = '';
|
||||||
document.getElementById('chat-with').textContent = `Chatting with ${friendUsername}`;
|
document.getElementById('chat-with').textContent = `${friendUsername}`;
|
||||||
document.getElementById('send-message-form').dataset.receiver = friendId;
|
document.getElementById('send-message-form').dataset.receiver = friendId;
|
||||||
|
|
||||||
data.messages.forEach((msg, index, messages) => {
|
data.messages.forEach((msg, index, messages) => {
|
||||||
|
@ -261,7 +261,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
usernameElement.classList.add('message-sender');
|
usernameElement.classList.add('message-sender');
|
||||||
const timestampFormatted = formatLocalTime(timestamp);
|
const timestampFormatted = formatLocalTime(timestamp);
|
||||||
const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference
|
const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference
|
||||||
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`;
|
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
|
||||||
messagesList.appendChild(usernameElement);
|
messagesList.appendChild(usernameElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,21 +436,4 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
document.addEventListener('click', function() {
|
document.addEventListener('click', function() {
|
||||||
contextMenu.style.display = 'none';
|
contextMenu.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle user online/offline status
|
|
||||||
socket.on('user_online', function(data) {
|
|
||||||
const statusIndicator = document.getElementById(`status-${data.username}`);
|
|
||||||
if (statusIndicator) {
|
|
||||||
statusIndicator.classList.remove('offline');
|
|
||||||
statusIndicator.classList.add('online');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('user_offline', function(data) {
|
|
||||||
const statusIndicator = document.getElementById(`status-${data.username}`);
|
|
||||||
if (statusIndicator) {
|
|
||||||
statusIndicator.classList.remove('online');
|
|
||||||
statusIndicator.classList.add('offline');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
33
static/js/connection_check.js
Normal file
33
static/js/connection_check.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
const socket = io();
|
||||||
|
let isConnected = true;
|
||||||
|
|
||||||
|
function checkConnection() {
|
||||||
|
socket.emit('ping');
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('pong', function () {
|
||||||
|
isConnected = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (!isConnected) {
|
||||||
|
if (confirm('Something went wrong, please reload the website')) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isConnected = false;
|
||||||
|
checkConnection();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
isConnected = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
isConnected = false;
|
||||||
|
if (confirm('Something went wrong, please reload the website')) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,24 +1,6 @@
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const socket = io();
|
const socket = io();
|
||||||
|
|
||||||
socket.on('user_online', function(data) {
|
|
||||||
const username = data.username;
|
|
||||||
const friendElement = document.querySelector(`[data-username="${username}"]`);
|
|
||||||
if (friendElement) {
|
|
||||||
friendElement.querySelector('.status-indicator').classList.remove('offline');
|
|
||||||
friendElement.querySelector('.status-indicator').classList.add('online');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('user_offline', function(data) {
|
|
||||||
const username = data.username;
|
|
||||||
const friendElement = document.querySelector(`[data-username="${username}"]`);
|
|
||||||
if (friendElement) {
|
|
||||||
friendElement.querySelector('.status-indicator').classList.remove('online');
|
|
||||||
friendElement.querySelector('.status-indicator').classList.add('offline');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function checkFriendRequests() {
|
function checkFriendRequests() {
|
||||||
const friendRequestsList = document.getElementById('friend-requests');
|
const friendRequestsList = document.getElementById('friend-requests');
|
||||||
const friendRequestsSection = document.getElementById('friend-requests-section');
|
const friendRequestsSection = document.getElementById('friend-requests-section');
|
||||||
|
|
|
@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const md = window.markdownit();
|
const md = window.markdownit();
|
||||||
socket.emit('join_group', { group_id: groupId });
|
socket.emit('join_group', { group_id: groupId });
|
||||||
|
|
||||||
document.title = `Group Chat: ${groupName}`;
|
document.title = `${groupName}`;
|
||||||
|
|
||||||
const messagesList = document.getElementById('messages');
|
const messagesList = document.getElementById('messages');
|
||||||
const imageOverlay = document.getElementById('image-overlay');
|
const imageOverlay = document.getElementById('image-overlay');
|
||||||
|
@ -76,7 +76,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
const usernameElement = document.createElement('div');
|
const usernameElement = document.createElement('div');
|
||||||
usernameElement.classList.add('message-sender');
|
usernameElement.classList.add('message-sender');
|
||||||
const profilePictureUrl = `/cdn/${data.sender_profile_picture}`;
|
const profilePictureUrl = `/cdn/${data.sender_profile_picture}`;
|
||||||
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture"> <strong>${data.sender}</strong>:<div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture enhanceable-image"> <strong>${data.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`;
|
||||||
messagesList.appendChild(usernameElement);
|
messagesList.appendChild(usernameElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
messagesList.innerHTML = '';
|
messagesList.innerHTML = '';
|
||||||
document.getElementById('chat-with').textContent = `Group Chat: ${groupName}`;
|
document.getElementById('chat-with').textContent = `${groupName}`;
|
||||||
document.getElementById('send-message-form').dataset.receiver = groupId;
|
document.getElementById('send-message-form').dataset.receiver = groupId;
|
||||||
|
|
||||||
data.messages.forEach((msg, index, messages) => {
|
data.messages.forEach((msg, index, messages) => {
|
||||||
|
@ -128,7 +128,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
usernameElement.classList.add('message-myself-sender');
|
usernameElement.classList.add('message-myself-sender');
|
||||||
}
|
}
|
||||||
const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`;
|
const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`;
|
||||||
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture"> <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture enhanceable-image"> <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
|
||||||
messagesList.appendChild(usernameElement);
|
messagesList.appendChild(usernameElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,21 +345,4 @@ document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
document.addEventListener('click', function() {
|
document.addEventListener('click', function() {
|
||||||
contextMenu.style.display = 'none';
|
contextMenu.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle user online/offline status
|
|
||||||
socket.on('user_online', function(data) {
|
|
||||||
const statusIndicator = document.getElementById(`status-${data.username}`);
|
|
||||||
if (statusIndicator) {
|
|
||||||
statusIndicator.classList.remove('offline');
|
|
||||||
statusIndicator.classList.add('online');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('user_offline', function(data) {
|
|
||||||
const statusIndicator = document.getElementById(`status-${data.username}`);
|
|
||||||
if (statusIndicator) {
|
|
||||||
statusIndicator.classList.remove('online');
|
|
||||||
statusIndicator.classList.add('offline');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
20
static/js/online_detection.js
Normal file
20
static/js/online_detection.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
|
// Handle user online/offline status
|
||||||
|
socket.on('user_online', function(data) {
|
||||||
|
const statusIndicator = document.querySelector(`.status-indicator[data-username="${data.username}"]`);
|
||||||
|
if (statusIndicator) {
|
||||||
|
statusIndicator.classList.remove('offline');
|
||||||
|
statusIndicator.classList.add('online');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user_offline', function(data) {
|
||||||
|
const statusIndicator = document.querySelector(`.status-indicator[data-username="${data.username}"]`);
|
||||||
|
if (statusIndicator) {
|
||||||
|
statusIndicator.classList.remove('online');
|
||||||
|
statusIndicator.classList.add('offline');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -616,6 +616,10 @@ video {
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-10 {
|
||||||
|
margin-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mr-5 {
|
.mr-5 {
|
||||||
margin-right: 1.25rem;
|
margin-right: 1.25rem;
|
||||||
}
|
}
|
||||||
|
@ -640,6 +644,10 @@ video {
|
||||||
height: 400px !important;
|
height: 400px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.\!h-\[460px\] {
|
||||||
|
height: 460px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.\!h-\[465px\] {
|
.\!h-\[465px\] {
|
||||||
height: 465px !important;
|
height: 465px !important;
|
||||||
}
|
}
|
||||||
|
@ -686,6 +694,10 @@ video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.min-w-\[280px\] {
|
||||||
|
min-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
|
@ -1203,6 +1215,37 @@ hr:is(.dark *) {
|
||||||
background-color: rgb(54 54 54 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(54 54 54 / var(--tw-bg-opacity)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-admin {
|
||||||
|
border-style: solid;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0.5rem;
|
||||||
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-admin:is(.dark *) {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-admin:disabled {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(163 163 163 / var(--tw-bg-opacity));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(229 229 229 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-admin:disabled:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(64 64 64 / var(--tw-bg-opacity));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(115 115 115 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:\!text-slate-400:hover {
|
.hover\:\!text-slate-400:hover {
|
||||||
--tw-text-opacity: 1 !important;
|
--tw-text-opacity: 1 !important;
|
||||||
color: rgb(148 163 184 / var(--tw-text-opacity)) !important;
|
color: rgb(148 163 184 / var(--tw-text-opacity)) !important;
|
||||||
|
|
|
@ -358,6 +358,10 @@ ul#friends-list {
|
||||||
background-color: #80848e;
|
background-color: #80848e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.idle {
|
||||||
|
background-color: #f0b232;
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom Scroll Bar */
|
/* Custom Scroll Bar */
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
|
|
@ -69,19 +69,28 @@
|
||||||
<table class="table dark:!text-white text-black">
|
<table class="table dark:!text-white text-black">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
<th>Username</th>
|
<th>Username</th>
|
||||||
<th>Actions</th>
|
<th>Change Password</th>
|
||||||
|
<th>Disable Account</th>
|
||||||
|
<th>Online?</th>
|
||||||
|
<th>Admin?</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{{ user.id }}</td>
|
||||||
<td>{{ user.username }}</td>
|
<td>{{ user.username }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-warning" onclick="changePassword({{ user.id }})">Change Password</button>
|
<button class="btn btn-warning" onclick="changePassword({{ user.id }})">Change Password</button>
|
||||||
<button onclick="disableAccount({{ user.id }})" {% if user.disabled %}disabled{% endif %}>Disable</button>
|
|
||||||
<button onclick="enableAccount({{ user.id }})" {% if not user.disabled %}disabled{% endif %}>Enable</button>
|
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="button-admin" onclick="disableAccount({{ user.id }})" {% if user.disabled %}disabled{% endif %}>Disable</button>
|
||||||
|
<button class="button-admin" onclick="enableAccount({{ user.id }})" {% if not user.disabled %}disabled{% endif %}>Enable</button>
|
||||||
|
</td>
|
||||||
|
<td>{{ user.online }}</td>
|
||||||
|
<td>{{ user.is_admin }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<div class="bg-white h-16 border-solid border-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]">
|
<div class="bg-white h-16 border-solid border-l-1 border-r-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]">
|
||||||
<i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200"> Chatting with {{ friend_username }}</h3>
|
<i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200">{{ friend_username }}</h3>
|
||||||
</div>
|
</div>
|
|
@ -1,3 +1,3 @@
|
||||||
<div class="bg-white h-16 border-solid border-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]">
|
<div class="bg-white h-16 border-solid border-l-1 border-r-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]">
|
||||||
<i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200"> Group Chat: {{ group.name }}</h3>
|
<i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200">{{ group.name }}</h3>
|
||||||
</div>
|
</div>
|
26
templates/custom_css.html
Normal file
26
templates/custom_css.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Custom CSS</title>
|
||||||
|
{% include 'head.html' %}
|
||||||
|
</head>
|
||||||
|
<body class="login-body">
|
||||||
|
<div class="login-div !h-[460px]">
|
||||||
|
<form class="login-form" id="custom-css-form" method="POST">
|
||||||
|
<h2 class="login-h2">Custom CSS</h2>
|
||||||
|
<div class="login-input-div-first">
|
||||||
|
<label for="custom_css" class="login-label">Enter your custom CSS:</label>
|
||||||
|
<textarea class="login-input" id="custom_css" name="custom_css" rows="10">{{ custom_css }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="login-input-div-first !flex-row">
|
||||||
|
<a class="no-account no-account-button" href="{{ url_for('dashboard') }}"> Go Back</a>
|
||||||
|
</div>
|
||||||
|
<div class="login-input-div-first items-end">
|
||||||
|
<button class="ms-button-signin" type="submit" class="btn btn-primary">Save CSS</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="message" class="mt-3"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -43,15 +43,50 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div>
|
{% if is_admin %}
|
||||||
<h4 class="text-black dark:!text-white"X>Create Group</h4>
|
<div class="dark:!bg-[#1f1f1f] shadow-lg bg-[#f1f1f1] w-fit flex ml-10 flex-col rounded-md items-center">
|
||||||
<form id="create-group-form" method="POST">
|
<h4 class="text-black dark:!text-white relative top-2">Create Group</h4>
|
||||||
<input type="text" name="group_name" placeholder="Group Name" required autocomplete="off">
|
<form id="create-group-form" method="POST" class="w-fit flex flex-col items-center justify-center">
|
||||||
<input type="text" name="members" placeholder="Member Usernames (comma separated)" required autocomplete="off">
|
<input class="dark:!bg-[#1f1f1f] bg-[#f1f1f1] pt-5 rounded-t-md w-3/4" type="text" name="group_name" placeholder="Group Name" required autocomplete="off">
|
||||||
<button class="ms-button" type="submit">Create Group</button>
|
<input class="dark:!bg-[#1f1f1f] bg-[#f1f1f1] pt-5 rounded-t-md w-full min-w-[280px]" type="text" name="members" placeholder="Member Usernames (comma separated)" required autocomplete="off">
|
||||||
<div id="create-group-message" style="color: green;"></div>
|
<button class="ms-button" type="submit">Create Group</button>
|
||||||
</form>
|
<div id="create-group-message" style="color: green;"></div>
|
||||||
</div> -->
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
const createGroupForm = document.getElementById('create-group-form');
|
||||||
|
if (createGroupForm) {
|
||||||
|
createGroupForm.addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = new FormData(createGroupForm);
|
||||||
|
const members = formData.get('members').split(',').map(member => member.trim());
|
||||||
|
formData.set('members', members.join(','));
|
||||||
|
fetch('/create_group', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'Group created') {
|
||||||
|
alert('Group created successfully');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error creating group: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert('Error creating group: ' + error.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
<meta charset="UTF-8">
|
||||||
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> -->
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
|
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
|
||||||
<script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/connection_check.js') }}"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Function to apply the theme based on user selection or system preference
|
// Function to apply the theme based on user selection or system preference
|
||||||
|
@ -34,3 +36,8 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{% if 'username' in session %}
|
||||||
|
<style>
|
||||||
|
{{ current_user.custom_css | safe }}
|
||||||
|
</style>
|
||||||
|
{% endif %}
|
|
@ -1,3 +1,4 @@
|
||||||
|
<script src="{{ url_for('static', filename='js/online_detection.js') }}"></script>
|
||||||
<div class="sidebar w-fit bg-transparent text-white flex flex-col items-center">
|
<div class="sidebar w-fit bg-transparent text-white flex flex-col items-center">
|
||||||
<div class="sidebar-content h-full w-60 p-5 rounded-t-lg">
|
<div class="sidebar-content h-full w-60 p-5 rounded-t-lg">
|
||||||
<h4 class="flex flex-row items-center">
|
<h4 class="flex flex-row items-center">
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
<ul id="friends-list">
|
<ul id="friends-list">
|
||||||
{% for friend in friends %}
|
{% for friend in friends %}
|
||||||
<li data-id="{{ friend.id }}" data-username="{{ friend.username }}">
|
<li data-id="{{ friend.id }}" data-username="{{ friend.username }}">
|
||||||
<span class="status-indicator {{ 'online' if friend.online else 'offline' }}"></span>
|
<span class="status-indicator {{ 'online' if friend.online else 'offline' }}" data-username="{{ friend.username }}"></span>
|
||||||
<a class="dark:!text-slate-400 text-slate-900 hover:!text-slate-400 hover:dark:!text-slate-200" href="/chat/{{ friend.id }}">{{ friend.username }}</a>
|
<a class="dark:!text-slate-400 text-slate-900 hover:!text-slate-400 hover:dark:!text-slate-200" href="/chat/{{ friend.id }}">{{ friend.username }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -8,10 +8,14 @@
|
||||||
<p>{{ username }}</p>
|
<p>{{ username }}</p>
|
||||||
<a href="/settings"><i class="fa-solid fa-gear"></i> Settings</a>
|
<a href="/settings"><i class="fa-solid fa-gear"></i> Settings</a>
|
||||||
<a href="/change_password"><i class="fa-solid fa-lock"></i> Change Password</a>
|
<a href="/change_password"><i class="fa-solid fa-lock"></i> Change Password</a>
|
||||||
|
<a href="{{ url_for('custom_css') }}"><i class="fa-brands fa-css3-alt"></i> Custom CSS</a>
|
||||||
|
|
||||||
{% if is_admin %}
|
{% if is_admin %}
|
||||||
<a href="/admin"><i class="fa-solid fa-shield-alt"></i> Admin Panel</a>
|
<a href="/admin"><i class="fa-solid fa-shield-alt"></i> Admin Panel</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/logout"><i class="fa-solid fa-right-from-bracket"></i> Logout</a>
|
<a href="/logout"><i class="fa-solid fa-right-from-bracket"></i> Logout</a>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue