Skip to content

Commit

Permalink
feature: Make the first user an admin automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedBassem committed Mar 2, 2024
1 parent a942e69 commit b7b5306
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 11 deletions.
31 changes: 21 additions & 10 deletions packages/web/lib/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ export async function seedUsers(db: TestDB) {
.returning();
}

export function getApiCaller(db: TestDB, userId: string) {
export function getApiCaller(db: TestDB, userId?: string) {
const createCaller = createCallerFactory(appRouter);
return createCaller({
user: {
id: userId,
role: "user",
},
user: userId
? {
id: userId,
role: "user",
}
: null,
db,
});
}
Expand All @@ -40,20 +42,29 @@ export type APICallerType = ReturnType<typeof getApiCaller>;

export interface CustomTestContext {
apiCallers: APICallerType[];
unauthedAPICaller: APICallerType;
db: TestDB;
}

export async function buildTestContext(): Promise<CustomTestContext> {
export async function buildTestContext(
seedDB: boolean,
): Promise<CustomTestContext> {
const db = getTestDB();
const users = await seedUsers(db);
let users: Awaited<ReturnType<typeof seedUsers>> = [];
if (seedDB) {
users = await seedUsers(db);
}
const callers = users.map((u) => getApiCaller(db, u.id));

return {
apiCallers: callers,
unauthedAPICaller: getApiCaller(db),
db,
};
}

export const defaultBeforeEach = async (context: object) => {
Object.assign(context, await buildTestContext());
};
export function defaultBeforeEach(seedDB: boolean = true) {
return async (context: object) => {
Object.assign(context, await buildTestContext(seedDB));
};
}
2 changes: 1 addition & 1 deletion packages/web/server/api/routers/bookmarks.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CustomTestContext, defaultBeforeEach } from "@/lib/testUtils";
import { expect, describe, test, beforeEach, assert } from "vitest";

beforeEach<CustomTestContext>(defaultBeforeEach);
beforeEach<CustomTestContext>(defaultBeforeEach(true));

describe("Bookmark Routes", () => {
test<CustomTestContext>("create bookmark", async ({ apiCallers }) => {
Expand Down
57 changes: 57 additions & 0 deletions packages/web/server/api/routers/users.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { CustomTestContext, defaultBeforeEach } from "@/lib/testUtils";
import { expect, describe, test, beforeEach } from "vitest";

beforeEach<CustomTestContext>(defaultBeforeEach(false));

describe("User Routes", () => {
test<CustomTestContext>("create user", async ({ unauthedAPICaller }) => {
const user = await unauthedAPICaller.users.create({
name: "Test User",
email: "test123@test.com",
password: "pass1234",
confirmPassword: "pass1234",
});

expect(user.name).toEqual("Test User");
expect(user.email).toEqual("test123@test.com");
});

test<CustomTestContext>("first user is admin", async ({
unauthedAPICaller,
}) => {
const user1 = await unauthedAPICaller.users.create({
name: "Test User",
email: "test123@test.com",
password: "pass1234",
confirmPassword: "pass1234",
});

const user2 = await unauthedAPICaller.users.create({
name: "Test User",
email: "test124@test.com",
password: "pass1234",
confirmPassword: "pass1234",
});

expect(user1.role).toEqual("admin");
expect(user2.role).toEqual("user");
});

test<CustomTestContext>("unique emails", async ({ unauthedAPICaller }) => {
await unauthedAPICaller.users.create({
name: "Test User",
email: "test123@test.com",
password: "pass1234",
confirmPassword: "pass1234",
});

await expect(() =>
unauthedAPICaller.users.create({
name: "Test User",
email: "test123@test.com",
password: "pass1234",
confirmPassword: "pass1234",
}),
).rejects.toThrow(/Email is already taken/);
});
});
7 changes: 7 additions & 0 deletions packages/web/server/api/routers/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { z } from "zod";
import { hashPassword } from "@/server/auth";
import { TRPCError } from "@trpc/server";
import { users } from "@hoarder/db/schema";
import { count } from "drizzle-orm";

export const usersAppRouter = router({
create: publicProcedure
Expand All @@ -13,20 +14,26 @@ export const usersAppRouter = router({
z.object({
name: z.string(),
email: z.string(),
role: z.enum(["user", "admin"]).nullable(),
}),
)
.mutation(async ({ input, ctx }) => {
const [{ count: userCount }] = await ctx.db
.select({ count: count() })
.from(users);
try {
const result = await ctx.db
.insert(users)
.values({
name: input.name,
email: input.email,
password: await hashPassword(input.password),
role: userCount == 0 ? "admin" : "user",
})
.returning({
name: users.name,
email: users.email,
role: users.role,
});
return result[0];
} catch (e) {
Expand Down

0 comments on commit b7b5306

Please sign in to comment.