[image-service] first commit
This commit is contained in:
commit
88351993cf
6 changed files with 153 additions and 0 deletions
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV TZ=Asia/Bangkok
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
16
README.md
Normal file
16
README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Image service
|
||||||
|
|
||||||
|
Image management enapoint:
|
||||||
|
|
||||||
|
## Upload Images
|
||||||
|
### POST /image/{folder}/upload
|
||||||
|
```
|
||||||
|
curl -X POST http://localhost/image/page_drink_n/upload \
|
||||||
|
-F "files=@img1.png" \
|
||||||
|
-F "files=@img2.png"
|
||||||
|
```
|
||||||
|
### POST /inter/{country}/image/{folder}/upload
|
||||||
|
```
|
||||||
|
curl -X POST http://localhost/inter/tha/image/page_drink_disable_n2/upload \
|
||||||
|
-F "files=@bn_hot_america_no.png"
|
||||||
|
```
|
||||||
24
docker-compose.yml
Normal file
24
docker-compose.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: taobin-image-container
|
||||||
|
ports:
|
||||||
|
- "8125:8125"
|
||||||
|
environment:
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
volumes:
|
||||||
|
- ~/repo/taobin_project:/taobin_project
|
||||||
|
restart: always
|
||||||
|
command: uvicorn main:app --host 0.0.0.0 --port 8125
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:latest
|
||||||
|
container_name: nginx-image-container
|
||||||
|
volumes:
|
||||||
|
- ~/repo/taobin_project:/taobin_project
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
ports:
|
||||||
|
- "8126:80"
|
||||||
|
|
||||||
80
main.py
Normal file
80
main.py
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
from fastapi import FastAPI, UploadFile, File, HTTPException, Query
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
import shutil
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
BASE_DIR = Path("/taobin_project")
|
||||||
|
|
||||||
|
ALLOWED_FOLDERS = {"page_drink_n", "page_drink_press_n", "page_drink_disable_n2"}
|
||||||
|
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||||
|
|
||||||
|
def validate_folder(folder: str):
|
||||||
|
if folder not in ALLOWED_FOLDERS:
|
||||||
|
raise HTTPException(400, f"folder must be {ALLOWED_FOLDERS}")
|
||||||
|
|
||||||
|
def validate_ext(filename: str):
|
||||||
|
ext = Path(filename).suffix.lower()
|
||||||
|
if ext not in ALLOWED_EXTENSIONS:
|
||||||
|
raise HTTPException(400, f"file not allow {ext}")
|
||||||
|
return ext
|
||||||
|
|
||||||
|
def get_image_dir(folder: str, country: Optional[str] = None) -> Path:
|
||||||
|
"""
|
||||||
|
no country → /taobin_project/image/{folder}/
|
||||||
|
has country → /taobin_project/inter/{country}/image/{folder}/
|
||||||
|
"""
|
||||||
|
if country:
|
||||||
|
path = BASE_DIR / "inter" / country / "image" / folder
|
||||||
|
else:
|
||||||
|
path = BASE_DIR / "image" / folder
|
||||||
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────
|
||||||
|
# UPLOAD
|
||||||
|
# ─────────────────────────────────────────
|
||||||
|
|
||||||
|
@app.post("/image/{folder}/upload")
|
||||||
|
async def upload_images(
|
||||||
|
folder: str,
|
||||||
|
files: list[UploadFile] = File(...)
|
||||||
|
):
|
||||||
|
validate_folder(folder)
|
||||||
|
saved = []
|
||||||
|
for file in files:
|
||||||
|
ext = validate_ext(file.filename)
|
||||||
|
filename = Path(file.filename).name
|
||||||
|
dest = get_image_dir(folder) / filename
|
||||||
|
with open(dest, "wb") as f:
|
||||||
|
shutil.copyfileobj(file.file, f)
|
||||||
|
saved.append({
|
||||||
|
"filename": filename,
|
||||||
|
"url": f"/image/{folder}/{filename}"
|
||||||
|
})
|
||||||
|
return {"uploaded": saved}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/inter/{country}/image/{folder}/upload")
|
||||||
|
async def upload_inter_images(
|
||||||
|
country: str,
|
||||||
|
folder: str,
|
||||||
|
files: list[UploadFile] = File(...)
|
||||||
|
):
|
||||||
|
validate_folder(folder)
|
||||||
|
saved = []
|
||||||
|
for file in files:
|
||||||
|
ext = validate_ext(file.filename)
|
||||||
|
filename = Path(file.filename).name
|
||||||
|
dest = get_image_dir(folder, country) / filename
|
||||||
|
with open(dest, "wb") as f:
|
||||||
|
shutil.copyfileobj(file.file, f)
|
||||||
|
saved.append({
|
||||||
|
"filename": filename,
|
||||||
|
"url": f"/inter/{country}/image/{folder}/{filename}"
|
||||||
|
})
|
||||||
|
return {"uploaded": saved}
|
||||||
18
nginx.conf
Normal file
18
nginx.conf
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
# /image/{folder}/...
|
||||||
|
location ~ ^/image/(page_drink_n|page_drink_press_n|page_drink_disable_n2)/(.*)$ {
|
||||||
|
alias /taobin_project/image/$1/$2;
|
||||||
|
}
|
||||||
|
|
||||||
|
# /inter/{country}/image/{folder}/...
|
||||||
|
location ~ ^/inter/([^/]+)/image/(page_drink_n|page_drink_press_n|page_drink_disable_n2)/(.*)$ {
|
||||||
|
alias /taobin_project/inter/$1/image/$2/$3;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block any path
|
||||||
|
location / {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
python-multipart
|
||||||
Loading…
Add table
Add a link
Reference in a new issue