File size: 12,800 Bytes
46612cf 3227549 5d0ba21 46612cf 3227549 62c419a 76fd41a 46612cf 76fd41a f9bb959 76fd41a 46612cf 62c419a 5d0ba21 62c419a 3227549 46612cf 62c419a 46612cf 5d0ba21 3227549 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 3227549 62c419a 3227549 5d0ba21 46612cf 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 46612cf 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 62c419a 5d0ba21 46612cf 3227549 62c419a 5d0ba21 3227549 46612cf 62c419a 46612cf 62c419a 46612cf 62c419a b8cf926 62c419a 46612cf 62c419a 46612cf 62c419a 46612cf 62c419a 3227549 62c419a 3227549 62c419a 76fd41a ecf93ea 3227549 f9bb959 3227549 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
import os
import json
from flask import Flask, request, g, Response, render_template, redirect, url_for, flash, session # Thêm 'session'
import psycopg2
from dotenv import load_dotenv
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
# Tải các biến môi trường từ file .env (chỉ cho môi trường phát triển cục bộ)
load_dotenv()
app = Flask(__name__)
# Cấu hình kết nối cơ sở dữ liệu từ biến môi trường
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')
# Biến môi trường cho mật khẩu trang
PAGE_PASSWORD = os.getenv('PAGE_PASSWORD')
# Cấu hình secret key cho Flask (CẦN THIẾT cho flash messages và session)
# Trong môi trường production, hãy tạo một chuỗi ngẫu nhiên mạnh và lưu vào biến môi trường FLASK_SECRET_KEY
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.")
# --- Hàm tiện ích để quản lý kết nối cơ sở dữ liệu ---
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.")
# --- Hàm tiện ích để trả về JSON với tiếng Việt (giữ cho các API nếu cần) ---
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
# --- Decorator để bảo vệ các trang bằng mật khẩu ---
def require_page_password(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Kiểm tra session trước
if session.get('page_logged_in'):
return f(*args, **kwargs)
# Nếu chưa đăng nhập qua session, kiểm tra mật khẩu từ form/query/header
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 # Đặt session là đã đăng nhập
# Để tránh lỗi chuyển hướng lại chính trang đó với POST,
# hãy chuyển hướng đến chính URL hiện tại nhưng là GET request
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) # Hiển thị form mật khẩu riêng
return decorated_function
# --- Tuyến đường mới để hiển thị form mật khẩu trang và xử lý đăng nhập ---
@app.route('/page_password', methods=['GET', 'POST'])
def page_password_form():
next_url = request.args.get('next') or url_for('home') # Lấy URL gốc muốn truy cập sau khi nhập mật khẩu
if request.method == 'POST':
provided_password = request.form.get('page_password_input') # Lấy từ form riêng này
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)
# Nếu là GET request, hiển thị form mật khẩu
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'))
# --- Các tuyến đường (Routes) của ứng dụng ---
@app.route('/')
def home():
"""Trang chủ của ứng dụng."""
return render_template('index.html')
# Áp dụng decorator cho các trang cần bảo vệ
@app.route('/test_db', methods=['GET']) # Đổi thành GET
@require_page_password # Áp dụng bảo vệ
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']) # Đổi thành GET
@require_page_password # Áp dụng bảo vệ
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 # Áp dụng bảo vệ
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']) # Đổi thành GET
@require_page_password # Áp dụng bảo vệ
def get_users(): # Đổi tên hàm thành get_users cho rõ ràng
"""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'])
#@require_page_password # Vẫn bảo vệ việc truy cập vào trang này bằng PAGE_PASSWORD
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")
# Có thể chuyển hướng đến trang dashboard hoặc lưu thông tin phiên đăng nhập
return redirect(url_for('home')) # Ví dụ: chuyển về trang chủ sau đăng nhập
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 # Áp dụng bảo vệ nếu bạn muốn
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')
# --- Chạy ứng dụng ---
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860))) |