mirror of
https://github.com/beilunyang/moemail.git
synced 2026-06-06 08:00:16 +08:00
feat(sharing): add email and message sharing functionality
This commit is contained in:
83
app/api/shared/[token]/messages/[messageId]/route.ts
Normal file
83
app/api/shared/[token]/messages/[messageId]/route.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { createDb } from "@/lib/db"
|
||||
import { emailShares, messages } from "@/lib/schema"
|
||||
import { eq, and } from "drizzle-orm"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
// 通过分享token获取消息详情
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ token: string; messageId: string }> }
|
||||
) {
|
||||
const { token, messageId } = await params
|
||||
const db = createDb()
|
||||
|
||||
try {
|
||||
// 验证分享token
|
||||
const share = await db.query.emailShares.findFirst({
|
||||
where: eq(emailShares.token, token),
|
||||
with: {
|
||||
email: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!share) {
|
||||
return NextResponse.json(
|
||||
{ error: "Share link not found or expired" },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查分享是否过期
|
||||
if (share.expiresAt && share.expiresAt < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Share link has expired" },
|
||||
{ status: 410 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查邮箱是否过期
|
||||
if (share.email.expiresAt < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Email has expired" },
|
||||
{ status: 410 }
|
||||
)
|
||||
}
|
||||
|
||||
// 获取消息详情
|
||||
const message = await db.query.messages.findFirst({
|
||||
where: and(
|
||||
eq(messages.id, messageId),
|
||||
eq(messages.emailId, share.email.id)
|
||||
)
|
||||
})
|
||||
|
||||
if (!message) {
|
||||
return NextResponse.json(
|
||||
{ error: "Message not found" },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
message: {
|
||||
id: message.id,
|
||||
from_address: message.fromAddress,
|
||||
to_address: message.toAddress,
|
||||
subject: message.subject,
|
||||
content: message.content,
|
||||
html: message.html,
|
||||
received_at: message.receivedAt,
|
||||
sent_at: message.sentAt
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch shared message:", error)
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch message" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
124
app/api/shared/[token]/messages/route.ts
Normal file
124
app/api/shared/[token]/messages/route.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { createDb } from "@/lib/db"
|
||||
import { emailShares, messages } from "@/lib/schema"
|
||||
import { eq, and, lt, or, sql, ne, isNull } from "drizzle-orm"
|
||||
import { NextResponse } from "next/server"
|
||||
import { encodeCursor, decodeCursor } from "@/lib/cursor"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
// 通过分享token获取邮箱的消息列表
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ token: string }> }
|
||||
) {
|
||||
const { token } = await params
|
||||
const db = createDb()
|
||||
const { searchParams } = new URL(request.url)
|
||||
const cursor = searchParams.get('cursor')
|
||||
|
||||
try {
|
||||
// 验证分享token
|
||||
const share = await db.query.emailShares.findFirst({
|
||||
where: eq(emailShares.token, token),
|
||||
with: {
|
||||
email: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!share) {
|
||||
return NextResponse.json(
|
||||
{ error: "Share link not found or expired" },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查分享是否过期
|
||||
if (share.expiresAt && share.expiresAt < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Share link has expired" },
|
||||
{ status: 410 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查邮箱是否过期
|
||||
if (share.email.expiresAt < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Email has expired" },
|
||||
{ status: 410 }
|
||||
)
|
||||
}
|
||||
|
||||
const emailId = share.email.id
|
||||
|
||||
// 只显示接收的邮件,不显示发送的邮件
|
||||
const baseConditions = and(
|
||||
eq(messages.emailId, emailId),
|
||||
or(
|
||||
ne(messages.type, "sent"),
|
||||
isNull(messages.type)
|
||||
)
|
||||
)
|
||||
|
||||
// 获取消息总数(只统计接收的邮件)
|
||||
const totalResult = await db.select({ count: sql<number>`count(*)` })
|
||||
.from(messages)
|
||||
.where(baseConditions)
|
||||
const totalCount = Number(totalResult[0].count)
|
||||
|
||||
const conditions = [baseConditions]
|
||||
|
||||
if (cursor) {
|
||||
const { timestamp, id } = decodeCursor(cursor)
|
||||
const cursorCondition = or(
|
||||
lt(messages.receivedAt, new Date(timestamp)),
|
||||
and(
|
||||
eq(messages.receivedAt, new Date(timestamp)),
|
||||
lt(messages.id, id)
|
||||
)
|
||||
)
|
||||
if (cursorCondition) {
|
||||
conditions.push(cursorCondition)
|
||||
}
|
||||
}
|
||||
|
||||
const results = await db.query.messages.findMany({
|
||||
where: and(...conditions),
|
||||
orderBy: (messages, { desc }) => [
|
||||
desc(messages.receivedAt),
|
||||
desc(messages.id)
|
||||
],
|
||||
limit: PAGE_SIZE + 1
|
||||
})
|
||||
|
||||
const hasMore = results.length > PAGE_SIZE
|
||||
const nextCursor = hasMore
|
||||
? encodeCursor(
|
||||
results[PAGE_SIZE - 1].receivedAt.getTime(),
|
||||
results[PAGE_SIZE - 1].id
|
||||
)
|
||||
: null
|
||||
const messageList = hasMore ? results.slice(0, PAGE_SIZE) : results
|
||||
|
||||
return NextResponse.json({
|
||||
messages: messageList.map(msg => ({
|
||||
id: msg.id,
|
||||
from_address: msg.fromAddress,
|
||||
to_address: msg.toAddress,
|
||||
subject: msg.subject,
|
||||
received_at: msg.receivedAt,
|
||||
sent_at: msg.sentAt
|
||||
})),
|
||||
nextCursor,
|
||||
total: totalCount
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch shared messages:", error)
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch messages" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
64
app/api/shared/[token]/route.ts
Normal file
64
app/api/shared/[token]/route.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createDb } from "@/lib/db"
|
||||
import { emailShares } from "@/lib/schema"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
// 通过分享token获取邮箱信息
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ token: string }> }
|
||||
) {
|
||||
const { token } = await params
|
||||
const db = createDb()
|
||||
|
||||
try {
|
||||
// 查找分享记录
|
||||
const share = await db.query.emailShares.findFirst({
|
||||
where: eq(emailShares.token, token),
|
||||
with: {
|
||||
email: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!share) {
|
||||
return NextResponse.json(
|
||||
{ error: "Share link not found or expired" },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查分享是否过期
|
||||
if (share.expiresAt && share.expiresAt < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Share link has expired" },
|
||||
{ status: 410 }
|
||||
)
|
||||
}
|
||||
|
||||
// 检查邮箱是否过期
|
||||
if (share.email.expiresAt < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: "Email has expired" },
|
||||
{ status: 410 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
email: {
|
||||
id: share.email.id,
|
||||
address: share.email.address,
|
||||
createdAt: share.email.createdAt,
|
||||
expiresAt: share.email.expiresAt
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch shared email:", error)
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch shared email" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user