96 lines
4.0 KiB
Python
96 lines
4.0 KiB
Python
import json
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from flask import Flask, jsonify, request
|
|
|
|
from app.api.routes import api
|
|
from app.config import Config
|
|
from app.data.repository import CsvPriceRepository, MySQLPriceRepository
|
|
from app.errors import register_error_handlers
|
|
from app.services.model_registry import ModelRegistry
|
|
from app.services.prediction_service import PredictionService
|
|
from app.services.siskaperbapo_scraper import SiskaperbapoScraper
|
|
|
|
|
|
def create_app(config_class=Config) -> Flask:
|
|
app = Flask(__name__)
|
|
app.config.from_object(config_class)
|
|
logging.basicConfig(level=logging.INFO)
|
|
origins = app.config["CORS_ORIGINS"]
|
|
allowed_origins = [value.strip() for value in origins.split(",") if value.strip()]
|
|
|
|
@app.after_request
|
|
def add_cors_credentials(response):
|
|
origin = request.headers.get("Origin")
|
|
if origin and (origins == "*" or origin in allowed_origins):
|
|
response.headers["Access-Control-Allow-Origin"] = origin
|
|
response.headers["Access-Control-Allow-Credentials"] = "true"
|
|
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
|
response.headers["Access-Control-Allow-Methods"] = "GET,POST,DELETE,OPTIONS"
|
|
response.headers["Vary"] = "Origin"
|
|
return response
|
|
|
|
model_registry = ModelRegistry(app.config["MODELS_DIR"])
|
|
if app.config["DATA_SOURCE"] == "mysql":
|
|
repository = MySQLPriceRepository(app.config)
|
|
elif app.config["DATA_SOURCE"] == "csv":
|
|
repository = CsvPriceRepository(app.config["DATA_DIR"], app.config["PRICE_UNIT_SCALE"])
|
|
else:
|
|
raise ValueError("DATA_SOURCE harus bernilai 'mysql' atau 'csv'.")
|
|
app.extensions["model_registry"] = model_registry
|
|
app.extensions["prediction_service"] = PredictionService(model_registry, repository, app.config["STALE_DATA_DAYS"], app.config["TREND_THRESHOLD_PCT"])
|
|
if hasattr(repository, "_connect"):
|
|
connection = repository._connect()
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*) FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'price_history'
|
|
AND column_name = 'scraping_log_id'
|
|
"""
|
|
)
|
|
if cursor.fetchone()[0] == 0:
|
|
cursor.execute(
|
|
"ALTER TABLE price_history ADD COLUMN scraping_log_id BIGINT UNSIGNED NULL AFTER source"
|
|
)
|
|
cursor.execute(
|
|
"""
|
|
ALTER TABLE price_history
|
|
ADD KEY idx_price_history_scraping_log (scraping_log_id),
|
|
ADD CONSTRAINT fk_price_history_scraping_log
|
|
FOREIGN KEY (scraping_log_id) REFERENCES scraping_logs(id)
|
|
ON UPDATE CASCADE ON DELETE CASCADE
|
|
"""
|
|
)
|
|
cursor.execute(
|
|
"""
|
|
UPDATE price_history ph
|
|
JOIN (
|
|
SELECT MAX(id) AS id FROM scraping_logs WHERE source = 'siskaperbapo'
|
|
) latest ON 1 = 1
|
|
SET ph.scraping_log_id = latest.id
|
|
WHERE ph.source = 'siskaperbapo' AND ph.scraping_log_id IS NULL
|
|
"""
|
|
)
|
|
connection.commit()
|
|
finally:
|
|
connection.close()
|
|
app.extensions["siskaperbapo_scraper"] = SiskaperbapoScraper(
|
|
app.config["SISKAPERBAPO_URL"],
|
|
app.config["SISKAPERBAPO_COMMODITY_PARAM"],
|
|
app.config["SISKAPERBAPO_TIMEOUT"],
|
|
)
|
|
markets_path = Path(__file__).parent / "data" / "markets.json"
|
|
app.extensions["markets_config"] = json.loads(markets_path.read_text(encoding="utf-8"))
|
|
app.register_blueprint(api)
|
|
register_error_handlers(app)
|
|
|
|
@app.get("/")
|
|
def root():
|
|
return jsonify({"message": "API Prediksi Harga Cabai aktif", "status": "ok"})
|
|
|
|
return app
|