Spaces:
Sleeping
Sleeping
Commit ·
6fc0261
1
Parent(s): 2d94b24
jwt
Browse files- .env +7 -3
- app/api/v1/__pycache__/auth_routes.cpython-313.pyc +0 -0
- app/api/v1/auth_routes.py +30 -0
- app/api/v1/media_routes.py +9 -5
- app/app.py +4 -2
- app/dependencies/__pycache__/auth.cpython-313.pyc +0 -0
- app/dependencies/auth.py +21 -0
- app/services/media_service.py +27 -5
- app/utils/__pycache__/compress.cpython-313.pyc +0 -0
- app/utils/__pycache__/jwt.cpython-313.pyc +0 -0
- app/utils/compress.py +24 -0
- app/utils/jwt.py +11 -0
- requirements.txt +3 -1
- settings.py +4 -3
.env
CHANGED
|
@@ -1,6 +1,10 @@
|
|
| 1 |
WASABI_BUCKET=bloom-associates
|
| 2 |
-
AWS_ACCESS_KEY_ID=
|
| 3 |
-
AWS_SECRET_ACCESS_KEY=
|
| 4 |
REGION_NAME=ap-southeast-1
|
| 5 |
-
ENDPOINT_URL=https://s3.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
|
|
|
| 1 |
WASABI_BUCKET=bloom-associates
|
| 2 |
+
AWS_ACCESS_KEY_ID=DPSE24FU9UCACAXMYSFO
|
| 3 |
+
AWS_SECRET_ACCESS_KEY=l4JymOwzqTB6DozzWyrXw62ah0zD6BznCj9Lp3Lf
|
| 4 |
REGION_NAME=ap-southeast-1
|
| 5 |
+
ENDPOINT_URL=https://s3.ap-southeast-1.wasabisys.com
|
| 6 |
+
SECRET_KEY=6a2b3c4d5e6f7g8h9i0j
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
|
| 10 |
|
app/api/v1/__pycache__/auth_routes.cpython-313.pyc
ADDED
|
Binary file (1.55 kB). View file
|
|
|
app/api/v1/auth_routes.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException, status, Depends, Form
|
| 2 |
+
from jose import jwt
|
| 3 |
+
from datetime import datetime, timedelta
|
| 4 |
+
from settings import SECRET_KEY, ALGORITHM
|
| 5 |
+
|
| 6 |
+
router = APIRouter(prefix="/auth", tags=["Authentication"])
|
| 7 |
+
|
| 8 |
+
# Hardcoded test user
|
| 9 |
+
USERS_DB = {
|
| 10 |
+
"admin@example.com": {
|
| 11 |
+
"password": "admin123",
|
| 12 |
+
"merchant_id": "MERCHANT001",
|
| 13 |
+
"user_id": "admin"
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
@router.post("/login")
|
| 18 |
+
def login(username: str = Form(...), password: str = Form(...)):
|
| 19 |
+
user = USERS_DB.get(username)
|
| 20 |
+
if not user or user["password"] != password:
|
| 21 |
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
| 22 |
+
|
| 23 |
+
token_data = {
|
| 24 |
+
"sub": user["user_id"],
|
| 25 |
+
"merchant_id": user["merchant_id"],
|
| 26 |
+
"exp": datetime.utcnow() + timedelta(hours=2)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
access_token = jwt.encode(token_data, SECRET_KEY, algorithm=ALGORITHM)
|
| 30 |
+
return {"access_token": access_token, "token_type": "bearer"}
|
app/api/v1/media_routes.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
| 1 |
-
from fastapi import APIRouter, UploadFile, File,
|
| 2 |
from app.services.media_service import upload_file_to_s3, generate_presigned_url, delete_s3_object
|
|
|
|
| 3 |
|
| 4 |
router = APIRouter(prefix="/media", tags=["Media"])
|
| 5 |
|
|
|
|
| 6 |
@router.post("/upload")
|
| 7 |
-
async def
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
|
| 12 |
@router.get("/presigned-url")
|
| 13 |
async def get_url(key: str):
|
|
|
|
| 1 |
+
from fastapi import APIRouter, UploadFile, File, Depends
|
| 2 |
from app.services.media_service import upload_file_to_s3, generate_presigned_url, delete_s3_object
|
| 3 |
+
from app.dependencies.auth import get_current_user
|
| 4 |
|
| 5 |
router = APIRouter(prefix="/media", tags=["Media"])
|
| 6 |
|
| 7 |
+
|
| 8 |
@router.post("/upload")
|
| 9 |
+
async def upload_file(
|
| 10 |
+
file: UploadFile = File(...),
|
| 11 |
+
current_user: dict = Depends(get_current_user)
|
| 12 |
+
):
|
| 13 |
+
merchant_id = current_user["merchant_id"]
|
| 14 |
+
return upload_file_to_s3(file, tenant_id=merchant_id)
|
| 15 |
|
| 16 |
@router.get("/presigned-url")
|
| 17 |
async def get_url(key: str):
|
app/app.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
-
from app.api.v1 import media_routes
|
| 3 |
|
| 4 |
app = FastAPI(title="Insightfy Media Service")
|
| 5 |
|
| 6 |
-
app.include_router(media_routes.router)
|
|
|
|
|
|
|
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
+
from app.api.v1 import media_routes, auth_routes
|
| 3 |
|
| 4 |
app = FastAPI(title="Insightfy Media Service")
|
| 5 |
|
| 6 |
+
app.include_router(media_routes.router)
|
| 7 |
+
|
| 8 |
+
app.include_router(auth_routes.router)
|
app/dependencies/__pycache__/auth.cpython-313.pyc
ADDED
|
Binary file (1.12 kB). View file
|
|
|
app/dependencies/auth.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import Depends
|
| 2 |
+
from fastapi.security import OAuth2PasswordBearer
|
| 3 |
+
from app.utils.jwt import decode_jwt_token
|
| 4 |
+
from settings import SECRET_KEY, ALGORITHM
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
|
| 8 |
+
|
| 9 |
+
def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
|
| 10 |
+
try:
|
| 11 |
+
payload = decode_jwt_token(token)
|
| 12 |
+
return {
|
| 13 |
+
"user_id": payload["sub"],
|
| 14 |
+
"merchant_id": payload["merchant_id"],
|
| 15 |
+
"role": payload.get("role", "user")
|
| 16 |
+
}
|
| 17 |
+
except Exception:
|
| 18 |
+
raise HTTPException(
|
| 19 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 20 |
+
detail="Invalid authentication credentials",
|
| 21 |
+
)
|
app/services/media_service.py
CHANGED
|
@@ -1,14 +1,36 @@
|
|
| 1 |
import boto3
|
|
|
|
|
|
|
| 2 |
from fastapi import UploadFile
|
| 3 |
from settings import WASABI_BUCKET, WASABI_CONFIG
|
| 4 |
-
import
|
| 5 |
|
| 6 |
s3 = boto3.client("s3", **WASABI_CONFIG)
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def generate_presigned_url(key: str, expiration: int = 3600):
|
| 14 |
return s3.generate_presigned_url(
|
|
|
|
| 1 |
import boto3
|
| 2 |
+
import uuid
|
| 3 |
+
|
| 4 |
from fastapi import UploadFile
|
| 5 |
from settings import WASABI_BUCKET, WASABI_CONFIG
|
| 6 |
+
from app.utils.compress import optimize_image, generate_thumbnail
|
| 7 |
|
| 8 |
s3 = boto3.client("s3", **WASABI_CONFIG)
|
| 9 |
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def upload_file_to_s3(file: UploadFile, tenant_id: str):
|
| 13 |
+
original_ext = os.path.splitext(file.filename)[-1] or ".jpg"
|
| 14 |
+
unique_id = str(uuid.uuid4())
|
| 15 |
+
|
| 16 |
+
# Optimize and upload main image
|
| 17 |
+
optimized_image = optimize_image(file)
|
| 18 |
+
key_image = f"{tenant_id}/uploads/{unique_id}{original_ext}"
|
| 19 |
+
s3.upload_fileobj(optimized_image, WASABI_BUCKET, key_image)
|
| 20 |
+
|
| 21 |
+
# Reset file and generate thumbnail
|
| 22 |
+
file.file.seek(0)
|
| 23 |
+
thumbnail = generate_thumbnail(file)
|
| 24 |
+
key_thumb = f"{tenant_id}/uploads/thumbnails/{unique_id}_thumb{original_ext}"
|
| 25 |
+
s3.upload_fileobj(thumbnail, WASABI_BUCKET, key_thumb)
|
| 26 |
+
|
| 27 |
+
return {
|
| 28 |
+
"key": key_image,
|
| 29 |
+
"url": generate_presigned_url(key_image),
|
| 30 |
+
"thumbnail_key": key_thumb,
|
| 31 |
+
"thumbnail_url": generate_presigned_url(key_thumb)
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
|
| 35 |
def generate_presigned_url(key: str, expiration: int = 3600):
|
| 36 |
return s3.generate_presigned_url(
|
app/utils/__pycache__/compress.cpython-313.pyc
ADDED
|
Binary file (1.44 kB). View file
|
|
|
app/utils/__pycache__/jwt.cpython-313.pyc
ADDED
|
Binary file (734 Bytes). View file
|
|
|
app/utils/compress.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PIL import Image
|
| 2 |
+
from io import BytesIO
|
| 3 |
+
from fastapi import UploadFile
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# 🔧 Optimize image (resize + compress)
|
| 7 |
+
def optimize_image(file: UploadFile, max_size=(1024, 1024), quality=75) -> BytesIO:
|
| 8 |
+
image = Image.open(file.file)
|
| 9 |
+
image = image.convert("RGB")
|
| 10 |
+
image.thumbnail(max_size)
|
| 11 |
+
output = BytesIO()
|
| 12 |
+
image.save(output, format="JPEG", quality=quality, optimize=True)
|
| 13 |
+
output.seek(0)
|
| 14 |
+
return output
|
| 15 |
+
|
| 16 |
+
# 🖼️ Generate thumbnail
|
| 17 |
+
def generate_thumbnail(file: UploadFile, size=(200, 200)) -> BytesIO:
|
| 18 |
+
image = Image.open(file.file)
|
| 19 |
+
image = image.convert("RGB")
|
| 20 |
+
image.thumbnail(size)
|
| 21 |
+
output = BytesIO()
|
| 22 |
+
image.save(output, format="JPEG", quality=70, optimize=True)
|
| 23 |
+
output.seek(0)
|
| 24 |
+
return output
|
app/utils/jwt.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from jose import jwt, JWTError
|
| 2 |
+
|
| 3 |
+
SECRET_KEY = "your-super-secret-key"
|
| 4 |
+
ALGORITHM = "HS256"
|
| 5 |
+
|
| 6 |
+
def decode_jwt_token(token: str) -> dict:
|
| 7 |
+
try:
|
| 8 |
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
| 9 |
+
return payload
|
| 10 |
+
except JWTError:
|
| 11 |
+
raise ValueError("Invalid token")
|
requirements.txt
CHANGED
|
@@ -2,4 +2,6 @@ fastapi
|
|
| 2 |
boto3
|
| 3 |
uvicorn
|
| 4 |
python-multipart
|
| 5 |
-
python-dotenv
|
|
|
|
|
|
|
|
|
| 2 |
boto3
|
| 3 |
uvicorn
|
| 4 |
python-multipart
|
| 5 |
+
python-dotenv
|
| 6 |
+
pillow
|
| 7 |
+
python-jose
|
settings.py
CHANGED
|
@@ -4,8 +4,6 @@ import os
|
|
| 4 |
|
| 5 |
load_dotenv()
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
#WASABI_BUCKET = "bloom-associates"
|
| 10 |
|
| 11 |
WASABI_BUCKET = os.getenv("WASABI_BUCKET", "bloom-associates")
|
|
@@ -17,4 +15,7 @@ WASABI_CONFIG = {
|
|
| 17 |
"region_name": os.getenv("REGION_NAME", "us-east-1"),
|
| 18 |
"endpoint_url": os.getenv("ENDPOINT_URL", "https://s3.us-east-1.wasabisys.com")
|
| 19 |
|
| 20 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
load_dotenv()
|
| 6 |
|
|
|
|
|
|
|
| 7 |
#WASABI_BUCKET = "bloom-associates"
|
| 8 |
|
| 9 |
WASABI_BUCKET = os.getenv("WASABI_BUCKET", "bloom-associates")
|
|
|
|
| 15 |
"region_name": os.getenv("REGION_NAME", "us-east-1"),
|
| 16 |
"endpoint_url": os.getenv("ENDPOINT_URL", "https://s3.us-east-1.wasabisys.com")
|
| 17 |
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
SECRET_KEY = os.getenv("SECRET_KEY", "your-super-secret-key")
|
| 21 |
+
ALGORITHM = os.getenv("ALGORITHM", "HS256")
|