Skip to content
Merged
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
19 changes: 10 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: TicketHub CI Pipeline

on:
push:
branches: [ "main", "develop" ]
branches: [ "main", "main_merge_2" ]
pull_request:
branches: [ "main" ]

jobs:
build-and-test:
Expand All @@ -25,23 +27,22 @@ jobs:
--health-retries=5

steps:
- name: Kéo source code (Start)
- name: Checkout code
uses: actions/checkout@v4

- name: Cài đặt Python
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Cài đặt thư viện (Build)
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
pip install cryptography
pip install -r requirements.txt
pip install pytest cryptography

- name: Chạy kiểm thử (Test)
- name: Run tests
env:
TEST_DATABASE_URL: mysql+pymysql://testuser:testpass@127.0.0.1:3306/tickethub_test
run: |
python -m pytest tests/
python -m pytest tests/ -v
4 changes: 0 additions & 4 deletions Dockerfile

This file was deleted.

6 changes: 6 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:3.10-slim
WORKDIR /app
COPY src/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ .
CMD ["python", "app.py"]
38 changes: 34 additions & 4 deletions backend/src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# File: backend/src/__init__.py
import os
from flask import Flask
from flask_jwt_extended import JWTManager
from models import db
from dotenv import load_dotenv
from flasgger import Swagger

load_dotenv()
jwt = JWTManager()

def create_app():

def create_app(test_config=None):
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
TEMPLATE_DIR = os.path.join(BASE_DIR, '../../frontend/src/templates')
STATIC_DIR = os.path.join(BASE_DIR, '../../frontend/src')
Expand All @@ -19,12 +20,42 @@ def create_app():
static_folder=STATIC_DIR,
static_url_path=''
)

swagger_config = {
"headers": [],
"specs": [{
"endpoint": 'apispec',
"route": '/apispec.json',
"rule_filter": lambda rule: True,
"model_filter": lambda tag: True,
}],
"static_url_path": "/flasgger_static",
"swagger_ui": True,
"specs_route": "/apidocs/"
}
swagger_template = {
"swagger": "2.0",
"info": {
"title": "TicketHub API",
"description": "RESTful API cho hệ thống đặt vé sự kiện TicketHub.",
"version": "1.0.0"
},
"basePath": "/api/v1",
}
Swagger(app, config=swagger_config, template=swagger_template)

app.secret_key = os.environ.get('SECRET_KEY', 'mixi_gaming')
app.config['JWT_IDENTITY_CLAIM'] = 'identity'
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'mysql+pymysql://root:08032005@localhost/event4')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL',
'mysql+pymysql://root:08032005@localhost/event4')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY',
'xinchaoxinchaoxinchaoxinchaoxinchaoxinchaoxinchaoxinchaoxinchao')

# Override với test_config nếu có
if test_config:
app.config.update(test_config)

db.init_app(app)
jwt.init_app(app)

Expand All @@ -36,7 +67,6 @@ def user_identity_lookup(user_data):

with app.app_context():
import models

from routes.admin_routes import admin_bp, admin_api_bp
from routes.organizer_routes import organizer_bp
from routes.api_auth_routes import api_auth_bp
Expand Down
60 changes: 49 additions & 11 deletions backend/src/routes/api_auth_routes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# File: backend/src/routes/api_auth_routes.py
from flask import Blueprint, request, jsonify
from services.auth_service import register_new_user, verify_login
from flask_jwt_extended import jwt_required, get_jwt_identity
import datetime
from models import db, User, Customer, Organizer
from utils import upload_avatar_to_cloudinary

api_auth_bp = Blueprint('api_auth', __name__, url_prefix='/api/v1/auth')

Expand Down Expand Up @@ -50,15 +50,13 @@ def api_login():
@api_auth_bp.route('/me', methods=['GET'])
@jwt_required()
def get_my_profile():
from models import User

