diff --git a/.gitignore b/.gitignore index a3f77df..9208753 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,10 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# IDE and tools specific +.history +.vscode + /service/service.json /service/user_account.json /service/models diff --git a/app/migrations/0000_rainy_brother_voodoo.sql b/app/migrations/0000_rainy_brother_voodoo.sql deleted file mode 100644 index 2b6c5f2..0000000 --- a/app/migrations/0000_rainy_brother_voodoo.sql +++ /dev/null @@ -1,20 +0,0 @@ -CREATE TABLE IF NOT EXISTS "registrations" ( - "id" text PRIMARY KEY NOT NULL, - "userName" text NOT NULL, - "userEmail" text NOT NULL, - "createdAt" timestamp DEFAULT now() -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "transcriptions" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "registrationId" text NOT NULL, - "transcription" text NOT NULL, - "createdAt" timestamp DEFAULT now(), - "documentUrl" text NOT NULL -); ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "transcriptions" ADD CONSTRAINT "transcriptions_registrationId_registrations_id_fk" FOREIGN KEY ("registrationId") REFERENCES "public"."registrations"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/app/migrations/0000_yielding_silverclaw.sql b/app/migrations/0000_yielding_silverclaw.sql new file mode 100644 index 0000000..b7b1a52 --- /dev/null +++ b/app/migrations/0000_yielding_silverclaw.sql @@ -0,0 +1,85 @@ +CREATE TABLE IF NOT EXISTS "analysis_feedback" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "analysis_id" uuid NOT NULL, + "is_found_useful" boolean NOT NULL, + "impact" text, + "createdAt" timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "interview_analysis" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" text NOT NULL, + "candidate_name" text NOT NULL, + "interviewer_name" text NOT NULL, + "interview_recording_link" text NOT NULL, + "job_description_document_link" text NOT NULL, + "transcript" text, + "questions_answers" text, + "parsed_job_description" text, + "analysis_result" text, + "conversation" text, + "status" text, + "created_at" timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "registrations" ( + "id" text PRIMARY KEY NOT NULL, + "userName" text NOT NULL, + "userEmail" text NOT NULL, + "createdAt" timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "session" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "createdAt" timestamp DEFAULT now() +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "transcriptions" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" text NOT NULL, + "translation" text NOT NULL, + "summary" text NOT NULL, + "createdAt" timestamp DEFAULT now(), + "documentUrl" text NOT NULL, + "documentName" text NOT NULL, + "isDefault" boolean DEFAULT false NOT NULL, + "audioDuration" integer +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user" ( + "id" text PRIMARY KEY NOT NULL, + "username" text NOT NULL, + "password_hash" text NOT NULL, + "name" text, + "contactNumber" text, + "role" text, + "createdAt" timestamp DEFAULT now(), + CONSTRAINT "user_username_unique" UNIQUE("username") +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "analysis_feedback" ADD CONSTRAINT "analysis_feedback_analysis_id_interview_analysis_id_fk" FOREIGN KEY ("analysis_id") REFERENCES "public"."interview_analysis"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "interview_analysis" ADD CONSTRAINT "interview_analysis_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "transcriptions" ADD CONSTRAINT "transcriptions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/app/migrations/0001_vengeful_black_tarantula.sql b/app/migrations/0001_vengeful_black_tarantula.sql deleted file mode 100644 index 3423325..0000000 --- a/app/migrations/0001_vengeful_black_tarantula.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "transcriptions" RENAME COLUMN "transcription" TO "translation";--> statement-breakpoint -ALTER TABLE "transcriptions" ADD COLUMN "summary" text NOT NULL; \ No newline at end of file diff --git a/app/migrations/0002_youthful_justice.sql b/app/migrations/0002_youthful_justice.sql deleted file mode 100644 index 2bd730c..0000000 --- a/app/migrations/0002_youthful_justice.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "transcriptions" ADD COLUMN "documentName" text NOT NULL; \ No newline at end of file diff --git a/app/migrations/0003_round_wasp.sql b/app/migrations/0003_round_wasp.sql deleted file mode 100644 index 9cc5703..0000000 --- a/app/migrations/0003_round_wasp.sql +++ /dev/null @@ -1,27 +0,0 @@ -CREATE TABLE IF NOT EXISTS "session" ( - "id" text PRIMARY KEY NOT NULL, - "user_id" text NOT NULL, - "expires_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "user" ( - "id" text PRIMARY KEY NOT NULL, - "username" text NOT NULL, - "password_hash" text NOT NULL, - CONSTRAINT "user_username_unique" UNIQUE("username") -); ---> statement-breakpoint -ALTER TABLE "transcriptions" RENAME COLUMN "registrationId" TO "user_id";--> statement-breakpoint -ALTER TABLE "transcriptions" DROP CONSTRAINT "transcriptions_registrationId_registrations_id_fk"; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "transcriptions" ADD CONSTRAINT "transcriptions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/app/migrations/0004_chunky_amazoness.sql b/app/migrations/0004_chunky_amazoness.sql deleted file mode 100644 index 7b83ec7..0000000 --- a/app/migrations/0004_chunky_amazoness.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "transcriptions" ADD COLUMN "isDefault" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/app/migrations/0005_blue_prowler.sql b/app/migrations/0005_blue_prowler.sql deleted file mode 100644 index c6f484e..0000000 --- a/app/migrations/0005_blue_prowler.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE "session" ADD COLUMN "createdAt" timestamp DEFAULT now();--> statement-breakpoint -ALTER TABLE "transcriptions" ADD COLUMN "audioDuration" integer;--> statement-breakpoint -ALTER TABLE "user" ADD COLUMN "role" text;--> statement-breakpoint -ALTER TABLE "user" ADD COLUMN "createdAt" timestamp DEFAULT now(); \ No newline at end of file diff --git a/app/migrations/0006_stormy_rocket_racer.sql b/app/migrations/0006_stormy_rocket_racer.sql deleted file mode 100644 index a911c94..0000000 --- a/app/migrations/0006_stormy_rocket_racer.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "user" ADD COLUMN "name" text;--> statement-breakpoint -ALTER TABLE "user" ADD COLUMN "contactNumber" text; \ No newline at end of file diff --git a/app/migrations/meta/0000_snapshot.json b/app/migrations/meta/0000_snapshot.json index c74a672..0989459 100644 --- a/app/migrations/meta/0000_snapshot.json +++ b/app/migrations/meta/0000_snapshot.json @@ -1,9 +1,174 @@ { - "id": "e8d68466-6cea-4d52-8e1b-593c60c231ae", + "id": "ca72394b-c21e-4116-8fba-58e1fca5e708", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", "tables": { + "public.analysis_feedback": { + "name": "analysis_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "analysis_id": { + "name": "analysis_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_found_useful": { + "name": "is_found_useful", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "impact": { + "name": "impact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "analysis_feedback_analysis_id_interview_analysis_id_fk": { + "name": "analysis_feedback_analysis_id_interview_analysis_id_fk", + "tableFrom": "analysis_feedback", + "tableTo": "interview_analysis", + "columnsFrom": [ + "analysis_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.interview_analysis": { + "name": "interview_analysis", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "candidate_name": { + "name": "candidate_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interviewer_name": { + "name": "interviewer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interview_recording_link": { + "name": "interview_recording_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "job_description_document_link": { + "name": "job_description_document_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transcript": { + "name": "transcript", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "questions_answers": { + "name": "questions_answers", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parsed_job_description": { + "name": "parsed_job_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_result": { + "name": "analysis_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation": { + "name": "conversation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "interview_analysis_user_id_user_id_fk": { + "name": "interview_analysis_user_id_user_id_fk", + "tableFrom": "interview_analysis", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "public.registrations": { "name": "registrations", "schema": "", @@ -39,6 +204,55 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, + "public.session": { + "name": "session", + "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 with time zone", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, "public.transcriptions": { "name": "transcriptions", "schema": "", @@ -50,14 +264,20 @@ "notNull": true, "default": "gen_random_uuid()" }, - "registrationId": { - "name": "registrationId", + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "translation": { + "name": "translation", "type": "text", "primaryKey": false, "notNull": true }, - "transcription": { - "name": "transcription", + "summary": { + "name": "summary", "type": "text", "primaryKey": false, "notNull": true @@ -74,26 +294,106 @@ "type": "text", "primaryKey": false, "notNull": true + }, + "documentName": { + "name": "documentName", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "isDefault": { + "name": "isDefault", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "audioDuration": { + "name": "audioDuration", + "type": "integer", + "primaryKey": false, + "notNull": false } }, "indexes": {}, "foreignKeys": { - "transcriptions_registrationId_registrations_id_fk": { - "name": "transcriptions_registrationId_registrations_id_fk", + "transcriptions_user_id_user_id_fk": { + "name": "transcriptions_user_id_user_id_fk", "tableFrom": "transcriptions", - "tableTo": "registrations", + "tableTo": "user", "columnsFrom": [ - "registrationId" + "user_id" ], "columnsTo": [ "id" ], - "onDelete": "cascade", + "onDelete": "no action", "onUpdate": "no action" } }, "compositePrimaryKeys": {}, "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contactNumber": { + "name": "contactNumber", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } } }, "enums": {}, diff --git a/app/migrations/meta/0001_snapshot.json b/app/migrations/meta/0001_snapshot.json deleted file mode 100644 index f37a9e6..0000000 --- a/app/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "id": "0ca8a22b-ed90-440b-8ac0-1fd7f9fa40b7", - "prevId": "e8d68466-6cea-4d52-8e1b-593c60c231ae", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.registrations": { - "name": "registrations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userName": { - "name": "userName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.transcriptions": { - "name": "transcriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "registrationId": { - "name": "registrationId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "translation": { - "name": "translation", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "summary": { - "name": "summary", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "documentUrl": { - "name": "documentUrl", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "transcriptions_registrationId_registrations_id_fk": { - "name": "transcriptions_registrationId_registrations_id_fk", - "tableFrom": "transcriptions", - "tableTo": "registrations", - "columnsFrom": [ - "registrationId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/app/migrations/meta/0002_snapshot.json b/app/migrations/meta/0002_snapshot.json deleted file mode 100644 index 7be118e..0000000 --- a/app/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "id": "21fe80ad-e01f-4679-8f45-d9b61904fb42", - "prevId": "0ca8a22b-ed90-440b-8ac0-1fd7f9fa40b7", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.registrations": { - "name": "registrations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userName": { - "name": "userName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.transcriptions": { - "name": "transcriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "registrationId": { - "name": "registrationId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "translation": { - "name": "translation", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "summary": { - "name": "summary", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "documentUrl": { - "name": "documentUrl", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "documentName": { - "name": "documentName", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "transcriptions_registrationId_registrations_id_fk": { - "name": "transcriptions_registrationId_registrations_id_fk", - "tableFrom": "transcriptions", - "tableTo": "registrations", - "columnsFrom": [ - "registrationId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/app/migrations/meta/0003_snapshot.json b/app/migrations/meta/0003_snapshot.json deleted file mode 100644 index 62f73d8..0000000 --- a/app/migrations/meta/0003_snapshot.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "id": "2ad32fb7-c2f6-4e9d-a858-c262aea7edc8", - "prevId": "21fe80ad-e01f-4679-8f45-d9b61904fb42", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.registrations": { - "name": "registrations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userName": { - "name": "userName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.session": { - "name": "session", - "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 with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.transcriptions": { - "name": "transcriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "translation": { - "name": "translation", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "summary": { - "name": "summary", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "documentUrl": { - "name": "documentUrl", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "documentName": { - "name": "documentName", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "transcriptions_user_id_user_id_fk": { - "name": "transcriptions_user_id_user_id_fk", - "tableFrom": "transcriptions", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "password_hash": { - "name": "password_hash", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_username_unique": { - "name": "user_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - } - } - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/app/migrations/meta/0004_snapshot.json b/app/migrations/meta/0004_snapshot.json deleted file mode 100644 index 7a6e22f..0000000 --- a/app/migrations/meta/0004_snapshot.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "id": "fe7a5b17-1b66-42ba-83f0-3e7da16e8a1e", - "prevId": "2ad32fb7-c2f6-4e9d-a858-c262aea7edc8", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.registrations": { - "name": "registrations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userName": { - "name": "userName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.session": { - "name": "session", - "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 with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.transcriptions": { - "name": "transcriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "translation": { - "name": "translation", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "summary": { - "name": "summary", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "documentUrl": { - "name": "documentUrl", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "documentName": { - "name": "documentName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "isDefault": { - "name": "isDefault", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": { - "transcriptions_user_id_user_id_fk": { - "name": "transcriptions_user_id_user_id_fk", - "tableFrom": "transcriptions", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "password_hash": { - "name": "password_hash", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_username_unique": { - "name": "user_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - } - } - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } - } \ No newline at end of file diff --git a/app/migrations/meta/0005_snapshot.json b/app/migrations/meta/0005_snapshot.json deleted file mode 100644 index 70bf3e1..0000000 --- a/app/migrations/meta/0005_snapshot.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "id": "45a3db15-c8bf-4c72-96e1-ee4ecc6115a7", - "prevId": "fe7a5b17-1b66-42ba-83f0-3e7da16e8a1e", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.registrations": { - "name": "registrations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userName": { - "name": "userName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.session": { - "name": "session", - "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 with time zone", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.transcriptions": { - "name": "transcriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "translation": { - "name": "translation", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "summary": { - "name": "summary", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "documentUrl": { - "name": "documentUrl", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "documentName": { - "name": "documentName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "isDefault": { - "name": "isDefault", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "audioDuration": { - "name": "audioDuration", - "type": "integer", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "transcriptions_user_id_user_id_fk": { - "name": "transcriptions_user_id_user_id_fk", - "tableFrom": "transcriptions", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "password_hash": { - "name": "password_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_username_unique": { - "name": "user_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - } - } - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/app/migrations/meta/0006_snapshot.json b/app/migrations/meta/0006_snapshot.json deleted file mode 100644 index fcdb6c6..0000000 --- a/app/migrations/meta/0006_snapshot.json +++ /dev/null @@ -1,242 +0,0 @@ -{ - "id": "da5e1ba2-42b5-4c56-9a01-83ef8cd77a95", - "prevId": "45a3db15-c8bf-4c72-96e1-ee4ecc6115a7", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.registrations": { - "name": "registrations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userName": { - "name": "userName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userEmail": { - "name": "userEmail", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.session": { - "name": "session", - "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 with time zone", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.transcriptions": { - "name": "transcriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "translation": { - "name": "translation", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "summary": { - "name": "summary", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "documentUrl": { - "name": "documentUrl", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "documentName": { - "name": "documentName", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "isDefault": { - "name": "isDefault", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "audioDuration": { - "name": "audioDuration", - "type": "integer", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "transcriptions_user_id_user_id_fk": { - "name": "transcriptions_user_id_user_id_fk", - "tableFrom": "transcriptions", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "username": { - "name": "username", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "password_hash": { - "name": "password_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "contactNumber": { - "name": "contactNumber", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_username_unique": { - "name": "user_username_unique", - "nullsNotDistinct": false, - "columns": [ - "username" - ] - } - } - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/app/migrations/meta/_journal.json b/app/migrations/meta/_journal.json index c202f1f..18cf246 100644 --- a/app/migrations/meta/_journal.json +++ b/app/migrations/meta/_journal.json @@ -5,50 +5,8 @@ { "idx": 0, "version": "7", - "when": 1726070335198, - "tag": "0000_rainy_brother_voodoo", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1726155692962, - "tag": "0001_vengeful_black_tarantula", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1726167459613, - "tag": "0002_youthful_justice", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1727517788789, - "tag": "0003_round_wasp", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1729509556889, - "tag": "0004_chunky_amazoness", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1732692329007, - "tag": "0005_blue_prowler", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1732709159106, - "tag": "0006_stormy_rocket_racer", + "when": 1733478178193, + "tag": "0000_yielding_silverclaw", "breakpoints": true }, { diff --git a/app/package-lock.json b/app/package-lock.json index 8efcdb6..179d0bf 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8747,9 +8747,9 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/type-check": { "version": "0.4.0", diff --git a/app/package.json b/app/package.json index 8e28aa7..3a9f715 100644 --- a/app/package.json +++ b/app/package.json @@ -31,6 +31,7 @@ "dotenv": "^16.4.5", "drizzle-orm": "^0.33.0", "framer-motion": "^11.3.31", + "jspdf": "^2.5.2", "lucia": "^3.2.0", "lucide-react": "^0.437.0", "next": "14.2.7", diff --git a/app/src/Validators/register.ts b/app/src/Validators/register.ts index d2ca0f5..e3e5098 100644 --- a/app/src/Validators/register.ts +++ b/app/src/Validators/register.ts @@ -18,6 +18,21 @@ export const signupUserSchema = z.object({ }); +export const aiCruitRequestSchema = z.object({ + interview_link: z.string().url({ + message: "Invalid URL format for the interview link", + }), + job_description_link: z.string().url({ + message: "Invalid URL format for the job description link", + }), + candidate_name: z.string().min(2, { + message: "Candidate Name should be at least 2 characters long", + }), + interviewer_name: z.string().min(2, { + message: "Interviewer Name should be at least 2 characters long", + }), +}); + export const signinUserSchema = z.object({ userEmail: z.string().email({ message: "Invalid email", @@ -29,5 +44,8 @@ export const signinUserSchema = z.object({ }), }); + +export type AiCruitRequest = z.infer; export type SignupUserRequest = z.infer; -export type SigninUserRequest = z.infer; \ No newline at end of file +export type SigninUserRequest = z.infer; +export type AiCruitRequest = z.infer; \ No newline at end of file diff --git a/app/src/app/analyse/page.tsx b/app/src/app/analyse/page.tsx new file mode 100644 index 0000000..e7a3740 --- /dev/null +++ b/app/src/app/analyse/page.tsx @@ -0,0 +1,22 @@ +import AiCruitFrom from "@/components/AiCruitForm"; +import NavigateBack from "@/components/NavigateBack"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Lingo.ai | New", +}; + +const page = async () => { + return ( +
+
+ +
+
+ +
+
+ ); +}; + +export default page; diff --git a/app/src/app/analysis/[analysis_id]/page.css b/app/src/app/analysis/[analysis_id]/page.css new file mode 100644 index 0000000..e69de29 diff --git a/app/src/app/analysis/[analysis_id]/page.tsx b/app/src/app/analysis/[analysis_id]/page.tsx new file mode 100644 index 0000000..c3ff040 --- /dev/null +++ b/app/src/app/analysis/[analysis_id]/page.tsx @@ -0,0 +1,163 @@ +'use client' + +import "./page.css"; +import Feedback from "@/components/Feedback"; +import NavigateBack from "@/components/NavigateBack"; +import ReadMore from "../../../components/ReadMore"; +import axios from "axios"; +import { useSearchParams } from "next/navigation"; +import { use, useEffect, useState } from "react"; +import { Loader2, Logs } from "lucide-react"; +import { useMutation } from "@tanstack/react-query"; +import { Card } from "../../../components/ui/card"; +import InterviewResult from "@/components/InterviewResult"; +import InterviewQA from "@/components/InterviewQA"; +import InterviewConversation from "@/components/InterviewConversation"; + +type PageProps = { + params: { analysis_id: string }; +} + +interface AnalysisData { + analysisResult: any; + candidateName: string; + interviewerName: string; + interviewRecordingLink: string; + jobDescriptionDocumentLink: string; + parsedJobDescription: string; + questionsAnswers: any; + transcript: string; +} + +const Page = (props: PageProps) => { + const analysis_id = props.params.analysis_id; + const searchParams = useSearchParams(); + const from = searchParams.get("f"); + const [analysisData, setAnalysisData] = useState>({}); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(""); + + useEffect(() => { + GetAnalysis({ id: analysis_id }); + }, [analysis_id]); + + const { mutate: GetAnalysis } = useMutation({ + mutationKey: ["GetAnalysis"], + mutationFn: async (payload: any) => { + setLoading(true); + const analysis = await axios.post("/api/analyse/get", payload); + const analysisData = analysis.data[0]; + console.log("Ananlysis Data : ", analysisData) + if(analysisData) { + const data = { + analysisResult: JSON.parse(analysisData.analysisResult), + candidateName: analysisData.candidateName, + interviewRecordingLink: analysisData.interviewRecordingLink, + jobDescriptionDocumentLink: analysisData.jobDescriptionDocumentLink, + parsedJobDescription: analysisData.parsedJobDescription, + questionsAnswers: JSON.parse(analysisData.questions_answers), + conversation: analysisData.conversation, + transcript: analysisData.transcript, + } + setAnalysisData(data); + } + }, + onSuccess: async (res: any) => { + setLoading(false); + }, + onError: (error) => { + setError("Something went wrong, please try again"); + setLoading(false); + }, + }); + + if (loading) { + return ( +
+ +

Just a moment

+
+ ); + } + + if (Object.keys(analysisData).length === 0) { + return ( +
+

Nothing to show

+
+ ); + } + + return ( +
+

+ Interview Analysis Report +

+
+ +
+
+
+ +

+ Candidate Name : {analysisData.candidateName} +

+
+
+

+ Interviewer Name : {analysisData.interviewerName} +

+
+ +
+ {/* Analysis Container on the Left */} +
+

Analysis

+

Overview -

+ +
+ + {/* Cards on the Right */} +
+ {analysisData && ( + +

Interview

+ + {analysisData.interviewRecordingLink} + +
+ )} + {analysisData && ( + +

Job Description

+ + {analysisData.jobDescriptionDocumentLink} + +
+ )} +
+
+
+ {analysisData && } + {analysisData && } + {analysisData && } + +
+ ); +}; + +export default Page; diff --git a/app/src/app/analysis/page.tsx b/app/src/app/analysis/page.tsx new file mode 100644 index 0000000..dc85408 --- /dev/null +++ b/app/src/app/analysis/page.tsx @@ -0,0 +1,45 @@ +import NavigateBack from "@/components/NavigateBack"; +import InterviewAnalysisItem from "@/components/InterviewAnalysisItem"; +import { db } from "@/db"; +import { interviewAnalysis } from "@/db/schema"; +import { desc } from "drizzle-orm"; + +export const dynamic = "force-dynamic"; +export const fetchCache = "force-no-store"; +export const revalidate = 0; + +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "AiCruit", +}; + +const page = async () => { + const interviewAnalysisList = await db + .select({ + id: interviewAnalysis.id, + candidateName: interviewAnalysis.candidateName, + createdAt: interviewAnalysis.createdAt, + status: interviewAnalysis.status, + interviewerName: interviewAnalysis.interviewerName, + }) + .from(interviewAnalysis) + .limit(10) + .orderBy(desc(interviewAnalysis.createdAt)); + + return ( +
+

Interview Analysis

+
+ +
+
+ {interviewAnalysisList.map((interviewAnalysis, idx) => ( + + ))} +
+
+ ); +}; + +export default page; diff --git a/app/src/app/api/analyse/get/route.ts b/app/src/app/api/analyse/get/route.ts new file mode 100644 index 0000000..90d5cd1 --- /dev/null +++ b/app/src/app/api/analyse/get/route.ts @@ -0,0 +1,23 @@ +import { db } from "@/db"; +import { eq } from "drizzle-orm"; +import { interviewAnalysis } from "@/db/schema"; +import axios from "axios"; +import { z } from "zod"; + +export async function POST(req: Request) { + try { + const body = await req.json(); + const analysis = await db + .select() + .from(interviewAnalysis) + .where(eq(interviewAnalysis.id, body.id)); + + return new Response(JSON.stringify(analysis), { status: 200 }); + + } catch (error) { + if (error instanceof z.ZodError) { + return new Response(error.message, { status: 422 }); + } + return new Response("Failed to fetch error analysis", { status: 500 }); + } +} diff --git a/app/src/app/api/feedback/route.ts b/app/src/app/api/feedback/route.ts new file mode 100644 index 0000000..c66fc1a --- /dev/null +++ b/app/src/app/api/feedback/route.ts @@ -0,0 +1,40 @@ +import { validateRequest } from "@/auth"; +import { db } from "@/db"; +import { analysisFeedback, feedbackPayload } from "@/db/schema"; +import { z } from "zod"; + +export async function POST(req: Request) { + try { + const { user } = await validateRequest() + if(!user) { + return new Response(JSON.stringify({ + "message": "unauthorized" + }), { status: 400 }); + } + const body = await req.json(); + const { + analysisId, + isFoundUseful, + comment + }: feedbackPayload = body; + + const response = await db.insert(analysisFeedback).values({ + analysisId, + isFoundUseful, + comment, + userID: user?.id, + }).returning(); + + return new Response(JSON.stringify(response), { status: 200 }); + + } catch (error) { + console.log(error); + + if (error instanceof z.ZodError) { + return new Response(error.message, { status: 422 }); + } + return new Response(JSON.stringify({ + "message": FEEDBACK_REQUEST_FAILED_MESSAGE + }), { status: 500 }); + } +} diff --git a/app/src/app/api/signin/route.ts b/app/src/app/api/signin/route.ts index 08d3d70..deb848d 100644 --- a/app/src/app/api/signin/route.ts +++ b/app/src/app/api/signin/route.ts @@ -18,14 +18,16 @@ export async function POST(req: Request) { .from(userTable) .where(eq(userTable.username, userEmail)) - if (!existingUser[0]) { + if (existingUser.length === 0) { return new Response("User not found", { status: 404, }) } + + const user = existingUser[0] // login user - const validPassword = await verify(existingUser[0].password_hash, password, { + const validPassword = await verify(user.password_hash, password, { memoryCost: 19456, timeCost: 2, outputLen: 32, @@ -35,11 +37,11 @@ export async function POST(req: Request) { return new Response("Incorrect username or password", { status: 401 }) } - const session = await lucia.createSession(existingUser[0].id, {}); + const session = await lucia.createSession(user.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + (await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); - return new Response("User Logged In", { status: 200 }); + return new Response(JSON.stringify(user), { status: 200 }); } catch (error) { console.log(error); diff --git a/app/src/app/api/signup/route.ts b/app/src/app/api/signup/route.ts index 7dd1da9..2cde3f7 100644 --- a/app/src/app/api/signup/route.ts +++ b/app/src/app/api/signup/route.ts @@ -54,7 +54,7 @@ export async function POST(req: Request) { if (response) { const session = await lucia.createSession(userId, {}); const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + (await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); return new Response(JSON.stringify({ userId: response[0].id }), { status: 201, diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx index 7fad1a4..093d42b 100644 --- a/app/src/app/layout.tsx +++ b/app/src/app/layout.tsx @@ -4,22 +4,74 @@ import { Toaster } from "@/components/ui/sonner"; import TanstackQueryProvider from "@/providers/TanstackQueryProvider"; import { secondaryFont } from "@/fonts"; import Header from "@/components/Header"; +import { validateRequest } from "@/auth"; +import { redirect } from "next/navigation"; +import { User } from "lucia"; +import { headers } from "next/headers"; export const metadata: Metadata = { - title: "Lingo.ai", + title: "Lingo.ai" }; -export default function RootLayout({ +/** + * This method checks if the user is allowed to access the current requested page + * @param user logged in user + * @returns {Promise} true if allowed and false if not + */ +async function accessAllowed(user: User | null): Promise { + const headerList = headers(); + const pathSegments = headerList.get("x-current-path")?.split("/") ?? [] + const path = pathSegments.length > 1 ? `/${pathSegments[1]}` : "/" + + switch(path) { + // Allowed regardless of user logged in or not + case "": + case "/": + return true + + // Allowed when user is not logged in + case "/signin": + case "/signup": + return !user + + // Allowed when user is logged in + case "/transcriptions": + case "/record": + return !!user + + // Allowed when user is logged in and has role 'hr' + case "/analysis": + case "/analyse": + return user?.role === "hr" + + // Denied in all other cases + default: + return false + } +} + +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + + const { user } = await validateRequest(); + + if(!await accessAllowed(user)) { + if (user) { + return redirect("/") + } else { + return redirect("/signin") + } + } + return ( -
+
{children}
diff --git a/app/src/app/page.tsx b/app/src/app/page.tsx index 2909ef5..0e62494 100644 --- a/app/src/app/page.tsx +++ b/app/src/app/page.tsx @@ -1,9 +1,12 @@ +import { validateRequest } from "@/auth"; import Landing from "@/components/Landing"; export default async function Home() { + const { user } = await validateRequest(); + return (
- +
); } diff --git a/app/src/app/new/page.tsx b/app/src/app/record/page.tsx similarity index 82% rename from app/src/app/new/page.tsx rename to app/src/app/record/page.tsx index 68e241d..81dc88a 100644 --- a/app/src/app/new/page.tsx +++ b/app/src/app/record/page.tsx @@ -2,7 +2,6 @@ import { validateRequest } from "@/auth"; import NavigateBack from "@/components/NavigateBack"; import RecorderCard from "@/components/RecorderCard"; import { Metadata } from "next"; -import { redirect } from "next/navigation"; export const metadata: Metadata = { title: "Lingo.ai | New", @@ -10,16 +9,14 @@ export const metadata: Metadata = { const page = async () => { const { user } = await validateRequest(); - - if (!user) return redirect("/signin"); - + return (
- +
); diff --git a/app/src/app/signin/page.tsx b/app/src/app/signin/page.tsx index 1ba1354..c91f39c 100644 --- a/app/src/app/signin/page.tsx +++ b/app/src/app/signin/page.tsx @@ -1,7 +1,5 @@ -import { validateRequest } from "@/auth"; import NavigateBack from "@/components/NavigateBack"; import UserForm from "@/components/UserForm"; -import { redirect } from "next/navigation"; export const dynamic = "force-dynamic"; export const fetchCache = "force-no-store"; @@ -9,10 +7,6 @@ export const revalidate = 0; export default async function Page() { - - const { user } = await validateRequest(); - if (user) return redirect("/new"); - return (
diff --git a/app/src/app/signup/page.tsx b/app/src/app/signup/page.tsx index 035ac9e..5e40732 100644 --- a/app/src/app/signup/page.tsx +++ b/app/src/app/signup/page.tsx @@ -1,7 +1,5 @@ -import { validateRequest } from "@/auth"; import NavigateBack from "@/components/NavigateBack"; import UserForm from "@/components/UserForm"; -import { redirect } from "next/navigation"; export const dynamic = "force-dynamic"; export const fetchCache = "force-no-store"; @@ -9,10 +7,6 @@ export const revalidate = 0; export default async function Page() { - - const { user } = await validateRequest(); - if (user) return redirect("/new"); - return (
diff --git a/app/src/app/transcriptions/[transcription_id]/page.tsx b/app/src/app/transcriptions/[transcription_id]/page.tsx index ce1346e..2cb94b6 100644 --- a/app/src/app/transcriptions/[transcription_id]/page.tsx +++ b/app/src/app/transcriptions/[transcription_id]/page.tsx @@ -1,4 +1,3 @@ -import { validateRequest } from "@/auth"; import DetailedTranscription from "@/components/DetailedTranscription"; import NavigateBack from "@/components/NavigateBack"; import { db } from "@/db"; diff --git a/app/src/auth.ts b/app/src/auth.ts index dce5ae3..e4cd04b 100644 --- a/app/src/auth.ts +++ b/app/src/auth.ts @@ -19,14 +19,15 @@ export const lucia = new Lucia(adapter, { getUserAttributes: (attributes) => { return { // attributes has the type of DatabaseUserAttributes - username: attributes.username + username: attributes.username, + role: attributes.role, }; } }); export const validateRequest = cache( async (): Promise<{ user: User; session: Session } | { user: null; session: null }> => { - const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null; + const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null; if (!sessionId) { return { user: null, @@ -39,11 +40,11 @@ export const validateRequest = cache( try { if (result.session && result.session.fresh) { const sessionCookie = lucia.createSessionCookie(result.session.id); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + (await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); } if (!result.session) { const sessionCookie = lucia.createBlankSessionCookie(); - cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + (await cookies()).set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); } } catch {} return result; @@ -59,4 +60,5 @@ declare module "lucia" { interface DatabaseUserAttributes { username: string; + role: string; } \ No newline at end of file diff --git a/app/src/components/AiCruitForm.tsx b/app/src/components/AiCruitForm.tsx new file mode 100644 index 0000000..c24b698 --- /dev/null +++ b/app/src/components/AiCruitForm.tsx @@ -0,0 +1,147 @@ +'use client' + +import { cn } from "@/lib/utils"; +import { Input } from "./ui/input"; +import { Button, buttonVariants } from "./ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { AiCruitRequest, aiCruitRequestSchema } from "@/Validators/register"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; +import { toast } from "sonner"; +import axios, { AxiosError } from "axios"; + +import Link from "next/link"; + +const AiCruitFrom = () => { + const router = useRouter(); + + const form = useForm({ + resolver: zodResolver(aiCruitRequestSchema), + mode: "all", + }); + + const [disableSubmit, setDisableSubmit] = useState(false); + const { mutate: submitForm, isPending: isSubmitting } = useMutation({ + mutationKey: ["submit-interview-analysis"], + mutationFn: async (payload: AiCruitRequest) => { + const response = await axios.post("/analyse-interview", payload); + return response.data; + }, + onSuccess: async (res) => { + toast.success("Your Details Received successfully!, Once Completed, You can visit listing page"); + router.push("/analysis"); + }, + onError: (error) => { + if (error instanceof AxiosError) { + toast.error("Failed to submit form", { + description: error.message, + }); + } else { + toast.error("An unexpected error occurred. Please try again later."); + } + }, + onSettled: () => { + setDisableSubmit(false); + }, + }); + + const onSubmit = (data: AiCruitRequest) => { + setDisableSubmit(true); + submitForm(data); + }; + + return ( +
+
+ +

+ New Interview Analysis +

+
+ ( + + Candidate Name + + + + + + )} + /> +
+
+ ( + + Interviewer Name + + + + + + )} + /> +
+
+ ( + + Interview Link + + + + + + )} + /> +
+
+ ( + + Job Description Link + + + + + + )} + /> +
+ + +
+ +
+ ); +}; + +export default AiCruitFrom; \ No newline at end of file diff --git a/app/src/components/DetailedInterviewAnalysis.css b/app/src/components/DetailedInterviewAnalysis.css new file mode 100644 index 0000000..60d7882 --- /dev/null +++ b/app/src/components/DetailedInterviewAnalysis.css @@ -0,0 +1,54 @@ +.analysis-section-subtitle { + padding-bottom: 8px; + border-bottom: solid 2px; +} +.analysis-section-cause { + border-color: #ffaa00; +} +.analysis-section-impact { + border-color: #f97315; +} +.analysis-section-fix { + border-color: #15b8a6; +} +.analysis-section li, +.analysis-section li, +.analysis-section li { + margin-bottom: 12px; +} +.analysis-section-card { + word-break: break-word; +} +.analysis-container { + margin-bottom: 0; +} + +.analysis-section { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; +} + +.analysis-section-card { + margin-top: 0; + margin-bottom: 0; +} + +@media print { + body { + -webkit-print-color-adjust: exact; + } + .analysis-section-card { + width: 100%; + margin: 0; + padding: 20px; + } + } + +.analysis-section-card { +word-wrap: break-word; /* Forces wrapping of long text */ +overflow-wrap: break-word; +white-space: pre-wrap; /* Preserves line breaks but allows wrapping */ +} + \ No newline at end of file diff --git a/app/src/components/DetailedTranscription.tsx b/app/src/components/DetailedTranscription.tsx index 2f7e464..7bce4c2 100644 --- a/app/src/components/DetailedTranscription.tsx +++ b/app/src/components/DetailedTranscription.tsx @@ -1,4 +1,5 @@ -"use client"; +'use client' + import { format } from "date-fns"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; diff --git a/app/src/components/Feedback.css b/app/src/components/Feedback.css new file mode 100644 index 0000000..e6ffca4 --- /dev/null +++ b/app/src/components/Feedback.css @@ -0,0 +1,18 @@ +.feedback-positive-button, +.feedback-negative-button { + height: auto; +} +.feedback-positive-button { + color: #388e3c; +} +.feedback-negative-button { + color: #f44336; +} +.feedback-positive-button:hover, +.feedback-negative-button:hover { + opacity: 0.85; +} +.feedback-positive-button:hover, +.feedback-negative-button:hover { + color: #ffffff; +} \ No newline at end of file diff --git a/app/src/components/Feedback.tsx b/app/src/components/Feedback.tsx new file mode 100644 index 0000000..13d27dd --- /dev/null +++ b/app/src/components/Feedback.tsx @@ -0,0 +1,175 @@ +'use client' + +import { useEffect, useState } from 'react'; +import './Feedback.css'; +import { useMutation } from '@tanstack/react-query'; +import axios from 'axios'; +import { Button } from './ui/button'; +import React from 'react'; +import { Loader2, ThumbsDown, ThumbsUp } from 'lucide-react'; +import { toast } from 'sonner'; + +interface feedBackProps { + analysisId: string +} + +const Feedback = (props: feedBackProps) => { + const { analysisId } = props; + const [feedback, setFeedback] = useState(""); + const [error, setError] = useState(""); + const [isFoundUseful, setIsFoundUseful] = useState(true); + const [openDialog, setOpenDialog] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isPositiveFeedbackLoading, setIsPositiveFeedbackLoading] = useState(false); + const [isNegativeFeedbackLoading, setIsNegativeFeedbackLoading] = useState(false); + const [isFeedbackSubmitted, setIsFeedbackSubmitted] = useState(false); + + + const onFeedback = (liked: boolean) => { + setIsFoundUseful(liked); + if (liked) { + const requestBody = { + comment: feedback, + isFoundUseful: isFoundUseful, + analysisId: analysisId + } + SaveAnalysisFeedBack(requestBody); + } else { + setError(""); + setOpenDialog(true); + } + } + + const { mutate: SaveAnalysisFeedBack } = useMutation({ + mutationKey: ["SaveAnalysisFeedBack"], + mutationFn: async (payload: any) => { + setIsLoading(true); + setIsPositiveFeedbackLoading(payload.isFoundUseful ?? false); + setIsNegativeFeedbackLoading(!(payload.isFoundUseful ?? true)); + const response = await axios.post("/api/feedback", payload); + return response; + }, + onSuccess: async (res: any) => { + setIsLoading(false); + setIsLoading(true); + setIsPositiveFeedbackLoading(false); + setIsNegativeFeedbackLoading(false); + setIsFeedbackSubmitted(true); + toast.success("Thanks for your feedback!"); + handleClose(true); + }, + onError: (error) => { + setError("Something went wrong, please try again"); + setIsLoading(false); + setIsPositiveFeedbackLoading(false); + setIsNegativeFeedbackLoading(false); + }, + }); + + const handleTextareaChange = (e: React.ChangeEvent) => { + setFeedback(e.target.value); + }; + + const handleClose = (forceClose: boolean = false) => { + if(isLoading && !forceClose) { + return; + } + setOpenDialog(false); + }; + + const handleSubmit = async () => { + if (feedback.trim() === "") { + setError("This field is required."); + } else { + const requestBody = { + comment: feedback, + isFoundUseful: isFoundUseful, + analysisId: analysisId + } + SaveAnalysisFeedBack(requestBody); + setError(""); + } + }; + + return ( +
+ {(!isFeedbackSubmitted && +
+
Was this helpful?
+
+ + +
+
+ )} + {(openDialog && +
+ +
+
+
+
+

Help us improve!

+

Share your suggestions to help us improve.

+
+ +