diff --git a/apps/next/.eslintrc.json b/apps/next/.eslintrc.json deleted file mode 100644 index bffb357..0000000 --- a/apps/next/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/apps/next/.gitignore b/apps/next/.gitignore deleted file mode 100644 index fd3dbb5..0000000 --- a/apps/next/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/apps/next/README.md b/apps/next/README.md deleted file mode 100644 index c403366..0000000 --- a/apps/next/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/apps/next/next.config.js b/apps/next/next.config.js deleted file mode 100644 index 658404a..0000000 --- a/apps/next/next.config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -module.exports = nextConfig; diff --git a/apps/next/package.json b/apps/next/package.json deleted file mode 100644 index 2b2d876..0000000 --- a/apps/next/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "next", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@marsidev/react-turnstile": "^0.4.1", - "@scaleway/random-name": "^4.0.2", - "@tanstack/react-query": "^5.17.10", - "database": "workspace:^", - "date-fns": "^3.2.0", - "icons": "workspace:^", - "next": "14.0.4", - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "@tailwindcss/typography": "^0.5.10", - "@types/node": "^20.11.1", - "@types/react": "^18.2.47", - "@types/react-dom": "^18.2.18", - "autoprefixer": "^10.4.16", - "eslint": "^8.56.0", - "eslint-config-next": "14.0.4", - "postcss": "^8.4.33", - "tailwindcss": "^3.4.1", - "typescript": "^5.3.3" - } -} diff --git a/apps/next/postcss.config.js b/apps/next/postcss.config.js deleted file mode 100644 index 12a703d..0000000 --- a/apps/next/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/apps/next/public/next.svg b/apps/next/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/apps/next/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/next/public/vercel.svg b/apps/next/public/vercel.svg deleted file mode 100644 index d2f8422..0000000 --- a/apps/next/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/next/src/app/api/mailbox/route.ts b/apps/next/src/app/api/mailbox/route.ts deleted file mode 100644 index 6be8c55..0000000 --- a/apps/next/src/app/api/mailbox/route.ts +++ /dev/null @@ -1,61 +0,0 @@ -"use server"; - -import { getMailbox } from "@/app/api/mails/route"; -import { sign } from "@/crypto"; -import { cookies } from "next/headers"; -// @ts-ignore -import randomName from "@scaleway/random-name"; -import { NextRequest, NextResponse } from "next/server"; - -const verifyEndpoint = - "https://challenges.cloudflare.com/turnstile/v0/siteverify"; -const turnstileSecret = process.env.TURNSTILE_SECRET || ""; - -export async function fetchMailbox(data: FormData) { - try { - const token = data.get("cf-turnstile-response"); - if (!token || typeof token !== "string") { - return; - } - if (await getMailbox()) { - return; - } - const res = await fetch(verifyEndpoint, { - method: "POST", - body: `secret=${encodeURIComponent( - turnstileSecret - )}&response=${encodeURIComponent(token)}`, - headers: { - "content-type": "application/x-www-form-urlencoded", - }, - }); - if (!res.ok) { - return; - } - const json = await res.json(); - if (!json.success) { - return; - } - - const name = randomName("", "."); - const domain = "vmail.dev"; - const mailbox = `${name}@${domain}`; - - const secret = process.env.COOKIES_SECRET as string; - const value = await sign(mailbox, secret); - cookies().set({ - name: "mailbox", - value: value, - httpOnly: true, - }); - } catch (e) { - console.log(e); - return; - } -} - -export async function POST(req: NextRequest) { - const form = await req.formData(); - await fetchMailbox(form); - return NextResponse.redirect("/"); -} diff --git a/apps/next/src/app/api/mails/route.ts b/apps/next/src/app/api/mails/route.ts deleted file mode 100644 index a9a4c27..0000000 --- a/apps/next/src/app/api/mails/route.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { unsign } from "@/crypto"; -import { emails, getWebTursoDB, orm } from "database"; -import { cookies } from "next/headers"; -import { NextResponse } from "next/server"; - -export async function getMailbox() { - const cookie = cookies().get("mailbox"); - if (!cookie) { - return undefined; - } - const secret = process.env.COOKIES_SECRET as string; - const mailbox = await unsign(cookie.value, secret); - if (!mailbox) { - return undefined; - } - return mailbox; -} - -export async function fetchEmails(mailbox: string | undefined) { - try { - if (!mailbox) { - return []; - } - const url = process.env.TURSO_DB_URL as string; - const roAuthToken = process.env.TURSO_DB_RO_AUTH_TOKEN as string; - const db = getWebTursoDB(url, roAuthToken); - const mails = await db - .select() - .from(emails) - .where(orm.and(orm.eq(emails.messageTo, mailbox))) - .orderBy(orm.desc(emails.createdAt)) - .execute(); - return mails; - } catch (e) { - console.log(e); - return []; - } -} - -export async function GET() { - const mailbox = await getMailbox(); - const mails = await fetchEmails(mailbox); - return NextResponse.json(mails); -} diff --git a/apps/next/src/app/favicon.ico b/apps/next/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/apps/next/src/app/favicon.ico and /dev/null differ diff --git a/apps/next/src/app/globals.css b/apps/next/src/app/globals.css deleted file mode 100644 index b5c61c9..0000000 --- a/apps/next/src/app/globals.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/apps/next/src/app/layout.tsx b/apps/next/src/app/layout.tsx deleted file mode 100644 index b7a746a..0000000 --- a/apps/next/src/app/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Metadata } from "next"; -import { ReactNode } from "react"; -import { Inter } from "next/font/google"; -import "./globals.css"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Vmail", - description: "Vmail is a temporary email service", -}; - -export default function RootLayout({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} diff --git a/apps/next/src/app/mails/[id]/layout.tsx b/apps/next/src/app/mails/[id]/layout.tsx deleted file mode 100644 index 8ff990f..0000000 --- a/apps/next/src/app/mails/[id]/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import Header from "@/components/Header"; -import Footer from "@/components/Footer"; -import { ReactNode } from "react"; - -export default function MailViewLayout({ children }: { children: ReactNode }) { - return ( -
-
- {children} -
- ); -} diff --git a/apps/next/src/app/mails/[id]/page.tsx b/apps/next/src/app/mails/[id]/page.tsx deleted file mode 100644 index 77fafb6..0000000 --- a/apps/next/src/app/mails/[id]/page.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { getMailbox } from "@/app/api/mails/route"; -import { emails, getWebTursoDB, orm } from "database"; -import { format } from "date-fns/format"; -import { ArrowUturnLeft, UserCircleIcon } from "icons"; -import Link from "next/link"; - -async function fetchMail(id: string) { - const mailbox = await getMailbox(); - if (!mailbox) { - return null; - } - const url = process.env.TURSO_DB_URL as string; - const roAuthToken = process.env.TURSO_DB_RO_AUTH_TOKEN as string; - const db = getWebTursoDB(url, roAuthToken); - const mails = await db - .select() - .from(emails) - .where(orm.and(orm.eq(emails.id, id))) - .limit(1) - .execute(); - if (mails.length === 0) { - return null; - } - const email = mails[0]; - if (email.messageTo !== mailbox) { - return null; - } - return email; -} - -export default async function MailViewer({ - params, -}: { - params: { id: string }; -}) { - const mail = await fetchMail(params.id); - - if (!mail) { - return ( -
- - - Back Home - - -
- No mail found -
-
- ); - } - - return ( -
- - - Back Home - -
-
-
- -
-
-
{mail.from.name}
-
{mail.subject}
-
- Reply-To: {mail.from.address} -
-
-
- {mail.date && ( -
- {format(new Date(mail.date), "PPpp")} -
- )} -
-
-
-
-
- ); -} diff --git a/apps/next/src/app/page.tsx b/apps/next/src/app/page.tsx deleted file mode 100644 index 5a20a85..0000000 --- a/apps/next/src/app/page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { fetchEmails, getMailbox } from "@/app/api/mails/route"; -import CopyButton from "@/components/CopyButton"; -import FetchMailboxFormWithCaptcha from "@/components/FetchMailboxForm"; -import Footer from "@/components/Footer"; -import Header from "@/components/Header"; -import MailListWithQuery from "@/components/MailList"; - -const turnstileKey = process.env.TURNSTILE_KEY || "1x00000000000000000000AA"; - -export default async function Home() { - const mailbox = await getMailbox(); - const mails = await fetchEmails(mailbox); - - return ( -
-
-
-

- YourTemporary - Email Address -

- {mailbox ? ( - - ) : ( - - )} -
-

- Forget about spam, advertising mailings, hacking and attacking robots. - Keep your real mailbox clean and secure. Temp Mail provides temporary, - secure, anonymous, free, disposable email address. -

- -
- ); -} - -export function MailboxWithCopyButton({ mailbox }: { mailbox: string }) { - return ( -
- {mailbox} - -
- ); -} diff --git a/apps/next/src/components/CopyButton.tsx b/apps/next/src/components/CopyButton.tsx deleted file mode 100644 index 4b70678..0000000 --- a/apps/next/src/components/CopyButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { CopyIcon, ExclamationCircle, CheckIcon } from "icons"; -import { ButtonHTMLAttributes, DetailedHTMLProps, useState } from "react"; - -interface CopyButtonProps - extends DetailedHTMLProps< - ButtonHTMLAttributes, - HTMLButtonElement - > { - content: string; -} - -export default function CopyButton(props: CopyButtonProps) { - const [status, setStatus] = useState("idle"); - - const icons = { - idle: , - error: , - success: , - }; - - function copy() { - navigator.clipboard - .writeText(props.content) - .then(() => setStatus("success")) - .catch(() => setStatus("error")) - .finally(() => setTimeout(() => setStatus("idle"), 1000)); - } - - return ( - - ); -} diff --git a/apps/next/src/components/FetchMailboxForm.tsx b/apps/next/src/components/FetchMailboxForm.tsx deleted file mode 100644 index 99e9608..0000000 --- a/apps/next/src/components/FetchMailboxForm.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { fetchMailbox } from "@/app/api/mailbox/route"; -import { Turnstile } from "@marsidev/react-turnstile"; -import { useState } from "react"; - -export default function FetchMailboxFormWithCaptcha({ - siteKey, -}: { - siteKey: string; -}) { - const [disabled, setDisabled] = useState(true); - return ( -
- setDisabled(false)} - /> - - - ); -} diff --git a/apps/next/src/components/Footer.tsx b/apps/next/src/components/Footer.tsx deleted file mode 100644 index b64336c..0000000 --- a/apps/next/src/components/Footer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export default function Footer() { - return ( -
- Made with ❤️ by{" "} - - akazwz - -
- ); -} diff --git a/apps/next/src/components/Header.tsx b/apps/next/src/components/Header.tsx deleted file mode 100644 index c2aa34d..0000000 --- a/apps/next/src/components/Header.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { GithubIcon } from "icons"; -import Link from "next/link"; - -export default function Header() { - return ( -
- - Vmail - - - - -
- ); -} diff --git a/apps/next/src/components/MailList.tsx b/apps/next/src/components/MailList.tsx deleted file mode 100644 index 207fe0f..0000000 --- a/apps/next/src/components/MailList.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; - -import { formatDistanceToNow } from "date-fns"; -import { Email } from "database/schema"; -import { MailIcon, InboxIcon, RefreshIcon } from "icons"; -import { - QueryClient, - QueryClientProvider, - useQuery, -} from "@tanstack/react-query"; -import Link from "next/link"; - -const queryClient = new QueryClient(); - -export function MailItem({ mail: item }: { mail: Email }) { - return ( - -
-
-
-
{item.from.name}
-
-
- {formatDistanceToNow(new Date(item.date || item.createdAt), { - addSuffix: true, - })} -
-
-
{item.subject}
-
-
- {item.text || item.html || "".substring(0, 300)} -
- - ); -} - -async function fetchMails() { - try { - const resp = await fetch("http://localhost:3000/api/mails"); - return await resp.json(); - } catch (e) { - return []; - } -} - -export function MailList(props: { mails: Email[] }) { - const { data, isFetching } = useQuery({ - queryKey: ["mails"], - queryFn: fetchMails, - initialData: props.mails, - refetchInterval: 10000, - }); - - return ( - <> -
- - mails - -
-
- {data.length === 0 && ( -
- -

No mails found

-
- )} - {data.map((mail: Email) => ( - - ))} -
- - ); -} - -export default function MailListWithQuery({ mails }: { mails: Email[] }) { - return ( - - - - ); -} diff --git a/apps/next/src/crypto.ts b/apps/next/src/crypto.ts deleted file mode 100644 index 0d90ad0..0000000 --- a/apps/next/src/crypto.ts +++ /dev/null @@ -1,45 +0,0 @@ -const encoder = new TextEncoder(); - -export async function sign(value: string, secret: string) { - const data = encoder.encode(value); - const key = await createKey(secret, ["sign"]); - const signature = await crypto.subtle.sign("HMAC", key, data); - const hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace( - /=+$/, - "", - ); - return value + "." + hash; -} - -export async function unsign(cookie: string, secret: string) { - const value = cookie.slice(0, cookie.lastIndexOf(".")); - const hash = cookie.slice(cookie.lastIndexOf(".") + 1); - const data = encoder.encode(value); - const key = await createKey(secret, ["verify"]); - const signature = byteStringToUint8Array(atob(hash)); - const valid = await crypto.subtle.verify("HMAC", key, signature, data); - return valid ? value : false; -} - -async function createKey( - secret: string, - usages: CryptoKey["usages"], -): Promise { - return await crypto.subtle.importKey( - "raw", - encoder.encode(secret), - { name: "HMAC", hash: "SHA-256" }, - false, - usages, - ); -} - -function byteStringToUint8Array(byteString: string): Uint8Array { - const array = new Uint8Array(byteString.length); - - for (let i = 0; i < byteString.length; i++) { - array[i] = byteString.charCodeAt(i); - } - - return array; -} diff --git a/apps/next/src/types.tsx b/apps/next/src/types.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/apps/next/tailwind.config.ts b/apps/next/tailwind.config.ts deleted file mode 100644 index 53eb985..0000000 --- a/apps/next/tailwind.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Config } from "tailwindcss"; -import typography from "@tailwindcss/typography"; - -const config: Config = { - content: [ - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", - "./src/components/**/*.{js,ts,jsx,tsx,mdx}", - "./src/app/**/*.{js,ts,jsx,tsx,mdx}", - ], - theme: { - extend: {}, - }, - plugins: [typography], -}; -export default config; diff --git a/apps/next/tsconfig.json b/apps/next/tsconfig.json deleted file mode 100644 index b0f2993..0000000 --- a/apps/next/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "downlevelIteration": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -}