gabriel / musehub public
feat patch mists task/phase4-mist-routes #1 / 1
gabriel · 163 days ago · Apr 15, 2026 · Diff

feat(mists): Phase 4 — Mist API routes, rate limits, and tests

Add 9 JSON REST endpoints for Mists (content-addressed, signed, forkable artifact shares) auto-registered via the existing musehub route package discovery mechanism.

Routes added (musehub/api/routes/musehub/mists.py): POST /api/mists create mist (rate-limited per handle) GET /api/mists/explore public discovery feed, cursor-paginated GET /api/mists/{mist_id} fetch single mist (increments view count) PATCH /api/mists/{mist_id} update mist (owner only) DELETE /api/mists/{mist_id} delete mist + backing repo (owner only) POST /api/mists/{mist_id}/fork fork mist (rate-limited per handle) GET /api/mists/{mist_id}/forks list forks of a mist GET /api/{owner}/mists list all mists for a handle GET /api/{owner}/mists/{mist_id}/embed embed metadata (iframe/js/badge)

Rate limits added (musehub/rate_limits.py): MIST_CREATE_LIMIT = '20/minute' (keyed on MSign handle) MIST_FORK_LIMIT = '30/minute' (keyed on MSign handle)

Tests added (tests/test_mist_routes.py, 65 tests, all passing): TestCreate, TestGet, TestUpdate, TestDelete, TestFork, TestList, TestIntegrationExplore, TestEmbed, TestStress, TestPerformance

Key implementation notes: - detect_artifact_type returns dict, not tuple; accessed via key lookup - explore pagination cursor contains '+00:00'; pass via httpx params= to avoid '+' → space mis-decoding in query strings - Bulk test data created via db_session with explicit timedelta offsets to guarantee stable cursor-based pagination in fast test runs

