Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
LeafYeeXYZ committed Aug 4, 2024
1 parent 45df0e5 commit 7857763
Show file tree
Hide file tree
Showing 16 changed files with 593 additions and 51 deletions.
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
> This project was still in development, but basic functions have been implemented. You can deploy it to your own server and use it with your custom domain. If you have any questions, please feel free to open an issue.
> 该项目仍在开发中,但基本功能已经实现. 你可以将其部署到你自己的服务器并使用自定义域名. 如果你有任何问题,请随时提出 issue
# MailBox
MailBox is a web application that allows you to send and receive emails serverlessly and costlessly with your custom domain.

MailBox 是一个允许你使用自定义域名进行免费邮件收发的 Web 应用
MailBox 是一个允许你使用自定义域名进行免费邮件收发的 Serverless 网页应用

![](./README.png)

Expand Down Expand Up @@ -104,9 +100,9 @@ bun run deploy # Deploy the workers
- [x] 单条邮件阅读组件
- [x] 发送邮件功能 (Resend)
- [x] 支持 Markdown 写邮件 (Marked)
- [ ] 个人资料页面 (记得游客账户不能修改)
- [x] 已发送邮件页面
- [x] 个人资料页面 (记得游客账户不能修改)
- [ ] 夜间模式
- [ ] 已发送邮件页面
- [ ] 找回密码功能 (向备用邮箱发送验证码)
- [ ] AI 总结邮件内容生成邮件摘要 (Cloudflare Workers AI)
- [ ] 附件支持
Expand Down
7 changes: 7 additions & 0 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export default function AuthLayout({ children }: Readonly<{ children: React.Reac
{children}
</div>
</div>
<div className='absolute bottom-3 left-0 text-gray-400 text-center text-xs w-full'>
GPL-3.0 License | Github: <a
href='https://github.com/LeafYeeXYZ/MailBox'
target='_blank'
className='text-rose-300'
>LeafYeeXYZ/MailBox</a>
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion app/(auth)/login/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function auth(email: string, password: string): Promise<string> {
const user = await inbox.findOne({
email,
password
})
}, { projection: { username: 1 } })
if (!user) {
throw new Error('邮箱地址或密码错误')
}
Expand Down
2 changes: 1 addition & 1 deletion app/(auth)/registry/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function registry(
return '注册码错误'
}
// 查询是否已存在
const result = await user.findOne({ email: fullEmail })
const result = await user.findOne({ email: fullEmail }, { projection: {} })
if (result) {
return '邮箱已注册'
}
Expand Down
15 changes: 7 additions & 8 deletions app/(auth)/registry/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import { Button, Input, Form, Flex, message } from 'antd'
import { UserOutlined, KeyOutlined, MailOutlined, CodeOutlined } from '@ant-design/icons'
import { useRouter } from 'next/navigation'
import { useState, useOptimistic } from 'react'
import { useState } from 'react'
import { flushSync } from 'react-dom'
import { registry } from './action'

type FieldType = {
Expand All @@ -18,14 +19,10 @@ export default function Login() {

const router = useRouter()
const [messageAPI, contextHolder] = message.useMessage()
const [disableForm] = useState(false)
const [disableState, setDisableState] = useOptimistic(
disableForm,
(_, value: boolean) => value
)
const [disableForm, setDisableForm] = useState<boolean>(false)

const handleSubmit = async (values: FieldType) => {
setDisableState(true)
flushSync(() => setDisableForm(true))
messageAPI.open({
type: 'loading',
content: '正在注册...',
Expand Down Expand Up @@ -62,6 +59,8 @@ export default function Login() {
duration: 3,
key: 'error'
})
}).finally(() => {
setDisableForm(false)
})
}

Expand All @@ -72,7 +71,7 @@ export default function Login() {
name='registry'
className='w-11/12'
onFinish={handleSubmit}
disabled={disableState}
disabled={disableForm}
>
<Form.Item>
<p className='mb-2 text-2xl font-bold text-center'>注册</p>
Expand Down
3 changes: 2 additions & 1 deletion app/(auth)/reset/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export default function Reset() {
const router = useRouter()
return (
<div className='flex flex-col items-center justify-center h-full w-full'>
<div className="text-center text-lg font-bold my-4">本功能开发中...</div>
<div className="text-center text-lg font-bold">本功能开发中...</div>
<div className="text-center text-sm my-6 px-6">如需找回账号, 请联系管理员删除原账号, 再重新进行注册, 收发件箱的邮件不会丢失</div>
<Button type='primary' onClick={() => router.push('/login')}>返回登录</Button>
</div>
)
Expand Down
15 changes: 6 additions & 9 deletions app/(dashboard)/inbox/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type Mail = {
_id: string
from: string
fromName: string
to: string
subject: string
content: string
date: string
Expand All @@ -22,7 +21,7 @@ export type Mail = {
// 删除特定邮件
export async function deleteEmail(email: string, password: string, _id: string): Promise<boolean | string> {
// 验证邮箱和密码
const auth = await user.findOne({ email, password })
const auth = await user.findOne({ email, password }, { projection: { role: 1 } })
if (!auth) {
return '401'
} else if (auth.role !== 'admin' && auth.role !== 'user') {
Expand All @@ -39,20 +38,19 @@ export async function deleteEmail(email: string, password: string, _id: string):
// 返回特定邮件
export async function getEmail(email: string, password: string, _id: string): Promise<Mail | string> {
// 验证邮箱和密码
const auth = await user.findOne({ email, password })
const auth = await user.findOne({ email, password }, { projection: {} })
if (!auth) {
return '401'
}
// 获取邮箱
const data = await inbox.findOne({ _id: new ObjectId(_id) })
const data = await inbox.findOne({ _id: new ObjectId(_id) }, { projection: { from: 1, subject: 1, html: 1, date: 1, attachments: 1 } })
if (!data) {
return '404'
}
return {
_id: data._id.toString(),
fromName: data.from.name,
from: data.from.address,
to: data.workers.to,
subject: data.subject,
content: data.html,
date: data.date,
Expand All @@ -63,22 +61,21 @@ export async function getEmail(email: string, password: string, _id: string): Pr
// 返回邮件列表, 内容仅需前 100 个字符
export async function getMails(email: string, password: string, limit: number, skip: number): Promise<Mail[] | string> {
// 验证邮箱和密码
const auth = await user.findOne({ email, password })
const auth = await user.findOne({ email, password }, { projection: {} })
if (!auth) {
return '401'
}
// 获取邮箱列表
const data = inbox.find({ 'workers.to': email }).sort({ date: -1 }).skip(skip).limit(limit).project({ text: 1, subject: 1, date: 1, workers: 1, from: 1 })
const data = inbox.find({ 'workers.to': email }).sort({ date: -1 }).skip(skip).limit(limit).project({ text: 1, subject: 1, date: 1, from: 1 })
const mails: Mail[] = []
for await (const doc of data) {
mails.push({
_id: doc._id.toString(),
from: doc.from.address,
fromName: doc.from.name,
to: doc.workers.to,
subject: doc.subject,
content: doc.text.slice(0, 100),
date: doc.date.split('T')[0].slice(2)
date: doc.date
})
}
return mails
Expand Down
30 changes: 17 additions & 13 deletions app/(dashboard)/inbox/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { flushSync } from 'react-dom'
export default function Inbox() {

const mailsPerPage = 20
const emailRef = useRef<string>('')
const passwordRef = useRef<string>('')
const [useremail, setUseremail] = useState<string>('')
const [password, setPassword] = useState<string>('')
const [username, setUsername] = useState<string>('')
const router = useRouter()
const [messageAPI, contextHolder] = message.useMessage()
Expand All @@ -21,7 +21,7 @@ export default function Inbox() {
const [btn, setBtn] = useState<'loading' | 'loaded' | 'null'>('loading')
const handleLoadMore = (limit: number, skip: number) => {
flushSync(() => setBtn('loading'))
getMails(emailRef.current, passwordRef.current, limit, skip)
getMails(useremail, password, limit, skip)
.then(res => {
if (res === '401') {
messageAPI.error('登陆失效 (2秒后自动跳转至登录页)')
Expand All @@ -41,7 +41,7 @@ export default function Inbox() {
}

// 当前显示的邮件
const loadingEmail: Mail = { _id: '', fromName: '', from: '', to: '', subject: '加载中...', content: '', date: '', attachments: [] }
const loadingEmail: Mail = { _id: '', fromName: '', from: '', subject: '加载中...', content: '', date: '', attachments: [] }
const [email, setEmail] = useState<Mail>(loadingEmail)
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(true)
Expand All @@ -51,7 +51,7 @@ export default function Inbox() {
setLoading(true)
setOpen(true)
})
getEmail(emailRef.current, passwordRef.current, _id)
getEmail(useremail, password, _id)
.then(res => {
if (res === '401') {
messageAPI.error('登陆失效 (2秒后自动跳转至登录页)')
Expand All @@ -74,7 +74,7 @@ export default function Inbox() {
})
}
const handleDeleteEmail = async (_id: string) => {
const res = await deleteEmail(emailRef.current, passwordRef.current, _id)
const res = await deleteEmail(useremail, password, _id)
if (res === '401') {
messageAPI.error('登陆失效 (2秒后自动跳转至登录页)')
localStorage.clear()
Expand All @@ -94,16 +94,19 @@ export default function Inbox() {

// 从服务器获取初始化邮件
useEffect(() => {
emailRef.current = localStorage.getItem('email') ?? sessionStorage.getItem('email') ?? ''
passwordRef.current = localStorage.getItem('password') ?? sessionStorage.getItem('password') ?? ''
setUsername(localStorage.getItem('username') ?? sessionStorage.getItem('username') ?? '')
if (!emailRef.current || !passwordRef.current) {
const email = localStorage.getItem('email') ?? sessionStorage.getItem('email') ?? ''
const password = localStorage.getItem('password') ?? sessionStorage.getItem('password') ?? ''
const username = localStorage.getItem('username') ?? sessionStorage.getItem('username') ?? ''
setUseremail(email)
setPassword(password)
setUsername(username)
if (!email.length || !password.length) {
messageAPI.error('登陆失效 (2秒后自动跳转至登录页)')
setTimeout(() => {
router.push('/login')
}, 2000)
} else {
getMails(emailRef.current, passwordRef.current, mailsPerPage, 0)
getMails(email, password, mailsPerPage, 0)
.then(res => {
if (res === '401') {
messageAPI.error('登陆失效 (2秒后自动跳转至登录页)')
Expand Down Expand Up @@ -164,7 +167,7 @@ export default function Inbox() {
<div className='w-dvw h-full absolute left-0 grid grid-rows-[3rem,1fr] sm:grid-rows-[1.75rem,1fr] items-center'>
<div className='w-full h-full flex flex-col sm:flex-row items-start justify-start -mt-8 px-3 gap-1 sm:flex-wrap'>
<div className='w-full sm:w-[49.5%] text-left text-xs text-gray-500'>来自 {email?.fromName?.length ? `${email?.fromName} <${email?.from}>` : email?.from}</div>
<div className='w-full sm:w-[49.5%] sm:text-right text-left text-xs text-gray-500'>收件人 {`${username} <${email?.to}>`}</div>
<div className='w-full sm:w-[49.5%] sm:text-right text-left text-xs text-gray-500'>收件人 {`${username} <${useremail}>`}</div>
<div className='w-full text-left text-xs text-gray-500'>{new Date(email?.date).toLocaleString()}</div>
</div>
<div className='w-full h-full border-t'>
Expand Down Expand Up @@ -193,6 +196,7 @@ function EmailPreview({ mail, onClick, onDelete }: { mail: Mail, onClick: (_id:
}
const colorStart = `rgb(${color()}, ${color()}, ${color()})`
const colorEnd = `rgb(${color()}, ${color()}, ${color()})`
const data = new Date(mail.date)
return (
<div className='flex flex-col w-full md:w-[48.5%] xl:w-[32.2%] p-4 relative shadow-sm border rounded-xl bg-gray-50 cursor-pointer transition-all hover:scale-[99%] md:hover:scale-[98%]' onClick={() => onClick(mail._id)}>
<div className='grid relative grid-cols-[2rem,1fr,4rem] gap-[0.6rem] items-center'>
Expand All @@ -203,7 +207,7 @@ function EmailPreview({ mail, onClick, onDelete }: { mail: Mail, onClick: (_id:
}}
></div>
<div className='text-sm font-bold text-gray-800 overflow-hidden overflow-ellipsis whitespace-nowrap'>{mail.subject}</div>
<div className='text-xs text-gray-500 text-right absolute right-0 top-0'>{mail.date}</div>
<div className='text-xs text-gray-500 text-right absolute right-0 top-0'>{`${data.getFullYear().toString().slice(2)}-${data.getMonth() + 1}-${data.getDate()}`}</div>
</div>
<div className='text-xs my-2 text-gray-500 overflow-hidden overflow-ellipsis whitespace-nowrap'>来自 {mail.fromName?.length ? `${mail.fromName} <${mail.from}>` : mail.from}</div>
<div className='text-sm w-[calc(100%-2.8rem)] text-gray-800 overflow-hidden overflow-ellipsis whitespace-nowrap'>{mail.content}</div>
Expand Down
32 changes: 32 additions & 0 deletions app/(dashboard)/profile/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use server'

import { MongoClient } from 'mongodb'
import type { UserData } from '@/app/COLL_TYPE'

// 连接 MongoDB
const client = new MongoClient(process.env.MONGODB_URI!)
const db = client.db('mailbox')
const user = db.collection('user')

export async function getUser(email: string, password: string): Promise<UserData | string> {
// 验证邮箱和密码
const auth = await user.findOne({ email, password }, { projection: { _id: 0 } })
if (!auth) {
return '401'
} else {
return auth as unknown as UserData
}
}

export async function updateUser(email: string, password: string, field: string, value: string): Promise<string> {
// 验证邮箱和密码
const auth = await user.findOne({ email, password }, { projection: { role: 1 } })
if (!auth) {
return '401'
} else if (auth.role !== 'admin' && auth.role !== 'user') {
return '403'
}
// 更新用户
await user.updateOne({ email, password }, { $set: { [field]: value } })
return '200'
}
Loading

0 comments on commit 7857763

Please sign in to comment.