Files
cloudlense/website-monitoring-frontend/src/app/api/organization/members/route.ts
T
Dennis 14a32bdc0d feat: initialize monorepo with full dev team best practices
- Unified monorepo with backend (Express), frontend (Next.js), and devops
- Backend: ESLint, Prettier, Jest tests (3 passing), health endpoint, .env.example
- Frontend: Fixed build errors, fixed all lint errors (0 remaining), tests passing
- DevOps: Docker Compose with PostgreSQL, backend, frontend + healthchecks
- CI/CD: 3 GitHub Actions workflows (backend, frontend, docker integration)
- DX: Husky pre-commit hooks with smart change detection
- Docs: Root README with architecture, CONTRIBUTING.md, PR template

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-06 00:05:50 +01:00

265 lines
8.7 KiB
TypeScript

import { NextResponse } from "next/server";
import { getSupabaseAdmin } from "@/lib/admin";
export async function GET(request: Request) {
try {
const url = new URL(request.url);
const organizationId = url.searchParams.get("organizationId");
const userId = url.searchParams.get("userId");
if (!organizationId || !userId) {
return NextResponse.json({ error: "Organization ID and User ID are required" }, { status: 400 });
}
// Verify user has access to this organization
const { data: userOrg, error: accessError } = await getSupabaseAdmin()
.from("users")
.select("organization_id, role")
.eq("id", userId)
.eq("organization_id", organizationId)
.single();
if (accessError || !userOrg) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
// Get all members of the organization
const { data: members, error: membersError } = await getSupabaseAdmin()
.from("users")
.select("id, name, email, role, created_at")
.eq("organization_id", organizationId)
.order("created_at", { ascending: true });
if (membersError) {
console.error("Error fetching members:", membersError);
return NextResponse.json({ error: "Failed to fetch members" }, { status: 500 });
}
return NextResponse.json({ members });
} catch (error) {
console.error("Error in members GET:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
export async function POST(request: Request) {
try {
const { organizationId, email, role, invitedBy } = await request.json();
if (!organizationId || !email || !role || !invitedBy) {
return NextResponse.json({
error: "Organization ID, email, role, and inviter ID are required"
}, { status: 400 });
}
// Verify inviter has permission (must be owner or admin)
const { data: inviter, error: inviterError } = await getSupabaseAdmin()
.from("users")
.select("organization_id, role")
.eq("id", invitedBy)
.eq("organization_id", organizationId)
.single();
if (inviterError || !inviter) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
if (inviter.role !== "owner" && inviter.role !== "admin") {
return NextResponse.json({
error: "Only owners and admins can invite members"
}, { status: 403 });
}
// Check if user already exists in the system
const { data: existingUsers, error: userCheckError } = await getSupabaseAdmin()
.auth.admin.listUsers();
if (userCheckError) {
console.error("Error checking existing users:", userCheckError);
return NextResponse.json({ error: "Failed to check existing users" }, { status: 500 });
}
const existingUser = existingUsers.users.find(u => u.email === email);
if (existingUser) {
// Check if user is already in an organization
const { data: userRecord } = await getSupabaseAdmin()
.from("users")
.select("organization_id")
.eq("id", existingUser.id)
.single();
if (userRecord?.organization_id) {
if (userRecord.organization_id === organizationId) {
return NextResponse.json({
error: "User is already a member of this organization"
}, { status: 400 });
} else {
return NextResponse.json({
error: "User is already a member of another organization"
}, { status: 400 });
}
}
// Add existing user to organization
const { error: updateError } = await getSupabaseAdmin()
.from("users")
.update({ organization_id: organizationId, role })
.eq("id", existingUser.id);
if (updateError) {
console.error("Error adding existing user to organization:", updateError);
return NextResponse.json({ error: "Failed to add user to organization" }, { status: 500 });
}
// Get updated user data
const { data: updatedUser } = await getSupabaseAdmin()
.from("users")
.select("id, name, email, role, created_at")
.eq("id", existingUser.id)
.single();
return NextResponse.json({
member: updatedUser,
message: "Existing user added to organization"
});
} else {
// Create invitation record for new user
// Note: In a real app, you'd send an email invitation here
// For now, we'll just create a placeholder record
return NextResponse.json({
message: "Invitation would be sent to new user",
action: "invitation_sent"
});
}
} catch (error) {
console.error("Error in members POST:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
export async function PUT(request: Request) {
try {
const { memberId, role, updatedBy, organizationId } = await request.json();
if (!memberId || !role || !updatedBy || !organizationId) {
return NextResponse.json({
error: "Member ID, role, updater ID, and organization ID are required"
}, { status: 400 });
}
// Verify updater has permission (must be owner)
const { data: updater, error: updaterError } = await getSupabaseAdmin()
.from("users")
.select("organization_id, role")
.eq("id", updatedBy)
.eq("organization_id", organizationId)
.single();
if (updaterError || !updater) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
if (updater.role !== "owner") {
return NextResponse.json({
error: "Only owners can update member roles"
}, { status: 403 });
}
// Don't allow changing the role of the organization owner
const { data: targetMember } = await getSupabaseAdmin()
.from("users")
.select("role")
.eq("id", memberId)
.single();
if (targetMember?.role === "owner" && role !== "owner") {
return NextResponse.json({
error: "Cannot change the role of the organization owner"
}, { status: 400 });
}
// Update member role
const { data: updatedMember, error: updateError } = await getSupabaseAdmin()
.from("users")
.update({ role })
.eq("id", memberId)
.eq("organization_id", organizationId)
.select("id, name, email, role, created_at")
.single();
if (updateError) {
console.error("Error updating member role:", updateError);
return NextResponse.json({ error: "Failed to update member role" }, { status: 500 });
}
return NextResponse.json({ member: updatedMember });
} catch (error) {
console.error("Error in members PUT:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
export async function DELETE(request: Request) {
try {
const url = new URL(request.url);
const memberId = url.searchParams.get("memberId");
const removedBy = url.searchParams.get("removedBy");
const organizationId = url.searchParams.get("organizationId");
if (!memberId || !removedBy || !organizationId) {
return NextResponse.json({
error: "Member ID, remover ID, and organization ID are required"
}, { status: 400 });
}
// Verify remover has permission (must be owner or admin)
const { data: remover, error: removerError } = await getSupabaseAdmin()
.from("users")
.select("organization_id, role")
.eq("id", removedBy)
.eq("organization_id", organizationId)
.single();
if (removerError || !remover) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
if (remover.role !== "owner" && remover.role !== "admin") {
return NextResponse.json({
error: "Only owners and admins can remove members"
}, { status: 403 });
}
// Don't allow removing the organization owner
const { data: targetMember } = await getSupabaseAdmin()
.from("users")
.select("role")
.eq("id", memberId)
.single();
if (targetMember?.role === "owner") {
return NextResponse.json({
error: "Cannot remove the organization owner"
}, { status: 400 });
}
// Remove member from organization (set organization_id to null)
const { error: removeError } = await getSupabaseAdmin()
.from("users")
.update({ organization_id: null, role: "member" })
.eq("id", memberId)
.eq("organization_id", organizationId);
if (removeError) {
console.error("Error removing member:", removeError);
return NextResponse.json({ error: "Failed to remove member" }, { status: 500 });
}
return NextResponse.json({ success: true });
} catch (error) {
console.error("Error in members DELETE:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}