|
import os |
|
import json |
|
from flask import Flask, request, g, Response, render_template, redirect, url_for, flash, session |
|
import psycopg2 |
|
from dotenv import load_dotenv |
|
from werkzeug.security import generate_password_hash, check_password_hash |
|
from functools import wraps |
|
|
|
|
|
load_dotenv() |
|
|
|
app = Flask(__name__) |
|
|
|
|
|
DB_HOST = os.getenv('DB_HOST') |
|
DB_NAME = os.getenv('DB_NAME') |
|
DB_USER = os.getenv('DB_USER') |
|
DB_PASSWORD = os.getenv('DB_PASSWORD') |
|
DB_PORT = os.getenv('DB_PORT') |
|
|
|
|
|
PAGE_PASSWORD = os.getenv('PAGE_PASSWORD') |
|
|
|
|
|
|
|
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'mot_chuoi_bi_mat_rat_ngau_nhien_va_dai') |
|
|
|
if not PAGE_PASSWORD: |
|
print("Cảnh báo: Biến môi trường 'PAGE_PASSWORD' chưa được đặt. Một số chức năng có thể không hoạt động đúng.") |
|
|
|
|
|
def get_db_connection(): |
|
if 'db' not in g: |
|
try: |
|
g.db = psycopg2.connect( |
|
host=DB_HOST, |
|
database=DB_NAME, |
|
user=DB_USER, |
|
password=DB_PASSWORD, |
|
port=DB_PORT |
|
) |
|
print("Kết nối đến Supabase PostgreSQL thành công!") |
|
except Exception as e: |
|
print(f"Lỗi khi kết nối đến cơ sở dữ liệu: {e}") |
|
g.db = None |
|
return g.db |
|
|
|
@app.teardown_appcontext |
|
def close_db_connection(exception): |
|
db = g.pop('db', None) |
|
if db is not None: |
|
db.close() |
|
print("Đã đóng kết nối Supabase PostgreSQL.") |
|
|
|
|
|
def custom_jsonify(data, status_code=200): |
|
response = Response( |
|
response=json.dumps(data, ensure_ascii=False), |
|
status=status_code, |
|
mimetype='application/json; charset=utf-8' |
|
) |
|
return response |
|
|
|
|
|
def require_page_password(f): |
|
@wraps(f) |
|
def decorated_function(*args, **kwargs): |
|
|
|
if session.get('page_logged_in'): |
|
return f(*args, **kwargs) |
|
|
|
|
|
provided_password = request.form.get('page_password') or request.args.get('page_password') |
|
if request.method == 'POST' and not provided_password: |
|
provided_password = request.headers.get('X-Page-Password') |
|
|
|
if not PAGE_PASSWORD: |
|
flash("Lỗi cấu hình server: Mật khẩu trang chưa được thiết lập.", "error") |
|
return redirect(url_for('home')) |
|
|
|
if provided_password == PAGE_PASSWORD: |
|
session['page_logged_in'] = True |
|
|
|
|
|
return redirect(request.url) |
|
else: |
|
flash("Truy cập bị từ chối: Mật khẩu trang không hợp lệ.", "error") |
|
return render_template('page_password_form.html', next_url=request.url) |
|
return decorated_function |
|
|
|
|
|
@app.route('/page_password', methods=['GET', 'POST']) |
|
def page_password_form(): |
|
next_url = request.args.get('next') or url_for('home') |
|
|
|
if request.method == 'POST': |
|
provided_password = request.form.get('page_password_input') |
|
|
|
if not PAGE_PASSWORD: |
|
flash("Lỗi cấu hình server: Mật khẩu trang chưa được thiết lập.", "error") |
|
return redirect(url_for('home')) |
|
|
|
if provided_password == PAGE_PASSWORD: |
|
session['page_logged_in'] = True |
|
flash("Mật khẩu trang đã được xác thực thành công!", "success") |
|
return redirect(next_url) |
|
else: |
|
flash("Mật khẩu trang không hợp lệ. Vui lòng thử lại.", "error") |
|
return render_template('page_password_form.html', next_url=next_url) |
|
|
|
|
|
return render_template('page_password_form.html', next_url=next_url) |
|
|
|
@app.route('/page_logout') |
|
def page_logout(): |
|
session.pop('page_logged_in', None) |
|
flash("Bạn đã đăng xuất khỏi phiên mật khẩu trang.", "info") |
|
return redirect(url_for('home')) |
|
|
|
|
|
|
|
@app.route('/') |
|
def home(): |
|
"""Trang chủ của ứng dụng.""" |
|
return render_template('index.html') |
|
|
|
|
|
@app.route('/test_db', methods=['GET']) |
|
@require_page_password |
|
def test_db(): |
|
"""Kiểm tra kết nối đến cơ sở dữ liệu và hiển thị kết quả.""" |
|
conn = get_db_connection() |
|
db_version_info = "Chưa kiểm tra" |
|
status_info = "none" |
|
if conn is None: |
|
flash("Không thể kết nối đến cơ sở dữ liệu.", "error") |
|
db_version_info = "Lỗi kết nối" |
|
status_info = "error" |
|
else: |
|
try: |
|
cur = conn.cursor() |
|
cur.execute("SELECT version();") |
|
db_version = cur.fetchone()[0] |
|
cur.close() |
|
flash("Kết nối cơ sở dữ liệu thành công!", "success") |
|
db_version_info = db_version |
|
status_info = "success" |
|
except Exception as e: |
|
flash(f"Lỗi truy vấn cơ sở dữ liệu: {e}", "error") |
|
db_version_info = "Lỗi truy vấn" |
|
status_info = "error" |
|
return render_template('test_db.html', db_version=db_version_info, status=status_info) |
|
|
|
|
|
@app.route('/create_table', methods=['GET']) |
|
@require_page_password |
|
def create_table(): |
|
"""Tạo bảng 'users' nếu chưa tồn tại và hiển thị kết quả.""" |
|
conn = get_db_connection() |
|
message_info = "Chưa tạo" |
|
status_info = "none" |
|
if conn is None: |
|
flash("Không thể kết nối đến cơ sở dữ liệu.", "error") |
|
message_info = "Lỗi kết nối" |
|
status_info = "error" |
|
else: |
|
try: |
|
cur = conn.cursor() |
|
cur.execute(""" |
|
CREATE TABLE IF NOT EXISTS users ( |
|
id SERIAL PRIMARY음을 PRIMARY KEY, |
|
name VARCHAR(100) NOT NULL, |
|
email VARCHAR(100) UNIQUE NOT NULL, |
|
password VARCHAR(255) NOT NULL |
|
); |
|
""") |
|
conn.commit() |
|
cur.close() |
|
flash("Bảng 'users' đã được tạo (nếu chưa tồn tại) với cột mật khẩu.", "success") |
|
message_info = "Tạo bảng thành công" |
|
status_info = "success" |
|
except Exception as e: |
|
conn.rollback() |
|
flash(f"Lỗi khi tạo bảng: {e}", "error") |
|
message_info = "Lỗi tạo bảng" |
|
status_info = "error" |
|
return render_template('create_table.html', message=message_info, status=status_info) |
|
|
|
|
|
@app.route('/add_user', methods=['GET', 'POST']) |
|
@require_page_password |
|
def add_user(): |
|
"""Thêm người dùng mới vào cơ sở dữ liệu với mật khẩu đã băm.""" |
|
if request.method == 'POST': |
|
name = request.form.get('name') |
|
email = request.form.get('email') |
|
plaintext_password = request.form.get('password') |
|
|
|
if not name or not email or not plaintext_password: |
|
flash("Vui lòng điền đầy đủ Tên, Email và Mật khẩu.", "error") |
|
return render_template('add_user.html') |
|
|
|
conn = get_db_connection() |
|
if conn is None: |
|
flash("Không thể kết nối đến cơ sở dữ liệu.", "error") |
|
return render_template('add_user.html') |
|
|
|
hashed_password = generate_password_hash(plaintext_password) |
|
|
|
try: |
|
cur = conn.cursor() |
|
cur.execute( |
|
"INSERT INTO users (name, email, password) VALUES (%s, %s, %s) RETURNING id;", |
|
(name, email, hashed_password) |
|
) |
|
user_id = cur.fetchone()[0] |
|
conn.commit() |
|
cur.close() |
|
flash(f"Người dùng '{name}' (ID: {user_id}) đã được thêm thành công!", "success") |
|
return render_template('add_user.html', added_user_id=user_id) |
|
except psycopg2.errors.UniqueViolation: |
|
conn.rollback() |
|
flash(f"Lỗi: Email '{email}' đã tồn tại. Vui lòng sử dụng email khác.", "error") |
|
return render_template('add_user.html') |
|
except Exception as e: |
|
conn.rollback() |
|
flash(f"Lỗi khi thêm người dùng: {e}", "error") |
|
return render_template('add_user.html') |
|
return render_template('add_user.html') |
|
|
|
@app.route('/get_users', methods=['GET']) |
|
@require_page_password |
|
def get_users(): |
|
"""Lấy danh sách tất cả người dùng từ cơ sở dữ liệu.""" |
|
conn = get_db_connection() |
|
users_list = [] |
|
if conn is None: |
|
flash("Không thể kết nối đến cơ sở dữ liệu.", "error") |
|
else: |
|
try: |
|
cur = conn.cursor() |
|
cur.execute("SELECT id, name, email FROM users;") |
|
users_data = cur.fetchall() |
|
cur.close() |
|
|
|
for user in users_data: |
|
users_list.append({"id": user[0], "name": user[1], "email": user[2]}) |
|
flash("Lấy danh sách người dùng thành công!", "success") |
|
except Exception as e: |
|
flash(f"Lỗi khi lấy danh sách người dùng: {e}", "error") |
|
return render_template('users.html', users=users_list) |
|
|
|
@app.route('/login', methods=['GET', 'POST']) |
|
|
|
def login(): |
|
"""Xác thực người dùng bằng email và mật khẩu.""" |
|
if request.method == 'POST': |
|
email = request.form.get('email') |
|
plaintext_password = request.form.get('password') |
|
|
|
if not email or not plaintext_password: |
|
flash("Vui lòng nhập Email và Mật khẩu.", "error") |
|
return render_template('login.html') |
|
|
|
conn = get_db_connection() |
|
if conn is None: |
|
flash("Không thể kết nối đến cơ sở dữ liệu.", "error") |
|
return render_template('login.html') |
|
|
|
try: |
|
cur = conn.cursor() |
|
cur.execute("SELECT id, name, password FROM users WHERE email = %s;", (email,)) |
|
user_data = cur.fetchone() |
|
cur.close() |
|
|
|
if user_data: |
|
user_id, user_name, stored_hashed_password = user_data |
|
if check_password_hash(stored_hashed_password, plaintext_password): |
|
flash(f"Đăng nhập thành công! Chào mừng, {user_name}!", "success") |
|
|
|
return redirect(url_for('home')) |
|
else: |
|
flash("Mật khẩu không đúng. Vui lòng thử lại.", "error") |
|
else: |
|
flash("Email không tồn tại. Vui lòng kiểm tra lại email.", "error") |
|
|
|
except Exception as e: |
|
flash(f"Lỗi trong quá trình đăng nhập: {e}", "error") |
|
return render_template('login.html') |
|
|
|
@app.route('/solar_flow') |
|
@require_page_password |
|
def solar_flow(): |
|
"""Hiển thị trang giao diện hệ thống điện mặt trời với hoạt hình.""" |
|
return render_template('solar_flow.html') |
|
|
|
|
|
if __name__ == '__main__': |
|
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860))) |