140 lines
3.6 KiB
TypeScript
140 lines
3.6 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
|
|
import type { Database } from "@/lib/types";
|
|
import { createSupabaseAdminClient } from "@/lib/supabase/admin";
|
|
import { createSupabaseRouteClient } from "@/lib/supabase/route";
|
|
import { createSupabaseServerClient } from "@/lib/supabase/server";
|
|
|
|
export type AgencyContext = {
|
|
userId: string;
|
|
agencyId: string;
|
|
role: "owner" | "admin" | "member";
|
|
};
|
|
|
|
export function assertAgencyAdmin(context: AgencyContext) {
|
|
if (context.role !== "owner" && context.role !== "admin") {
|
|
throw new Error("Forbidden");
|
|
}
|
|
}
|
|
|
|
type AgencyMemberSummary = Pick<
|
|
Database["public"]["Tables"]["agency_members"]["Row"],
|
|
"agency_id" | "role"
|
|
>;
|
|
|
|
async function getOrCreateAgencyMembership(
|
|
userId: string,
|
|
email?: string | null,
|
|
) {
|
|
const supabase = createSupabaseServerClient() as any;
|
|
const admin = createSupabaseAdminClient() as any;
|
|
|
|
const { data: existingMembership } = (await supabase
|
|
.from("agency_members")
|
|
.select("agency_id, role")
|
|
.eq("user_id", userId)
|
|
.order("created_at", { ascending: true })
|
|
.limit(1)
|
|
.maybeSingle()) as { data: AgencyMemberSummary | null };
|
|
|
|
if (existingMembership) {
|
|
return existingMembership;
|
|
}
|
|
|
|
const defaultAgencyName = email
|
|
? `${email.split("@")[0] ?? "New"} Agency`
|
|
: "New Agency";
|
|
|
|
const { data: createdAgency, error: agencyError } = (await admin
|
|
.from("agencies")
|
|
.insert({ name: defaultAgencyName })
|
|
.select("id")
|
|
.single()) as {
|
|
data: Pick<Database["public"]["Tables"]["agencies"]["Row"], "id"> | null;
|
|
error: { message: string } | null;
|
|
};
|
|
|
|
if (agencyError || !createdAgency) {
|
|
throw new Error(
|
|
`Unable to create agency: ${agencyError?.message ?? "no row returned"}`,
|
|
);
|
|
}
|
|
|
|
const { error: membershipError } = await admin.from("agency_members").insert({
|
|
agency_id: createdAgency.id,
|
|
user_id: userId,
|
|
role: "owner",
|
|
});
|
|
|
|
if (!membershipError) {
|
|
return {
|
|
agency_id: createdAgency.id,
|
|
role: "owner" as const,
|
|
};
|
|
}
|
|
|
|
// If another request created membership first, fall back to a fresh read.
|
|
const { data: retriedMembership } = (await supabase
|
|
.from("agency_members")
|
|
.select("agency_id, role")
|
|
.eq("user_id", userId)
|
|
.order("created_at", { ascending: true })
|
|
.limit(1)
|
|
.maybeSingle()) as { data: AgencyMemberSummary | null };
|
|
|
|
if (retriedMembership) {
|
|
return retriedMembership;
|
|
}
|
|
|
|
throw new Error("Unable to create agency membership");
|
|
}
|
|
|
|
export async function getServerAgencyContextOrRedirect(): Promise<AgencyContext> {
|
|
const supabase = createSupabaseServerClient();
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser();
|
|
|
|
if (!user) {
|
|
redirect("/login");
|
|
}
|
|
|
|
const membership = await getOrCreateAgencyMembership(user.id, user.email);
|
|
|
|
return {
|
|
userId: user.id,
|
|
agencyId: membership.agency_id,
|
|
role: membership.role,
|
|
};
|
|
}
|
|
|
|
export async function getRouteAgencyContextOrThrow(): Promise<AgencyContext> {
|
|
const supabase = createSupabaseRouteClient();
|
|
const db = supabase as any;
|
|
const {
|
|
data: { session },
|
|
} = await supabase.auth.getSession();
|
|
|
|
if (!session?.user) {
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
const { data: membership } = (await db
|
|
.from("agency_members")
|
|
.select("agency_id, role")
|
|
.eq("user_id", session.user.id)
|
|
.order("created_at", { ascending: true })
|
|
.limit(1)
|
|
.maybeSingle()) as { data: AgencyMemberSummary | null };
|
|
|
|
if (!membership) {
|
|
throw new Error("No agency membership");
|
|
}
|
|
|
|
return {
|
|
userId: session.user.id,
|
|
agencyId: membership.agency_id,
|
|
role: membership.role,
|
|
};
|
|
}
|