API handler for applications endpoints
cgen-b083d8cd14c042498e883b910da6368e
This commit is contained in:
parent
936fe8a3f8
commit
9f206d0534
1 changed files with 345 additions and 0 deletions
345
api/applications.ts
Normal file
345
api/applications.ts
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const supabaseUrl = process.env.VITE_SUPABASE_URL || "";
|
||||
const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE || "";
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceRole);
|
||||
|
||||
export async function getMyApplications(
|
||||
req: Request,
|
||||
userId: string
|
||||
) {
|
||||
const url = new URL(req.url);
|
||||
const page = parseInt(url.searchParams.get("page") || "1");
|
||||
const limit = parseInt(url.searchParams.get("limit") || "10");
|
||||
const status = url.searchParams.get("status");
|
||||
|
||||
try {
|
||||
// Get creator ID for this user
|
||||
const { data: creator } = await supabase
|
||||
.from("aethex_creators")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.single();
|
||||
|
||||
if (!creator) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Creator profile not found" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
let query = supabase
|
||||
.from("aethex_applications")
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
creator_id,
|
||||
opportunity_id,
|
||||
status,
|
||||
cover_letter,
|
||||
response_message,
|
||||
applied_at,
|
||||
updated_at,
|
||||
aethex_opportunities(id, title, arm_affiliation, job_type, posted_by_id, aethex_creators!aethex_opportunities_posted_by_id_fkey(username, avatar_url))
|
||||
`,
|
||||
{ count: "exact" }
|
||||
)
|
||||
.eq("creator_id", creator.id);
|
||||
|
||||
if (status) {
|
||||
query = query.eq("status", status);
|
||||
}
|
||||
|
||||
query = query.order("applied_at", { ascending: false });
|
||||
|
||||
const start = (page - 1) * limit;
|
||||
query = query.range(start, start + limit - 1);
|
||||
|
||||
const { data, error, count } = await query;
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
data,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: count || 0,
|
||||
pages: Math.ceil((count || 0) / limit),
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching applications:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Failed to fetch applications" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApplicationsForOpportunity(
|
||||
opportunityId: string,
|
||||
userId: string
|
||||
) {
|
||||
try {
|
||||
// Verify user owns this opportunity
|
||||
const { data: opportunity } = await supabase
|
||||
.from("aethex_opportunities")
|
||||
.select("posted_by_id")
|
||||
.eq("id", opportunityId)
|
||||
.single();
|
||||
|
||||
if (!opportunity) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Opportunity not found" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data: creator } = await supabase
|
||||
.from("aethex_creators")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.single();
|
||||
|
||||
if (creator?.id !== opportunity.posted_by_id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Unauthorized" }),
|
||||
{ status: 403, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("aethex_applications")
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
creator_id,
|
||||
status,
|
||||
cover_letter,
|
||||
applied_at,
|
||||
aethex_creators(username, avatar_url, bio, skills)
|
||||
`
|
||||
)
|
||||
.eq("opportunity_id", opportunityId)
|
||||
.order("applied_at", { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return new Response(JSON.stringify({ data }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching opportunity applications:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Failed to fetch applications" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitApplication(
|
||||
req: Request,
|
||||
userId: string
|
||||
) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { opportunity_id, cover_letter } = body;
|
||||
|
||||
// Get creator ID for this user
|
||||
const { data: creator } = await supabase
|
||||
.from("aethex_creators")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.single();
|
||||
|
||||
if (!creator) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Creator profile not found" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if opportunity exists and is open
|
||||
const { data: opportunity } = await supabase
|
||||
.from("aethex_opportunities")
|
||||
.select("id")
|
||||
.eq("id", opportunity_id)
|
||||
.eq("status", "open")
|
||||
.single();
|
||||
|
||||
if (!opportunity) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Opportunity not found or closed" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if already applied
|
||||
const { data: existing } = await supabase
|
||||
.from("aethex_applications")
|
||||
.select("id")
|
||||
.eq("creator_id", creator.id)
|
||||
.eq("opportunity_id", opportunity_id)
|
||||
.single();
|
||||
|
||||
if (existing) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "You have already applied to this opportunity" }),
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("aethex_applications")
|
||||
.insert({
|
||||
creator_id: creator.id,
|
||||
opportunity_id,
|
||||
cover_letter,
|
||||
status: "submitted",
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error submitting application:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Failed to submit application" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateApplicationStatus(
|
||||
req: Request,
|
||||
applicationId: string,
|
||||
userId: string
|
||||
) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { status, response_message } = body;
|
||||
|
||||
// Get the application and verify user owns the opportunity
|
||||
const { data: application } = await supabase
|
||||
.from("aethex_applications")
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
opportunity_id,
|
||||
aethex_opportunities(posted_by_id)
|
||||
`
|
||||
)
|
||||
.eq("id", applicationId)
|
||||
.single();
|
||||
|
||||
if (!application) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Application not found" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data: creator } = await supabase
|
||||
.from("aethex_creators")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.single();
|
||||
|
||||
if (creator?.id !== application.aethex_opportunities.posted_by_id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Unauthorized" }),
|
||||
{ status: 403, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("aethex_applications")
|
||||
.update({
|
||||
status,
|
||||
response_message,
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq("id", applicationId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating application:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Failed to update application" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function withdrawApplication(
|
||||
applicationId: string,
|
||||
userId: string
|
||||
) {
|
||||
try {
|
||||
// Verify user owns this application
|
||||
const { data: application } = await supabase
|
||||
.from("aethex_applications")
|
||||
.select("creator_id")
|
||||
.eq("id", applicationId)
|
||||
.single();
|
||||
|
||||
if (!application) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Application not found" }),
|
||||
{ status: 404, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data: creator } = await supabase
|
||||
.from("aethex_creators")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.single();
|
||||
|
||||
if (creator?.id !== application.creator_id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Unauthorized" }),
|
||||
{ status: 403, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
.from("aethex_applications")
|
||||
.delete()
|
||||
.eq("id", applicationId);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error withdrawing application:", error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Failed to withdraw application" }),
|
||||
{ status: 500, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue