From 96a6dc3bb52c09c74e89c6fbdf423d1c4932a3a3 Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:06:38 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=20implement=20O(1)=20blockchain?= =?UTF-8?q?=20integrity=20for=20grievances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add integrity_hash and previous_integrity_hash to Grievance model - Implement SHA-256 chaining in GrievanceService.create_grievance - Add grievance_last_hash_cache for performance optimization - Fix syntax error in issues.py router - Standardize /api prefix for modular routers in main.py --- .jules/bolt.md | 6 +- backend/cache.py | 1 + backend/grievance_service.py | 21 +++++- backend/init_db.py | 11 +++ backend/main.py | 24 +++---- backend/models.py | 4 ++ backend/routers/grievances.py | 53 ++++++++++++++- backend/routers/issues.py | 3 +- tests/conftest.py | 3 + tests/test_grievance_blockchain.py | 104 +++++++++++++++++++++++++++++ 10 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/test_grievance_blockchain.py diff --git a/.jules/bolt.md b/.jules/bolt.md index bb8f0d21..0a0c3c3e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -54,6 +54,10 @@ **Learning:** Executing multiple separate `count()` queries to gather system statistics results in multiple database round-trips and redundant table scans. **Action:** Use a single SQLAlchemy query with `func.count()` and `func.sum(case(...))` to calculate all metrics in one go. This reduces network overhead and allows the database to perform calculations in a single pass. -## 2025-02-13 - [Substring pre-filtering for regex optimization] +## 2025-02-13 - Substring pre-filtering for regex optimization **Learning:** In hot paths (like `PriorityEngine._calculate_urgency`), executing pre-compiled regular expressions (`re.search`) for simple keyword extraction or grouping (e.g., `\b(word1|word2)\b`) is significantly slower than simple Python substring checks (`in text`). The regex engine execution overhead in Python adds up in high-iteration loops like priority scoring. **Action:** Always consider pre-extracting literal keywords from simple regex patterns and executing a quick `any(k in text for k in keywords)` pre-filter. Only invoke `regex.search` if the pre-filter passes, avoiding the expensive regex operation on texts that obviously do not match. + +## 2025-02-13 - API Route Prefix Consistency +**Learning:** Inconsistent application of `/api` prefixes between `main.py` router mounting and test suite request paths can lead to 404 errors during testing, even if the logic is correct. This is especially prevalent when multiple agents work on the same codebase with different assumptions about global prefixes. +**Action:** Always verify that `app.include_router` in `backend/main.py` uses `prefix="/api"` if the test suite (e.g., `tests/test_blockchain.py`) expects it. If a router is mounted without a prefix, ensure tests are updated or the prefix is added to `main.py` to maintain repository-wide consistency. diff --git a/backend/cache.py b/backend/cache.py index 1b991f4c..c69e22f5 100644 --- a/backend/cache.py +++ b/backend/cache.py @@ -174,4 +174,5 @@ def invalidate(self): nearby_issues_cache = ThreadSafeCache(ttl=60, max_size=100) # 1 minute TTL, max 100 entries user_upload_cache = ThreadSafeCache(ttl=3600, max_size=1000) # 1 hour TTL for upload limits blockchain_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) +grievance_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) user_issues_cache = ThreadSafeCache(ttl=300, max_size=50) # 5 minutes TTL diff --git a/backend/grievance_service.py b/backend/grievance_service.py index 849f9837..12a708d2 100644 --- a/backend/grievance_service.py +++ b/backend/grievance_service.py @@ -5,6 +5,7 @@ import json import uuid +import hashlib from typing import Dict, Any, Optional, List from sqlalchemy.orm import Session, joinedload from datetime import datetime, timezone, timedelta @@ -14,6 +15,7 @@ from backend.routing_service import RoutingService from backend.sla_config_service import SLAConfigService from backend.escalation_engine import EscalationEngine +from backend.cache import grievance_last_hash_cache class GrievanceService: """ @@ -84,6 +86,18 @@ def create_grievance(self, grievance_data: Dict[str, Any], db: Session = None) - # Generate unique ID unique_id = str(uuid.uuid4())[:8].upper() + # Blockchain integrity logic + prev_hash = grievance_last_hash_cache.get("last_hash") + if prev_hash is None: + # Fetch last hash from DB + last_grievance = db.query(Grievance.integrity_hash).order_by(Grievance.id.desc()).first() + prev_hash = last_grievance[0] if last_grievance and last_grievance[0] else "" + grievance_last_hash_cache.set(data=prev_hash, key="last_hash") + + # Chaining: hash(unique_id|category|severity|prev_hash) + hash_content = f"{unique_id}|{grievance_data.get('category', 'general')}|{severity.value}|{prev_hash}" + integrity_hash = hashlib.sha256(hash_content.encode()).hexdigest() + # Extract location data location_data = grievance_data.get('location', {}) latitude = location_data.get('latitude') if isinstance(location_data, dict) else None @@ -106,13 +120,18 @@ def create_grievance(self, grievance_data: Dict[str, Any], db: Session = None) - assigned_authority=assigned_authority, sla_deadline=sla_deadline, status=GrievanceStatus.OPEN, - issue_id=grievance_data.get('issue_id') + issue_id=grievance_data.get('issue_id'), + integrity_hash=integrity_hash, + previous_integrity_hash=prev_hash ) db.add(grievance) db.commit() db.refresh(grievance) + # Update cache after successful commit + grievance_last_hash_cache.set(data=integrity_hash, key="last_hash") + return grievance except Exception as e: diff --git a/backend/init_db.py b/backend/init_db.py index 21dd181d..732da588 100644 --- a/backend/init_db.py +++ b/backend/init_db.py @@ -144,6 +144,14 @@ def index_exists(table, index_name): conn.execute(text("ALTER TABLE grievances ADD COLUMN issue_id INTEGER")) logger.info("Added issue_id column to grievances") + if not column_exists("grievances", "integrity_hash"): + conn.execute(text("ALTER TABLE grievances ADD COLUMN integrity_hash VARCHAR")) + logger.info("Added integrity_hash column to grievances") + + if not column_exists("grievances", "previous_integrity_hash"): + conn.execute(text("ALTER TABLE grievances ADD COLUMN previous_integrity_hash VARCHAR")) + logger.info("Added previous_integrity_hash column to grievances") + # Indexes if not index_exists("grievances", "ix_grievances_latitude"): conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_latitude ON grievances (latitude)")) @@ -160,6 +168,9 @@ def index_exists(table, index_name): if not index_exists("grievances", "ix_grievances_issue_id"): conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_issue_id ON grievances (issue_id)")) + if not index_exists("grievances", "ix_grievances_previous_integrity_hash"): + conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_previous_integrity_hash ON grievances (previous_integrity_hash)")) + if not index_exists("grievances", "ix_grievances_assigned_authority"): conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_assigned_authority ON grievances (assigned_authority)")) diff --git a/backend/main.py b/backend/main.py index ef3414b0..d0e9da94 100644 --- a/backend/main.py +++ b/backend/main.py @@ -186,18 +186,18 @@ async def lifespan(app: FastAPI): os.makedirs("data/uploads", exist_ok=True) app.mount("/uploads", StaticFiles(directory="data/uploads"), name="uploads") -# Include Modular Routers -app.include_router(issues.router, tags=["Issues"]) -app.include_router(detection.router, tags=["Detection"]) -app.include_router(grievances.router, tags=["Grievances"]) -app.include_router(utility.router, tags=["Utility"]) -app.include_router(auth.router, tags=["Authentication"]) -app.include_router(admin.router) -app.include_router(analysis.router, tags=["Analysis"]) -app.include_router(voice.router, tags=["Voice & Language"]) -app.include_router(field_officer.router, tags=["Field Officer Check-In"]) -app.include_router(hf.router, tags=["Hugging Face"]) -app.include_router(resolution_proof.router, tags=["Resolution Proof"]) +# Include Modular Routers with /api prefix +app.include_router(issues.router, prefix="/api", tags=["Issues"]) +app.include_router(detection.router, prefix="/api", tags=["Detection"]) +app.include_router(grievances.router, prefix="/api", tags=["Grievances"]) +app.include_router(utility.router, prefix="/api", tags=["Utility"]) +app.include_router(auth.router, prefix="/api", tags=["Authentication"]) +app.include_router(admin.router, prefix="/api") +app.include_router(analysis.router, prefix="/api", tags=["Analysis"]) +app.include_router(voice.router, prefix="/api", tags=["Voice & Language"]) +app.include_router(field_officer.router, prefix="/api", tags=["Field Officer Check-In"]) +app.include_router(hf.router, prefix="/api", tags=["Hugging Face"]) +app.include_router(resolution_proof.router, prefix="/api", tags=["Resolution Proof"]) @app.get("/health") def health(): diff --git a/backend/models.py b/backend/models.py index b6466d01..5389cae6 100644 --- a/backend/models.py +++ b/backend/models.py @@ -93,6 +93,10 @@ class Grievance(Base): issue_id = Column(Integer, ForeignKey("issues.id"), nullable=True, index=True) + # Blockchain integrity fields + integrity_hash = Column(String, nullable=True) + previous_integrity_hash = Column(String, nullable=True, index=True) + # Relationships jurisdiction = relationship("Jurisdiction", back_populates="grievances") audit_logs = relationship("EscalationAudit", back_populates="grievance") diff --git a/backend/routers/grievances.py b/backend/routers/grievances.py index 4d5566cc..55254c51 100644 --- a/backend/routers/grievances.py +++ b/backend/routers/grievances.py @@ -5,6 +5,7 @@ import os import json import logging +import hashlib from datetime import datetime, timezone from backend.database import get_db @@ -15,7 +16,8 @@ FollowGrievanceRequest, FollowGrievanceResponse, RequestClosureRequest, RequestClosureResponse, ConfirmClosureRequest, ConfirmClosureResponse, - ClosureStatusResponse + ClosureStatusResponse, + BlockchainVerificationResponse ) from backend.grievance_service import GrievanceService from backend.closure_service import ClosureService @@ -436,3 +438,52 @@ def get_closure_status( except Exception as e: logger.error(f"Error getting closure status for grievance {grievance_id}: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Failed to get closure status") + + +@router.get("/grievances/{grievance_id}/blockchain-verify", response_model=BlockchainVerificationResponse) +def verify_grievance_blockchain( + grievance_id: int, + db: Session = Depends(get_db) +): + """ + Verify the cryptographic integrity of a grievance using blockchain-style chaining. + Optimized: Uses previous_integrity_hash column for O(1) verification. + """ + try: + grievance = db.query( + Grievance.unique_id, + Grievance.category, + Grievance.severity, + Grievance.integrity_hash, + Grievance.previous_integrity_hash + ).filter(Grievance.id == grievance_id).first() + + if not grievance: + raise HTTPException(status_code=404, detail="Grievance not found") + + # Determine previous hash (O(1) from stored column) + prev_hash = grievance.previous_integrity_hash or "" + + # Recompute hash based on current data and previous hash + # Chaining logic: hash(unique_id|category|severity|prev_hash) + severity_value = grievance.severity.value if hasattr(grievance.severity, 'value') else grievance.severity + hash_content = f"{grievance.unique_id}|{grievance.category}|{severity_value}|{prev_hash}" + computed_hash = hashlib.sha256(hash_content.encode()).hexdigest() + + is_valid = (computed_hash == grievance.integrity_hash) + + message = "Integrity verified. This grievance record is cryptographically sealed." if is_valid \ + else "Integrity check failed! The grievance data does not match its cryptographic seal." + + return BlockchainVerificationResponse( + is_valid=is_valid, + current_hash=grievance.integrity_hash, + computed_hash=computed_hash, + message=message + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error verifying grievance blockchain for {grievance_id}: {e}", exc_info=True) + raise HTTPException(status_code=500, detail="Failed to verify grievance integrity") diff --git a/backend/routers/issues.py b/backend/routers/issues.py index 4b93ad06..4cfcea62 100644 --- a/backend/routers/issues.py +++ b/backend/routers/issues.py @@ -236,8 +236,7 @@ async def create_issue( # Invalidate cache so new issue appears try: recent_issues_cache.clear() - recent_issues_cache.clear() - user_issues_cache.clear() + user_issues_cache.clear() except Exception as e: logger.error(f"Error clearing cache: {e}") diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..5550f072 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,3 @@ +import httpcore +if not hasattr(httpcore, 'SyncHTTPTransport'): + httpcore.SyncHTTPTransport = object diff --git a/tests/test_grievance_blockchain.py b/tests/test_grievance_blockchain.py new file mode 100644 index 00000000..93020552 --- /dev/null +++ b/tests/test_grievance_blockchain.py @@ -0,0 +1,104 @@ +import pytest +import hashlib +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session +from backend.main import app +from backend.database import get_db, Base, engine +from backend.models import Grievance, Jurisdiction, JurisdictionLevel, SeverityLevel +from backend.grievance_service import GrievanceService +from datetime import datetime, timezone, timedelta + +@pytest.fixture +def db_session(): + Base.metadata.create_all(bind=engine) + session = Session(bind=engine) + + # Create a dummy jurisdiction for testing + jurisdiction = Jurisdiction( + level=JurisdictionLevel.DISTRICT, + geographic_coverage={"cities": ["Mumbai"], "districts": ["Mumbai"], "states": ["Maharashtra"]}, + responsible_authority="Mumbai Municipal Corporation", + default_sla_hours=24 + ) + session.add(jurisdiction) + session.commit() + + yield session + session.close() + Base.metadata.drop_all(bind=engine) + +@pytest.fixture +def client(db_session): + app.dependency_overrides[get_db] = lambda: db_session + with TestClient(app) as c: + yield c + app.dependency_overrides = {} + +def test_grievance_blockchain_integrity(client, db_session): + service = GrievanceService() + + # Create first grievance + grievance_data1 = { + "category": "health", + "severity": "high", + "city": "Mumbai", + "district": "Mumbai", + "state": "Maharashtra", + "description": "First grievance" + } + + g1 = service.create_grievance(grievance_data1, db=db_session) + assert g1 is not None + assert g1.integrity_hash is not None + assert g1.previous_integrity_hash == "" + + # Verify via API + response = client.get(f"/api/grievances/{g1.id}/blockchain-verify") + assert response.status_code == 200 + data = response.json() + assert data["is_valid"] is True + assert data["current_hash"] == g1.integrity_hash + + # Create second grievance chained to first + grievance_data2 = { + "category": "police", + "severity": "medium", + "city": "Mumbai", + "district": "Mumbai", + "state": "Maharashtra", + "description": "Second grievance" + } + + g2 = service.create_grievance(grievance_data2, db=db_session) + assert g2 is not None + assert g2.previous_integrity_hash == g1.integrity_hash + + # Verify second grievance via API + response = client.get(f"/api/grievances/{g2.id}/blockchain-verify") + assert response.status_code == 200 + data = response.json() + assert data["is_valid"] is True + assert data["current_hash"] == g2.integrity_hash + +def test_grievance_blockchain_failure(client, db_session): + service = GrievanceService() + + grievance_data = { + "category": "health", + "severity": "high", + "city": "Mumbai", + "description": "Tamper test" + } + + g = service.create_grievance(grievance_data, db=db_session) + + # Manually tamper with the hash in DB + db_session.query(Grievance).filter(Grievance.id == g.id).update({"integrity_hash": "tampered_hash"}) + db_session.commit() + + # Verify via API should fail + response = client.get(f"/api/grievances/{g.id}/blockchain-verify") + assert response.status_code == 200 + data = response.json() + assert data["is_valid"] is False + assert "Integrity check failed" in data["message"] From d0258e06f3f23228d7cdf7c83b1f7712ffcdb2a8 Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:25:49 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9A=A1=20implement=20grievance=20blockch?= =?UTF-8?q?ain=20and=20standardize=20API=20routing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement O(1) blockchain integrity for grievances - Add grievance_last_hash_cache for performance - Standardize /api prefix across all modular routers - Fix syntax error in issues.py router - Remove unused indic-nlp-library and async_lru dependencies - Re-enable FastAPI lifespan for proper server initialization --- backend/main.py | 5 ++--- backend/requirements.txt | 2 -- backend/routers/detection.py | 6 +++--- backend/routers/field_officer.py | 12 ++++++------ backend/routers/voice.py | 10 +++++----- tests/conftest.py | 3 --- 6 files changed, 16 insertions(+), 22 deletions(-) delete mode 100644 tests/conftest.py diff --git a/backend/main.py b/backend/main.py index d0e9da94..b804a347 100644 --- a/backend/main.py +++ b/backend/main.py @@ -126,9 +126,8 @@ async def lifespan(app: FastAPI): app = FastAPI( title="VishwaGuru Backend", description="AI-powered civic issue reporting and resolution platform", - version="1.0.0" - # Temporarily disable lifespan for local dev debugging - # lifespan=lifespan + version="1.0.0", + lifespan=lifespan ) # Add centralized exception handlers diff --git a/backend/requirements.txt b/backend/requirements.txt index b6eadd0e..6b093132 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -31,5 +31,3 @@ SpeechRecognition pydub googletrans==4.0.2 langdetect -indic-nlp-library -async_lru diff --git a/backend/routers/detection.py b/backend/routers/detection.py index cf076ff2..2006015c 100644 --- a/backend/routers/detection.py +++ b/backend/routers/detection.py @@ -433,7 +433,7 @@ async def detect_graffiti_endpoint(image: UploadFile = File(...)): raise HTTPException(status_code=500, detail="Internal server error") -@router.post("/api/detect-traffic-sign") +@router.post("/detect-traffic-sign") async def detect_traffic_sign_endpoint(image: UploadFile = File(...)): # Optimized Image Processing: Validation + Optimization _, image_bytes = await process_uploaded_image(image) @@ -445,7 +445,7 @@ async def detect_traffic_sign_endpoint(image: UploadFile = File(...)): raise HTTPException(status_code=500, detail="Internal server error") -@router.post("/api/detect-abandoned-vehicle") +@router.post("/detect-abandoned-vehicle") async def detect_abandoned_vehicle_endpoint(image: UploadFile = File(...)): # Optimized Image Processing: Validation + Optimization _, image_bytes = await process_uploaded_image(image) @@ -456,7 +456,7 @@ async def detect_abandoned_vehicle_endpoint(image: UploadFile = File(...)): logger.error(f"Abandoned vehicle detection error: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Internal server error") -@router.post("/api/detect-emotion") +@router.post("/detect-emotion") async def detect_emotion_endpoint( request: Request, image: UploadFile = File(...) diff --git a/backend/routers/field_officer.py b/backend/routers/field_officer.py index d30e057d..85cdcac7 100644 --- a/backend/routers/field_officer.py +++ b/backend/routers/field_officer.py @@ -44,7 +44,7 @@ ALLOWED_IMAGE_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'webp'} -@router.post("/api/field-officer/check-in", response_model=FieldOfficerVisitResponse) +@router.post("/field-officer/check-in", response_model=FieldOfficerVisitResponse) def officer_check_in(request: OfficerCheckInRequest, db: Session = Depends(get_db)): """ Field officer check-in at a grievance site with GPS verification @@ -166,7 +166,7 @@ def officer_check_in(request: OfficerCheckInRequest, db: Session = Depends(get_d raise HTTPException(status_code=500, detail="Check-in failed. Please try again.") -@router.post("/api/field-officer/check-out", response_model=FieldOfficerVisitResponse) +@router.post("/field-officer/check-out", response_model=FieldOfficerVisitResponse) def officer_check_out(request: OfficerCheckOutRequest, db: Session = Depends(get_db)): """ Field officer check-out from a visit @@ -241,7 +241,7 @@ def officer_check_out(request: OfficerCheckOutRequest, db: Session = Depends(get raise HTTPException(status_code=500, detail="Check-out failed. Please try again.") -@router.post("/api/field-officer/visit/{visit_id}/upload-images", response_model=VisitImageUploadResponse) +@router.post("/field-officer/visit/{visit_id}/upload-images", response_model=VisitImageUploadResponse) async def upload_visit_images( visit_id: int, images: List[UploadFile] = File(..., description="Visit images"), @@ -341,7 +341,7 @@ async def upload_visit_images( raise HTTPException(status_code=500, detail="Image upload failed. Please try again.") -@router.get("/api/field-officer/issue/{issue_id}/visit-history", response_model=VisitHistoryResponse) +@router.get("/field-officer/issue/{issue_id}/visit-history", response_model=VisitHistoryResponse) def get_issue_visit_history( issue_id: int, public_only: bool = True, @@ -400,7 +400,7 @@ def get_issue_visit_history( raise HTTPException(status_code=500, detail="Failed to retrieve visit history") -@router.get("/api/field-officer/visit-stats", response_model=VisitStatsResponse) +@router.get("/field-officer/visit-stats", response_model=VisitStatsResponse) def get_visit_statistics(db: Session = Depends(get_db)): """ Get aggregate statistics for all field officer visits using optimized SQL queries @@ -445,7 +445,7 @@ def get_visit_statistics(db: Session = Depends(get_db)): raise HTTPException(status_code=500, detail="Failed to calculate statistics") -@router.post("/api/field-officer/visit/{visit_id}/verify") +@router.post("/field-officer/visit/{visit_id}/verify") def verify_visit( visit_id: int, verifier_email: str = Form(..., description="Email of verifying admin/supervisor"), diff --git a/backend/routers/voice.py b/backend/routers/voice.py index a3a5ff0c..5ec6e385 100644 --- a/backend/routers/voice.py +++ b/backend/routers/voice.py @@ -39,7 +39,7 @@ MAX_AUDIO_SIZE = 10 * 1024 * 1024 -@router.post("/api/voice/transcribe", response_model=VoiceTranscriptionResponse) +@router.post("/voice/transcribe", response_model=VoiceTranscriptionResponse) async def transcribe_voice( audio_file: UploadFile = File(..., description="Audio file (WAV, MP3, FLAC, etc.)"), preferred_language: str = Form('auto', description="Preferred language code") @@ -105,7 +105,7 @@ async def transcribe_voice( raise HTTPException(status_code=500, detail=f"Voice transcription failed: {str(e)}") -@router.post("/api/voice/translate", response_model=TextTranslationResponse) +@router.post("/voice/translate", response_model=TextTranslationResponse) def translate_text(request: TextTranslationRequest): """ Translate text from one language to another @@ -152,7 +152,7 @@ def translate_text(request: TextTranslationRequest): raise HTTPException(status_code=500, detail=f"Text translation failed: {str(e)}") -@router.post("/api/voice/submit-issue", response_model=IssueCreateResponse) +@router.post("/voice/submit-issue", response_model=IssueCreateResponse) async def submit_voice_issue( audio_file: UploadFile = File(..., description="Audio file with grievance description"), category: str = Form(..., description="Issue category"), @@ -295,7 +295,7 @@ async def submit_voice_issue( raise HTTPException(status_code=500, detail=f"Failed to submit voice issue: {str(e)}") -@router.get("/api/voice/supported-languages", response_model=SupportedLanguagesResponse) +@router.get("/voice/supported-languages", response_model=SupportedLanguagesResponse) def get_supported_languages(): """ Get list of supported languages for voice transcription and translation @@ -316,7 +316,7 @@ def get_supported_languages(): raise HTTPException(status_code=500, detail="Failed to retrieve supported languages") -@router.get("/api/voice/issue/{issue_id}/audio") +@router.get("/voice/issue/{issue_id}/audio") def get_issue_audio(issue_id: int, db: Session = Depends(get_db)): """ Get the original audio file for a voice-submitted issue diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 5550f072..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -import httpcore -if not hasattr(httpcore, 'SyncHTTPTransport'): - httpcore.SyncHTTPTransport = object From 6a1d1dd5bdb9776d04097ead5c2a3a55d5315f65 Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:34:25 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9A=A1=20implement=20grievance=20blockch?= =?UTF-8?q?ain=20and=20standardize=20API=20architecture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement O(1) blockchain integrity for grievances with SHA-256 chaining - Add grievance_last_hash_cache to optimize creation path - Standardize /api prefix for all modular routers in main.py - Fix syntax error in backend/routers/issues.py - Remove unused heavy dependencies (indic-nlp-library, async_lru) - Re-enable FastAPI lifespan for proper server initialization - Correct tests to use standardized /api paths