przebudowa endpointow i inne porządki
This commit is contained in:
188
app.py
188
app.py
@@ -34,6 +34,17 @@ UPLOAD_FOLDER = app.config.get('UPLOAD_FOLDER', 'uploads')
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
AUTHORIZED_COOKIE_VALUE = app.config.get('AUTHORIZED_COOKIE_VALUE', '80d31cdfe63539c9')
|
||||
|
||||
PROTECTED_JS_FILES = {
|
||||
"live.js",
|
||||
"list_share.js",
|
||||
"hide_list.js",
|
||||
"socket_reconnect.js",
|
||||
"product_suggestion.js",
|
||||
"expenses.js",
|
||||
"toggle_button.js",
|
||||
"user_management.js",
|
||||
}
|
||||
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
|
||||
failed_login_attempts = defaultdict(deque)
|
||||
@@ -217,12 +228,10 @@ def require_system_password():
|
||||
and request.endpoint != 'system_auth' \
|
||||
and not request.endpoint.startswith('login') \
|
||||
and request.endpoint != 'favicon':
|
||||
# specjalny wyjątek dla statycznych, ale sprawdzany ręcznie niżej
|
||||
|
||||
if request.endpoint == 'static_bp.serve_js':
|
||||
# tu sprawdzamy czy to JS, który ma być chroniony
|
||||
protected_js = ["live.js", "list_guest.js", "hide_list.js", "socket_reconnect.js","product_suggestion.js", "expenses.js", "toggle_button.js"]
|
||||
requested_file = request.view_args.get("filename", "")
|
||||
if requested_file in protected_js:
|
||||
if requested_file in PROTECTED_JS_FILES:
|
||||
return redirect(url_for('system_auth', next=request.url))
|
||||
else:
|
||||
return # pozwól na inne pliki statyczne
|
||||
@@ -238,6 +247,7 @@ def require_system_password():
|
||||
fixed_url = urlunparse(parsed._replace(netloc=request.host))
|
||||
return redirect(url_for('system_auth', next=fixed_url))
|
||||
|
||||
|
||||
@app.template_filter('filemtime')
|
||||
def file_mtime_filter(path):
|
||||
try:
|
||||
@@ -260,11 +270,30 @@ def filesizeformat_filter(path):
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
return render_template(
|
||||
'errors.html',
|
||||
code=404,
|
||||
title="Strona nie znaleziona",
|
||||
message="Ups! Podana strona nie istnieje lub została przeniesiona."
|
||||
), 404
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden(e):
|
||||
return '403 Forbidden', 403
|
||||
return render_template(
|
||||
'errors.html',
|
||||
code=403,
|
||||
title="Brak dostępu",
|
||||
message="Nie masz uprawnień do wyświetlenia tej strony."
|
||||
), 403
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(e):
|
||||
return render_template(
|
||||
'errors.html',
|
||||
code=500,
|
||||
title="Błąd serwera",
|
||||
message="Wystąpił nieoczekiwany błąd. Spróbuj ponownie później."
|
||||
), 500
|
||||
|
||||
@app.route('/favicon.svg')
|
||||
def favicon():
|
||||
@@ -276,7 +305,7 @@ def favicon():
|
||||
return svg, 200, {'Content-Type': 'image/svg+xml'}
|
||||
|
||||
@app.route('/')
|
||||
def index_guest():
|
||||
def main_page():
|
||||
now = datetime.utcnow()
|
||||
|
||||
if current_user.is_authenticated:
|
||||
@@ -318,7 +347,7 @@ def index_guest():
|
||||
def system_auth():
|
||||
#ip = request.remote_addr
|
||||
ip = request.access_route[0]
|
||||
next_page = request.args.get('next') or url_for('index_guest')
|
||||
next_page = request.args.get('next') or url_for('main_page')
|
||||
|
||||
if is_ip_blocked(ip):
|
||||
flash('Przekroczono limit prób logowania. Dostęp zablokowany na 1 godzinę.', 'danger')
|
||||
@@ -344,7 +373,7 @@ def toggle_archive_list(list_id):
|
||||
l = ShoppingList.query.get_or_404(list_id)
|
||||
if l.owner_id != current_user.id:
|
||||
flash('Nie masz uprawnień do tej listy', 'danger')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
# Pobieramy parametr archive z query string
|
||||
archive = request.args.get('archive', 'true').lower() == 'true'
|
||||
@@ -357,7 +386,7 @@ def toggle_archive_list(list_id):
|
||||
flash(f'Lista „{l.title}” została przywrócona.', 'success')
|
||||
|
||||
db.session.commit()
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
@app.route('/edit_my_list/<int:list_id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@@ -365,7 +394,7 @@ def edit_my_list(list_id):
|
||||
l = ShoppingList.query.get_or_404(list_id)
|
||||
if l.owner_id != current_user.id:
|
||||
flash('Nie masz uprawnień do tej listy', 'danger')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
if request.method == 'POST':
|
||||
new_title = request.form.get('title')
|
||||
@@ -373,7 +402,7 @@ def edit_my_list(list_id):
|
||||
l.title = new_title.strip()
|
||||
db.session.commit()
|
||||
flash('Zaktualizowano tytuł listy', 'success')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
else:
|
||||
flash('Podaj poprawny tytuł', 'danger')
|
||||
return render_template('edit_my_list.html', list=l)
|
||||
@@ -386,7 +415,7 @@ def toggle_visibility(list_id):
|
||||
if request.is_json or request.method == 'POST':
|
||||
return {'error': 'Unauthorized'}, 403
|
||||
flash('Nie masz uprawnień do tej listy', 'danger')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
l.is_public = not l.is_public
|
||||
db.session.commit()
|
||||
@@ -401,7 +430,7 @@ def toggle_visibility(list_id):
|
||||
else:
|
||||
flash('Lista została ukryta przed gośćmi', 'info')
|
||||
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
@@ -410,7 +439,7 @@ def login():
|
||||
if user and check_password_hash(user.password_hash, request.form['password']):
|
||||
login_user(user)
|
||||
flash('Zalogowano pomyślnie', 'success')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
flash('Nieprawidłowy login lub hasło', 'danger')
|
||||
return render_template('login.html')
|
||||
|
||||
@@ -419,7 +448,7 @@ def login():
|
||||
def logout():
|
||||
logout_user()
|
||||
flash('Wylogowano pomyślnie', 'success')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
@app.route('/create', methods=['POST'])
|
||||
@login_required
|
||||
@@ -467,7 +496,7 @@ def share_list(token):
|
||||
|
||||
if not shopping_list.is_public:
|
||||
flash('Ta lista nie jest publicznie dostępna', 'danger')
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
items = Item.query.filter_by(list_id=shopping_list.id).all()
|
||||
|
||||
@@ -479,7 +508,7 @@ def share_list(token):
|
||||
total_expense = sum(e.amount for e in expenses)
|
||||
|
||||
return render_template(
|
||||
'list_guest.html',
|
||||
'list_share.html',
|
||||
list=shopping_list,
|
||||
items=items,
|
||||
receipt_files=receipt_files,
|
||||
@@ -497,7 +526,7 @@ def guest_list(list_id):
|
||||
expenses = Expense.query.filter_by(list_id=list_id).all()
|
||||
total_expense = sum(e.amount for e in expenses)
|
||||
return render_template(
|
||||
'list_guest.html',
|
||||
'list_share.html',
|
||||
list=shopping_list,
|
||||
items=items,
|
||||
receipt_files=receipt_files,
|
||||
@@ -570,7 +599,7 @@ def uploaded_file(filename):
|
||||
@login_required
|
||||
def admin_panel():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
now = datetime.utcnow()
|
||||
user_count = User.query.count()
|
||||
@@ -652,7 +681,7 @@ def admin_panel():
|
||||
@login_required
|
||||
def delete_list(list_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
delete_receipts_for_list(list_id)
|
||||
list_to_delete = ShoppingList.query.get_or_404(list_id)
|
||||
Item.query.filter_by(list_id=list_to_delete.id).delete()
|
||||
@@ -662,63 +691,86 @@ def delete_list(list_id):
|
||||
flash(f'Usunięto listę: {list_to_delete.title}', 'success')
|
||||
return redirect(url_for('admin_panel'))
|
||||
|
||||
@app.route('/admin/add_user', methods=['GET', 'POST'])
|
||||
@app.route('/admin/add_user', methods=['POST'])
|
||||
@login_required
|
||||
def add_user():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = generate_password_hash(request.form['password'])
|
||||
new_user = User(username=username, password_hash=password)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
flash('Dodano nowego użytkownika', 'success')
|
||||
return redirect(url_for('admin_panel'))
|
||||
return render_template('admin/add_user.html')
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
if not username or not password:
|
||||
flash('Wypełnij wszystkie pola', 'danger')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash('Użytkownik o takiej nazwie już istnieje', 'warning')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
hashed_password = generate_password_hash(password)
|
||||
new_user = User(username=username, password_hash=hashed_password)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
flash('Dodano nowego użytkownika', 'success')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
@app.route('/admin/users')
|
||||
@login_required
|
||||
def list_users():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
users = User.query.all()
|
||||
user_count = User.query.count()
|
||||
list_count = ShoppingList.query.count()
|
||||
item_count = Item.query.count()
|
||||
activity_log = ["Utworzono listę: Zakupy weekendowe", "Dodano produkt: Mleko"]
|
||||
return render_template('admin/list_users.html', users=users, user_count=user_count, list_count=list_count, item_count=item_count, activity_log=activity_log)
|
||||
return render_template('admin/user_management.html', users=users, user_count=user_count, list_count=list_count, item_count=item_count, activity_log=activity_log)
|
||||
|
||||
@app.route('/admin/reset_password/<int:user_id>', methods=['GET', 'POST'])
|
||||
@app.route('/admin/change_password/<int:user_id>', methods=['POST'])
|
||||
@login_required
|
||||
def reset_password(user_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
if request.method == 'POST':
|
||||
new_password = generate_password_hash(request.form['password'])
|
||||
user.password_hash = new_password
|
||||
db.session.commit()
|
||||
flash('Hasło zresetowane', 'success')
|
||||
new_password = request.form['password']
|
||||
|
||||
if not new_password:
|
||||
flash('Podaj nowe hasło', 'danger')
|
||||
return redirect(url_for('list_users'))
|
||||
return render_template('admin/reset_password.html', user=user)
|
||||
|
||||
user.password_hash = generate_password_hash(new_password)
|
||||
db.session.commit()
|
||||
flash(f'Hasło dla użytkownika {user.username} zostało zaktualizowane', 'success')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
@app.route('/admin/delete_user/<int:user_id>')
|
||||
@login_required
|
||||
def delete_user(user_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# Zabezpieczenie: sprawdź ilu adminów
|
||||
if user.is_admin:
|
||||
admin_count = User.query.filter_by(is_admin=True).count()
|
||||
if admin_count <= 1:
|
||||
flash('Nie można usunąć ostatniego administratora.', 'danger')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
flash('Użytkownik usunięty', 'success')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
|
||||
@app.route('/admin/receipts')
|
||||
@login_required
|
||||
def admin_receipts():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
all_files = os.listdir(app.config['UPLOAD_FOLDER'])
|
||||
image_files = [f for f in all_files if allowed_file(f)]
|
||||
return render_template(
|
||||
@@ -731,7 +783,7 @@ def admin_receipts():
|
||||
@login_required
|
||||
def delete_receipt(filename):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
@@ -744,7 +796,7 @@ def delete_receipt(filename):
|
||||
@login_required
|
||||
def delete_selected_lists():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
ids = request.form.getlist('list_ids')
|
||||
for list_id in ids:
|
||||
lst = ShoppingList.query.get(int(list_id))
|
||||
@@ -761,7 +813,7 @@ def delete_selected_lists():
|
||||
@login_required
|
||||
def archive_list(list_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
l = ShoppingList.query.get_or_404(list_id)
|
||||
l.is_archived = True
|
||||
db.session.commit()
|
||||
@@ -772,7 +824,7 @@ def archive_list(list_id):
|
||||
@login_required
|
||||
def delete_all_items():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
Item.query.delete()
|
||||
db.session.commit()
|
||||
flash('Usunięto wszystkie produkty', 'success')
|
||||
@@ -782,7 +834,7 @@ def delete_all_items():
|
||||
@login_required
|
||||
def edit_list(list_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
l = ShoppingList.query.get_or_404(list_id)
|
||||
expenses = Expense.query.filter_by(list_id=list_id).all()
|
||||
@@ -841,7 +893,7 @@ def edit_list(list_id):
|
||||
@login_required
|
||||
def list_products():
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('index_guest'))
|
||||
return redirect(url_for('main_page'))
|
||||
|
||||
items = Item.query.order_by(Item.id.desc()).all()
|
||||
users = User.query.all()
|
||||
@@ -989,6 +1041,42 @@ def admin_expenses_data():
|
||||
response.headers["Cache-Control"] = "no-store, no-cache"
|
||||
return response
|
||||
|
||||
@app.route('/admin/promote_user/<int:user_id>')
|
||||
@login_required
|
||||
def promote_user(user_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('main_page'))
|
||||
user = User.query.get_or_404(user_id)
|
||||
user.is_admin = True
|
||||
db.session.commit()
|
||||
flash(f'Użytkownik {user.username} został ustawiony jako admin.', 'success')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
@app.route('/admin/demote_user/<int:user_id>')
|
||||
@login_required
|
||||
def demote_user(user_id):
|
||||
if not current_user.is_admin:
|
||||
return redirect(url_for('main_page'))
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
# Nie pozwalamy zdegradować siebie
|
||||
if user.id == current_user.id:
|
||||
flash('Nie możesz zdegradować samego siebie!', 'danger')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
# Zabezpieczenie: sprawdź ilu jest adminów
|
||||
admin_count = User.query.filter_by(is_admin=True).count()
|
||||
if admin_count <= 1 and user.is_admin:
|
||||
flash('Nie można zdegradować. Musi pozostać co najmniej jeden administrator.', 'danger')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
user.is_admin = False
|
||||
db.session.commit()
|
||||
flash(f'Użytkownik {user.username} został zdegradowany.', 'success')
|
||||
return redirect(url_for('list_users'))
|
||||
|
||||
|
||||
|
||||
# chyba do usuniecia przeniesione na eventy socket.io
|
||||
@app.route('/update-note/<int:item_id>', methods=['POST'])
|
||||
def update_note(item_id):
|
||||
|
Reference in New Issue
Block a user