aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-07-12 12:33:23 +0000
committerMohamed Bassem <me@mbassem.com>2025-07-12 12:33:23 +0000
commit8e3013ba96532cab61eb6e5fae2ce30be5e94a57 (patch)
treeb101a0ac7e1218ea2562178e055044f315b0c245 /packages
parent140311d7419fa2192e5149df8f589c3c3733a399 (diff)
downloadkarakeep-8e3013ba96532cab61eb6e5fae2ce30be5e94a57.tar.zst
refactor: Move db interactions into the trpc routes
Diffstat (limited to 'packages')
-rw-r--r--packages/trpc/email.ts32
-rw-r--r--packages/trpc/routers/invites.test.ts26
-rw-r--r--packages/trpc/routers/users.test.ts6
-rw-r--r--packages/trpc/routers/users.ts35
4 files changed, 66 insertions, 33 deletions
diff --git a/packages/trpc/email.ts b/packages/trpc/email.ts
index 1c0b8800..e69c4507 100644
--- a/packages/trpc/email.ts
+++ b/packages/trpc/email.ts
@@ -1,25 +1,16 @@
-import { randomBytes } from "crypto";
import { createTransport } from "nodemailer";
-import { db } from "@karakeep/db";
-import { passwordResetTokens, verificationTokens } from "@karakeep/db/schema";
import serverConfig from "@karakeep/shared/config";
-export async function sendVerificationEmail(email: string, name: string) {
+export async function sendVerificationEmail(
+ email: string,
+ name: string,
+ token: string,
+) {
if (!serverConfig.email.smtp) {
throw new Error("SMTP is not configured");
}
- const token = randomBytes(10).toString("hex");
- const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
-
- // Store verification token
- await db.insert(verificationTokens).values({
- identifier: email,
- token,
- expires,
- });
-
const transporter = createTransport({
host: serverConfig.email.smtp.host,
port: serverConfig.email.smtp.port,
@@ -133,22 +124,12 @@ If you weren't expecting this invitation, you can safely ignore this email.
export async function sendPasswordResetEmail(
email: string,
name: string,
- userId: string,
+ token: string,
) {
if (!serverConfig.email.smtp) {
throw new Error("SMTP is not configured");
}
- const token = randomBytes(32).toString("hex");
- const expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
-
- // Store password reset token
- await db.insert(passwordResetTokens).values({
- userId,
- token,
- expires,
- });
-
const transporter = createTransport({
host: serverConfig.email.smtp.host,
port: serverConfig.email.smtp.port,
@@ -197,5 +178,4 @@ If you didn't request a password reset, please ignore this email. Your password
};
await transporter.sendMail(mailOptions);
- return token;
}
diff --git a/packages/trpc/routers/invites.test.ts b/packages/trpc/routers/invites.test.ts
index bb1209c4..f4f048df 100644
--- a/packages/trpc/routers/invites.test.ts
+++ b/packages/trpc/routers/invites.test.ts
@@ -6,6 +6,32 @@ import { invites, users } from "@karakeep/db/schema";
import type { CustomTestContext } from "../testUtils";
import { defaultBeforeEach, getApiCaller } from "../testUtils";
+// Mock server config with email settings
+vi.mock("@karakeep/shared/config", async (original) => {
+ const mod = (await original()) as typeof import("@karakeep/shared/config");
+ return {
+ ...mod,
+ default: {
+ ...mod.default,
+ email: {
+ smtp: {
+ host: "test-smtp.example.com",
+ port: 587,
+ secure: false,
+ user: "test@example.com",
+ password: "test-password",
+ from: "test@example.com",
+ },
+ },
+ },
+ };
+});
+
+// Mock email functions
+vi.mock("../email", () => ({
+ sendInviteEmail: vi.fn().mockResolvedValue(undefined),
+}));
+
beforeEach<CustomTestContext>(defaultBeforeEach(false));
describe("Invites Router", () => {
diff --git a/packages/trpc/routers/users.test.ts b/packages/trpc/routers/users.test.ts
index 03e5d590..1c03f47a 100644
--- a/packages/trpc/routers/users.test.ts
+++ b/packages/trpc/routers/users.test.ts
@@ -14,7 +14,7 @@ import type { CustomTestContext } from "../testUtils";
import * as emailModule from "../email";
import { defaultBeforeEach, getApiCaller } from "../testUtils";
-// Mock server config with all required properties - MUST be before any imports that use config
+// Mock server config with email settings
vi.mock("@karakeep/shared/config", async (original) => {
const mod = (await original()) as typeof import("@karakeep/shared/config");
return {
@@ -516,7 +516,7 @@ describe("User Routes", () => {
unauthedAPICaller,
}) => {
// Create a user first
- const user = await unauthedAPICaller.users.create({
+ await unauthedAPICaller.users.create({
name: "Test User",
email: "reset@test.com",
password: "pass1234",
@@ -534,7 +534,7 @@ describe("User Routes", () => {
expect(emailModule.sendPasswordResetEmail).toHaveBeenCalledWith(
"reset@test.com",
"Test User",
- user.id,
+ expect.any(String), // token
);
});
diff --git a/packages/trpc/routers/users.ts b/packages/trpc/routers/users.ts
index 79f06057..8d6db6c7 100644
--- a/packages/trpc/routers/users.ts
+++ b/packages/trpc/routers/users.ts
@@ -1,3 +1,4 @@
+import { randomBytes } from "crypto";
import { TRPCError } from "@trpc/server";
import { and, count, desc, eq, gte, sql } from "drizzle-orm";
import invariant from "tiny-invariant";
@@ -39,7 +40,21 @@ import {
router,
} from "../index";
-export async function verifyEmailToken(
+async function genEmailVerificationToken(db: Context["db"], email: string) {
+ const token = randomBytes(10).toString("hex");
+ const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
+
+ // Store verification token
+ await db.insert(verificationTokens).values({
+ identifier: email,
+ token,
+ expires,
+ });
+
+ return token;
+}
+
+async function verifyEmailToken(
db: Context["db"],
email: string,
token: string,
@@ -156,8 +171,9 @@ export async function createUser(
});
// Send verification email if required
if (serverConfig.auth.emailVerificationRequired) {
+ const token = await genEmailVerificationToken(ctx.db, input.email);
try {
- await sendVerificationEmail(input.email, input.name);
+ await sendVerificationEmail(input.email, input.name, token);
} catch (error) {
console.error("Failed to send verification email:", error);
// Don't fail user creation if email sending fails
@@ -671,8 +687,9 @@ export const usersAppRouter = router({
});
}
+ const token = await genEmailVerificationToken(ctx.db, input.email);
try {
- await sendVerificationEmail(input.email, user.name);
+ await sendVerificationEmail(input.email, user.name, token);
return { success: true };
} catch (error) {
console.error("Failed to send verification email:", error);
@@ -718,7 +735,17 @@ export const usersAppRouter = router({
}
try {
- await sendPasswordResetEmail(input.email, user.name, user.id);
+ const token = randomBytes(32).toString("hex");
+ const expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
+
+ // Store password reset token
+ await ctx.db.insert(passwordResetTokens).values({
+ userId: user.id,
+ token,
+ expires,
+ });
+
+ await sendPasswordResetEmail(input.email, user.name, token);
return { success: true };
} catch (error) {
console.error("Failed to send password reset email:", error);