Skip to content

Commit

Permalink
Merge pull request #52 from zacharyLYH/dev
Browse files Browse the repository at this point in the history
Merge deployment v1.0
  • Loading branch information
zacharyLYH authored Dec 23, 2023
2 parents 0eb8fe0 + 970deb2 commit a3aad83
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 179 deletions.
6 changes: 6 additions & 0 deletions .xata/migrations/.ledger
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ mig_cll7h3v3d17cd4sn3q60_deca122c
mig_cll7kiv3d17cd4sn3qdg_1d8d01c9
mig_cll7kp2ifsth3gbude0g_4d9ec4f1
mig_cll7ksaifsth3gbude2g_4f044676
mig_cm34vkpq6978ggbn3vug_5d52fdf6
mig_cm34vphq6978ggbn4000_70251885
mig_cm34vshq6978ggbn4010_757966e2
mig_cm3500fe1um8tedvc2fg_3ce35d63
mig_cm3505ve1um8tedvc2gg_5b23f048
mig_cm350a7e1um8tedvc2hg_9ff667b3
12 changes: 12 additions & 0 deletions .xata/migrations/mig_cm34vkpq6978ggbn3vug_5d52fdf6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "mig_cm34vkpq6978ggbn3vug",
"parentID": "mig_cll7ksaifsth3gbude2g",
"checksum": "1:5d52fdf657b549157718e1034424567986c2dca4789963fb267a04233e1d3a49",
"operations": [
{
"removeTable": {
"table": "Backlog-Score"
}
}
]
}
12 changes: 12 additions & 0 deletions .xata/migrations/mig_cm34vphq6978ggbn4000_70251885.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "mig_cm34vphq6978ggbn4000",
"parentID": "mig_cm34vkpq6978ggbn3vug",
"checksum": "1:702518851c8273236f41652bdd32368b31c0c5f5c01015297160a964966ab795",
"operations": [
{
"addTable": {
"table": "SubmissionsMVP"
}
}
]
}
16 changes: 16 additions & 0 deletions .xata/migrations/mig_cm34vshq6978ggbn4010_757966e2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "mig_cm34vshq6978ggbn4010",
"parentID": "mig_cm34vphq6978ggbn4000",
"checksum": "1:757966e2960f70642055f9193194b45397a8a7d560d4f2d9f16a5b9a0c8f26d5",
"operations": [
{
"addColumn": {
"column": {
"name": "email",
"type": "string"
},
"table": "SubmissionsMVP"
}
}
]
}
16 changes: 16 additions & 0 deletions .xata/migrations/mig_cm3500fe1um8tedvc2fg_3ce35d63.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "mig_cm3500fe1um8tedvc2fg",
"parentID": "mig_cm34vshq6978ggbn4010",
"checksum": "1:3ce35d631d9bcba7efc112a792b4211a2950659afaadbc6a022be4db518b03a1",
"operations": [
{
"addColumn": {
"column": {
"name": "code",
"type": "text"
},
"table": "SubmissionsMVP"
}
}
]
}
16 changes: 16 additions & 0 deletions .xata/migrations/mig_cm3505ve1um8tedvc2gg_5b23f048.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "mig_cm3505ve1um8tedvc2gg",
"parentID": "mig_cm3500fe1um8tedvc2fg",
"checksum": "1:5b23f048ebe20e787b277b8e71291d5ef7a0373d7915a2f917f04d155b9e76b7",
"operations": [
{
"addColumn": {
"column": {
"name": "challengeName",
"type": "string"
},
"table": "SubmissionsMVP"
}
}
]
}
16 changes: 16 additions & 0 deletions .xata/migrations/mig_cm350a7e1um8tedvc2hg_9ff667b3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "mig_cm350a7e1um8tedvc2hg",
"parentID": "mig_cm3505ve1um8tedvc2gg",
"checksum": "1:9ff667b37df60fbaee668dbe3008d5491be0f9a2af6d6a4e5b5f6b272bc93866",
"operations": [
{
"addColumn": {
"column": {
"name": "dateTime",
"type": "string"
},
"table": "SubmissionsMVP"
}
}
]
}
119 changes: 61 additions & 58 deletions app/api/code/score/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import LandingPageChallengeCode from "@/components/landing/test-challenges/challenge-code";
import axios from "axios";
import { xata } from "@/lib/xata_client";
import { NextResponse } from "next/server";
import OpenAI from "openai";
import sendScoreResultEmail from "./submitEmailHandler";

