diff --git a/.xata/migrations/.ledger b/.xata/migrations/.ledger
index cbb3b54..d4aaeb1 100644
--- a/.xata/migrations/.ledger
+++ b/.xata/migrations/.ledger
@@ -4,3 +4,14 @@ mig_ckeabvjgnj3bm0ispvgg_1ce47743
mig_ckevkeengvghfvu4alkg_ddcffced
mig_ckevkhungvghfvu4alsg_742fbcbb
mig_ckevkpungvghfvu4am50_1511cbec
+mig_clhe670vdfloffdfg9s0_4744dcb2
+mig_clhe6b6r7je6m59gktsg_fd744927
+mig_clhe6cur7je6m59gkttg_107f96be
+mig_clhe6g8vdfloffdfg9t0_8f3eb990
+mig_clhe6l6r7je6m59gktug_5ae5be9b
+mig_clhe6s6r7je6m59gktvg_1c6c9cf0
+mig_cll7guf3d17cd4sn3q50_f99b28e0
+mig_cll7h3v3d17cd4sn3q60_deca122c
+mig_cll7kiv3d17cd4sn3qdg_1d8d01c9
+mig_cll7kp2ifsth3gbude0g_4d9ec4f1
+mig_cll7ksaifsth3gbude2g_4f044676
diff --git a/.xata/migrations/mig_clhe670vdfloffdfg9s0_4744dcb2.json b/.xata/migrations/mig_clhe670vdfloffdfg9s0_4744dcb2.json
new file mode 100644
index 0000000..95efa3e
--- /dev/null
+++ b/.xata/migrations/mig_clhe670vdfloffdfg9s0_4744dcb2.json
@@ -0,0 +1,12 @@
+{
+ "id": "mig_clhe670vdfloffdfg9s0",
+ "parentID": "mig_ckevkpungvghfvu4am50",
+ "checksum": "1:4744dcb2d75e9cba5183e24a6a49447ea0282ba342d7a4fe86afb0dafefc0f79",
+ "operations": [
+ {
+ "addTable": {
+ "table": "Backlog-Score"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_clhe6b6r7je6m59gktsg_fd744927.json b/.xata/migrations/mig_clhe6b6r7je6m59gktsg_fd744927.json
new file mode 100644
index 0000000..0be8141
--- /dev/null
+++ b/.xata/migrations/mig_clhe6b6r7je6m59gktsg_fd744927.json
@@ -0,0 +1,16 @@
+{
+ "id": "mig_clhe6b6r7je6m59gktsg",
+ "parentID": "mig_clhe670vdfloffdfg9s0",
+ "checksum": "1:fd7449272458d918d7cf0f07f08e98650176ae39f2847539e63cfb43b996ba24",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "code",
+ "type": "string"
+ },
+ "table": "Backlog-Score"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_clhe6cur7je6m59gkttg_107f96be.json b/.xata/migrations/mig_clhe6cur7je6m59gkttg_107f96be.json
new file mode 100644
index 0000000..a04eb05
--- /dev/null
+++ b/.xata/migrations/mig_clhe6cur7je6m59gkttg_107f96be.json
@@ -0,0 +1,16 @@
+{
+ "id": "mig_clhe6cur7je6m59gkttg",
+ "parentID": "mig_clhe6b6r7je6m59gktsg",
+ "checksum": "1:107f96be15c59f116769877a3ede887da639354f69e3dfa5e3080aa4f35f1908",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "email",
+ "type": "string"
+ },
+ "table": "Backlog-Score"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_clhe6g8vdfloffdfg9t0_8f3eb990.json b/.xata/migrations/mig_clhe6g8vdfloffdfg9t0_8f3eb990.json
new file mode 100644
index 0000000..64a730d
--- /dev/null
+++ b/.xata/migrations/mig_clhe6g8vdfloffdfg9t0_8f3eb990.json
@@ -0,0 +1,17 @@
+{
+ "id": "mig_clhe6g8vdfloffdfg9t0",
+ "parentID": "mig_clhe6cur7je6m59gkttg",
+ "checksum": "1:8f3eb990da0dfd0ca576d59a6aaeba6f55e0d00709bd250e7f208f44dfbdabab",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "retry",
+ "type": "int",
+ "defaultValue": "0"
+ },
+ "table": "Backlog-Score"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_clhe6l6r7je6m59gktug_5ae5be9b.json b/.xata/migrations/mig_clhe6l6r7je6m59gktug_5ae5be9b.json
new file mode 100644
index 0000000..88d52e8
--- /dev/null
+++ b/.xata/migrations/mig_clhe6l6r7je6m59gktug_5ae5be9b.json
@@ -0,0 +1,14 @@
+{
+ "id": "mig_clhe6l6r7je6m59gktug",
+ "parentID": "mig_clhe6g8vdfloffdfg9t0",
+ "checksum": "1:5ae5be9b8b8d79df03cd1c6d5b6f3dd47d5ae1926ed3f526dc10456a5f2c1cc9",
+ "operations": [
+ {
+ "renameColumn": {
+ "newName": "retry_count",
+ "oldName": "retry",
+ "table": "Backlog-Score"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_clhe6s6r7je6m59gktvg_1c6c9cf0.json b/.xata/migrations/mig_clhe6s6r7je6m59gktvg_1c6c9cf0.json
new file mode 100644
index 0000000..9005063
--- /dev/null
+++ b/.xata/migrations/mig_clhe6s6r7je6m59gktvg_1c6c9cf0.json
@@ -0,0 +1,16 @@
+{
+ "id": "mig_clhe6s6r7je6m59gktvg",
+ "parentID": "mig_clhe6l6r7je6m59gktug",
+ "checksum": "1:1c6c9cf0790fe6efe3454c3edc25cc228681f0520c0cc2a233ba7515adad5929",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "challenge",
+ "type": "string"
+ },
+ "table": "Backlog-Score"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_cll7guf3d17cd4sn3q50_f99b28e0.json b/.xata/migrations/mig_cll7guf3d17cd4sn3q50_f99b28e0.json
new file mode 100644
index 0000000..ea53d22
--- /dev/null
+++ b/.xata/migrations/mig_cll7guf3d17cd4sn3q50_f99b28e0.json
@@ -0,0 +1,12 @@
+{
+ "id": "mig_cll7guf3d17cd4sn3q50",
+ "parentID": "mig_clhe6s6r7je6m59gktvg",
+ "checksum": "1:f99b28e07a41df81e9f84ed761484daf31c3820f0fb251d12c037af42107346a",
+ "operations": [
+ {
+ "addTable": {
+ "table": "EmailSubmitRateLimiting"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_cll7h3v3d17cd4sn3q60_deca122c.json b/.xata/migrations/mig_cll7h3v3d17cd4sn3q60_deca122c.json
new file mode 100644
index 0000000..11f49e9
--- /dev/null
+++ b/.xata/migrations/mig_cll7h3v3d17cd4sn3q60_deca122c.json
@@ -0,0 +1,17 @@
+{
+ "id": "mig_cll7h3v3d17cd4sn3q60",
+ "parentID": "mig_cll7guf3d17cd4sn3q50",
+ "checksum": "1:deca122c44d63a03805dd49378cb9b9fc470d2826fab6f9f5c018191bd139023",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "email",
+ "type": "string",
+ "unique": true
+ },
+ "table": "EmailSubmitRateLimiting"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_cll7kiv3d17cd4sn3qdg_1d8d01c9.json b/.xata/migrations/mig_cll7kiv3d17cd4sn3qdg_1d8d01c9.json
new file mode 100644
index 0000000..01527a7
--- /dev/null
+++ b/.xata/migrations/mig_cll7kiv3d17cd4sn3qdg_1d8d01c9.json
@@ -0,0 +1,16 @@
+{
+ "id": "mig_cll7kiv3d17cd4sn3qdg",
+ "parentID": "mig_cll7h3v3d17cd4sn3q60",
+ "checksum": "1:1d8d01c975b06096e6ceba8841f904dbe4158868fdca69371272e619f5c6db09",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "lastSubmission",
+ "type": "datetime"
+ },
+ "table": "EmailSubmitRateLimiting"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_cll7kp2ifsth3gbude0g_4d9ec4f1.json b/.xata/migrations/mig_cll7kp2ifsth3gbude0g_4d9ec4f1.json
new file mode 100644
index 0000000..d1261b9
--- /dev/null
+++ b/.xata/migrations/mig_cll7kp2ifsth3gbude0g_4d9ec4f1.json
@@ -0,0 +1,13 @@
+{
+ "id": "mig_cll7kp2ifsth3gbude0g",
+ "parentID": "mig_cll7kiv3d17cd4sn3qdg",
+ "checksum": "1:4d9ec4f1bc9566499f066e2d50b4f4b2403d7fbd52b38c6a7cfbbdec56ca4e01",
+ "operations": [
+ {
+ "removeColumn": {
+ "column": "lastSubmission",
+ "table": "EmailSubmitRateLimiting"
+ }
+ }
+ ]
+}
diff --git a/.xata/migrations/mig_cll7ksaifsth3gbude2g_4f044676.json b/.xata/migrations/mig_cll7ksaifsth3gbude2g_4f044676.json
new file mode 100644
index 0000000..f1c8d4c
--- /dev/null
+++ b/.xata/migrations/mig_cll7ksaifsth3gbude2g_4f044676.json
@@ -0,0 +1,17 @@
+{
+ "id": "mig_cll7ksaifsth3gbude2g",
+ "parentID": "mig_cll7kp2ifsth3gbude0g",
+ "checksum": "1:4f0446762ff26866fb83e1415120923426212e10433b786ea582050c42f8811c",
+ "operations": [
+ {
+ "addColumn": {
+ "column": {
+ "name": "lastSubmission",
+ "type": "datetime",
+ "defaultValue": "now"
+ },
+ "table": "EmailSubmitRateLimiting"
+ }
+ }
+ ]
+}
diff --git a/app/api/code/submit/route.ts b/app/api/code/submit/route.ts
index 0e38706..3d30ed6 100644
--- a/app/api/code/submit/route.ts
+++ b/app/api/code/submit/route.ts
@@ -35,6 +35,7 @@ On a successful OpenAI call, call the email generator (api)
export async function POST(req: Request) {
try {
const body = await req.json();
+
const { code } = body;
if (!validateHTML(code)) {
return NextResponse.json(
@@ -44,6 +45,35 @@ export async function POST(req: Request) {
},
{ status: 400 }
);
+
+ const { email } = body;
+ const record = await xata.db.EmailSubmitRateLimiting.filter({
+ email: email,
+ }).getFirst();
+ if (record) {
+ if (record.lastSubmission && process.env.IS_PRODUCTION === "true") {
+ //Time between submissions has to be at least 1 minute
+ const lastSubmission = new Date(record.lastSubmission);
+ const currentTime = new Date();
+ if (
+ currentTime.getTime() - lastSubmission.getTime() <
+ 60 * 1000 * 1
+ ) {
+ return NextResponse.json(
+ { message: "Time between submissions too small!" },
+ { status: 400 }
+ );
+ } else {
+ await xata.db.EmailSubmitRateLimiting.update(record.id, {
+ lastSubmission: new Date().toISOString(),
+ });
+ }
+ }
+ } else {
+ await xata.db.EmailSubmitRateLimiting.create({
+ email: email,
+ lastSubmission: new Date().toISOString(),
+ });
}
// Extract the host and protocol from the incoming request
const url = new URL(req.url);
diff --git a/client-side-queries/rq-queries/code-submit.ts b/client-side-queries/rq-queries/code-submit.ts
index ae404ae..feb13cb 100644
--- a/client-side-queries/rq-queries/code-submit.ts
+++ b/client-side-queries/rq-queries/code-submit.ts
@@ -13,19 +13,22 @@ export function getCodeSubmitCount() {
return resp;
}
-interface UsePostSubmitCountProps {
+interface UseCodeSubmissionProps {
code: string;
dateTime: string;
email: string;
challenge: string;
}
-export async function postSubmitCount({
+export async function codeSubmission({
code,
dateTime,
email,
challenge,
-}: UsePostSubmitCountProps) {
+}: UseCodeSubmissionProps) {
+ if (!code || !dateTime || !email || !challenge) {
+ throw new Error("Something wasn't passed into codeSubmission.");
+ }
const resp = await axios.post("/api/code/submit", {
code: code,
dateTime: dateTime,
diff --git a/components/core/code-area-actions/submit-button.tsx b/components/core/code-area-actions/submit-button.tsx
index ff943c8..ed0354c 100644
--- a/components/core/code-area-actions/submit-button.tsx
+++ b/components/core/code-area-actions/submit-button.tsx
@@ -14,9 +14,10 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { useResetFeState } from "@/lib/reset-fe-state";
-import { postSubmitCount } from "@/client-side-queries/rq-queries/code-submit";
+import { codeSubmission } from "@/client-side-queries/rq-queries/code-submit";
import useSessionStore from "@/data-store/session-store";
import { useStepperStore } from "@/data-store/stepper-store";
+import { loadFromLocalStorage } from "@/lib/localStorage";
const smallProps: ConfettiProps = {
force: 0.6,
@@ -63,10 +64,20 @@ const SubmitButton = () => {
const handleSubmitButtonClick = async () => {
try {
const dateTime = getCurrentDateTime();
- await postSubmitCount({ code, dateTime, email, challenge });
+
+ const submitEmail = email ? email : loadFromLocalStorage("email");
+ const submitChallenge = challenge
+ ? challenge
+ : loadFromLocalStorage("challenge");
+ await codeSubmission({
+ code,
+ dateTime,
+ email: submitEmail,
+ challenge: submitChallenge,
+ });
setSubmitClicked(true);
- } catch (error) {
- alert("Something went wrong. Try submitting again!");
+ } catch (error: any) {
+ alert(error.response.data.message);
}
};
diff --git a/components/core/editor.tsx b/components/core/editor.tsx
index 9a87a28..79c3aa0 100644
--- a/components/core/editor.tsx
+++ b/components/core/editor.tsx
@@ -10,6 +10,7 @@ import { Button } from "../ui/button";
import StaticPrompt from "./target-image";
import LandingPageChallengeCode from "../landing/test-challenges/challenge-code";
import { useStepperStore } from "@/data-store/stepper-store";
+import { loadFromLocalStorage, saveToLocalStorage } from "@/lib/localStorage";
const Editor = () => {
const { code, setCode } = useSessionStore();
@@ -39,10 +40,19 @@ const Editor = () => {
};
loadAce();
+ const localStorageCode = loadFromLocalStorage("code");
+ if (code.length === 0 && localStorageCode.length > 0) {
+ setCode(localStorageCode);
+ }
}, []);
if (!AceEditor) return