From 0f52c3dc03a23c688c9687eef99cbae17b48e2e0 Mon Sep 17 00:00:00 2001 From: livindra Date: Tue, 28 Apr 2026 14:03:31 +0700 Subject: [PATCH] update Co-authored-by: Copilot --- .env.example | 10 ++++++++++ Procfile | 1 + QUICK_START.md | 17 +++++++++++++++++ app.py | 4 +++- deteksi_PMK | 1 - railway.json | 11 +++++++++++ requirements.txt | 5 +++-- utils/mysql_db.py | 33 +++++++++++++++++++++++---------- 8 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 Procfile delete mode 160000 deteksi_PMK create mode 100644 railway.json diff --git a/.env.example b/.env.example index cc48775..7fe301c 100644 --- a/.env.example +++ b/.env.example @@ -5,5 +5,15 @@ DB_USER=root DB_PASSWORD= DB_NAME=deteksi_pmk +# Railway / hosted MySQL support +# Railway biasanya menyediakan variabel ini otomatis saat plugin database dipasang. +MYSQLHOST= +MYSQLPORT= +MYSQLUSER= +MYSQLPASSWORD= +MYSQLDATABASE= +DATABASE_URL= + # Flask Configuration FLASK_SECRET=your-secret-key-here +FLASK_DEBUG=0 diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..45d24ff --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn app:app --bind 0.0.0.0:$PORT \ No newline at end of file diff --git a/QUICK_START.md b/QUICK_START.md index d9f75fa..22d779d 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -22,6 +22,21 @@ python app.py Buka browser ke: **http://localhost:5000** +## Deploy ke Railway + +Repository ini sudah disiapkan untuk Railway dengan entrypoint Flask di `app.py`. + +1. Hubungkan repository ini ke Railway. +2. Tambahkan layanan database MySQL di Railway jika ingin penyimpanan riwayat berjalan. +3. Set environment variable berikut di Railway: + - `FLASK_SECRET` + - `DATABASE_URL` atau `MYSQLHOST`, `MYSQLPORT`, `MYSQLUSER`, `MYSQLPASSWORD`, `MYSQLDATABASE` +4. Railway akan menjalankan `gunicorn app:app --bind 0.0.0.0:$PORT`. + +Catatan: +- `opencv-python-headless` dipakai supaya build Railway tidak bergantung pada library GUI. +- Jika model belum ada di folder `models/`, jalankan training dulu sebelum deploy. + ## Langkah 3: Upload Gambar & Lihat Riwayat 1. **Upload**: Klik "Upload Gambar" → Pilih file → Upload @@ -45,6 +60,8 @@ DB_HOST=localhost DB_USER=root DB_PASSWORD= DB_NAME=deteksi_pmk + +Untuk Railway, lebih aman pakai variabel environment yang disediakan oleh plugin database atau `DATABASE_URL`. ``` ## 🔗 Useful Routes diff --git a/app.py b/app.py index feaff0b..e379645 100644 --- a/app.py +++ b/app.py @@ -911,4 +911,6 @@ def export_excel(): if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file + port = int(os.environ.get('PORT', 5000)) + debug = os.environ.get('FLASK_DEBUG', '0') == '1' + app.run(host='0.0.0.0', port=port, debug=debug) \ No newline at end of file diff --git a/deteksi_PMK b/deteksi_PMK deleted file mode 160000 index ce2e577..0000000 --- a/deteksi_PMK +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ce2e5771dd78cd0ade90b677c15668361743e345 diff --git a/railway.json b/railway.json new file mode 100644 index 0000000..0855fab --- /dev/null +++ b/railway.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "NIXPACKS" + }, + "deploy": { + "startCommand": "gunicorn app:app --bind 0.0.0.0:$PORT", + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 10 + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e8beb8b..6eef47d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -opencv-python==4.9.0.80 +opencv-python-headless==4.9.0.80 numpy<2 pandas>=2.1.0 scikit-learn>=1.3.2 @@ -9,4 +9,5 @@ joblib>=1.3.2 Flask>=2.3.3 SQLAlchemy>=2.0.0 PyMySQL>=1.1.0 -python-dotenv>=1.0.0 \ No newline at end of file +python-dotenv>=1.0.0 +gunicorn>=21.2.0 \ No newline at end of file diff --git a/utils/mysql_db.py b/utils/mysql_db.py index 16cdcf1..9f72e4f 100644 --- a/utils/mysql_db.py +++ b/utils/mysql_db.py @@ -8,6 +8,7 @@ import json import datetime from collections import OrderedDict from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, Text, Boolean, ForeignKey, Table, inspect, text +from sqlalchemy.engine import make_url from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.exc import SQLAlchemyError @@ -16,18 +17,30 @@ from sqlalchemy.exc import SQLAlchemyError import pytz TZ_INDONESIA = pytz.timezone('Asia/Jakarta') -# Database configuration from environment variables -DB_HOST = os.environ.get('DB_HOST', 'localhost') -DB_PORT = os.environ.get('DB_PORT', '3306') -DB_USER = os.environ.get('DB_USER', 'root') -DB_PASSWORD = os.environ.get('DB_PASSWORD', '') -DB_NAME = os.environ.get('DB_NAME', 'deteksi_pmk') +DATABASE_URL = os.environ.get('DATABASE_URL') + + +def _build_database_uri(): + """Build a SQLAlchemy database URI from Railway or local environment variables.""" + if DATABASE_URL: + url = make_url(DATABASE_URL) + if url.drivername == 'mysql': + url = url.set(drivername='mysql+pymysql') + return str(url) + + db_host = os.environ.get('DB_HOST') or os.environ.get('MYSQLHOST', 'localhost') + db_port = os.environ.get('DB_PORT') or os.environ.get('MYSQLPORT', '3306') + db_user = os.environ.get('DB_USER') or os.environ.get('MYSQLUSER', 'root') + db_password = os.environ.get('DB_PASSWORD') or os.environ.get('MYSQLPASSWORD', '') + db_name = os.environ.get('DB_NAME') or os.environ.get('MYSQLDATABASE', 'deteksi_pmk') + + if db_password: + return f"mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" + return f"mysql+pymysql://{db_user}@{db_host}:{db_port}/{db_name}" + # Create database URI -if DB_PASSWORD: - DATABASE_URI = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" -else: - DATABASE_URI = f"mysql+pymysql://{DB_USER}@{DB_HOST}:{DB_PORT}/{DB_NAME}" +DATABASE_URI = _build_database_uri() # Create engine engine = create_engine(DATABASE_URI, echo=False, pool_pre_ping=True)