"""JustVitamin × QuikCue — AI Content Engine
Live proposal site with real Gemini-powered demos.
PostgreSQL-backed data dashboard."""
import os, json, time, traceback, logging, secrets
from pathlib import Path
from functools import wraps
from flask import (Flask, render_template, jsonify, request,
send_from_directory, redirect, session, url_for,
make_response)
from scraper import scrape_product, scrape_competitor
from ai_engine import (generate_asset_pack, competitor_xray, pdp_surgeon,
optimise_pdp_copy, generate_product_images)
from db import init_db, get_dashboard_data, get_meta
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
app = Flask(__name__,
static_folder="static",
template_folder="templates")
app.secret_key = os.environ.get("SECRET_KEY", secrets.token_hex(32))
SITE_PASSWORD = os.environ.get("SITE_PASSWORD", "jv2026")
GEN_DIR = Path(__file__).parent / "generated"
GEN_DIR.mkdir(exist_ok=True)
# ── Init DB on startup ───────────────────────────────────────
with app.app_context():
try:
init_db()
except Exception as e:
logging.error(f"DB init failed: {e}")
# ── Auth ─────────────────────────────────────────────────────
PUBLIC_PATHS = {"/login", "/api/health"}
@app.before_request
def require_auth():
"""Gate every request behind a simple password session."""
path = request.path
# Allow login page, health check, and static login assets
if path in PUBLIC_PATHS or path.startswith("/static/"):
return
if not session.get("authed"):
if request.is_json:
return jsonify({"error": "Authentication required"}), 401
return redirect(url_for("login", next=request.path))
LOGIN_HTML = """
Login — JustVitamins × QuikCue
JustVitamins × QuikCue
Confidential proposal — enter password to continue
{{ERR}}
"""
@app.route("/login", methods=["GET", "POST"])
def login():
err = ""
if request.method == "POST":
pw = request.form.get("password", "")
if pw == SITE_PASSWORD:
session["authed"] = True
session.permanent = True
app.permanent_session_lifetime = __import__("datetime").timedelta(days=30)
dest = request.args.get("next", "/")
return redirect(dest)
err = 'Wrong password
'
html = LOGIN_HTML.replace("{{ERR}}", err)
return make_response(html)
@app.route("/logout")
def logout():
session.clear()
return redirect("/login")
# ── Page routes ──────────────────────────────────────────────
@app.route("/")
def index():
return render_template("index.html")
@app.route("/dashboard")
@app.route("/dashboard/")
def dashboard():
return redirect("/dashboard/index.html")
@app.route("/dashboard/")
def dashboard_files(filename):
return send_from_directory("static/dashboard", filename)
# ── Dashboard API (Postgres-backed) ─────────────────────────
@app.route("/api/dashboard/data")
def api_dashboard_data():
"""Return filtered dashboard data from PostgreSQL.
Query params: ?start=YYYY-MM&end=YYYY-MM
Replaces the 4MB static JSON with fast server-side SQL filtering."""
start = request.args.get("start") or None
end = request.args.get("end") or None
try:
t0 = time.time()
data = get_dashboard_data(start, end)
data["_query_time"] = f"{time.time()-t0:.3f}s"
data["_source"] = "postgresql"
resp = jsonify(data)
resp.headers['Cache-Control'] = 'public, max-age=60'
return resp
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
@app.route("/api/dashboard/meta")
def api_dashboard_meta():
"""Return just the meta info (fast, tiny)."""
try:
return jsonify(get_meta())
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/proposal")
def proposal():
return send_from_directory("static/proposal", "index.html")
@app.route("/offer")
def offer():
return send_from_directory("static/offer", "index.html")
@app.route("/generated/")
def serve_generated(filename):
return send_from_directory(GEN_DIR, filename)
# ── API: Scrape ──────────────────────────────────────────────
@app.route("/api/scrape", methods=["POST"])
def api_scrape():
"""Scrape a JustVitamins product page."""
url = request.json.get("url", "").strip()
if not url:
return jsonify({"error": "No URL provided"}), 400
try:
data = scrape_product(url)
if not data.get("title"):
return jsonify({"error": "Could not find product. Check URL."}), 400
return jsonify(data)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
@app.route("/api/scrape-competitor", methods=["POST"])
def api_scrape_competitor():
"""Scrape any competitor product page."""
url = request.json.get("url", "").strip()
if not url:
return jsonify({"error": "No URL provided"}), 400
try:
data = scrape_competitor(url)
if not data.get("title"):
return jsonify({"error": "Could not extract product data."}), 400
return jsonify(data)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
# ── API: Demo A — 12-Asset Pack ──────────────────────────────
@app.route("/api/generate-pack", methods=["POST"])
def api_generate_pack():
"""Generate a full 12-asset marketing pack from product data."""
product = request.json
if not product:
return jsonify({"error": "No product data"}), 400
try:
t0 = time.time()
pack = generate_asset_pack(product)
pack["_generation_time"] = f"{time.time()-t0:.1f}s"
pack["_product_title"] = product.get("title", "")
return jsonify(pack)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
# ── API: Demo B — Competitor X-Ray ───────────────────────────
@app.route("/api/competitor-xray", methods=["POST"])
def api_competitor_xray():
"""Scrape competitor + run AI analysis."""
url = request.json.get("url", "").strip()
if not url:
return jsonify({"error": "No URL provided"}), 400
try:
t0 = time.time()
comp_data = scrape_competitor(url)
if not comp_data.get("title"):
return jsonify({"error": "Could not scrape competitor page."}), 400
analysis = competitor_xray(comp_data)
analysis["_scrape_data"] = {
"title": comp_data.get("title"),
"price": comp_data.get("price"),
"brand": comp_data.get("brand"),
"url": url,
}
analysis["_generation_time"] = f"{time.time()-t0:.1f}s"
return jsonify(analysis)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
# ── API: Demo C — PDP Surgeon ────────────────────────────────
@app.route("/api/pdp-surgeon", methods=["POST"])
def api_pdp_surgeon():
"""Rewrite PDP copy in a given style."""
data = request.json or {}
product = data.get("product")
style = data.get("style", "balanced")
if not product:
return jsonify({"error": "No product data"}), 400
try:
t0 = time.time()
result = pdp_surgeon(product, style)
result["_generation_time"] = f"{time.time()-t0:.1f}s"
return jsonify(result)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
# ── API: Full PDP Optimise ───────────────────────────────────
@app.route("/api/optimise", methods=["POST"])
def api_optimise():
"""Full PDP rewrite — scrape + AI copy."""
product = request.json
if not product:
return jsonify({"error": "No product data"}), 400
try:
t0 = time.time()
copy = optimise_pdp_copy(product)
copy["_generation_time"] = f"{time.time()-t0:.1f}s"
return jsonify(copy)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
# ── API: Image Generation ────────────────────────────────────
@app.route("/api/generate-images", methods=["POST"])
def api_generate_images():
"""Generate AI product images via Nano Banana / Pro."""
product = request.json
if not product:
return jsonify({"error": "No product data"}), 400
try:
t0 = time.time()
images = generate_product_images(product)
images["_generation_time"] = f"{time.time()-t0:.1f}s"
return jsonify(images)
except Exception as e:
traceback.print_exc()
return jsonify({"error": str(e)}), 500
# ── Health check ─────────────────────────────────────────────
@app.route("/api/health")
def health():
db_ok = False
try:
from db import get_conn
conn = get_conn()
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM monthly")
count = cur.fetchone()[0]
cur.close()
conn.close()
db_ok = count > 0
except Exception:
pass
return jsonify({
"status": "ok",
"gemini_key_set": bool(os.environ.get("GEMINI_API_KEY")),
"db_connected": db_ok,
"db_rows": count if db_ok else 0,
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5050, debug=True)