sha256:e2722f4df4004a5fbf83d22acfa2f0cfb70747cca17f6c6365be2e98f374611d sha
+140 symbols
sha256:c949f89c78d0683a23b5b159f7fe93fbcc100c71646fda0e96d5f61fce54831c snapshot
+140
symbols added
0
dead code introduced
Semantic Changes 140 symbols
~ musehub/api/routes/musehub/mists.py .py 44 symbols added
+ _base_url function function _base_url L55–57
+ _guard_mist_read function function _guard_mist_read L60–78
+ create_mist function async_function create_mist L92–177
+ delete_mist function async_function delete_mist L298–314
+ explore_mists function async_function explore_mists L189–215
+ fork_mist function async_function fork_mist L328–388
+ get_mist function async_function get_mist L227–244
+ get_mist_embed function async_function get_mist_embed L466–514
+ APIRouter import import APIRouter L32–32
+ AsyncSession import import AsyncSession L34–34
+ Depends import import Depends L32–32
+ HTTPException import import HTTPException L32–32
+ IntegrityError import import IntegrityError L33–33
+ MIST_CREATE_LIMIT import import MIST_CREATE_LIMIT L39–39
+ MIST_FORK_LIMIT import import MIST_FORK_LIMIT L39–39
+ MistCreateRequest import import MistCreateRequest L40–40
+ MistEmbedResponse import import MistEmbedResponse L40–40
+ MistForkResponse import import MistForkResponse L40–40
+ MistListResponse import import MistListResponse L40–40
+ MistResponse import import MistResponse L40–40
+ MistUpdateRequest import import MistUpdateRequest L40–40
+ PaginationParams import import PaginationParams L37–37
+ Query import import Query L32–32
+ Request import import Request L32–32
+ Response import import Response L32–32
+ TokenClaims import import TokenClaims L36–36
+ annotations import import annotations L26–26
+ build_cursor_link_header import import build_cursor_link_header L37–37
+ get_db import import get_db L38–38
+ get_msign_handle import import get_msign_handle L39–39
+ limiter import import limiter L39–39
+ logging import import logging L28–28
+ musehub_mists import import musehub_mists L48–48
+ musehub_repository import import musehub_repository L48–48
+ optional_token import import optional_token L36–36
+ require_scope import import require_scope L36–36
+ status import import status L32–32
+ time import import time L29–29
+ uuid import import uuid L30–30
+ list_mist_forks function async_function list_mist_forks L399–415
+ list_owner_mists function async_function list_owner_mists L427–454
+ logger variable variable logger L50–50
+ router variable variable router L52–52
+ update_mist function async_function update_mist L256–286
~ tests/test_mist_routes.py .py 94 symbols added
+ TestDataIntegrity class class TestDataIntegrity L817–874
+ test_delete_removes_from_list method async_method test_delete_removes_from_list L854–863
+ test_embed_count_independent_per_mist method async_method test_embed_count_independent_per_mist L866–874
+ test_fork_count_increments_on_parent method async_method test_fork_count_increments_on_parent L832–840
+ test_version_increments_on_content_update method async_method test_version_increments_on_content_update L843–851
+ test_view_count_increments_per_get method async_method test_view_count_increments_per_get L821–829
+ TestDocstrings class class TestDocstrings L1018–1045
+ test_guard_helper_has_docstring method method test_guard_helper_has_docstring L1038–1040
+ test_rate_limit_constants_exported method method test_rate_limit_constants_exported L1042–1045
+ test_route_handlers_have_docstrings method method test_route_handlers_have_docstrings L1021–1036
+ TestEdgeCases class class TestEdgeCases L712–779
+ test_content_analysis_json_schema method async_method test_content_analysis_json_schema L738–749
+ test_content_analysis_prose method async_method test_content_analysis_prose L727–735
+ test_explore_route_not_shadowed_by_mist_id method async_method test_explore_route_not_shadowed_by_mist_id L716–724
+ test_fork_depth_limit_enforced method async_method test_fork_depth_limit_enforced L752–765
+ test_update_no_fields_noop method async_method test_update_no_fields_noop L768–779
+ TestIntegrationCreate class class TestIntegrationCreate L145–235
+ test_create_detects_artifact_type method async_method test_create_detects_artifact_type L160–166
+ test_create_duplicate_content_returns_409 method async_method test_create_duplicate_content_returns_409 L201–209
+ test_create_requires_auth method async_method test_create_requires_auth L233–235
+ test_create_returns_201_and_mist_id method async_method test_create_returns_201_and_mist_id L149–157
+ test_create_returns_url_when_base_url_available method async_method test_create_returns_url_when_base_url_available L212–219
+ test_create_secret_visibility method async_method test_create_secret_visibility L190–198
+ test_create_signed_flag_when_gpg_signature_provided method async_method test_create_signed_flag_when_gpg_signature_provided L222–230
+ test_create_stores_tags method async_method test_create_stores_tags L169–173
+ test_create_stores_title_description method async_method test_create_stores_title_description L176–187
+ TestIntegrationDelete class class TestIntegrationDelete L552–593
+ test_delete_not_found_returns_404 method async_method test_delete_not_found_returns_404 L573–577
+ test_delete_removes_mist method async_method test_delete_removes_mist L564–570
+ test_delete_requires_auth method async_method test_delete_requires_auth L580–593
+ test_delete_returns_204 method async_method test_delete_returns_204 L556–561
+ TestIntegrationEmbed class class TestIntegrationEmbed L676–704
+ test_embed_increments_embed_count method async_method test_embed_increments_embed_count L688–696
+ test_embed_returns_200 method async_method test_embed_returns_200 L680–685
+ test_embed_wrong_owner_returns_404 method async_method test_embed_wrong_owner_returns_404 L699–704
+ TestIntegrationExplore class class TestIntegrationExplore L305–413
+ test_explore_artifact_type_filter method async_method test_explore_artifact_type_filter L349–358
+ test_explore_excludes_secret_mists method async_method test_explore_excludes_secret_mists L320–346
+ test_explore_pagination method async_method test_explore_pagination L361–413
+ test_explore_returns_public_mists method async_method test_explore_returns_public_mists L309–317
+ TestIntegrationFork class class TestIntegrationFork L596–641
+ test_fork_creates_unique_id method async_method test_fork_creates_unique_id L611–618
+ test_fork_not_found_returns_404 method async_method test_fork_not_found_returns_404 L621–625
+ test_fork_requires_auth method async_method test_fork_requires_auth L628–641
+ test_fork_returns_201 method async_method test_fork_returns_201 L600–608
+ TestIntegrationForkList class class TestIntegrationForkList L644–673
+ test_list_forks_after_fork method async_method test_list_forks_after_fork L657–668
+ test_list_forks_empty method async_method test_list_forks_empty L648–654
+ test_list_forks_parent_not_found method async_method test_list_forks_parent_not_found L671–673
+ TestIntegrationGet class class TestIntegrationGet L238–302
+ test_get_existing_returns_200 method async_method test_get_existing_returns_200 L242–250
+ test_get_increments_view_count method async_method test_get_increments_view_count L258–269
+ test_get_not_found_returns_404 method async_method test_get_not_found_returns_404 L253–255
+ test_get_secret_by_owner_succeeds method async_method test_get_secret_by_owner_succeeds L272–281
+ test_get_secret_without_auth_returns_403 method async_method test_get_secret_without_auth_returns_403 L284–302
+ TestIntegrationList class class TestIntegrationList L416–475
+ test_list_artifact_type_filter method async_method test_list_artifact_type_filter L466–475
+ test_list_excludes_secret_for_anon method async_method test_list_excludes_secret_for_anon L432–452
+ test_list_includes_secret_for_owner method async_method test_list_includes_secret_for_owner L455–463
+ test_list_owner_mists method async_method test_list_owner_mists L420–429
+ TestIntegrationUpdate class class TestIntegrationUpdate L478–549
+ test_update_content_increments_version method async_method test_update_content_increments_version L510–522
+ test_update_not_found_returns_404 method async_method test_update_not_found_returns_404 L525–533
+ test_update_requires_auth method async_method test_update_requires_auth L536–549
+ test_update_title method async_method test_update_title L482–493
+ test_update_visibility method async_method test_update_visibility L496–507
+ TestPerformance class class TestPerformance L882–904
+ test_explore_50_mists_under_1s method async_method test_explore_50_mists_under_1s L884–904
+ TestSecurity class class TestSecurity L912–1010
+ test_create_without_auth_returns_401 method async_method test_create_without_auth_returns_401 L916–918
+ test_delete_without_auth_returns_401 method async_method test_delete_without_auth_returns_401 L937–950
+ test_fork_depth_limit_prevents_over_5 method async_method test_fork_depth_limit_prevents_over_5 L994–1010
+ test_secret_mist_direct_get_403_for_anon method async_method test_secret_mist_direct_get_403_for_anon L974–991
+ test_secret_mist_hidden_in_explore method async_method test_secret_mist_hidden_in_explore L953–971
+ test_update_without_auth_returns_401 method async_method test_update_without_auth_returns_401 L921–934
+ TestStress class class TestStress L787–809
+ test_create_20_mists_and_explore method async_method test_create_20_mists_and_explore L791–809
+ TestUnitEmbedCodes class class TestUnitEmbedCodes L103–137
+ test_embed_badge_links_to_detail method async_method test_embed_badge_links_to_detail L129–137
+ test_embed_iframe_contains_mist_id method async_method test_embed_iframe_contains_mist_id L120–126
+ test_embed_returns_three_codes method async_method test_embed_returns_three_codes L107–117
+ _MD_CONTENT variable variable _MD_CONTENT L60–60
+ _OWNER variable variable _OWNER L57–57
+ _PY_CONTENT variable variable _PY_CONTENT L59–59
+ _create function async_function _create L92–95
+ _db_repo function async_function _db_repo L74–89
+ _mist_payload function function _mist_payload L63–71
+ AsyncClient import import AsyncClient L47–47
+ AsyncSession import import AsyncSession L48–48
+ JSONObject import import JSONObject L50–50
+ annotations import import annotations L41–41
+ pytest import import pytest L46–46
+ time import import time L43–43
+ uuid import import uuid L44–44
~ musehub/rate_limits.py .py 2 symbols added
+ MIST_CREATE_LIMIT variable variable MIST_CREATE_LIMIT L70–70
+ MIST_FORK_LIMIT variable variable MIST_FORK_LIMIT L71–71
← Older Oldest on task/phase4-mist-routes
All commits
Newer → Latest on task/phase4-mist-routes

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:e2722f4df4004a5fbf83d22acfa2f0cfb70747cca17f6c6365be2e98f374611d --body "your comment"