Monorepo: consolidate 7 repos into one

Combines shared, blog, market, cart, events, federation, and account
into a single repository. Eliminates submodule sync, sibling model
copying at build time, and per-app CI orchestration.

Changes:
- Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs
- Remove stale sibling model copies from each app
- Update all 6 Dockerfiles for monorepo build context (root = .)
- Add build directives to docker-compose.yml
- Add single .gitea/workflows/ci.yml with change detection
- Add .dockerignore for monorepo build context
- Create __init__.py for federation and account (cross-app imports)
This commit is contained in:
giles
2026-02-24 19:44:17 +00:00
commit f42042ccb7
895 changed files with 61147 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
from __future__ import annotations
# create the blueprint at package import time
from .routes import register # = Blueprint("browse_bp", __name__)
# import routes AFTER browse_bp is defined so routes can attach to it
from . import routes # noqa: F401

View File

View File

@@ -0,0 +1,28 @@
from __future__ import annotations
from quart import (
render_template, make_response, Blueprint
)
from shared.browser.app.authz import require_admin
def register():
bp = Blueprint("admin", __name__, url_prefix='/admin')
# ---------- Pages ----------
@bp.get("/")
@require_admin
async def admin():
from shared.browser.app.utils.htmx import is_htmx_request
# Determine which template to use based on request type
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/market/admin/index.html")
else:
html = await render_template("_types/market/admin/_oob_elements.html")
return await make_response(html)
return bp

View File

View File

@@ -0,0 +1,101 @@
from quart import request
from typing import Iterable, Optional, Union
from shared.browser.app.filters.qs_base import (
KEEP, _norm, make_filter_set, build_qs,
)
from shared.browser.app.filters.query_types import MarketQuery
def decode() -> MarketQuery:
page = int(request.args.get("page", 1))
search = request.args.get("search")
sort = request.args.get("sort")
liked = request.args.get("liked")
selected_brands = tuple(s.strip() for s in request.args.getlist("brand") if s.strip())
selected_stickers = tuple(s.strip().lower() for s in request.args.getlist("sticker") if s.strip())
selected_labels = tuple(s.strip().lower() for s in request.args.getlist("label") if s.strip())
return MarketQuery(page, search, sort, selected_brands, selected_stickers, selected_labels, liked)
def makeqs_factory():
"""
Build a makeqs(...) that starts from the current filters + page.
Auto-resets page to 1 when filters change unless you pass page explicitly.
"""
q = decode()
base_stickers = [s for s in q.selected_stickers if (s or "").strip()]
base_labels = [s for s in q.selected_labels if (s or "").strip()]
base_brands = [s for s in q.selected_brands if (s or "").strip()]
base_search = q.search or None
base_liked = q.liked or None
base_sort = q.sort or None
base_page = int(q.page or 1)
def makeqs(
*,
clear_filters: bool = False,
add_sticker: Union[str, Iterable[str], None] = None,
remove_sticker: Union[str, Iterable[str], None] = None,
add_label: Union[str, Iterable[str], None] = None,
remove_label: Union[str, Iterable[str], None] = None,
add_brand: Union[str, Iterable[str], None] = None,
remove_brand: Union[str, Iterable[str], None] = None,
search: Union[str, None, object] = KEEP,
sort: Union[str, None, object] = KEEP,
page: Union[int, None, object] = None,
extra: Optional[Iterable[tuple]] = None,
leading_q: bool = True,
liked: Union[bool, None, object] = KEEP,
) -> str:
stickers = make_filter_set(base_stickers, add_sticker, remove_sticker, clear_filters)
labels = make_filter_set(base_labels, add_label, remove_label, clear_filters)
brands = make_filter_set(base_brands, add_brand, remove_brand, clear_filters)
final_search = None if clear_filters else base_search if search is KEEP else ((search or "").strip() or None)
final_sort = base_sort if sort is KEEP else (sort or None)
final_liked = None if clear_filters else base_liked if liked is KEEP else liked
# Did filters change?
filters_changed = (
set(map(_norm, stickers)) != set(map(_norm, base_stickers))
or set(map(_norm, labels)) != set(map(_norm, base_labels))
or set(map(_norm, brands)) != set(map(_norm, base_brands))
or final_search != base_search
or final_sort != base_sort
or final_liked != base_liked
)
# Page logic
if page is KEEP:
final_page = 1 if filters_changed else base_page
else:
final_page = page
# Build params
params = []
for s in stickers:
params.append(("sticker", s))
for s in labels:
params.append(("label", s))
for s in brands:
params.append(("brand", s))
if final_search:
params.append(("search", final_search))
if final_liked is not None:
params.append(("liked", final_liked))
if final_sort:
params.append(("sort", final_sort))
if final_page is not None:
params.append(("page", str(final_page)))
if extra:
for k, v in extra:
if v is not None:
params.append((k, str(v)))
return build_qs(params, leading_q=leading_q)
return makeqs

View File

@@ -0,0 +1,49 @@
from __future__ import annotations
from quart import Blueprint, g, render_template, make_response, url_for
from ..browse.routes import register as register_browse_bp
from .filters.qs import makeqs_factory
from ..browse.services.nav import get_nav
from ..api.routes import products_api
from .admin.routes import register as register_admin
def register(url_prefix, title):
bp = Blueprint("market", __name__, url_prefix)
@bp.before_request
def route():
g.makeqs_factory = makeqs_factory
@bp.context_processor
async def inject_root():
market = getattr(g, "market", None)
market_id = market.id if market else None
post_data = getattr(g, "post_data", None) or {}
return {
**post_data,
"market_title": market.name if market else title,
"categories": (await get_nav(g.s, market_id=market_id))["cats"],
"qs": makeqs_factory()(),
"market": market,
}
bp.register_blueprint(
register_browse_bp(),
)
bp.register_blueprint(
products_api,
)
bp.register_blueprint(
register_admin(),
)
return bp