-
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of ssh://github.com/reacherhq/webapp into am/re…
…move-free-trial
- Loading branch information
Showing
9 changed files
with
412 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import { NextRequest } from "next/server"; | ||
import amqplib from "amqplib"; | ||
import { supabaseAdmin } from "@/util/supabaseServer"; | ||
import { sentryException } from "@/util/sentry"; | ||
import { getWebappURL } from "@/util/helpers"; | ||
import { Tables } from "@/supabase/database.types"; | ||
|
||
interface BulkPayload { | ||
input_type: "array"; | ||
input: string[]; | ||
} | ||
|
||
export const POST = async (req: NextRequest): Promise<Response> => { | ||
// TODO Remove this once we allow Bulk. | ||
if (process.env.VERCEL_ENV === "production") { | ||
return Response.json( | ||
{ error: "Not available in production" }, | ||
{ status: 403 } | ||
); | ||
} | ||
|
||
try { | ||
const user = await getUser(req); | ||
|
||
const payload: BulkPayload = await req.json(); | ||
|
||
// Add to Supabase | ||
const res1 = await supabaseAdmin | ||
.from("bulk_jobs") | ||
.insert({ | ||
user_id: user.id, | ||
payload, | ||
}) | ||
.select("*"); | ||
if (res1.error) { | ||
throw res1.error; | ||
} | ||
const bulkJob = res1.data[0]; | ||
const res2 = await supabaseAdmin | ||
.from("bulk_emails") | ||
.insert( | ||
payload.input.map((email) => ({ | ||
bulk_job_id: bulkJob.id, | ||
email, | ||
})) | ||
) | ||
.select("*"); | ||
if (res2.error) { | ||
throw res2.error; | ||
} | ||
|
||
const conn = await amqplib | ||
.connect(process.env.RCH_AMQP_ADDR || "amqp://localhost") | ||
.catch((err) => { | ||
const message = `Error connecting to RabbitMQ: ${ | ||
(err as AggregateError).errors | ||
? (err as AggregateError).errors | ||
.map((e) => e.message) | ||
.join(", ") | ||
: err.message | ||
}`; | ||
|
||
throw new Error(message); | ||
}); | ||
|
||
const ch1 = await conn.createChannel().catch((err) => { | ||
throw new Error(`Error creating RabbitMQ channel: ${err.message}`); | ||
}); | ||
const queueName = `check_email.Smtp`; // TODO | ||
await ch1.assertQueue(queueName, { | ||
maxPriority: 5, | ||
}); | ||
|
||
res2.data.forEach(({ email, id }) => { | ||
ch1.sendToQueue( | ||
queueName, | ||
Buffer.from( | ||
JSON.stringify({ | ||
input: { | ||
to_email: email, | ||
}, | ||
webhook: { | ||
url: `${getWebappURL()}/api/v1/bulk/webhook`, | ||
extra: { | ||
bulkEmailId: id, | ||
userId: user.id, | ||
endpoint: "/v1/bulk", | ||
}, | ||
}, | ||
}) | ||
), | ||
{ | ||
contentType: "application/json", | ||
priority: 1, | ||
} | ||
); | ||
}); | ||
|
||
await ch1.close(); | ||
await conn.close(); | ||
|
||
return Response.json({ message: "Hello world!", res: res1 }); | ||
} catch (err) { | ||
if (isEarlyResponse(err)) { | ||
return err.response; | ||
} | ||
|
||
sentryException(err as Error); | ||
return Response.json( | ||
{ | ||
error: (err as Error).message, | ||
}, | ||
{ | ||
status: 500, | ||
} | ||
); | ||
} | ||
}; | ||
|
||
async function getUser(req: NextRequest): Promise<Tables<"users">> { | ||
const token = req.headers.get("Authorization"); | ||
|
||
if (typeof token !== "string") { | ||
throw new Error("Expected API token in the Authorization header."); | ||
} | ||
|
||
const { data, error } = await supabaseAdmin | ||
.from<Tables<"users">>("users") | ||
.select("*") | ||
.eq("api_token", token); | ||
if (error) { | ||
throw error; | ||
} | ||
if (!data?.length) { | ||
throw { | ||
response: newEarlyResponse( | ||
Response.json( | ||
{ error: "Invalid API token." }, | ||
{ | ||
status: 401, | ||
} | ||
) | ||
), | ||
}; | ||
} | ||
|
||
return data[0]; | ||
} | ||
|
||
type EarlyResponse = { | ||
response: Response; | ||
}; | ||
|
||
function newEarlyResponse(response: Response): EarlyResponse { | ||
return { response }; | ||
} | ||
|
||
function isEarlyResponse(err: unknown): err is EarlyResponse { | ||
return (err as EarlyResponse).response !== undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { CheckEmailOutput } from "@reacherhq/api"; | ||
import { supabaseAdmin } from "@/util/supabaseServer"; | ||
import { NextRequest } from "next/server"; | ||
import { removeSensitiveData } from "@/util/api"; | ||
import { Tables } from "@/supabase/database.types"; | ||
|
||
export interface WebhookExtra { | ||
bulkEmailId: string; | ||
userId: string; | ||
endpoint: string; | ||
} | ||
|
||
export interface WebhookPayload { | ||
output: CheckEmailOutput; | ||
extra: WebhookExtra; | ||
} | ||
|
||
export const POST = async (req: NextRequest): Promise<Response> => { | ||
if (req.headers.get("x-reacher-secret") !== process.env.RCH_HEADER_SECRET) { | ||
return Response.json({ error: "Invalid header secret" }); | ||
} | ||
|
||
const body: WebhookPayload = await req.json(); | ||
const { output, extra } = body; | ||
|
||
// Add to supabase calls | ||
const res1 = await supabaseAdmin | ||
.from<Tables<"calls">>("calls") | ||
.insert({ | ||
endpoint: extra.endpoint, | ||
user_id: extra.userId, | ||
backend: output.debug?.server_name, | ||
domain: output.syntax.domain, | ||
duration: Math.round( | ||
(output.debug?.duration.secs || 0) * 1000 + | ||
(output.debug?.duration.nanos || 0) / 1000000 | ||
), | ||
is_reachable: output.is_reachable, | ||
verif_method: output.debug?.smtp?.verif_method?.type, | ||
result: removeSensitiveData(output), | ||
}) | ||
.select("*"); | ||
if (res1.error) { | ||
return Response.json(res1.error, res1); | ||
} | ||
|
||
// Update bulk_emails table | ||
const res2 = await supabaseAdmin | ||
.from("bulk_emails") | ||
.update({ call_id: res1.data[0].id }) | ||
.eq("id", extra.bulkEmailId); | ||
if (res2.error) { | ||
return Response.json(res2.error, res2); | ||
} | ||
|
||
return Response.json({ message: "ok" }, { status: 200 }); | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
export * from "./Commercial"; | ||
export * from "./FreeTrial"; | ||
export * from "./SaaS10k"; | ||
export * from "./SaaS100k"; |
Oops, something went wrong.