Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions backend/app/api/disputes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from typing import Optional, List

Check failure on line 1 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:1:30: F401 `typing.List` imported but unused help: Remove unused import

Check failure on line 1 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:1:20: F401 `typing.Optional` imported but unused help: Remove unused import

Check failure on line 1 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:1:30: F401 `typing.List` imported but unused help: Remove unused import

Check failure on line 1 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:1:20: F401 `typing.Optional` imported but unused help: Remove unused import
from fastapi import APIRouter, Depends, HTTPException, Query, status

Check failure on line 2 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:2:56: F401 `fastapi.Query` imported but unused help: Remove unused import: `fastapi.Query`

Check failure on line 2 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:2:56: F401 `fastapi.Query` imported but unused help: Remove unused import: `fastapi.Query`

from app.auth import get_current_user_id
from app.constants import INTERNAL_SYSTEM_USER_ID
from app.models.dispute import (
DisputeCreate, DisputeResponse, DisputeListResponse,

Check failure on line 7 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:7:37: F401 `app.models.dispute.DisputeListResponse` imported but unused help: Remove unused import: `app.models.dispute.DisputeListResponse`

Check failure on line 7 in backend/app/api/disputes.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/disputes.py:7:37: F401 `app.models.dispute.DisputeListResponse` imported but unused help: Remove unused import: `app.models.dispute.DisputeListResponse`
DisputeEvidenceCreate, DisputeResolve
)
from app.services import dispute_service

router = APIRouter(prefix="/disputes", tags=["disputes"])

@router.post("", response_model=DisputeResponse, status_code=201)
async def create_dispute(
data: DisputeCreate,
user_id: str = Depends(get_current_user_id)
):
"""Initiate a dispute for a rejected submission."""
dispute, error = await dispute_service.initiate_dispute(data, user_id)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return dispute

@router.get("/{dispute_id}", response_model=DisputeResponse)
async def get_dispute(
dispute_id: str,
user_id: str = Depends(get_current_user_id)
):
"""Retrieve dispute details."""
dispute = await dispute_service.get_dispute(dispute_id)
if not dispute:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dispute not found")

# Access control: Contributor, Creator, or Admin
if user_id not in [dispute.contributor_id, dispute.creator_id, INTERNAL_SYSTEM_USER_ID]:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")

return dispute

@router.post("/{dispute_id}/evidence", status_code=200)
async def submit_evidence(
dispute_id: str,
data: DisputeEvidenceCreate,
user_id: str = Depends(get_current_user_id)
):
"""Submit evidence (link or explanation) for an open dispute."""
# Logic is handled in service (which checks dispute state)
success, error = await dispute_service.submit_evidence(
dispute_id, user_id, data.type, data.content
)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)
return {"status": "success", "message": "Evidence submitted"}

@router.post("/{dispute_id}/resolve", response_model=DisputeResponse)
async def resolve_dispute(
dispute_id: str,
data: DisputeResolve,
user_id: str = Depends(get_current_user_id)
):
"""Resolve a dispute (Admin only)."""
# Simple admin check for now
if user_id != INTERNAL_SYSTEM_USER_ID:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only platform admins can resolve disputes")

success, error = await dispute_service.resolve_dispute(dispute_id, data, user_id)
if error:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error)

# Return updated dispute
return await dispute_service.get_dispute(dispute_id)
4 changes: 4 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from app.api.agents import router as agents_router
from app.api.stats import router as stats_router
from app.api.escrow import router as escrow_router
from app.api.disputes import router as disputes_router
from app.database import init_db, close_db, engine
from app.services.auth_service import AuthError
from app.services.websocket_manager import manager as ws_manager
Expand Down Expand Up @@ -279,6 +280,9 @@ async def value_error_handler(request: Request, exc: ValueError):
# Escrow: /api/escrow/*
app.include_router(escrow_router, prefix="/api")

# Disputes: /api/disputes/*
app.include_router(disputes_router, prefix="/api")

# Stats: /api/stats (public endpoint)
app.include_router(stats_router, prefix="/api")

Expand Down
Loading
Loading