Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FLASK_APP=app.py
FLASK_ENV=development
SECRET_KEY=your_development_secret_key_here
16 changes: 16 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os
from dotenv import load_dotenv
from app import create_app
from app.models.database import db

# 載入環境變數
load_dotenv()

app = create_app()

# 初始化建立資料表
with app.app_context():
db.create_all()

if __name__ == '__main__':
app.run(debug=True)
48 changes: 48 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from flask import Flask
import os
from .models.database import db

# 載入路由 Blueprints
from .routes.main_routes import main_bp
from .routes.auth_routes import auth_bp
from .routes.event_routes import event_bp
from .routes.registration_routes import registration_bp

def create_app(test_config=None):
# 建立與設定 app
app = Flask(__name__, instance_relative_config=True)

# 確保 sqlite 在 Windows 的絕對路徑不會因 \ 造成 500 錯誤
db_path = os.path.join(app.instance_path, 'application.db')
sqlite_uri = 'sqlite:///' + db_path.replace('\\', '/')

# 預設設定
app.config.from_mapping(
SECRET_KEY=os.environ.get('SECRET_KEY', 'dev'),
SQLALCHEMY_DATABASE_URI=sqlite_uri,
SQLALCHEMY_TRACK_MODIFICATIONS=False
)

if test_config is None:
# 如果有 instance/config.py,則載入
app.config.from_pyfile('config.py', silent=True)
else:
# 使用測試設定
app.config.from_mapping(test_config)

# 確保 instance_path 存在
try:
os.makedirs(app.instance_path)
except OSError:
pass

# 初始化資料庫
db.init_app(app)

# 註冊 Blueprints
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(event_bp)
app.register_blueprint(registration_bp)

return app
Binary file added app/__pycache__/__init__.cpython-314.pyc
Binary file not shown.
Binary file not shown.
199 changes: 199 additions & 0 deletions app/models/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

db = SQLAlchemy()

class User(db.Model):
"""
使用者模型:代表學生或主辦方
"""
__tablename__ = 'users'

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
role = db.Column(db.String(20), nullable=False, default='student')
created_at = db.Column(db.DateTime, default=datetime.utcnow)

events_organized = db.relationship('Event', backref='organizer', lazy=True)
registrations = db.relationship('Registration', backref='user', lazy=True)

@classmethod
def create(cls, username, email, password_hash, role='student'):
"""
新增一筆使用者記錄
"""
try:
new_user = cls(username=username, email=email, password_hash=password_hash, role=role)
db.session.add(new_user)
db.session.commit()
return new_user
except Exception as e:
db.session.rollback()
print(f"Error creating user: {e}")
raise

@classmethod
def get_by_id(cls, user_id):
"""
取得單筆使用者記錄
"""
try:
return cls.query.get(user_id)
except Exception as e:
print(f"Error getting user by id: {e}")
return None


class Event(db.Model):
"""
活動模型:代表主辦方建立的活動
"""
__tablename__ = 'events'

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
organizer_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
event_date = db.Column(db.DateTime, nullable=False)
location = db.Column(db.String(200), nullable=False)
capacity = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

registrations = db.relationship('Registration', backref='event', lazy=True)

@classmethod
def create(cls, organizer_id, title, description, event_date, location, capacity):
"""
新增一筆活動記錄
"""
try:
new_event = cls(
organizer_id=organizer_id,
title=title,
description=description,
event_date=event_date,
location=location,
capacity=capacity
)
db.session.add(new_event)
db.session.commit()
return new_event
except Exception as e:
db.session.rollback()
print(f"Error creating event: {e}")
raise

@classmethod
def get_all(cls, keyword=None):
"""
取得所有活動記錄,依建立時間遞減排序,支援關鍵字搜尋
"""
try:
query = cls.query
if keyword:
search_term = f"%{keyword}%"
query = query.filter(db.or_(cls.title.ilike(search_term), cls.description.ilike(search_term)))
return query.order_by(cls.created_at.desc()).all()
except Exception as e:
print(f"Error getting all events: {e}")
return []

@classmethod
def get_by_id(cls, event_id):
"""
取得單筆活動記錄
"""
try:
return cls.query.get(event_id)
except Exception as e:
print(f"Error getting event by id: {e}")
return None

