feat: add send email counter

This commit is contained in:
oiov
2025-04-23 23:04:46 +08:00
parent a95ceb9448
commit 600f2e90c7
6 changed files with 109 additions and 10 deletions
+26
View File
@@ -1,10 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
import { getUserSendEmailCount, saveUserSendEmail } from "@/lib/dto/email";
import { checkUserStatus } from "@/lib/dto/user";
import { resend } from "@/lib/email";
import { getCurrentUser } from "@/lib/session";
import { isValidEmail } from "@/lib/utils";
export async function POST(req: NextRequest) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const { from, to, subject, html } = await req.json();
if (!from || !to || !subject || !html) {
@@ -27,9 +33,29 @@ export async function POST(req: NextRequest) {
return NextResponse.json("Failed to send email", { status: 500 });
}
await saveUserSendEmail(user.id, from, to, subject, html);
return NextResponse.json("success", { status: 200 });
} catch (error) {
console.log("Error sending email:", error);
return NextResponse.json("Internal server error", { status: 500 });
}
}
export async function GET(req: NextRequest) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const { searchParams } = new URL(req.url);
const all = searchParams.get("all") || "false";
const count = await getUserSendEmailCount(
user.id,
user.role === "ADMIN" && all === "true",
);
return NextResponse.json(count);
} catch (error) {
return NextResponse.json("Internal server error", { status: 500 });
}
}
+10 -2
View File
@@ -91,6 +91,14 @@ export default function EmailSidebar({
{ dedupingInterval: 5000 },
);
const { data: sendEmails } = useSWR<number>(
`/api/email/send?all=${isAdminModel}`,
fetcher,
{
dedupingInterval: 5000,
},
);
if (!selectedEmailAddress && data && data.list.length > 0) {
onSelectEmail(data.list[0].emailAddress);
}
@@ -332,7 +340,7 @@ export default function EmailSidebar({
</div>
{/* Sent Emails */}
<div className="flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-200 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700">
<div className="flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-100 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700">
<div className="flex items-center gap-1">
<Icons.send className="size-3" />
<p className="line-clamp-1 text-start font-medium">
@@ -340,7 +348,7 @@ export default function EmailSidebar({
</p>
</div>
<p className="text-sm font-semibold text-gray-900 dark:text-gray-100">
{/* <CountUp count={0} /> */}--
<CountUp count={sendEmails || 0} />
</p>
</div>
</div>
+27
View File
@@ -517,3 +517,30 @@ export async function markAllEmailsAsRead(userEmailId: string, userId: string) {
throw error;
}
}
export async function saveUserSendEmail(
userId: string,
from: string,
to: string,
subject: string,
html: string,
) {
return prisma.userSendEmail.create({
data: {
userId,
from,
to,
subject,
html,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
});
}
export async function getUserSendEmailCount(userId: string, admin: boolean) {
if (admin) {
return prisma.userSendEmail.count();
}
return prisma.userSendEmail.count({ where: { userId } });
}
@@ -261,3 +261,20 @@ ON DELETE SET NULL
ON UPDATE CASCADE;
ALTER TABLE "user_urls" ADD COLUMN "password" TEXT NOT NULL DEFAULT '';
CREATE TABLE "user_send_emails"
(
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
"userId" TEXT NOT NULL,
"from" TEXT NOT NULL,
"fromName" TEXT DEFAULT '',
"to" TEXT NOT NULL,
"subject" TEXT DEFAULT 'No Subject',
"text" TEXT DEFAULT '',
"html" TEXT DEFAULT '',
"replyTo" TEXT DEFAULT '',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
);
CREATE INDEX "user_send_emails_userId_idx" ON "user_send_emails" ("userId");
+28 -7
View File
@@ -62,12 +62,13 @@ model User {
updatedAt DateTime @default(now()) @map(name: "updated_at")
role UserRole @default(USER)
accounts Account[]
sessions Session[]
UserRecord UserRecord[]
UserUrl UserUrl[]
ScrapeMeta ScrapeMeta[]
UserEmail UserEmail[]
accounts Account[]
sessions Session[]
UserRecord UserRecord[]
UserUrl UserUrl[]
ScrapeMeta ScrapeMeta[]
UserEmail UserEmail[]
UserSendEmail UserSendEmail[]
@@map(name: "users")
}
@@ -183,7 +184,7 @@ model ScrapeMeta {
}
model ForwardEmail {
id String @id @default(uuid()) // 使用 UUID 作为主键
id String @id @default(uuid())
from String
fromName String
to String
@@ -222,3 +223,23 @@ model UserEmail {
@@map("user_emails")
}
model UserSendEmail {
id String @id @default(uuid())
userId String
from String
fromName String? @default("")
to String
subject String? @default("No Subject")
text String? @default("")
html String? @default("")
replyTo String? @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_send_emails")
}
+1 -1
View File
File diff suppressed because one or more lines are too long