Add agency plans: Starter(1000/55) Growth(3000/99) Scale(5000/499) Enterprise(10000/222) - no unlimited for agencies
This commit is contained in:
+140
-17
@@ -18,12 +18,20 @@ router = APIRouter(prefix="/autojobs/api")
|
||||
|
||||
# --- Plans & Pricing ---
|
||||
PLANS = {
|
||||
"free": {"name": "Free", "applications": 5, "ai_customize": 5, "price": 0},
|
||||
"starter": {"name": "Starter", "applications": 50, "ai_customize": 50, "price": 9},
|
||||
"pro": {"name": "Pro", "applications": 100, "ai_customize": 100, "price": 39},
|
||||
"ultra": {"name": "Ultra", "applications": 200, "ai_customize": 200, "price": 69},
|
||||
"business": {"name": "Business", "applications": 500, "ai_customize": 500, "price": 119},
|
||||
"unlimited": {"name": "Unlimited", "applications": 99999, "ai_customize": 99999, "price": 319},
|
||||
"free": {"name": "Free", "applications": 5, "ai_customize": 5, "price": 0, "type": "jobseeker"},
|
||||
"starter": {"name": "Starter", "applications": 50, "ai_customize": 50, "price": 9, "type": "jobseeker"},
|
||||
"pro": {"name": "Pro", "applications": 100, "ai_customize": 100, "price": 39, "type": "jobseeker"},
|
||||
"ultra": {"name": "Ultra", "applications": 200, "ai_customize": 200, "price": 69, "type": "jobseeker"},
|
||||
"business": {"name": "Business", "applications": 500, "ai_customize": 500, "price": 119, "type": "jobseeker"},
|
||||
"unlimited": {"name": "Unlimited", "applications": 99999, "ai_customize": 99999, "price": 319, "type": "jobseeker"},
|
||||
}
|
||||
|
||||
# Agency Plans (recruiting agencies - manage multiple clients)
|
||||
AGENCY_PLANS = {
|
||||
"agency_starter": {"name": "Agency Starter", "submissions": 1000, "clients": 10, "price": 555, "type": "agency"},
|
||||
"agency_growth": {"name": "Agency Growth", "submissions": 3000, "clients": 50, "price": 999, "type": "agency"},
|
||||
"agency_scale": {"name": "Agency Scale", "submissions": 5000, "clients": 150, "price": 1499, "type": "agency"},
|
||||
"agency_enterprise": {"name": "Agency Enterprise", "submissions": 10000, "clients": 500, "price": 2222, "type": "agency"},
|
||||
}
|
||||
|
||||
# --- Database ---
|
||||
@@ -76,6 +84,20 @@ def init_db():
|
||||
)
|
||||
""")
|
||||
|
||||
# Agency client profiles
|
||||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS client_profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
agency_id TEXT NOT NULL,
|
||||
client_name TEXT,
|
||||
client_email TEXT,
|
||||
profile_data TEXT DEFAULT '{}',
|
||||
submission_count INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (agency_id) REFERENCES users(id)
|
||||
)
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -111,8 +133,20 @@ def get_user_row(user_id: str):
|
||||
def check_plan_limit(user: dict, action: str, count: int = 1) -> dict:
|
||||
"""Check if user has reached their plan limit. Returns {"allowed": bool, "remaining": int}"""
|
||||
plan_name = user.get("plan", "free")
|
||||
|
||||
# Check if agency plan
|
||||
if plan_name in AGENCY_PLANS:
|
||||
plan = AGENCY_PLANS[plan_name]
|
||||
if action == "submission":
|
||||
used = user.get("monthly_count", 0)
|
||||
limit = plan["submissions"]
|
||||
else:
|
||||
return {"allowed": True, "remaining": 99999}
|
||||
remaining = max(0, limit - used)
|
||||
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used, "type": "agency"}
|
||||
|
||||
# Jobseeker plan
|
||||
plan = PLANS.get(plan_name, PLANS["free"])
|
||||
|
||||
if action == "application":
|
||||
used = user.get("monthly_count", 0)
|
||||
limit = plan["applications"]
|
||||
@@ -123,12 +157,12 @@ def check_plan_limit(user: dict, action: str, count: int = 1) -> dict:
|
||||
return {"allowed": True, "remaining": 99999}
|
||||
|
||||
remaining = max(0, limit - used)
|
||||
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used}
|
||||
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used, "type": "jobseeker"}
|
||||
|
||||
def increment_usage(user_id: str, action: str):
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
if action == "application":
|
||||
if action == "submission":
|
||||
c.execute("UPDATE users SET monthly_count = monthly_count + 1 WHERE id = ?", (user_id,))
|
||||
elif action == "ai_customize":
|
||||
c.execute("UPDATE users SET ai_customize_count = ai_customize_count + 1 WHERE id = ?", (user_id,))
|
||||
@@ -315,8 +349,11 @@ def get_profile(user=Depends(get_current_user)):
|
||||
|
||||
@router.get("/plans")
|
||||
def list_plans():
|
||||
"""List all available plans"""
|
||||
return {"plans": PLANS}
|
||||
"""List all available plans (jobseeker + agency)"""
|
||||
return {
|
||||
"jobseeker_plans": PLANS,
|
||||
"agency_plans": AGENCY_PLANS
|
||||
}
|
||||
|
||||
# --- Job Search ---
|
||||
@router.post("/jobs/search")
|
||||
@@ -504,17 +541,20 @@ def submit_application(app_data: ApplicationSubmit, user=Depends(get_current_use
|
||||
if not user_row:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# Check plan limit
|
||||
check = check_plan_limit(user_row, "application")
|
||||
# Check plan limit (use "submission" for agency, "application" for jobseeker)
|
||||
plan_name = user_row.get("plan", "free")
|
||||
action = "submission" if plan_name in AGENCY_PLANS else "application"
|
||||
check = check_plan_limit(user_row, action)
|
||||
if not check["allowed"]:
|
||||
raise HTTPException(
|
||||
status_code=402,
|
||||
detail={
|
||||
"error": "Application limit reached",
|
||||
"plan": user_row["plan"],
|
||||
"error": "Limit reached",
|
||||
"plan": plan_name,
|
||||
"limit": check["limit"],
|
||||
"used": check["used"],
|
||||
"message": f"You've applied to {check['used']} jobs this billing cycle. Upgrade your plan for more applications."
|
||||
"type": check.get("type", "jobseeker"),
|
||||
"message": f"You've used {check['used']} of your {check['limit']} submissions this billing cycle. Upgrade your plan for more."
|
||||
}
|
||||
)
|
||||
|
||||
@@ -541,7 +581,8 @@ def submit_application(app_data: ApplicationSubmit, user=Depends(get_current_use
|
||||
conn.close()
|
||||
|
||||
# Increment usage
|
||||
increment_usage(user["id"], "application")
|
||||
action = "submission" if plan_name in AGENCY_PLANS else "application"
|
||||
increment_usage(user["id"], action)
|
||||
|
||||
return {"id": app_id, "status": "submitted"}
|
||||
|
||||
@@ -648,6 +689,88 @@ def admin_applications(limit: int = 100):
|
||||
conn.close()
|
||||
return {"applications": [dict(r) for r in rows]}
|
||||
|
||||
# --- Agency Routes ---
|
||||
@router.post("/agency/clients")
|
||||
def create_client(client_data: dict, user=Depends(get_current_user)):
|
||||
"""Create a client profile under the agency account"""
|
||||
plan_name = user.get("id") # We need to check plan from user dict
|
||||
user_row = get_user_row(user["id"])
|
||||
if not user_row or user_row["plan"] not in AGENCY_PLANS:
|
||||
raise HTTPException(status_code=403, detail="Agency plan required")
|
||||
|
||||
plan = AGENCY_PLANS[user_row["plan"]]
|
||||
# Check client limit
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT COUNT(*) as count FROM client_profiles WHERE agency_id = ?", (user["id"],))
|
||||
current_clients = c.fetchone()["count"]
|
||||
|
||||
if current_clients >= plan["clients"]:
|
||||
conn.close()
|
||||
raise HTTPException(status_code=402, detail=f"Client limit reached. Your plan allows {plan['clients']} clients.")
|
||||
|
||||
client_id = str(uuid.uuid4())
|
||||
c.execute("""
|
||||
INSERT INTO client_profiles (id, agency_id, client_name, client_email, profile_data)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", (
|
||||
client_id,
|
||||
user["id"],
|
||||
client_data.get("name", ""),
|
||||
client_data.get("email", ""),
|
||||
json.dumps(client_data.get("profile", {}))
|
||||
))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"client_id": client_id, "name": client_data.get("name", "")}
|
||||
|
||||
@router.get("/agency/clients")
|
||||
def list_clients(user=Depends(get_current_user)):
|
||||
"""List all clients under agency account"""
|
||||
user_row = get_user_row(user["id"])
|
||||
if not user_row or user_row["plan"] not in AGENCY_PLANS:
|
||||
raise HTTPException(status_code=403, detail="Agency plan required")
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT * FROM client_profiles WHERE agency_id = ? ORDER BY created_at DESC", (user["id"],))
|
||||
rows = c.fetchall()
|
||||
conn.close()
|
||||
return {"clients": [dict(r) for r in rows]}
|
||||
|
||||
@router.get("/agency/usage")
|
||||
def agency_usage(user=Depends(get_current_user)):
|
||||
"""Get agency submission usage"""
|
||||
user_row = get_user_row(user["id"])
|
||||
if not user_row or user_row["plan"] not in AGENCY_PLANS:
|
||||
raise HTTPException(status_code=403, detail="Agency plan required")
|
||||
|
||||
plan = AGENCY_PLANS[user_row["plan"]]
|
||||
return {
|
||||
"plan": user_row["plan"],
|
||||
"submissions_limit": plan["submissions"],
|
||||
"submissions_used": user_row["monthly_count"],
|
||||
"submissions_remaining": max(0, plan["submissions"] - user_row["monthly_count"]),
|
||||
"clients_limit": plan["clients"],
|
||||
"clients_used": len([]), # Will be populated from DB
|
||||
}
|
||||
|
||||
@router.delete("/agency/clients/{client_id}")
|
||||
def delete_client(client_id: str, user=Depends(get_current_user)):
|
||||
"""Delete a client profile"""
|
||||
user_row = get_user_row(user["id"])
|
||||
if not user_row or user_row["plan"] not in AGENCY_PLANS:
|
||||
raise HTTPException(status_code=403, detail="Agency plan required")
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
c = conn.cursor()
|
||||
c.execute("DELETE FROM client_profiles WHERE id = ? AND agency_id = ?", (client_id, user["id"]))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"deleted": True}
|
||||
|
||||
@router.post("/admin/reset-usage")
|
||||
def admin_reset_usage(user_id: str = None):
|
||||
"""Admin: Reset usage counts (monthly billing cycle reset)"""
|
||||
|
||||
Reference in New Issue
Block a user