def update(self, **kwargs):
"""
更新活動記錄
"""
try:
for key, value in kwargs.items():
setattr(self, key, value)
db.session.commit()
except Exception as e:
db.session.rollback()
print(f"Error updating event: {e}")
raise

def delete(self):
"""
刪除活動記錄
"""
try:
db.session.delete(self)
db.session.commit()
except Exception as e:
db.session.rollback()
print(f"Error deleting event: {e}")
raise


class Registration(db.Model):
"""
報名記錄模型:追蹤學生報名狀態
"""
__tablename__ = 'registrations'

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
event_id = db.Column(db.Integer, db.ForeignKey('events.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
status = db.Column(db.String(20), nullable=False) # 'Confirmed', 'Waitlist', 'Cancelled'
created_at = db.Column(db.DateTime, default=datetime.utcnow)

@classmethod
def create(cls, event_id, user_id, status):
"""
新增一筆報名記錄
"""
try:
new_reg = cls(event_id=event_id, user_id=user_id, status=status)
db.session.add(new_reg)
db.session.commit()
return new_reg
except Exception as e:
db.session.rollback()
print(f"Error creating registration: {e}")
raise

@classmethod
def get_by_id(cls, reg_id):
"""
取得單筆報名記錄
"""
try:
return cls.query.get(reg_id)
except Exception as e:
print(f"Error getting registration by id: {e}")
return None

@classmethod
def get_user_registrations(cls, user_id):
"""
取得指定使用者的所有報名記錄
"""
try:
return cls.query.filter_by(user_id=user_id).order_by(cls.created_at.desc()).all()
except Exception as e:
print(f"Error getting user registrations: {e}")
return []

def update_status(self, new_status):
"""
更新報名記錄狀態
"""
try:
self.status = new_status
db.session.commit()
except Exception as e:
db.session.rollback()
print(f"Error updating registration status: {e}")
raise
Binary file not shown.
Binary file not shown.
Binary file added app/routes/__pycache__/main_routes.cpython-314.pyc
Binary file not shown.
Binary file not shown.
86 changes: 86 additions & 0 deletions app/routes/auth_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from werkzeug.security import generate_password_hash, check_password_hash
from app.models.database import User

auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
"""
使用者註冊
輸入:[GET] 無 / [POST] 表單 (username, email, password)
"""
# 若已登入,導回首頁
if 'user_id' in session:
return redirect(url_for('main.index'))

if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')

# 基礎輸入驗證
if not username or not email or not password:
flash('所有欄位皆為必填。', 'danger')
return render_template('auth/register.html')

# 檢查信箱是否已存在
existing_user = User.query.filter_by(email=email).first()
if existing_user:
flash('此信箱已被註冊。', 'danger')
return render_template('auth/register.html')

password_hash = generate_password_hash(password)
try:
# 建立使用者記錄 (預設角色設為 student)
User.create(username=username, email=email, password_hash=password_hash, role='student')
flash('註冊成功!請進行登入。', 'success')
return redirect(url_for('auth.login'))
except Exception as e:
flash('發生未知的錯誤,請稍後再試。', 'danger')

# GET 請求,渲染註冊表單
return render_template('auth/register.html')


@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
"""
使用者登入
輸入:[GET] 無 / [POST] 表單 (email, password)
"""
# 若已登入,導回首頁
if 'user_id' in session:
return redirect(url_for('main.index'))

if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')

if not email or not password:
flash('請輸入信箱與密碼。', 'warning')
return render_template('auth/login.html')

# 查詢使用者並比對密碼
user = User.query.filter_by(email=email).first()
if user and check_password_hash(user.password_hash, password):
session.clear()
session['user_id'] = user.id
session['user_role'] = user.role
flash('登入成功!', 'success')
return redirect(url_for('main.index'))
else:
flash('信箱或密碼錯誤。', 'danger')

# GET 請求,渲染登入表單
return render_template('auth/login.html')


@auth_bp.route('/logout', methods=['POST'])
def logout():
"""
使用者登出
"""
session.clear()
flash('您已成功登出。', 'info')
return redirect(url_for('main.index'))
Loading