{hash_short}Activity Not Found
This activity does not exist.
- + ''' return HTMLResponse(base_html("Activity Not Found", content, username)) @@ -802,7 +808,7 @@ async def ui_activity_detail(activity_index: int, request: Request): ''' content = f''' - +{webfinger}Asset Not Found
No asset named "{name}" exists.
- + ''' return HTMLResponse(base_html("Asset Not Found", content, username)) @@ -1093,7 +1099,7 @@ async def ui_asset_detail(name: str, request: Request): ''' content = f''' - +{name}
@@ -1106,7 +1112,7 @@ async def ui_asset_detail(name: str, request: Request):User Not Found
No user named "{username}" exists.
- + ''' return HTMLResponse(base_html("User Not Found", content, current_user)) @@ -1192,7 +1198,7 @@ async def ui_user_detail(username: str, request: Request): rows += f'''{hash_short}No published assets yet.
' content = f''' - +{username}
@@ -1381,20 +1387,114 @@ async def webfinger(resource: str): ) +@app.get("/users") +async def get_users_list(request: Request, page: int = 1, limit: int = 20): + """Get all users. HTML for browsers (with infinite scroll), JSON for APIs (with pagination).""" + all_users = list(load_users(DATA_DIR).items()) + total = len(all_users) + + # Sort by username + all_users.sort(key=lambda x: x[0]) + + # Pagination + start = (page - 1) * limit + end = start + limit + users_page = all_users[start:end] + has_more = end < total + + if wants_html(request): + username = get_user_from_cookie(request) + + if not users_page: + if page == 1: + content = ''' +Users
+No users registered yet.
+ ''' + else: + return HTMLResponse("") # Empty for infinite scroll + else: + rows = "" + for uname, user_data in users_page: + webfinger = f"@{uname}@{DOMAIN}" + rows += f''' +{webfinger}Users ({total} total)
+| Username | +WebFinger | +Created | +
|---|
User Not Found
+No user named "{username}" exists.
+ + ''' + return HTMLResponse(base_html("User Not Found", content, get_user_from_cookie(request))) raise HTTPException(404, f"Unknown user: {username}") - # Check Accept header for content negotiation - accept = request.headers.get("accept", "") - wants_html = "text/html" in accept and "application/json" not in accept and "application/activity+json" not in accept - - if wants_html: - # Redirect to UI page for browsers - from fastapi.responses import RedirectResponse - return RedirectResponse(url=f"/ui/user/{username}", status_code=303) + if wants_html(request): + # Render user detail page + return await ui_user_detail(username, request) actor = load_actor(username) @@ -1496,14 +1596,130 @@ async def get_followers(username: str): # ============ Registry Endpoints ============ @app.get("/registry") -async def get_registry(): - """Get full registry.""" - return load_registry() +async def get_registry(request: Request, page: int = 1, limit: int = 20): + """Get registry. HTML for browsers (with infinite scroll), JSON for APIs (with pagination).""" + registry = load_registry() + all_assets = list(registry.get("assets", {}).items()) + total = len(all_assets) + + # Sort by created_at descending + all_assets.sort(key=lambda x: x[1].get("created_at", ""), reverse=True) + + # Pagination + start = (page - 1) * limit + end = start + limit + assets_page = all_assets[start:end] + has_more = end < total + + if wants_html(request): + username = get_user_from_cookie(request) + + if not assets_page: + if page == 1: + content = ''' +Registry
+No assets registered yet.
+ ''' + else: + return HTMLResponse("") # Empty for infinite scroll + else: + rows = "" + for name, asset in assets_page: + asset_type = asset.get("asset_type", "") + type_color = "bg-blue-600" if asset_type == "image" else "bg-purple-600" if asset_type == "video" else "bg-gray-600" + owner = asset.get("owner", "unknown") + content_hash = asset.get("content_hash", "")[:16] + "..." + rows += f''' +{content_hash}Registry ({total} assets)
+| Name | +Type | +Owner | +Hash | ++ |
|---|
Asset Not Found
+No asset named "{name}" exists.
+ + ''' + return HTMLResponse(base_html("Asset Not Found", content, get_user_from_cookie(request))) + raise HTTPException(404, f"Asset not found: {name}") + + if wants_html(request): + return await ui_asset_detail(name, request) + + return registry["assets"][name] @app.get("/registry/{name}") async def get_asset(name: str): - """Get a specific asset.""" + """Get a specific asset (API only, use /asset/{name} for content negotiation).""" registry = load_registry() if name not in registry.get("assets", {}): raise HTTPException(404, f"Asset not found: {name}") @@ -1777,9 +1993,130 @@ async def publish_cache(req: PublishCacheRequest, user: User = Depends(get_requi # ============ Activities Endpoints ============ @app.get("/activities") -async def get_activities(): - """Get all activities.""" - return {"activities": load_activities()} +async def get_activities(request: Request, page: int = 1, limit: int = 20): + """Get activities. HTML for browsers (with infinite scroll), JSON for APIs (with pagination).""" + all_activities = load_activities() + total = len(all_activities) + + # Reverse for newest first + all_activities = list(reversed(all_activities)) + + # Pagination + start = (page - 1) * limit + end = start + limit + activities_page = all_activities[start:end] + has_more = end < total + + if wants_html(request): + username = get_user_from_cookie(request) + + if not activities_page: + if page == 1: + content = ''' +Activities
+No activities yet.
+ ''' + else: + return HTMLResponse("") # Empty for infinite scroll + else: + rows = "" + for i, activity in enumerate(activities_page): + activity_index = total - 1 - (start + i) # Original index + obj = activity.get("object_data", {}) + activity_type = activity.get("activity_type", "") + type_color = "bg-green-600" if activity_type == "Create" else "bg-yellow-600" if activity_type == "Update" else "bg-gray-600" + actor_id = activity.get("actor_id", "") + actor_name = actor_id.split("/")[-1] if actor_id else "unknown" + rows += f''' +Activities ({total} total)
+| Type | +Object | +Actor | +Published | ++ |
|---|
Activity Not Found
+This activity does not exist.
+ + ''' + return HTMLResponse(base_html("Activity Not Found", content, get_user_from_cookie(request))) + raise HTTPException(404, "Activity not found") + + activity = activities[activity_index] + + if wants_html(request): + # Reuse the UI activity detail logic + return await ui_activity_detail(activity_index, request) + + return activity @app.get("/objects/{content_hash}")