131 lines
5.5 KiB
Python
131 lines
5.5 KiB
Python
"""
|
|
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):
|
|
import os as _os
|
|
# Resolve paths relative to this file to avoid CWD-dependent behaviour.
|
|
# app/tenant/__init__.py → ../../templates = <project_root>/templates
|
|
_here = _os.path.dirname(_os.path.abspath(__file__))
|
|
_root = _os.path.normpath(_os.path.join(_here, "..", ".."))
|
|
flask_app = Flask(
|
|
__name__,
|
|
template_folder=_os.path.join(_root, "templates"),
|
|
static_folder=_os.path.join(_root, "static", "tenant"),
|
|
static_url_path="/static",
|
|
)
|
|
|
|
# ── 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
|
|
from app.tenant.locations.routes import locations_bp
|
|
from app.tenant.customers.routes import customers_bp
|
|
from app.tenant.services.routes import services_bp
|
|
from app.tenant.appointments.routes import appointments_bp
|
|
from app.tenant.pos.routes import pos_bp
|
|
from app.tenant.gift_cards.routes import gift_cards_bp
|
|
from app.tenant.staff.routes import staff_bp
|
|
from app.tenant.staff_portal.routes import staff_portal_bp
|
|
from app.tenant.booking.routes import booking_bp
|
|
from app.tenant.waitlist.routes import waitlist_bp
|
|
from app.tenant.reviews.routes import reviews_bp
|
|
from app.tenant.reconciliation.routes import reconciliation_bp
|
|
from app.tenant.settings.routes import settings_bp
|
|
|
|
for bp in [
|
|
tenant_auth_bp, staff_auth_bp, checkin_bp, dashboard_bp,
|
|
locations_bp, customers_bp, services_bp, appointments_bp,
|
|
pos_bp, gift_cards_bp, staff_bp, staff_portal_bp,
|
|
booking_bp, waitlist_bp, reviews_bp, reconciliation_bp,
|
|
settings_bp,
|
|
]:
|
|
flask_app.register_blueprint(bp)
|
|
|
|
# Phase 4+ stubs (inventory, marketing, reports)
|
|
from app.tenant.inventory.routes import inventory_bp
|
|
from app.tenant.marketing.routes import marketing_bp
|
|
from app.tenant.reports.routes import reports_bp
|
|
flask_app.register_blueprint(inventory_bp)
|
|
flask_app.register_blueprint(marketing_bp)
|
|
flask_app.register_blueprint(reports_bp)
|
|
|
|
# ── 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
|