Skip to content

Commit

Permalink
feat: add upload function & disable email sending for now
Browse files Browse the repository at this point in the history
  • Loading branch information
BlankParticle committed Aug 15, 2024
1 parent a2d6807 commit 633f93d
Show file tree
Hide file tree
Showing 8 changed files with 3,962 additions and 3,040 deletions.
1 change: 1 addition & 0 deletions apps/mailtools/drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dotenv.config();
export default {
schema: './src/lib/db/schema.ts',
out: './drizzle/migrations',
dialect: 'sqlite',
driver: 'turso',
dbCredentials: {
url: process.env.TURSO_URI!,
Expand Down
60 changes: 30 additions & 30 deletions apps/mailtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,43 @@
"db:studio": "drizzle-kit studio"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^8.56.0",
"@sveltejs/adapter-auto": "^3.2.4",
"@sveltejs/kit": "^2.5.22",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@types/eslint": "^9.6.0",
"@types/mailparser": "^3.4.4",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"autoprefixer": "^10.4.16",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"autoprefixer": "^10.4.20",
"dotenv": "^16.4.5",
"drizzle-kit": "^0.20.14",
"eslint": "^8.56.0",
"eslint-plugin-svelte": "^2.35.1",
"mailparser": "^3.6.9",
"postcss": "^8.4.32",
"postcss-load-config": "^5.0.2",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tailwindcss": "^3.3.6",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.3"
"drizzle-kit": "^0.24.0",
"eslint": "^9.9.0",
"eslint-plugin-svelte": "^2.43.0",
"mailparser": "^3.7.1",
"postcss": "^8.4.41",
"postcss-load-config": "^6.0.1",
"svelte": "^4.2.18",
"svelte-check": "^3.8.5",
"tailwindcss": "^3.4.10",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"vite": "^5.4.1"
},
"type": "module",
"dependencies": {
"@libsql/client": "^0.6.0",
"@libsql/client": "^0.9.0",
"@types/qrcode": "^1.5.5",
"@u22n/mailtools": "workspace:^",
"bits-ui": "^0.21.1",
"clsx": "^2.1.0",
"drizzle-orm": "^0.30.6",
"lucide-svelte": "^0.363.0",
"mode-watcher": "^0.3.0",
"nanoid": "^5.0.6",
"qrcode": "^1.5.3",
"svelte-sonner": "^0.3.20",
"svelte-turnstile": "^0.5.0",
"tailwind-merge": "^2.2.2",
"bits-ui": "^0.21.13",
"clsx": "^2.1.1",
"drizzle-orm": "^0.33.0",
"lucide-svelte": "^0.427.0",
"mode-watcher": "^0.4.1",
"nanoid": "^5.0.7",
"qrcode": "^1.5.4",
"svelte-sonner": "^0.3.27",
"svelte-turnstile": "^0.8.0",
"tailwind-merge": "^2.5.2",
"tailwind-variants": "^0.2.1"
}
}
27 changes: 22 additions & 5 deletions apps/mailtools/src/components/custom/Dropzone.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
export let onFileAdded: (file: File) => void;
let form: HTMLFormElement;
function handleFileAdded(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (file?.type !== 'message/rfc822') {
toast.error('Invalid file type. Please upload a .eml file');
form.reset();
return;
}
if (file) onFileAdded(file);
}
</script>

<div
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-32 w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
role="form">
<form
class="flex w-full flex-col gap-1"
bind:this={form}>
<h2 class="font-bold">Drop a file here to upload</h2>
<input
type="file"
class="hidden" />
</div>
accept="message/rfc822"
on:change={handleFileAdded}
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-32 w-full rounded-md border border-dashed px-3 py-2 text-sm file:hidden focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
</form>
123 changes: 103 additions & 20 deletions apps/mailtools/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import { toast } from 'svelte-sonner';
import { Turnstile } from 'svelte-turnstile';
import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public';
import Dropzone from '@components/custom/Dropzone.svelte';
import { Badge } from '@components/ui/badge';
import { Textarea } from '@components/ui/textarea';
import Error from './+error.svelte';
export let data: PageData;
let copied = false;
Expand Down Expand Up @@ -66,6 +70,58 @@
data.email = email.email;
}
}
let file: File | null = null;
let fileContents = '';
async function uploadFile() {
if (!turnstileResponse) return;
if (file && fileContents) {
toast.warning(
'Please Either upload a file or paste the contents, not both, refresh the page to try again'
);
return;
}
pending = true;
const formData = new FormData();
if (fileContents) {
formData.append(
'file',
new Blob([fileContents], { type: 'message/rfc822' }),
'email.eml'
);
} else if (file) {
formData.append('file', file, 'email.eml');
} else {
toast.warning('Please upload a file or paste the contents');
return;
}
formData.append('token', turnstileResponse);
const response = await fetch('/api/upload-file', {
method: 'POST',
body: formData
})
.then((res) => res.json())
.catch((err) => {
toast(
err instanceof Error
? err.message
: 'Failed to upload email. Please try again '
);
return null;
})
.finally(() => {
pending = false;
});
if (response.success) {
toast('Email uploaded successfully. Redirecting to the result page...');
goto('/result');
} else {
toast(response.message);
}
}
</script>

