Skip to content
Closed

Jim2 #71

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
1 change: 0 additions & 1 deletion backend/app/api/v1/campaigns.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import base64
import uuid
from datetime import datetime, timezone
from uuid import UUID

Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/v1/certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ async def list_donor_certificates(
if not user or (user.id != donor_id and user.role.value != "admin"):
query = select(DonationCertificate).where(
DonationCertificate.donation.has(donor_id=donor_id),
DonationCertificate.is_public == True,
DonationCertificate.is_public,
)
else:
query = select(DonationCertificate).where(
Expand Down
28 changes: 14 additions & 14 deletions backend/app/api/v1/fund_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

# --- Constants & Thresholds ---
TIER_1_THRESHOLD_PHP = 15000.00
EMERGENCY_RELEASE_CAP_PERCENT = 0.50
EMERGENCY_RELEASE_CAP_PERCENT = 0.50
CREDIBILITY_PENALTY = 15.0
CRITICAL_CREDIBILITY_SCORE = 50.0

Expand Down Expand Up @@ -70,51 +70,51 @@ async def process_fund_release(request: FundReleaseRequest):
organizer = evaluate_account_standing(organizer)
if organizer.is_frozen:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
status_code=status.HTTP_403_FORBIDDEN,
detail="Account frozen: Pending or overdue receipts detected."
)

if request.requested_amount < TIER_1_THRESHOLD_PHP:
# Tier 1 Logic: Emergency Quick-Release
if not organizer.registered_off_ramp_partner:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
status_code=status.HTTP_400_BAD_REQUEST,
detail="Unregistered off-ramp partner. Cannot route emergency funds."
)

capped_amount = min(request.requested_amount, request.total_pool_balance * EMERGENCY_RELEASE_CAP_PERCENT)
organizer.receipt_state = ReceiptState.PENDING

return FundReleaseResponse(
status="APPROVED",
tier=1,
released_amount=capped_amount,
message=f"Emergency funds routed to {organizer.registered_off_ramp_partner}. Post-expenditure receipt required.",
account_updates={
"receipt_state": organizer.receipt_state.value,
"receipt_state": organizer.receipt_state.value,
"credibility_score": organizer.credibility_score
}
)
else:
# Tier 2 Logic: Milestone-Based Capital
if not request.current_milestone_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
status_code=status.HTTP_400_BAD_REQUEST,
detail="Milestone ID required for Tier 2 capital release."
)

milestone = milestones_db.get(request.current_milestone_id)
if not milestone:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Milestone tracking not found.")

if milestone.state != MilestoneState.VERIFIED:
raise HTTPException(
status_code=status.HTTP_423_LOCKED,
status_code=status.HTTP_423_LOCKED,
detail="Programmatic lock enforced: Preceding milestone proof of completion not explicitly verified."
)

allowed_release = min(request.requested_amount, milestone.amount, request.total_pool_balance)

return FundReleaseResponse(
status="APPROVED",
tier=2,
Expand All @@ -127,8 +127,8 @@ async def milestone_validation_hook(milestone_id: str, proof_url: str):
milestone = milestones_db.get(milestone_id)
if not milestone:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Milestone not found.")

milestone.proof_of_completion = proof_url
milestone.state = MilestoneState.VERIFIED

return {"status": "success", "message": f"Milestone {milestone_id} explicitly verified. Subsequent payouts unlocked."}
1 change: 0 additions & 1 deletion backend/app/api/v1/onchain_certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from __future__ import annotations

import uuid
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
Expand Down
1 change: 0 additions & 1 deletion backend/app/api/v1/volunteer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
VolunteerSignup,
VolunteerCategory,
OpportunityStatus,
SignupStatus,
)

router = APIRouter(prefix="/volunteer", tags=["volunteer"])
Expand Down
1 change: 0 additions & 1 deletion backend/app/certificates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def generate_certificate_pdf(

emerald = HexColor("#10B891")
navy = HexColor("#0F172A")
gold = HexColor("#F5B923")
text_color = HexColor("#4B5563")

title_style = ParagraphStyle(
Expand Down
17 changes: 5 additions & 12 deletions backend/app/certificates/onchain_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
generate_svg_certificate,
get_certificate_hash,
)
from app.storage.svg_s3 import (
upload_svg_certificate,
upload_certificate_html_wrapper,
StoredSVGCertificate,
)


class OnChainCertificateService:
Expand Down Expand Up @@ -86,20 +81,18 @@ async def generate_and_store_certificate(

svg_hash = get_certificate_hash(svg_content)

# Step 3: Generate HTML wrapper with embedded SVG for social previews
# Step 3 and 4: Upload to public S3 bucket (async)
# We no longer need the PNG fallback wrapper since the new generator handles HTML
svg_stored = await upload_svg_certificate(svg_content, donation_id)
html_s3_url = await upload_certificate_html_wrapper(svg_content, donation_id)
# Step 3: Persist only metadata in DB; no external object storage required.
svg_public_url = ""
html_public_url = ""

# Step 5: Compute certificate integrity hash (for future merkle proofs)
cert_integrity_hash = hashlib.sha256(
f"{svg_hash}{stellar_tx_hash}{stellar_ledger}".encode()
).hexdigest()

return {
"svg_s3_url": svg_stored.s3_url,
"html_s3_url": html_s3_url,
"svg_s3_url": svg_public_url,
"html_s3_url": html_public_url,
"svg_hash": svg_hash,
"verified": True,
"tx_verified": tx_verified,
Expand Down
1 change: 0 additions & 1 deletion backend/app/certificates/png_hydrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from __future__ import annotations

import logging
import textwrap
import time
from io import BytesIO
from datetime import datetime
Expand Down
4 changes: 1 addition & 3 deletions backend/app/certificates/service.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Certificate creation service - orchestrates certificate generation and storage."""
from __future__ import annotations

import hashlib
import logging
import uuid
from typing import Optional

from sqlalchemy.ext.asyncio import AsyncSession
Expand All @@ -15,8 +15,6 @@
from app.models.user import User
from app.models.provenance import ProvenanceRecord
from app.models.campaign_drive import CampaignDrive
from app.certificates.generator import generate_certificate_pdf
from app.storage.s3 import upload_certificate_pdf

logger = logging.getLogger(__name__)

Expand Down
4 changes: 1 addition & 3 deletions backend/app/certificates/svg_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,8 +771,6 @@ def generate_svg_certificate(
merkle_proof=merkle_proof,
onchain_hash=onchain_hash,
)
html_escaped = html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")

svg = f"""<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 800 1100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
Expand Down Expand Up @@ -812,4 +810,4 @@ def generate_svg_certificate(

cert_hash = get_certificate_hash(sample)
print(f"Certificate written to: {output_path}")
print(f"SHA-256 integrity hash: {cert_hash}")
print(f"SHA-256 integrity hash: {cert_hash}")
1 change: 0 additions & 1 deletion backend/app/storage/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import hashlib
from dataclasses import dataclass
from datetime import timedelta

import boto3
from botocore.exceptions import ClientError
Expand Down
Loading