AkashKumarave commited on
Commit
e69e7b2
·
verified ·
1 Parent(s): 272e95e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +380 -375
app.py CHANGED
@@ -1,376 +1,381 @@
1
- import logging
2
- from fastapi import FastAPI, UploadFile, File, HTTPException, Form, Request
3
- from fastapi.middleware.cors import CORSMiddleware
4
- from fastapi.responses import FileResponse, JSONResponse
5
- import base64
6
- import os
7
- import time
8
- import jwt
9
- from pathlib import Path
10
- from typing import List
11
- import io
12
- import razorpay
13
- from razorpay.errors import SignatureVerificationError
14
- from supabase import create_client, Client
15
- from pydantic import BaseModel
16
- from typing import Optional
17
- from PIL import Image
18
- from io import BytesIO
19
- import google.generativeai as genai
20
- import tempfile
21
-
22
- # Configure logging
23
- logging.basicConfig(level=logging.INFO)
24
- logger = logging.getLogger(__name__)
25
-
26
- # Initialize FastAPI app
27
- app = FastAPI(title="Gemini Image Editing API with Razorpay")
28
-
29
- # Enable CORS for frontend
30
- app.add_middleware(
31
- CORSMiddleware,
32
- allow_origins=[
33
- "https://hivili.web.app",
34
- "http://localhost:3000",
35
- "https://*.lovable.dev",
36
- "https://*.sandbox.lovable.dev",
37
- ],
38
- allow_origin_regex=r"https://.*\.lovable\.dev|https://.*\.sandbox\.lovable\.dev",
39
- allow_credentials=True,
40
- allow_methods=["*"],
41
- allow_headers=["*"],
42
- )
43
-
44
- # ===== API CONFIGURATION =====
45
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDL5Rilo7ptJpUOZdY6wy8PJYUcVcnDADs")
46
- GEMINI_MODEL = "gemini-2.5-flash-image-preview"
47
-
48
- # Configure Gemini API
49
- genai.configure(api_key=GEMINI_API_KEY)
50
-
51
- # ===== RAZORPAY CONFIGURATION =====
52
- RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
53
- RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET")
54
- razorpay_client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET)) if RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET else None
55
-
56
- # ===== SUPABASE CONFIGURATION =====
57
- SUPABASE_URL = os.getenv("SUPABASE_URL")
58
- SUPABASE_KEY = os.getenv("SUPABASE_KEY")
59
- supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL and SUPABASE_KEY else None
60
-
61
- # Pydantic models for JSON input validation
62
- class CreateOrderRequest(BaseModel):
63
- amount: int
64
-
65
- class VerifyPaymentRequest(BaseModel):
66
- razorpay_order_id: str
67
- razorpay_payment_id: str
68
- razorpay_signature: str
69
- user_id: Optional[str] = None
70
-
71
- class GenerateImageRequest(BaseModel):
72
- prompt: str
73
- user_id: Optional[str] = None
74
-
75
- # ===== AUTHENTICATION =====
76
- def generate_jwt_token():
77
- """Generate JWT token for API authentication"""
78
- payload = {
79
- "iss": "gemini-image-editor",
80
- "exp": int(time.time()) + 1800, # 30 minutes expiration
81
- "nbf": int(time.time()) - 5 # Not before 5 seconds ago
82
- }
83
- return jwt.encode(payload, GEMINI_API_KEY, algorithm="HS256")
84
-
85
- # ===== IMAGE PROCESSING =====
86
- def prepare_image_base64(image_content: bytes):
87
- """Convert image bytes to base64 without prefix"""
88
- try:
89
- return base64.b64encode(image_content).decode('utf-8')
90
- except Exception as e:
91
- logger.error(f"Image processing failed: {str(e)}")
92
- raise HTTPException(status_code=500, detail=f"Image processing failed: {str(e)}")
93
-
94
- def validate_image(image_content: bytes):
95
- """Validate image meets API requirements"""
96
- try:
97
- size_mb = len(image_content) / (1024 * 1024)
98
- if size_mb > 10:
99
- raise HTTPException(status_code=400, detail="Image too large (max 10MB)")
100
- img = Image.open(BytesIO(image_content))
101
- if img.format.lower() not in ['png', 'jpg', 'jpeg', 'webp']:
102
- raise HTTPException(status_code=400, detail="Unsupported image format. Use PNG, JPG, JPEG, or WEBP")
103
- return True, ""
104
- except Exception as e:
105
- raise HTTPException(status_code=400, detail=f"Image validation error: {str(e)}")
106
-
107
- def generate_gemini_image(images: List[Image.Image], prompt: str):
108
- """Generate or edit image using Gemini API"""
109
- try:
110
- contents = images + [prompt]
111
- response = genai.GenerativeModel(GEMINI_MODEL).generate_content(contents)
112
-
113
- text_response = ""
114
- image_path = None
115
-
116
- for part in response.candidates[0].content.parts:
117
- if part.text:
118
- text_response += part.text + "\n"
119
- elif hasattr(part, 'inline_data') and part.inline_data:
120
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
121
- temp_path = tmp.name
122
- generated_image = Image.open(BytesIO(part.inline_data.data))
123
- generated_image.save(temp_path)
124
- image_path = temp_path
125
- logger.info(f"Generated image saved to: {temp_path} with prompt: {prompt}")
126
-
127
- if image_path:
128
- return image_path, ""
129
- else:
130
- return None, text_response.strip()
131
- except Exception as e:
132
- logger.error(f"Gemini API error: {str(e)}")
133
- raise HTTPException(status_code=500, detail=f"Gemini API error: {str(e)}")
134
-
135
- # ===== RAZORPAY FUNCTIONS =====
136
- def create_razorpay_order(amount: int):
137
- """Create a Razorpay order"""
138
- try:
139
- if not razorpay_client:
140
- raise HTTPException(status_code=500, detail="Razorpay configuration missing")
141
- if amount <= 0:
142
- raise ValueError("Amount must be a positive integer")
143
- order_data = {
144
- "amount": amount * 100, # Convert INR to paise
145
- "currency": "INR",
146
- "payment_capture": 1 # Auto-capture payment
147
- }
148
- order = razorpay_client.order.create(data=order_data)
149
- logger.info(f"Razorpay order created successfully: {order['id']}")
150
- return order
151
- except Exception as e:
152
- logger.error(f"Failed to create Razorpay order: {str(e)}")
153
- raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
154
-
155
- def verify_payment_signature(order_id: str, payment_id: str, signature: str):
156
- """Verify Razorpay payment signature"""
157
- try:
158
- if not razorpay_client:
159
- raise HTTPException(status_code=500, detail="Razorpay configuration missing")
160
- params_dict = {
161
- "razorpay_order_id": order_id,
162
- "razorpay_payment_id": payment_id,
163
- "razorpay_signature": signature
164
- }
165
- razorpay_client.utility.verify_payment_signature(params_dict)
166
- logger.info(f"Payment signature verified successfully for order: {order_id}")
167
- return True
168
- except SignatureVerificationError as e:
169
- logger.error(f"Payment signature verification failed: {str(e)}")
170
- return False
171
- except Exception as e:
172
- logger.error(f"Error verifying payment signature: {str(e)}")
173
- raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
174
-
175
- # ===== MAIN PROCESSING =====
176
- async def generate_image(images: List[bytes], prompt: str, user_id: Optional[str] = None):
177
- """Handle complete image generation workflow"""
178
- # Validate images
179
- for img_content in images:
180
- if img_content:
181
- validate_image(img_content)
182
-
183
- # Convert bytes to PIL Images
184
- pil_images = []
185
- for img_content in images:
186
- try:
187
- img = Image.open(BytesIO(img_content))
188
- if img.mode == "RGBA":
189
- img = img.convert("RGBA")
190
- pil_images.append(img)
191
- except Exception as e:
192
- logger.error(f"Image conversion failed: {str(e)}")
193
- raise HTTPException(status_code=400, detail=f"Image conversion failed: {str(e)}")
194
-
195
- if len(pil_images) < 1:
196
- raise HTTPException(status_code=400, detail="At least one image required")
197
-
198
- # Check user premium status if Supabase is configured
199
- if user_id and supabase:
200
- try:
201
- user_data = supabase.table("users").select("is_premium").eq("user_id", user_id).execute()
202
- if not user_data.data or not user_data.data[0].get("is_premium"):
203
- raise HTTPException(status_code=403, detail="Premium subscription required")
204
- except Exception as e:
205
- logger.error(f"Supabase user check failed: {str(e)}")
206
- raise HTTPException(status_code=500, detail=f"User verification failed: {str(e)}")
207
-
208
- # Generate image using Gemini API
209
- image_path, text_response = generate_gemini_image(pil_images, prompt)
210
-
211
- if image_path:
212
- return image_path
213
- else:
214
- raise HTTPException(status_code=500, detail=f"Image generation failed: {text_response or 'Unknown error'}")
215
-
216
- # ===== API ENDPOINTS =====
217
- @app.post("/generate")
218
- async def generate_image_endpoint(
219
- prompt: str = Form(...),
220
- images: List[UploadFile] = File(...),
221
- user_id: Optional[str] = Form(None)
222
- ):
223
- """Endpoint to generate or edit an image using Gemini API"""
224
- try:
225
- if len(images) < 1:
226
- raise HTTPException(status_code=400, detail="At least one image required")
227
- if len(images) > 4:
228
- raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
229
-
230
- image_contents = [await image.read() for image in images]
231
- output_path = await generate_image(image_contents, prompt, user_id)
232
-
233
- return FileResponse(
234
- path=output_path,
235
- media_type="image/png",
236
- filename=f"gemini_output_{Path(output_path).stem}.png"
237
- )
238
- except HTTPException as e:
239
- raise
240
- except Exception as e:
241
- logger.error(f"Error in /generate: {str(e)}")
242
- raise HTTPException(status_code=500, detail=str(e))
243
-
244
- @app.post("/create-razorpay-order")
245
- async def create_order_endpoint(
246
- request: Request,
247
- amount: Optional[int] = Form(None),
248
- body: Optional[CreateOrderRequest] = None
249
- ):
250
- """Create a Razorpay order (supports form-data and JSON)"""
251
- logger.info("Received create order request")
252
-
253
- try:
254
- if not razorpay_client:
255
- raise HTTPException(status_code=500, detail="Razorpay configuration missing")
256
-
257
- # Handle JSON body if provided
258
- if body and body.amount:
259
- amount = body.amount
260
- elif not amount:
261
- try:
262
- json_body = await request.json()
263
- amount = json_body.get('amount')
264
- except:
265
- pass
266
-
267
- if not amount or amount <= 0:
268
- raise HTTPException(status_code=422, detail="Missing or invalid 'amount' parameter")
269
-
270
- logger.info(f"Creating order with amount: {amount}")
271
- order = create_razorpay_order(amount)
272
-
273
- response_data = {
274
- "id": order["id"],
275
- "amount": order["amount"],
276
- "currency": order["currency"],
277
- "key_id": RAZORPAY_KEY_ID
278
- }
279
-
280
- logger.info(f"Order created successfully: {order['id']}")
281
- return JSONResponse(content=response_data)
282
-
283
- except HTTPException:
284
- raise
285
- except Exception as e:
286
- logger.error(f"Error creating order: {str(e)}")
287
- raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
288
-
289
- @app.post("/verify-razorpay-payment")
290
- async def verify_payment_endpoint(
291
- request: Request,
292
- razorpay_order_id: Optional[str] = Form(None),
293
- razorpay_payment_id: Optional[str] = Form(None),
294
- razorpay_signature: Optional[str] = Form(None),
295
- user_id: Optional[str] = Form(None),
296
- body: Optional[VerifyPaymentRequest] = None
297
- ):
298
- """Verify Razorpay payment signature (supports form-data and JSON)"""
299
- logger.info("Received payment verification request")
300
-
301
- try:
302
- # Handle JSON body if provided
303
- if body:
304
- razorpay_order_id = razorpay_order_id or body.razorpay_order_id
305
- razorpay_payment_id = razorpay_payment_id or body.razorpay_payment_id
306
- razorpay_signature = razorpay_signature or body.razorpay_signature
307
- user_id = user_id or body.user_id
308
- else:
309
- try:
310
- json_body = await request.json()
311
- razorpay_order_id = razorpay_order_id or json_body.get('razorpay_order_id')
312
- razorpay_payment_id = razorpay_payment_id or json_body.get('razorpay_payment_id')
313
- razorpay_signature = razorpay_signature or json_body.get('razorpay_signature')
314
- user_id = user_id or json_body.get('user_id')
315
- except:
316
- pass
317
-
318
- # Validate required fields
319
- if not all([razorpay_order_id, razorpay_payment_id, razorpay_signature]):
320
- missing_fields = []
321
- if not razorpay_order_id: missing_fields.append("razorpay_order_id")
322
- if not razorpay_payment_id: missing_fields.append("razorpay_payment_id")
323
- if not razorpay_signature: missing_fields.append("razorpay_signature")
324
-
325
- logger.error(f"Missing required fields: {missing_fields}")
326
- raise HTTPException(
327
- status_code=422,
328
- detail=f"Missing required fields: {', '.join(missing_fields)}"
329
- )
330
-
331
- logger.info(f"Verifying payment for order_id: {razorpay_order_id}")
332
- is_valid = verify_payment_signature(razorpay_order_id, razorpay_payment_id, razorpay_signature)
333
-
334
- if is_valid:
335
- if user_id and supabase:
336
- logger.info(f"Updating Supabase for user_id: {user_id}")
337
- try:
338
- supabase.table("users").update({"is_premium": True}).eq("user_id", user_id).execute()
339
- logger.info(f"Successfully updated premium status for user: {user_id}")
340
- except Exception as e:
341
- logger.error(f"Failed to update Supabase: {str(e)}")
342
-
343
- return JSONResponse(content={"success": True, "message": "Payment verified successfully"})
344
- else:
345
- logger.warning(f"Payment verification failed for order: {razorpay_order_id}")
346
- return JSONResponse(content={"success": False, "message": "Payment verification failed"}, status_code=400)
347
-
348
- except HTTPException:
349
- raise
350
- except Exception as e:
351
- logger.error(f"Error verifying payment: {str(e)}")
352
- raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
353
-
354
- @app.get("/")
355
- async def index():
356
- return {
357
- "status": "Gemini Image Editing API with Razorpay is running",
358
- "endpoints": {
359
- "generate": "POST /generate",
360
- "create_order": "POST /create-razorpay-order",
361
- "verify_payment": "POST /verify-razorpay-payment"
362
- }
363
- }
364
-
365
- @app.get("/health")
366
- async def health_check():
367
- return {
368
- "status": "healthy",
369
- "razorpay_configured": bool(RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET),
370
- "supabase_configured": bool(SUPABASE_URL and SUPABASE_KEY),
371
- "gemini_configured": bool(GEMINI_API_KEY)
372
- }
373
-
374
- if __name__ == "__main__":
375
- import uvicorn
 
 
 
 
 
