added missing email and email test files

This commit is contained in:
Khalim Conn-Kowlessar 2026-05-27 16:19:14 +00:00
parent c921db7d9c
commit 7e9193313b
3 changed files with 81 additions and 0 deletions

View file

@ -65,6 +65,69 @@ export async function GET(
}
}
// DELETE: remove a collaborator from this portfolio.
export async function DELETE(
req: NextRequest,
props: { params: Promise<{ portfolioId: string }> }
) {
const { portfolioId } = await props.params;
const session = await getServerSession(AuthOptions);
if (!session?.user?.dbId) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}
const bodySchema = z.object({ portfolioUserId: z.string() });
let body: z.infer<typeof bodySchema>;
try {
body = bodySchema.parse(await req.json());
} catch {
return NextResponse.json({ error: "Invalid body" }, { status: 400 });
}
try {
const pId = BigInt(portfolioId);
const puId = BigInt(body.portfolioUserId);
// Refuse to remove the creator — they own the portfolio.
const [target] = await db
.select({ id: portfolioUsers.id, role: portfolioUsers.role })
.from(portfolioUsers)
.where(
and(
eq(portfolioUsers.id, puId),
eq(portfolioUsers.portfolioId, pId),
),
)
.limit(1);
if (!target) {
return NextResponse.json(
{ error: "Membership not found in this portfolio" },
{ status: 404 },
);
}
if (target.role === "creator") {
return NextResponse.json(
{ error: "Cannot remove the portfolio creator" },
{ status: 400 },
);
}
await db.delete(portfolioUsers).where(eq(portfolioUsers.id, puId));
return NextResponse.json(
{ success: true, portfolioUserId: body.portfolioUserId },
{ status: 200 },
);
} catch (err) {
console.error("DELETE /colloborators error:", err);
return NextResponse.json(
{ error: "Failed to remove user" },
{ status: 500 },
);
}
}
// PUT: update a collaborators role
export async function PUT(
req: NextRequest,

15
src/app/lib/email.test.ts Normal file
View file

@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { normaliseEmail } from "./email";
describe("normaliseEmail", () => {
it("lowercases mixed-case addresses", () => {
expect(normaliseEmail("Craig.Williams@Example.com")).toBe(
"craig.williams@example.com",
);
});
it("trims surrounding whitespace (common from copy-paste into invite forms)", () => {
expect(normaliseEmail(" user@example.com ")).toBe("user@example.com");
expect(normaliseEmail("\tuser@example.com\n")).toBe("user@example.com");
});
});

3
src/app/lib/email.ts Normal file
View file

@ -0,0 +1,3 @@
export function normaliseEmail(email: string): string {
return email.trim().toLowerCase();
}