From cf516fcacb222c76bbf44ea2bddf306730ec7529 Mon Sep 17 00:00:00 2001 From: AmoabaKelvin Date: Thu, 6 Jun 2024 17:37:14 +0000 Subject: [PATCH] feat: tag spam submissions --- apps/web/src/app/(main)/form/[id]/page.tsx | 7 +- .../(main)/form/[id]/submissions-table.tsx | 5 + apps/web/src/app/api/s/[id]/route.ts | 12 +- packages/api/routers/formData.ts | 1 + packages/db/drizzle/0001_blushing_dust.sql | 1 + packages/db/drizzle/meta/0001_snapshot.json | 472 ++++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + packages/db/schema/form-data.ts | 11 +- 8 files changed, 512 insertions(+), 4 deletions(-) create mode 100644 packages/db/drizzle/0001_blushing_dust.sql create mode 100644 packages/db/drizzle/meta/0001_snapshot.json diff --git a/apps/web/src/app/(main)/form/[id]/page.tsx b/apps/web/src/app/(main)/form/[id]/page.tsx index 9f15195..c7f584d 100644 --- a/apps/web/src/app/(main)/form/[id]/page.tsx +++ b/apps/web/src/app/(main)/form/[id]/page.tsx @@ -48,7 +48,12 @@ export default async function FormPage({ params }: { params: { id: string } }) { <>
- Total Submissions: {formSubmissions.length} + {formSubmissions.length} Submissions with{' '} + { + formSubmissions.filter((submission) => submission.isSpam) + .length + }{' '} + Spam {row.getVisibleCells().map((cell) => ( diff --git a/apps/web/src/app/api/s/[id]/route.ts b/apps/web/src/app/api/s/[id]/route.ts index 15363e6..57d8b76 100644 --- a/apps/web/src/app/api/s/[id]/route.ts +++ b/apps/web/src/app/api/s/[id]/route.ts @@ -10,7 +10,7 @@ import { assignFileOrImage, uploadFileFromBlob } from '~/lib/upload-file'; type Json = string | number | boolean | null | { [key: string]: Json } | Json[]; async function getFormData(request: Request): Promise<{ - data: Record; + data: Record & { _gotcha?: string }; source: 'formData' | 'json'; }> { try { @@ -88,14 +88,22 @@ export async function POST( if (source === 'formData') await processFileUploads(formData, formData as unknown as FormData); - const formDataKeys = Object.keys(formData); + const formDataKeys = Object.keys(formData).filter( + (key) => key !== '_gotcha', + ); const formKeys = form.keys; const updatedKeys = [...new Set([...formKeys, ...formDataKeys])]; + const submissionIsSpam = !!formData._gotcha; + if (submissionIsSpam) delete formData._gotcha; + + console.log('Submission is spam: ', submissionIsSpam); + await api.formData.setFormData({ data: formData as Json, formId, keys: updatedKeys, + isSpam: submissionIsSpam, }); void handleEmailNotifications(form, formId); diff --git a/packages/api/routers/formData.ts b/packages/api/routers/formData.ts index 8712e6a..c3deb9c 100644 --- a/packages/api/routers/formData.ts +++ b/packages/api/routers/formData.ts @@ -104,6 +104,7 @@ export const formDataRouter = createTRPCRouter({ data: input.data, formId: input.formId, id: generateId(15), + isSpam: input.isSpam ?? false, createdAt: new Date(), }); diff --git a/packages/db/drizzle/0001_blushing_dust.sql b/packages/db/drizzle/0001_blushing_dust.sql new file mode 100644 index 0000000..715d0d7 --- /dev/null +++ b/packages/db/drizzle/0001_blushing_dust.sql @@ -0,0 +1 @@ +ALTER TABLE "form_datas" ADD COLUMN "is_spam" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0001_snapshot.json b/packages/db/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..353f16a --- /dev/null +++ b/packages/db/drizzle/meta/0001_snapshot.json @@ -0,0 +1,472 @@ +{ + "id": "d852b0af-de24-4468-b0d7-37f230ca5837", + "prevId": "3b08f407-2220-4159-95e7-68a7faa162ea", + "version": "6", + "dialect": "postgresql", + "tables": { + "public.email_verification_codes": { + "name": "email_verification_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_verif_user_idx": { + "name": "email_verif_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "email_verif_idx": { + "name": "email_verif_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_verification_codes_user_id_unique": { + "name": "email_verification_codes_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.form_datas": { + "name": "form_datas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "form_id": { + "name": "form_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_spam": { + "name": "is_spam", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "form_idx": { + "name": "form_idx", + "columns": [ + "form_id" + ], + "isUnique": false + }, + "form_data_created_at_idx": { + "name": "form_data_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.forms": { + "name": "forms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "return_url": { + "name": "return_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "send_email_for_new_submissions": { + "name": "send_email_for_new_submissions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "keys": { + "name": "keys", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "enable_submissions": { + "name": "enable_submissions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_retention": { + "name": "enable_retention", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "form_user_idx": { + "name": "form_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "form_created_at_idx": { + "name": "form_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.oauth_account": { + "name": "oauth_account", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_account_user_id_users_id_fk": { + "name": "oauth_account_user_id_users_id_fk", + "tableFrom": "oauth_account", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oauth_account_provider_id_provider_user_id_pk": { + "name": "oauth_account_provider_id_provider_user_id_pk", + "columns": [ + "provider_id", + "provider_user_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "password_reset_user_idx": { + "name": "password_reset_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "sessions_user_idx": { + "name": "sessions_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "github_id": { + "name": "github_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hashed_password": { + "name": "hashed_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_current_period_end": { + "name": "stripe_current_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "github_idx": { + "name": "github_idx", + "columns": [ + "github_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_github_id_unique": { + "name": "users_github_id_unique", + "nullsNotDistinct": false, + "columns": [ + "github_id" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index c682e71..7717957 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1717559991446, "tag": "0000_left_mimic", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1717691863594, + "tag": "0001_blushing_dust", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/schema/form-data.ts b/packages/db/schema/form-data.ts index 265ea37..cb92fb7 100644 --- a/packages/db/schema/form-data.ts +++ b/packages/db/schema/form-data.ts @@ -1,4 +1,11 @@ -import { index, json, pgTable, text, timestamp } from 'drizzle-orm/pg-core'; +import { + boolean, + index, + json, + pgTable, + text, + timestamp, +} from 'drizzle-orm/pg-core'; import { createInsertSchema, createSelectSchema } from 'drizzle-zod'; import { type z } from 'zod'; @@ -9,6 +16,7 @@ export const formDatas = pgTable( formId: text('form_id').notNull(), data: json('data').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), + isSpam: boolean('is_spam').default(false).notNull(), }, (t) => ({ formIdx: index('form_idx').on(t.formId), @@ -21,6 +29,7 @@ export const ZInsertFormDataSchema = createInsertSchema(formDatas); export const ZUpdateFormDataSchema = createInsertSchema(formDatas).pick({ formId: true, data: true, + isSpam: true, }); export type FormData = z.infer;