Files
ubuntu-post-install/paintplus/backend/app/routers/projects.py
T
Claude 084922afaa paintplus: vendor the app source and rename from EditmaskwithAI
Bring the full EditmaskwithAI application into the repo under paintplus/
(429 files) so the service is self-contained — the installer copies the
vendored source to ~/docker/paintplus/src instead of cloning at runtime.

Rename to PaintPlus (service + branding; app logic untouched):
- services/editmaskwithai.sh -> services/paintplus.sh (register_service
  paintplus, install_paintplus, ~/docker/paintplus, Caddy paintplus:8000,
  Authelia option preserved)
- container names -> paintplus across docker-compose*.yml; dev network
  -> paintplus-network
- browser <title> -> "PaintPlus - AI Image Editor"; README heading ->
  PaintPlus with upstream provenance note
- README utilities table: editmaskwithai -> paintplus

Backend/frontend code (help strings referencing the old container name,
the ai_photo_edit.db filename) is intentionally left as-is to avoid
touching application logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
2026-06-26 05:48:43 +00:00

145 lines
4.1 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, status
from sqlalchemy.orm import Session
from typing import List
import shutil
from pathlib import Path
from PIL import Image
from app.database import get_db
from app.models.project import Project
from app.models.edit import Edit
from app.schemas import ProjectCreate, ProjectResponse, EditResponse, UploadResponse
from app.services.edit_service import EditService
from app.config import settings
router = APIRouter(prefix="/projects", tags=["projects"])
@router.post("/", response_model=ProjectResponse)
def create_project(
project: ProjectCreate,
db: Session = Depends(get_db)
):
"""Create a new project"""
# For MVP, we'll use a default user_id of 1
# In production, this would come from authentication
user_id = 1
db_project = Project(
user_id=user_id,
name=project.name
)
db.add(db_project)
db.commit()
db.refresh(db_project)
# Create project directory
edit_service = EditService()
edit_service.ensure_project_dir(db_project.id)
return db_project
@router.get("/", response_model=List[ProjectResponse])
def list_projects(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
"""List all projects"""
projects = db.query(Project).offset(skip).limit(limit).all()
return projects
@router.get("/{project_id}", response_model=ProjectResponse)
def get_project(
project_id: int,
db: Session = Depends(get_db)
):
"""Get a specific project"""
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return project
@router.delete("/{project_id}")
def delete_project(
project_id: int,
db: Session = Depends(get_db)
):
"""Delete a project"""
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# Delete project directory
edit_service = EditService()
project_dir = edit_service.get_project_dir(project_id)
if project_dir.exists():
shutil.rmtree(project_dir)
db.delete(project)
db.commit()
return {"status": "success", "message": f"Project {project_id} deleted"}
@router.post("/{project_id}/upload", response_model=UploadResponse)
async def upload_image(
project_id: int,
file: UploadFile = File(...),
db: Session = Depends(get_db)
):
"""Upload an image to a project"""
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# Validate file type
if not file.content_type.startswith('image/'):
raise HTTPException(status_code=400, detail="File must be an image")
# Create project directory
edit_service = EditService()
edit_service.ensure_project_dir(project_id)
# Save original and current images
original_path = edit_service.get_original_image_path(project_id)
current_path = edit_service.get_current_image_path(project_id)
# Read and validate image
contents = await file.read()
try:
image = Image.open(BytesIO(contents))
image = image.convert('RGBA')
# Save images
image.save(original_path, 'PNG')
image.save(current_path, 'PNG')
except Exception as e:
raise HTTPException(status_code=400, detail=f"Invalid image file: {str(e)}")
return UploadResponse(
project_id=project_id,
original_url=f"/projects/{project_id}/original",
current_url=f"/projects/{project_id}/current"
)
@router.get("/{project_id}/edits", response_model=List[EditResponse])
def list_edits(
project_id: int,
db: Session = Depends(get_db)
):
"""List all edits for a project"""
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
edits = db.query(Edit).filter(Edit.project_id == project_id).order_by(Edit.created_at.desc()).all()
return edits
from io import BytesIO