diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa386b6..7253871 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: @@ -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/ \ No newline at end of file + python -m pytest tests/ -v \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 2d59c7d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM ubuntu:latest -LABEL authors="nguye" - -ENTRYPOINT ["top", "-b"] \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..e82dcd9 --- /dev/null +++ b/backend/Dockerfile @@ -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"] \ No newline at end of file diff --git a/backend/src/__init__.py b/backend/src/__init__.py index b38d45f..cdab2c8 100644 --- a/backend/src/__init__.py +++ b/backend/src/__init__.py @@ -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') @@ -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) @@ -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 diff --git a/backend/src/routes/api_auth_routes.py b/backend/src/routes/api_auth_routes.py index 212f19d..d8cdd2e 100644 --- a/backend/src/routes/api_auth_routes.py +++ b/backend/src/routes/api_auth_routes.py @@ -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') @@ -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 @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/backend/src/test_api.py b/backend/src/test_api.py deleted file mode 100644 index 9472ec3..0000000 --- a/backend/src/test_api.py +++ /dev/null @@ -1,10 +0,0 @@ -# File: backend/src/test_api.py -from app import create_app -from utils import EventController -import json - -app = create_app() -with app.app_context(): - res, code = EventController.get_event_by_id(5) - print(f"Status Code: {code}") - print(json.dumps(res, indent=2)) diff --git a/database/migrations/init.sql b/database/migrations/init.sql index 8bf8870..e58dec9 100644 --- a/database/migrations/init.sql +++ b/database/migrations/init.sql @@ -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; @@ -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'), diff --git a/docker-compose.yml b/docker-compose.yml index ecd7a6f..1bb7e42 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 @@ -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: \ No newline at end of file diff --git a/frontend/src/templates/booking_detail.html b/frontend/src/templates/booking_detail.html index c5a52c4..c2698ef 100644 --- a/frontend/src/templates/booking_detail.html +++ b/frontend/src/templates/booking_detail.html @@ -10,6 +10,8 @@ + + @@ -102,6 +104,51 @@
Vé / Ghế + diff --git a/frontend/src/templates/event_detail.html b/frontend/src/templates/event_detail.html index 3d7147b..fe62f4f 100644 --- a/frontend/src/templates/event_detail.html +++ b/frontend/src/templates/event_detail.html @@ -10,6 +10,8 @@ + + @@ -154,6 +156,51 @@
Vé đã ch + diff --git a/frontend/src/templates/history.html b/frontend/src/templates/history.html index 1c2f031..84b8c65 100644 --- a/frontend/src/templates/history.html +++ b/frontend/src/templates/history.html @@ -10,6 +10,7 @@ +