Maksymilian Jankowski commited on
Commit
ea12465
·
1 Parent(s): ccf7dda

deploy fix

Browse files
Files changed (4) hide show
  1. Dockerfile +11 -0
  2. check_deployment.py +145 -0
  3. main.py +54 -21
  4. requirements.txt +4 -63
Dockerfile CHANGED
@@ -39,5 +39,16 @@ EXPOSE 7860
39
  ENV PYTHONUNBUFFERED=1
40
  ENV PORT=7860
41
 
 
 
 
 
 
 
 
 
 
 
 
42
  # Command to run the application
43
  CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
39
  ENV PYTHONUNBUFFERED=1
40
  ENV PORT=7860
41
 
42
+ # Add environment variable validation
43
+ ENV SUPABASE_URL=${SUPABASE_URL}
44
+ ENV SUPABASE_KEY=${SUPABASE_KEY}
45
+ ENV JWT_SECRET_KEY=${JWT_SECRET_KEY:-your-secret-key-change-this-in-production}
46
+ ENV OPENAI_API_KEY=${OPENAI_API_KEY}
47
+ ENV STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
48
+
49
+ # Add health check
50
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
51
+ CMD curl -f http://localhost:7860/ || exit 1
52
+
53
  # Command to run the application
54
  CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
check_deployment.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Deployment troubleshooting script for 3DAI API Backend
4
+ Run this script to check your deployment configuration
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from dotenv import load_dotenv
10
+
11
+ def check_environment_variables():
12
+ """Check if all required environment variables are set"""
13
+ print("🔍 Checking Environment Variables...")
14
+
15
+ required_vars = [
16
+ "SUPABASE_URL",
17
+ "SUPABASE_KEY",
18
+ "OPENAI_API_KEY",
19
+ "STRIPE_SECRET_KEY"
20
+ ]
21
+
22
+ optional_vars = [
23
+ "JWT_SECRET_KEY"
24
+ ]
25
+
26
+ missing_vars = []
27
+
28
+ for var in required_vars:
29
+ value = os.getenv(var)
30
+ if not value:
31
+ missing_vars.append(var)
32
+ print(f"❌ {var}: Not set")
33
+ else:
34
+ # Show partial value for security
35
+ masked_value = value[:8] + "..." if len(value) > 8 else "***"
36
+ print(f"✅ {var}: {masked_value}")
37
+
38
+ for var in optional_vars:
39
+ value = os.getenv(var)
40
+ if not value:
41
+ print(f"⚠️ {var}: Not set (using default)")
42
+ else:
43
+ masked_value = value[:8] + "..." if len(value) > 8 else "***"
44
+ print(f"✅ {var}: {masked_value}")
45
+
46
+ return missing_vars
47
+
48
+ def check_dependencies():
49
+ """Check if all required Python packages are installed"""
50
+ print("\n📦 Checking Dependencies...")
51
+
52
+ required_packages = [
53
+ "fastapi",
54
+ "uvicorn",
55
+ "supabase",
56
+ "httpx",
57
+ "stripe",
58
+ "pydantic",
59
+ "python-jose",
60
+ "passlib"
61
+ ]
62
+
63
+ missing_packages = []
64
+
65
+ for package in required_packages:
66
+ try:
67
+ __import__(package.replace("-", "_"))
68
+ print(f"✅ {package}: Installed")
69
+ except ImportError:
70
+ missing_packages.append(package)
71
+ print(f"❌ {package}: Not installed")
72
+
73
+ return missing_packages
74
+
75
+ def check_supabase_connection():
76
+ """Test Supabase connection"""
77
+ print("\n🗄️ Testing Supabase Connection...")
78
+
79
+ try:
80
+ from supabase import create_client
81
+
82
+ url = os.getenv("SUPABASE_URL")
83
+ key = os.getenv("SUPABASE_KEY")
84
+
85
+ if not url or not key:
86
+ print("❌ Supabase credentials not set")
87
+ return False
88
+
89
+ client = create_client(url, key)
90
+
91
+ # Test a simple query
92
+ result = client.from_("User").select("user_id").limit(1).execute()
93
+ print("✅ Supabase connection successful")
94
+ return True
95
+
96
+ except Exception as e:
97
+ print(f"❌ Supabase connection failed: {str(e)}")
98
+ return False
99
+
100
+ def main():
101
+ """Main troubleshooting function"""
102
+ print("🚀 3DAI API Backend Deployment Check")
103
+ print("=" * 50)
104
+
105
+ # Load environment variables
106
+ load_dotenv()
107
+
108
+ # Check environment variables
109
+ missing_env_vars = check_environment_variables()
110
+
111
+ # Check dependencies
112
+ missing_packages = check_dependencies()
113
+
114
+ # Check Supabase connection
115
+ supabase_ok = check_supabase_connection()
116
+
117
+ # Summary
118
+ print("\n📋 Summary:")
119
+ print("=" * 30)
120
+
121
+ if missing_env_vars:
122
+ print(f"❌ Missing environment variables: {', '.join(missing_env_vars)}")
123
+ print(" Fix: Set these in your deployment environment or .env file")
124
+
125
+ if missing_packages:
126
+ print(f"❌ Missing packages: {', '.join(missing_packages)}")
127
+ print(" Fix: Run 'pip install -r requirements.txt'")
128
+
129
+ if not supabase_ok:
130
+ print("❌ Supabase connection failed")
131
+ print(" Fix: Check your SUPABASE_URL and SUPABASE_KEY")
132
+
133
+ if not missing_env_vars and not missing_packages and supabase_ok:
134
+ print("✅ All checks passed! Your deployment should work.")
135
+ print("\n🔧 If sync-supabase-user still doesn't work, check:")
136
+ print(" 1. Network connectivity to your deployed service")
137
+ print(" 2. API endpoint URL is correct")
138
+ print(" 3. Check application logs for specific errors")
139
+ print(" 4. Verify your Supabase database schema matches the code")
140
+ else:
141
+ print("\n❌ Issues found. Fix the above problems and try again.")
142
+ sys.exit(1)
143
+
144
+ if __name__ == "__main__":
145
+ main()
main.py CHANGED
@@ -174,10 +174,17 @@ async def sync_supabase_user(request: SyncUserRequest, supabase_token: str = Dep
174
  """
175
  try:
176
  # Verify the Supabase token
 
177
  supabase_user = verify_supabase_token(supabase_token)
 
178
 
179
  # Check if user already exists in our database
180
- existing_user = supabase.from_("User").select("*").eq("user_id", supabase_user["id"]).execute()
 
 
 
 
 
181
 
182
  user_data = {
183
  "user_id": supabase_user["id"],
@@ -188,47 +195,73 @@ async def sync_supabase_user(request: SyncUserRequest, supabase_token: str = Dep
188
  }
189
 
190
  if existing_user.data:
 
191
  # Update existing user with any new information
192
  if request.full_name:
193
- supabase.from_("User").update({
194
- "fullname": request.full_name
195
- }).eq("user_id", supabase_user["id"]).execute()
 
 
 
 
 
196
  else:
197
- # Create new user profile
198
- supabase.from_("User").insert(user_data).execute()
199
-
200
- # Initialize credits for new user
201
- supabase.from_("User_Credit_Account").insert({
202
- "user_id": supabase_user["id"],
203
- "num_of_available_gens": 3 # Give new users 3 free credits
204
- }).execute()
 
 
 
 
 
 
 
205
 
206
  # Generate backend JWT token
207
- backend_token = create_backend_jwt_token({
208
- "id": supabase_user["id"],
209
- "email": supabase_user["email"]
210
- })
 
 
 
 
 
211
 
212
  # Get updated user profile
213
- user_profile = supabase.from_("User").select("*").eq("user_id", supabase_user["id"]).single().execute()
 
 
 
 
 
214
 
215
- return {
216
  "access_token": backend_token,
217
  "token_type": "bearer",
218
  "user": {
219
  "id": supabase_user["id"],
220
  "email": supabase_user["email"],
221
- "profile": user_profile.data if user_profile.data else user_data
222
  },
223
  "message": "User synced successfully"
224
  }
225
 
 
 
 
226
  except HTTPException:
227
  # Re-raise HTTP exceptions as-is
228
  raise
229
  except Exception as e:
230
- logging.error(f"Sync user error: {str(e)}")
231
- raise HTTPException(status_code=400, detail=f"Failed to sync user: {str(e)}")
232
 
233
  # TODO: The signup also creates a profile, so this endpoint should either be removed or /signup updated.
234
  @app.post("/auth/complete-profile", tags=["Authentication"])
 
174
  """
175
  try:
176
  # Verify the Supabase token
177
+ logging.info(f"Verifying Supabase token for user sync")
178
  supabase_user = verify_supabase_token(supabase_token)
179
+ logging.info(f"Token verified for user: {supabase_user.get('email', 'unknown')}")
180
 
181
  # Check if user already exists in our database
182
+ try:
183
+ existing_user = supabase.from_("User").select("*").eq("user_id", supabase_user["id"]).execute()
184
+ logging.info(f"Database query completed. Existing user found: {bool(existing_user.data)}")
185
+ except Exception as db_error:
186
+ logging.error(f"Database query failed: {str(db_error)}")
187
+ raise HTTPException(status_code=500, detail=f"Database error: {str(db_error)}")
188
 
189
  user_data = {
190
  "user_id": supabase_user["id"],
 
195
  }
196
 
197
  if existing_user.data:
198
+ logging.info("Updating existing user profile")
199
  # Update existing user with any new information
200
  if request.full_name:
201
+ try:
202
+ supabase.from_("User").update({
203
+ "fullname": request.full_name
204
+ }).eq("user_id", supabase_user["id"]).execute()
205
+ logging.info("User profile updated successfully")
206
+ except Exception as update_error:
207
+ logging.error(f"Failed to update user profile: {str(update_error)}")
208
+ # Don't fail the entire operation for profile update errors
209
  else:
210
+ logging.info("Creating new user profile")
211
+ try:
212
+ # Create new user profile
213
+ supabase.from_("User").insert(user_data).execute()
214
+ logging.info("User profile created successfully")
215
+
216
+ # Initialize credits for new user
217
+ supabase.from_("User_Credit_Account").insert({
218
+ "user_id": supabase_user["id"],
219
+ "num_of_available_gens": 3 # Give new users 3 free credits
220
+ }).execute()
221
+ logging.info("User credits initialized successfully")
222
+ except Exception as create_error:
223
+ logging.error(f"Failed to create user profile or credits: {str(create_error)}")
224
+ raise HTTPException(status_code=500, detail=f"Failed to create user profile: {str(create_error)}")
225
 
226
  # Generate backend JWT token
227
+ try:
228
+ backend_token = create_backend_jwt_token({
229
+ "id": supabase_user["id"],
230
+ "email": supabase_user["email"]
231
+ })
232
+ logging.info("Backend JWT token generated successfully")
233
+ except Exception as token_error:
234
+ logging.error(f"Failed to generate backend token: {str(token_error)}")
235
+ raise HTTPException(status_code=500, detail=f"Token generation failed: {str(token_error)}")
236
 
237
  # Get updated user profile
238
+ try:
239
+ user_profile = supabase.from_("User").select("*").eq("user_id", supabase_user["id"]).single().execute()
240
+ profile_data = user_profile.data if user_profile.data else user_data
241
+ except Exception as profile_error:
242
+ logging.warning(f"Failed to fetch updated profile: {str(profile_error)}")
243
+ profile_data = user_data # Use the original user_data as fallback
244
 
245
+ response_data = {
246
  "access_token": backend_token,
247
  "token_type": "bearer",
248
  "user": {
249
  "id": supabase_user["id"],
250
  "email": supabase_user["email"],
251
+ "profile": profile_data
252
  },
253
  "message": "User synced successfully"
254
  }
255
 
256
+ logging.info("User sync completed successfully")
257
+ return response_data
258
+
259
  except HTTPException:
260
  # Re-raise HTTP exceptions as-is
261
  raise
262
  except Exception as e:
263
+ logging.error(f"Unexpected error in sync_supabase_user: {str(e)}", exc_info=True)
264
+ raise HTTPException(status_code=500, detail=f"Internal server error during user sync: {str(e)}")
265
 
266
  # TODO: The signup also creates a profile, so this endpoint should either be removed or /signup updated.
267
  @app.post("/auth/complete-profile", tags=["Authentication"])
requirements.txt CHANGED
@@ -8,66 +8,7 @@ openai
8
  supabase>=2.0.0
9
  python-jose[cryptography]>=3.3.0
10
  passlib[bcrypt]>=1.7.4
11
- stripe>=5.0.0annotated-types==0.7.0
12
- anyio==4.9.0
13
- fastapi==0.115.12
14
- idna==3.10
15
- pydantic==2.11.5
16
- pydantic_core==2.33.2
17
- sniffio==1.3.1
18
- starlette==0.46.2
19
- typing-inspection==0.4.1
20
- typing_extensions==4.14.0
21
- aiohappyeyeballs==2.6.1
22
- aiohttp==3.12.11
23
- aiosignal==1.3.2
24
- annotated-types==0.7.0
25
- anyio==4.9.0
26
- attrs==25.3.0
27
- certifi==2025.4.26
28
- charset-normalizer==3.4.2
29
- click==8.2.1
30
- colorama==0.4.6
31
- deprecation==2.1.0
32
- dotenv==0.9.9
33
- fastapi==0.115.12
34
- frozenlist==1.6.2
35
- gotrue==2.12.0
36
- h11==0.16.0
37
- h2==4.2.0
38
- hpack==4.1.0
39
- httpcore==1.0.9
40
- httpx==0.28.1
41
- hyperframe==6.1.0
42
- idna==3.10
43
- iniconfig==2.1.0
44
- multidict==6.4.4
45
- packaging==25.0
46
- pluggy==1.6.0
47
- postgrest==1.0.2
48
- propcache==0.3.1
49
- pydantic==2.11.5
50
- pydantic_core==2.33.2
51
- Pygments==2.19.1
52
- PyJWT==2.10.1
53
- pytest==8.4.0
54
- pytest-mock==3.14.1
55
- python-dateutil==2.9.0.post0
56
- python-dotenv==1.1.0
57
- python-multipart==0.0.20
58
- realtime==2.4.3
59
- requests==2.32.3
60
- six==1.17.0
61
- sniffio==1.3.1
62
- starlette==0.46.2
63
- storage3==0.11.3
64
- StrEnum==0.4.15
65
- stripe==12.2.0
66
- supabase==2.15.2
67
- supafunc==0.9.4
68
- typing-inspection==0.4.1
69
- typing_extensions==4.14.0
70
- urllib3==2.4.0
71
- uvicorn==0.34.3
72
- websockets==14.2
73
- yarl==1.20.0
 
8
  supabase>=2.0.0
9
  python-jose[cryptography]>=3.3.0
10
  passlib[bcrypt]>=1.7.4
11
+ stripe>=5.0.0
12
+ python-multipart>=0.0.20
13
+ requests>=2.32.3
14
+ PyJWT>=2.10.1