feat: decouple blog from shared_lib, add app-owned models

Phase 1-3 of decoupling:
- path_setup.py adds project root to sys.path
- Blog-owned models in blog/models/ (ghost_content, snippet, tag_group)
- Re-export shims for shared models (user, kv, magic_link, menu_item)
- All imports updated: shared.infrastructure, shared.db, shared.browser, etc.
- No more cross-app post_id FKs in calendar/market/page_config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-11 12:46:31 +00:00
parent 5053448ee2
commit a01016d8d5
33 changed files with 550 additions and 106 deletions

View File

@@ -10,9 +10,9 @@ from quart import (
redirect,
url_for,
)
from suma_browser.app.authz import require_admin, require_post_author
from suma_browser.app.utils.htmx import is_htmx_request
from utils import host_url
from shared.browser.app.authz import require_admin, require_post_author
from shared.browser.app.utils.htmx import is_htmx_request
from shared.utils import host_url
def register():
bp = Blueprint("admin", __name__, url_prefix='/admin')
@@ -21,8 +21,8 @@ def register():
@bp.get("/")
@require_admin
async def admin(slug: str):
from suma_browser.app.utils.htmx import is_htmx_request
from models.page_config import PageConfig
from shared.browser.app.utils.htmx import is_htmx_request
from cart.models.page_config import PageConfig
from sqlalchemy import select as sa_select
# Load features for page admin
@@ -33,7 +33,7 @@ def register():
sumup_checkout_prefix = ""
if post.get("is_page"):
pc = (await g.s.execute(
sa_select(PageConfig).where(PageConfig.post_id == post["id"])
sa_select(PageConfig).where(PageConfig.container_type == "page", PageConfig.container_id == post["id"])
)).scalar_one_or_none()
if pc:
features = pc.features or {}
@@ -62,7 +62,7 @@ def register():
@require_admin
async def update_features(slug: str):
"""Update PageConfig.features for a page."""
from models.page_config import PageConfig
from cart.models.page_config import PageConfig
from models.ghost_content import Post
from sqlalchemy import select as sa_select
from quart import jsonify
@@ -76,10 +76,10 @@ def register():
# Load or create PageConfig
pc = (await g.s.execute(
sa_select(PageConfig).where(PageConfig.post_id == post_id)
sa_select(PageConfig).where(PageConfig.container_type == "page", PageConfig.container_id == post_id)
)).scalar_one_or_none()
if pc is None:
pc = PageConfig(post_id=post_id, features={})
pc = PageConfig(container_type="page", container_id=post_id, features={})
g.s.add(pc)
await g.s.flush()
@@ -127,7 +127,7 @@ def register():
@require_admin
async def update_sumup(slug: str):
"""Update PageConfig SumUp credentials for a page."""
from models.page_config import PageConfig
from cart.models.page_config import PageConfig
from sqlalchemy import select as sa_select
from quart import jsonify
@@ -138,10 +138,10 @@ def register():
post_id = post["id"]
pc = (await g.s.execute(
sa_select(PageConfig).where(PageConfig.post_id == post_id)
sa_select(PageConfig).where(PageConfig.container_type == "page", PageConfig.container_id == post_id)
)).scalar_one_or_none()
if pc is None:
pc = PageConfig(post_id=post_id, features={})
pc = PageConfig(container_type="page", container_id=post_id, features={})
g.s.add(pc)
await g.s.flush()
@@ -187,7 +187,7 @@ def register():
@require_admin
async def calendar_view(slug: str, calendar_id: int):
"""Show calendar month view for browsing entries"""
from models.calendars import Calendar
from events.models.calendars import Calendar
from sqlalchemy import select
from datetime import datetime, timezone
from quart import request
@@ -269,7 +269,7 @@ def register():
@require_admin
async def entries(slug: str):
from ..services.entry_associations import get_post_entry_ids
from models.calendars import Calendar
from events.models.calendars import Calendar
from sqlalchemy import select
post_id = g.post_data["post"]["id"]
@@ -305,7 +305,7 @@ def register():
@require_admin
async def toggle_entry(slug: str, entry_id: int):
from ..services.entry_associations import toggle_entry_association, get_post_entry_ids, get_associated_entries
from models.calendars import Calendar
from events.models.calendars import Calendar
from sqlalchemy import select
from quart import jsonify
@@ -339,7 +339,7 @@ def register():
calendars = (
await g.s.execute(
select(Calendar)
.where(Calendar.post_id == post_id, Calendar.deleted_at.is_(None))
.where(Calendar.container_type == "page", Calendar.container_id == post_id, Calendar.deleted_at.is_(None))
.order_by(Calendar.name.asc())
)
).scalars().all()
@@ -389,7 +389,7 @@ def register():
async def settings_save(slug: str):
from ...blog.ghost.ghost_posts import update_post_settings
from ...blog.ghost.ghost_sync import sync_single_post
from suma_browser.app.redis_cacher import invalidate_tag_cache
from shared.browser.app.redis_cacher import invalidate_tag_cache
ghost_id = g.post_data["post"]["ghost_id"]
form = await request.form
@@ -452,7 +452,7 @@ def register():
@require_post_author
async def edit(slug: str):
from ...blog.ghost.ghost_posts import get_post_for_edit
from models.ghost_membership_entities import GhostNewsletter
from shared.models.ghost_membership_entities import GhostNewsletter
from sqlalchemy import select as sa_select
ghost_id = g.post_data["post"]["ghost_id"]
@@ -487,7 +487,7 @@ def register():
from ...blog.ghost.ghost_posts import update_post
from ...blog.ghost.lexical_validator import validate_lexical
from ...blog.ghost.ghost_sync import sync_single_post
from suma_browser.app.redis_cacher import invalidate_tag_cache
from shared.browser.app.redis_cacher import invalidate_tag_cache
ghost_id = g.post_data["post"]["ghost_id"]
form = await request.form
@@ -599,7 +599,7 @@ def register():
@require_admin
async def markets(slug: str):
"""List markets for this page."""
from models.market_place import MarketPlace
from market.models.market_place import MarketPlace
from sqlalchemy import select as sa_select
post = (g.post_data or {}).get("post", {})
@@ -609,7 +609,8 @@ def register():
page_markets = (await g.s.execute(
sa_select(MarketPlace).where(
MarketPlace.post_id == post_id,
MarketPlace.container_type == "page",
MarketPlace.container_id == post_id,
MarketPlace.deleted_at.is_(None),
).order_by(MarketPlace.name)
)).scalars().all()
@@ -626,7 +627,7 @@ def register():
async def create_market(slug: str):
"""Create a new market for this page."""
from ..services.markets import create_market as _create_market, MarketError
from models.market_place import MarketPlace
from market.models.market_place import MarketPlace
from sqlalchemy import select as sa_select
from quart import jsonify
@@ -646,7 +647,8 @@ def register():
# Return updated markets list
page_markets = (await g.s.execute(
sa_select(MarketPlace).where(
MarketPlace.post_id == post_id,
MarketPlace.container_type == "page",
MarketPlace.container_id == post_id,
MarketPlace.deleted_at.is_(None),
).order_by(MarketPlace.name)
)).scalars().all()
@@ -663,7 +665,7 @@ def register():
async def delete_market(slug: str, market_slug: str):
"""Soft-delete a market."""
from ..services.markets import soft_delete_market
from models.market_place import MarketPlace
from market.models.market_place import MarketPlace
from sqlalchemy import select as sa_select
from quart import jsonify
@@ -677,7 +679,8 @@ def register():
# Return updated markets list
page_markets = (await g.s.execute(
sa_select(MarketPlace).where(
MarketPlace.post_id == post_id,
MarketPlace.container_type == "page",
MarketPlace.container_id == post_id,
MarketPlace.deleted_at.is_(None),
).order_by(MarketPlace.name)
)).scalars().all()