Skip to content

Commit

Permalink
Merge pull request #59 from zkemail/feat/sample-email
Browse files Browse the repository at this point in the history
feat: added sample email flow when creating patterns
  • Loading branch information
javiersuweijie authored Oct 23, 2024
2 parents f5e0515 + 75bfdc4 commit 94f0437
Show file tree
Hide file tree
Showing 9 changed files with 607 additions and 353 deletions.
5 changes: 3 additions & 2 deletions packages/app/src/app/api/download/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateCodeLibrary } from "@/lib/code-gen/gen";
import { generateCodeLibrary, generateZip } from "@/lib/code-gen/gen";
import { NextRequest, NextResponse } from "next/server";
import { getEntryBySlug } from "@/lib/models/entry";
import { readFileSync, statSync } from "fs";
Expand All @@ -13,7 +13,8 @@ export async function GET(request: NextRequest, { params }: { params: { slug: st
status: 404
})
}
const output = await generateCodeLibrary(entry.parameters, entry.slug, entry.status);
await generateCodeLibrary(entry.parameters, entry.slug, entry.status);
const output = await generateZip(entry.slug);
const stats = statSync(output);
const fileContent = readFileSync(output)

Expand Down
24 changes: 11 additions & 13 deletions packages/app/src/app/api/script/circuit_input/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { NextRequest, NextResponse } from "next/server";
import fs from 'fs';
import path from 'path';
import { getEntryBySlug } from "@/lib/models/entry";

const OUTPUT_DIR = process.env.GENERATED_OUTPUT_DIR || "./output";

