AkashKumarave commited on
Commit
7152cdd
·
verified ·
1 Parent(s): 9ef1ca2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -137
app.py CHANGED
@@ -5,8 +5,6 @@ from fastapi.responses import FileResponse, JSONResponse
5
  import requests
6
  import base64
7
  import os
8
- import time
9
- import jwt
10
  from pathlib import Path
11
  from typing import List
12
  import io
@@ -21,17 +19,16 @@ logging.basicConfig(level=logging.INFO)
21
  logger = logging.getLogger(__name__)
22
 
23
  # Initialize FastAPI app
24
- app = FastAPI(title="Kling AI Multi-Image Generator API with Razorpay")
25
 
26
- # Enable CORS for the frontend - Updated for Lovable domains
27
  app.add_middleware(
28
  CORSMiddleware,
29
  allow_origins=[
30
- "https://hivili.web.app",
31
  "http://localhost:3000",
32
  "https://*.lovable.dev",
33
  "https://*.sandbox.lovable.dev",
34
- # Add pattern matching for Lovable subdomains
35
  ],
36
  allow_origin_regex=r"https://.*\.lovable\.dev|https://.*\.sandbox\.lovable\.dev",
37
  allow_credentials=True,
@@ -40,10 +37,9 @@ app.add_middleware(
40
  )
41
 
42
  # ===== API CONFIGURATION =====
43
- ACCESS_KEY_ID = os.getenv("ACCESS_KEY_ID", "AFyHfnQATghFdCMyAG3gRPbNY4TNKFGB")
44
- ACCESS_KEY_SECRET = os.getenv("ACCESS_KEY_SECRET", "TTepeLyBterLNM3brYPGmdndBnnyKJBA")
45
- API_BASE_URL = "https://api-singapore.klingai.com"
46
- CREATE_TASK_ENDPOINT = f"{API_BASE_URL}/v1/images/multi-image2image"
47
 
48
  # ===== RAZORPAY CONFIGURATION =====
49
  RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
@@ -65,16 +61,6 @@ class VerifyPaymentRequest(BaseModel):
65
  razorpay_signature: str
66
  user_id: Optional[str] = None
67
 
68
- # ===== AUTHENTICATION =====
69
- def generate_jwt_token():
70
- """Generate JWT token for API authentication"""
71
- payload = {
72
- "iss": ACCESS_KEY_ID,
73
- "exp": int(time.time()) + 1800, # 30 minutes expiration
74
- "nbf": int(time.time()) - 5 # Not before 5 seconds ago
75
- }
76
- return jwt.encode(payload, ACCESS_KEY_SECRET, algorithm="HS256")
77
-
78
  # ===== IMAGE PROCESSING =====
79
  def prepare_image_base64(image_content: bytes):
80
  """Convert image bytes to base64 without prefix"""
@@ -96,88 +82,49 @@ def validate_image(image_content: bytes):
96
 
97
  # ===== API FUNCTIONS =====
98
  def create_multi_image_task(subject_images: List[bytes], prompt: str):
99
- """Create multi-image generation task"""
100
  headers = {
101
- "Authorization": f"Bearer {generate_jwt_token()}",
102
  "Content-Type": "application/json"
103
  }
104
-
105
  subject_image_list = []
106
  for img_content in subject_images:
107
  if img_content:
108
  base64_img = prepare_image_base64(img_content)
109
  if base64_img:
110
- subject_image_list.append({"subject_image": base64_img})
 
 
 
 
 
111
 
112
  if len(subject_image_list) < 2:
113
  raise HTTPException(status_code=400, detail="At least 2 subject images required")
114
-
115
  payload = {
116
- "model_name": "kling-v2",
117
- "prompt": prompt,
118
- "subject_image_list": subject_image_list,
119
- "n": 1,
120
- "aspect_ratio": "1:1"
 
 
 
121
  }
122
-
123
  try:
124
  response = requests.post(CREATE_TASK_ENDPOINT, json=payload, headers=headers)
125
  response.raise_for_status()
126
- return response.json()
 
 
 
127
  except requests.exceptions.RequestException as e:
128
  logger.error(f"API request failed: {str(e)}")
129
  if hasattr(e, 'response') and e.response:
130
  logger.error(f"API response: {e.response.text}")
131
  raise HTTPException(status_code=500, detail=f"API Error: {str(e)}")
132
 
133
- def check_task_status(task_id: str):
134
- """Check task completion status"""
135
- headers = {"Authorization": f"Bearer {generate_jwt_token()}"}
136
- status_url = f"{API_BASE_URL}/v1/images/multi-image2image/{task_id}"
137
-
138
- try:
139
- response = requests.get(status_url, headers=headers)
140
- response.raise_for_status()
141
- return response.json()
142
- except requests.exceptions.RequestException as e:
143
- raise HTTPException(status_code=500, detail=f"Status check failed: {str(e)}")
144
-
145
- # ===== RAZORPAY FUNCTIONS =====
146
- def create_razorpay_order(amount: int):
147
- """Create a Razorpay order"""
148
- try:
149
- if amount <= 0:
150
- raise ValueError("Amount must be a positive integer")
151
- order_data = {
152
- "amount": amount * 100, # Convert INR to paise
153
- "currency": "INR",
154
- "payment_capture": 1 # Auto-capture payment
155
- }
156
- order = razorpay_client.order.create(data=order_data)
157
- logger.info(f"Razorpay order created successfully: {order['id']}")
158
- return order
159
- except Exception as e:
160
- logger.error(f"Failed to create Razorpay order: {str(e)}")
161
- raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
162
-
163
- def verify_payment_signature(order_id: str, payment_id: str, signature: str):
164
- """Verify Razorpay payment signature"""
165
- try:
166
- params_dict = {
167
- "razorpay_order_id": order_id,
168
- "razorpay_payment_id": payment_id,
169
- "razorpay_signature": signature
170
- }
171
- razorpay_client.utility.verify_payment_signature(params_dict)
172
- logger.info(f"Payment signature verified successfully for order: {order_id}")
173
- return True
174
- except SignatureVerificationError as e:
175
- logger.error(f"Payment signature verification failed: {str(e)}")
176
- return False
177
- except Exception as e:
178
- logger.error(f"Error verifying payment signature: {str(e)}")
179
- raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
180
-
181
  # ===== MAIN PROCESSING =====
182
  async def generate_image(subject_images: List[bytes], prompt: str):
183
  """Handle complete image generation workflow"""
@@ -186,37 +133,21 @@ async def generate_image(subject_images: List[bytes], prompt: str):
186
  validate_image(img_content)
187
 
188
  task_response = create_multi_image_task(subject_images, prompt)
189
- if task_response.get("code") != 0:
190
- raise HTTPException(status_code=500, detail=f"API error: {task_response.get('message', 'Unknown error')}")
191
-
192
- task_id = task_response["data"]["task_id"]
193
- logger.info(f"Task created: {task_id}")
194
-
195
- for _ in range(60):
196
- task_data = check_task_status(task_id)
197
- status = task_data["data"]["task_status"]
198
 
199
- if status == "succeed":
200
- image_url = task_data["data"]["task_result"]["images"][0]["url"]
201
- try:
202
- response = requests.get(image_url)
203
- response.raise_for_status()
204
- output_dir = Path("/tmp")
205
- output_dir.mkdir(exist_ok=True)
206
- output_path = output_dir / f"kling_output_{task_id}.png"
207
- with open(output_path, "wb") as f:
208
- f.write(response.content)
209
- return output_path
210
- except Exception as e:
211
- raise HTTPException(status_code=500, detail=f"Failed to download result: {str(e)}")
212
-
213
- elif status in ("failed", "canceled"):
214
- error_msg = task_data["data"].get("task_status_msg", "Unknown error")
215
- raise HTTPException(status_code=500, detail=f"Task failed: {error_msg}")
216
-
217
- time.sleep(10)
218
-
219
- raise HTTPException(status_code=500, detail="Task timed out after 10 minutes")
220
 
221
  # ===== API ENDPOINTS =====
222
  @app.post("/generate")
@@ -230,14 +161,12 @@ async def generate_image_endpoint(
230
  raise HTTPException(status_code=400, detail="At least 2 images are required")
231
  if len(images) > 4:
232
  raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
233
-
234
  image_contents = [await image.read() for image in images]
235
  output_path = await generate_image(image_contents, prompt)
236
-
237
  return FileResponse(
238
  path=output_path,
239
  media_type="image/png",
240
- filename=f"kling_output_{Path(output_path).stem}.png"
241
  )
242
  except Exception as e:
243
  logger.error(f"Error in /generate: {str(e)}")
@@ -251,39 +180,33 @@ async def create_order_endpoint(
251
  ):
252
  """Create a Razorpay order (supports form-data and JSON)"""
253
  logger.info("Received create order request")
254
-
255
  if not RAZORPAY_KEY_ID or not RAZORPAY_KEY_SECRET:
256
  logger.error("Razorpay configuration missing")
257
  raise HTTPException(status_code=500, detail="Razorpay configuration missing")
258
 
259
- # Handle JSON body if provided
260
  try:
261
  if body and body.amount:
262
  amount = body.amount
263
  elif not amount:
264
- # Try to parse JSON from request body
265
  try:
266
  json_body = await request.json()
267
  amount = json_body.get('amount')
268
  except:
269
  pass
270
-
271
  if not amount or amount <= 0:
272
  raise HTTPException(status_code=422, detail="Missing or invalid 'amount' parameter")
273
-
274
  logger.info(f"Creating order with amount: {amount}")
275
  order = create_razorpay_order(amount)
276
-
277
  response_data = {
278
  "id": order["id"],
279
  "amount": order["amount"],
280
  "currency": order["currency"],
281
  "key_id": RAZORPAY_KEY_ID
282
  }
283
-
284
  logger.info(f"Order created successfully: {order['id']}")
285
  return JSONResponse(content=response_data)
286
-
287
  except HTTPException:
288
  raise
289
  except Exception as e:
@@ -301,16 +224,13 @@ async def verify_payment_endpoint(
301
  ):
302
  """Verify Razorpay payment signature (supports form-data and JSON)"""
303
  logger.info("Received payment verification request")
304
-
305
  try:
306
- # Handle JSON body if provided
307
  if body:
308
  razorpay_order_id = razorpay_order_id or body.razorpay_order_id
309
  razorpay_payment_id = razorpay_payment_id or body.razorpay_payment_id
310
  razorpay_signature = razorpay_signature or body.razorpay_signature
311
  user_id = user_id or body.user_id
312
  else:
313
- # Try to parse JSON from request body
314
  try:
315
  json_body = await request.json()
316
  razorpay_order_id = razorpay_order_id or json_body.get('razorpay_order_id')
@@ -319,23 +239,23 @@ async def verify_payment_endpoint(
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}")
@@ -344,13 +264,10 @@ async def verify_payment_endpoint(
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
- # Don't fail the payment verification if Supabase update fails
348
-
349
  return JSONResponse(content={"success": True, "message": "Payment verified successfully"})
350
  else:
351
  logger.warning(f"Payment verification failed for order: {razorpay_order_id}")
352
  return JSONResponse(content={"success": False, "message": "Payment verification failed"}, status_code=400)
353
-
354
  except HTTPException:
355
  raise
356
  except Exception as e:
@@ -360,7 +277,7 @@ async def verify_payment_endpoint(
360
  @app.get("/")
361
  async def index():
362
  return {
363
- "status": "Kling AI Multi-Image Generator API with Razorpay is running",
364
  "endpoints": {
365
  "generate": "POST /generate",
366
  "create_order": "POST /create-razorpay-order",
@@ -376,6 +293,41 @@ async def health_check():
376
  "supabase_configured": bool(SUPABASE_URL and SUPABASE_KEY)
377
  }
378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  if __name__ == "__main__":
380
  import uvicorn
381
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
5
  import requests
6
  import base64
7
  import os
 
 
8
  from pathlib import Path
9
  from typing import List
10
  import io
 
19
  logger = logging.getLogger(__name__)
20
 
21
  # Initialize FastAPI app
22
+ app = FastAPI(title="Gemini 2.5 Flash Image Generator API with Razorpay")
23
 
24
+ # Enable CORS for the frontend
25
  app.add_middleware(
26
  CORSMiddleware,
27
  allow_origins=[
28
+ "https://hivili.web.app",
29
  "http://localhost:3000",
30
  "https://*.lovable.dev",
31
  "https://*.sandbox.lovable.dev",
 
32
  ],
33
  allow_origin_regex=r"https://.*\.lovable\.dev|https://.*\.sandbox\.lovable\.dev",
34
  allow_credentials=True,
 
37
  )
38
 
39
  # ===== API CONFIGURATION =====
40
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDL5Rilo7ptJpUOZdY6wy8PJYUcVcnDADs")
41
+ API_BASE_URL = "https://generativelanguage.googleapis.com"
42
+ CREATE_TASK_ENDPOINT = f"{API_BASE_URL}/v1beta/models/gemini-2.5-flash-image-preview:generateContent?key={GEMINI_API_KEY}"
 
43
 
44
  # ===== RAZORPAY CONFIGURATION =====
45
  RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
 
61
  razorpay_signature: str
62
  user_id: Optional[str] = None
63
 
 
 
 
 
 
 
 
 
 
 
64
  # ===== IMAGE PROCESSING =====
65
  def prepare_image_base64(image_content: bytes):
66
  """Convert image bytes to base64 without prefix"""
 
82
 
83
  # ===== API FUNCTIONS =====
84
  def create_multi_image_task(subject_images: List[bytes], prompt: str):
85
+ """Create multi-image generation task with Gemini API"""
86
  headers = {
 
87
  "Content-Type": "application/json"
88
  }
 
89
  subject_image_list = []
90
  for img_content in subject_images:
91
  if img_content:
92
  base64_img = prepare_image_base64(img_content)
93
  if base64_img:
94
+ subject_image_list.append({
95
+ "inline_data": {
96
+ "mime_type": "image/png",
97
+ "data": base64_img
98
+ }
99
+ })
100
 
101
  if len(subject_image_list) < 2:
102
  raise HTTPException(status_code=400, detail="At least 2 subject images required")
103
+
104
  payload = {
105
+ "contents": [
106
+ {
107
+ "parts": [
108
+ {"text": prompt},
109
+ *subject_image_list
110
+ ]
111
+ }
112
+ ]
113
  }
114
+
115
  try:
116
  response = requests.post(CREATE_TASK_ENDPOINT, json=payload, headers=headers)
117
  response.raise_for_status()
118
+ data = response.json()
119
+ if not data.get("candidates") or not data["candidates"][0].get("content"):
120
+ raise HTTPException(status_code=500, detail="No valid content returned from API")
121
+ return data
122
  except requests.exceptions.RequestException as e:
123
  logger.error(f"API request failed: {str(e)}")
124
  if hasattr(e, 'response') and e.response:
125
  logger.error(f"API response: {e.response.text}")
126
  raise HTTPException(status_code=500, detail=f"API Error: {str(e)}")
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  # ===== MAIN PROCESSING =====
129
  async def generate_image(subject_images: List[bytes], prompt: str):
130
  """Handle complete image generation workflow"""
 
133
  validate_image(img_content)
134
 
135
  task_response = create_multi_image_task(subject_images, prompt)
136
+ try:
137
+ # Extract base64-encoded image from response
138
+ image_base64 = task_response["candidates"][0]["content"]["parts"][0]["inline_data"]["data"]
139
+ image_data = base64.b64decode(image_base64)
 
 
 
 
 
140
 
141
+ # Save the image to a temporary file
142
+ output_dir = Path("/tmp")
143
+ output_dir.mkdir(exist_ok=True)
144
+ output_path = output_dir / f"gemini_output_{int(time.time())}.png"
145
+ with open(output_path, "wb") as f:
146
+ f.write(image_data)
147
+ return output_path
148
+ except Exception as e:
149
+ logger.error(f"Failed to process API response: {str(e)}")
150
+ raise HTTPException(status_code=500, detail=f"Failed to process result: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  # ===== API ENDPOINTS =====
153
  @app.post("/generate")
 
161
  raise HTTPException(status_code=400, detail="At least 2 images are required")
162
  if len(images) > 4:
163
  raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
 
164
  image_contents = [await image.read() for image in images]
165
  output_path = await generate_image(image_contents, prompt)
 
166
  return FileResponse(
167
  path=output_path,
168
  media_type="image/png",
169
+ filename=f"gemini_output_{Path(output_path).stem}.png"
170
  )
171
  except Exception as e:
172
  logger.error(f"Error in /generate: {str(e)}")
 
180
  ):
181
  """Create a Razorpay order (supports form-data and JSON)"""
182
  logger.info("Received create order request")
 
183
  if not RAZORPAY_KEY_ID or not RAZORPAY_KEY_SECRET:
184
  logger.error("Razorpay configuration missing")
185
  raise HTTPException(status_code=500, detail="Razorpay configuration missing")
186
 
 
187
  try:
188
  if body and body.amount:
189
  amount = body.amount
190
  elif not amount:
 
191
  try:
192
  json_body = await request.json()
193
  amount = json_body.get('amount')
194
  except:
195
  pass
196
+
197
  if not amount or amount <= 0:
198
  raise HTTPException(status_code=422, detail="Missing or invalid 'amount' parameter")
199
+
200
  logger.info(f"Creating order with amount: {amount}")
201
  order = create_razorpay_order(amount)
 
202
  response_data = {
203
  "id": order["id"],
204
  "amount": order["amount"],
205
  "currency": order["currency"],
206
  "key_id": RAZORPAY_KEY_ID
207
  }
 
208
  logger.info(f"Order created successfully: {order['id']}")
209
  return JSONResponse(content=response_data)
 
210
  except HTTPException:
211
  raise
212
  except Exception as e:
 
224
  ):
225
  """Verify Razorpay payment signature (supports form-data and JSON)"""
226
  logger.info("Received payment verification request")
 
227
  try:
 
228
  if body:
229
  razorpay_order_id = razorpay_order_id or body.razorpay_order_id
230
  razorpay_payment_id = razorpay_payment_id or body.razorpay_payment_id
231
  razorpay_signature = razorpay_signature or body.razorpay_signature
232
  user_id = user_id or body.user_id
233
  else:
 
234
  try:
235
  json_body = await request.json()
236
  razorpay_order_id = razorpay_order_id or json_body.get('razorpay_order_id')
 
239
  user_id = user_id or json_body.get('user_id')
240
  except:
241
  pass
242
+
 
243
  if not all([razorpay_order_id, razorpay_payment_id, razorpay_signature]):
244
  missing_fields = []
245
+ if not razorpay_order_id:
246
+ missing_fields.append("razorpay_order_id")
247
+ if not razorpay_payment_id:
248
+ missing_fields.append("razorpay_payment_id")
249
+ if not razorpay_signature:
250
+ missing_fields.append("razorpay_signature")
251
  logger.error(f"Missing required fields: {missing_fields}")
252
  raise HTTPException(
253
+ status_code=422,
254
  detail=f"Missing required fields: {', '.join(missing_fields)}"
255
  )
256
+
257
  logger.info(f"Verifying payment for order_id: {razorpay_order_id}")
258
  is_valid = verify_payment_signature(razorpay_order_id, razorpay_payment_id, razorpay_signature)
 
259
  if is_valid:
260
  if user_id and supabase:
261
  logger.info(f"Updating Supabase for user_id: {user_id}")
 
264
  logger.info(f"Successfully updated premium status for user: {user_id}")
265
  except Exception as e:
266
  logger.error(f"Failed to update Supabase: {str(e)}")
 
 
267
  return JSONResponse(content={"success": True, "message": "Payment verified successfully"})
268
  else:
269
  logger.warning(f"Payment verification failed for order: {razorpay_order_id}")
270
  return JSONResponse(content={"success": False, "message": "Payment verification failed"}, status_code=400)
 
271
  except HTTPException:
272
  raise
273
  except Exception as e:
 
277
  @app.get("/")
278
  async def index():
279
  return {
280
+ "status": "Gemini 2.5 Flash Image Generator API with Razorpay is running",
281
  "endpoints": {
282
  "generate": "POST /generate",
283
  "create_order": "POST /create-razorpay-order",
 
293
  "supabase_configured": bool(SUPABASE_URL and SUPABASE_KEY)
294
  }
295
 
296
+ def create_razorpay_order(amount: int):
297
+ """Create a Razorpay order"""
298
+ try:
299
+ if amount <= 0:
300
+ raise ValueError("Amount must be a positive integer")
301
+ order_data = {
302
+ "amount": amount * 100, # Convert INR to paise
303
+ "currency": "INR",
304
+ "payment_capture": 1 # Auto-capture payment
305
+ }
306
+ order = razorpay_client.order.create(data=order_data)
307
+ logger.info(f"Razorpay order created successfully: {order['id']}")
308
+ return order
309
+ except Exception as e:
310
+ logger.error(f"Failed to create Razorpay order: {str(e)}")
311
+ raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
312
+
313
+ def verify_payment_signature(order_id: str, payment_id: str, signature: str):
314
+ """Verify Razorpay payment signature"""
315
+ try:
316
+ params_dict = {
317
+ "razorpay_order_id": order_id,
318
+ "razorpay_payment_id": payment_id,
319
+ "razorpay_signature": signature
320
+ }
321
+ razorpay_client.utility.verify_payment_signature(params_dict)
322
+ logger.info(f"Payment signature verified successfully for order: {order_id}")
323
+ return True
324
+ except SignatureVerificationError as e:
325
+ logger.error(f"Payment signature verification failed: {str(e)}")
326
+ return False
327
+ except Exception as e:
328
+ logger.error(f"Error verifying payment signature: {str(e)}")
329
+ raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
330
+
331
  if __name__ == "__main__":
332
  import uvicorn
333
  uvicorn.run(app, host="0.0.0.0", port=7860)