<main
Expand All @@ -88,11 +144,21 @@
target="_blank">UnInbox</a> to clean up emails before displaying them to users.
</p>
<h2 class="pt-6 text-3xl font-bold">Get a Live Demo</h2>
<Tabs.Root value="email">
<Tabs.Root value="file">
<Tabs.List>
<Tabs.Trigger value="email">
<span class="flex gap-1">Send us an Email</span>
</Tabs.Trigger>
<Tooltip.Root>
<Tooltip.Trigger>
<Tabs.Trigger
value="email"
disabled>
<span class="flex gap-1">Send us an Email</span>
</Tabs.Trigger>
</Tooltip.Trigger>
<Tooltip.Content>
Disabled for now as we have disabled the spare email server
temporarily.
</Tooltip.Content>
</Tooltip.Root>
<Tabs.Trigger value="file">
<span class="flex gap-1">
<code>.eml</code> File
Expand Down Expand Up @@ -174,12 +240,7 @@
We will generate a unique email address for you to send your
mail.
</h2>
<Turnstile
siteKey={PUBLIC_TURNSTILE_SITE_KEY}
theme="auto"
on:turnstile-callback={(e) => {
turnstileResponse = e.detail.token;
}} />

<Button
variant="default"
disabled={!turnstileResponse || pending}
Expand Down Expand Up @@ -213,13 +274,11 @@
</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex flex-col">
<h2 class="mx-auto text-2xl font-bold">
<span class="animate-pulse">🚧</span>
<span>Work in Progress</span>
<span class="animate-pulse">🚧</span>
</h2>
<!-- <Dropzone />
<div class="flex flex-col items-center justify-center gap-2">
<Dropzone
onFileAdded={(f) => {
file = f;
}} />
<div
class="relative mx-auto flex w-3/4 items-center justify-center px-2 py-8">
<hr class="w-full" />
Expand All @@ -229,10 +288,34 @@
</div>
<Textarea
placeholder="Paste file contents here"
class="font-mono" />
</div> -->
</div></Card.Content>
class="font-mono"
bind:value={fileContents} />

<Button
variant="default"
disabled={!turnstileResponse ||
pending ||
(!file && !fileContents)}
on:click={uploadFile}>
{#if !turnstileResponse}
Waiting for Captcha...
{:else if pending}
Uploading...
{:else}
Upload
{/if}
</Button>
</div>
</Card.Content>
</Card.Root>
</Tabs.Content>
</Tabs.Root>
<div class="mx-auto flex w-fit flex-col items-center justify-center gap-2">
<Turnstile
siteKey={PUBLIC_TURNSTILE_SITE_KEY}
theme="auto"
on:turnstile-callback={(e) => {
turnstileResponse = e.detail.token;
}} />
</div>
</main>
4 changes: 4 additions & 0 deletions apps/mailtools/src/routes/api/generate-email/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import { error, json, type RequestHandler } from '@sveltejs/kit';
import verifyTurnstileToken from '@lib/turnstile';
import { TURNSTILE_SECRET_KEY } from '$env/static/private';

const emailDisabled = true;

export const POST: RequestHandler = async ({
cookies,
request,
getClientAddress
}) => {
if (emailDisabled) return error(404, 'Email generation is disabled');

const body = await request.json();
if (!body.token) {
error(400, 'Missing turnstile response');
Expand Down
68 changes: 68 additions & 0 deletions apps/mailtools/src/routes/api/upload-file/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { db } from '@lib/db';
import { emails, emailStorage } from '@lib/db/schema';
import { nanoid } from 'nanoid';
import { error, json, type RequestHandler } from '@sveltejs/kit';
import verifyTurnstileToken from '@lib/turnstile';
import { TURNSTILE_SECRET_KEY } from '$env/static/private';
import { simpleParser } from 'mailparser';

export const POST: RequestHandler = async ({
cookies,
request,
getClientAddress
}) => {
const body = await request.formData();
const cloudflareToken = body.get('token');

if (typeof cloudflareToken !== 'string' || !cloudflareToken) {
error(400, 'Missing turnstile response');
}
const valid = await verifyTurnstileToken({
response: cloudflareToken,
secretKey: TURNSTILE_SECRET_KEY,
remoteIp: getClientAddress()
});
if (!valid) {
error(400, 'Invalid turnstile response');
}
const email = body.get('file');
if (!(email instanceof File)) {
error(400, 'Missing email file');
}

const rawEmail = await email.text();

const parsedEmail = await simpleParser(rawEmail).catch((err) => {
if (err instanceof Error) {
return { error: err.message };
} else {
return { error: 'Unknown error' };
}
});

if ('error' in parsedEmail) {
error(400, parsedEmail.error);
}
console.log(parsedEmail);

const bodyHtml = parsedEmail.html || parsedEmail.textAsHtml;
if (!bodyHtml) {
error(400, 'Email has no body');
}

const token = nanoid();
const insert = await db.insert(emails).values({
emailAddress: 'uploaded-email',
token,
emailReceived: true
});

await db.insert(emailStorage).values({
emailId: Number(insert.lastInsertRowid ?? 0),
emailFrom: parsedEmail.from?.value[0].address || 'unknown',
emailContent: bodyHtml
});

cookies.set('token', token, { path: '/' });
return json({ success: true });
};
2 changes: 1 addition & 1 deletion apps/mailtools/src/routes/result/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { error } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ cookies, url }) => {
const token = url.searchParams.get('token') || cookies.get('token');
const email = await db.query.emails.findFirst({
where: and(eq(emails.token, token || '')),
where: and(eq(emails.token, token || 'none')),
columns: {
emailReceived: true
},
Expand Down
Loading

0 comments on commit 633f93d

Please sign in to comment.