_load_user ran before _check_auth_state, so g.user was set to the wrong
user before the grant check could clear the stale session. Now grant
verification runs first, ensuring stale sessions are cleared before
the user is loaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues fixed:
- Sessions with uid but no grant_token (legacy or corrupt) were not
validated at all, allowing a user to be logged in as whoever got
their old numeric user ID after a DB rebuild
- DB errors during grant verification silently kept stale sessions
alive; now treated as invalid to fail-safe
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each service (blog, market, cart, events, federation, account) now owns
its own database schema with independent Alembic migrations. Removes the
monolithic shared/alembic/ that ran all migrations against a single DB.
- Add per-service alembic.ini, env.py, and 0001_initial.py migrations
- Add shared/db/alembic_env.py helper with table-name filtering
- Fix cross-DB FK in blog/models/snippet.py (users lives in db_account)
- Fix cart_impl.py cross-DB queries: fetch products and market_places
via internal data endpoints instead of direct SQL joins
- Fix blog ghost_sync to fetch page_configs from cart via data endpoint
- Add products-by-ids and page-config-ensure data endpoints
- Update all entrypoint.sh to create own DB and run own migrations
- Cart now uses db_cart instead of db_market
- Add docker-compose.dev.yml, dev.sh for local development
- CI deploys both rose-ash swarm stack and rose-ash-dev compose stack
- Fix Quart namespace package crash (root_path in factory.py)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AP blueprints (activitypub.py, ap_social.py) were querying federation
tables (ap_actor_profiles etc.) on g.s which points to the app's own DB
after the per-app split. Now uses g._ap_s backed by get_federation_session()
for non-federation apps.
Also hardens Ghost sync before_app_serving to catch/rollback on failure
instead of crashing the Hypercorn worker.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move Ghost membership sync from blog to account service so blog no
longer queries account tables (users, ghost_labels, etc.). Account
runs membership sync at startup and exposes HTTP action/data endpoints
for webhook-triggered syncs and user lookups.
Key changes:
- account/services/ghost_membership.py: all membership sync functions
- account/bp/actions + data: ghost-sync-member, user-by-email, newsletters
- blog ghost_sync.py: stripped to content-only (posts, authors, tags)
- blog webhook member: delegates to account via call_action()
- try_publish: opens federation session when DBs differ
- oauth.py callback: uses get_account_session() for OAuthCode
- page_configs moved from db_events to db_blog in split script
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
send_follow now emits a Follow activity via emit_activity() instead of
inline HTTP POST. New ap_follow_handler delivers to the remote inbox;
EventProcessor retries on failure. Wildcard delivery handler skips
Follow type to avoid duplicate broadcast.
Also add /social/ index page to per-app social blueprint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add app_domain to APNotification model and NotificationDTO so follow
notifications display "followed you on blog" instead of just "followed
you" when the follow targets a per-app actor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lightweight social pages (search, follow/unfollow, followers, following,
actor timeline) auto-registered for AP-enabled apps via shared blueprint.
Federation keeps the full social hub. Followers scoped per app_domain;
post cards show "View on Hub" link instead of interaction buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redis: per-app DB index (0-5) with shared auth DB 15 for SSO keys;
flushdb replaces flushall so deploys don't wipe cross-app auth state.
Postgres: drop 13 cross-domain FK constraints (migration v2t0p8q9r0),
remove dead ORM relationships, add explicit joins for 4 live ones.
Multi-engine sessions (account + federation) ready for per-domain DBs
via DATABASE_URL_ACCOUNT / DATABASE_URL_FEDERATION env vars.
All URLs initially point to the same appdb — zero behaviour change
until split-databases.sh is run to migrate data to per-domain DBs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_load_user runs before _check_auth_state, so g.user was already set
when the grant was found revoked. The session was cleared but g.user
stayed populated, causing the template to render the signed-in UI
for one request after logout. Now sets g.user = None alongside the
session clear.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete shared/contracts/widgets.py, shared/services/widget_registry.py,
and shared/services/widgets/ (empty stubs). Remove register_all_widgets()
from factory and widgets Jinja global from jinja_setup. Zero consumers
remain — all cross-app UI composition now uses the fragment API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Append synthetic artdag nav entry to blog's nav-tree handler so
Art-DAG appears in the shared navigation across all 6 coop apps.
Register artdag_url as Jinja global.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- fetch_fragment_batch() for N+1 avoidance with per-key Redis cache
- link-card fragment handlers in blog, market, events, federation (single + batch mode)
- link_card.html templates per app with content-specific previews
- shared/infrastructure/oembed.py: build_oembed_response, build_og_meta, build_oembed_link_tag
- GET /oembed routes on blog, market, events
- og_meta + oembed_link rendering in base template <head>
- INTERNAL_URL_ARTDAG in docker-compose.yml for cross-stack fragment fetches
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cart _cart.html: replace url_for('market.browse.product...') with
market_product_url() for links and cart_global.update_quantity for
quantity forms (market endpoints don't exist in cart app)
- Factory favicon route: use STATIC_DIR instead of relative "static"
(resolves to shared/static/ where favicon.ico actually lives)
- Cart context processor: fetch all 3 fragments (cart-mini, auth-menu,
nav-tree) concurrently, matching pattern in all other apps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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)