Remove REST API, keep GraphQL as sole API
- Add missing GraphQL mutations: retryJob, updateAsset, deleteAsset - Add UpdateAssetRequest and DeleteResult to schema source of truth - Move Lambda callback endpoint to main.py (only REST endpoint) - Remove REST routes, pydantic schemas, and deps - Remove pydantic target from modelgen.json - Update architecture diagrams and documentation
This commit is contained in:
54
api/deps.py
54
api/deps.py
@@ -1,54 +0,0 @@
|
||||
"""
|
||||
FastAPI dependencies.
|
||||
|
||||
Provides database sessions, settings, and common dependencies.
|
||||
"""
|
||||
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from typing import Generator
|
||||
|
||||
import django
|
||||
from django.conf import settings as django_settings
|
||||
|
||||
# Initialize Django
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mpr.settings")
|
||||
django.setup()
|
||||
|
||||
from mpr.media_assets.models import MediaAsset, TranscodeJob, TranscodePreset
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings():
|
||||
"""Get Django settings."""
|
||||
return django_settings
|
||||
|
||||
|
||||
def get_asset(asset_id: str) -> MediaAsset:
|
||||
"""Get asset by ID or raise 404."""
|
||||
from fastapi import HTTPException
|
||||
|
||||
try:
|
||||
return MediaAsset.objects.get(id=asset_id)
|
||||
except MediaAsset.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Asset not found")
|
||||
|
||||
|
||||
def get_preset(preset_id: str) -> TranscodePreset:
|
||||
"""Get preset by ID or raise 404."""
|
||||
from fastapi import HTTPException
|
||||
|
||||
try:
|
||||
return TranscodePreset.objects.get(id=preset_id)
|
||||
except TranscodePreset.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Preset not found")
|
||||
|
||||
|
||||
def get_job(job_id: str) -> TranscodeJob:
|
||||
"""Get job by ID or raise 404."""
|
||||
from fastapi import HTTPException
|
||||
|
||||
try:
|
||||
return TranscodeJob.objects.get(id=job_id)
|
||||
except TranscodeJob.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
GraphQL API using graphene, mounted on FastAPI/Starlette.
|
||||
|
||||
Provides the same data as the REST API but via GraphQL queries and mutations.
|
||||
Primary API for MPR — all client interactions go through GraphQL.
|
||||
Uses Django ORM directly for data access.
|
||||
Types are generated from schema/ via modelgen — see api/schema/graphql.py.
|
||||
"""
|
||||
@@ -12,11 +12,13 @@ import graphene
|
||||
|
||||
from api.schema.graphql import (
|
||||
CreateJobInput,
|
||||
DeleteResultType,
|
||||
MediaAssetType,
|
||||
ScanResultType,
|
||||
SystemStatusType,
|
||||
TranscodeJobType,
|
||||
TranscodePresetType,
|
||||
UpdateAssetInput,
|
||||
)
|
||||
from core.storage import BUCKET_IN, list_objects
|
||||
|
||||
@@ -238,10 +240,83 @@ class CancelJob(graphene.Mutation):
|
||||
return job
|
||||
|
||||
|
||||
class RetryJob(graphene.Mutation):
|
||||
class Arguments:
|
||||
id = graphene.UUID(required=True)
|
||||
|
||||
Output = TranscodeJobType
|
||||
|
||||
def mutate(self, info, id):
|
||||
from mpr.media_assets.models import TranscodeJob
|
||||
|
||||
try:
|
||||
job = TranscodeJob.objects.get(id=id)
|
||||
except TranscodeJob.DoesNotExist:
|
||||
raise Exception("Job not found")
|
||||
|
||||
if job.status != "failed":
|
||||
raise Exception("Only failed jobs can be retried")
|
||||
|
||||
job.status = "pending"
|
||||
job.progress = 0
|
||||
job.error_message = None
|
||||
job.save(update_fields=["status", "progress", "error_message"])
|
||||
return job
|
||||
|
||||
|
||||
class UpdateAsset(graphene.Mutation):
|
||||
class Arguments:
|
||||
id = graphene.UUID(required=True)
|
||||
input = UpdateAssetInput(required=True)
|
||||
|
||||
Output = MediaAssetType
|
||||
|
||||
def mutate(self, info, id, input):
|
||||
from mpr.media_assets.models import MediaAsset
|
||||
|
||||
try:
|
||||
asset = MediaAsset.objects.get(id=id)
|
||||
except MediaAsset.DoesNotExist:
|
||||
raise Exception("Asset not found")
|
||||
|
||||
update_fields = []
|
||||
if input.comments is not None:
|
||||
asset.comments = input.comments
|
||||
update_fields.append("comments")
|
||||
if input.tags is not None:
|
||||
asset.tags = input.tags
|
||||
update_fields.append("tags")
|
||||
|
||||
if update_fields:
|
||||
asset.save(update_fields=update_fields)
|
||||
|
||||
return asset
|
||||
|
||||
|
||||
class DeleteAsset(graphene.Mutation):
|
||||
class Arguments:
|
||||
id = graphene.UUID(required=True)
|
||||
|
||||
Output = DeleteResultType
|
||||
|
||||
def mutate(self, info, id):
|
||||
from mpr.media_assets.models import MediaAsset
|
||||
|
||||
try:
|
||||
asset = MediaAsset.objects.get(id=id)
|
||||
asset.delete()
|
||||
return DeleteResultType(ok=True)
|
||||
except MediaAsset.DoesNotExist:
|
||||
raise Exception("Asset not found")
|
||||
|
||||
|
||||
class Mutation(graphene.ObjectType):
|
||||
scan_media_folder = ScanMediaFolder.Field()
|
||||
create_job = CreateJob.Field()
|
||||
cancel_job = CancelJob.Field()
|
||||
retry_job = RetryJob.Field()
|
||||
update_asset = UpdateAsset.Field()
|
||||
delete_asset = DeleteAsset.Field()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
61
api/main.py
61
api/main.py
@@ -1,11 +1,13 @@
|
||||
"""
|
||||
MPR FastAPI Application
|
||||
|
||||
Main entry point for the REST API.
|
||||
Serves GraphQL API and Lambda callback endpoint.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
@@ -17,16 +19,17 @@ import django
|
||||
|
||||
django.setup()
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Header, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette_graphene3 import GraphQLApp, make_graphiql_handler
|
||||
|
||||
from api.graphql import schema as graphql_schema
|
||||
from api.routes import assets_router, jobs_router, presets_router, system_router
|
||||
from starlette_graphene3 import GraphQLApp, make_graphiql_handler
|
||||
|
||||
CALLBACK_API_KEY = os.environ.get("CALLBACK_API_KEY", "")
|
||||
|
||||
app = FastAPI(
|
||||
title="MPR API",
|
||||
description="Media Processor REST API",
|
||||
description="Media Processor — GraphQL API",
|
||||
version="0.1.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
@@ -41,12 +44,6 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Routes - all under /api prefix
|
||||
app.include_router(system_router, prefix="/api")
|
||||
app.include_router(assets_router, prefix="/api")
|
||||
app.include_router(presets_router, prefix="/api")
|
||||
app.include_router(jobs_router, prefix="/api")
|
||||
|
||||
# GraphQL
|
||||
app.mount("/graphql", GraphQLApp(schema=graphql_schema, on_get=make_graphiql_handler()))
|
||||
|
||||
@@ -57,5 +54,45 @@ def root():
|
||||
return {
|
||||
"name": "MPR API",
|
||||
"version": "0.1.0",
|
||||
"docs": "/docs",
|
||||
"graphql": "/graphql",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/jobs/{job_id}/callback")
|
||||
def job_callback(
|
||||
job_id: UUID,
|
||||
payload: dict,
|
||||
x_api_key: Optional[str] = Header(None),
|
||||
):
|
||||
"""
|
||||
Callback endpoint for Lambda to report job completion.
|
||||
Protected by API key.
|
||||
"""
|
||||
if CALLBACK_API_KEY and x_api_key != CALLBACK_API_KEY:
|
||||
raise HTTPException(status_code=403, detail="Invalid API key")
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from mpr.media_assets.models import TranscodeJob
|
||||
|
||||
try:
|
||||
job = TranscodeJob.objects.get(id=job_id)
|
||||
except TranscodeJob.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
|
||||
status = payload.get("status", "failed")
|
||||
job.status = status
|
||||
job.progress = 100.0 if status == "completed" else job.progress
|
||||
update_fields = ["status", "progress"]
|
||||
|
||||
if payload.get("error"):
|
||||
job.error_message = payload["error"]
|
||||
update_fields.append("error_message")
|
||||
|
||||
if status in ("completed", "failed"):
|
||||
job.completed_at = timezone.now()
|
||||
update_fields.append("completed_at")
|
||||
|
||||
job.save(update_fields=update_fields)
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
"""API Routes."""
|
||||
|
||||
from .assets import router as assets_router
|
||||
from .jobs import router as jobs_router
|
||||
from .presets import router as presets_router
|
||||
from .system import router as system_router
|
||||
|
||||
__all__ = ["assets_router", "jobs_router", "presets_router", "system_router"]
|
||||
@@ -1,117 +0,0 @@
|
||||
"""
|
||||
Asset endpoints - media file registration and metadata.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
||||
from api.deps import get_asset
|
||||
from api.schema import AssetCreate, AssetResponse, AssetUpdate
|
||||
from core.storage import BUCKET_IN, list_objects
|
||||
|
||||
router = APIRouter(prefix="/assets", tags=["assets"])
|
||||
|
||||
# Supported media extensions
|
||||
VIDEO_EXTS = {".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".m4v"}
|
||||
AUDIO_EXTS = {".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a"}
|
||||
MEDIA_EXTS = VIDEO_EXTS | AUDIO_EXTS
|
||||
|
||||
|
||||
@router.post("/", response_model=AssetResponse, status_code=201)
|
||||
def create_asset(data: AssetCreate):
|
||||
"""Register a media file as an asset."""
|
||||
from mpr.media_assets.models import MediaAsset
|
||||
|
||||
asset = MediaAsset.objects.create(
|
||||
filename=data.filename or data.file_path.split("/")[-1],
|
||||
file_path=data.file_path,
|
||||
file_size=data.file_size,
|
||||
)
|
||||
return asset
|
||||
|
||||
|
||||
@router.get("/", response_model=list[AssetResponse])
|
||||
def list_assets(
|
||||
status: Optional[str] = Query(None, description="Filter by status"),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
offset: int = Query(0, ge=0),
|
||||
):
|
||||
"""List assets with optional filtering."""
|
||||
from mpr.media_assets.models import MediaAsset
|
||||
|
||||
qs = MediaAsset.objects.all()
|
||||
if status:
|
||||
qs = qs.filter(status=status)
|
||||
return list(qs[offset : offset + limit])
|
||||
|
||||
|
||||
@router.get("/{asset_id}", response_model=AssetResponse)
|
||||
def get_asset_detail(asset_id: UUID, asset=Depends(get_asset)):
|
||||
"""Get asset details."""
|
||||
return asset
|
||||
|
||||
|
||||
@router.patch("/{asset_id}", response_model=AssetResponse)
|
||||
def update_asset(asset_id: UUID, data: AssetUpdate, asset=Depends(get_asset)):
|
||||
"""Update asset metadata (comments, tags)."""
|
||||
update_fields = []
|
||||
|
||||
if data.comments is not None:
|
||||
asset.comments = data.comments
|
||||
update_fields.append("comments")
|
||||
|
||||
if data.tags is not None:
|
||||
asset.tags = data.tags
|
||||
update_fields.append("tags")
|
||||
|
||||
if update_fields:
|
||||
asset.save(update_fields=update_fields)
|
||||
|
||||
return asset
|
||||
|
||||
|
||||
@router.delete("/{asset_id}", status_code=204)
|
||||
def delete_asset(asset_id: UUID, asset=Depends(get_asset)):
|
||||
"""Delete an asset."""
|
||||
asset.delete()
|
||||
|
||||
|
||||
@router.post("/scan", response_model=dict)
|
||||
def scan_media_folder():
|
||||
"""
|
||||
Scan the S3 media-in bucket for new video/audio files and register them as assets.
|
||||
"""
|
||||
from mpr.media_assets.models import MediaAsset
|
||||
|
||||
# List objects from S3 bucket
|
||||
objects = list_objects(BUCKET_IN, extensions=MEDIA_EXTS)
|
||||
|
||||
# Get existing filenames to avoid duplicates
|
||||
existing_filenames = set(MediaAsset.objects.values_list("filename", flat=True))
|
||||
|
||||
registered_files = []
|
||||
skipped_files = []
|
||||
|
||||
for obj in objects:
|
||||
if obj["filename"] in existing_filenames:
|
||||
skipped_files.append(obj["filename"])
|
||||
continue
|
||||
|
||||
try:
|
||||
MediaAsset.objects.create(
|
||||
filename=obj["filename"],
|
||||
file_path=obj["key"],
|
||||
file_size=obj["size"],
|
||||
)
|
||||
registered_files.append(obj["filename"])
|
||||
except Exception as e:
|
||||
print(f"Error registering {obj['filename']}: {e}")
|
||||
|
||||
return {
|
||||
"found": len(objects),
|
||||
"registered": len(registered_files),
|
||||
"skipped": len(skipped_files),
|
||||
"files": registered_files,
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
"""
|
||||
Job endpoints - transcode/trim job management.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Query
|
||||
|
||||
from api.deps import get_asset, get_job, get_preset
|
||||
from api.schema import JobCreate, JobResponse
|
||||
|
||||
router = APIRouter(prefix="/jobs", tags=["jobs"])
|
||||
|
||||
CALLBACK_API_KEY = os.environ.get("CALLBACK_API_KEY", "")
|
||||
|
||||
|
||||
@router.post("/", response_model=JobResponse, status_code=201)
|
||||
def create_job(data: JobCreate):
|
||||
"""
|
||||
Create a transcode or trim job.
|
||||
|
||||
- With preset_id: Full transcode using preset settings
|
||||
- Without preset_id but with trim_start/end: Trim only (stream copy)
|
||||
"""
|
||||
from mpr.media_assets.models import MediaAsset, TranscodeJob, TranscodePreset
|
||||
|
||||
# Get source asset
|
||||
try:
|
||||
source = MediaAsset.objects.get(id=data.source_asset_id)
|
||||
except MediaAsset.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Source asset not found")
|
||||
|
||||
# Get preset if specified
|
||||
preset = None
|
||||
preset_snapshot = {}
|
||||
if data.preset_id:
|
||||
try:
|
||||
preset = TranscodePreset.objects.get(id=data.preset_id)
|
||||
preset_snapshot = {
|
||||
"name": preset.name,
|
||||
"container": preset.container,
|
||||
"video_codec": preset.video_codec,
|
||||
"video_bitrate": preset.video_bitrate,
|
||||
"video_crf": preset.video_crf,
|
||||
"video_preset": preset.video_preset,
|
||||
"resolution": preset.resolution,
|
||||
"framerate": preset.framerate,
|
||||
"audio_codec": preset.audio_codec,
|
||||
"audio_bitrate": preset.audio_bitrate,
|
||||
"audio_channels": preset.audio_channels,
|
||||
"audio_samplerate": preset.audio_samplerate,
|
||||
"extra_args": preset.extra_args,
|
||||
}
|
||||
except TranscodePreset.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Preset not found")
|
||||
|
||||
# Validate trim-only job
|
||||
if not preset and not data.trim_start and not data.trim_end:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="Must specify preset_id or trim_start/trim_end"
|
||||
)
|
||||
|
||||
# Generate output filename - stored as S3 key in output bucket
|
||||
output_filename = data.output_filename
|
||||
if not output_filename:
|
||||
stem = Path(source.filename).stem
|
||||
ext = preset_snapshot.get("container", "mp4") if preset else "mp4"
|
||||
output_filename = f"{stem}_output.{ext}"
|
||||
|
||||
# Create job
|
||||
job = TranscodeJob.objects.create(
|
||||
source_asset_id=source.id,
|
||||
preset_id=preset.id if preset else None,
|
||||
preset_snapshot=preset_snapshot,
|
||||
trim_start=data.trim_start,
|
||||
trim_end=data.trim_end,
|
||||
output_filename=output_filename,
|
||||
output_path=output_filename, # S3 key in output bucket
|
||||
priority=data.priority or 0,
|
||||
)
|
||||
|
||||
# Dispatch based on executor mode
|
||||
executor_mode = os.environ.get("MPR_EXECUTOR", "local")
|
||||
|
||||
if executor_mode == "lambda":
|
||||
_dispatch_lambda(job, source, preset_snapshot)
|
||||
else:
|
||||
_dispatch_celery(job, source, preset_snapshot)
|
||||
|
||||
return job
|
||||
|
||||
|
||||
def _dispatch_celery(job, source, preset_snapshot):
|
||||
"""Dispatch job to Celery worker."""
|
||||
from task.tasks import run_transcode_job
|
||||
|
||||
result = run_transcode_job.delay(
|
||||
job_id=str(job.id),
|
||||
source_key=source.file_path,
|
||||
output_key=job.output_filename,
|
||||
preset=preset_snapshot or None,
|
||||
trim_start=job.trim_start,
|
||||
trim_end=job.trim_end,
|
||||
duration=source.duration,
|
||||
)
|
||||
job.celery_task_id = result.id
|
||||
job.save(update_fields=["celery_task_id"])
|
||||
|
||||
|
||||
def _dispatch_lambda(job, source, preset_snapshot):
|
||||
"""Dispatch job to AWS Step Functions."""
|
||||
from task.executor import get_executor
|
||||
|
||||
executor = get_executor()
|
||||
executor.run(
|
||||
job_id=str(job.id),
|
||||
source_path=source.file_path,
|
||||
output_path=job.output_filename,
|
||||
preset=preset_snapshot or None,
|
||||
trim_start=job.trim_start,
|
||||
trim_end=job.trim_end,
|
||||
duration=source.duration,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{job_id}/callback")
|
||||
def job_callback(
|
||||
job_id: UUID,
|
||||
payload: dict,
|
||||
x_api_key: Optional[str] = Header(None),
|
||||
):
|
||||
"""
|
||||
Callback endpoint for Lambda to report job completion.
|
||||
Protected by API key.
|
||||
"""
|
||||
if CALLBACK_API_KEY and x_api_key != CALLBACK_API_KEY:
|
||||
raise HTTPException(status_code=403, detail="Invalid API key")
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from mpr.media_assets.models import TranscodeJob
|
||||
|
||||
try:
|
||||
job = TranscodeJob.objects.get(id=job_id)
|
||||
except TranscodeJob.DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
|
||||
status = payload.get("status", "failed")
|
||||
job.status = status
|
||||
job.progress = 100.0 if status == "completed" else job.progress
|
||||
update_fields = ["status", "progress"]
|
||||
|
||||
if payload.get("error"):
|
||||
job.error_message = payload["error"]
|
||||
update_fields.append("error_message")
|
||||
|
||||
if status == "completed":
|
||||
job.completed_at = timezone.now()
|
||||
update_fields.append("completed_at")
|
||||
elif status == "failed":
|
||||
job.completed_at = timezone.now()
|
||||
update_fields.append("completed_at")
|
||||
|
||||
job.save(update_fields=update_fields)
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/", response_model=list[JobResponse])
|
||||
def list_jobs(
|
||||
status: Optional[str] = Query(None, description="Filter by status"),
|
||||
source_asset_id: Optional[UUID] = Query(None),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
offset: int = Query(0, ge=0),
|
||||
):
|
||||
"""List jobs with optional filtering."""
|
||||
from mpr.media_assets.models import TranscodeJob
|
||||
|
||||
qs = TranscodeJob.objects.all()
|
||||
if status:
|
||||
qs = qs.filter(status=status)
|
||||
if source_asset_id:
|
||||
qs = qs.filter(source_asset_id=source_asset_id)
|
||||
return list(qs[offset : offset + limit])
|
||||
|
||||
|
||||
@router.get("/{job_id}", response_model=JobResponse)
|
||||
def get_job_detail(job_id: UUID, job=Depends(get_job)):
|
||||
"""Get job details including progress."""
|
||||
return job
|
||||
|
||||
|
||||
@router.get("/{job_id}/progress")
|
||||
def get_job_progress(job_id: UUID, job=Depends(get_job)):
|
||||
"""Get real-time job progress."""
|
||||
return {
|
||||
"job_id": str(job.id),
|
||||
"status": job.status,
|
||||
"progress": job.progress,
|
||||
"current_frame": job.current_frame,
|
||||
"current_time": job.current_time,
|
||||
"speed": job.speed,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{job_id}/cancel", response_model=JobResponse)
|
||||
def cancel_job(job_id: UUID, job=Depends(get_job)):
|
||||
"""Cancel a pending or processing job."""
|
||||
if job.status not in ("pending", "processing"):
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Cannot cancel job with status: {job.status}"
|
||||
)
|
||||
|
||||
job.status = "cancelled"
|
||||
job.save(update_fields=["status"])
|
||||
return job
|
||||
|
||||
|
||||
@router.post("/{job_id}/retry", response_model=JobResponse)
|
||||
def retry_job(job_id: UUID, job=Depends(get_job)):
|
||||
"""Retry a failed job."""
|
||||
if job.status != "failed":
|
||||
raise HTTPException(status_code=400, detail="Only failed jobs can be retried")
|
||||
|
||||
job.status = "pending"
|
||||
job.progress = 0
|
||||
job.error_message = None
|
||||
job.save(update_fields=["status", "progress", "error_message"])
|
||||
|
||||
return job
|
||||
@@ -1,100 +0,0 @@
|
||||
"""
|
||||
Preset endpoints - transcode configuration templates.
|
||||
"""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from api.deps import get_preset
|
||||
from api.schema import PresetCreate, PresetResponse, PresetUpdate
|
||||
|
||||
router = APIRouter(prefix="/presets", tags=["presets"])
|
||||
|
||||
|
||||
@router.post("/", response_model=PresetResponse, status_code=201)
|
||||
def create_preset(data: PresetCreate):
|
||||
"""Create a custom preset."""
|
||||
from mpr.media_assets.models import TranscodePreset
|
||||
|
||||
preset = TranscodePreset.objects.create(
|
||||
name=data.name,
|
||||
description=data.description or "",
|
||||
container=data.container or "mp4",
|
||||
video_codec=data.video_codec or "libx264",
|
||||
video_bitrate=data.video_bitrate,
|
||||
video_crf=data.video_crf,
|
||||
video_preset=data.video_preset,
|
||||
resolution=data.resolution,
|
||||
framerate=data.framerate,
|
||||
audio_codec=data.audio_codec or "aac",
|
||||
audio_bitrate=data.audio_bitrate,
|
||||
audio_channels=data.audio_channels,
|
||||
audio_samplerate=data.audio_samplerate,
|
||||
extra_args=data.extra_args or [],
|
||||
is_builtin=False,
|
||||
)
|
||||
|
||||
return preset
|
||||
|
||||
|
||||
@router.get("/", response_model=list[PresetResponse])
|
||||
def list_presets(include_builtin: bool = True):
|
||||
"""List all presets."""
|
||||
from mpr.media_assets.models import TranscodePreset
|
||||
|
||||
qs = TranscodePreset.objects.all()
|
||||
|
||||
if not include_builtin:
|
||||
qs = qs.filter(is_builtin=False)
|
||||
|
||||
return list(qs)
|
||||
|
||||
|
||||
@router.get("/{preset_id}", response_model=PresetResponse)
|
||||
def get_preset_detail(preset_id: UUID, preset=Depends(get_preset)):
|
||||
"""Get preset details."""
|
||||
return preset
|
||||
|
||||
|
||||
@router.patch("/{preset_id}", response_model=PresetResponse)
|
||||
def update_preset(preset_id: UUID, data: PresetUpdate, preset=Depends(get_preset)):
|
||||
"""Update a custom preset. Builtin presets cannot be modified."""
|
||||
if preset.is_builtin:
|
||||
raise HTTPException(status_code=403, detail="Cannot modify builtin preset")
|
||||
|
||||
update_fields = []
|
||||
for field in [
|
||||
"name",
|
||||
"description",
|
||||
"container",
|
||||
"video_codec",
|
||||
"video_bitrate",
|
||||
"video_crf",
|
||||
"video_preset",
|
||||
"resolution",
|
||||
"framerate",
|
||||
"audio_codec",
|
||||
"audio_bitrate",
|
||||
"audio_channels",
|
||||
"audio_samplerate",
|
||||
"extra_args",
|
||||
]:
|
||||
value = getattr(data, field, None)
|
||||
if value is not None:
|
||||
setattr(preset, field, value)
|
||||
update_fields.append(field)
|
||||
|
||||
if update_fields:
|
||||
preset.save(update_fields=update_fields)
|
||||
|
||||
return preset
|
||||
|
||||
|
||||
@router.delete("/{preset_id}", status_code=204)
|
||||
def delete_preset(preset_id: UUID, preset=Depends(get_preset)):
|
||||
"""Delete a custom preset. Builtin presets cannot be deleted."""
|
||||
if preset.is_builtin:
|
||||
raise HTTPException(status_code=403, detail="Cannot delete builtin preset")
|
||||
|
||||
preset.delete()
|
||||
@@ -1,51 +0,0 @@
|
||||
"""
|
||||
System endpoints - health checks and FFmpeg capabilities.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from core.ffmpeg import get_decoders, get_encoders, get_formats
|
||||
|
||||
router = APIRouter(prefix="/system", tags=["system"])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def health_check():
|
||||
"""Health check endpoint."""
|
||||
return {"status": "healthy"}
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def system_status():
|
||||
"""System status for UI."""
|
||||
return {"status": "ok", "version": "0.1.0"}
|
||||
|
||||
|
||||
@router.get("/worker")
|
||||
def worker_status():
|
||||
"""Worker status from gRPC."""
|
||||
try:
|
||||
from rpc.client import get_client
|
||||
|
||||
client = get_client()
|
||||
status = client.get_worker_status()
|
||||
if status:
|
||||
return status
|
||||
return {"available": False, "error": "No response from worker"}
|
||||
except Exception as e:
|
||||
return {"available": False, "error": str(e)}
|
||||
|
||||
|
||||
@router.get("/ffmpeg/codecs")
|
||||
def ffmpeg_codecs():
|
||||
"""Get available FFmpeg encoders and decoders."""
|
||||
return {
|
||||
"encoders": get_encoders(),
|
||||
"decoders": get_decoders(),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/ffmpeg/formats")
|
||||
def ffmpeg_formats():
|
||||
"""Get available FFmpeg muxers and demuxers."""
|
||||
return get_formats()
|
||||
@@ -1,10 +0,0 @@
|
||||
"""API Schemas - GENERATED FILE"""
|
||||
|
||||
from .base import BaseSchema
|
||||
from .asset import AssetCreate, AssetUpdate, AssetResponse
|
||||
from .asset import AssetStatus
|
||||
from .preset import PresetCreate, PresetUpdate, PresetResponse
|
||||
from .job import JobCreate, JobUpdate, JobResponse
|
||||
from .job import JobStatus
|
||||
|
||||
__all__ = ["BaseSchema", "AssetCreate", "AssetUpdate", "AssetResponse", "AssetStatus", "PresetCreate", "PresetUpdate", "PresetResponse", "JobCreate", "JobUpdate", "JobResponse", "JobStatus"]
|
||||
@@ -1,70 +0,0 @@
|
||||
"""MediaAsset Schemas - GENERATED FILE"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from .base import BaseSchema
|
||||
|
||||
|
||||
class AssetStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
READY = "ready"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class AssetCreate(BaseSchema):
|
||||
"""AssetCreate schema."""
|
||||
filename: str
|
||||
file_path: str
|
||||
file_size: Optional[int] = None
|
||||
duration: Optional[float] = None
|
||||
video_codec: Optional[str] = None
|
||||
audio_codec: Optional[str] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
framerate: Optional[float] = None
|
||||
bitrate: Optional[int] = None
|
||||
properties: Dict[str, Any]
|
||||
comments: str = ""
|
||||
tags: List[str] = Field(default_factory=list)
|
||||
|
||||
class AssetUpdate(BaseSchema):
|
||||
"""AssetUpdate schema."""
|
||||
filename: Optional[str] = None
|
||||
file_path: Optional[str] = None
|
||||
status: Optional[AssetStatus] = None
|
||||
error_message: Optional[str] = None
|
||||
file_size: Optional[int] = None
|
||||
duration: Optional[float] = None
|
||||
video_codec: Optional[str] = None
|
||||
audio_codec: Optional[str] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
framerate: Optional[float] = None
|
||||
bitrate: Optional[int] = None
|
||||
properties: Optional[Dict[str, Any]] = None
|
||||
comments: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
|
||||
class AssetResponse(BaseSchema):
|
||||
"""AssetResponse schema."""
|
||||
id: UUID
|
||||
filename: str
|
||||
file_path: str
|
||||
status: AssetStatus = "AssetStatus.PENDING"
|
||||
error_message: Optional[str] = None
|
||||
file_size: Optional[int] = None
|
||||
duration: Optional[float] = None
|
||||
video_codec: Optional[str] = None
|
||||
audio_codec: Optional[str] = None
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
framerate: Optional[float] = None
|
||||
bitrate: Optional[int] = None
|
||||
properties: Dict[str, Any]
|
||||
comments: str = ""
|
||||
tags: List[str] = Field(default_factory=list)
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
@@ -1,8 +0,0 @@
|
||||
"""Pydantic Base Schema - GENERATED FILE"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class BaseSchema(BaseModel):
|
||||
"""Base schema with ORM mode."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -104,6 +104,13 @@ class CreateJobInput(graphene.InputObjectType):
|
||||
priority = graphene.Int(default_value=0)
|
||||
|
||||
|
||||
class UpdateAssetInput(graphene.InputObjectType):
|
||||
"""Request body for updating asset metadata."""
|
||||
|
||||
comments = graphene.String()
|
||||
tags = graphene.List(graphene.String)
|
||||
|
||||
|
||||
class SystemStatusType(graphene.ObjectType):
|
||||
"""System status response."""
|
||||
|
||||
@@ -120,6 +127,12 @@ class ScanResultType(graphene.ObjectType):
|
||||
files = graphene.List(graphene.String)
|
||||
|
||||
|
||||
class DeleteResultType(graphene.ObjectType):
|
||||
"""Result of a delete operation."""
|
||||
|
||||
ok = graphene.Boolean()
|
||||
|
||||
|
||||
class WorkerStatusType(graphene.ObjectType):
|
||||
"""Worker health and capabilities."""
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
"""TranscodeJob Schemas - GENERATED FILE"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from .base import BaseSchema
|
||||
|
||||
|
||||
class JobStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class JobCreate(BaseSchema):
|
||||
"""JobCreate schema."""
|
||||
source_asset_id: UUID
|
||||
preset_id: Optional[UUID] = None
|
||||
preset_snapshot: Dict[str, Any]
|
||||
trim_start: Optional[float] = None
|
||||
trim_end: Optional[float] = None
|
||||
output_filename: str = ""
|
||||
output_path: Optional[str] = None
|
||||
output_asset_id: Optional[UUID] = None
|
||||
progress: float = 0.0
|
||||
current_frame: Optional[int] = None
|
||||
current_time: Optional[float] = None
|
||||
speed: Optional[str] = None
|
||||
celery_task_id: Optional[str] = None
|
||||
execution_arn: Optional[str] = None
|
||||
priority: int = 0
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
class JobUpdate(BaseSchema):
|
||||
"""JobUpdate schema."""
|
||||
source_asset_id: Optional[UUID] = None
|
||||
preset_id: Optional[UUID] = None
|
||||
preset_snapshot: Optional[Dict[str, Any]] = None
|
||||
trim_start: Optional[float] = None
|
||||
trim_end: Optional[float] = None
|
||||
output_filename: Optional[str] = None
|
||||
output_path: Optional[str] = None
|
||||
output_asset_id: Optional[UUID] = None
|
||||
status: Optional[JobStatus] = None
|
||||
progress: Optional[float] = None
|
||||
current_frame: Optional[int] = None
|
||||
current_time: Optional[float] = None
|
||||
speed: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
celery_task_id: Optional[str] = None
|
||||
execution_arn: Optional[str] = None
|
||||
priority: Optional[int] = None
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
class JobResponse(BaseSchema):
|
||||
"""JobResponse schema."""
|
||||
id: UUID
|
||||
source_asset_id: UUID
|
||||
preset_id: Optional[UUID] = None
|
||||
preset_snapshot: Dict[str, Any]
|
||||
trim_start: Optional[float] = None
|
||||
trim_end: Optional[float] = None
|
||||
output_filename: str = ""
|
||||
output_path: Optional[str] = None
|
||||
output_asset_id: Optional[UUID] = None
|
||||
status: JobStatus = "JobStatus.PENDING"
|
||||
progress: float = 0.0
|
||||
current_frame: Optional[int] = None
|
||||
current_time: Optional[float] = None
|
||||
speed: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
celery_task_id: Optional[str] = None
|
||||
execution_arn: Optional[str] = None
|
||||
priority: int = 0
|
||||
created_at: Optional[datetime] = None
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
@@ -1,66 +0,0 @@
|
||||
"""TranscodePreset Schemas - GENERATED FILE"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from .base import BaseSchema
|
||||
|
||||
|
||||
class PresetCreate(BaseSchema):
|
||||
"""PresetCreate schema."""
|
||||
name: str
|
||||
description: str = ""
|
||||
is_builtin: bool = False
|
||||
container: str = "mp4"
|
||||
video_codec: str = "libx264"
|
||||
video_bitrate: Optional[str] = None
|
||||
video_crf: Optional[int] = None
|
||||
video_preset: Optional[str] = None
|
||||
resolution: Optional[str] = None
|
||||
framerate: Optional[float] = None
|
||||
audio_codec: str = "aac"
|
||||
audio_bitrate: Optional[str] = None
|
||||
audio_channels: Optional[int] = None
|
||||
audio_samplerate: Optional[int] = None
|
||||
extra_args: List[str] = Field(default_factory=list)
|
||||
|
||||
class PresetUpdate(BaseSchema):
|
||||
"""PresetUpdate schema."""
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
is_builtin: Optional[bool] = None
|
||||
container: Optional[str] = None
|
||||
video_codec: Optional[str] = None
|
||||
video_bitrate: Optional[str] = None
|
||||
video_crf: Optional[int] = None
|
||||
video_preset: Optional[str] = None
|
||||
resolution: Optional[str] = None
|
||||
framerate: Optional[float] = None
|
||||
audio_codec: Optional[str] = None
|
||||
audio_bitrate: Optional[str] = None
|
||||
audio_channels: Optional[int] = None
|
||||
audio_samplerate: Optional[int] = None
|
||||
extra_args: Optional[List[str]] = None
|
||||
|
||||
class PresetResponse(BaseSchema):
|
||||
"""PresetResponse schema."""
|
||||
id: UUID
|
||||
name: str
|
||||
description: str = ""
|
||||
is_builtin: bool = False
|
||||
container: str = "mp4"
|
||||
video_codec: str = "libx264"
|
||||
video_bitrate: Optional[str] = None
|
||||
video_crf: Optional[int] = None
|
||||
video_preset: Optional[str] = None
|
||||
resolution: Optional[str] = None
|
||||
framerate: Optional[float] = None
|
||||
audio_codec: str = "aac"
|
||||
audio_bitrate: Optional[str] = None
|
||||
audio_channels: Optional[int] = None
|
||||
audio_samplerate: Optional[int] = None
|
||||
extra_args: List[str] = Field(default_factory=list)
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
@@ -35,7 +35,7 @@ digraph local_architecture {
|
||||
fillcolor="#f0f8e8"
|
||||
|
||||
django [label="Django Admin\n/admin\nport 8701"]
|
||||
fastapi [label="FastAPI + GraphQL\n/api + /graphql\nport 8702"]
|
||||
fastapi [label="GraphQL API\n/graphql\nport 8702"]
|
||||
timeline [label="Timeline UI\n/\nport 5173"]
|
||||
}
|
||||
|
||||
@@ -74,11 +74,11 @@ digraph local_architecture {
|
||||
browser -> nginx [label="HTTP"]
|
||||
|
||||
nginx -> django [xlabel="/admin"]
|
||||
nginx -> fastapi [xlabel="/api, /graphql"]
|
||||
nginx -> fastapi [xlabel="/graphql"]
|
||||
nginx -> timeline [xlabel="/"]
|
||||
nginx -> minio [xlabel="/media/*"]
|
||||
|
||||
timeline -> fastapi [label="REST API\nGraphQL"]
|
||||
timeline -> fastapi [label="GraphQL"]
|
||||
django -> postgres
|
||||
|
||||
fastapi -> postgres [label="read/write jobs"]
|
||||
|
||||
@@ -4,31 +4,31 @@
|
||||
<!-- Generated by graphviz version 14.1.2 (0)
|
||||
-->
|
||||
<!-- Title: local_architecture Pages: 1 -->
|
||||
<svg width="667pt" height="1108pt"
|
||||
viewBox="0.00 0.00 667.00 1108.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1103.51)">
|
||||
<svg width="667pt" height="1095pt"
|
||||
viewBox="0.00 0.00 667.00 1095.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1090.76)">
|
||||
<title>local_architecture</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-1103.51 663,-1103.51 663,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="329.5" y="-1080.31" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">MPR - Local Architecture (Celery + MinIO)</text>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-1090.76 663,-1090.76 663,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="329.5" y="-1067.56" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">MPR - Local Architecture (Celery + MinIO)</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_external</title>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="270,-960.41 270,-1064.01 424,-1064.01 424,-960.41 270,-960.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-1044.81" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">External</text>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="270,-947.66 270,-1051.26 424,-1051.26 424,-947.66 270,-947.66"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-1032.06" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">External</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_proxy</title>
|
||||
<polygon fill="#e8f4f8" stroke="black" points="274,-832.66 274,-918.66 420,-918.66 420,-832.66 274,-832.66"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-899.46" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Reverse Proxy</text>
|
||||
<polygon fill="#e8f4f8" stroke="black" points="274,-819.91 274,-905.91 420,-905.91 420,-819.91 274,-819.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-886.71" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Reverse Proxy</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_apps</title>
|
||||
<polygon fill="#f0f8e8" stroke="black" points="19,-556.16 19,-802.66 301,-802.66 301,-556.16 19,-556.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="160" y="-783.46" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Application Layer</text>
|
||||
<polygon fill="#f0f8e8" stroke="black" points="19,-556.16 19,-789.91 301,-789.91 301,-556.16 19,-556.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="160" y="-770.71" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Application Layer</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_workers</title>
|
||||
<polygon fill="#fff8e8" stroke="black" points="188,-302.41 188,-501.66 364,-501.66 364,-302.41 188,-302.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276" y="-482.46" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Worker Layer</text>
|
||||
<polygon fill="#fff8e8" stroke="black" points="193,-302.41 193,-501.66 369,-501.66 369,-302.41 193,-302.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="281" y="-482.46" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Worker Layer</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_data</title>
|
||||
@@ -43,68 +43,68 @@
|
||||
<!-- browser -->
|
||||
<g id="node1" class="node">
|
||||
<title>browser</title>
|
||||
<ellipse fill="none" stroke="black" cx="347" cy="-998.46" rx="69.12" ry="30.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-1002.41" font-family="Helvetica,sans-Serif" font-size="14.00">Browser</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-985.16" font-family="Helvetica,sans-Serif" font-size="14.00">mpr.local.ar</text>
|
||||
<ellipse fill="none" stroke="black" cx="347" cy="-985.71" rx="69.12" ry="30.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-989.66" font-family="Helvetica,sans-Serif" font-size="14.00">Browser</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-972.41" font-family="Helvetica,sans-Serif" font-size="14.00">mpr.local.ar</text>
|
||||
</g>
|
||||
<!-- nginx -->
|
||||
<g id="node2" class="node">
|
||||
<title>nginx</title>
|
||||
<path fill="none" stroke="black" d="M368.5,-883.16C368.5,-883.16 325.5,-883.16 325.5,-883.16 319.5,-883.16 313.5,-877.16 313.5,-871.16 313.5,-871.16 313.5,-852.66 313.5,-852.66 313.5,-846.66 319.5,-840.66 325.5,-840.66 325.5,-840.66 368.5,-840.66 368.5,-840.66 374.5,-840.66 380.5,-846.66 380.5,-852.66 380.5,-852.66 380.5,-871.16 380.5,-871.16 380.5,-877.16 374.5,-883.16 368.5,-883.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-865.86" font-family="Helvetica,sans-Serif" font-size="14.00">nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-848.61" font-family="Helvetica,sans-Serif" font-size="14.00">port 80</text>
|
||||
<path fill="none" stroke="black" d="M368.5,-870.41C368.5,-870.41 325.5,-870.41 325.5,-870.41 319.5,-870.41 313.5,-864.41 313.5,-858.41 313.5,-858.41 313.5,-839.91 313.5,-839.91 313.5,-833.91 319.5,-827.91 325.5,-827.91 325.5,-827.91 368.5,-827.91 368.5,-827.91 374.5,-827.91 380.5,-833.91 380.5,-839.91 380.5,-839.91 380.5,-858.41 380.5,-858.41 380.5,-864.41 374.5,-870.41 368.5,-870.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-853.11" font-family="Helvetica,sans-Serif" font-size="14.00">nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347" y="-835.86" font-family="Helvetica,sans-Serif" font-size="14.00">port 80</text>
|
||||
</g>
|
||||
<!-- browser->nginx -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>browser->nginx</title>
|
||||
<path fill="none" stroke="black" d="M347,-968.15C347,-968.15 347,-895.16 347,-895.16"/>
|
||||
<polygon fill="black" stroke="black" points="350.5,-895.16 347,-885.16 343.5,-895.16 350.5,-895.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="359.75" y="-929.91" font-family="Helvetica,sans-Serif" font-size="10.00">HTTP</text>
|
||||
<path fill="none" stroke="black" d="M347,-955.4C347,-955.4 347,-882.41 347,-882.41"/>
|
||||
<polygon fill="black" stroke="black" points="350.5,-882.41 347,-872.41 343.5,-882.41 350.5,-882.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="359.75" y="-917.16" font-family="Helvetica,sans-Serif" font-size="10.00">HTTP</text>
|
||||
</g>
|
||||
<!-- django -->
|
||||
<g id="node3" class="node">
|
||||
<title>django</title>
|
||||
<path fill="none" stroke="black" d="M128.75,-767.16C128.75,-767.16 39.25,-767.16 39.25,-767.16 33.25,-767.16 27.25,-761.16 27.25,-755.16 27.25,-755.16 27.25,-719.41 27.25,-719.41 27.25,-713.41 33.25,-707.41 39.25,-707.41 39.25,-707.41 128.75,-707.41 128.75,-707.41 134.75,-707.41 140.75,-713.41 140.75,-719.41 140.75,-719.41 140.75,-755.16 140.75,-755.16 140.75,-761.16 134.75,-767.16 128.75,-767.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-749.86" font-family="Helvetica,sans-Serif" font-size="14.00">Django Admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-732.61" font-family="Helvetica,sans-Serif" font-size="14.00">/admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-715.36" font-family="Helvetica,sans-Serif" font-size="14.00">port 8701</text>
|
||||
<path fill="none" stroke="black" d="M128.75,-754.41C128.75,-754.41 39.25,-754.41 39.25,-754.41 33.25,-754.41 27.25,-748.41 27.25,-742.41 27.25,-742.41 27.25,-706.66 27.25,-706.66 27.25,-700.66 33.25,-694.66 39.25,-694.66 39.25,-694.66 128.75,-694.66 128.75,-694.66 134.75,-694.66 140.75,-700.66 140.75,-706.66 140.75,-706.66 140.75,-742.41 140.75,-742.41 140.75,-748.41 134.75,-754.41 128.75,-754.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-737.11" font-family="Helvetica,sans-Serif" font-size="14.00">Django Admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-719.86" font-family="Helvetica,sans-Serif" font-size="14.00">/admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-702.61" font-family="Helvetica,sans-Serif" font-size="14.00">port 8701</text>
|
||||
</g>
|
||||
<!-- nginx->django -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>nginx->django</title>
|
||||
<path fill="none" stroke="black" d="M313.16,-869C242.12,-869 84,-869 84,-869 84,-869 84,-779.01 84,-779.01"/>
|
||||
<polygon fill="black" stroke="black" points="87.5,-779.01 84,-769.01 80.5,-779.01 87.5,-779.01"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="136.71" y="-872.25" font-family="Helvetica,sans-Serif" font-size="10.00">/admin</text>
|
||||
<path fill="none" stroke="black" d="M313.16,-856C242.12,-856 84,-856 84,-856 84,-856 84,-766.21 84,-766.21"/>
|
||||
<polygon fill="black" stroke="black" points="87.5,-766.21 84,-756.21 80.5,-766.21 87.5,-766.21"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="136.81" y="-859.25" font-family="Helvetica,sans-Serif" font-size="10.00">/admin</text>
|
||||
</g>
|
||||
<!-- fastapi -->
|
||||
<g id="node4" class="node">
|
||||
<title>fastapi</title>
|
||||
<path fill="none" stroke="black" d="M281.38,-623.91C281.38,-623.91 156.62,-623.91 156.62,-623.91 150.62,-623.91 144.62,-617.91 144.62,-611.91 144.62,-611.91 144.62,-576.16 144.62,-576.16 144.62,-570.16 150.62,-564.16 156.62,-564.16 156.62,-564.16 281.38,-564.16 281.38,-564.16 287.38,-564.16 293.38,-570.16 293.38,-576.16 293.38,-576.16 293.38,-611.91 293.38,-611.91 293.38,-617.91 287.38,-623.91 281.38,-623.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="219" y="-606.61" font-family="Helvetica,sans-Serif" font-size="14.00">FastAPI + GraphQL</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="219" y="-589.36" font-family="Helvetica,sans-Serif" font-size="14.00">/api + /graphql</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="219" y="-572.11" font-family="Helvetica,sans-Serif" font-size="14.00">port 8702</text>
|
||||
<path fill="none" stroke="black" d="M281.25,-623.91C281.25,-623.91 200.75,-623.91 200.75,-623.91 194.75,-623.91 188.75,-617.91 188.75,-611.91 188.75,-611.91 188.75,-576.16 188.75,-576.16 188.75,-570.16 194.75,-564.16 200.75,-564.16 200.75,-564.16 281.25,-564.16 281.25,-564.16 287.25,-564.16 293.25,-570.16 293.25,-576.16 293.25,-576.16 293.25,-611.91 293.25,-611.91 293.25,-617.91 287.25,-623.91 281.25,-623.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="241" y="-606.61" font-family="Helvetica,sans-Serif" font-size="14.00">GraphQL API</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="241" y="-589.36" font-family="Helvetica,sans-Serif" font-size="14.00">/graphql</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="241" y="-572.11" font-family="Helvetica,sans-Serif" font-size="14.00">port 8702</text>
|
||||
</g>
|
||||
<!-- nginx->fastapi -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>nginx->fastapi</title>
|
||||
<path fill="none" stroke="black" d="M334.56,-840.44C334.56,-776.86 334.56,-594 334.56,-594 334.56,-594 305.33,-594 305.33,-594"/>
|
||||
<polygon fill="black" stroke="black" points="305.33,-590.5 295.33,-594 305.33,-597.5 305.33,-590.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="302.69" y="-693.1" font-family="Helvetica,sans-Serif" font-size="10.00">/api, /graphql</text>
|
||||
<path fill="none" stroke="black" d="M337.06,-827.84C337.06,-766.52 337.06,-594 337.06,-594 337.06,-594 305.04,-594 305.04,-594"/>
|
||||
<polygon fill="black" stroke="black" points="305.04,-590.5 295.04,-594 305.04,-597.5 305.04,-590.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="317.19" y="-698.16" font-family="Helvetica,sans-Serif" font-size="10.00">/graphql</text>
|
||||
</g>
|
||||
<!-- timeline -->
|
||||
<g id="node5" class="node">
|
||||
<title>timeline</title>
|
||||
<path fill="none" stroke="black" d="M281,-767.16C281,-767.16 211,-767.16 211,-767.16 205,-767.16 199,-761.16 199,-755.16 199,-755.16 199,-719.41 199,-719.41 199,-713.41 205,-707.41 211,-707.41 211,-707.41 281,-707.41 281,-707.41 287,-707.41 293,-713.41 293,-719.41 293,-719.41 293,-755.16 293,-755.16 293,-761.16 287,-767.16 281,-767.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246" y="-749.86" font-family="Helvetica,sans-Serif" font-size="14.00">Timeline UI</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246" y="-732.61" font-family="Helvetica,sans-Serif" font-size="14.00">/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246" y="-715.36" font-family="Helvetica,sans-Serif" font-size="14.00">port 5173</text>
|
||||
<path fill="none" stroke="black" d="M281,-754.41C281,-754.41 211,-754.41 211,-754.41 205,-754.41 199,-748.41 199,-742.41 199,-742.41 199,-706.66 199,-706.66 199,-700.66 205,-694.66 211,-694.66 211,-694.66 281,-694.66 281,-694.66 287,-694.66 293,-700.66 293,-706.66 293,-706.66 293,-742.41 293,-742.41 293,-748.41 287,-754.41 281,-754.41"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246" y="-737.11" font-family="Helvetica,sans-Serif" font-size="14.00">Timeline UI</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246" y="-719.86" font-family="Helvetica,sans-Serif" font-size="14.00">/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246" y="-702.61" font-family="Helvetica,sans-Serif" font-size="14.00">port 5173</text>
|
||||
</g>
|
||||
<!-- nginx->timeline -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>nginx->timeline</title>
|
||||
<path fill="none" stroke="black" d="M313.34,-855C298.97,-855 285.44,-855 285.44,-855 285.44,-855 285.44,-779.11 285.44,-779.11"/>
|
||||
<polygon fill="black" stroke="black" points="288.94,-779.11 285.44,-769.11 281.94,-779.11 288.94,-779.11"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="283.94" y="-834.25" font-family="Helvetica,sans-Serif" font-size="10.00">/</text>
|
||||
<path fill="none" stroke="black" d="M313.34,-842C298.97,-842 285.44,-842 285.44,-842 285.44,-842 285.44,-766.3 285.44,-766.3"/>
|
||||
<polygon fill="black" stroke="black" points="288.94,-766.3 285.44,-756.3 281.94,-766.3 288.94,-766.3"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="283.94" y="-821.35" font-family="Helvetica,sans-Serif" font-size="10.00">/</text>
|
||||
</g>
|
||||
<!-- minio -->
|
||||
<g id="node10" class="node">
|
||||
@@ -117,9 +117,9 @@
|
||||
<!-- nginx->minio -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>nginx->minio</title>
|
||||
<path fill="none" stroke="black" d="M368.06,-840.47C368.06,-840.47 368.06,-200.34 368.06,-200.34"/>
|
||||
<polygon fill="black" stroke="black" points="371.56,-200.34 368.06,-190.34 364.56,-200.34 371.56,-200.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="347.06" y="-523.66" font-family="Helvetica,sans-Serif" font-size="10.00">/media/*</text>
|
||||
<path fill="none" stroke="black" d="M370.56,-827.73C370.56,-827.73 370.56,-200.13 370.56,-200.13"/>
|
||||
<polygon fill="black" stroke="black" points="374.06,-200.13 370.56,-190.13 367.06,-200.13 374.06,-200.13"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="391.56" y="-517.18" font-family="Helvetica,sans-Serif" font-size="10.00">/media/*</text>
|
||||
</g>
|
||||
<!-- postgres -->
|
||||
<g id="node8" class="node">
|
||||
@@ -132,58 +132,57 @@
|
||||
<!-- django->postgres -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>django->postgres</title>
|
||||
<path fill="none" stroke="black" d="M55.42,-706.98C55.42,-706.98 55.42,-199.74 55.42,-199.74"/>
|
||||
<polygon fill="black" stroke="black" points="58.92,-199.74 55.42,-189.74 51.92,-199.74 58.92,-199.74"/>
|
||||
<path fill="none" stroke="black" d="M48.38,-694.5C48.38,-694.5 48.38,-199.71 48.38,-199.71"/>
|
||||
<polygon fill="black" stroke="black" points="51.88,-199.71 48.38,-189.71 44.88,-199.71 51.88,-199.71"/>
|
||||
</g>
|
||||
<!-- grpc_server -->
|
||||
<g id="node6" class="node">
|
||||
<title>grpc_server</title>
|
||||
<path fill="none" stroke="black" d="M296.5,-466.16C296.5,-466.16 217.5,-466.16 217.5,-466.16 211.5,-466.16 205.5,-460.16 205.5,-454.16 205.5,-454.16 205.5,-435.66 205.5,-435.66 205.5,-429.66 211.5,-423.66 217.5,-423.66 217.5,-423.66 296.5,-423.66 296.5,-423.66 302.5,-423.66 308.5,-429.66 308.5,-435.66 308.5,-435.66 308.5,-454.16 308.5,-454.16 308.5,-460.16 302.5,-466.16 296.5,-466.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257" y="-448.86" font-family="Helvetica,sans-Serif" font-size="14.00">gRPC Server</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257" y="-431.61" font-family="Helvetica,sans-Serif" font-size="14.00">port 50051</text>
|
||||
<path fill="none" stroke="black" d="M301.5,-466.16C301.5,-466.16 222.5,-466.16 222.5,-466.16 216.5,-466.16 210.5,-460.16 210.5,-454.16 210.5,-454.16 210.5,-435.66 210.5,-435.66 210.5,-429.66 216.5,-423.66 222.5,-423.66 222.5,-423.66 301.5,-423.66 301.5,-423.66 307.5,-423.66 313.5,-429.66 313.5,-435.66 313.5,-435.66 313.5,-454.16 313.5,-454.16 313.5,-460.16 307.5,-466.16 301.5,-466.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="262" y="-448.86" font-family="Helvetica,sans-Serif" font-size="14.00">gRPC Server</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="262" y="-431.61" font-family="Helvetica,sans-Serif" font-size="14.00">port 50051</text>
|
||||
</g>
|
||||
<!-- fastapi->grpc_server -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>fastapi->grpc_server</title>
|
||||
<path fill="none" stroke="black" d="M249.44,-563.85C249.44,-563.85 249.44,-477.88 249.44,-477.88"/>
|
||||
<polygon fill="black" stroke="black" points="252.94,-477.88 249.44,-467.88 245.94,-477.88 252.94,-477.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276" y="-525.66" font-family="Helvetica,sans-Serif" font-size="10.00">gRPC</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276" y="-512.91" font-family="Helvetica,sans-Serif" font-size="10.00">progress updates</text>
|
||||
<path fill="none" stroke="black" d="M251.88,-563.85C251.88,-563.85 251.88,-477.88 251.88,-477.88"/>
|
||||
<polygon fill="black" stroke="black" points="255.38,-477.88 251.88,-467.88 248.38,-477.88 255.38,-477.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="292" y="-525.66" font-family="Helvetica,sans-Serif" font-size="10.00">gRPC</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="292" y="-512.91" font-family="Helvetica,sans-Serif" font-size="10.00">progress updates</text>
|
||||
</g>
|
||||
<!-- fastapi->postgres -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>fastapi->postgres</title>
|
||||
<path fill="none" stroke="black" d="M157.25,-563.79C157.25,-465.13 157.25,-159 157.25,-159 157.25,-159 123.5,-159 123.5,-159"/>
|
||||
<polygon fill="black" stroke="black" points="123.5,-155.5 113.5,-159 123.5,-162.5 123.5,-155.5"/>
|
||||
<path fill="none" stroke="black" d="M188.61,-594C138.18,-594 69.5,-594 69.5,-594 69.5,-594 69.5,-199.68 69.5,-199.68"/>
|
||||
<polygon fill="black" stroke="black" points="73,-199.68 69.5,-189.68 66,-199.68 73,-199.68"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="82.38" y="-385.16" font-family="Helvetica,sans-Serif" font-size="10.00">read/write jobs</text>
|
||||
</g>
|
||||
<!-- timeline->fastapi -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>timeline->fastapi</title>
|
||||
<path fill="none" stroke="black" d="M246,-707.25C246,-707.25 246,-635.54 246,-635.54"/>
|
||||
<polygon fill="black" stroke="black" points="249.5,-635.54 246,-625.54 242.5,-635.54 249.5,-635.54"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="253.75" y="-668.91" font-family="Helvetica,sans-Serif" font-size="10.00">REST API</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="253.75" y="-656.16" font-family="Helvetica,sans-Serif" font-size="10.00">GraphQL</text>
|
||||
<path fill="none" stroke="black" d="M246,-694.26C246,-694.26 246,-635.65 246,-635.65"/>
|
||||
<polygon fill="black" stroke="black" points="249.5,-635.65 246,-625.65 242.5,-635.65 249.5,-635.65"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="264" y="-656.16" font-family="Helvetica,sans-Serif" font-size="10.00">GraphQL</text>
|
||||
</g>
|
||||
<!-- celery -->
|
||||
<g id="node7" class="node">
|
||||
<title>celery</title>
|
||||
<path fill="none" stroke="black" d="M343.62,-352.91C343.62,-352.91 208.38,-352.91 208.38,-352.91 202.38,-352.91 196.38,-346.91 196.38,-340.91 196.38,-340.91 196.38,-322.41 196.38,-322.41 196.38,-316.41 202.38,-310.41 208.38,-310.41 208.38,-310.41 343.62,-310.41 343.62,-310.41 349.62,-310.41 355.62,-316.41 355.62,-322.41 355.62,-322.41 355.62,-340.91 355.62,-340.91 355.62,-346.91 349.62,-352.91 343.62,-352.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276" y="-335.61" font-family="Helvetica,sans-Serif" font-size="14.00">Celery Worker</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276" y="-318.36" font-family="Helvetica,sans-Serif" font-size="14.00">FFmpeg transcoding</text>
|
||||
<path fill="none" stroke="black" d="M348.62,-352.91C348.62,-352.91 213.38,-352.91 213.38,-352.91 207.38,-352.91 201.38,-346.91 201.38,-340.91 201.38,-340.91 201.38,-322.41 201.38,-322.41 201.38,-316.41 207.38,-310.41 213.38,-310.41 213.38,-310.41 348.62,-310.41 348.62,-310.41 354.62,-310.41 360.62,-316.41 360.62,-322.41 360.62,-322.41 360.62,-340.91 360.62,-340.91 360.62,-346.91 354.62,-352.91 348.62,-352.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="281" y="-335.61" font-family="Helvetica,sans-Serif" font-size="14.00">Celery Worker</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="281" y="-318.36" font-family="Helvetica,sans-Serif" font-size="14.00">FFmpeg transcoding</text>
|
||||
</g>
|
||||
<!-- grpc_server->celery -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>grpc_server->celery</title>
|
||||
<path fill="none" stroke="black" d="M257,-423.34C257,-423.34 257,-364.66 257,-364.66"/>
|
||||
<polygon fill="black" stroke="black" points="260.5,-364.66 257,-354.66 253.5,-364.66 260.5,-364.66"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="300.25" y="-385.16" font-family="Helvetica,sans-Serif" font-size="10.00">dispatch tasks</text>
|
||||
<path fill="none" stroke="black" d="M262,-423.34C262,-423.34 262,-364.66 262,-364.66"/>
|
||||
<polygon fill="black" stroke="black" points="265.5,-364.66 262,-354.66 258.5,-364.66 265.5,-364.66"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="305.25" y="-385.16" font-family="Helvetica,sans-Serif" font-size="10.00">dispatch tasks</text>
|
||||
</g>
|
||||
<!-- celery->postgres -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>celery->postgres</title>
|
||||
<path fill="none" stroke="black" d="M196.07,-332C143.2,-332 83.58,-332 83.58,-332 83.58,-332 83.58,-199.51 83.58,-199.51"/>
|
||||
<polygon fill="black" stroke="black" points="87.08,-199.51 83.58,-189.51 80.08,-199.51 87.08,-199.51"/>
|
||||
<path fill="none" stroke="black" d="M201.09,-332C148.99,-332 90.62,-332 90.62,-332 90.62,-332 90.62,-199.51 90.62,-199.51"/>
|
||||
<polygon fill="black" stroke="black" points="94.13,-199.51 90.63,-189.51 87.13,-199.51 94.13,-199.51"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="181.38" y="-259.16" font-family="Helvetica,sans-Serif" font-size="10.00">update job status</text>
|
||||
</g>
|
||||
<!-- redis -->
|
||||
@@ -198,15 +197,15 @@
|
||||
<!-- celery->redis -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>celery->redis</title>
|
||||
<path fill="none" stroke="black" d="M237.25,-310.09C237.25,-310.09 237.25,-211.49 237.25,-211.49"/>
|
||||
<polygon fill="black" stroke="black" points="240.75,-211.49 237.25,-201.49 233.75,-211.49 240.75,-211.49"/>
|
||||
<path fill="none" stroke="black" d="M239.75,-310.09C239.75,-310.09 239.75,-211.49 239.75,-211.49"/>
|
||||
<polygon fill="black" stroke="black" points="243.25,-211.49 239.75,-201.49 236.25,-211.49 243.25,-211.49"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="314" y="-259.16" font-family="Helvetica,sans-Serif" font-size="10.00">task queue</text>
|
||||
</g>
|
||||
<!-- celery->minio -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>celery->minio</title>
|
||||
<path fill="none" stroke="black" d="M349.62,-310.09C349.62,-310.09 349.62,-200.39 349.62,-200.39"/>
|
||||
<polygon fill="black" stroke="black" points="353.13,-200.39 349.63,-190.39 346.13,-200.39 353.13,-200.39"/>
|
||||
<path fill="none" stroke="black" d="M352.12,-310.09C352.12,-310.09 352.12,-200.39 352.12,-200.39"/>
|
||||
<polygon fill="black" stroke="black" points="355.63,-200.39 352.13,-190.39 348.63,-200.39 355.63,-200.39"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="441.5" y="-271.91" font-family="Helvetica,sans-Serif" font-size="10.00">S3 API</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="441.5" y="-259.16" font-family="Helvetica,sans-Serif" font-size="10.00">download input</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="441.5" y="-246.41" font-family="Helvetica,sans-Serif" font-size="10.00">upload output</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@@ -35,7 +35,7 @@ digraph aws_architecture {
|
||||
fillcolor="#f0f8e8"
|
||||
|
||||
django [label="Django Admin\n/admin\nport 8701"]
|
||||
fastapi [label="FastAPI + GraphQL\n/api + /graphql\nport 8702"]
|
||||
fastapi [label="GraphQL API\n/graphql\nport 8702"]
|
||||
timeline [label="Timeline UI\n/\nport 5173"]
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ digraph aws_architecture {
|
||||
browser -> nginx [label="HTTP"]
|
||||
|
||||
nginx -> django [xlabel="/admin"]
|
||||
nginx -> fastapi [xlabel="/api, /graphql"]
|
||||
nginx -> fastapi [xlabel="/graphql"]
|
||||
nginx -> timeline [xlabel="/"]
|
||||
|
||||
timeline -> fastapi [label="REST API\nGraphQL"]
|
||||
timeline -> fastapi [label="GraphQL"]
|
||||
django -> postgres
|
||||
|
||||
fastapi -> postgres [label="read/write jobs"]
|
||||
|
||||
@@ -4,222 +4,221 @@
|
||||
<!-- Generated by graphviz version 14.1.2 (0)
|
||||
-->
|
||||
<!-- Title: aws_architecture Pages: 1 -->
|
||||
<svg width="620pt" height="1094pt"
|
||||
viewBox="0.00 0.00 620.00 1094.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1090.1)">
|
||||
<svg width="639pt" height="1081pt"
|
||||
viewBox="0.00 0.00 639.00 1081.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1077.35)">
|
||||
<title>aws_architecture</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-1090.1 616.25,-1090.1 616.25,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="306.12" y="-1066.9" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">MPR - AWS Architecture (Lambda + Step Functions)</text>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-1077.35 635.25,-1077.35 635.25,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="315.62" y="-1054.15" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">MPR - AWS Architecture (Lambda + Step Functions)</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_external</title>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="158,-947 158,-1050.6 318,-1050.6 318,-947 158,-947"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-1031.4" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">External</text>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="155,-934.25 155,-1037.85 315,-1037.85 315,-934.25 155,-934.25"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-1018.65" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">External</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_proxy</title>
|
||||
<polygon fill="#e8f4f8" stroke="black" points="165,-819.25 165,-905.25 311,-905.25 311,-819.25 165,-819.25"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-886.05" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Reverse Proxy</text>
|
||||
<polygon fill="#e8f4f8" stroke="black" points="162,-806.5 162,-892.5 308,-892.5 308,-806.5 162,-806.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-873.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Reverse Proxy</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_apps</title>
|
||||
<polygon fill="#f0f8e8" stroke="black" points="11,-542.75 11,-789.25 293,-789.25 293,-542.75 11,-542.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="152" y="-770.05" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Application Layer</text>
|
||||
<polygon fill="#f0f8e8" stroke="black" points="8,-542.75 8,-776.5 290,-776.5 290,-542.75 8,-542.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="149" y="-757.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Application Layer</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_data</title>
|
||||
<polygon fill="#f8e8f0" stroke="black" points="8,-372.91 8,-474.84 122,-474.84 122,-372.91 8,-372.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="65" y="-455.64" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Data Layer</text>
|
||||
<polygon fill="#f8e8f0" stroke="black" points="27,-372.91 27,-474.84 141,-474.84 141,-372.91 27,-372.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-455.64" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">Data Layer</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_aws</title>
|
||||
<polygon fill="#fde8d0" stroke="black" points="245,-8 245,-475.5 577,-475.5 577,-8 245,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="411" y="-456.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AWS Cloud</text>
|
||||
<polygon fill="#fde8d0" stroke="black" points="264,-8 264,-475.5 596,-475.5 596,-8 264,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="430" y="-456.3" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">AWS Cloud</text>
|
||||
</g>
|
||||
<!-- browser -->
|
||||
<g id="node1" class="node">
|
||||
<title>browser</title>
|
||||
<ellipse fill="none" stroke="black" cx="238" cy="-985.05" rx="71.77" ry="30.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-989" font-family="Helvetica,sans-Serif" font-size="14.00">Browser</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-971.75" font-family="Helvetica,sans-Serif" font-size="14.00">mpr.mcrn.ar</text>
|
||||
<ellipse fill="none" stroke="black" cx="235" cy="-972.3" rx="71.77" ry="30.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-976.25" font-family="Helvetica,sans-Serif" font-size="14.00">Browser</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-959" font-family="Helvetica,sans-Serif" font-size="14.00">mpr.mcrn.ar</text>
|
||||
</g>
|
||||
<!-- nginx -->
|
||||
<g id="node2" class="node">
|
||||
<title>nginx</title>
|
||||
<path fill="none" stroke="black" d="M259.5,-869.75C259.5,-869.75 216.5,-869.75 216.5,-869.75 210.5,-869.75 204.5,-863.75 204.5,-857.75 204.5,-857.75 204.5,-839.25 204.5,-839.25 204.5,-833.25 210.5,-827.25 216.5,-827.25 216.5,-827.25 259.5,-827.25 259.5,-827.25 265.5,-827.25 271.5,-833.25 271.5,-839.25 271.5,-839.25 271.5,-857.75 271.5,-857.75 271.5,-863.75 265.5,-869.75 259.5,-869.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-852.45" font-family="Helvetica,sans-Serif" font-size="14.00">nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-835.2" font-family="Helvetica,sans-Serif" font-size="14.00">port 80</text>
|
||||
<path fill="none" stroke="black" d="M256.5,-857C256.5,-857 213.5,-857 213.5,-857 207.5,-857 201.5,-851 201.5,-845 201.5,-845 201.5,-826.5 201.5,-826.5 201.5,-820.5 207.5,-814.5 213.5,-814.5 213.5,-814.5 256.5,-814.5 256.5,-814.5 262.5,-814.5 268.5,-820.5 268.5,-826.5 268.5,-826.5 268.5,-845 268.5,-845 268.5,-851 262.5,-857 256.5,-857"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-839.7" font-family="Helvetica,sans-Serif" font-size="14.00">nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-822.45" font-family="Helvetica,sans-Serif" font-size="14.00">port 80</text>
|
||||
</g>
|
||||
<!-- browser->nginx -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>browser->nginx</title>
|
||||
<path fill="none" stroke="black" d="M238,-954.75C238,-954.75 238,-881.75 238,-881.75"/>
|
||||
<polygon fill="black" stroke="black" points="241.5,-881.75 238,-871.75 234.5,-881.75 241.5,-881.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="250.75" y="-916.5" font-family="Helvetica,sans-Serif" font-size="10.00">HTTP</text>
|
||||
<path fill="none" stroke="black" d="M235,-942C235,-942 235,-869 235,-869"/>
|
||||
<polygon fill="black" stroke="black" points="238.5,-869 235,-859 231.5,-869 238.5,-869"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="247.75" y="-903.75" font-family="Helvetica,sans-Serif" font-size="10.00">HTTP</text>
|
||||
</g>
|
||||
<!-- django -->
|
||||
<g id="node3" class="node">
|
||||
<title>django</title>
|
||||
<path fill="none" stroke="black" d="M120.75,-753.75C120.75,-753.75 31.25,-753.75 31.25,-753.75 25.25,-753.75 19.25,-747.75 19.25,-741.75 19.25,-741.75 19.25,-706 19.25,-706 19.25,-700 25.25,-694 31.25,-694 31.25,-694 120.75,-694 120.75,-694 126.75,-694 132.75,-700 132.75,-706 132.75,-706 132.75,-741.75 132.75,-741.75 132.75,-747.75 126.75,-753.75 120.75,-753.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="76" y="-736.45" font-family="Helvetica,sans-Serif" font-size="14.00">Django Admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="76" y="-719.2" font-family="Helvetica,sans-Serif" font-size="14.00">/admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="76" y="-701.95" font-family="Helvetica,sans-Serif" font-size="14.00">port 8701</text>
|
||||
<path fill="none" stroke="black" d="M117.75,-741C117.75,-741 28.25,-741 28.25,-741 22.25,-741 16.25,-735 16.25,-729 16.25,-729 16.25,-693.25 16.25,-693.25 16.25,-687.25 22.25,-681.25 28.25,-681.25 28.25,-681.25 117.75,-681.25 117.75,-681.25 123.75,-681.25 129.75,-687.25 129.75,-693.25 129.75,-693.25 129.75,-729 129.75,-729 129.75,-735 123.75,-741 117.75,-741"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="73" y="-723.7" font-family="Helvetica,sans-Serif" font-size="14.00">Django Admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="73" y="-706.45" font-family="Helvetica,sans-Serif" font-size="14.00">/admin</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="73" y="-689.2" font-family="Helvetica,sans-Serif" font-size="14.00">port 8701</text>
|
||||
</g>
|
||||
<!-- nginx->django -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>nginx->django</title>
|
||||
<path fill="none" stroke="black" d="M204.04,-856C156.54,-856 76,-856 76,-856 76,-856 76,-765.7 76,-765.7"/>
|
||||
<polygon fill="black" stroke="black" points="79.5,-765.7 76,-755.7 72.5,-765.7 79.5,-765.7"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="78" y="-859.25" font-family="Helvetica,sans-Serif" font-size="10.00">/admin</text>
|
||||
<path fill="none" stroke="black" d="M201.04,-843C153.54,-843 73,-843 73,-843 73,-843 73,-752.89 73,-752.89"/>
|
||||
<polygon fill="black" stroke="black" points="76.5,-752.89 73,-742.89 69.5,-752.89 76.5,-752.89"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="75.09" y="-846.25" font-family="Helvetica,sans-Serif" font-size="10.00">/admin</text>
|
||||
</g>
|
||||
<!-- fastapi -->
|
||||
<g id="node4" class="node">
|
||||
<title>fastapi</title>
|
||||
<path fill="none" stroke="black" d="M273.38,-610.5C273.38,-610.5 148.62,-610.5 148.62,-610.5 142.62,-610.5 136.62,-604.5 136.62,-598.5 136.62,-598.5 136.62,-562.75 136.62,-562.75 136.62,-556.75 142.62,-550.75 148.62,-550.75 148.62,-550.75 273.38,-550.75 273.38,-550.75 279.38,-550.75 285.38,-556.75 285.38,-562.75 285.38,-562.75 285.38,-598.5 285.38,-598.5 285.38,-604.5 279.38,-610.5 273.38,-610.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="211" y="-593.2" font-family="Helvetica,sans-Serif" font-size="14.00">FastAPI + GraphQL</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="211" y="-575.95" font-family="Helvetica,sans-Serif" font-size="14.00">/api + /graphql</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="211" y="-558.7" font-family="Helvetica,sans-Serif" font-size="14.00">port 8702</text>
|
||||
<path fill="none" stroke="black" d="M270.25,-610.5C270.25,-610.5 189.75,-610.5 189.75,-610.5 183.75,-610.5 177.75,-604.5 177.75,-598.5 177.75,-598.5 177.75,-562.75 177.75,-562.75 177.75,-556.75 183.75,-550.75 189.75,-550.75 189.75,-550.75 270.25,-550.75 270.25,-550.75 276.25,-550.75 282.25,-556.75 282.25,-562.75 282.25,-562.75 282.25,-598.5 282.25,-598.5 282.25,-604.5 276.25,-610.5 270.25,-610.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="230" y="-593.2" font-family="Helvetica,sans-Serif" font-size="14.00">GraphQL API</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="230" y="-575.95" font-family="Helvetica,sans-Serif" font-size="14.00">/graphql</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="230" y="-558.7" font-family="Helvetica,sans-Serif" font-size="14.00">port 8702</text>
|
||||
</g>
|
||||
<!-- nginx->fastapi -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>nginx->fastapi</title>
|
||||
<path fill="none" stroke="black" d="M204.14,-841C190.81,-841 178.61,-841 178.61,-841 178.61,-841 178.61,-622.26 178.61,-622.26"/>
|
||||
<polygon fill="black" stroke="black" points="182.11,-622.26 178.61,-612.26 175.11,-622.26 182.11,-622.26"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="162.68" y="-734.9" font-family="Helvetica,sans-Serif" font-size="10.00">/api, /graphql</text>
|
||||
<path fill="none" stroke="black" d="M201.11,-829C191.15,-829 182.88,-829 182.88,-829 182.88,-829 182.88,-622.1 182.88,-622.1"/>
|
||||
<polygon fill="black" stroke="black" points="186.38,-622.1 182.88,-612.1 179.38,-622.1 186.38,-622.1"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="163" y="-737.91" font-family="Helvetica,sans-Serif" font-size="10.00">/graphql</text>
|
||||
</g>
|
||||
<!-- timeline -->
|
||||
<g id="node5" class="node">
|
||||
<title>timeline</title>
|
||||
<path fill="none" stroke="black" d="M273,-753.75C273,-753.75 203,-753.75 203,-753.75 197,-753.75 191,-747.75 191,-741.75 191,-741.75 191,-706 191,-706 191,-700 197,-694 203,-694 203,-694 273,-694 273,-694 279,-694 285,-700 285,-706 285,-706 285,-741.75 285,-741.75 285,-747.75 279,-753.75 273,-753.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-736.45" font-family="Helvetica,sans-Serif" font-size="14.00">Timeline UI</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-719.2" font-family="Helvetica,sans-Serif" font-size="14.00">/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="238" y="-701.95" font-family="Helvetica,sans-Serif" font-size="14.00">port 5173</text>
|
||||
<path fill="none" stroke="black" d="M270,-741C270,-741 200,-741 200,-741 194,-741 188,-735 188,-729 188,-729 188,-693.25 188,-693.25 188,-687.25 194,-681.25 200,-681.25 200,-681.25 270,-681.25 270,-681.25 276,-681.25 282,-687.25 282,-693.25 282,-693.25 282,-729 282,-729 282,-735 276,-741 270,-741"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-723.7" font-family="Helvetica,sans-Serif" font-size="14.00">Timeline UI</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-706.45" font-family="Helvetica,sans-Serif" font-size="14.00">/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235" y="-689.2" font-family="Helvetica,sans-Serif" font-size="14.00">port 5173</text>
|
||||
</g>
|
||||
<!-- nginx->timeline -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>nginx->timeline</title>
|
||||
<path fill="none" stroke="black" d="M238,-826.79C238,-826.79 238,-765.72 238,-765.72"/>
|
||||
<polygon fill="black" stroke="black" points="241.5,-765.72 238,-755.72 234.5,-765.72 241.5,-765.72"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="236.5" y="-799.5" font-family="Helvetica,sans-Serif" font-size="10.00">/</text>
|
||||
<path fill="none" stroke="black" d="M235,-814.04C235,-814.04 235,-752.97 235,-752.97"/>
|
||||
<polygon fill="black" stroke="black" points="238.5,-752.97 235,-742.97 231.5,-752.97 238.5,-752.97"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="233.5" y="-786.75" font-family="Helvetica,sans-Serif" font-size="10.00">/</text>
|
||||
</g>
|
||||
<!-- postgres -->
|
||||
<g id="node6" class="node">
|
||||
<title>postgres</title>
|
||||
<path fill="none" stroke="black" d="M112.75,-434.03C112.75,-436.96 91.35,-439.34 65,-439.34 38.65,-439.34 17.25,-436.96 17.25,-434.03 17.25,-434.03 17.25,-386.22 17.25,-386.22 17.25,-383.29 38.65,-380.91 65,-380.91 91.35,-380.91 112.75,-383.29 112.75,-386.22 112.75,-386.22 112.75,-434.03 112.75,-434.03"/>
|
||||
<path fill="none" stroke="black" d="M112.75,-434.03C112.75,-431.1 91.35,-428.72 65,-428.72 38.65,-428.72 17.25,-431.1 17.25,-434.03"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="65" y="-414.07" font-family="Helvetica,sans-Serif" font-size="14.00">PostgreSQL</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="65" y="-396.82" font-family="Helvetica,sans-Serif" font-size="14.00">port 5436</text>
|
||||
<path fill="none" stroke="black" d="M131.75,-434.03C131.75,-436.96 110.35,-439.34 84,-439.34 57.65,-439.34 36.25,-436.96 36.25,-434.03 36.25,-434.03 36.25,-386.22 36.25,-386.22 36.25,-383.29 57.65,-380.91 84,-380.91 110.35,-380.91 131.75,-383.29 131.75,-386.22 131.75,-386.22 131.75,-434.03 131.75,-434.03"/>
|
||||
<path fill="none" stroke="black" d="M131.75,-434.03C131.75,-431.1 110.35,-428.72 84,-428.72 57.65,-428.72 36.25,-431.1 36.25,-434.03"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-414.07" font-family="Helvetica,sans-Serif" font-size="14.00">PostgreSQL</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84" y="-396.82" font-family="Helvetica,sans-Serif" font-size="14.00">port 5436</text>
|
||||
</g>
|
||||
<!-- django->postgres -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>django->postgres</title>
|
||||
<path fill="none" stroke="black" d="M42.62,-693.51C42.62,-693.51 42.62,-451.11 42.62,-451.11"/>
|
||||
<polygon fill="black" stroke="black" points="46.13,-451.11 42.63,-441.11 39.13,-451.11 46.13,-451.11"/>
|
||||
<path fill="none" stroke="black" d="M83,-680.89C83,-680.89 83,-450.97 83,-450.97"/>
|
||||
<polygon fill="black" stroke="black" points="86.5,-450.97 83,-440.97 79.5,-450.97 86.5,-450.97"/>
|
||||
</g>
|
||||
<!-- fastapi->postgres -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>fastapi->postgres</title>
|
||||
<path fill="none" stroke="black" d="M136.38,-591C100.85,-591 66,-591 66,-591 66,-591 66,-451.1 66,-451.1"/>
|
||||
<polygon fill="black" stroke="black" points="69.5,-451.1 66,-441.1 62.5,-451.1 69.5,-451.1"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="247.38" y="-499.5" font-family="Helvetica,sans-Serif" font-size="10.00">read/write jobs</text>
|
||||
<path fill="none" stroke="black" d="M201.38,-550.41C201.38,-503.88 201.38,-420 201.38,-420 201.38,-420 143.59,-420 143.59,-420"/>
|
||||
<polygon fill="black" stroke="black" points="143.59,-416.5 133.59,-420 143.59,-423.5 143.59,-416.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="266.38" y="-499.5" font-family="Helvetica,sans-Serif" font-size="10.00">read/write jobs</text>
|
||||
</g>
|
||||
<!-- fastapi->postgres -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>fastapi->postgres</title>
|
||||
<path fill="none" stroke="black" d="M136.4,-571C111.22,-571 89.38,-571 89.38,-571 89.38,-571 89.38,-451.15 89.38,-451.15"/>
|
||||
<polygon fill="black" stroke="black" points="92.88,-451.15 89.38,-441.15 85.88,-451.15 92.88,-451.15"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="106.25" y="-505.88" font-family="Helvetica,sans-Serif" font-size="10.00">callback updates</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="106.25" y="-493.12" font-family="Helvetica,sans-Serif" font-size="10.00">job status</text>
|
||||
<path fill="none" stroke="black" d="M225,-550.39C225,-498.97 225,-400 225,-400 225,-400 143.64,-400 143.64,-400"/>
|
||||
<polygon fill="black" stroke="black" points="143.64,-396.5 133.64,-400 143.64,-403.5 143.64,-396.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="125.25" y="-505.88" font-family="Helvetica,sans-Serif" font-size="10.00">callback updates</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="125.25" y="-493.12" font-family="Helvetica,sans-Serif" font-size="10.00">job status</text>
|
||||
</g>
|
||||
<!-- step_functions -->
|
||||
<g id="node7" class="node">
|
||||
<title>step_functions</title>
|
||||
<path fill="none" stroke="black" d="M365.38,-440C365.38,-440 270.62,-440 270.62,-440 264.62,-440 258.62,-434 258.62,-428 258.62,-428 258.62,-392.25 258.62,-392.25 258.62,-386.25 264.62,-380.25 270.62,-380.25 270.62,-380.25 365.38,-380.25 365.38,-380.25 371.38,-380.25 377.38,-386.25 377.38,-392.25 377.38,-392.25 377.38,-428 377.38,-428 377.38,-434 371.38,-440 365.38,-440"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="318" y="-422.7" font-family="Helvetica,sans-Serif" font-size="14.00">Step Functions</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="318" y="-405.45" font-family="Helvetica,sans-Serif" font-size="14.00">Orchestration</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="318" y="-388.2" font-family="Helvetica,sans-Serif" font-size="14.00">state machine</text>
|
||||
<path fill="none" stroke="black" d="M384.38,-440C384.38,-440 289.62,-440 289.62,-440 283.62,-440 277.62,-434 277.62,-428 277.62,-428 277.62,-392.25 277.62,-392.25 277.62,-386.25 283.62,-380.25 289.62,-380.25 289.62,-380.25 384.38,-380.25 384.38,-380.25 390.38,-380.25 396.38,-386.25 396.38,-392.25 396.38,-392.25 396.38,-428 396.38,-428 396.38,-434 390.38,-440 384.38,-440"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="337" y="-422.7" font-family="Helvetica,sans-Serif" font-size="14.00">Step Functions</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="337" y="-405.45" font-family="Helvetica,sans-Serif" font-size="14.00">Orchestration</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="337" y="-388.2" font-family="Helvetica,sans-Serif" font-size="14.00">state machine</text>
|
||||
</g>
|
||||
<!-- fastapi->step_functions -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>fastapi->step_functions</title>
|
||||
<path fill="none" stroke="black" d="M272,-550.54C272,-550.54 272,-452 272,-452"/>
|
||||
<polygon fill="black" stroke="black" points="275.5,-452 272,-442 268.5,-452 275.5,-452"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="388.25" y="-512.25" font-family="Helvetica,sans-Serif" font-size="10.00">boto3</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="388.25" y="-499.5" font-family="Helvetica,sans-Serif" font-size="10.00">start_execution()</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="388.25" y="-486.75" font-family="Helvetica,sans-Serif" font-size="10.00">execution_arn</text>
|
||||
<path fill="none" stroke="black" d="M282.68,-581C289.69,-581 294.51,-581 294.51,-581 294.51,-581 294.51,-451.79 294.51,-451.79"/>
|
||||
<polygon fill="black" stroke="black" points="298.01,-451.79 294.51,-441.79 291.01,-451.79 298.01,-451.79"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="407.25" y="-512.25" font-family="Helvetica,sans-Serif" font-size="10.00">boto3</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="407.25" y="-499.5" font-family="Helvetica,sans-Serif" font-size="10.00">start_execution()</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="407.25" y="-486.75" font-family="Helvetica,sans-Serif" font-size="10.00">execution_arn</text>
|
||||
</g>
|
||||
<!-- timeline->fastapi -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>timeline->fastapi</title>
|
||||
<path fill="none" stroke="black" d="M238,-693.85C238,-693.85 238,-622.13 238,-622.13"/>
|
||||
<polygon fill="black" stroke="black" points="241.5,-622.13 238,-612.13 234.5,-622.13 241.5,-622.13"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="245.75" y="-655.5" font-family="Helvetica,sans-Serif" font-size="10.00">REST API</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="245.75" y="-642.75" font-family="Helvetica,sans-Serif" font-size="10.00">GraphQL</text>
|
||||
<path fill="none" stroke="black" d="M235,-680.86C235,-680.86 235,-622.24 235,-622.24"/>
|
||||
<polygon fill="black" stroke="black" points="238.5,-622.24 235,-612.24 231.5,-622.24 238.5,-622.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="253" y="-642.75" font-family="Helvetica,sans-Serif" font-size="10.00">GraphQL</text>
|
||||
</g>
|
||||
<!-- lambda -->
|
||||
<g id="node8" class="node">
|
||||
<title>lambda</title>
|
||||
<path fill="none" stroke="black" d="M467,-296.75C467,-296.75 349,-296.75 349,-296.75 343,-296.75 337,-290.75 337,-284.75 337,-284.75 337,-249 337,-249 337,-243 343,-237 349,-237 349,-237 467,-237 467,-237 473,-237 479,-243 479,-249 479,-249 479,-284.75 479,-284.75 479,-290.75 473,-296.75 467,-296.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="408" y="-279.45" font-family="Helvetica,sans-Serif" font-size="14.00">Lambda Function</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="408" y="-262.2" font-family="Helvetica,sans-Serif" font-size="14.00">FFmpeg container</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="408" y="-244.95" font-family="Helvetica,sans-Serif" font-size="14.00">transcoding</text>
|
||||
<path fill="none" stroke="black" d="M486,-296.75C486,-296.75 368,-296.75 368,-296.75 362,-296.75 356,-290.75 356,-284.75 356,-284.75 356,-249 356,-249 356,-243 362,-237 368,-237 368,-237 486,-237 486,-237 492,-237 498,-243 498,-249 498,-249 498,-284.75 498,-284.75 498,-290.75 492,-296.75 486,-296.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="427" y="-279.45" font-family="Helvetica,sans-Serif" font-size="14.00">Lambda Function</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="427" y="-262.2" font-family="Helvetica,sans-Serif" font-size="14.00">FFmpeg container</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="427" y="-244.95" font-family="Helvetica,sans-Serif" font-size="14.00">transcoding</text>
|
||||
</g>
|
||||
<!-- step_functions->lambda -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>step_functions->lambda</title>
|
||||
<path fill="none" stroke="black" d="M357.19,-380.1C357.19,-380.1 357.19,-308.38 357.19,-308.38"/>
|
||||
<polygon fill="black" stroke="black" points="360.69,-308.38 357.19,-298.38 353.69,-308.38 360.69,-308.38"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="388.12" y="-341.75" font-family="Helvetica,sans-Serif" font-size="10.00">invoke with</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="388.12" y="-329" font-family="Helvetica,sans-Serif" font-size="10.00">job parameters</text>
|
||||
<path fill="none" stroke="black" d="M376.19,-380.1C376.19,-380.1 376.19,-308.38 376.19,-308.38"/>
|
||||
<polygon fill="black" stroke="black" points="379.69,-308.38 376.19,-298.38 372.69,-308.38 379.69,-308.38"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="407.12" y="-341.75" font-family="Helvetica,sans-Serif" font-size="10.00">invoke with</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="407.12" y="-329" font-family="Helvetica,sans-Serif" font-size="10.00">job parameters</text>
|
||||
</g>
|
||||
<!-- lambda->fastapi -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>lambda->fastapi</title>
|
||||
<path fill="none" stroke="black" d="M336.76,-267C274.82,-267 194.94,-267 194.94,-267 194.94,-267 194.94,-538.75 194.94,-538.75"/>
|
||||
<polygon fill="black" stroke="black" points="191.44,-538.75 194.94,-548.75 198.44,-538.75 191.44,-538.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="552.62" y="-413.38" font-family="Helvetica,sans-Serif" font-size="10.00">POST /jobs/{id}/callback</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="552.62" y="-400.62" font-family="Helvetica,sans-Serif" font-size="10.00">update status</text>
|
||||
<path fill="none" stroke="black" d="M355.73,-267C306.05,-267 248.62,-267 248.62,-267 248.62,-267 248.62,-538.75 248.62,-538.75"/>
|
||||
<polygon fill="black" stroke="black" points="245.13,-538.75 248.63,-548.75 252.13,-538.75 245.13,-538.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="571.62" y="-413.38" font-family="Helvetica,sans-Serif" font-size="10.00">POST /jobs/{id}/callback</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="571.62" y="-400.62" font-family="Helvetica,sans-Serif" font-size="10.00">update status</text>
|
||||
</g>
|
||||
<!-- s3 -->
|
||||
<g id="node9" class="node">
|
||||
<title>s3</title>
|
||||
<polygon fill="none" stroke="black" points="454.62,-153.5 451.62,-157.5 430.62,-157.5 427.62,-153.5 361.38,-153.5 361.38,-117.5 454.62,-117.5 454.62,-153.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="408" y="-130.82" font-family="Helvetica,sans-Serif" font-size="14.00">S3 Buckets</text>
|
||||
<polygon fill="none" stroke="black" points="473.62,-153.5 470.62,-157.5 449.62,-157.5 446.62,-153.5 380.38,-153.5 380.38,-117.5 473.62,-117.5 473.62,-153.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="427" y="-130.82" font-family="Helvetica,sans-Serif" font-size="14.00">S3 Buckets</text>
|
||||
</g>
|
||||
<!-- lambda->s3 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>lambda->s3</title>
|
||||
<path fill="none" stroke="black" d="M408,-236.73C408,-236.73 408,-165.27 408,-165.27"/>
|
||||
<polygon fill="black" stroke="black" points="411.5,-165.27 408,-155.27 404.5,-165.27 411.5,-165.27"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="445.5" y="-198.5" font-family="Helvetica,sans-Serif" font-size="10.00">download input</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="445.5" y="-185.75" font-family="Helvetica,sans-Serif" font-size="10.00">upload output</text>
|
||||
<path fill="none" stroke="black" d="M427,-236.73C427,-236.73 427,-165.27 427,-165.27"/>
|
||||
<polygon fill="black" stroke="black" points="430.5,-165.27 427,-155.27 423.5,-165.27 430.5,-165.27"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="464.5" y="-198.5" font-family="Helvetica,sans-Serif" font-size="10.00">download input</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="464.5" y="-185.75" font-family="Helvetica,sans-Serif" font-size="10.00">upload output</text>
|
||||
</g>
|
||||
<!-- bucket_in -->
|
||||
<g id="node10" class="node">
|
||||
<title>bucket_in</title>
|
||||
<polygon fill="none" stroke="black" points="360.75,-58.5 253.25,-58.5 253.25,-16 366.75,-16 366.75,-52.5 360.75,-58.5"/>
|
||||
<polyline fill="none" stroke="black" points="360.75,-58.5 360.75,-52.5"/>
|
||||
<polyline fill="none" stroke="black" points="366.75,-52.5 360.75,-52.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="310" y="-41.2" font-family="Helvetica,sans-Serif" font-size="14.00">mpr-media-in/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="310" y="-23.95" font-family="Helvetica,sans-Serif" font-size="14.00">input videos</text>
|
||||
<polygon fill="none" stroke="black" points="379.75,-58.5 272.25,-58.5 272.25,-16 385.75,-16 385.75,-52.5 379.75,-58.5"/>
|
||||
<polyline fill="none" stroke="black" points="379.75,-58.5 379.75,-52.5"/>
|
||||
<polyline fill="none" stroke="black" points="385.75,-52.5 379.75,-52.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="329" y="-41.2" font-family="Helvetica,sans-Serif" font-size="14.00">mpr-media-in/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="329" y="-23.95" font-family="Helvetica,sans-Serif" font-size="14.00">input videos</text>
|
||||
</g>
|
||||
<!-- s3->bucket_in -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>s3->bucket_in</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M361.09,-136C354.1,-136 349.19,-136 349.19,-136 349.19,-136 349.19,-87.72 349.19,-58.68"/>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M380.09,-136C373.1,-136 368.19,-136 368.19,-136 368.19,-136 368.19,-87.72 368.19,-58.68"/>
|
||||
</g>
|
||||
<!-- bucket_out -->
|
||||
<g id="node11" class="node">
|
||||
<title>bucket_out</title>
|
||||
<polygon fill="none" stroke="black" points="563.12,-58.5 424.88,-58.5 424.88,-16 569.12,-16 569.12,-52.5 563.12,-58.5"/>
|
||||
<polyline fill="none" stroke="black" points="563.12,-58.5 563.12,-52.5"/>
|
||||
<polyline fill="none" stroke="black" points="569.12,-52.5 563.12,-52.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="497" y="-41.2" font-family="Helvetica,sans-Serif" font-size="14.00">mpr-media-out/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="497" y="-23.95" font-family="Helvetica,sans-Serif" font-size="14.00">transcoded output</text>
|
||||
<polygon fill="none" stroke="black" points="582.12,-58.5 443.88,-58.5 443.88,-16 588.12,-16 588.12,-52.5 582.12,-58.5"/>
|
||||
<polyline fill="none" stroke="black" points="582.12,-58.5 582.12,-52.5"/>
|
||||
<polyline fill="none" stroke="black" points="588.12,-52.5 582.12,-52.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="516" y="-41.2" font-family="Helvetica,sans-Serif" font-size="14.00">mpr-media-out/</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="516" y="-23.95" font-family="Helvetica,sans-Serif" font-size="14.00">transcoded output</text>
|
||||
</g>
|
||||
<!-- s3->bucket_out -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>s3->bucket_out</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M439.75,-117.02C439.75,-100.45 439.75,-76.15 439.75,-58.73"/>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M458.75,-117.02C458.75,-100.45 458.75,-76.15 458.75,-58.73"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
@@ -57,8 +57,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<span class="color-box" style="background: #f0f8e8"></span>
|
||||
Application Layer (Django Admin, FastAPI + GraphQL, Timeline
|
||||
UI)
|
||||
Application Layer (Django Admin, GraphQL API, Timeline UI)
|
||||
</li>
|
||||
<li>
|
||||
<span class="color-box" style="background: #fff8e8"></span>
|
||||
@@ -170,24 +169,31 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>API Interfaces</h2>
|
||||
<pre><code># REST API
|
||||
http://mpr.local.ar/api/docs - Swagger UI
|
||||
POST /api/assets/scan - Scan S3 bucket for media
|
||||
POST /api/jobs/ - Create transcode job
|
||||
POST /api/jobs/{id}/callback - Lambda completion callback
|
||||
<h2>API (GraphQL)</h2>
|
||||
<pre><code># GraphiQL IDE
|
||||
http://mpr.local.ar/graphql
|
||||
|
||||
# GraphQL (GraphiQL)
|
||||
http://mpr.local.ar/graphql - GraphiQL IDE
|
||||
query { assets { id filename } }
|
||||
mutation { createJob(input: {...}) { id status } }
|
||||
mutation { scanMediaFolder { found registered } }</code></pre>
|
||||
# Queries
|
||||
query { assets(status: "ready") { id filename duration } }
|
||||
query { jobs(status: "processing") { id status progress } }
|
||||
query { presets { id name container videoCodec } }
|
||||
query { systemStatus { status version } }
|
||||
|
||||
# Mutations
|
||||
mutation { scanMediaFolder { found registered skipped } }
|
||||
mutation { createJob(input: { sourceAssetId: "...", presetId: "..." }) { id status } }
|
||||
mutation { cancelJob(id: "...") { id status } }
|
||||
mutation { retryJob(id: "...") { id status } }
|
||||
mutation { updateAsset(id: "...", input: { comments: "..." }) { id comments } }
|
||||
mutation { deleteAsset(id: "...") { ok } }
|
||||
|
||||
# Lambda callback (REST)
|
||||
POST /api/jobs/{id}/callback - Lambda completion webhook</code></pre>
|
||||
|
||||
<h2>Access Points</h2>
|
||||
<pre><code># Local development
|
||||
127.0.0.1 mpr.local.ar
|
||||
http://mpr.local.ar/admin - Django Admin
|
||||
http://mpr.local.ar/api/docs - FastAPI Swagger
|
||||
http://mpr.local.ar/graphql - GraphiQL
|
||||
http://mpr.local.ar/ - Timeline UI
|
||||
http://localhost:9001 - MinIO Console
|
||||
|
||||
@@ -65,8 +65,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<span class="color-box" style="background: #f0f8e8"></span>
|
||||
Application Layer (Django Admin, FastAPI + GraphQL, Timeline
|
||||
UI)
|
||||
Application Layer (Django Admin, GraphQL API, Timeline UI)
|
||||
</li>
|
||||
<li>
|
||||
<span class="color-box" style="background: #fff8e8"></span>
|
||||
@@ -223,15 +222,22 @@ MEDIA_BASE_URL=https://source-bucket.s3.amazonaws.com/media/</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<h3>API Endpoints</h3>
|
||||
<h3>API (GraphQL)</h3>
|
||||
<p>
|
||||
All client interactions go through GraphQL at
|
||||
<code>/graphql</code>.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<code>POST /api/assets/scan</code> - Recursively scans
|
||||
MEDIA_IN and registers video/audio files
|
||||
<code>scanMediaFolder</code> - Scan S3 bucket for media
|
||||
files
|
||||
</li>
|
||||
<li><code>createJob</code> - Create transcode/trim job</li>
|
||||
<li>
|
||||
<code>cancelJob / retryJob</code> - Job lifecycle management
|
||||
</li>
|
||||
<li>
|
||||
<code>POST /api/jobs/</code> - Creates transcoding job with
|
||||
source asset, preset, and optional trim times
|
||||
<code>updateAsset / deleteAsset</code> - Asset management
|
||||
</li>
|
||||
</ul>
|
||||
<p><strong>Supported File Types:</strong></p>
|
||||
@@ -241,15 +247,13 @@ MEDIA_BASE_URL=https://source-bucket.s3.amazonaws.com/media/</code></pre>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Quick Reference</h2>
|
||||
|
||||
<h2>Access Points</h2>
|
||||
<pre><code># Add to /etc/hosts
|
||||
127.0.0.1 mpr.local.ar
|
||||
|
||||
# URLs
|
||||
http://mpr.local.ar/admin - Django Admin
|
||||
http://mpr.local.ar/api - FastAPI (docs at /api/docs)
|
||||
http://mpr.local.ar/ui - Timeline UI</code></pre>
|
||||
http://mpr.local.ar/admin - Django Admin
|
||||
http://mpr.local.ar/graphql - GraphiQL IDE
|
||||
http://mpr.local.ar/ - Timeline UI</code></pre>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
"output": "mpr/media_assets/models.py",
|
||||
"include": ["dataclasses", "enums"]
|
||||
},
|
||||
{
|
||||
"target": "pydantic",
|
||||
"output": "api/schema/",
|
||||
"include": ["dataclasses", "enums"],
|
||||
"name_map": {
|
||||
"TranscodeJob": "Job",
|
||||
"MediaAsset": "Asset",
|
||||
"TranscodePreset": "Preset"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target": "graphene",
|
||||
"output": "api/schema/graphql.py",
|
||||
|
||||
@@ -5,7 +5,13 @@ This module exports all dataclasses, enums, and constants that the generator
|
||||
should process. Add new models here to have them included in generation.
|
||||
"""
|
||||
|
||||
from .api import CreateJobRequest, ScanResult, SystemStatus
|
||||
from .api import (
|
||||
CreateJobRequest,
|
||||
DeleteResult,
|
||||
ScanResult,
|
||||
SystemStatus,
|
||||
UpdateAssetRequest,
|
||||
)
|
||||
from .grpc import (
|
||||
GRPC_SERVICE,
|
||||
CancelRequest,
|
||||
@@ -26,7 +32,14 @@ DATACLASSES = [MediaAsset, TranscodePreset, TranscodeJob]
|
||||
|
||||
# API request/response models - generates TypeScript only (no Django)
|
||||
# WorkerStatus from grpc.py is reused here
|
||||
API_MODELS = [CreateJobRequest, SystemStatus, ScanResult, WorkerStatus]
|
||||
API_MODELS = [
|
||||
CreateJobRequest,
|
||||
UpdateAssetRequest,
|
||||
SystemStatus,
|
||||
ScanResult,
|
||||
DeleteResult,
|
||||
WorkerStatus,
|
||||
]
|
||||
|
||||
# Status enums - included in generated code
|
||||
ENUMS = [AssetStatus, JobStatus]
|
||||
@@ -50,6 +63,8 @@ __all__ = [
|
||||
"TranscodeJob",
|
||||
# API Models
|
||||
"CreateJobRequest",
|
||||
"UpdateAssetRequest",
|
||||
"DeleteResult",
|
||||
"ScanResult",
|
||||
"SystemStatus",
|
||||
# Enums
|
||||
|
||||
@@ -40,4 +40,19 @@ class ScanResult:
|
||||
files: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class UpdateAssetRequest:
|
||||
"""Request body for updating asset metadata."""
|
||||
|
||||
comments: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeleteResult:
|
||||
"""Result of a delete operation."""
|
||||
|
||||
ok: bool = False
|
||||
|
||||
|
||||
# Note: WorkerStatus is defined in grpc.py and reused here
|
||||
|
||||
@@ -82,6 +82,11 @@ export interface CreateJobRequest {
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface UpdateAssetRequest {
|
||||
comments: string | null;
|
||||
tags: string[] | null;
|
||||
}
|
||||
|
||||
export interface SystemStatus {
|
||||
status: string;
|
||||
version: string;
|
||||
@@ -94,6 +99,10 @@ export interface ScanResult {
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export interface DeleteResult {
|
||||
ok: boolean;
|
||||
}
|
||||
|
||||
export interface WorkerStatus {
|
||||
available: boolean;
|
||||
active_jobs: number;
|
||||
|
||||
Reference in New Issue
Block a user