#!/usr/bin/env python3 """ App Factory Base Template — Production-ready Flask app with all monetization built-in. Customize APP_NAME, APP_SLUG, TAGLINE, PREMIUM_PRICE, and the core_feature() function. """ import os import json import hashlib import sqlite3 import secrets import ssl import urllib.request import urllib.parse from datetime import datetime from typing import Optional from flask import Flask, request, redirect, session, jsonify, make_response # ─── CONFIG ─────────────────────────────────────────────── APP_NAME = "InterviewForge" APP_SLUG = "interviewforge" TAGLINE = "Generate tailored interview questions in seconds" PREMIUM_PRICE = 9 # USD, one-time PREMIUM_DESCRIPTION = "Premium unlocks unlimited question sets with industry follow-ups" PRIMARY_COLOR = "#2563EB" ACCENT_COLOR = "#F59E0B" # BTCPay (replaced during deployment) BTC_URL = "https://10.30.20.140" BTC_STORE = "FEw7ACTcckppRK2KMbZHvS34P96dLQE4ZBVqfTM6qyfR" BTC_KEY = "68c8ba2c6815d0432ec30fe3cccb8fb3fb04fc5f" # Database DB_PATH = f"/opt/{APP_SLUG}/data.db" app = Flask(__name__) app.secret_key = secrets.token_hex(32) # ─── DATABASE ───────────────────────────────────────────── def get_db(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") return conn def init_db(): conn = get_db() conn.executescript(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, is_premium INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now')), btcpay_invoice_id TEXT, payment_status TEXT DEFAULT 'none' ); CREATE TABLE IF NOT EXISTS payments ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, invoice_id TEXT UNIQUE, amount_usd REAL, status TEXT DEFAULT 'pending', created_at TEXT DEFAULT (datetime('now')), settled_at TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE IF NOT EXISTS results ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, data TEXT, created_at TEXT DEFAULT (datetime('now')), FOREIGN KEY (user_id) REFERENCES users(id) ); """) conn.commit() conn.close() # ─── AUTH HELPERS ───────────────────────────────────────── def hash_password(password: str) -> str: salt = "appfactory2026" return hashlib.sha256((password + salt).encode()).hexdigest() def login_required(f): from functools import wraps @wraps(f) def decorated(*args, **kwargs): if 'user_id' not in session: return redirect('/login?next=' + request.path) return f(*args, **kwargs) return decorated # ─── BTCPAY HELPERS ─────────────────────────────────────── def create_btcpay_invoice(amount_usd: float, order_id: str, description: str) -> Optional[dict]: try: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE data = json.dumps({ "amount": str(amount_usd), "currency": "USD", "metadata": {"orderId": order_id, "description": description} }).encode() req = urllib.request.Request( f"{BTC_URL}/api/v1/stores/{BTC_STORE}/invoices", data=data, headers={"Authorization": f"token {BTC_KEY}", "Content-Type": "application/json"} ) resp = urllib.request.urlopen(req, context=ctx, timeout=30) inv = json.loads(resp.read()) return {"id": inv["id"], "checkout_url": inv["checkoutLink"]} except Exception as e: app.logger.error(f"BTCPay invoice creation failed: {e}") return None def check_btcpay_invoice(invoice_id: str) -> Optional[dict]: try: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE req = urllib.request.Request( f"{BTC_URL}/api/v1/stores/{BTC_STORE}/invoices/{invoice_id}", headers={"Authorization": f"token {BTC_KEY}"} ) resp = urllib.request.urlopen(req, context=ctx, timeout=15) return json.loads(resp.read()) except Exception as e: app.logger.error(f"BTCPay check failed: {e}") return None # ─── TEMPLATES ──────────────────────────────────────────── BASE_STYLE = """ """ BMAC_FOOTER = """
Support This App — Buy Me a Coffee
""" BASE_LAYOUT = """ {{page_title}} | {{APP_NAME}} {{STYLE}}
{{CONTENT}}
{{BMAC_FOOTER}} """ # ─── ROUTES: Auth ───────────────────────────────────────── @app.route('/register', methods=['GET', 'POST']) def register(): error = None if request.method == 'POST': email = request.form.get('email', '').strip() password = request.form.get('password', '').strip() if not email or not password: error = "All fields required" elif len(password) < 4: error = "Password too short" else: db = get_db() exists = db.execute("SELECT id FROM users WHERE email=?", (email,)).fetchone() if exists: error = "Email already registered" else: db.execute("INSERT INTO users (email,password_hash) VALUES (?,?)", (email, hash_password(password))) db.commit() user = db.execute("SELECT id FROM users WHERE email=?", (email,)).fetchone() session['user_id'] = user['id'] session['email'] = email db.close() return redirect('/dashboard') db.close() content = f"""