const promptHeader =
"Compare code B against code A. Using as few tokens as possible, output a percentage of similaritiy in semantics and intent.";
Expand Down Expand Up @@ -30,33 +31,49 @@ function extractPercentageScore(inputString: string): number {
return 0;
}

export async function POST(req: Request) {
const body = await req.json();
console.log("ENTERED SCORE: ", body);
const {
code,
dateTime,
email,
challenge,
}: { code: string; dateTime: string; email: string; challenge: string } =
body;
const recommendedSolution = LandingPageChallengeCode(challenge);
let score = "0";
if (!recommendedSolution) {
return NextResponse.json(
{ message: `The challenge ${challenge} wasn't recognized.` },
{ status: 400 }
);
}
if (process.env.IS_PRODUCTION === "true") {
const prompt =
promptHeader +
"\nCode A:\n" +
recommendedSolution +
"\nCode B:\n" +
code;
const result = await openAiCall(prompt);
console.log(`
function deleteSubmissionFromDB(id: string) {
return xata.db.SubmissionsMVP.delete(id);
}

/*
Cron job endpoint that runs every minute.
*/
export async function POST() {
const record = await xata.db.SubmissionsMVP.getFirst();
if (record) {
if (
!record.challengeName ||
!record.code ||
!record.dateTime ||
!record.email
) {
deleteSubmissionFromDB(record.id);
return NextResponse.json(
{ message: "Record is incomplete and has been deleted." },
{ status: 400 }
);
}
const challenge = record.challengeName;
const code = record.code;
const dateTime = record.dateTime;
const email = record.email;
const recommendedSolution = LandingPageChallengeCode(challenge);
let score = "0";
if (!recommendedSolution) {
return NextResponse.json(
{ message: `The challenge ${challenge} wasn't recognized.` },
{ status: 400 }
);
}
if (process.env.IS_PRODUCTION === "true") {
const prompt =
promptHeader +
"\nCode A:\n" +
recommendedSolution +
"\nCode B:\n" +
code;
const result = await openAiCall(prompt);
console.log(`
===Debugging OpenAI===
- Prompt generated:
Expand All @@ -65,39 +82,25 @@ export async function POST(req: Request) {
- Result:
${result}
`);
if (!result) {
//add this to the db for retrying later
return NextResponse.json(
{
message: `Error occured. Adding this to the DB for processing later.`,
},
{ status: 500 }
);
}
const rawPercentageScore = extractPercentageScore(result);
console.log(`
if (!result) {
return NextResponse.json(
{
message: `Result was null. Needs to be retried.`,
},
{ status: 500 }
);
}
const rawPercentageScore = extractPercentageScore(result);
console.log(`
- Percentage score extracted
${rawPercentageScore}
`);
score = String(rawPercentageScore);
} else {
score = "58";
score = String(rawPercentageScore);
} else {
score = "58";
}
await sendScoreResultEmail(score, challenge, code, dateTime, email);
await deleteSubmissionFromDB(record.id);
}
// Extract the host and protocol from the incoming request
const url = new URL(req.url);
const baseUrl = `${url.protocol}//${url.host}`;
console.log(
"ABOUT TO SEND RESULT ",
score,
"to",
`${baseUrl}/api/feedback/result`
);
axios.post(`${baseUrl}/api/feedback/result`, {
score,
challenge,
code,
dateTime,
email,
});
return NextResponse.json({ message: "Success" }, { status: 200 });
}
37 changes: 37 additions & 0 deletions app/api/code/score/submitEmailHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import nodemailer from "nodemailer";
import ScoreEmail from "../../code/score/result-email-template";
import { render } from "@react-email/render";

export default async function sendScoreResultEmail(
score: string,
challenge: string,
code: string,
dateTime: string,
email: string
) {
const transporter = nodemailer.createTransport({
port: 465,
host: "smtp.gmail.com",
auth: {
user: process.env.EMAIL,
pass: process.env.PASSWORD,
},
secure: true,
});
const emailHtml = render(ScoreEmail({ score, challenge, code, dateTime }));
const options = {
from: `"Tailspin Team" <tailspin.official@gmail.com>"`,
to: email,
subject: `The results are in!`,
html: emailHtml,
};
const sendMessage = async (options: {
from: string | undefined;
to: string;
subject: string;
html: string;
}) => {
await transporter.sendMail(options);
};
await sendMessage(options);
}
46 changes: 22 additions & 24 deletions app/api/code/submit/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NextResponse } from "next/server";
import { xata } from "@/lib/xata_client";
import axios from "axios";
import { validateHTML } from "./submit-helpers";

export async function GET() {
Expand All @@ -19,25 +18,25 @@ export async function GET() {
/*
This is the entry point after code gets submitted. At the moment, this endpoint and its children endpoints are not protected. In the future, these routes need to be protected from DOS or spam attacks.
Warning:
This endpoint can get confusing because we've decoupled the functionalities we require into their own endpoints. The benefits of this approach is a decoupled service and easier debugging efforts. The downside is that it might be harder to reason with its correctness because of added complexity. Moreover, another downside is that every endpoint will need to be protected against DOS and spam attacks as mentioned above, which is a small overhead to the entire process.
Flow:
User makes request
↓ User's wait ends in this step.
Increment the submit counter (api)
Make the call to the OpenAI wrapper (api)
On a successful OpenAI call, call the email generator (api)
This endpoint creates a record of the submission. Every minute, a cron job kicks off and processes a single submission at a time.
*/

export async function POST(req: Request) {
try {
const body = await req.json();
console.log("ENTERED SUBMIT: ", body);
const { code } = body;
if (!validateHTML(code)) {
const {
challenge,
code,
dateTime,
email,
}: {
challenge: string;
code: string;
dateTime: string;
email: string;
} = body;
const cleanedCode = validateHTML(code);
if (cleanedCode.length === 0) {
return NextResponse.json(
{
message:
Expand All @@ -47,7 +46,7 @@ export async function POST(req: Request) {
);
}

const { email } = body;
//Update the last submission time for this email. For rate limiting purposes.
const record = await xata.db.EmailSubmitRateLimiting.filter({
email: email,
}).getFirst();
Expand Down Expand Up @@ -76,16 +75,15 @@ export async function POST(req: Request) {
lastSubmission: new Date().toISOString(),
});
}
// Extract the host and protocol from the incoming request
const url = new URL(req.url);
const baseUrl = `${url.protocol}//${url.host}`;
console.log("BASE URL: ", baseUrl);
// Use the base URL for Axios requests
axios.put(`${baseUrl}/api/increment/submit`, {});

axios.post(`${baseUrl}/api/code/score`, body);
await xata.db.SubmissionsMVP.create({
email: email,
code: cleanedCode,
challengeName: challenge,
dateTime: dateTime,
});

return NextResponse.json({ message: "Accepted" }, { status: 202 });
return NextResponse.json({ message: "Created" }, { status: 201 });
} catch (error) {
console.error(error);
return NextResponse.json(
Expand Down
12 changes: 8 additions & 4 deletions app/api/code/submit/submit-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ const window = new JSDOM("").window;
const DOMPurify = createDOMPurify(window);

// Sanitizer function
export const validateHTML = (html: string): boolean => {
export const validateHTML = (html: string): string => {
html = html.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
html = html.replace(/<!--.*?-->/g, "");
if (!isHtml(html)) {
return false;
return "";
}

const cleanHTML = DOMPurify.sanitize(html, {
Expand Down Expand Up @@ -39,8 +39,12 @@ export const validateHTML = (html: string): boolean => {
});

if (cleanHTML !== html) {
return false;
return "";
}

return isHtml(cleanHTML);
if (isHtml(cleanHTML)) {
return cleanHTML;
} else {
return "";
}
};
Loading

0 comments on commit a3aad83

Please sign in to comment.