first commit

This commit is contained in:
AGX 2025-07-17 10:16:31 +09:00
commit fd6815f11a
18 changed files with 281 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
bin/
include/
lib/
lib64/
share/
pyvenv.cfg

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY ./app /app/app
EXPOSE 7860
EXPOSE 5555
# 컨테이너 실행시 아무 것도 안 띄움 (docker-compose에서 실행)
CMD ["bash"]

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# FastAPI + Celery + Redis + Flower 메인서버 예시
## 실행 방법
```bash
docker-compose up --build
FastAPI: http://localhost:7860
Flower: http://localhost:5555
API 예시
POST /process (파일 업로드)
GET /result/{task_id} (결과 확인)
---

0
app/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

11
app/beat_schedule.py Normal file
View File

@ -0,0 +1,11 @@
# app/beat_schedule.py
from celery.schedules import crontab
beat_schedule = {
"delete-old-files-every-10-mins": {
"task": "app.tasks.delete_old_files",
"schedule": crontab(minute="*/10"),
"args": ("/app/images", 1800), # 30분 이상된 파일 삭제
}
}
# celery_app.conf.beat_schedule = beat_schedule

22
app/celery_worker.py Normal file
View File

@ -0,0 +1,22 @@
from celery import Celery
import os
# Redis URL 설정
redis_url = os.getenv("REDIS_URL", "redis://redis:6379/0")
# Celery 앱 생성
celery_app = Celery(
"worker",
broker=redis_url,
backend=redis_url,
include=['app.tasks'] # 태스크 모듈 포함
)
# Celery 설정
celery_app.conf.update(
task_serializer='json',
accept_content=['json'],
result_serializer='json',
timezone='Asia/Seoul',
enable_utc=True,
)

106
app/main.py Normal file
View File

@ -0,0 +1,106 @@
from fastapi import FastAPI, Request, HTTPException, Response, UploadFile, File, Form
from pydantic import BaseModel, Field
from typing import Dict, Optional, List
from app.celery_worker import celery_app
from app.supabase_auth import check_user_permission
from celery.result import AsyncResult
import time
import os, shutil
import uuid
import logging
# 로거 설정
logger = logging.getLogger(__name__)
app = FastAPI()
# ==== 입력 모델 ====
class TranslateRequest(BaseModel):
toggle_states: Dict
unwanted_texts: Dict
image_data: str
user_id: str
ocr_method: Optional[str] = "paddleocr"
inpaint_method: Optional[str] = "lama"
class InpaintRequest(BaseModel):
mask_image_data: str
image_data: str
user_id: str
inpaint_method: Optional[str] = "lama"
class OCRRequest(BaseModel):
image_data: str
user_id: str
ocr_method: Optional[str] = "paddleocr"
# ==== 리턴 모델 ====
class OCRBox(BaseModel):
text: str
box: List[int] # [x1, y1, x2, y2, ...]
class TranslateResponse(BaseModel):
ocr_texts: List[str]
ocr_boxes: List[OCRBox]
translated_texts: List[str]
inpainted_image: str # base64
class InpaintResponse(BaseModel):
inpainted_image: str # base64
class OCRResponse(BaseModel):
ocr_texts: List[str]
ocr_boxes: List[OCRBox]
# ==== 사용자 인증 ====
async def validate_user(user_id):
allowed = await check_user_permission(user_id)
if not allowed:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
# ==== 셀러리 태스크 ====
def start_celery_task(task_name, **kwargs):
return celery_app.send_task(task_name, kwargs=kwargs)
# ==== 엔드포인트 ====
@app.post("/translate_me")
async def translate_me(req: TranslateRequest):
await validate_user(req.user_id)
filename = f"{uuid.uuid4().hex}_{int(time.time())}.png"
# (여기서 파일 경로 생성 및 저장)
task = start_celery_task("app.tasks.translate_task", **req.dict(), filename=filename)
logger.info(f"태스크 등록: {task.id}, 파일명: {filename}")
return {"task_id": task.id, "filename": filename}
@app.post("/inpaint_me")
async def inpaint_me(req: InpaintRequest):
await validate_user(req.user_id)
task = start_celery_task("app.tasks.inpaint_task", **req.dict())
return {"task_id": task.id}
@app.post("/ocr_me")
async def ocr_me(req: OCRRequest):
await validate_user(req.user_id)
task = start_celery_task("app.tasks.ocr_task", **req.dict())
return {"task_id": task.id}
@app.post("/upload_image")
async def upload_image(user_id: str = Form(...), file: UploadFile = File(...)):
original_dir = f"images/{user_id}/original/"
os.makedirs(original_dir, exist_ok=True)
filename = file.filename
file_path = os.path.join(original_dir, filename)
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
url = f"/images/{user_id}/original/{filename}"
return {"url": url}
@app.get("/download_image/")
def download_image(user_id: str, filename: str):
file_path = f"images/{user_id}/translated/{filename}"
if not os.path.exists(file_path):
return {"error": "파일이 없습니다"}
with open(file_path, "rb") as f:
data = f.read()
os.remove(file_path) # 다운로드 직후 삭제
return Response(content=data, media_type="image/png")

18
app/supabase_auth.py Normal file
View File

@ -0,0 +1,18 @@
from supabase import create_client
import os
SUPABASE_URL = 'http://146.56.101.199:8000'
SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE'
#SUPABASE_URL = os.getenv("SUPABASE_URL")
#SUPABASE_KEY = os.getenv("SUPABASE_KEY")
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
async def check_user_permission(user_id: str) -> bool:
# 실제 쿼리 구조는 supabase 테이블에 맞게!
user = supabase.table("users").select("id, membership_level").eq("id", user_id).execute()
print(f"user info : {user}")
if user.data and user.data[0]["membership_level"] in ["premiun", "vip"]:
return True
return False

32
app/tasks.py Normal file
View File

@ -0,0 +1,32 @@
from app.celery_worker import celery_app
@celery_app.task(name="app.tasks.translate_task")
def translate_task(**kwargs):
# 실제 번역 처리 로직
return {
"ocr_texts": ["중국어1", "중국어2"],
"ocr_boxes": [
{"text": "중국어1", "box": [10, 20, 100, 120]},
{"text": "중국어2", "box": [110, 120, 200, 220]}
],
"translated_texts": ["한글1", "한글2"],
"inpainted_image": "base64string...."
}
@celery_app.task(name="app.tasks.inpaint_task")
def inpaint_task(**kwargs):
# 실제 인페인팅 처리 로직
return {
"inpainted_image": "base64string...."
}
@celery_app.task(name="app.tasks.ocr_task")
def ocr_task(**kwargs):
# 실제 OCR 처리 로직
return {
"ocr_texts": ["중국어1", "중국어2"],
"ocr_boxes": [
{"text": "중국어1", "box": [10, 20, 100, 120]},
{"text": "중국어2", "box": [110, 120, 200, 220]}
]
}

BIN
docker-compose Executable file

Binary file not shown.

45
docker-compose.yml Normal file
View File

@ -0,0 +1,45 @@
version: "3.8"
services:
fastapi:
build: .
container_name: fastapi_app
command: uvicorn app.main:app --host 0.0.0.0 --port 7890
volumes:
- ./app:/app/app
- ./images:/app/images
ports:
- "7890:7890"
depends_on:
- redis
celery:
build: .
container_name: celery_worker
command: celery -A app.celery_worker worker --loglevel=info --concurrency=2
volumes:
- ./app:/app/app
depends_on:
- redis
celery-beat:
build: .
container_name: celery_beat
command: celery -A app.celery_worker beat --loglevel=info
volumes:
- ./app:/app/app
- ./images:/app/images
depends_on:
- redis
redis:
image: redis:6.2
container_name: redis_server
ports:
- "6379:6379"
flower:
build: .
container_name: flower_monitor
command: celery -A app.celery_worker flower --port=5555
ports:
- "5555:5555"
depends_on:
- redis

1
lib64 Symbolic link
View File

@ -0,0 +1 @@
lib

9
requirements.txt Normal file
View File

@ -0,0 +1,9 @@
fastapi
uvicorn[standard]
celery[redis]
redis
flower
python-dotenv
pillow
supabase
python-multipart