Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/app/api/rooms/[roomId]/invite/route.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { getRoomById, getRoomMembers, addRoomMember } from '@/lib/supabase-rooms';
import { githubUsernamesEqual, normalizeRoomGithubUsername } from '@/lib/rooms';
import { NextResponse } from 'next/server';

export async function POST(
req: Request,
{ params }: { params: { roomId: string } }
) {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const room = await getRoomById(params.roomId, session.user.name);
const room = await getRoomById(params.roomId, session.githubLogin);
if (!room) return NextResponse.json({ error: 'Not found' }, { status: 404 });
if (!room.is_owner)
return NextResponse.json({ error: 'Only the room owner can invite' }, { status: 403 });
const { github_username } = await req.json();
if (!github_username?.trim())
return NextResponse.json({ error: 'github_username required' }, { status: 400 });
const ghRes = await fetch(`https://api.github.com/users/${github_username}`, {
const normalizedUsername = normalizeRoomGithubUsername(github_username);
if (!normalizedUsername)
return NextResponse.json({ error: 'Valid github_username required' }, { status: 400 });
const ghRes = await fetch(`https://api.github.com/users/${encodeURIComponent(normalizedUsername)}`, {
headers: {
Accept: 'application/vnd.github+json',
...(process.env.GITHUB_TOKEN
Expand All @@ -26,12 +28,14 @@ export async function POST(
},
});
if (ghRes.status === 404)
return NextResponse.json({ error: `GitHub user "${github_username}" does not exist` }, { status: 404 });
return NextResponse.json({ error: `GitHub user "${normalizedUsername}" does not exist` }, { status: 404 });
if (!ghRes.ok)
return NextResponse.json({ error: 'Could not verify GitHub user' }, { status: 502 });
const githubUser = await ghRes.json() as { login?: string };
const canonicalUsername = normalizeRoomGithubUsername(githubUser.login) ?? normalizedUsername;
const members = await getRoomMembers(params.roomId);
if (members.some((m) => m.github_username === github_username))
if (members.some((m) => githubUsernamesEqual(m.github_username, canonicalUsername)))
return NextResponse.json({ error: 'User is already a member' }, { status: 409 });
await addRoomMember(params.roomId, github_username);
await addRoomMember(params.roomId, canonicalUsername);
return NextResponse.json({ success: true });
}
}
14 changes: 7 additions & 7 deletions src/app/api/rooms/[roomId]/messages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export async function GET(
{ params }: { params: { roomId: string } }
) {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const room = await getRoomById(params.roomId, session.user.name);
const room = await getRoomById(params.roomId, session.githubLogin);
if (!room) return NextResponse.json({ error: 'Not found' }, { status: 404 });
const url = new URL(req.url);
const before = url.searchParams.get('before') ?? undefined;
Expand All @@ -24,19 +24,19 @@ export async function POST(
{ params }: { params: { roomId: string } }
) {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const room = await getRoomById(params.roomId, session.user.name);
const room = await getRoomById(params.roomId, session.githubLogin);
if (!room) return NextResponse.json({ error: 'Not found' }, { status: 404 });
const body = await req.json();
const validation = validateTextInput(body?.content, 'content', 4000);
if (!validation.ok)
return NextResponse.json({ error: validation.error }, { status: 400 });
const message = await sendRoomMessage(
params.roomId,
session.user.name,
session.user.image ?? null,
session.githubLogin,
session.user?.image ?? null,
validation.value
);
return NextResponse.json(message, { status: 201 });
}
}
10 changes: 5 additions & 5 deletions src/app/api/rooms/[roomId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export async function GET(
{ params }: { params: { roomId: string } }
) {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const room = await getRoomById(params.roomId, session.user.name);
const room = await getRoomById(params.roomId, session.githubLogin);
if (!room) return NextResponse.json({ error: 'Not found or not a member' }, { status: 404 });
const members = await getRoomMembers(params.roomId);
return NextResponse.json({ ...room, members });
Expand All @@ -22,10 +22,10 @@ export async function DELETE(
{ params }: { params: { roomId: string } }
) {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

const room = await getRoomById(params.roomId, session.user.name);
const room = await getRoomById(params.roomId, session.githubLogin);
if (!room) return NextResponse.json({ error: 'Not found' }, { status: 404 });
if (!room.is_owner)
return NextResponse.json({ error: 'Only the owner can delete this room' }, { status: 403 });
Expand All @@ -37,4 +37,4 @@ export async function DELETE(

if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ success: true });
}
}
10 changes: 5 additions & 5 deletions src/app/api/rooms/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import type { CreateRoomPayload } from '@/types/rooms';

export async function GET() {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
try {
const rooms = await getRoomsForUser(session.user.name);
const rooms = await getRoomsForUser(session.githubLogin);
return NextResponse.json(rooms);
} catch (err: any) {
return NextResponse.json({ error: err.message }, { status: 500 });
Expand All @@ -18,15 +18,15 @@ export async function GET() {

export async function POST(req: Request) {
const session = await getServerSession(authOptions);
if (!session?.user?.name)
if (!session?.githubLogin)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const body: CreateRoomPayload = await req.json();
if (!body.name?.trim() || !body.repo_owner?.trim() || !body.repo_name?.trim())
return NextResponse.json({ error: 'name, repo_owner, and repo_name are required' }, { status: 400 });
try {
const room = await createRoom(body, session.user.name);
const room = await createRoom(body, session.githubLogin);
return NextResponse.json(room, { status: 201 });
} catch (err: any) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
}
}
11 changes: 11 additions & 0 deletions src/lib/rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { normalizeGitHubUsername } from "./validate-github-username";

export function normalizeRoomGithubUsername(
value: string | null | undefined
): string | null {
return normalizeGitHubUsername(value);
}

export function githubUsernamesEqual(a: string, b: string): boolean {
return a.toLowerCase() === b.toLowerCase();
}
22 changes: 22 additions & 0 deletions test/rooms.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import {
githubUsernamesEqual,
normalizeRoomGithubUsername,
} from "@/lib/rooms";

describe("room username helpers", () => {
it("normalizes valid GitHub usernames", () => {
expect(normalizeRoomGithubUsername(" Octocat ")).toBe("Octocat");
});

it("rejects invalid GitHub usernames", () => {
expect(normalizeRoomGithubUsername("../octocat")).toBeNull();
expect(normalizeRoomGithubUsername("-octocat")).toBeNull();
expect(normalizeRoomGithubUsername("octocat-")).toBeNull();
});

it("compares GitHub usernames case-insensitively", () => {
expect(githubUsernamesEqual("Octocat", "octocat")).toBe(true);
expect(githubUsernamesEqual("hubot", "octocat")).toBe(false);
});
});
Loading