376
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import logging
2
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form, Request
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.responses import FileResponse, JSONResponse
5
+ import base64
6
+ import os
7
+ import time
8
+ import jwt
9
+ from pathlib import Path
10
+ from typing import List
11
+ import io
12
+ import razorpay
13
+ from razorpay.errors import SignatureVerificationError
14
+ from supabase import create_client, Client
15
+ from pydantic import BaseModel
16
+ from typing import Optional
17
+ from PIL import Image
18
+ from io import BytesIO
19
+ import google.generativeai as genai
20
+ import tempfile
21
+
22
+ # Configure logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Initialize FastAPI app
27
+ app = FastAPI(title="Gemini Image Editing API with Razorpay")
28
+
29
+ # Enable CORS for frontend
30
+ app.add_middleware(
31
+ CORSMiddleware,
32
+ allow_origins=[
33
+ "https://hivili.web.app",
34
+ "http://localhost:3000",
35
+ "https://*.lovable.dev",
36
+ "https://*.sandbox.lovable.dev",
37
+ ],
38
+ allow_origin_regex=r"https://.*\.lovable\.dev|https://.*\.sandbox\.lovable\.dev",
39
+ allow_credentials=True,
40
+ allow_methods=["*"],
41
+ allow_headers=["*"],
42
+ )
43
+
44
+ # ===== API CONFIGURATION =====
45
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
46
+ GEMINI_MODEL = "gemini-2.5-flash-image-preview"
47
+
48
+ # Validate Gemini API key presence
49
+ if not GEMINI_API_KEY:
50
+ logger.error("GEMINI_API_KEY environment variable is not set")
51
+ raise ValueError("GEMINI_API_KEY environment variable is required")
52
+
53
+ # Configure Gemini API
54
+ genai.configure(api_key=GEMINI_API_KEY)
55
+
56
+ # ===== RAZORPAY CONFIGURATION =====
57
+ RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
58
+ RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET")
59
+ razorpay_client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET)) if RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET else None
60
+
61
+ # ===== SUPABASE CONFIGURATION =====
62
+ SUPABASE_URL = os.getenv("SUPABASE_URL")
63
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY")
64
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL and SUPABASE_KEY else None
65
+
66
+ # Pydantic models for JSON input validation
67
+ class CreateOrderRequest(BaseModel):
68
+ amount: int
69
+
70
+ class VerifyPaymentRequest(BaseModel):
71
+ razorpay_order_id: str
72
+ razorpay_payment_id: str
73
+ razorpay_signature: str
74
+ user_id: Optional[str] = None
75
+
76
+ class GenerateImageRequest(BaseModel):
77
+ prompt: str
78
+ user_id: Optional[str] = None
79
+
80
+ # ===== AUTHENTICATION =====
81
+ def generate_jwt_token():
82
+ """Generate JWT token for API authentication"""
83
+ payload = {
84
+ "iss": "gemini-image-editor",
85
+ "exp": int(time.time()) + 1800, # 30 minutes expiration
86
+ "nbf": int(time.time()) - 5 # Not before 5 seconds ago
87
+ }
88
+ return jwt.encode(payload, GEMINI_API_KEY, algorithm="HS256")
89
+
90
+ # ===== IMAGE PROCESSING =====
91
+ def prepare_image_base64(image_content: bytes):
92
+ """Convert image bytes to base64 without prefix"""
93
+ try:
94
+ return base64.b64encode(image_content).decode('utf-8')
95
+ except Exception as e:
96
+ logger.error(f"Image processing failed: {str(e)}")
97
+ raise HTTPException(status_code=500, detail=f"Image processing failed: {str(e)}")
98
+
99
+ def validate_image(image_content: bytes):
100
+ """Validate image meets API requirements"""
101
+ try:
102
+ size_mb = len(image_content) / (1024 * 1024)
103
+ if size_mb > 10:
104
+ raise HTTPException(status_code=400, detail="Image too large (max 10MB)")
105
+ img = Image.open(BytesIO(image_content))
106
+ if img.format.lower() not in ['png', 'jpg', 'jpeg', 'webp']:
107
+ raise HTTPException(status_code=400, detail="Unsupported image format. Use PNG, JPG, JPEG, or WEBP")
108
+ return True, ""
109
+ except Exception as e:
110
+ raise HTTPException(status_code=400, detail=f"Image validation error: {str(e)}")
111
+
112
+ def generate_gemini_image(images: List[Image.Image], prompt: str):
113
+ """Generate or edit image using Gemini API"""
114
+ try:
115
+ contents = images + [prompt]
116
+ response = genai.GenerativeModel(GEMINI_MODEL).generate_content(contents)
117
+
118
+ text_response = ""
119
+ image_path = None
120
+
121
+ for part in response.candidates[0].content.parts:
122
+ if part.text:
123
+ text_response += part.text + "\n"
124
+ elif hasattr(part, 'inline_data') and part.inline_data:
125
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
126
+ temp_path = tmp.name
127
+ generated_image = Image.open(BytesIO(part.inline_data.data))
128
+ generated_image.save(temp_path)
129
+ image_path = temp_path
130
+ logger.info(f"Generated image saved to: {temp_path} with prompt: {prompt}")
131
+
132
+ if image_path:
133
+ return image_path, ""
134
+ else:
135
+ return None, text_response.strip()
136
+ except Exception as e:
137
+ logger.error(f"Gemini API error: {str(e)}")
138
+ raise HTTPException(status_code=500, detail=f"Gemini API error: {str(e)}")
139
+
140
+ # ===== RAZORPAY FUNCTIONS =====
141
+ def create_razorpay_order(amount: int):
142
+ """Create a Razorpay order"""
143
+ try:
144
+ if not razorpay_client:
145
+ raise HTTPException(status_code=500, detail="Razorpay configuration missing")
146
+ if amount <= 0:
147
+ raise ValueError("Amount must be a positive integer")
148
+ order_data = {
149
+ "amount": amount * 100, # Convert INR to paise
150
+ "currency": "INR",
151
+ "payment_capture": 1 # Auto-capture payment
152
+ }
153
+ order = razorpay_client.order.create(data=order_data)
154
+ logger.info(f"Razorpay order created successfully: {order['id']}")
155
+ return order
156
+ except Exception as e:
157
+ logger.error(f"Failed to create Razorpay order: {str(e)}")
158
+ raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
159
+
160
+ def verify_payment_signature(order_id: str, payment_id: str, signature: str):
161
+ """Verify Razorpay payment signature"""
162
+ try:
163
+ if not razorpay_client:
164
+ raise HTTPException(status_code=500, detail="Razorpay configuration missing")
165
+ params_dict = {
166
+ "razorpay_order_id": order_id,
167
+ "razorpay_payment_id": payment_id,
168
+ "razorpay_signature": signature
169
+ }
170
+ razorpay_client.utility.verify_payment_signature(params_dict)
171
+ logger.info(f"Payment signature verified successfully for order: {order_id}")
172
+ return True
173
+ except SignatureVerificationError as e:
174
+ logger.error(f"Payment signature verification failed: {str(e)}")
175
+ return False
176
+ except Exception as e:
177
+ logger.error(f"Error verifying payment signature: {str(e)}")
178
+ raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
179
+
180
+ # ===== MAIN PROCESSING =====
181
+ async def generate_image(images: List[bytes], prompt: str, user_id: Optional[str] = None):
182
+ """Handle complete image generation workflow"""
183
+ # Validate images
184
+ for img_content in images:
185
+ if img_content:
186
+ validate_image(img_content)
187
+
188
+ # Convert bytes to PIL Images
189
+ pil_images = []
190
+ for img_content in images:
191
+ try:
192
+ img = Image.open(BytesIO(img_content))
193
+ if img.mode == "RGBA":
194
+ img = img.convert("RGBA")
195
+ pil_images.append(img)
196
+ except Exception as e:
197
+ logger.error(f"Image conversion failed: {str(e)}")
198
+ raise HTTPException(status_code=400, detail=f"Image conversion failed: {str(e)}")
199
+
200
+ if len(pil_images) < 1:
201
+ raise HTTPException(status_code=400, detail="At least one image required")
202
+
203
+ # Check user premium status if Supabase is configured
204
+ if user_id and supabase:
205
+ try:
206
+ user_data = supabase.table("users").select("is_premium").eq("user_id", user_id).execute()
207
+ if not user_data.data or not user_data.data[0].get("is_premium"):
208
+ raise HTTPException(status_code=403, detail="Premium subscription required")
209
+ except Exception as e:
210
+ logger.error(f"Supabase user check failed: {str(e)}")
211
+ raise HTTPException(status_code=500, detail=f"User verification failed: {str(e)}")
212
+
213
+ # Generate image using Gemini API
214
+ image_path, text_response = generate_gemini_image(pil_images, prompt)
215
+
216
+ if image_path:
217
+ return image_path
218
+ else:
219
+ raise HTTPException(status_code=500, detail=f"Image generation failed: {text_response or 'Unknown error'}")
220
+
221
+ # ===== API ENDPOINTS =====
222
+ @app.post("/generate")
223
+ async def generate_image_endpoint(
224
+ prompt: str = Form(...),
225
+ images: List[UploadFile] = File(...),
226
+ user_id: Optional[str] = Form(None)
227
+ ):
228
+ """Endpoint to generate or edit an image using Gemini API"""
229
+ try:
230
+ if len(images) < 1:
231
+ raise HTTPException(status_code=400, detail="At least one image required")
232
+ if len(images) > 4:
233
+ raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
234
+
235
+ image_contents = [await image.read() for image in images]
236
+ output_path = await generate_image(image_contents, prompt, user_id)
237
+
238
+ return FileResponse(
239
+ path=output_path,
240
+ media_type="image/png",
241
+ filename=f"gemini_output_{Path(output_path).stem}.png"
242
+ )
243
+ except HTTPException as e:
244
+ raise
245
+ except Exception as e:
246
+ logger.error(f"Error in /generate: {str(e)}")
247
+ raise HTTPException(status_code=500, detail=str(e))
248
+
249
+ @app.post("/create-razorpay-order")
250
+ async def create_order_endpoint(
251
+ request: Request,
252
+ amount: Optional[int] = Form(None),
253
+ body: Optional[CreateOrderRequest] = None
254
+ ):
255
+ """Create a Razorpay order (supports form-data and JSON)"""
256
+ logger.info("Received create order request")
257
+
258
+ try:
259
+ if not razorpay_client:
260
+ raise HTTPException(status_code=500, detail="Razorpay configuration missing")
261
+
262
+ # Handle JSON body if provided
263
+ if body and body.amount:
264
+ amount = body.amount
265
+ elif not amount:
266
+ try:
267
+ json_body = await request.json()
268
+ amount = json_body.get('amount')
269
+ except:
270
+ pass
271
+
272
+ if not amount or amount <= 0:
273
+ raise HTTPException(status_code=422, detail="Missing or invalid 'amount' parameter")
274
+
275
+ logger.info(f"Creating order with amount: {amount}")
276
+ order = create_razorpay_order(amount)
277
+
278
+ response_data = {
279
+ "id": order["id"],
280
+ "amount": order["amount"],
281
+ "currency": "INR",
282
+ "key_id": RAZORPAY_KEY_ID
283
+ }
284
+
285
+ logger.info(f"Order created successfully: {order['id']}")
286
+ return JSONResponse(content=response_data)
287
+
288
+ except HTTPException:
289
+ raise
290
+ except Exception as e:
291
+ logger.error(f"Error creating order: {str(e)}")
292
+ raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
293
+
294
+ @app.post("/verify-razorpay-payment")
295
+ async def verify_payment_endpoint(
296
+ request: Request,
297
+ razorpay_order_id: Optional[str] = Form(None),
298
+ razorpay_payment_id: Optional[str] = Form(None),
299
+ razorpay_signature: Optional[str] = Form(None),
300
+ user_id: Optional[str] = Form(None),
301
+ body: Optional[VerifyPaymentRequest] = None
302
+ ):
303
+ """Verify Razorpay payment signature (supports form-data and JSON)"""
304
+ logger.info("Received payment verification request")
305
+
306
+ try:
307
+ # Handle JSON body if provided
308
+ if body:
309
+ razorpay_order_id = razorpay_order_id or body.razorpay_order_id
310
+ razorpay_payment_id = razorpay_payment_id or body.razorpay_payment_id
311
+ razorpay_signature = razorpay_signature or body.razorpay_signature
312
+ user_id = user_id or body.user_id
313
+ else:
314
+ try:
315
+ json_body = await request.json()
316
+ razorpay_order_id = razorpay_order_id or json_body.get('razorpay_order_id')
317
+ razorpay_payment_id = razorpay_payment_id or json_body.get('razorpay_payment_id')
318
+ razorpay_signature = razorpay_signature or json_body.get('razorpay_signature')
319
+ user_id = user_id or json_body.get('user_id')
320
+ except:
321
+ pass
322
+
323
+ # Validate required fields
324
+ if not all([razorpay_order_id, razorpay_payment_id, razorpay_signature]):
325
+ missing_fields = []
326
+ if not razorpay_order_id: missing_fields.append("razorpay_order_id")
327
+ if not razorpay_payment_id: missing_fields.append("razorpay_payment_id")
328
+ if not razorpay_signature: missing_fields.append("razorpay_signature")
329
+
330
+ logger.error(f"Missing required fields: {missing_fields}")
331
+ raise HTTPException(
332
+ status_code=422,
333
+ detail=f"Missing required fields: {', '.join(missing_fields)}"
334
+ )
335
+
336
+ logger.info(f"Verifying payment for order_id: {razorpay_order_id}")
337
+ is_valid = verify_payment_signature(razorpay_order_id, razorpay_payment_id, razorpay_signature)
338
+
339
+ if is_valid:
340
+ if user_id and supabase:
341
+ logger.info(f"Updating Supabase for user_id: {user_id}")
342
+ try:
343
+ supabase.table("users").update({"is_premium": True}).eq("user_id", user_id).execute()
344
+ logger.info(f"Successfully updated premium status for user: {user_id}")
345
+ except Exception as e:
346
+ logger.error(f"Failed to update Supabase: {str(e)}")
347
+
348
+ return JSONResponse(content={"success": True, "message": "Payment verified successfully"})
349
+ else:
350
+ logger.warning(f"Payment verification failed for order: {razorpay_order_id}")
351
+ return JSONResponse(content={"success": False, "message": "Payment verification failed"}, status_code=400)
352
+
353
+ except HTTPException:
354
+ raise
355
+ except Exception as e:
356
+ logger.error(f"Error verifying payment: {str(e)}")
357
+ raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
358
+
359
+ @app.get("/")
360
+ async def index():
361
+ return {
362
+ "status": "Gemini Image Editing API with Razorpay is running",
363
+ "endpoints": {
364
+ "generate": "POST /generate",
365
+ "create_order": "POST /create-razorpay-order",
366
+ "verify_payment": "POST /verify-razorpay-payment"
367
+ }
368
+ }
369
+
370
+ @app.get("/health")
371
+ async def health_check():
372
+ return {
373
+ "status": "healthy",
374
+ "razorpay_configured": bool(RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET),
375
+ "supabase_configured": bool(SUPABASE_URL and SUPABASE_KEY),
376
+ "gemini_configured": bool(GEMINI_API_KEY)
377
+ }
378
+
379
+ if __name__ == "__main__":
380
+ import uvicorn
381
  uvicorn.run(app, host="0.0.0.0", port=7860)