Skip to content
Open
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
2 changes: 1 addition & 1 deletion cli/macrostrat/cli/database/migrations/people/main.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ INSERT INTO ecosystem.roles (name, description) VALUES
('Researcher', 'Conducts academic or applied research'),
('Developer', 'Writes and maintains software code'),
('Leader', 'Leads research or development projects and mentors others'),
('Collaborator', 'Contributes to joint projects'),
('Collaborator', 'Contributes to joint projects')
ON CONFLICT (name) DO NOTHING;
8 changes: 8 additions & 0 deletions services/api-v3/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ access_key=<S3>
secret_key=<S3>

ENVIRONMENT=development

# image upload
S3_ENDPOINT=''
S3_BUCKET=''
S3_PATH=''

S3_ACCESS_KEY=''
S3_SECRET_KEY=''
2 changes: 2 additions & 0 deletions services/api-v3/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import api.routes.security
from api.database import connect_engine, dispose_engine
from api.routes.image_upload import router as image_upload_router
from api.routes.ingest import router as ingest_router
from api.routes.object import router as object_router
from api.routes.sources import router as sources_router
Expand Down Expand Up @@ -46,6 +47,7 @@ async def setup_engine(a: FastAPI):
app.include_router(object_router)
app.include_router(ingest_router)
app.include_router(sources_router)
app.include_router(image_upload_router)

if __name__ == "__main__":
uvicorn.run(
Expand Down
61 changes: 61 additions & 0 deletions services/api-v3/api/routes/image_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import logging
import os
import uuid

import boto3
from botocore.client import Config
from dotenv import load_dotenv
from fastapi import APIRouter, File, HTTPException, UploadFile

router = APIRouter(
prefix="/image_upload",
tags=["s3"],
responses={404: {"description": "Image upload not found"}},
)

load_dotenv()

S3_ENDPOINT = os.getenv("S3_ENDPOINT")
S3_BUCKET = os.getenv("S3_BUCKET")
S3_PATH = os.getenv("S3_PATH", "")
S3_ACCESS_KEY = os.getenv("S3_ACCESS_KEY")
S3_SECRET_KEY = os.getenv("S3_SECRET_KEY")

s3_client = boto3.client(
"s3",
endpoint_url=S3_ENDPOINT,
aws_access_key_id=S3_ACCESS_KEY,
aws_secret_access_key=S3_SECRET_KEY,
config=Config(
request_checksum_calculation="WHEN_REQUIRED",
response_checksum_validation="WHEN_REQUIRED",
),
)


@router.post("")
async def upload_file(file: UploadFile = File(...)):
try:
file_content = await file.read()

if not file_content:
raise HTTPException(status_code=400, detail="File is empty")

new_filename = f"{uuid.uuid4()}-{file.filename}"
s3_key = f"{S3_PATH.strip('/')}/{new_filename}" if S3_PATH else new_filename

# Upload the file
s3_client.put_object(
Bucket=S3_BUCKET,
Key=s3_key,
Body=file_content,
ContentType=file.content_type or "application/octet-stream",
ContentLength=len(file_content),
)

file_url = f"{S3_ENDPOINT}/{S3_BUCKET}/{s3_key}"

return {"filename": new_filename, "url": file_url}

except Exception as e:
raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
Loading