Add mentorship API endpoints
cgen-7f9d8f341cfb4280907e15c6536f8003
This commit is contained in:
parent
64ebedd07f
commit
1240174cee
1 changed files with 205 additions and 0 deletions
205
server/index.ts
205
server/index.ts
|
|
@ -1179,6 +1179,211 @@ export function createServer() {
|
||||||
return res.status(500).json({ error: e?.message || String(e) });
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mentorship API
|
||||||
|
app.get("/api/mentors", async (req, res) => {
|
||||||
|
const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 20));
|
||||||
|
const available = String(req.query.available || "true").toLowerCase() !== "false";
|
||||||
|
const expertise = String(req.query.expertise || "").trim();
|
||||||
|
const q = String(req.query.q || "").trim().toLowerCase();
|
||||||
|
try {
|
||||||
|
const { data, error } = await adminSupabase
|
||||||
|
.from("mentors")
|
||||||
|
.select(
|
||||||
|
`user_id, bio, expertise, available, hourly_rate, created_at, updated_at, user_profiles:user_id ( id, username, full_name, avatar_url, bio )`,
|
||||||
|
)
|
||||||
|
.eq("available", available)
|
||||||
|
.order("updated_at", { ascending: false })
|
||||||
|
.limit(limit);
|
||||||
|
if (error) return res.status(500).json({ error: error.message });
|
||||||
|
let rows = (data || []) as any[];
|
||||||
|
if (expertise) {
|
||||||
|
const terms = expertise
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim().toLowerCase())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (terms.length) {
|
||||||
|
rows = rows.filter((r: any) =>
|
||||||
|
Array.isArray(r.expertise) && r.expertise.some((e: string) => terms.includes(String(e).toLowerCase())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (q) {
|
||||||
|
rows = rows.filter((r: any) => {
|
||||||
|
const up = (r as any).user_profiles || {};
|
||||||
|
const name = String(up.full_name || up.username || "").toLowerCase();
|
||||||
|
const bio = String(r.bio || up.bio || "").toLowerCase();
|
||||||
|
return name.includes(q) || bio.includes(q);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.json(rows);
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/mentors/apply", async (req, res) => {
|
||||||
|
const { user_id, bio, expertise, hourly_rate, available } = (req.body || {}) as {
|
||||||
|
user_id?: string;
|
||||||
|
bio?: string | null;
|
||||||
|
expertise?: string[];
|
||||||
|
hourly_rate?: number | null;
|
||||||
|
available?: boolean | null;
|
||||||
|
};
|
||||||
|
if (!user_id) return res.status(400).json({ error: "user_id required" });
|
||||||
|
try {
|
||||||
|
const payload: any = {
|
||||||
|
user_id,
|
||||||
|
bio: bio ?? null,
|
||||||
|
expertise: Array.isArray(expertise) ? expertise : [],
|
||||||
|
available: available ?? true,
|
||||||
|
hourly_rate: typeof hourly_rate === "number" ? hourly_rate : null,
|
||||||
|
};
|
||||||
|
const { data, error } = await adminSupabase
|
||||||
|
.from("mentors")
|
||||||
|
.upsert(payload, { onConflict: "user_id" as any })
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
if (error) return res.status(500).json({ error: error.message });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await adminSupabase.from("notifications").insert({
|
||||||
|
user_id,
|
||||||
|
type: "success",
|
||||||
|
title: "Mentor profile updated",
|
||||||
|
message: "Your mentor availability and expertise are saved.",
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return res.json(data || payload);
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/mentorship/request", async (req, res) => {
|
||||||
|
const { mentee_id, mentor_id, message } = (req.body || {}) as {
|
||||||
|
mentee_id?: string;
|
||||||
|
mentor_id?: string;
|
||||||
|
message?: string | null;
|
||||||
|
};
|
||||||
|
if (!mentee_id || !mentor_id) {
|
||||||
|
return res.status(400).json({ error: "mentee_id and mentor_id required" });
|
||||||
|
}
|
||||||
|
if (mentee_id === mentor_id) {
|
||||||
|
return res.status(400).json({ error: "cannot request yourself" });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data, error } = await adminSupabase
|
||||||
|
.from("mentorship_requests")
|
||||||
|
.insert({ mentee_id, mentor_id, message: message || null } as any)
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
if (error) return res.status(500).json({ error: error.message });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: mentee } = await adminSupabase
|
||||||
|
.from("user_profiles")
|
||||||
|
.select("full_name, username")
|
||||||
|
.eq("id", mentee_id)
|
||||||
|
.maybeSingle();
|
||||||
|
const menteeName = (mentee as any)?.full_name || (mentee as any)?.username || "Someone";
|
||||||
|
await adminSupabase.from("notifications").insert({
|
||||||
|
user_id: mentor_id,
|
||||||
|
type: "info",
|
||||||
|
title: "Mentorship request",
|
||||||
|
message: `${menteeName} requested mentorship.`,
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return res.json({ ok: true, request: data });
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/mentorship/requests/:id/status", async (req, res) => {
|
||||||
|
const id = String(req.params.id || "");
|
||||||
|
const { actor_id, status } = (req.body || {}) as {
|
||||||
|
actor_id?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
if (!id || !actor_id || !status) {
|
||||||
|
return res.status(400).json({ error: "id, actor_id, status required" });
|
||||||
|
}
|
||||||
|
const allowed = ["accepted", "rejected", "cancelled"];
|
||||||
|
if (!allowed.includes(String(status))) {
|
||||||
|
return res.status(400).json({ error: "invalid status" });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data: reqRow, error } = await adminSupabase
|
||||||
|
.from("mentorship_requests")
|
||||||
|
.select("id, mentor_id, mentee_id, status")
|
||||||
|
.eq("id", id)
|
||||||
|
.maybeSingle();
|
||||||
|
if (error) return res.status(500).json({ error: error.message });
|
||||||
|
if (!reqRow) return res.status(404).json({ error: "not_found" });
|
||||||
|
|
||||||
|
const isMentor = (reqRow as any).mentor_id === actor_id;
|
||||||
|
const isMentee = (reqRow as any).mentee_id === actor_id;
|
||||||
|
if ((status === "accepted" || status === "rejected") && !isMentor) {
|
||||||
|
return res.status(403).json({ error: "forbidden" });
|
||||||
|
}
|
||||||
|
if (status === "cancelled" && !isMentee) {
|
||||||
|
return res.status(403).json({ error: "forbidden" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error: upErr } = await adminSupabase
|
||||||
|
.from("mentorship_requests")
|
||||||
|
.update({ status })
|
||||||
|
.eq("id", id)
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
if (upErr) return res.status(500).json({ error: upErr.message });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const target = status === "cancelled" ? (reqRow as any).mentor_id : (reqRow as any).mentee_id;
|
||||||
|
const title =
|
||||||
|
status === "accepted"
|
||||||
|
? "Mentorship accepted"
|
||||||
|
: status === "rejected"
|
||||||
|
? "Mentorship rejected"
|
||||||
|
: "Mentorship cancelled";
|
||||||
|
await adminSupabase.from("notifications").insert({
|
||||||
|
user_id: target,
|
||||||
|
type: "info",
|
||||||
|
title,
|
||||||
|
message: null,
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return res.json({ ok: true, request: data });
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/api/mentorship/requests", async (req, res) => {
|
||||||
|
const userId = String(req.query.user_id || "");
|
||||||
|
const role = String(req.query.role || "").toLowerCase();
|
||||||
|
if (!userId) return res.status(400).json({ error: "user_id required" });
|
||||||
|
try {
|
||||||
|
let query = adminSupabase
|
||||||
|
.from("mentorship_requests")
|
||||||
|
.select(
|
||||||
|
`*, mentor:user_profiles!mentorship_requests_mentor_id_fkey ( id, full_name, username, avatar_url ), mentee:user_profiles!mentorship_requests_mentee_id_fkey ( id, full_name, username, avatar_url )`,
|
||||||
|
)
|
||||||
|
.order("created_at", { ascending: false });
|
||||||
|
if (role === "mentor") query = query.eq("mentor_id", userId);
|
||||||
|
else if (role === "mentee") query = query.eq("mentee_id", userId);
|
||||||
|
else query = query.or(`mentor_id.eq.${userId},mentee_id.eq.${userId}`);
|
||||||
|
const { data, error } = await query;
|
||||||
|
if (error) return res.status(500).json({ error: error.message });
|
||||||
|
return res.json(data || []);
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).json({ error: e?.message || String(e) });
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Admin API not initialized:", e);
|
console.warn("Admin API not initialized:", e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue