
Features implemented: - User authentication with JWT tokens and role-based access (developer/buyer) - Blockchain wallet linking and management with Ethereum integration - Carbon project creation and management for developers - Marketplace for browsing and purchasing carbon offsets - Transaction tracking with blockchain integration - Database models for users, projects, offsets, and transactions - Comprehensive API with authentication, wallet, project, and trading endpoints - Health check endpoint and platform information - SQLite database with Alembic migrations - Full API documentation with OpenAPI/Swagger Technical stack: - FastAPI with Python - SQLAlchemy ORM with SQLite - Web3.py for blockchain integration - JWT authentication with bcrypt - CORS enabled for frontend integration - Comprehensive error handling and validation Environment variables required: - SECRET_KEY (JWT secret) - BLOCKCHAIN_RPC_URL (optional, defaults to localhost)
175 lines
5.0 KiB
Python
175 lines
5.0 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import desc
|
|
from typing import Optional
|
|
from app.db.session import get_db
|
|
from app.core.deps import get_current_user, get_current_developer
|
|
from app.models.user import User
|
|
from app.models.carbon_project import CarbonProject
|
|
from app.models.carbon_offset import CarbonOffset
|
|
from app.schemas.carbon_project import (
|
|
CarbonProjectCreate,
|
|
CarbonProjectResponse,
|
|
CarbonProjectUpdate,
|
|
CarbonProjectListResponse
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
@router.post("/", response_model=CarbonProjectResponse)
|
|
def create_project(
|
|
project: CarbonProjectCreate,
|
|
current_user: User = Depends(get_current_developer),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new carbon offset project (Developer only)"""
|
|
|
|
# Validate project dates
|
|
if project.start_date >= project.end_date:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Start date must be before end date"
|
|
)
|
|
|
|
# Create project
|
|
db_project = CarbonProject(
|
|
**project.dict(),
|
|
developer_id=current_user.id
|
|
)
|
|
|
|
db.add(db_project)
|
|
db.commit()
|
|
db.refresh(db_project)
|
|
|
|
# Create initial carbon offsets
|
|
db_offset = CarbonOffset(
|
|
serial_number=f"CO{db_project.id}-{project.total_credits_available}",
|
|
vintage_year=project.start_date.year,
|
|
quantity=project.total_credits_available,
|
|
project_id=db_project.id
|
|
)
|
|
|
|
db.add(db_offset)
|
|
db.commit()
|
|
|
|
return db_project
|
|
|
|
@router.get("/", response_model=CarbonProjectListResponse)
|
|
def list_projects(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(10, ge=1, le=100),
|
|
project_type: Optional[str] = None,
|
|
verification_status: Optional[str] = None,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""List all active carbon offset projects"""
|
|
|
|
query = db.query(CarbonProject).filter(CarbonProject.is_active == True)
|
|
|
|
if project_type:
|
|
query = query.filter(CarbonProject.project_type == project_type)
|
|
|
|
if verification_status:
|
|
query = query.filter(CarbonProject.verification_status == verification_status)
|
|
|
|
# Get total count
|
|
total = query.count()
|
|
|
|
# Apply pagination
|
|
projects = query.order_by(desc(CarbonProject.created_at)).offset(
|
|
(page - 1) * page_size
|
|
).limit(page_size).all()
|
|
|
|
return CarbonProjectListResponse(
|
|
projects=projects,
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size
|
|
)
|
|
|
|
@router.get("/my-projects", response_model=CarbonProjectListResponse)
|
|
def get_my_projects(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(10, ge=1, le=100),
|
|
current_user: User = Depends(get_current_developer),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get projects created by the current developer"""
|
|
|
|
query = db.query(CarbonProject).filter(CarbonProject.developer_id == current_user.id)
|
|
|
|
total = query.count()
|
|
|
|
projects = query.order_by(desc(CarbonProject.created_at)).offset(
|
|
(page - 1) * page_size
|
|
).limit(page_size).all()
|
|
|
|
return CarbonProjectListResponse(
|
|
projects=projects,
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size
|
|
)
|
|
|
|
@router.get("/{project_id}", response_model=CarbonProjectResponse)
|
|
def get_project(project_id: int, db: Session = Depends(get_db)):
|
|
"""Get a specific project by ID"""
|
|
|
|
project = db.query(CarbonProject).filter(
|
|
CarbonProject.id == project_id,
|
|
CarbonProject.is_active == True
|
|
).first()
|
|
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
return project
|
|
|
|
@router.put("/{project_id}", response_model=CarbonProjectResponse)
|
|
def update_project(
|
|
project_id: int,
|
|
project_update: CarbonProjectUpdate,
|
|
current_user: User = Depends(get_current_developer),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update a project (Developer only - own projects)"""
|
|
|
|
project = db.query(CarbonProject).filter(
|
|
CarbonProject.id == project_id,
|
|
CarbonProject.developer_id == current_user.id
|
|
).first()
|
|
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
# Update project fields
|
|
update_data = project_update.dict(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(project, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(project)
|
|
|
|
return project
|
|
|
|
@router.delete("/{project_id}")
|
|
def delete_project(
|
|
project_id: int,
|
|
current_user: User = Depends(get_current_developer),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Delete a project (Developer only - own projects)"""
|
|
|
|
project = db.query(CarbonProject).filter(
|
|
CarbonProject.id == project_id,
|
|
CarbonProject.developer_id == current_user.id
|
|
).first()
|
|
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
# Soft delete - mark as inactive
|
|
project.is_active = False
|
|
db.commit()
|
|
|
|
return {"message": "Project deleted successfully"} |