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