Create Account

{'
'+error+'
' if error else ''}

Already have an account? Login

""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Register").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) @app.route('/login', methods=['GET', 'POST']) def login(): error = None if request.method == 'POST': email = request.form.get('email', '').strip() password = request.form.get('password', '').strip() db = get_db() user = db.execute("SELECT * FROM users WHERE email=? AND password_hash=?", (email, hash_password(password))).fetchone() db.close() if user: session['user_id'] = user['id'] session['email'] = email nxt = request.args.get('next', '/dashboard') return redirect(nxt) error = "Invalid email or password" content = f"""

Welcome Back

{'
'+error+'
' if error else ''}

No account? Create one

""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Login").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) @app.route('/logout') def logout(): session.clear() return redirect('/') # ─── ROUTES: Landing ────────────────────────────────────── @app.route('/') def index(): content = f"""

{TAGLINE}

{APP_NAME} helps you get results fast. Free to start, premium when you need more power. Built for people who want things done.

Try Free → View Pricing
🎯

Tailored to the Role

Questions are matched to the real skills in the job description — not generic fluff.

🧠

STAR Behavioral Set

Proven behavioral questions with the STAR method baked in for strong 2-minute answers.

Pay with Bitcoin

Premium upgrades via BTCPay Server. No credit card, no KYC.

Ready to get started?

Join thousands of users who already trust {APP_NAME}

Create Free Account →
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Home").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) # ─── ROUTES: Pricing ────────────────────────────────────── @app.route('/pricing') def pricing(): content = f"""

Simple, Transparent Pricing

Pay once, own forever. No subscriptions.

Free

$0
Get Started Free

Premium

${PREMIUM_PRICE} one-time
Upgrade Now
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Pricing").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) # ─── ROUTES: Checkout & BTCPay ──────────────────────────── @app.route('/checkout') @login_required def checkout(): content = f"""

Upgrade to Premium

One-time payment of ${PREMIUM_PRICE} via Bitcoin

What you get:

Powered by BTCPay Server — secure, private, no middleman

← Back to pricing

""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Checkout").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) @app.route('/checkout/create', methods=['POST']) @login_required def checkout_create(): amount = float(request.form.get('amount', PREMIUM_PRICE)) order_id = f"order_{session['user_id']}_{int(datetime.now().timestamp())}" inv = create_btcpay_invoice(amount, order_id, f"{APP_NAME} Premium Upgrade") if inv: db = get_db() db.execute("INSERT INTO payments (user_id,invoice_id,amount_usd) VALUES (?,?,?)", (session['user_id'], inv['id'], amount)) db.execute("UPDATE users SET btcpay_invoice_id=? WHERE id=?", (inv['id'], session['user_id'])) db.commit() db.close() return redirect(inv['checkout_url']) content = """

Payment Error

Could not create payment invoice. Please try again.

Try Again
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Error").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) @app.route('/webhook/btcpay', methods=['POST']) def webhook_btcpay(): try: data = request.get_json(force=True) event_type = data.get('type', '') invoice_id = data.get('invoiceId') or (data.get('data', {}).get('id')) if event_type == 'InvoiceSettled' and invoice_id: db = get_db() db.execute("UPDATE payments SET status='settled',settled_at=datetime('now') WHERE invoice_id=?", (invoice_id,)) db.execute("UPDATE users SET is_premium=1,payment_status='paid' WHERE btcpay_invoice_id=?", (invoice_id,)) db.commit() db.close() return jsonify({"status": "ok"}), 200 except Exception as e: app.logger.error(f"Webhook error: {e}") return jsonify({"status": "error"}), 200 @app.route('/payment/success') @login_required def payment_success(): content = """

Payment Successful!

Your premium features are now unlocked. Thank you!

Go to Dashboard →
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Payment Success").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) @app.route('/payment/cancel') @login_required def payment_cancel(): content = """

Payment Cancelled

No worries! You can upgrade anytime.

View Plans
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Cancelled").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) # ─── ROUTES: Dashboard ──────────────────────────────────── @app.route('/dashboard') @login_required def dashboard(): db = get_db() user = db.execute("SELECT * FROM users WHERE id=?", (session['user_id'],)).fetchone() results = db.execute("SELECT * FROM results WHERE user_id=? ORDER BY created_at DESC LIMIT 10", (session['user_id'],)).fetchall() db.close() premium_badge = 'PREMIUM' if user['is_premium'] else '' upgrade_btn = 'Upgrade to Premium' if not user['is_premium'] else '' results_html = '' for r in results: results_html += '

' + str(r['data'])[:200] + '

' + r['created_at'] + '
' empty_msg = '

No results yet. Use the tool above to get started!

' if not results else '' content = """

Dashboard """ + premium_badge + """

""" + upgrade_btn + """

Welcome back, """ + user['email'] + """

Get Started

Paste a job description and InterviewForge forges tailored technical, behavioral, and follow-up questions.

Recent Results

""" + empty_msg + """ """ + results_html + """
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Dashboard").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) # ─── CORE FEATURE ENGINE (stdlib only) ───────────────────── SKILL_MAP = { "python": ["Walk me through a Python project you built and what you would refactor.", "How do you structure a large Python codebase for maintainability?", "Explain the differences between a list, tuple, and set. When do you reach for each?"], "java": ["Explain Java's inheritance model. How do you handle polymorphism in practice?", "How would you make a concurrent Java application thread-safe?"], "javascript":["How does the JavaScript event loop work?", "Explain closures. When would you use one?"], "sql": ["Explain the differences between INNER, LEFT, and FULL OUTER joins.", "How do you optimize a slow query? Walk me through your process."], "react": ["How does React handle re-rendering? How do you keep performance sane at scale?", "Explain hooks and the rules of hooks."], "aws": ["Walk me through how you would design a highly available service on AWS.", "How do you manage costs and multi-account strategy in AWS?"], "docker": ["Explain how Docker images, containers, and volumes relate.", "How would you build a reproducible deployment pipeline with Docker?"], "project management":["How do you prioritize when stakeholders have competing demands?", "Tell me about a time a project was off track. What did you do?"], "leadership": ["Give an example of a time you influenced a team without formal authority.", "How do you handle an underperforming team member?"], "sales": ["Walk me through your process for closing a difficult deal.", "Tell me about a time you lost a deal. What would you change?"], } BEHAVIORAL = [ "Tell me about a time you faced a significant challenge at work. How did you handle it?", "Describe a conflict with a coworker. How did you resolve it?", "Give an example of a goal you met or missed, and what you learned.", "How do you prioritize your tasks on a busy day?", "Tell me about a time you made a mistake. What happened, and what changed?", "Describe a situation where you had to make a quick decision with incomplete information.", "Give an example of how you trained or mentored someone.", "Tell me about a time you succeeded despite having minimal resources.", ] CLARIFY = [ "What did you mean by that? Can you give a concrete example?", "What was your specific role in that situation?", "How did you measure the outcome? What were the numbers?", "What would you do differently in hindsight?", "Can you walk me through the decision process step by step?", ] def detect_skills(text): low = text.lower() found = [] for skill, _ in SKILL_MAP.items(): if skill in low: found.append(skill) # generic tech markers if not found: for marker in ["software", "engineer", "developer", "devops", "data", "product", "design", "manager", "analyst"]: if marker in low: found.append("general") break return found if found else ["general"] def build_questions(td): import random import re role = re.sub(r'\s+', ' ', td.strip()) skills = detect_skills(role) tech = [] for s in skills: if s == "general": tech += ["Walk me through your most complex technical project. What were the tradeoffs?", "How do you debug a system that's failing in production but works locally?"] else: tech += SKILL_MAP[s] random.seed(len(td)) # deterministic per input behav = random.sample(BEHAVIORAL, k=min(3, len(BEHAVIORAL))) clarify = random.sample(CLARIFY, k=2) lines = [] lines.append("🎯 TAILORED INTERVIEW QUESTION SET") lines.append("") lines.append(f"Detected focus areas: {', '.join(skills).upper()}") lines.append("") lines.append("— TECHNICAL / ROLE-SPECIFIC —") for i, q in enumerate(tech[:6], 1): lines.append(f"{i}. {q}") lines.append("") lines.append("— BEHAVIORAL (STAR) —") for i, q in enumerate(behav, 1): lines.append(f"{i}. {q}") lines.append("") lines.append("— FOLLOW-UP / CLARIFYING —") for q in clarify: lines.append(f"• {q}") lines.append("") lines.append("💡 STAR method: Situation → Task → Action → Result. Aim for 2-minute answers.") return "\n".join(lines) def _process_core(input_data): return build_questions(input_data) # ─── ROUTES: Core Feature (customize this!) ─────────────── @app.route('/process', methods=['POST']) @login_required def process(): input_data = request.form.get('input_data', '') result = _process_core(input_data) db = get_db() db.execute("INSERT INTO results (user_id,data) VALUES (?,?)", (session['user_id'], result)) db.commit() db.close() tweet_text = f"I just used {APP_NAME} — {TAGLINE} Check it out!" tweet_url = f"https://twitter.com/intent/tweet?text={urllib.parse.quote(tweet_text)}" content = f"""

Your Result

{result}

← New Process
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Result").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) # ─── ROUTES: SEO & Health ───────────────────────────────── @app.route('/health') def health(): return jsonify({"status": "ok", "app": APP_NAME, "slug": APP_SLUG, "version": "1.0.0"}) @app.route('/sitemap.xml') def sitemap(): base = request.host_url.rstrip('/') urls = ['/', '/pricing', '/register', '/login'] xml = '\n\n' for u in urls: xml += f' {base}{u}\n' xml += '' response = make_response(xml) response.headers['Content-Type'] = 'application/xml' return response @app.route('/about') def about(): content = f"""

About {APP_NAME}

{TAGLINE}

Built with Flask, SQLite, and BTCPay Server for Bitcoin payments. Deployed on Proxmox infrastructure. Part of the Daily App Factory — one new app every day.

Version 1.0.0 — Built by Daily App Factory

""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "About").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)) # ─── ERROR HANDLERS ─────────────────────────────────────── @app.errorhandler(404) def not_found(e): content = """

404

Page not found

Go Home
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "404").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)), 404 @app.errorhandler(500) def server_error(e): content = """

500

Something went wrong. Please try again.

Go Home
""" return BASE_LAYOUT.replace("{{STYLE}}", BASE_STYLE).replace("{{APP_NAME}}", APP_NAME).replace("{{TAGLINE}}", TAGLINE).replace("{{ACCENT_COLOR}}", ACCENT_COLOR).replace("{{PRIMARY_COLOR}}", PRIMARY_COLOR).replace("{{page_title}}", "Error").replace("{{CONTENT}}", content).replace("{{BMAC_FOOTER}}", BMAC_FOOTER).replace("{{year}}", str(datetime.now().year)), 500 # ─── MAIN ───────────────────────────────────────────────── if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument('--port', type=int, default=5000) parser.add_argument('--host', default='0.0.0.0') args = parser.parse_args() os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) init_db() print(f"🚀 {APP_NAME} running on {args.host}:{args.port}") app.run(host=args.host, port=args.port, debug=False)