identity = get_jwt_identity()
if isinstance(identity, dict):
current_user_id = identity.get('id')
else:
current_user_id = identity
user = User.query.get(current_user_id)

user = db.session.get(User, current_user_id)
if not user:
return jsonify({"status": "error", "message": "User not found"}), 404

Expand All @@ -71,16 +69,56 @@ def get_my_profile():
}

if user.type == 'customer':
from models import Customer
customer = Customer.query.get(user.id)
customer = db.session.get(Customer, user.id)
if customer and customer.tier:
user_data["tier"] = customer.tier.name

elif user.type == 'organizer':
from models import Organizer
org = Organizer.query.get(user.id)
org = db.session.get(Organizer, user.id)
if org:
user_data["company_name"] = org.company_name
user_data["bank_account"] = org.bank_account

return jsonify({"status": "success", "user": user_data}), 200
return jsonify({"status": "success", "user": user_data}), 200


@api_auth_bp.route('/me', methods=['PUT'])
@jwt_required()
def update_my_profile():
identity = get_jwt_identity()
if isinstance(identity, dict):
current_user_id = identity.get('id')
else:
current_user_id = identity

user = db.session.get(User, current_user_id)
if not user:
return jsonify({"status": "error", "message": "User not found"}), 404

if user.type != 'customer':
return jsonify({"status": "error", "message": "Chỉ khách hàng mới được cập nhật hồ sơ"}), 403

avatar_file = request.files.get('avatar')
if avatar_file:
avatar_url = upload_avatar_to_cloudinary(avatar_file)
user.avatar = avatar_url

name = request.form.get('name')
if name:
user.name = name

db.session.commit()

user_data = {
"id": user.id, "name": user.name, "email": user.email,
"phone_number": user.phone_number, "username": user.username,
"avatar": user.avatar,
"dob": user.dob.strftime('%Y-%m-%d') if user.dob else None,
"type": user.type
}

if user.type == 'customer':
customer = db.session.get(Customer, user.id)
if customer and customer.tier:
user_data["tier"] = customer.tier.name

return jsonify({"status": "success", "message": "Cập nhật thành công!", "user": user_data}), 200
10 changes: 0 additions & 10 deletions backend/src/test_api.py

This file was deleted.

4 changes: 2 additions & 2 deletions database/migrations/init.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- Đảm bảo sử dụng đúng database
USE event3;
USE event4;

-- 1. Xóa data cũ (nếu có) để tránh lỗi
SET FOREIGN_KEY_CHECKS = 0;
Expand All @@ -16,7 +16,7 @@ SET FOREIGN_KEY_CHECKS = 1;

-- ==========================================
-- 2. TẠO USER (Mật khẩu mặc định: 123456)
-- Đã dùng mã hash Werkzeug chuẩn của ông
-- Đã dùng mã hash Werkzeug chuẩn
-- ==========================================
INSERT INTO user (id, name, email, phone_number, username, password, status, avatar, dob, type) VALUES
(1, 'Admin Khó Tính', 'admin@tickethub.com', '0901234567', 'admin01', 'scrypt:32768:8:1$CvPnwjsOo87bnIaY$cc8cc2dd4b31acd0931bd1a976bda8ab5f84ba47bf3d27571216bd78bc5234b1957a6cd0ccbe8659dcd186cef507c7bc6b1d5510078bde5fa8ef524bc6c31289', 1, 'https://ui-avatars.com/api/?name=Admin', '2000-01-01', 'admin'),
Expand Down
55 changes: 45 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
version: '3.8'

services:
db:
image: mysql:8.0
container_name: tickethub_db
environment:
MYSQL_ROOT_PASSWORD: 08032005
MYSQL_DATABASE: event4
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./database/migrations/init.sql:/docker-entrypoint-initdb.d/init.sql
restart: always

rabbitmq:
image: rabbitmq:3-management-alpine
container_name: tickethub_rabbitmq
Expand All @@ -11,18 +24,40 @@ services:
- rabbitmq_data:/var/lib/rabbitmq
restart: always

