Skip to content

Commit

Permalink
Define routes for getting all users in an org, adding a user, removin…
Browse files Browse the repository at this point in the history
…g and updating a user from an org
  • Loading branch information
virinci committed Dec 4, 2024
1 parent 397db6e commit 52d965f
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 0 deletions.
51 changes: 51 additions & 0 deletions app/api/orgs/[orgId]/users/[userId]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { NextRequest, NextResponse } from "next/server";
import * as userService from "./service";
import { IdSchema } from "@/app/api/types";
import { updateUserRoleSchema } from "@/lib/validations";
import { z } from "zod";

export async function DELETE(
_req: NextRequest,
{ params }: { params: { orgId: string; userId: string } },
) {
try {
const orgId = IdSchema.parse(params.orgId);
const userId = IdSchema.parse(params.userId);

const deleted = await userService.removeUserFromOrg(orgId, userId);
return NextResponse.json(deleted);
} catch (error) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 404 });
}
return NextResponse.json(
{ message: "Failed to remove user" },
{ status: 500 },
);
}
}

export async function PATCH(
_req: NextRequest,
{ params }: { params: { orgId: string; userId: string } },
) {
try {
const orgId = IdSchema.parse(params.orgId);
const userId = IdSchema.parse(params.userId);
const { role } = updateUserRoleSchema.parse(await _req.json());

const updated = await userService.updateUserRole(orgId, userId, role);
return NextResponse.json(updated);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ errors: error.errors }, { status: 400 });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 404 });
}
return NextResponse.json(
{ message: "Failed to update role" },
{ status: 500 },
);
}
}
32 changes: 32 additions & 0 deletions app/api/orgs/[orgId]/users/[userId]/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { db } from "@/db/drizzle";
import { memberships } from "@/db/schema";
import { eq, and } from "drizzle-orm";

export async function removeUserFromOrg(orgId: number, userId: number) {
const [deleted] = await db
.delete(memberships)
.where(and(eq(memberships.orgId, orgId), eq(memberships.userId, userId)))
.returning();

if (!deleted) {
throw new Error("Membership not found");
}
return deleted;
}

export async function updateUserRole(
orgId: number,
userId: number,
role: string,
) {
const [updated] = await db
.update(memberships)
.set({ role })
.where(and(eq(memberships.orgId, orgId), eq(memberships.userId, userId)))
.returning();

if (!updated) {
throw new Error("Membership not found");
}
return updated;
}
45 changes: 45 additions & 0 deletions app/api/orgs/[orgId]/users/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from "next/server";
import * as userService from "./service";
import { IdSchema } from "@/app/api/types";
import { inviteUserSchema } from "@/lib/validations";
import { z } from "zod";

export async function GET(
_req: NextRequest,
{ params }: { params: { orgId: string } },
) {
try {
const orgId = IdSchema.parse(params.orgId);
const users = await userService.getOrgUsers(orgId);
return NextResponse.json(users);
} catch (error) {
return NextResponse.json(
{ message: "Failed to fetch users" },
{ status: 500 },
);
}
}

export async function POST(
request: NextRequest,
{ params }: { params: { orgId: string } },
) {
try {
const orgId = IdSchema.parse(params.orgId);
const data = inviteUserSchema.parse(await request.json());

const membership = await userService.inviteUser(orgId, data);
return NextResponse.json(membership, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ errors: error.errors }, { status: 400 });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json(
{ message: "Failed to invite user" },
{ status: 500 },
);
}
}
64 changes: 64 additions & 0 deletions app/api/orgs/[orgId]/users/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { db } from "@/db/drizzle";
import { users, memberships } from "@/db/schema";
import { inviteUserSchema } from "@/lib/validations";
import { eq, and } from "drizzle-orm";
import { z } from "zod";

export async function getOrgUsers(orgId: number) {
return await db
.select({
id: users.id,
name: users.name,
nameId: users.nameId,
avatar: users.avatar,
about: users.about,
role: memberships.role,
joinedAt: memberships.joinedAt,
})
.from(users)
.innerJoin(memberships, eq(memberships.userId, users.id))
.where(eq(memberships.orgId, orgId))
.orderBy(memberships.joinedAt);
}

export async function inviteUser(
orgId: number,
data: z.infer<typeof inviteUserSchema>,
) {
return await db.transaction(async (tx) => {
const user = await tx
.select({
id: users.id,
})
.from(users)
.where(eq(users.email, data.email))
.limit(1);

if (user.length === 0) {
throw new Error("User not found");
}

const existingMembership = await tx
.select()
.from(memberships)
.where(
and(eq(memberships.userId, user[0].id), eq(memberships.orgId, orgId)),
)
.limit(1);

if (existingMembership.length > 0) {
throw new Error("User is already a member");
}

const [membership] = await tx
.insert(memberships)
.values({
userId: user[0].id,
orgId,
role: data.role,
})
.returning();

return membership;
});
}

0 comments on commit 52d965f

Please sign in to comment.