diff --git a/examples/federation_example.py b/examples/federation_example.py deleted file mode 100644 index 84d0b59a8351011603f856791d9328770e3c9bd9..0000000000000000000000000000000000000000 --- a/examples/federation_example.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Federation example showing handlers and signatures. -""" - -import asyncio -import logging -from datetime import datetime, timezone -from pathlib import Path - -from pyfed.models import APCreate, APNote, APPerson, APLike -from pyfed.security import KeyManager -from pyfed.security.http_signatures import HTTPSignatureVerifier -from pyfed.storage import StorageBackend -from pyfed.federation import ActivityDelivery -from pyfed.handlers import CreateHandler, LikeHandler -from pyfed.protocols.webfinger import WebFingerClient - -logger = logging.getLogger(__name__) - -async def federation_example(): - """Example of federation with handlers and signatures.""" - try: - # Initialize components - storage = StorageBackend.create( - provider="sqlite", - database_url="example_data/pyfed_example.db" - ) - await storage.initialize() - - # Initialize key manager and signature verifier - key_manager = KeyManager( - domain="example.com", - private_key_path="example_keys/private.pem", - public_key_path="example_keys/public.pem" - ) - await key_manager.initialize() - - signature_verifier = HTTPSignatureVerifier(key_manager) - - # Initialize handlers - create_handler = CreateHandler(storage) - like_handler = LikeHandler(storage) - - # Initialize delivery with signature support - delivery = ActivityDelivery( - key_manager=key_manager, - signature_verifier=signature_verifier - ) - - # Create and send a note - actor = APPerson( - id="https://example.com/users/alice", - name="Alice", - preferred_username="alice", - inbox="https://example.com/users/alice/inbox", - outbox="https://example.com/users/alice/outbox" - ) - - note = APNote( - id=f"https://example.com/notes/{datetime.now(timezone.utc).timestamp()}", - content="Hello Fediverse!", - attributed_to=str(actor.id), - to=["https://www.w3.org/ns/activitystreams#Public"] - ) - - # Create activity - create_activity = APCreate( - id=f"https://example.com/activities/{datetime.now(timezone.utc).timestamp()}", - actor=str(actor.id), - object=note, - to=note.to - ) - - # Process outgoing activity through handler - await create_handler.handle_outgoing(create_activity.serialize()) - - # Sign and deliver activity - activity_dict = create_activity.serialize() - signed_headers = await signature_verifier.sign_request( - method="POST", - path="/inbox", - body=activity_dict - ) - - # Deliver with signed headers - result = await delivery.deliver_activity( - activity=activity_dict, - recipients=["https://remote.example/inbox"], - headers=signed_headers - ) - - # Simulate receiving an activity - incoming_headers = { - "Signature": "...", # Signature from remote server - "Date": "...", - "Host": "example.com" - } - incoming_activity = { - "type": "Like", - "actor": "https://remote.example/users/bob", - "object": note.id - } - - # Verify signature of incoming activity - if await signature_verifier.verify_request(incoming_headers): - # Process through handler - await like_handler.handle_incoming(incoming_activity) - else: - logger.error("Invalid signature on incoming activity") - - finally: - await storage.close() - -if __name__ == "__main__": - asyncio.run(federation_example()) \ No newline at end of file diff --git a/examples/integration_examples.py b/examples/integration_examples.py deleted file mode 100644 index 3c483af555494f80313a6625b6957d47eb6116a3..0000000000000000000000000000000000000000 --- a/examples/integration_examples.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Framework integration examples. -""" - -# FastAPI Integration -from fastapi import FastAPI -from pyfed.integration.frameworks.fastapi import FastAPIIntegration -from pyfed.integration.config import IntegrationConfig - -async def fastapi_example(): - """FastAPI integration example.""" - # Create config - config = IntegrationConfig( - domain="example.com", - database_url="postgresql://user:pass@localhost/pyfed", - redis_url="redis://localhost", - media_path="uploads/", - key_path="keys/" - ) - - # Initialize integration - integration = FastAPIIntegration(config) - await integration.initialize() - - app = integration.app - - # Add custom routes - @app.get("/custom") - async def custom_route(): - return {"message": "Custom route"} - - return app - -# Django Integration -from django.urls import path -from pyfed.integration.frameworks.django import DjangoIntegration - -# settings.py -PYFED_CONFIG = { - 'domain': 'example.com', - 'database_url': 'postgresql://user:pass@localhost/pyfed', - 'redis_url': 'redis://localhost', - 'media_path': 'uploads/', - 'key_path': 'keys/', -} - -# urls.py -from django.urls import path, include - -urlpatterns = [ - path('', include('pyfed.integration.frameworks.django.urls')), -] - -# Flask Integration -from flask import Flask -from pyfed.integration.frameworks.flask import FlaskIntegration - -def flask_example(): - """Flask integration example.""" - app = Flask(__name__) - - config = IntegrationConfig( - domain="example.com", - database_url="postgresql://user:pass@localhost/pyfed", - redis_url="redis://localhost", - media_path="uploads/", - key_path="keys/" - ) - - integration = FlaskIntegration(config) - integration.initialize() - - return app \ No newline at end of file diff --git a/examples/key_server.py b/examples/key_server.py deleted file mode 100644 index 630529c3d45040d86ee070f9d72bdb8d8e028674..0000000000000000000000000000000000000000 --- a/examples/key_server.py +++ /dev/null @@ -1,168 +0,0 @@ -from fastapi import FastAPI, HTTPException, Query -from fastapi.responses import JSONResponse -import uvicorn -from pathlib import Path -from config import CONFIG -from datetime import datetime -from urllib.parse import unquote - -app = FastAPI() - -@app.get("/keys/{key_id}") -async def get_public_key(key_id: str): - """Serve public key for Mastodon to verify signatures""" - print(f"\n=== Received request for key: {key_id} ===") - - try: - # Construct the key path - key_files = list(Path(CONFIG['keys_path']).glob("*public.pem")) - print(f"Available files in {CONFIG['keys_path']}:") - print(key_files) - - # Find the key file that matches the key_id - key_path = None - for file in key_files: - if key_id in str(file): - key_path = file - break - - if not key_path: - print(f"ERROR: No key file found containing ID {key_id}") - raise HTTPException(status_code=404, detail="Key not found") - - print(f"Found key file at: {key_path}") - - # Read the public key - with open(key_path, 'r') as f: - public_key = f.read() - - response = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" - ], - "id": f"https://{CONFIG['domain']}/keys/{key_id}", - "owner": f"https://{CONFIG['domain']}/users/{CONFIG['user']}", - "publicKeyPem": public_key - } - - print(f"Sending response: {response}") - return JSONResponse( - content=response, - headers={ - "Content-Type": "application/activity+json" - } - ) - except Exception as e: - print(f"ERROR serving key: {str(e)}") - raise - -@app.get("/users/{username}") -async def get_user(username: str): - """Serve actor profile""" - print(f"\n=== Received request for user: {username} ===") - - if username != CONFIG['user']: - raise HTTPException(status_code=404, detail="User not found") - - # Get the active key ID from the key file - key_files = list(Path(CONFIG['keys_path']).glob("*public.pem")) - if not key_files: - raise HTTPException(status_code=500, detail="No public key found") - - # Extract key ID from filename - key_id = key_files[0].stem.split('_')[-2] - - actor = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" - ], - "id": f"https://{CONFIG['domain']}/users/{username}", - "type": "Person", - "preferredUsername": username, - "inbox": f"https://{CONFIG['domain']}/users/{username}/inbox", - "outbox": f"https://{CONFIG['domain']}/users/{username}/outbox", - "followers": f"https://{CONFIG['domain']}/users/{username}/followers", - "following": f"https://{CONFIG['domain']}/users/{username}/following", - "publicKey": { - "id": f"https://{CONFIG['domain']}/keys/{key_id}", - "owner": f"https://{CONFIG['domain']}/users/{username}", - "publicKeyPem": open(key_files[0], 'r').read() - }, - "endpoints": { - "sharedInbox": f"https://{CONFIG['domain']}/inbox" - } - } - - return JSONResponse( - content=actor, - headers={ - "Content-Type": "application/activity+json" - } - ) - -@app.get("/.well-known/webfinger") -async def webfinger(resource: str = Query(...)): - """Handle WebFinger requests""" - print(f"\n=== Received WebFinger request for: {resource} ===") - - # Parse the resource - resource = unquote(resource) - if not resource.startswith("acct:"): - raise HTTPException(status_code=400, detail="Invalid resource format") - - # Extract username and domain - try: - _, identifier = resource.split("acct:") - username, domain = identifier.split("@") - except ValueError: - raise HTTPException(status_code=400, detail="Invalid resource format") - - # Verify domain and username - if domain != CONFIG['domain'] or username != CONFIG['user']: - raise HTTPException(status_code=404, detail="User not found") - - response = { - "subject": f"acct:{username}@{domain}", - "aliases": [ - f"https://{domain}/users/{username}", - ], - "links": [ - { - "rel": "self", - "type": "application/activity+json", - "href": f"https://{domain}/users/{username}" - }, - { - "rel": "http://webfinger.net/rel/profile-page", - "type": "text/html", - "href": f"https://{domain}/users/{username}" - } - ] - } - - return JSONResponse( - content=response, - headers={ - "Content-Type": "application/jrd+json" - } - ) - -@app.get("/") -async def root(): - """Test endpoint""" - return { - "status": "running", - "domain": CONFIG['domain'], - "available_keys": list(str(p) for p in Path(CONFIG['keys_path']).glob("*public.pem")) - } - -if __name__ == "__main__": - print("\n=== Starting Key Server ===") - print(f"Domain: {CONFIG['domain']}") - print(f"Keys path: {CONFIG['keys_path']}") - print("Available keys:") - print(list(Path(CONFIG['keys_path']).glob("*public.pem"))) - - uvicorn.run(app, host="0.0.0.0", port=8880) \ No newline at end of file diff --git a/examples/note_example.py b/examples/note_example.py deleted file mode 100644 index 9cdc809bb5488eae439b362d83fcb3808fed15ff..0000000000000000000000000000000000000000 --- a/examples/note_example.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -Note creation and interaction examples. -""" - -import asyncio -import logging -from datetime import datetime, timezone -import os -import sys -from pathlib import Path -import ssl -from urllib.parse import urlparse - -# Add src directory to Python path -src_path = Path(__file__).parent.parent / "src" -sys.path.insert(0, str(src_path)) - -from pyfed.models import APCreate, APNote, APPerson -from pyfed.security import KeyManager -from pyfed.storage import StorageBackend -from pyfed.federation import ActivityDelivery -from pyfed.protocols.webfinger import WebFingerClient -from pyfed.security.http_signatures import HTTPSignatureVerifier -from pyfed.serializers.json_serializer import to_json, from_json - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -def ensure_directories(): - """Ensure required directories exist.""" - dirs = [ - "example_keys", - "example_data", - "example_media" - ] - - for dir_name in dirs: - path = Path(dir_name) - path.mkdir(parents=True, exist_ok=True) - logger.info(f"Ensured directory exists: {path}") - -async def create_note_example(): - """Example of creating and delivering a note.""" - try: - ensure_directories() - - # Initialize storage - storage = StorageBackend.create( - provider="sqlite", - database_url="example_data/pyfed_example.db" - ) - await storage.initialize() - - # Initialize key manager - key_manager = KeyManager( - domain="example.com", - active_keys=Path("example_keys").resolve(), - keys_path=str(Path("example_keys").resolve()) - ) - await key_manager.initialize() - - # Get the active key for signing - active_key = await key_manager.get_active_key() - logger.info(f"Active key: {active_key}") - - # Initialize HTTP signature verifier with the active key paths - logger.info(f"Using key paths: {key_manager.keys_path}/{active_key.key_id}_private.pem and {key_manager.keys_path}/{active_key.key_id}_public.pem") - signature_verifier = HTTPSignatureVerifier( - private_key_path=f"{key_manager.keys_path}/{active_key.key_id}_private.pem", - public_key_path=f"{key_manager.keys_path}/{active_key.key_id}_public.pem" - ) - - # Initialize delivery - delivery = ActivityDelivery( - key_manager=key_manager, - timeout=30 - ) - await delivery.initialize() - - # Initialize WebFinger client with SSL verification disabled for testing - webfinger = WebFingerClient(verify_ssl=False) - await webfinger.initialize() - - # Get inbox URL for recipient - recipient = "kene29@mastodon.social" - logger.info(f"Looking up inbox for {recipient}...") - inbox_url = await webfinger.get_inbox_url(recipient) - if not inbox_url: - logger.error(f"Could not find inbox for {recipient}") - return - - logger.info(f"Found inbox URL: {inbox_url}") - - # Create actor - actor = APPerson( - id="https://example.com/users/alice", - name="Alice", - preferred_username="alice", - inbox="https://example.com/users/alice/inbox", - outbox="https://example.com/users/alice/outbox", - followers="https://example.com/users/alice/followers" - ) - - # Create note with string attributed_to - note = APNote( - id=f"https://example.com/notes/{datetime.now(timezone.utc).timestamp()}", - content=f"Hello @{recipient}! This is a test note!", - attributed_to=str(actor.id), # Convert URL to string - to=[inbox_url], - cc=["https://www.w3.org/ns/activitystreams#Public"], - published=datetime.now(timezone.utc).isoformat() - ) - - # Create activity - create_activity = APCreate( - id=f"https://example.com/activities/{datetime.now(timezone.utc).timestamp()}", - actor=str(actor.id), # Convert URL to string - object=note, - to=note.to, - cc=note.cc, - published=datetime.now(timezone.utc).isoformat(), - ) - - # Serialize and deliver - logger.info("Serializing activity...") - activity_dict = create_activity.serialize() - # logger.info(f"Serialized activity: {activity_dict}") - # logger.info(f"Activity: {to_json(create_activity, indent=2)}") - - # Sign the request - parsed_url = urlparse(inbox_url) - headers = { - "Accept": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", - "Content-Type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", - "User-Agent": "PyFed/1.0", - "Host": parsed_url.netloc - } - logger.debug(f"Resolved host: {urlparse(inbox_url).netloc}") - signed_headers = await signature_verifier.sign_request( - method="POST", - path="/inbox", - headers=headers, - body=activity_dict - ) - - logger.debug(f"Headers before signing: {signed_headers}") - - logger.info("Delivering activity...") - result = await delivery.deliver_activity( - activity=activity_dict, - recipients=[inbox_url], - headers=signed_headers # Include signed headers - ) - logger.info(f"Delivery result: {result}") - - except Exception as e: - logger.error(f"Error in note example: {e}") - raise - finally: - if 'storage' in locals(): - await storage.close() - if 'delivery' in locals(): - await delivery.close() - if 'webfinger' in locals(): - await webfinger.close() - -def main(): - """Main entry point.""" - try: - asyncio.run(create_note_example()) - except KeyboardInterrupt: - logger.info("Example stopped by user") - except Exception as e: - logger.error(f"Example failed: {e}") - sys.exit(1) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/examples/run_example.py b/examples/run_example.py deleted file mode 100644 index 420ec82ea95b1e9e4196ebaaa6e8248b3c6b4aa7..0000000000000000000000000000000000000000 --- a/examples/run_example.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -PyFed runnable example. -""" - -import asyncio -import logging -from datetime import datetime -import os -import sys -from pathlib import Path - -# Add src directory to Python path -src_path = Path(__file__).parent.parent / "src" -sys.path.insert(0, str(src_path)) - -from pyfed.models import APCreate, APNote, APPerson, APLike -from pyfed.security import KeyManager -from pyfed.storage import StorageBackend -from pyfed.federation import ActivityDelivery -from pyfed.serializers.json_serializer import to_json, from_json - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -def ensure_directories(): - """Ensure required directories exist.""" - dirs = [ - "example_keys", - "example_data", - "example_media" - ] - - for dir_name in dirs: - path = Path(dir_name) - path.mkdir(parents=True, exist_ok=True) - logger.info(f"Ensured directory exists: {path}") - -async def run_examples(): - """Run PyFed examples.""" - try: - logger.info("Setting up PyFed components...") - ensure_directories() - - # Initialize storage - storage = StorageBackend.create( - provider="sqlite", - database_url="example_data/pyfed_example.db" - ) - await storage.initialize() - - # Initialize key manager - key_manager = KeyManager( - domain="example.com", - active_keys=Path("example_keys").resolve(), - keys_path=str(Path("example_keys").resolve()) - ) - await key_manager.initialize() - - # Initialize delivery - delivery = ActivityDelivery( - key_manager=key_manager, - timeout=30 - ) - - # Create actor - actor = APPerson( - id="https://example.com/users/alice", - name="Alice", - preferred_username="alice", - inbox="https://example.com/users/alice/inbox", - outbox="https://example.com/users/alice/outbox", - followers="https://example.com/users/alice/followers" - ) - - # Create note - note = APNote( - id="https://example.com/notes/123", - content="Hello, Federation! #test @bob@remote.com", - attributed_to=str(actor.id), - to=["https://www.w3.org/ns/activitystreams#Public"], - published=datetime.utcnow().isoformat() - ) - - # Create activity - create_activity = APCreate( - id=f"https://example.com/activities/{datetime.utcnow().timestamp()}", - actor=str(actor.id), - object=note, - to=note.to, - published=datetime.utcnow().isoformat() - ) - - # Store activity - logger.info("Storing activity...") - activity_dict = create_activity.serialize() - logger.info(f"Serialized activity (with context):\n{to_json(create_activity, indent=2)}") - activity_id = await storage.create_activity(activity_dict) - logger.info(f"Activity stored with ID: {activity_id}") - - # Create like activity - like_activity = APLike( - id=f"https://example.com/activities/like_{datetime.utcnow().timestamp()}", - actor=str(actor.id), - object=note.id, - to=["https://www.w3.org/ns/activitystreams#Public"], - published=datetime.utcnow().isoformat() - ) - - # Store like - logger.info("Storing like activity...") - like_dict = like_activity.serialize() - logger.info(f"Serialized like activity (with context):\n{to_json(like_activity, indent=2)}") - like_id = await storage.create_activity(like_dict) - logger.info(f"Like activity stored with ID: {like_id}") - - # Retrieve activities - logger.info("\nRetrieving activities...") - stored_activity = await storage.get_activity(activity_id) - stored_like = await storage.get_activity(like_id) - - logger.info("Retrieved Create activity:") - logger.info(stored_activity) - logger.info("\nRetrieved Like activity:") - logger.info(stored_like) - - except Exception as e: - logger.error(f"Error running examples: {e}") - raise - finally: - if storage: - await storage.close() - -def main(): - """Main entry point.""" - try: - asyncio.run(run_examples()) - except KeyboardInterrupt: - logger.info("Example stopped by user") - except Exception as e: - logger.error(f"Example failed: {e}") - sys.exit(1) - -if __name__ == "__main__": - main() \ No newline at end of file