ngrok:
image: ngrok/ngrok:latest
container_name: ticket_ngrok
backend:
build: ./backend
container_name: tickethub_backend
command: python app.py
ports:
- "4040:4040"
- "8000:8000"
environment:
- NGROK_AUTHTOKEN=${NGROK_TOKEN}
command:
- "http"
- "--url=${NGROK_DOMAIN}"
- "host.docker.internal:8000"
restart: unless-stopped
- DATABASE_URL=mysql+pymysql://root:08032005@db/event4
- SECRET_KEY=mixi_gaming
- JWT_SECRET_KEY=event_ticketing_super_secret
- MAIL_USERNAME=dephucau@gmail.com
- MAIL_PASSWORD=afew famg pggg emzh
- SEPA\Y_API_TOKEN=hanolu
- BANK_ID=MB
- BANK_ACCOUNT_NO=0168508418888
- BANK_ACCOUNT_NAME=Hoang Ngoc Luyen
depends_on:
- db
- rabbitmq
restart: always

worker:
build: ./backend
container_name: tickethub_worker
command: celery -A tasks.celery_app worker --loglevel=info --pool=solo
environment:
- DATABASE_URL=mysql+pymysql://root:08032005@db/event4
- MAIL_USERNAME=dephucau@gmail.com
- MAIL_PASSWORD=afew famg pggg emzh
depends_on:
- db
- rabbitmq
restart: always

volumes:
mysql_data:
rabbitmq_data:
47 changes: 47 additions & 0 deletions frontend/src/templates/booking_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<link rel="stylesheet" href="../css/booking_detail.css">
<link rel="stylesheet" href="/css/style.css">

</head>

<body class="bg-light">
Expand Down Expand Up @@ -102,6 +104,51 @@ <h5 class="fw-bold mb-3"><i class="bi bi-ticket-perforated me-2"></i>Vé / Ghế
</div>
</div>
</main>
<footer class="tickethub-footer">
<div class="container">
<div class="row">
<div class="col-md-3 mb-4">
<h6>Hotline</h6>
<p class="mb-0"><i class="fas fa-phone-alt me-2"></i>Thứ 2 - Chủ Nhật (8:00 - 23:00)</p>
<div class="footer-hotline">1900.xxxx</div>
<h6>Email</h6>
<p><i class="fas fa-envelope me-2"></i>support@tickethub.vn</p>
<h6>Văn phòng chính</h6>
<p><i class="fas fa-map-marker-alt me-2"></i>Tầng 12, Tòa nhà OU, HCM</p>
</div>
<div class="col-md-3 mb-4">
<h6>Dành cho Khách hàng</h6>
<ul>
<li><a href="#">Điều khoản sử dụng</a></li>
<li><a href="#">Hướng dẫn mua vé</a></li>
<li><a href="#">Chính sách hoàn tiền</a></li>
</ul>
</div>
<div class="col-md-3 mb-4">
<h6>Về công ty chúng tôi</h6>
<ul>
<li><a href="#">Quy chế hoạt động</a></li>
<li><a href="#">Chính sách bảo mật</a></li>
<li><a href="#">Cơ chế giải quyết khiếu nại</a></li>
</ul>
</div>
<div class="col-md-3 mb-4">
<h6>Ứng dụng TicketHub</h6>
<a href="#" class="app-btn"><i class="fab fa-google-play me-2"></i>Tải trên Google Play</a>
<a href="#" class="app-btn"><i class="fab fa-apple me-2"></i>Tải trên App Store</a>
<h6 class="mt-4">Follow us</h6>
<div class="social-icons">
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
</div>
</div>
</div>
<hr style="border-color: #444;">
<div class="text-center pt-2" style="font-size: 13px;">
© 2026 TicketHub.
</div>
</div>
</footer>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/js/common.js"></script>
Expand Down
Loading
Loading