yashgori20 commited on
Commit
714150d
Β·
verified Β·
1 Parent(s): cfa3103

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +18 -0
  2. README.md +86 -12
  3. app.py +343 -0
  4. app_interface.py +61 -0
  5. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy requirements first for better caching
6
+ COPY requirements.txt .
7
+
8
+ # Install dependencies
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy application code
12
+ COPY . .
13
+
14
+ # Expose port 7860 (Hugging Face Spaces standard)
15
+ EXPOSE 7860
16
+
17
+ # Run the application
18
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,12 +1,86 @@
1
- ---
2
- title: Get Me A Job Ap
3
- emoji: 😻
4
- colorFrom: purple
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 5.42.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Get Me A Job - Backend API
2
+
3
+ FastAPI backend with Firebase integration for the Get Me A Job application.
4
+
5
+ ## Setup Instructions
6
+
7
+ ### 1. Firebase Setup
8
+
9
+ 1. Go to [Firebase Console](https://console.firebase.google.com/)
10
+ 2. Create a new project: **"get-me-a-job-backend"**
11
+ 3. Enable **Firestore Database**
12
+ 4. Enable **Authentication** (Email/Password)
13
+ 5. Go to **Project Settings** β†’ **Service Accounts**
14
+ 6. Click **"Generate new private key"**
15
+ 7. Download the JSON file
16
+
17
+ ### 2. Hugging Face Spaces Deployment
18
+
19
+ 1. Create new Space at [huggingface.co/spaces](https://huggingface.co/spaces)
20
+ 2. Name: **"yashgori20/get-me-a-job-api"**
21
+ 3. Framework: **Gradio** (we'll override with FastAPI)
22
+ 4. Upload these files to the Space
23
+
24
+ ### 3. Environment Variables
25
+
26
+ Add these secrets in Hugging Face Spaces settings:
27
+
28
+ ```bash
29
+ # Firebase Service Account (paste entire JSON content)
30
+ FIREBASE_SERVICE_ACCOUNT={"type":"service_account","project_id":"your-project-id",...}
31
+
32
+ # JWT Secret (generate a random string)
33
+ JWT_SECRET=your-super-secret-jwt-key-here
34
+ ```
35
+
36
+ ### 4. Database Collections
37
+
38
+ Firebase will auto-create these collections:
39
+
40
+ - **users**: User accounts
41
+ - **subscriptions**: User subscription plans
42
+ - **usage**: Usage tracking for free tier
43
+
44
+ ## API Endpoints
45
+
46
+ - `POST /getmeajob/api/auth/register` - User registration
47
+ - `POST /getmeajob/api/auth/login` - User login
48
+ - `GET /getmeajob/api/auth/verify` - Token verification
49
+ - `GET /getmeajob/api/subscription/status` - Check subscription
50
+ - `GET /getmeajob/api/check-updates` - App update check
51
+ - `POST /getmeajob/api/usage/track` - Track API usage
52
+
53
+ ## Local Development
54
+
55
+ ```bash
56
+ # Install dependencies
57
+ pip install -r requirements.txt
58
+
59
+ # Add firebase-service-account.json file
60
+
61
+ # Run the server
62
+ python app.py
63
+ ```
64
+
65
+ Server will run on: http://localhost:7860
66
+
67
+ ## Frontend Integration
68
+
69
+ Your Electron app is already configured to use:
70
+ `https://yashgori20-get-me-a-job-api.hf.space/getmeajob/api/`
71
+
72
+ Just deploy this backend and it will work immediately!
73
+
74
+ ## Security Features
75
+
76
+ - JWT token authentication
77
+ - Password hashing with bcrypt
78
+ - CORS protection
79
+ - Input validation with Pydantic
80
+ - Firebase security rules
81
+
82
+ ## Free Tier Limits
83
+
84
+ - 10 AI requests per user
85
+ - Email/password authentication
86
+ - Persistent data storage
app.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Depends, status
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel, EmailStr
5
+ import firebase_admin
6
+ from firebase_admin import credentials, firestore, auth
7
+ import jwt
8
+ import bcrypt
9
+ import os
10
+ from datetime import datetime, timedelta
11
+ from typing import Optional
12
+ import uvicorn
13
+ import json
14
+
15
+ # Initialize FastAPI app
16
+ app = FastAPI(title="Get Me A Job API", version="1.0.0")
17
+
18
+ # CORS middleware
19
+ app.add_middleware(
20
+ CORSMiddleware,
21
+ allow_origins=["*"], # In production, replace with specific domains
22
+ allow_credentials=True,
23
+ allow_methods=["*"],
24
+ allow_headers=["*"],
25
+ )
26
+
27
+ # Security
28
+ security = HTTPBearer()
29
+ JWT_SECRET = os.getenv("JWT_SECRET", "your-super-secret-key-change-in-production")
30
+ JWT_ALGORITHM = "HS256"
31
+
32
+ # Firebase setup
33
+ def init_firebase():
34
+ """Initialize Firebase Admin SDK"""
35
+ try:
36
+ # Try to get service account from environment variable
37
+ service_account_json = os.getenv("FIREBASE_SERVICE_ACCOUNT")
38
+ if service_account_json:
39
+ service_account_info = json.loads(service_account_json)
40
+ cred = credentials.Certificate(service_account_info)
41
+ else:
42
+ # Fallback to service account file (for local development)
43
+ cred = credentials.Certificate("firebase-service-account.json")
44
+
45
+ firebase_admin.initialize_app(cred)
46
+ print("Firebase initialized successfully")
47
+ except Exception as e:
48
+ print(f"Firebase initialization failed: {e}")
49
+ # For development, continue without Firebase
50
+ pass
51
+
52
+ # Initialize Firebase
53
+ init_firebase()
54
+
55
+ # Get Firestore client
56
+ try:
57
+ db = firestore.client()
58
+ except:
59
+ db = None
60
+ print("Firestore client not available - running in development mode")
61
+
62
+ # Pydantic models
63
+ class UserRegister(BaseModel):
64
+ email: EmailStr
65
+ password: str
66
+
67
+ class UserLogin(BaseModel):
68
+ email: EmailStr
69
+ password: str
70
+
71
+ class UserResponse(BaseModel):
72
+ id: str
73
+ email: str
74
+
75
+ class TokenResponse(BaseModel):
76
+ token: str
77
+ user: UserResponse
78
+
79
+ class SubscriptionResponse(BaseModel):
80
+ isPaid: bool
81
+ plan: str
82
+ usageLeft: Optional[int] = None
83
+ usageLimit: Optional[int] = None
84
+
85
+ class UpdateResponse(BaseModel):
86
+ latestVersion: str
87
+ downloadUrl: str
88
+
89
+ # Helper functions
90
+ def hash_password(password: str) -> str:
91
+ return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
92
+
93
+ def verify_password(password: str, hashed: str) -> bool:
94
+ return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
95
+
96
+ def create_jwt_token(user_id: str, email: str) -> str:
97
+ payload = {
98
+ 'user_id': user_id,
99
+ 'email': email,
100
+ 'exp': datetime.utcnow() + timedelta(days=7)
101
+ }
102
+ return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
103
+
104
+ def verify_jwt_token(token: str) -> dict:
105
+ try:
106
+ payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
107
+ return payload
108
+ except jwt.ExpiredSignatureError:
109
+ raise HTTPException(status_code=401, detail="Token expired")
110
+ except jwt.InvalidTokenError:
111
+ raise HTTPException(status_code=401, detail="Invalid token")
112
+
113
+ def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:
114
+ return verify_jwt_token(credentials.credentials)
115
+
116
+ # API Endpoints
117
+ @app.get("/")
118
+ async def root():
119
+ return {"message": "Get Me A Job API is running!", "firebase_status": "connected" if db else "disconnected"}
120
+
121
+ @app.post("/getmeajob/api/auth/register", response_model=TokenResponse)
122
+ async def register(user: UserRegister):
123
+ if not db:
124
+ raise HTTPException(status_code=503, detail="Database not available")
125
+
126
+ try:
127
+ # Check if user already exists
128
+ users_ref = db.collection('users')
129
+ existing_user = users_ref.where('email', '==', user.email).limit(1).get()
130
+
131
+ if existing_user:
132
+ raise HTTPException(status_code=400, detail="Email already exists")
133
+
134
+ # Hash password and create user
135
+ password_hash = hash_password(user.password)
136
+
137
+ # Create user document
138
+ user_data = {
139
+ 'email': user.email,
140
+ 'password_hash': password_hash,
141
+ 'created_at': firestore.SERVER_TIMESTAMP,
142
+ 'updated_at': firestore.SERVER_TIMESTAMP
143
+ }
144
+
145
+ # Add user to Firestore
146
+ user_ref = users_ref.add(user_data)
147
+ user_id = user_ref[1].id
148
+
149
+ # Create default subscription (free plan)
150
+ subscription_data = {
151
+ 'user_id': user_id,
152
+ 'plan': 'free',
153
+ 'is_active': True,
154
+ 'created_at': firestore.SERVER_TIMESTAMP,
155
+ 'expires_at': None
156
+ }
157
+ db.collection('subscriptions').add(subscription_data)
158
+
159
+ # Create usage record (10 free uses)
160
+ usage_data = {
161
+ 'user_id': user_id,
162
+ 'usage_count': 10,
163
+ 'last_reset': firestore.SERVER_TIMESTAMP,
164
+ 'created_at': firestore.SERVER_TIMESTAMP
165
+ }
166
+ db.collection('usage').add(usage_data)
167
+
168
+ # Generate JWT token
169
+ token = create_jwt_token(user_id, user.email)
170
+
171
+ return TokenResponse(
172
+ token=token,
173
+ user=UserResponse(id=user_id, email=user.email)
174
+ )
175
+
176
+ except Exception as e:
177
+ if "Email already exists" in str(e):
178
+ raise e
179
+ print(f"Registration error: {e}")
180
+ raise HTTPException(status_code=500, detail="Registration failed")
181
+
182
+ @app.post("/getmeajob/api/auth/login", response_model=TokenResponse)
183
+ async def login(user: UserLogin):
184
+ if not db:
185
+ raise HTTPException(status_code=503, detail="Database not available")
186
+
187
+ try:
188
+ # Get user by email
189
+ users_ref = db.collection('users')
190
+ user_query = users_ref.where('email', '==', user.email).limit(1).get()
191
+
192
+ if not user_query:
193
+ raise HTTPException(status_code=401, detail="Invalid credentials")
194
+
195
+ user_doc = user_query[0]
196
+ user_data = user_doc.to_dict()
197
+
198
+ # Verify password
199
+ if not verify_password(user.password, user_data['password_hash']):
200
+ raise HTTPException(status_code=401, detail="Invalid credentials")
201
+
202
+ # Generate JWT token
203
+ token = create_jwt_token(user_doc.id, user_data['email'])
204
+
205
+ return TokenResponse(
206
+ token=token,
207
+ user=UserResponse(id=user_doc.id, email=user_data['email'])
208
+ )
209
+
210
+ except HTTPException:
211
+ raise
212
+ except Exception as e:
213
+ print(f"Login error: {e}")
214
+ raise HTTPException(status_code=500, detail="Login failed")
215
+
216
+ @app.get("/getmeajob/api/auth/verify")
217
+ async def verify_token(current_user: dict = Depends(get_current_user)):
218
+ return {
219
+ "valid": True,
220
+ "user": {
221
+ "id": current_user["user_id"],
222
+ "email": current_user["email"]
223
+ }
224
+ }
225
+
226
+ @app.get("/getmeajob/api/subscription/status", response_model=SubscriptionResponse)
227
+ async def get_subscription_status(current_user: dict = Depends(get_current_user)):
228
+ if not db:
229
+ raise HTTPException(status_code=503, detail="Database not available")
230
+
231
+ try:
232
+ user_id = current_user["user_id"]
233
+
234
+ # Get subscription info
235
+ subscriptions_ref = db.collection('subscriptions')
236
+ subscription_query = subscriptions_ref.where('user_id', '==', user_id).where('is_active', '==', True).limit(1).get()
237
+
238
+ if not subscription_query:
239
+ # Create default free subscription if none exists
240
+ subscription_data = {
241
+ 'user_id': user_id,
242
+ 'plan': 'free',
243
+ 'is_active': True,
244
+ 'created_at': firestore.SERVER_TIMESTAMP,
245
+ 'expires_at': None
246
+ }
247
+ db.collection('subscriptions').add(subscription_data)
248
+ plan = 'free'
249
+ else:
250
+ subscription_doc = subscription_query[0]
251
+ subscription_data = subscription_doc.to_dict()
252
+ plan = subscription_data.get('plan', 'free')
253
+
254
+ is_paid = plan != 'free'
255
+
256
+ # Get usage info for free users
257
+ usage_left = None
258
+ usage_limit = None
259
+
260
+ if not is_paid:
261
+ usage_ref = db.collection('usage')
262
+ usage_query = usage_ref.where('user_id', '==', user_id).limit(1).get()
263
+
264
+ if usage_query:
265
+ usage_doc = usage_query[0]
266
+ usage_data = usage_doc.to_dict()
267
+ usage_left = max(0, usage_data.get('usage_count', 0))
268
+ usage_limit = 10 # Free tier limit
269
+ else:
270
+ # Create usage record if none exists
271
+ usage_data = {
272
+ 'user_id': user_id,
273
+ 'usage_count': 10,
274
+ 'last_reset': firestore.SERVER_TIMESTAMP,
275
+ 'created_at': firestore.SERVER_TIMESTAMP
276
+ }
277
+ db.collection('usage').add(usage_data)
278
+ usage_left = 10
279
+ usage_limit = 10
280
+
281
+ return SubscriptionResponse(
282
+ isPaid=is_paid,
283
+ plan=plan,
284
+ usageLeft=usage_left,
285
+ usageLimit=usage_limit
286
+ )
287
+
288
+ except Exception as e:
289
+ print(f"Subscription status error: {e}")
290
+ raise HTTPException(status_code=500, detail="Failed to get subscription status")
291
+
292
+ @app.get("/getmeajob/api/check-updates", response_model=UpdateResponse)
293
+ async def check_updates():
294
+ return UpdateResponse(
295
+ latestVersion="0.1.1", # Update this when you release new versions
296
+ downloadUrl="https://yashgori20.vercel.app/getmeajob/download"
297
+ )
298
+
299
+ # Usage tracking endpoint (for decrementing usage count)
300
+ @app.post("/getmeajob/api/usage/track")
301
+ async def track_usage(current_user: dict = Depends(get_current_user)):
302
+ if not db:
303
+ raise HTTPException(status_code=503, detail="Database not available")
304
+
305
+ try:
306
+ user_id = current_user["user_id"]
307
+
308
+ # Get current usage
309
+ usage_ref = db.collection('usage')
310
+ usage_query = usage_ref.where('user_id', '==', user_id).limit(1).get()
311
+
312
+ if usage_query:
313
+ usage_doc = usage_query[0]
314
+ usage_data = usage_doc.to_dict()
315
+ current_count = usage_data.get('usage_count', 0)
316
+
317
+ if current_count > 0:
318
+ # Decrement usage count
319
+ usage_doc.reference.update({
320
+ 'usage_count': current_count - 1,
321
+ 'updated_at': firestore.SERVER_TIMESTAMP
322
+ })
323
+ return {"success": True, "remaining": current_count - 1}
324
+ else:
325
+ return {"success": False, "error": "No usage left"}
326
+ else:
327
+ return {"success": False, "error": "Usage record not found"}
328
+
329
+ except Exception as e:
330
+ print(f"Usage tracking error: {e}")
331
+ raise HTTPException(status_code=500, detail="Failed to track usage")
332
+
333
+ # Health check endpoint
334
+ @app.get("/health")
335
+ async def health_check():
336
+ return {
337
+ "status": "healthy",
338
+ "timestamp": datetime.utcnow().isoformat(),
339
+ "firebase_connected": db is not None
340
+ }
341
+
342
+ if __name__ == "__main__":
343
+ uvicorn.run(app, host="0.0.0.0", port=7860) # Port 7860 is standard for Hugging Face Spaces
app_interface.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is required for Hugging Face Spaces
2
+ # It wraps our FastAPI app to work with Gradio interface
3
+
4
+ import gradio as gr
5
+ from app import app
6
+ import uvicorn
7
+ import threading
8
+ import time
9
+ import requests
10
+
11
+ # Start FastAPI server in background thread
12
+ def start_fastapi():
13
+ uvicorn.run(app, host="0.0.0.0", port=7860)
14
+
15
+ # Start the server
16
+ thread = threading.Thread(target=start_fastapi, daemon=True)
17
+ thread.start()
18
+
19
+ # Wait for server to start
20
+ time.sleep(3)
21
+
22
+ def test_api():
23
+ """Test function to verify API is working"""
24
+ try:
25
+ response = requests.get("http://localhost:7860/health")
26
+ if response.status_code == 200:
27
+ return f"βœ… API is running!\n\nHealth Check: {response.json()}\n\nAPI Base URL: https://yashgori20-get-me-a-job-api.hf.space/getmeajob/api/"
28
+ else:
29
+ return f"❌ API returned status code: {response.status_code}"
30
+ except Exception as e:
31
+ return f"❌ Error: {str(e)}"
32
+
33
+ # Create Gradio interface (required for Hugging Face Spaces)
34
+ with gr.Blocks(title="Get Me A Job API") as demo:
35
+ gr.Markdown("# πŸš€ Get Me A Job - Backend API")
36
+ gr.Markdown("FastAPI backend with Firebase integration")
37
+
38
+ with gr.Row():
39
+ test_btn = gr.Button("Test API Status", variant="primary")
40
+ result = gr.Textbox(label="API Status", lines=10)
41
+
42
+ test_btn.click(test_api, outputs=result)
43
+
44
+ # Auto-test on load
45
+ demo.load(test_api, outputs=result)
46
+
47
+ gr.Markdown("## API Endpoints")
48
+ gr.Markdown("""
49
+ - `POST /getmeajob/api/auth/register` - User registration
50
+ - `POST /getmeajob/api/auth/login` - User login
51
+ - `GET /getmeajob/api/auth/verify` - Token verification
52
+ - `GET /getmeajob/api/subscription/status` - Check subscription
53
+ - `GET /getmeajob/api/check-updates` - App update check
54
+ - `POST /getmeajob/api/usage/track` - Track API usage
55
+ """)
56
+
57
+ gr.Markdown("## Integration")
58
+ gr.Markdown("Your Electron app will connect to: `https://yashgori20-get-me-a-job-api.hf.space/getmeajob/api/`")
59
+
60
+ if __name__ == "__main__":
61
+ demo.launch(server_port=7860, share=True)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0
3
+ firebase-admin==6.2.0
4
+ pydantic[email]==2.5.0
5
+ python-multipart==0.0.6
6
+ pyjwt==2.8.0
7
+ bcrypt==4.0.1
8
+ python-dotenv==1.0.0