export async function GET(request: NextRequest, { params }: { params: { slug: string[] }}) {
const slug = params.slug.join('/')
const entry = await getEntryBySlug(slug);
if (!entry) {
return new NextResponse("Entry not found", { status: 404 })
try {
const file = fs.readFileSync(path.join(OUTPUT_DIR, "code", slug, "generate_inputs_worker_bundled.js"))
const content = Buffer.from(file);
return new NextResponse(content, {
headers: {
'Content-Type': 'text/javascript',
"Content-Length": '' + content.length,
}
})
} catch (e) {
return new NextResponse("Not found", { status: 404 })
}
const file = fs.readFileSync(path.join(OUTPUT_DIR, "code", entry?.slug, "generate_inputs_worker_bundled.js"))
const content = Buffer.from(file);

return new NextResponse(content, {
headers: {
'Content-Type': 'text/javascript',
"Content-Length": ''+content.length,
}
})
}
4 changes: 2 additions & 2 deletions packages/app/src/app/edit/[[...slug]]/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { submit } from "./action";
import { Entry } from "@prisma/client";
import { EntryForm } from "@/components/entry-form";

export default function Content({entry}: {entry: Entry}) {
export default function Content({entry, sampleEmail}: {entry: Entry, sampleEmail: string}) {
return (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
Expand All @@ -13,7 +13,7 @@ export default function Content({entry}: {entry: Entry}) {
<h1 className="text-xl md:text-3xl tracking-tighter text-left font-extrabold">Edit pattern</h1>
<h1 className="text-md md:text-xl tracking-tighter text-left">{entry.title}</h1>
<h1 className="text-md md:text-xl tracking-tighter text-left">{entry.description}</h1>
<EntryForm entry={entry} onFormSubmit={submit} />
<EntryForm entry={entry} onFormSubmit={submit} sampleEmailFlag={sampleEmail ? true : false} />
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/app/edit/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { getEntryBySlug } from "@/lib/models/entry";
import Content from "./content";

export default async function EditPage({params}: {params: {slug: string[]}}) {
export default async function EditPage({params, searchParams: {sampleEmail}}: {params: {slug: string[]}, searchParams: {sampleEmail: string}}) {
const slug = params.slug.join('/');
const entry = await getEntryBySlug(slug);

if (!entry) {
return (<h1>Entry not found</h1>)
}
return (<Content entry={entry}/>)
return (<Content entry={entry} sampleEmail={sampleEmail} />)
}
8 changes: 4 additions & 4 deletions packages/app/src/app/submit/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { z } from 'zod';
import { formSchema } from './form';
import prisma from '@/lib/prisma';

export async function createEntry(values: z.infer<typeof formSchema>) {

export async function createEntry(values: z.infer<typeof formSchema>, draft = false) {
const entry = {
title: values.title,
slug: values.slug,
Expand All @@ -15,11 +14,12 @@ export async function createEntry(values: z.infer<typeof formSchema>) {
...values.parameters,
version: values.useNewSdk ? "v2" : "v1",
},
emailQuery: values.emailQuery
emailQuery: values.emailQuery,
status: draft ? "DRAFT" : "PENDING",
}

try {
const createdEntry = await prisma.entry.create({data: entry})
const createdEntry = await prisma.entry.create({ data: entry })
return {
error: false,
message: createdEntry
Expand Down
139 changes: 139 additions & 0 deletions packages/app/src/app/submit/email/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use server';
import { z } from 'zod';
import { formSchema } from '../form';
import { generateCodeLibrary } from '@/lib/code-gen/gen';
import { verifyDKIMSignature } from "@zk-email/helpers/dist/dkim";

export interface ProcessEmailResult {
error: boolean,
message: string,
parameters?: {
maxHeaderLength: number,
maxBodyLength?: number,
domain: string,
selector: string,
},
matches: {
name: string,
match: string,
}[],
}

export async function processEmail(values: z.infer<typeof formSchema>, email: string): Promise<ProcessEmailResult> {
let res;
let result;
let bodyString;
let headerString;

try {
result = await verifyDKIMSignature(email)
} catch (e: any) {
return {
error: true,
matches: [],
message: "Error verifying DKIM signature: " + e.toString()
}
}
headerString = result.headers.toString();
const headerLength = result.headers.length;
const maxHeaderLength = Math.ceil(headerLength / 64) * 64;
const domain = result.signingDomain;
const selector = result.selector;
res = {
maxHeaderLength,
domain,
selector,
}
if (!values.parameters.ignoreBodyHashCheck) {
bodyString = result.body.toString();
if (values.parameters.shaPrecomputeSelector) {
const split = bodyString.split(values.parameters.shaPrecomputeSelector);
if (split.length > 2) {
return {
error: true,
matches: [],
message: "Non-unique email body cut-off value. Use something that can split the email into strictly two parts."
}
}
if (split.length == 1) {
return {
error: true,
matches: [],
message: "Email body cut-off value is not found in the email body."
}
}
const bodyLength = split[1].length;
const maxBodyLength = Math.ceil(bodyLength / 64) * 64;
res = {
...res,
maxBodyLength,
}
}
}

// Apply regex
const regexes = values.parameters.values.map((v: any) => {
let publicGroups: number[] = [];
let index = 1;
const regex = v.parts.reduce((acc: any, part: any) => {
if (part.is_public) {
publicGroups.push(index);
index++;
return acc + "(" + part.regex_def + ")"
}
return acc + part.regex_def
}, "");
return {
regex,
publicGroups,
name: v.name,
location: v.location,
}
})

let matches = []

for (const regex of regexes) {
if (regex.location == "body" && bodyString) {
const match = bodyString.match(regex.regex)
if (match) {
for (const group of regex.publicGroups) {
matches.push({
name: regex.name,
match: match[group],
})
}
}
} else {
const match = headerString.match(regex.regex)
if (match) {
for (const group of regex.publicGroups) {
matches.push({
name: regex.name,
match: match[group],
})
}
}
}
}

const parameters = {
...values.parameters,
version: values.useNewSdk ? "v2" : "v1",
}
try {
await generateCodeLibrary(parameters, "drafts/" + values.slug, "DRAFT");
} catch (e: any) {
return {
error: true,
matches: [],
message: "Error generating code: " + e.toString()
}
}
return {
error: false,
message: "Email processed successfully",
parameters: res,
matches,
}
}
27 changes: 9 additions & 18 deletions packages/app/src/app/submit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
'use client';
import { z } from 'zod';
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm, useFieldArray } from "react-hook-form"
import { Button } from '@/components/ui/button';
import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox';
import { Plus, Trash } from 'lucide-react';
import { formSchema } from './form';
import { createEntry } from './action';
import { Dialog, DialogTrigger, DialogContent, DialogTitle, DialogDescription, DialogHeader } from '@/components/ui/dialog';
import { useEffect, useState } from 'react';
import { FromAddressPattern, SubjectPattern, TimestampPattern, ToAddressPattern } from './patterns';
import { Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectLabel, SelectItem } from '@/components/ui/select';
import { EntryForm } from '@/components/entry-form';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Entry } from '@prisma/client';
import { JsonValue } from '@prisma/client/runtime/library';
import { useState } from 'react';
import { z } from 'zod';
import { createEntry } from './action';
import { formSchema } from './form';

export default function Submit() {
export default function Submit({searchParams: {sampleEmail}}: {searchParams: {sampleEmail: string}}) {

const [modal, setModal] = useState<boolean>(false);
const [modalMessage, setModalMessage] = useState<string>("");
Expand Down Expand Up @@ -69,21 +60,21 @@ export default function Submit() {
setModal(true)
}


return (
<div className="w-full py-20 lg:py-40">
<div className="container mx-auto">
<div className="flex flex-col gap-10">
<div className="flex text-left justify-center items-center gap-4 flex-col px-10 md:px-40">
<h1 className="text-xl md:text-3xl tracking-tighter text-left font-extrabold">Submit new pattern</h1>
<Button className="mb-6" variant="secondary" size="sm" onClick={fillDemo}>Fill form using a sample</Button>
<EntryForm entry={entry} onFormSubmit={onSubmit} />
<EntryForm entry={entry} onFormSubmit={onSubmit} sampleEmailFlag={sampleEmail ? true : false}/>
</div>
</div>
</div>
<Dialog open={modal} onOpenChange={setModal}>
<DialogTrigger></DialogTrigger>
<DialogContent>
<DialogTitle>{modalError ? "Error" : "Success"}</DialogTitle>
<DialogHeader>
<DialogDescription>
{modalMessage}
Expand Down
Loading

0 comments on commit 94f0437

Please sign in to comment.