-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
147 lines (127 loc) · 4.59 KB
/
server.py
File metadata and controls
147 lines (127 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
from fastapi import FastAPI, BackgroundTasks, Response
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
import os
from main import run_job_agent, scraping_status
from config import OUTPUT_FILENAME
from database import init_db, get_all_jobs, get_stats
app = FastAPI()
@app.get("/api/health")
async def health_check():
return {"status": "ok", "environment": "vercel" if os.environ.get("VERCEL") else "local"}
# SaaS Setup: Initialize Database
init_db()
# Enable CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
from pydantic import BaseModel
from typing import List, Optional
class SearchFilters(BaseModel):
roles: List[str]
location: str
language: str # 'English', 'German', or 'Both'
# Shared state
scraping_state = {
"active": False,
"progress": 0,
"message": "Idle",
"job_count": 0,
"ready_to_download": False,
"current_role": "",
"filters": None
}
@app.get("/api/status")
async def get_status():
return scraping_state
async def status_updater(new_state):
global scraping_state
scraping_state.update(new_state)
async def search_task(roles: List[str], location: str, language: str):
global scraping_state
report_path = await run_job_agent(
roles=roles,
location=location,
language=language,
status_callback=status_updater
)
if report_path:
scraping_state["ready_to_download"] = True
else:
scraping_state["ready_to_download"] = False
scraping_state["active"] = False
@app.post("/api/start-search")
async def start_search(filters: SearchFilters, background_tasks: BackgroundTasks):
global scraping_state
if scraping_state["active"]:
return {"error": "Search already in progress"}
if os.environ.get("VERCEL"):
scraping_state = {
"active": False,
"progress": 0,
"message": "Scraping not supported on Vercel Serverless. Run locally for full functionality.",
"job_count": 0,
"ready_to_download": False,
"current_role": "",
"filters": filters.dict()
}
return {"message": "Scraping not supported on Vercel", "unsupported": True}
scraping_state = {
"active": True,
"progress": 0,
"message": "Starting...",
"job_count": 0,
"ready_to_download": False,
"current_role": "",
"filters": filters.dict()
}
background_tasks.add_task(search_task, filters.roles, filters.location, filters.language)
return {"message": "Search started"}
@app.get("/api/download-report")
async def download_report():
print(f"DEBUG: Export download requested. File exists: {os.path.exists(OUTPUT_FILENAME)}")
if os.path.exists(OUTPUT_FILENAME):
with open(OUTPUT_FILENAME, "rb") as f:
file_content = f.read()
# Manually construct the response to avoid any auto-header conflicts
return Response(
content=file_content,
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={
"Content-Disposition": 'attachment; filename="jobs_report.xlsx"',
"Content-Length": str(len(file_content)),
"Cache-Control": "no-cache, no-store, must-revalidate",
}
)
return {"error": "File not found"}
# --- SAAS ENDPOINTS ---
@app.get("/api/stats")
async def get_db_stats():
"""Returns total jobs and scans from DB."""
return get_stats()
@app.get("/api/jobs")
async def get_saved_jobs(limit: int = 50):
"""Returns recent job leads from DB."""
return get_all_jobs(limit=limit)
# Serve React production build only when not on Vercel
# Vercel handles static file serving via vercel.json rewrites
if not os.environ.get("VERCEL"):
react_build_path = os.path.join(os.getcwd(), "web-app", "dist")
if os.path.exists(react_build_path):
@app.get("/")
async def serve_index():
return FileResponse(os.path.join(react_build_path, "index.html"))
app.mount("/", StaticFiles(directory=react_build_path), name="static")
else:
print(f"Warning: React build at {react_build_path} not found.")
if os.path.exists("static"):
app.mount("/", StaticFiles(directory="static", html=True), name="static")
if __name__ == "__main__":
import uvicorn
# Kill any existing server on 8000
os.system("pkill -f 'uvicorn server:app' || true")
uvicorn.run(app, host="0.0.0.0", port=8000)