first
This commit is contained in:
33
routes/__init__.py
Normal file
33
routes/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from flask import Blueprint, Flask, render_template
|
||||
from flask_login import login_required
|
||||
|
||||
def init_app(app: Flask):
|
||||
# Create blueprints
|
||||
main_bp = Blueprint('main', __name__)
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
rooms_bp = Blueprint('rooms', __name__)
|
||||
|
||||
# Import and initialize routes
|
||||
from .main import init_routes as init_main_routes
|
||||
from .auth import init_routes as init_auth_routes
|
||||
from .contacts import contacts_bp as contacts_routes
|
||||
from .rooms import rooms_bp as rooms_routes
|
||||
|
||||
# Initialize routes
|
||||
init_main_routes(main_bp)
|
||||
init_auth_routes(auth_bp)
|
||||
|
||||
# Register blueprints
|
||||
app.register_blueprint(main_bp)
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(rooms_routes)
|
||||
app.register_blueprint(contacts_routes)
|
||||
|
||||
@app.route('/rooms/<int:room_id>/trash')
|
||||
@login_required
|
||||
def trash_page(room_id):
|
||||
return render_template('trash.html')
|
||||
|
||||
return app
|
||||
|
||||
# This file makes the routes directory a Python package
|
||||
BIN
routes/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
routes/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/auth.cpython-313.pyc
Normal file
BIN
routes/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/contacts.cpython-313.pyc
Normal file
BIN
routes/__pycache__/contacts.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/main.cpython-313.pyc
Normal file
BIN
routes/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/room_files.cpython-313.pyc
Normal file
BIN
routes/__pycache__/room_files.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/room_members.cpython-313.pyc
Normal file
BIN
routes/__pycache__/room_members.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/rooms.cpython-313.pyc
Normal file
BIN
routes/__pycache__/rooms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/trash.cpython-313.pyc
Normal file
BIN
routes/__pycache__/trash.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/user.cpython-313.pyc
Normal file
BIN
routes/__pycache__/user.cpython-313.pyc
Normal file
Binary file not shown.
62
routes/auth.py
Normal file
62
routes/auth.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from flask import render_template, request, flash, redirect, url_for
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from models import db, User
|
||||
|
||||
def init_routes(auth_bp):
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
if request.method == 'POST':
|
||||
email = request.form.get('email')
|
||||
password = request.form.get('password')
|
||||
remember = True if request.form.get('remember') else False
|
||||
|
||||
user = User.query.filter_by(email=email).first()
|
||||
|
||||
if not user or not user.check_password(password):
|
||||
flash('Please check your login details and try again.', 'danger')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
login_user(user, remember=remember)
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
return render_template('login.html')
|
||||
|
||||
@auth_bp.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
if request.method == 'POST':
|
||||
email = request.form.get('email')
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if user:
|
||||
flash('Email address already exists', 'danger')
|
||||
return redirect(url_for('auth.register'))
|
||||
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user:
|
||||
flash('Username already exists', 'danger')
|
||||
return redirect(url_for('auth.register'))
|
||||
|
||||
new_user = User(email=email, username=username)
|
||||
new_user.set_password(password)
|
||||
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
|
||||
flash('Registration successful! Please login.', 'success')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return render_template('register.html')
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('main.home'))
|
||||
264
routes/contacts.py
Normal file
264
routes/contacts.py
Normal file
@@ -0,0 +1,264 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, User
|
||||
from forms import UserForm
|
||||
from flask import abort
|
||||
from sqlalchemy import or_
|
||||
import json
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
contacts_bp = Blueprint('contacts', __name__, url_prefix='/contacts')
|
||||
|
||||
UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads', 'profile_pics')
|
||||
if not os.path.exists(UPLOAD_FOLDER):
|
||||
os.makedirs(UPLOAD_FOLDER)
|
||||
|
||||
def admin_required():
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('auth.login'))
|
||||
if not current_user.is_admin:
|
||||
flash('You must be an admin to access this page.', 'error')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
@contacts_bp.route('/')
|
||||
@login_required
|
||||
def contacts_list():
|
||||
result = admin_required()
|
||||
if result: return result
|
||||
|
||||
# Get query parameters
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = 10 # Number of items per page
|
||||
search = request.args.get('search', '')
|
||||
status = request.args.get('status', '')
|
||||
role = request.args.get('role', '')
|
||||
|
||||
# Start with base query
|
||||
query = User.query
|
||||
|
||||
# Apply search filter
|
||||
if search:
|
||||
search_term = f"%{search}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
User.username.ilike(search_term),
|
||||
User.last_name.ilike(search_term),
|
||||
User.email.ilike(search_term),
|
||||
User.company.ilike(search_term),
|
||||
User.position.ilike(search_term)
|
||||
)
|
||||
)
|
||||
|
||||
# Apply status filter
|
||||
if status == 'active':
|
||||
query = query.filter(User.is_active == True)
|
||||
elif status == 'inactive':
|
||||
query = query.filter(User.is_active == False)
|
||||
|
||||
# Apply role filter
|
||||
if role == 'admin':
|
||||
query = query.filter(User.is_admin == True)
|
||||
elif role == 'user':
|
||||
query = query.filter(User.is_admin == False)
|
||||
|
||||
# Order by creation date
|
||||
query = query.order_by(User.created_at.desc())
|
||||
|
||||
# Get pagination
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
users = pagination.items
|
||||
|
||||
return render_template('contacts/list.html',
|
||||
users=users,
|
||||
pagination=pagination,
|
||||
current_user=current_user)
|
||||
|
||||
@contacts_bp.route('/new', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def new_contact():
|
||||
result = admin_required()
|
||||
if result: return result
|
||||
form = UserForm()
|
||||
total_admins = User.query.filter_by(is_admin=True).count()
|
||||
if request.method == 'GET':
|
||||
form.is_admin.data = False # Ensure admin role is unchecked by default
|
||||
elif request.method == 'POST' and 'is_admin' not in request.form:
|
||||
form.is_admin.data = False # Explicitly set to False if not present in POST
|
||||
if form.validate_on_submit():
|
||||
# Check if a user with this email already exists
|
||||
existing_user = User.query.filter_by(email=form.email.data).first()
|
||||
if existing_user:
|
||||
flash('A user with this email already exists.', 'error')
|
||||
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins)
|
||||
|
||||
# Handle profile picture upload
|
||||
profile_picture = None
|
||||
file = request.files.get('profile_picture')
|
||||
if file and file.filename:
|
||||
filename = secure_filename(file.filename)
|
||||
file_path = os.path.join(UPLOAD_FOLDER, filename)
|
||||
file.save(file_path)
|
||||
profile_picture = filename
|
||||
|
||||
# Create new user account
|
||||
user = User(
|
||||
username=form.first_name.data,
|
||||
last_name=form.last_name.data,
|
||||
email=form.email.data,
|
||||
phone=form.phone.data,
|
||||
company=form.company.data,
|
||||
position=form.position.data,
|
||||
notes=form.notes.data,
|
||||
is_active=form.is_active.data,
|
||||
is_admin=form.is_admin.data,
|
||||
profile_picture=profile_picture
|
||||
)
|
||||
user.set_password('changeme') # Set a default password that must be changed
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
flash('User created successfully! They will need to set their password on first login.', 'success')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins)
|
||||
|
||||
@contacts_bp.route('/profile/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_profile():
|
||||
form = UserForm()
|
||||
total_admins = User.query.filter_by(is_admin=True).count()
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Check if trying to remove admin status from the only admin
|
||||
if not form.is_admin.data and current_user.is_admin:
|
||||
if total_admins <= 1:
|
||||
flash('There must be at least one admin user in the system.', 'error')
|
||||
return render_template('contacts/form.html', form=form, title='Edit Profile', total_admins=total_admins)
|
||||
|
||||
current_user.username = form.first_name.data
|
||||
current_user.last_name = form.last_name.data
|
||||
current_user.email = form.email.data
|
||||
current_user.phone = form.phone.data
|
||||
current_user.company = form.company.data
|
||||
current_user.position = form.position.data
|
||||
current_user.notes = form.notes.data
|
||||
current_user.is_active = form.is_active.data
|
||||
current_user.is_admin = form.is_admin.data
|
||||
# Set password if provided
|
||||
if form.new_password.data:
|
||||
current_user.set_password(form.new_password.data)
|
||||
db.session.commit()
|
||||
flash('Profile updated successfully!', 'success')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
|
||||
# Pre-fill the form with current user data
|
||||
if request.method == 'GET':
|
||||
form.first_name.data = current_user.username
|
||||
form.last_name.data = current_user.last_name
|
||||
form.email.data = current_user.email
|
||||
form.phone.data = current_user.phone
|
||||
form.company.data = current_user.company
|
||||
form.position.data = current_user.position
|
||||
form.notes.data = current_user.notes
|
||||
form.is_active.data = current_user.is_active
|
||||
form.is_admin.data = current_user.is_admin
|
||||
|
||||
return render_template('contacts/form.html', form=form, title='Edit Profile', total_admins=total_admins)
|
||||
|
||||
@contacts_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_contact(id):
|
||||
result = admin_required()
|
||||
if result: return result
|
||||
total_admins = User.query.filter_by(is_admin=True).count()
|
||||
user = User.query.get_or_404(id)
|
||||
form = UserForm()
|
||||
if request.method == 'GET':
|
||||
form.first_name.data = user.username
|
||||
form.last_name.data = user.last_name
|
||||
form.email.data = user.email
|
||||
form.phone.data = user.phone
|
||||
form.company.data = user.company
|
||||
form.position.data = user.position
|
||||
form.notes.data = user.notes
|
||||
form.is_active.data = user.is_active
|
||||
form.is_admin.data = user.is_admin
|
||||
if form.validate_on_submit():
|
||||
# Handle profile picture removal
|
||||
if 'remove_picture' in request.form:
|
||||
if user.profile_picture:
|
||||
# Delete the old profile picture file
|
||||
old_picture_path = os.path.join(UPLOAD_FOLDER, user.profile_picture)
|
||||
if os.path.exists(old_picture_path):
|
||||
os.remove(old_picture_path)
|
||||
user.profile_picture = None
|
||||
db.session.commit()
|
||||
flash('Profile picture removed successfully!', 'success')
|
||||
return redirect(url_for('contacts.edit_contact', id=user.id))
|
||||
|
||||
# Handle profile picture upload
|
||||
file = request.files.get('profile_picture')
|
||||
if file and file.filename:
|
||||
# Delete old profile picture if it exists
|
||||
if user.profile_picture:
|
||||
old_picture_path = os.path.join(UPLOAD_FOLDER, user.profile_picture)
|
||||
if os.path.exists(old_picture_path):
|
||||
os.remove(old_picture_path)
|
||||
filename = secure_filename(file.filename)
|
||||
file_path = os.path.join(UPLOAD_FOLDER, filename)
|
||||
file.save(file_path)
|
||||
user.profile_picture = filename
|
||||
|
||||
# Prevent removing admin from the last admin
|
||||
if not form.is_admin.data and user.is_admin and total_admins <= 1:
|
||||
flash('There must be at least one admin user in the system.', 'error')
|
||||
return render_template('contacts/form.html', form=form, title='Edit User', total_admins=total_admins, user=user)
|
||||
# Check if the new email is already used by another user
|
||||
if form.email.data != user.email:
|
||||
existing_user = User.query.filter_by(email=form.email.data).first()
|
||||
if existing_user:
|
||||
flash('A user with this email already exists.', 'error')
|
||||
return render_template('contacts/form.html', form=form, title='Edit User', total_admins=total_admins, user=user)
|
||||
user.username = form.first_name.data
|
||||
user.last_name = form.last_name.data
|
||||
user.email = form.email.data
|
||||
user.phone = form.phone.data
|
||||
user.company = form.company.data
|
||||
user.position = form.position.data
|
||||
user.notes = form.notes.data
|
||||
user.is_active = form.is_active.data
|
||||
user.is_admin = form.is_admin.data
|
||||
# Set password if provided
|
||||
if form.new_password.data:
|
||||
user.set_password(form.new_password.data)
|
||||
db.session.commit()
|
||||
flash('User updated successfully!', 'success')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
return render_template('contacts/form.html', form=form, title='Edit User', total_admins=total_admins, user=user)
|
||||
|
||||
@contacts_bp.route('/<int:id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_contact(id):
|
||||
result = admin_required()
|
||||
if result: return result
|
||||
user = User.query.get_or_404(id)
|
||||
if user.email == current_user.email:
|
||||
flash('You cannot delete your own account.', 'error')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
flash('User deleted successfully!', 'success')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
|
||||
@contacts_bp.route('/<int:id>/toggle-active', methods=['POST'])
|
||||
@login_required
|
||||
def toggle_active(id):
|
||||
result = admin_required()
|
||||
if result: return result
|
||||
user = User.query.get_or_404(id)
|
||||
if user.email == current_user.email:
|
||||
flash('You cannot deactivate your own account.', 'error')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
user.is_active = not user.is_active
|
||||
db.session.commit()
|
||||
flash(f'User marked as {"active" if user.is_active else "inactive"}!', 'success')
|
||||
return redirect(url_for('contacts.contacts_list'))
|
||||
284
routes/main.py
Normal file
284
routes/main.py
Normal file
@@ -0,0 +1,284 @@
|
||||
from flask import render_template, Blueprint, redirect, url_for, request, flash
|
||||
from flask_login import current_user, login_required
|
||||
from models import User, db, Room, RoomFile, RoomMemberPermission
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
from sqlalchemy import func, case, literal_column, text
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def init_routes(main_bp):
|
||||
@main_bp.route('/')
|
||||
def home():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return render_template('home.html')
|
||||
|
||||
@main_bp.route('/dashboard')
|
||||
@login_required
|
||||
def dashboard():
|
||||
# Get 3 most recent users
|
||||
recent_contacts = User.query.order_by(User.created_at.desc()).limit(3).all()
|
||||
# Count active and inactive users
|
||||
active_count = User.query.filter_by(is_active=True).count()
|
||||
inactive_count = User.query.filter_by(is_active=False).count()
|
||||
|
||||
# Room count and size logic
|
||||
if current_user.is_admin:
|
||||
room_count = Room.query.count()
|
||||
# Get total file and folder counts for admin
|
||||
file_count = RoomFile.query.filter_by(type='file').count()
|
||||
folder_count = RoomFile.query.filter_by(type='folder').count()
|
||||
# Get total size of all files including trash
|
||||
total_size = db.session.query(func.sum(RoomFile.size)).filter(RoomFile.type == 'file').scalar() or 0
|
||||
# Get recent activity for all files
|
||||
recent_activity = db.session.query(
|
||||
RoomFile,
|
||||
Room,
|
||||
User
|
||||
).join(
|
||||
Room, RoomFile.room_id == Room.id
|
||||
).join(
|
||||
User, RoomFile.uploaded_by == User.id
|
||||
).order_by(
|
||||
RoomFile.uploaded_at.desc()
|
||||
).limit(10).all()
|
||||
|
||||
# Format the activity data
|
||||
formatted_activity = []
|
||||
for file, room, user in recent_activity:
|
||||
activity = {
|
||||
'name': file.name,
|
||||
'type': file.type,
|
||||
'room': room,
|
||||
'uploader': user,
|
||||
'uploaded_at': file.uploaded_at,
|
||||
'is_starred': current_user in file.starred_by,
|
||||
'is_deleted': file.deleted,
|
||||
'can_download': True # Admin can download everything
|
||||
}
|
||||
formatted_activity.append(activity)
|
||||
recent_activity = formatted_activity
|
||||
# Get storage usage by file type including trash
|
||||
storage_by_type = db.session.query(
|
||||
case(
|
||||
(RoomFile.name.like('%.%'),
|
||||
func.split_part(RoomFile.name, '.', -1)),
|
||||
else_=literal_column("'unknown'")
|
||||
).label('extension'),
|
||||
func.count(RoomFile.id).label('count'),
|
||||
func.sum(RoomFile.size).label('total_size')
|
||||
).filter(
|
||||
RoomFile.type == 'file'
|
||||
).group_by('extension').all()
|
||||
|
||||
# Get trash and starred stats for admin
|
||||
trash_count = RoomFile.query.filter_by(deleted=True).count()
|
||||
starred_count = RoomFile.query.filter(RoomFile.starred_by.contains(current_user)).count()
|
||||
# Get oldest trash date and total trash size
|
||||
oldest_trash = RoomFile.query.filter_by(deleted=True).order_by(RoomFile.deleted_at.asc()).first()
|
||||
oldest_trash_date = oldest_trash.deleted_at.strftime('%Y-%m-%d') if oldest_trash else None
|
||||
trash_size = db.session.query(db.func.sum(RoomFile.size)).filter(RoomFile.deleted==True).scalar() or 0
|
||||
|
||||
# Get files that will be deleted in next 7 days
|
||||
seven_days_from_now = datetime.utcnow() + timedelta(days=7)
|
||||
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
|
||||
pending_deletion = RoomFile.query.filter(
|
||||
RoomFile.deleted==True,
|
||||
RoomFile.deleted_at <= thirty_days_ago,
|
||||
RoomFile.deleted_at > thirty_days_ago - timedelta(days=7)
|
||||
).count()
|
||||
|
||||
# Get trash file type breakdown
|
||||
trash_by_type = db.session.query(
|
||||
case(
|
||||
(RoomFile.name.like('%.%'),
|
||||
func.split_part(RoomFile.name, '.', -1)),
|
||||
else_=literal_column("'unknown'")
|
||||
).label('extension'),
|
||||
func.count(RoomFile.id).label('count')
|
||||
).filter(
|
||||
RoomFile.deleted==True
|
||||
).group_by('extension').all()
|
||||
else:
|
||||
# Get rooms the user has access to
|
||||
accessible_rooms = Room.query.filter(Room.members.any(id=current_user.id)).all()
|
||||
room_count = len(accessible_rooms)
|
||||
# Get file and folder counts for accessible rooms
|
||||
room_ids = [room.id for room in accessible_rooms]
|
||||
file_count = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.type == 'file').count()
|
||||
folder_count = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.type == 'folder').count()
|
||||
# Get total size of files in accessible rooms including trash
|
||||
total_size = db.session.query(func.sum(RoomFile.size)).filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.type == 'file'
|
||||
).scalar() or 0
|
||||
# Get recent activity for accessible rooms
|
||||
recent_activity = db.session.query(
|
||||
RoomFile,
|
||||
Room,
|
||||
User
|
||||
).join(
|
||||
Room, RoomFile.room_id == Room.id
|
||||
).join(
|
||||
User, RoomFile.uploaded_by == User.id
|
||||
).filter(
|
||||
RoomFile.room_id.in_(room_ids)
|
||||
).order_by(
|
||||
RoomFile.uploaded_at.desc()
|
||||
).limit(10).all()
|
||||
|
||||
# Format the activity data
|
||||
formatted_activity = []
|
||||
user_perms = {p.room_id: p for p in RoomMemberPermission.query.filter(
|
||||
RoomMemberPermission.room_id.in_(room_ids),
|
||||
RoomMemberPermission.user_id==current_user.id
|
||||
).all()}
|
||||
|
||||
for file, room, user in recent_activity:
|
||||
perm = user_perms.get(room.id)
|
||||
activity = {
|
||||
'name': file.name,
|
||||
'type': file.type,
|
||||
'room': room,
|
||||
'uploader': user,
|
||||
'uploaded_at': file.uploaded_at,
|
||||
'is_starred': current_user in file.starred_by,
|
||||
'is_deleted': file.deleted,
|
||||
'can_download': perm.can_download if perm else False
|
||||
}
|
||||
formatted_activity.append(activity)
|
||||
recent_activity = formatted_activity
|
||||
# Get storage usage by file type for accessible rooms including trash
|
||||
storage_by_type = db.session.query(
|
||||
case(
|
||||
(RoomFile.name.like('%.%'),
|
||||
func.split_part(RoomFile.name, '.', -1)),
|
||||
else_=literal_column("'unknown'")
|
||||
).label('extension'),
|
||||
func.count(RoomFile.id).label('count'),
|
||||
func.sum(RoomFile.size).label('total_size')
|
||||
).filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.type == 'file'
|
||||
).group_by('extension').all()
|
||||
|
||||
# Get trash and starred stats for user's accessible rooms
|
||||
trash_count = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.deleted==True).count()
|
||||
starred_count = RoomFile.query.filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.starred_by.contains(current_user)
|
||||
).count()
|
||||
# Get oldest trash date and total trash size for user's rooms
|
||||
oldest_trash = RoomFile.query.filter(RoomFile.room_id.in_(room_ids), RoomFile.deleted==True).order_by(RoomFile.deleted_at.asc()).first()
|
||||
oldest_trash_date = oldest_trash.deleted_at.strftime('%Y-%m-%d') if oldest_trash else None
|
||||
trash_size = db.session.query(db.func.sum(RoomFile.size)).filter(RoomFile.room_id.in_(room_ids), RoomFile.deleted==True).scalar() or 0
|
||||
|
||||
# Get files that will be deleted in next 7 days for user's rooms
|
||||
seven_days_from_now = datetime.utcnow() + timedelta(days=7)
|
||||
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
|
||||
pending_deletion = RoomFile.query.filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.deleted==True,
|
||||
RoomFile.deleted_at <= thirty_days_ago,
|
||||
RoomFile.deleted_at > thirty_days_ago - timedelta(days=7)
|
||||
).count()
|
||||
|
||||
# Get trash file type breakdown for user's rooms
|
||||
trash_by_type = db.session.query(
|
||||
case(
|
||||
(RoomFile.name.like('%.%'),
|
||||
func.split_part(RoomFile.name, '.', -1)),
|
||||
else_=literal_column("'unknown'")
|
||||
).label('extension'),
|
||||
func.count(RoomFile.id).label('count')
|
||||
).filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.deleted==True
|
||||
).group_by('extension').all()
|
||||
|
||||
return render_template('dashboard.html',
|
||||
recent_contacts=recent_contacts,
|
||||
active_count=active_count,
|
||||
inactive_count=inactive_count,
|
||||
room_count=room_count,
|
||||
file_count=file_count,
|
||||
folder_count=folder_count,
|
||||
total_size=total_size,
|
||||
recent_activity=recent_activity,
|
||||
storage_by_type=storage_by_type,
|
||||
trash_count=trash_count,
|
||||
starred_count=starred_count,
|
||||
oldest_trash_date=oldest_trash_date,
|
||||
trash_size=trash_size,
|
||||
pending_deletion=pending_deletion,
|
||||
trash_by_type=trash_by_type)
|
||||
|
||||
UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads', 'profile_pics')
|
||||
if not os.path.exists(UPLOAD_FOLDER):
|
||||
os.makedirs(UPLOAD_FOLDER)
|
||||
|
||||
@main_bp.route('/profile', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def profile():
|
||||
if request.method == 'POST':
|
||||
# Handle profile picture removal
|
||||
if 'remove_picture' in request.form:
|
||||
if current_user.profile_picture:
|
||||
# Delete the old profile picture file
|
||||
old_picture_path = os.path.join(UPLOAD_FOLDER, current_user.profile_picture)
|
||||
if os.path.exists(old_picture_path):
|
||||
os.remove(old_picture_path)
|
||||
current_user.profile_picture = None
|
||||
db.session.commit()
|
||||
flash('Profile picture removed successfully!', 'success')
|
||||
return redirect(url_for('main.profile'))
|
||||
|
||||
new_email = request.form.get('email')
|
||||
# Check if the new email is already used by another user
|
||||
if new_email != current_user.email:
|
||||
existing_user = User.query.filter_by(email=new_email).first()
|
||||
if existing_user:
|
||||
flash('A user with this email already exists.', 'error')
|
||||
return render_template('profile.html')
|
||||
# Handle profile picture upload
|
||||
file = request.files.get('profile_picture')
|
||||
if file and file.filename:
|
||||
filename = secure_filename(file.filename)
|
||||
file_path = os.path.join(UPLOAD_FOLDER, filename)
|
||||
file.save(file_path)
|
||||
current_user.profile_picture = filename
|
||||
# Update user information
|
||||
current_user.username = request.form.get('first_name')
|
||||
current_user.last_name = request.form.get('last_name')
|
||||
current_user.email = new_email
|
||||
current_user.phone = request.form.get('phone')
|
||||
current_user.company = request.form.get('company')
|
||||
current_user.position = request.form.get('position')
|
||||
current_user.notes = request.form.get('notes')
|
||||
# Handle password change if provided
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
if new_password:
|
||||
if new_password != confirm_password:
|
||||
flash('Passwords do not match.', 'error')
|
||||
return render_template('profile.html')
|
||||
current_user.set_password(new_password)
|
||||
flash('Password updated successfully.', 'success')
|
||||
try:
|
||||
db.session.commit()
|
||||
flash('Profile updated successfully!', 'success')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash('An error occurred while updating your profile.', 'error')
|
||||
return redirect(url_for('main.profile'))
|
||||
return render_template('profile.html')
|
||||
|
||||
@main_bp.route('/starred')
|
||||
@login_required
|
||||
def starred():
|
||||
return render_template('starred.html')
|
||||
|
||||
@main_bp.route('/trash')
|
||||
@login_required
|
||||
def trash():
|
||||
return render_template('trash.html')
|
||||
668
routes/room_files.py
Normal file
668
routes/room_files.py
Normal file
@@ -0,0 +1,668 @@
|
||||
from flask import Blueprint, jsonify, request, abort, send_from_directory, send_file
|
||||
from flask_login import login_required, current_user
|
||||
import os
|
||||
from models import Room, RoomMemberPermission, RoomFile, TrashedFile, db
|
||||
from werkzeug.utils import secure_filename, safe_join
|
||||
import time
|
||||
import shutil
|
||||
import io
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
|
||||
room_files_bp = Blueprint('room_files', __name__, url_prefix='/api/rooms')
|
||||
|
||||
DATA_ROOT = '/data/rooms' # This should be a Docker volume
|
||||
|
||||
ALLOWED_EXTENSIONS = {
|
||||
# Documents
|
||||
'pdf', 'docx', 'doc', 'txt', 'rtf', 'odt', 'md', 'csv',
|
||||
# Spreadsheets
|
||||
'xlsx', 'xls', 'ods', 'xlsm',
|
||||
# Presentations
|
||||
'pptx', 'ppt', 'odp',
|
||||
# Images
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'tiff',
|
||||
# Archives
|
||||
'zip', 'rar', '7z', 'tar', 'gz',
|
||||
# Code/Text
|
||||
'py', 'js', 'html', 'css', 'json', 'xml', 'sql', 'sh', 'bat',
|
||||
# Audio
|
||||
'mp3', 'wav', 'ogg', 'm4a', 'flac',
|
||||
# Video
|
||||
'mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm',
|
||||
# CAD/Design
|
||||
'dwg', 'dxf', 'ai', 'psd', 'eps', 'indd',
|
||||
# Other
|
||||
'eml', 'msg', 'vcf', 'ics'
|
||||
}
|
||||
|
||||
def get_room_dir(room_id):
|
||||
return os.path.join(DATA_ROOT, str(room_id))
|
||||
|
||||
def user_has_permission(room, perm_name):
|
||||
if current_user.is_admin:
|
||||
return True
|
||||
perm = RoomMemberPermission.query.filter_by(room_id=room.id, user_id=current_user.id).first()
|
||||
return getattr(perm, perm_name, False) if perm else False
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
def clean_path(path):
|
||||
if not path:
|
||||
return ''
|
||||
return path.strip('/\\')
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/files', methods=['GET'])
|
||||
@login_required
|
||||
def list_room_files(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
|
||||
path = request.args.get('path', '')
|
||||
path = clean_path(path)
|
||||
|
||||
# Get files in the current path
|
||||
files = RoomFile.query.filter_by(room_id=room_id, path=path, deleted=False).all()
|
||||
|
||||
# Debug: Check user permissions
|
||||
if not current_user.is_admin:
|
||||
perm = RoomMemberPermission.query.filter_by(room_id=room.id, user_id=current_user.id).first()
|
||||
print("=== User Permissions ===")
|
||||
print(f" - can_view: {getattr(perm, 'can_view', False) if perm else False}")
|
||||
print(f" - can_download: {getattr(perm, 'can_download', False) if perm else False}")
|
||||
print(f" - can_upload: {getattr(perm, 'can_upload', False) if perm else False}")
|
||||
print(f" - can_delete: {getattr(perm, 'can_delete', False) if perm else False}")
|
||||
print(f" - can_rename: {getattr(perm, 'can_rename', False) if perm else False}")
|
||||
print(f" - can_move: {getattr(perm, 'can_move', False) if perm else False}")
|
||||
print(f" - can_share: {getattr(perm, 'can_share', False) if perm else False}")
|
||||
print("-------------------")
|
||||
|
||||
result = []
|
||||
for f in files:
|
||||
uploader_full_name = None
|
||||
uploader_profile_pic = None
|
||||
if f.uploader:
|
||||
uploader_full_name = f.uploader.username
|
||||
if getattr(f.uploader, 'last_name', None):
|
||||
uploader_full_name += ' ' + f.uploader.last_name
|
||||
uploader_profile_pic = f.uploader.profile_picture if getattr(f.uploader, 'profile_picture', None) else None
|
||||
result.append({
|
||||
'name': f.name,
|
||||
'type': f.type,
|
||||
'size': f.size if f.type == 'file' else '-',
|
||||
'modified': f.modified,
|
||||
'uploaded_by': uploader_full_name,
|
||||
'uploader_profile_pic': uploader_profile_pic,
|
||||
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
||||
'path': f.path,
|
||||
'starred': current_user in f.starred_by
|
||||
})
|
||||
print(f"Returning {len(result)} files") # Debug log
|
||||
return jsonify(result)
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/files/upload', methods=['POST'])
|
||||
@login_required
|
||||
def upload_room_file(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_upload'):
|
||||
abort(403)
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'error': 'No file part'}), 400
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({'error': 'No selected file'}), 400
|
||||
if not allowed_file(file.filename):
|
||||
return jsonify({'error': 'File type not allowed'}), 400
|
||||
filename = secure_filename(file.filename)
|
||||
room_dir = get_room_dir(room_id)
|
||||
rel_path = clean_path(request.form.get('path', ''))
|
||||
target_dir = os.path.join(room_dir, rel_path) if rel_path else room_dir
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
file_path = os.path.join(target_dir, filename)
|
||||
|
||||
# Check for overwrite flag
|
||||
overwrite = request.form.get('overwrite', 'false').lower() == 'true'
|
||||
|
||||
# First check for non-deleted files
|
||||
existing_file = RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path, deleted=False).first()
|
||||
if existing_file and not overwrite:
|
||||
return jsonify({'error': 'A file with this name already exists in this location', 'conflict': True}), 409
|
||||
|
||||
# Then check for deleted files
|
||||
trashed_file = RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path, deleted=True).first()
|
||||
if trashed_file:
|
||||
# If we're not overwriting, return conflict
|
||||
if not overwrite:
|
||||
return jsonify({'error': 'A file with this name exists in the trash', 'conflict': True}), 409
|
||||
|
||||
# If we are overwriting, delete the trashed file record
|
||||
db.session.delete(trashed_file)
|
||||
db.session.commit()
|
||||
existing_file = None
|
||||
|
||||
file.save(file_path)
|
||||
stat = os.stat(file_path)
|
||||
if existing_file:
|
||||
# Overwrite: update the RoomFile record
|
||||
existing_file.size = stat.st_size
|
||||
existing_file.modified = stat.st_mtime
|
||||
existing_file.uploaded_by = current_user.id
|
||||
existing_file.uploaded_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'filename': filename, 'overwritten': True})
|
||||
else:
|
||||
rf = RoomFile(
|
||||
room_id=room_id,
|
||||
name=filename,
|
||||
path=rel_path,
|
||||
type='file',
|
||||
size=stat.st_size,
|
||||
modified=stat.st_mtime,
|
||||
uploaded_by=current_user.id,
|
||||
uploaded_at=datetime.utcnow()
|
||||
)
|
||||
db.session.add(rf)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'filename': filename})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/files/<filename>', methods=['GET'])
|
||||
@login_required
|
||||
def download_room_file(room_id, filename):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_download'):
|
||||
abort(403)
|
||||
rel_path = clean_path(request.args.get('path', ''))
|
||||
# Lookup in RoomFile
|
||||
rf = RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path).first()
|
||||
if not rf or rf.type != 'file':
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
room_dir = get_room_dir(room_id)
|
||||
file_path = os.path.join(room_dir, rel_path, filename) if rel_path else os.path.join(room_dir, filename)
|
||||
if not os.path.exists(file_path):
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
return send_from_directory(os.path.dirname(file_path), filename, as_attachment=True)
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/files/<path:filename>', methods=['DELETE'])
|
||||
@login_required
|
||||
def delete_file(room_id, filename):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_delete'):
|
||||
abort(403)
|
||||
|
||||
rel_path = clean_path(request.args.get('path', ''))
|
||||
|
||||
# Lookup in RoomFile
|
||||
rf = RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path).first()
|
||||
if not rf:
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
# Mark as deleted and record who deleted it and when
|
||||
rf.deleted = True
|
||||
rf.deleted_by = current_user.id
|
||||
rf.deleted_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/folders', methods=['POST'])
|
||||
@login_required
|
||||
def create_room_folder(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_upload'):
|
||||
abort(403)
|
||||
data = request.get_json()
|
||||
folder_name = data.get('name', '').strip()
|
||||
rel_path = clean_path(data.get('path', ''))
|
||||
|
||||
if not folder_name or '/' in folder_name or '\\' in folder_name or folder_name.startswith('.'):
|
||||
return jsonify({'error': 'Invalid folder name'}), 400
|
||||
|
||||
room_dir = get_room_dir(room_id)
|
||||
target_dir = os.path.join(room_dir, rel_path) if rel_path else room_dir
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
folder_path = os.path.join(target_dir, folder_name)
|
||||
|
||||
# First check for trashed folder
|
||||
trashed_folder = RoomFile.query.filter_by(
|
||||
room_id=room_id,
|
||||
name=folder_name,
|
||||
path=rel_path,
|
||||
type='folder',
|
||||
deleted=True
|
||||
).first()
|
||||
|
||||
if trashed_folder:
|
||||
return jsonify({'error': 'A folder with this name exists in the trash'}), 409
|
||||
|
||||
# Then check for existing folder in current location
|
||||
existing_folder = RoomFile.query.filter_by(
|
||||
room_id=room_id,
|
||||
name=folder_name,
|
||||
path=rel_path,
|
||||
type='folder',
|
||||
deleted=False
|
||||
).first()
|
||||
|
||||
if existing_folder:
|
||||
return jsonify({'error': 'A folder with this name already exists in this location'}), 400
|
||||
|
||||
if os.path.exists(folder_path):
|
||||
return jsonify({'error': 'A folder with this name already exists in this location'}), 400
|
||||
|
||||
os.makedirs(folder_path)
|
||||
# Add RoomFile entry
|
||||
stat = os.stat(folder_path)
|
||||
rf = RoomFile(
|
||||
room_id=room_id,
|
||||
name=folder_name,
|
||||
path=rel_path,
|
||||
type='folder',
|
||||
size=None,
|
||||
modified=stat.st_mtime,
|
||||
uploaded_by=current_user.id,
|
||||
uploaded_at=datetime.utcnow()
|
||||
)
|
||||
db.session.add(rf)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'name': folder_name})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/rename', methods=['POST'])
|
||||
@login_required
|
||||
def rename_room_file(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Allow rename if user can upload or delete
|
||||
if not (user_has_permission(room, 'can_upload') or user_has_permission(room, 'can_delete')):
|
||||
abort(403)
|
||||
data = request.get_json()
|
||||
old_name = data.get('old_name', '').strip()
|
||||
new_name = data.get('new_name', '').strip()
|
||||
rel_path = clean_path(data.get('path', ''))
|
||||
if not old_name or not new_name or '/' in new_name or '\\' in new_name or new_name.startswith('.'):
|
||||
return jsonify({'error': 'Invalid name'}), 400
|
||||
# Lookup in RoomFile
|
||||
rf = RoomFile.query.filter_by(room_id=room_id, name=old_name, path=rel_path).first()
|
||||
if not rf:
|
||||
return jsonify({'error': 'Original file/folder not found'}), 404
|
||||
room_dir = get_room_dir(room_id)
|
||||
base_dir = os.path.join(room_dir, rel_path) if rel_path else room_dir
|
||||
old_path = os.path.join(base_dir, old_name)
|
||||
new_path = os.path.join(base_dir, new_name)
|
||||
if not os.path.exists(old_path):
|
||||
return jsonify({'error': 'Original file/folder not found'}), 404
|
||||
if os.path.exists(new_path):
|
||||
return jsonify({'error': 'A file or folder with the new name already exists'}), 400
|
||||
# Prevent file extension change for files
|
||||
if os.path.isfile(old_path):
|
||||
old_ext = os.path.splitext(old_name)[1].lower()
|
||||
new_ext = os.path.splitext(new_name)[1].lower()
|
||||
if old_ext != new_ext:
|
||||
return jsonify({'error': 'File extension cannot be changed'}), 400
|
||||
os.rename(old_path, new_path)
|
||||
# Update RoomFile entry
|
||||
rf.name = new_name
|
||||
rf.modified = os.path.getmtime(new_path)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'old_name': old_name, 'new_name': new_name})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/download-zip', methods=['POST'])
|
||||
@login_required
|
||||
def download_zip(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
data = request.get_json()
|
||||
items = data.get('items', [])
|
||||
if not items or not isinstance(items, list):
|
||||
return jsonify({'error': 'No items selected'}), 400
|
||||
room_dir = get_room_dir(room_id)
|
||||
mem_zip = io.BytesIO()
|
||||
with zipfile.ZipFile(mem_zip, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for item in items:
|
||||
name = item.get('name')
|
||||
rel_path = item.get('path', '').strip('/\\')
|
||||
abs_path = os.path.join(room_dir, rel_path, name) if rel_path else os.path.join(room_dir, name)
|
||||
if not os.path.exists(abs_path):
|
||||
continue
|
||||
if os.path.isfile(abs_path):
|
||||
zf.write(abs_path, arcname=name)
|
||||
elif os.path.isdir(abs_path):
|
||||
for root, dirs, files in os.walk(abs_path):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
arcname = os.path.relpath(file_path, room_dir)
|
||||
zf.write(file_path, arcname=arcname)
|
||||
mem_zip.seek(0)
|
||||
return send_file(mem_zip, mimetype='application/zip', as_attachment=True, download_name='download.zip')
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/search', methods=['GET'])
|
||||
@login_required
|
||||
def search_room_files(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
query = request.args.get('query', '').strip().lower()
|
||||
# Search RoomFile for this room
|
||||
files = RoomFile.query.filter(RoomFile.room_id==room_id).all()
|
||||
matches = []
|
||||
for f in files:
|
||||
if query in f.name.lower():
|
||||
matches.append({
|
||||
'name': f.name,
|
||||
'type': f.type,
|
||||
'size': f.size if f.type == 'file' else '-',
|
||||
'modified': f.modified,
|
||||
'uploaded_by': f.uploader.username if f.uploader else None,
|
||||
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
||||
'path': f.path,
|
||||
})
|
||||
return jsonify(matches)
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/move', methods=['POST'])
|
||||
@login_required
|
||||
def move_room_file(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_move'):
|
||||
abort(403)
|
||||
data = request.get_json()
|
||||
filename = data.get('filename', '').strip()
|
||||
source_path = clean_path(data.get('source_path', ''))
|
||||
target_path = clean_path(data.get('target_path', ''))
|
||||
|
||||
if not filename or '/' in filename or '\\' in filename or filename.startswith('.'):
|
||||
return jsonify({'error': 'Invalid filename'}), 400
|
||||
|
||||
# Lookup in RoomFile
|
||||
rf = RoomFile.query.filter_by(room_id=room_id, name=filename, path=source_path).first()
|
||||
if not rf:
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
room_dir = get_room_dir(room_id)
|
||||
source_dir = os.path.join(room_dir, source_path) if source_path else room_dir
|
||||
target_dir = os.path.join(room_dir, target_path) if target_path else room_dir
|
||||
|
||||
source_file_path = os.path.join(source_dir, filename)
|
||||
target_file_path = os.path.join(target_dir, filename)
|
||||
|
||||
if not os.path.exists(source_file_path):
|
||||
return jsonify({'error': 'Source file not found'}), 404
|
||||
|
||||
if os.path.exists(target_file_path):
|
||||
return jsonify({'error': 'A file with this name already exists in the target location'}), 400
|
||||
|
||||
# Create target directory if it doesn't exist
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
# Move the file
|
||||
shutil.move(source_file_path, target_file_path)
|
||||
|
||||
# Update RoomFile entry
|
||||
rf.path = target_path
|
||||
rf.modified = os.path.getmtime(target_file_path)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/folders', methods=['GET'])
|
||||
@login_required
|
||||
def list_room_folders(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
|
||||
# Get all files in the room that are not deleted
|
||||
files = RoomFile.query.filter_by(room_id=room_id, deleted=False).all()
|
||||
|
||||
# Extract unique folder paths
|
||||
folders = set()
|
||||
for f in files:
|
||||
if f.type == 'folder':
|
||||
full_path = f.path + '/' + f.name if f.path else f.name
|
||||
folders.add(full_path)
|
||||
|
||||
# Convert to sorted list
|
||||
folder_list = sorted(list(folders))
|
||||
|
||||
return jsonify(folder_list)
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/star', methods=['POST'])
|
||||
@login_required
|
||||
def toggle_star(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
|
||||
data = request.get_json()
|
||||
filename = data.get('filename', '').strip()
|
||||
rel_path = clean_path(data.get('path', ''))
|
||||
|
||||
if not filename:
|
||||
return jsonify({'error': 'Invalid filename'}), 400
|
||||
|
||||
# Lookup in RoomFile
|
||||
rf = RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path).first()
|
||||
if not rf:
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
# Check if the file is already starred by this user
|
||||
is_starred = current_user in rf.starred_by
|
||||
|
||||
if is_starred:
|
||||
# Unstar the file
|
||||
rf.starred_by.remove(current_user)
|
||||
else:
|
||||
# Star the file
|
||||
rf.starred_by.append(current_user)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True, 'starred': not is_starred})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/starred', methods=['GET'])
|
||||
@login_required
|
||||
def get_starred_files(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
|
||||
# Get all starred files in the room
|
||||
files = RoomFile.query.filter_by(room_id=room_id, starred=True).all()
|
||||
|
||||
result = []
|
||||
for f in files:
|
||||
uploader_full_name = None
|
||||
uploader_profile_pic = None
|
||||
if f.uploader:
|
||||
uploader_full_name = f.uploader.username
|
||||
if getattr(f.uploader, 'last_name', None):
|
||||
uploader_full_name += ' ' + f.uploader.last_name
|
||||
uploader_profile_pic = f.uploader.profile_picture if getattr(f.uploader, 'profile_picture', None) else None
|
||||
result.append({
|
||||
'name': f.name,
|
||||
'type': f.type,
|
||||
'size': f.size if f.type == 'file' else '-',
|
||||
'modified': f.modified,
|
||||
'uploaded_by': uploader_full_name,
|
||||
'uploader_profile_pic': uploader_profile_pic,
|
||||
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
||||
'path': f.path,
|
||||
'starred': f.starred
|
||||
})
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@room_files_bp.route('/starred', methods=['GET'])
|
||||
@login_required
|
||||
def get_all_starred_files():
|
||||
# Get all rooms the user has access to
|
||||
if current_user.is_admin:
|
||||
rooms = Room.query.all()
|
||||
else:
|
||||
rooms = Room.query.filter(Room.members.any(id=current_user.id)).all()
|
||||
|
||||
room_ids = [room.id for room in rooms]
|
||||
room_names = {room.id: room.name for room in rooms}
|
||||
|
||||
# Get all files starred by the current user from accessible rooms
|
||||
files = RoomFile.query.filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.starred_by.contains(current_user)
|
||||
).all()
|
||||
|
||||
result = []
|
||||
for f in files:
|
||||
uploader_full_name = None
|
||||
uploader_profile_pic = None
|
||||
if f.uploader:
|
||||
uploader_full_name = f.uploader.username
|
||||
if getattr(f.uploader, 'last_name', None):
|
||||
uploader_full_name += ' ' + f.uploader.last_name
|
||||
uploader_profile_pic = f.uploader.profile_picture if getattr(f.uploader, 'profile_picture', None) else None
|
||||
result.append({
|
||||
'name': f.name,
|
||||
'type': f.type,
|
||||
'size': f.size if f.type == 'file' else '-',
|
||||
'modified': f.modified,
|
||||
'uploaded_by': uploader_full_name,
|
||||
'uploader_profile_pic': uploader_profile_pic,
|
||||
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
||||
'path': f.path,
|
||||
'starred': True,
|
||||
'room_id': f.room_id,
|
||||
'room_name': room_names.get(f.room_id)
|
||||
})
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@room_files_bp.route('/trash', methods=['GET'])
|
||||
@login_required
|
||||
def get_trash_files():
|
||||
# Get all rooms the user has access to
|
||||
if current_user.is_admin:
|
||||
rooms = Room.query.all()
|
||||
else:
|
||||
rooms = Room.query.filter(Room.members.any(id=current_user.id)).all()
|
||||
|
||||
room_ids = [room.id for room in rooms]
|
||||
room_names = {room.id: room.name for room in rooms}
|
||||
|
||||
# Get all deleted files from accessible rooms
|
||||
files = RoomFile.query.filter(
|
||||
RoomFile.room_id.in_(room_ids),
|
||||
RoomFile.deleted == True
|
||||
).all()
|
||||
|
||||
result = []
|
||||
for f in files:
|
||||
uploader_full_name = None
|
||||
uploader_profile_pic = None
|
||||
if f.uploader:
|
||||
uploader_full_name = f.uploader.username
|
||||
if getattr(f.uploader, 'last_name', None):
|
||||
uploader_full_name += ' ' + f.uploader.last_name
|
||||
uploader_profile_pic = f.uploader.profile_picture if getattr(f.uploader, 'profile_picture', None) else None
|
||||
|
||||
deleter_full_name = None
|
||||
if f.deleter:
|
||||
deleter_full_name = f.deleter.username
|
||||
if getattr(f.deleter, 'last_name', None):
|
||||
deleter_full_name += ' ' + f.deleter.last_name
|
||||
|
||||
# Check if user has delete permission in this room
|
||||
room = Room.query.get(f.room_id)
|
||||
has_delete_permission = user_has_permission(room, 'can_delete') if room else False
|
||||
|
||||
# Check if user can restore this file
|
||||
can_restore = has_delete_permission and f.deleted_by == current_user.id
|
||||
|
||||
result.append({
|
||||
'name': f.name,
|
||||
'type': f.type,
|
||||
'size': f.size if f.type == 'file' else '-',
|
||||
'modified': f.modified,
|
||||
'uploaded_by': uploader_full_name,
|
||||
'uploader_profile_pic': uploader_profile_pic,
|
||||
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
||||
'path': f.path,
|
||||
'room_id': f.room_id,
|
||||
'room_name': room_names.get(f.room_id),
|
||||
'deleted_by': deleter_full_name,
|
||||
'deleted_at': f.deleted_at.isoformat() if f.deleted_at else None,
|
||||
'can_restore': can_restore
|
||||
})
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/restore', methods=['POST'])
|
||||
@login_required
|
||||
def restore_file(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Check for delete permission instead of view permission
|
||||
if not user_has_permission(room, 'can_delete'):
|
||||
abort(403)
|
||||
|
||||
data = request.get_json()
|
||||
filename = data.get('filename', '').strip()
|
||||
rel_path = clean_path(data.get('path', ''))
|
||||
|
||||
if not filename:
|
||||
return jsonify({'error': 'Invalid filename'}), 400
|
||||
|
||||
# Lookup in RoomFile
|
||||
rf = RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path).first()
|
||||
if not rf:
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
# Check if the current user was the one who deleted the file
|
||||
if rf.deleted_by != current_user.id:
|
||||
return jsonify({'error': 'You can only restore files that you deleted'}), 403
|
||||
|
||||
# Restore file by setting deleted to False
|
||||
rf.deleted = False
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@room_files_bp.route('/<int:room_id>/delete-permanent', methods=['POST'])
|
||||
@login_required
|
||||
def delete_permanent(room_id):
|
||||
# Only allow admin users to permanently delete files
|
||||
if not current_user.is_admin:
|
||||
abort(403)
|
||||
|
||||
room = Room.query.get_or_404(room_id)
|
||||
data = request.get_json()
|
||||
filename = data.get('filename', '').strip()
|
||||
rel_path = clean_path(data.get('path', ''))
|
||||
|
||||
if not filename:
|
||||
return jsonify({'error': 'Invalid filename'}), 400
|
||||
|
||||
# If filename is '*', delete all deleted files in the room
|
||||
if filename == '*':
|
||||
files_to_delete = RoomFile.query.filter_by(room_id=room_id, deleted=True).all()
|
||||
else:
|
||||
# Lookup specific file
|
||||
files_to_delete = [RoomFile.query.filter_by(room_id=room_id, name=filename, path=rel_path).first()]
|
||||
if not files_to_delete[0]:
|
||||
return jsonify({'error': 'File not found'}), 404
|
||||
|
||||
for rf in files_to_delete:
|
||||
if not rf:
|
||||
continue
|
||||
|
||||
# Delete the file from storage if it's a file
|
||||
if rf.type == 'file':
|
||||
try:
|
||||
file_path = os.path.join(get_room_dir(room_id), rf.path, rf.name)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
except Exception as e:
|
||||
print(f"Error deleting file from storage: {e}")
|
||||
|
||||
# Delete the database record
|
||||
db.session.delete(rf)
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True})
|
||||
121
routes/room_members.py
Normal file
121
routes/room_members.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from flask import Blueprint, jsonify, request, abort
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Room, User, RoomMemberPermission
|
||||
from utils import user_has_permission
|
||||
|
||||
room_members_bp = Blueprint('room_members', __name__)
|
||||
|
||||
@room_members_bp.route('/<int:room_id>/members', methods=['GET'])
|
||||
@login_required
|
||||
def list_room_members(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
|
||||
members = []
|
||||
for member in room.members:
|
||||
permission = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=member.id).first()
|
||||
members.append({
|
||||
'id': member.id,
|
||||
'username': member.username,
|
||||
'last_name': member.last_name,
|
||||
'email': member.email,
|
||||
'profile_picture': member.profile_picture,
|
||||
'permissions': {
|
||||
'can_view': permission.can_view if permission else False,
|
||||
'can_download': permission.can_download if permission else False,
|
||||
'can_upload': permission.can_upload if permission else False,
|
||||
'can_delete': permission.can_delete if permission else False,
|
||||
'can_rename': permission.can_rename if permission else False,
|
||||
'can_move': permission.can_move if permission else False,
|
||||
'can_share': permission.can_share if permission else False
|
||||
}
|
||||
})
|
||||
|
||||
return jsonify(members)
|
||||
|
||||
@room_members_bp.route('/<int:room_id>/members', methods=['POST'])
|
||||
@login_required
|
||||
def add_room_member(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_share'):
|
||||
abort(403)
|
||||
|
||||
data = request.get_json()
|
||||
user_id = data.get('user_id')
|
||||
permissions = data.get('permissions', {})
|
||||
|
||||
if not user_id:
|
||||
return jsonify({'error': 'User ID is required'}), 400
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# Add user to room members
|
||||
if user not in room.members:
|
||||
room.members.append(user)
|
||||
|
||||
# Update permissions
|
||||
permission = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first()
|
||||
if not permission:
|
||||
permission = RoomMemberPermission(room_id=room_id, user_id=user_id)
|
||||
db.session.add(permission)
|
||||
|
||||
permission.can_view = permissions.get('can_view', True)
|
||||
permission.can_download = permissions.get('can_download', False)
|
||||
permission.can_upload = permissions.get('can_upload', False)
|
||||
permission.can_delete = permissions.get('can_delete', False)
|
||||
permission.can_rename = permissions.get('can_rename', False)
|
||||
permission.can_move = permissions.get('can_move', False)
|
||||
permission.can_share = permissions.get('can_share', False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@room_members_bp.route('/<int:room_id>/members/<int:user_id>', methods=['DELETE'])
|
||||
@login_required
|
||||
def remove_room_member(room_id, user_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_share'):
|
||||
abort(403)
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# Remove user from room members
|
||||
if user in room.members:
|
||||
room.members.remove(user)
|
||||
|
||||
# Remove permissions
|
||||
permission = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first()
|
||||
if permission:
|
||||
db.session.delete(permission)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@room_members_bp.route('/<int:room_id>/members/<int:user_id>/permissions', methods=['PUT'])
|
||||
@login_required
|
||||
def update_member_permissions(room_id, user_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_share'):
|
||||
abort(403)
|
||||
|
||||
data = request.get_json()
|
||||
permissions = data.get('permissions', {})
|
||||
|
||||
permission = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first()
|
||||
if not permission:
|
||||
return jsonify({'error': 'User is not a member of this room'}), 404
|
||||
|
||||
permission.can_view = permissions.get('can_view', permission.can_view)
|
||||
permission.can_download = permissions.get('can_download', permission.can_download)
|
||||
permission.can_upload = permissions.get('can_upload', permission.can_upload)
|
||||
permission.can_delete = permissions.get('can_delete', permission.can_delete)
|
||||
permission.can_rename = permissions.get('can_rename', permission.can_rename)
|
||||
permission.can_move = permissions.get('can_move', permission.can_move)
|
||||
permission.can_share = permissions.get('can_share', permission.can_share)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
223
routes/rooms.py
Normal file
223
routes/rooms.py
Normal file
@@ -0,0 +1,223 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Room, User, RoomMemberPermission, RoomFile
|
||||
from forms import RoomForm
|
||||
from routes.room_files import user_has_permission
|
||||
|
||||
rooms_bp = Blueprint('rooms', __name__, url_prefix='/rooms')
|
||||
|
||||
@rooms_bp.route('/')
|
||||
@login_required
|
||||
def rooms():
|
||||
search = request.args.get('search', '').strip()
|
||||
if current_user.is_admin:
|
||||
query = Room.query
|
||||
else:
|
||||
query = Room.query.filter(Room.members.any(id=current_user.id))
|
||||
if search:
|
||||
query = query.filter(Room.name.ilike(f'%{search}%'))
|
||||
rooms = query.order_by(Room.created_at.desc()).all()
|
||||
return render_template('rooms.html', rooms=rooms, search=search)
|
||||
|
||||
@rooms_bp.route('/create', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def create_room():
|
||||
form = RoomForm()
|
||||
if form.validate_on_submit():
|
||||
room = Room(
|
||||
name=form.name.data,
|
||||
description=form.description.data,
|
||||
created_by=current_user.id
|
||||
)
|
||||
|
||||
# Add creator as a member with full permissions
|
||||
room.members.append(current_user)
|
||||
creator_permission = RoomMemberPermission(
|
||||
room=room,
|
||||
user=current_user,
|
||||
can_view=True,
|
||||
can_upload=True,
|
||||
can_delete=True,
|
||||
can_share=True
|
||||
)
|
||||
db.session.add(room)
|
||||
db.session.add(creator_permission)
|
||||
db.session.commit()
|
||||
|
||||
flash('Room created successfully!', 'success')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
return render_template('create_room.html', form=form)
|
||||
|
||||
@rooms_bp.route('/<int:room_id>')
|
||||
@login_required
|
||||
def room(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Admins always have access
|
||||
if not current_user.is_admin:
|
||||
is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None
|
||||
if not is_member:
|
||||
flash('You do not have access to this room.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
can_download = user_has_permission(room, 'can_download')
|
||||
can_upload = user_has_permission(room, 'can_upload')
|
||||
can_delete = user_has_permission(room, 'can_delete')
|
||||
can_rename = user_has_permission(room, 'can_rename')
|
||||
can_move = user_has_permission(room, 'can_move')
|
||||
can_share = user_has_permission(room, 'can_share')
|
||||
return render_template('room.html', room=room, can_download=can_download, can_upload=can_upload, can_delete=can_delete, can_rename=can_rename, can_move=can_move, can_share=can_share)
|
||||
|
||||
@rooms_bp.route('/<int:room_id>/members')
|
||||
@login_required
|
||||
def room_members(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Admins always have access
|
||||
if not current_user.is_admin:
|
||||
is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None
|
||||
if not is_member:
|
||||
flash('You do not have access to this room.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
if not current_user.is_admin:
|
||||
flash('Only administrators can manage room members.', 'error')
|
||||
return redirect(url_for('rooms.room', room_id=room_id))
|
||||
member_permissions = {p.user_id: p for p in room.member_permissions}
|
||||
available_users = User.query.filter(~User.id.in_(member_permissions.keys())).all()
|
||||
return render_template('room_members.html', room=room, available_users=available_users, member_permissions=member_permissions)
|
||||
|
||||
@rooms_bp.route('/<int:room_id>/members/add', methods=['POST'])
|
||||
@login_required
|
||||
def add_member(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Membership check using RoomMemberPermission
|
||||
is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None
|
||||
if not is_member:
|
||||
flash('You do not have access to this room.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
if not current_user.is_admin:
|
||||
flash('Only administrators can manage room members.', 'error')
|
||||
return redirect(url_for('rooms.room', room_id=room_id))
|
||||
user_id = request.form.get('user_id')
|
||||
if not user_id:
|
||||
flash('Please select a user to add.', 'error')
|
||||
return redirect(url_for('rooms.room_members', room_id=room_id))
|
||||
user = User.query.get_or_404(user_id)
|
||||
# Check if already a member
|
||||
if RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user.id).first():
|
||||
flash('User is already a member of this room.', 'error')
|
||||
else:
|
||||
perm = RoomMemberPermission(room_id=room_id, user_id=user.id, can_view=True)
|
||||
db.session.add(perm)
|
||||
# Ensure user is added to the room.members relationship
|
||||
if user not in room.members:
|
||||
room.members.append(user)
|
||||
db.session.commit()
|
||||
flash(f'{user.username} has been added to the room.', 'success')
|
||||
return redirect(url_for('rooms.room_members', room_id=room_id))
|
||||
|
||||
@rooms_bp.route('/<int:room_id>/members/<int:user_id>/remove', methods=['POST'])
|
||||
@login_required
|
||||
def remove_member(room_id, user_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Membership check using RoomMemberPermission
|
||||
is_member = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=current_user.id).first() is not None
|
||||
if not is_member:
|
||||
flash('You do not have access to this room.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
if not current_user.is_admin:
|
||||
flash('Only administrators can manage room members.', 'error')
|
||||
return redirect(url_for('rooms.room', room_id=room_id))
|
||||
if user_id == room.created_by:
|
||||
flash('Cannot remove the room creator.', 'error')
|
||||
else:
|
||||
perm = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first()
|
||||
if not perm:
|
||||
flash('User is not a member of this room.', 'error')
|
||||
else:
|
||||
db.session.delete(perm)
|
||||
db.session.commit()
|
||||
flash('User has been removed from the room.', 'success')
|
||||
return redirect(url_for('rooms.room_members', room_id=room_id))
|
||||
|
||||
@rooms_bp.route('/<int:room_id>/members/<int:user_id>/permissions', methods=['POST'])
|
||||
@login_required
|
||||
def update_member_permissions(room_id, user_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not current_user.is_admin:
|
||||
flash('Only administrators can update permissions.', 'error')
|
||||
return redirect(url_for('rooms.room_members', room_id=room_id))
|
||||
perm = RoomMemberPermission.query.filter_by(room_id=room_id, user_id=user_id).first()
|
||||
if not perm:
|
||||
flash('Member not found.', 'error')
|
||||
return redirect(url_for('rooms.room_members', room_id=room_id))
|
||||
perm.can_view = bool(request.form.get('can_view'))
|
||||
perm.can_download = bool(request.form.get('can_download'))
|
||||
perm.can_upload = bool(request.form.get('can_upload'))
|
||||
perm.can_delete = bool(request.form.get('can_delete'))
|
||||
perm.can_rename = bool(request.form.get('can_rename'))
|
||||
perm.can_move = bool(request.form.get('can_move'))
|
||||
perm.can_share = bool(request.form.get('can_share'))
|
||||
db.session.commit()
|
||||
flash('Permissions updated.', 'success')
|
||||
return redirect(url_for('rooms.room_members', room_id=room_id))
|
||||
|
||||
@rooms_bp.route('/<int:room_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_room(room_id):
|
||||
if not current_user.is_admin:
|
||||
flash('Only administrators can edit rooms.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
|
||||
room = Room.query.get_or_404(room_id)
|
||||
form = RoomForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
room.name = form.name.data
|
||||
room.description = form.description.data
|
||||
db.session.commit()
|
||||
flash('Room updated successfully!', 'success')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
|
||||
# Pre-populate form with existing room data
|
||||
if request.method == 'GET':
|
||||
form.name.data = room.name
|
||||
form.description.data = room.description
|
||||
|
||||
return render_template('edit_room.html', form=form, room=room)
|
||||
|
||||
@rooms_bp.route('/<int:room_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
def delete_room(room_id):
|
||||
if not current_user.is_admin:
|
||||
flash('Only administrators can delete rooms.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
|
||||
room = Room.query.get_or_404(room_id)
|
||||
room_name = room.name
|
||||
|
||||
try:
|
||||
print(f"Attempting to delete room {room_id} ({room_name})")
|
||||
|
||||
# Delete the room (cascade will handle the rest)
|
||||
db.session.delete(room)
|
||||
db.session.commit()
|
||||
print("Room deleted successfully")
|
||||
|
||||
flash(f'Room "{room_name}" has been deleted.', 'success')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash('An error occurred while deleting the room. Please try again.', 'error')
|
||||
print(f"Error deleting room: {str(e)}")
|
||||
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
|
||||
@rooms_bp.route('/room/<int:room_id>/view/<path:file_path>')
|
||||
@login_required
|
||||
def view_file(room_id, file_path):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
# Check if user has access to the room
|
||||
if not current_user.is_admin and current_user not in room.members:
|
||||
flash('You do not have access to this room.', 'error')
|
||||
return redirect(url_for('rooms.rooms'))
|
||||
|
||||
file = RoomFile.query.filter_by(room_id=room_id, path=file_path).first_or_404()
|
||||
|
||||
# Continue with file viewing logic...
|
||||
110
routes/trash.py
Normal file
110
routes/trash.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from flask import Blueprint, jsonify, request, abort
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Room, RoomFile, TrashedFile
|
||||
from utils import user_has_permission, clean_path
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
trash_bp = Blueprint('trash', __name__)
|
||||
|
||||
@trash_bp.route('/<int:room_id>/trash', methods=['GET'])
|
||||
@login_required
|
||||
def list_trashed_files(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_view'):
|
||||
abort(403)
|
||||
|
||||
# Get all trashed files in the room
|
||||
files = TrashedFile.query.filter_by(room_id=room_id).order_by(TrashedFile.deleted_at.desc()).all()
|
||||
|
||||
result = []
|
||||
for f in files:
|
||||
uploader_full_name = None
|
||||
uploader_profile_pic = None
|
||||
if f.uploader:
|
||||
uploader_full_name = f.uploader.username
|
||||
if getattr(f.uploader, 'last_name', None):
|
||||
uploader_full_name += ' ' + f.uploader.last_name
|
||||
uploader_profile_pic = f.uploader.profile_picture if getattr(f.uploader, 'profile_picture', None) else None
|
||||
|
||||
deleter_full_name = None
|
||||
if f.deleter:
|
||||
deleter_full_name = f.deleter.username
|
||||
if getattr(f.deleter, 'last_name', None):
|
||||
deleter_full_name += ' ' + f.deleter.last_name
|
||||
|
||||
result.append({
|
||||
'id': f.id,
|
||||
'name': f.name,
|
||||
'type': f.type,
|
||||
'size': f.size if f.type == 'file' else '-',
|
||||
'modified': f.modified,
|
||||
'uploaded_by': uploader_full_name,
|
||||
'uploader_profile_pic': uploader_profile_pic,
|
||||
'uploaded_at': f.uploaded_at.isoformat() if f.uploaded_at else None,
|
||||
'original_path': f.original_path,
|
||||
'deleted_by': deleter_full_name,
|
||||
'deleted_at': f.deleted_at.isoformat()
|
||||
})
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@trash_bp.route('/<int:room_id>/trash/<int:trash_id>/restore', methods=['POST'])
|
||||
@login_required
|
||||
def restore_file(room_id, trash_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_upload'):
|
||||
abort(403)
|
||||
|
||||
trashed_file = TrashedFile.query.get_or_404(trash_id)
|
||||
if trashed_file.room_id != room_id:
|
||||
abort(404)
|
||||
|
||||
# Create new RoomFile entry
|
||||
rf = RoomFile(
|
||||
room_id=room_id,
|
||||
name=trashed_file.name,
|
||||
path=trashed_file.original_path,
|
||||
type=trashed_file.type,
|
||||
size=trashed_file.size,
|
||||
modified=trashed_file.modified,
|
||||
uploaded_by=trashed_file.uploaded_by,
|
||||
uploaded_at=trashed_file.uploaded_at
|
||||
)
|
||||
db.session.add(rf)
|
||||
|
||||
# Delete the trashed file entry
|
||||
db.session.delete(trashed_file)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@trash_bp.route('/<int:room_id>/trash/<int:trash_id>', methods=['DELETE'])
|
||||
@login_required
|
||||
def permanently_delete_file(room_id, trash_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_delete'):
|
||||
abort(403)
|
||||
|
||||
trashed_file = TrashedFile.query.get_or_404(trash_id)
|
||||
if trashed_file.room_id != room_id:
|
||||
abort(404)
|
||||
|
||||
# Delete the trashed file entry
|
||||
db.session.delete(trashed_file)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
@trash_bp.route('/<int:room_id>/trash/empty', methods=['POST'])
|
||||
@login_required
|
||||
def empty_trash(room_id):
|
||||
room = Room.query.get_or_404(room_id)
|
||||
if not user_has_permission(room, 'can_delete'):
|
||||
abort(403)
|
||||
|
||||
# Delete all trashed files for this room
|
||||
TrashedFile.query.filter_by(room_id=room_id).delete()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
22
routes/user.py
Normal file
22
routes/user.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_login import login_required, current_user
|
||||
from models import db
|
||||
|
||||
user_bp = Blueprint('user', __name__, url_prefix='/api/user')
|
||||
|
||||
@user_bp.route('/preferred_view', methods=['GET'])
|
||||
@login_required
|
||||
def get_preferred_view():
|
||||
return jsonify({'preferred_view': current_user.preferred_view})
|
||||
|
||||
@user_bp.route('/preferred_view', methods=['POST'])
|
||||
@login_required
|
||||
def update_preferred_view():
|
||||
data = request.get_json()
|
||||
if not data or 'preferred_view' not in data:
|
||||
return jsonify({'error': 'Missing preferred_view'}), 400
|
||||
if data['preferred_view'] not in ['grid', 'list']:
|
||||
return jsonify({'error': 'Invalid preferred_view'}), 400
|
||||
current_user.preferred_view = data['preferred_view']
|
||||
db.session.commit()
|
||||
return jsonify({'preferred_view': current_user.preferred_view})
|
||||
Reference in New Issue
Block a user