""" app/tenant/__init__.py — Tenant app factory (mydomain.com). """ import logging from datetime import timedelta import os from flask import Flask from app.extensions import ( db, migrate, csrf, mail, scheduler, tenant_login_manager, tenant_jwt, limiter, ) from app.security import apply_security_headers from app.context import load_tenant_context, load_location_context from config import get_config logger = logging.getLogger(__name__) def create_tenant_app(config_override=None): flask_app = Flask( __name__, template_folder="../templates", static_folder="../static", static_url_path="/static/tenant", ) # ── Config ──────────────────────────────────────────────── cfg = config_override or get_config() flask_app.config.from_object(cfg) flask_app.config["SESSION_COOKIE_NAME"] = "salon_pos_tenant_session" flask_app.config["PERMANENT_SESSION_LIFETIME"] = timedelta( seconds=int(os.environ.get("SESSION_TIMEOUT_TENANT", 1800)) ) flask_app.config["SESSION_COOKIE_HTTPONLY"] = True flask_app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # ── Extensions ──────────────────────────────────────────── db.init_app(flask_app) migrate.init_app(flask_app, db) csrf.init_app(flask_app) mail.init_app(flask_app) limiter.init_app(flask_app) # Tenant login manager tenant_login_manager.login_view = "tenant_auth.login" tenant_login_manager.login_message_category = "warning" tenant_login_manager.session_protection = "strong" tenant_login_manager.init_app(flask_app) # Tenant JWT tenant_jwt.init_app(flask_app) # ── User loaders ────────────────────────────────────────── from app.models.salon import User, Staff @tenant_login_manager.user_loader def load_tenant_user(user_id: str): if user_id.startswith("user:"): try: uid = int(user_id.split(":")[1]) except (ValueError, IndexError): return None return User.query.filter_by(id=uid, is_active=True)\ .filter(User.deleted_at.is_(None)).first() if user_id.startswith("staff:"): try: sid = int(user_id.split(":")[1]) except (ValueError, IndexError): return None return Staff.query.filter_by(id=sid, is_active=True)\ .filter(Staff.deleted_at.is_(None)).first() return None # ── Context hooks ───────────────────────────────────────── flask_app.before_request(load_tenant_context) flask_app.before_request(load_location_context) # ── Security ────────────────────────────────────────────── apply_security_headers(flask_app) # ── Blueprints ──────────────────────────────────────────── from app.tenant.auth.routes import tenant_auth_bp from app.tenant.staff_auth.routes import staff_auth_bp from app.tenant.checkin.routes import checkin_bp from app.tenant.dashboard.routes import dashboard_bp flask_app.register_blueprint(tenant_auth_bp) flask_app.register_blueprint(staff_auth_bp) flask_app.register_blueprint(checkin_bp) flask_app.register_blueprint(dashboard_bp) # Remaining blueprints registered in Phases 3–6: # locations, customers, appointments, services, pos, staff, # staff_portal, booking, waitlist, gift_cards, reviews, # reconciliation, inventory, marketing, reports, settings # ── Import all models for Migrate ───────────────────────── import app.models # noqa: F401 logger.info("Tenant app created (env=%s)", flask_app.config.get("FLASK_ENV")) return flask_app