Files
MyPOS/app/tenant/__init__.py
T

110 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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
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 36:
# 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