chore(v2): restructure monorepo to src/ layout with uv
Aligns the repo with the python-project-spec-design.md template chosen for V2.0.0. Big move, no logic changes. The 3 pre-existing test failures (test_recipes::test_update_recipe, test_recipes:: test_recipe_versioning, test_tasks::test_reorder_tasks, plus the client test_save_measurement_proxy) survive unchanged. Layout changes - server/ -> src/backend/ - server/middleware/ -> src/backend/api/middleware/ - server/routers/ -> src/backend/api/routers/ - server/models/ -> src/backend/models/orm/ - server/schemas/ -> src/backend/models/api/ - server/uploads/ -> uploads/ (project root, mounted volume) - server/tests/ -> src/backend/tests/ - client/ -> src/frontend/flask_app/ (Flask kept; React deroga is documented in CLAUDE.md, justified by tablet UX, USB caliper/barcode workflow and Fabric.js integration) Tooling - pyproject.toml: monorepo with [project] core deps and optional-dependencies server / client / dev. Replaces both server/requirements.txt and client/requirements.txt. - uv.lock + .python-version (3.11) committed for reproducible builds. - Dockerfile (root, backend) and Dockerfile.frontend rewritten to use uv sync --frozen --no-dev --extra server|client; legacy Dockerfiles preserved as Dockerfile.legacy for reference but excluded from build context via .dockerignore. - docker-compose.dev.yml + docker-compose.yml: build context now ".", dockerfile pointing to the root files. Code adjustments forced by the move - Every "from config|database|models|schemas|services|routers|middleware import ..." rewritten to its src.backend.* equivalent (50+ files including indented inline imports inside test bodies). - src/backend/migrations/env.py: insert project root into sys.path so alembic can resolve src.backend.* imports regardless of cwd. - src/backend/config.py: env_file ../../.env (was ../.env), upload_path resolves project root via parents[2]. - src/backend/tests/conftest.py + tests: import ... from src.backend.* instead of bare names; old per-directory pytest.ini files removed in favor of root pyproject.toml [tool.pytest.ini_options]. - .gitignore: uploads/ at root, src/frontend/flask_app/static/css/ tailwind.css path; .dockerignore tightened. - CLAUDE.md: rewrote sections "Layout del repository", "Comandi di Sviluppo", "Database & Migrations", "Test", "i18n", and all path references throughout the architecture sections. Verified - uv lock resolves 77 packages; uv sync --extra server --extra client --extra dev installs cleanly. - uv run pytest: 171 passed, 4 pre-existing failures. - uv run alembic -c src/backend/migrations/alembic.ini check loads config and metadata (errors only on the absent local MySQL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""TieMeasureFlow Client - Flask Entry Point."""
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Flask, redirect, url_for, session, request
|
||||
from flask_babel import Babel
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from markupsafe import Markup
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from config import Config
|
||||
|
||||
|
||||
def get_locale():
|
||||
"""Get user's preferred language from session or Accept-Language header."""
|
||||
# 1. User preference in session
|
||||
if "language" in session:
|
||||
return session["language"]
|
||||
# 2. Browser Accept-Language
|
||||
return request.accept_languages.best_match(
|
||||
Config.LANGUAGES.keys(), default="it"
|
||||
)
|
||||
|
||||
|
||||
def create_app() -> Flask:
|
||||
"""Application factory."""
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
|
||||
# Trust one reverse-proxy hop (Nginx in dev, Traefik in prod) so that
|
||||
# request.remote_addr returns the real tablet IP rather than the proxy IP.
|
||||
# The APIClient forwards that IP to FastAPI for accurate rate limiting.
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
|
||||
|
||||
# Initialize CSRF protection
|
||||
csrf = CSRFProtect(app)
|
||||
|
||||
# Initialize Flask-Babel
|
||||
Babel(app, locale_selector=get_locale)
|
||||
|
||||
# Register blueprints
|
||||
from blueprints.auth import auth_bp
|
||||
from blueprints.measure import measure_bp
|
||||
from blueprints.maker import maker_bp
|
||||
from blueprints.statistics import statistics_bp
|
||||
from blueprints.admin import admin_bp
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(measure_bp, url_prefix="/measure")
|
||||
app.register_blueprint(maker_bp, url_prefix="/maker")
|
||||
app.register_blueprint(statistics_bp, url_prefix="/statistics")
|
||||
app.register_blueprint(admin_bp, url_prefix="/admin")
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
"""Root redirect to login or dashboard based on session."""
|
||||
if "user" in session:
|
||||
return redirect(url_for("measure.select_recipe"))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
@app.route("/set-language/<lang>")
|
||||
def set_language(lang):
|
||||
"""Set user's preferred language and store in session."""
|
||||
if lang in Config.LANGUAGES:
|
||||
session["language"] = lang
|
||||
return redirect(request.referrer or url_for("auth.login"))
|
||||
|
||||
@app.template_filter("tojson_attr")
|
||||
def tojson_attr_filter(value):
|
||||
"""JSON encode safe for HTML attributes (x-data, etc.).
|
||||
|
||||
Unlike |tojson, this escapes double quotes to " so the output
|
||||
can be safely embedded inside double-quoted HTML attributes.
|
||||
The browser decodes the entities before Alpine.js evaluates them.
|
||||
"""
|
||||
rv = json.dumps(value, ensure_ascii=False)
|
||||
rv = (
|
||||
rv.replace("&", "\\u0026")
|
||||
.replace("<", "\\u003c")
|
||||
.replace(">", "\\u003e")
|
||||
.replace("'", "\\u0027")
|
||||
.replace('"', """)
|
||||
)
|
||||
return Markup(rv)
|
||||
|
||||
@app.context_processor
|
||||
def inject_globals():
|
||||
"""Inject global variables into all templates."""
|
||||
return {
|
||||
"current_user": session.get("user"),
|
||||
"current_theme": session.get("theme", "light"),
|
||||
"current_language": get_locale(),
|
||||
"languages": Config.LANGUAGES,
|
||||
"company_logo": session.get("company_logo"),
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = create_app()
|
||||
app.run(
|
||||
host=os.getenv("CLIENT_HOST", "0.0.0.0"),
|
||||
port=int(os.getenv("CLIENT_PORT", "5000")),
|
||||
debug=True,
|
||||
)
|
||||
Reference in New Issue
Block a user