refactor: change all about fastapi endpoint integration
This commit is contained in:
parent
03fe41419e
commit
16d1312f63
|
|
@ -3,6 +3,9 @@ import { ApiHandler, ServerActionHandler } from "@/src/types";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
export const revalidate = 0;
|
||||||
|
|
||||||
export function withAuth(handler: ApiHandler) {
|
export function withAuth(handler: ApiHandler) {
|
||||||
return async (req: Request, context: any) => {
|
return async (req: Request, context: any) => {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `Account` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `Account` table. All the data in the column will be lost.
|
||||||
|
- The primary key for the `Analysis` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `compatibilityScore` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `generalSentiment` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `id` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `modelId` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `productId` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `targetProfession` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `topKeywords` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `verdict` on the `Analysis` table. All the data in the column will be lost.
|
||||||
|
- The primary key for the `Model` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `Model` table. All the data in the column will be lost.
|
||||||
|
- The primary key for the `Review` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `Review` table. All the data in the column will be lost.
|
||||||
|
- The primary key for the `Session` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `User` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `Product` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `UserPreference` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- Added the required column `updatedAt` to the `Analysis` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "BrandName" AS ENUM ('APPLE', 'ASUS', 'ACER', 'LENOVO', 'HP', 'DELL', 'MSI', 'AXIOO', 'ADVAN', 'ZYREX', 'OTHER');
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Analysis" DROP CONSTRAINT "Analysis_modelId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Analysis" DROP CONSTRAINT "Analysis_productId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Analysis" DROP CONSTRAINT "Analysis_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Review" DROP CONSTRAINT "Review_modelId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Review" DROP CONSTRAINT "Review_productId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Review" DROP CONSTRAINT "Review_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "UserPreference" DROP CONSTRAINT "UserPreference_userId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Account" DROP CONSTRAINT "Account_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD COLUMN "accountId" SERIAL NOT NULL,
|
||||||
|
ADD CONSTRAINT "Account_pkey" PRIMARY KEY ("accountId");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Analysis" DROP CONSTRAINT "Analysis_pkey",
|
||||||
|
DROP COLUMN "compatibilityScore",
|
||||||
|
DROP COLUMN "generalSentiment",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
DROP COLUMN "modelId",
|
||||||
|
DROP COLUMN "productId",
|
||||||
|
DROP COLUMN "targetProfession",
|
||||||
|
DROP COLUMN "topKeywords",
|
||||||
|
DROP COLUMN "verdict",
|
||||||
|
ADD COLUMN "analysisId" SERIAL NOT NULL,
|
||||||
|
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
ADD CONSTRAINT "Analysis_pkey" PRIMARY KEY ("analysisId");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Model" DROP CONSTRAINT "Model_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD COLUMN "modelId" SERIAL NOT NULL,
|
||||||
|
ADD CONSTRAINT "Model_pkey" PRIMARY KEY ("modelId");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Review" DROP CONSTRAINT "Review_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD COLUMN "reviewId" SERIAL NOT NULL,
|
||||||
|
ADD CONSTRAINT "Review_pkey" PRIMARY KEY ("reviewId");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" DROP CONSTRAINT "Session_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD COLUMN "sessionId" SERIAL NOT NULL,
|
||||||
|
ADD CONSTRAINT "Session_pkey" PRIMARY KEY ("sessionId");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" DROP CONSTRAINT "User_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD COLUMN "userId" SERIAL NOT NULL,
|
||||||
|
ADD CONSTRAINT "User_pkey" PRIMARY KEY ("userId");
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "Product";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "UserPreference";
|
||||||
|
|
||||||
|
-- DropEnum
|
||||||
|
DROP TYPE "Brand";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "user_preferences" (
|
||||||
|
"userPreferenceId" SERIAL NOT NULL,
|
||||||
|
"profession" "Profession",
|
||||||
|
"preferredOS" "OS",
|
||||||
|
"budgetMin" INTEGER,
|
||||||
|
"budgetMax" INTEGER,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
"preferedBrandId" INTEGER,
|
||||||
|
|
||||||
|
CONSTRAINT "user_preferences_pkey" PRIMARY KEY ("userPreferenceId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "products" (
|
||||||
|
"productId" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"image" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"brandId" INTEGER,
|
||||||
|
|
||||||
|
CONSTRAINT "products_pkey" PRIMARY KEY ("productId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Metric" (
|
||||||
|
"metricId" SERIAL NOT NULL,
|
||||||
|
"generalSentiment" DOUBLE PRECISION NOT NULL,
|
||||||
|
"compatibilityScore" DOUBLE PRECISION NOT NULL,
|
||||||
|
"verdict" TEXT NOT NULL,
|
||||||
|
"topKeywords" TEXT[],
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"analysisId" INTEGER NOT NULL,
|
||||||
|
"productId" INTEGER NOT NULL,
|
||||||
|
"modelId" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Metric_pkey" PRIMARY KEY ("metricId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "brands" (
|
||||||
|
"brandId" SERIAL NOT NULL,
|
||||||
|
"name" "BrandName",
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "brands_pkey" PRIMARY KEY ("brandId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "user_preferences_userId_key" ON "user_preferences"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "products_url_key" ON "products"("url");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_pref_brand_fkey" FOREIGN KEY ("preferedBrandId") REFERENCES "brands"("brandId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "products" ADD CONSTRAINT "product_brand_fkey" FOREIGN KEY ("brandId") REFERENCES "brands"("brandId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Review" ADD CONSTRAINT "Review_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("productId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Review" ADD CONSTRAINT "Review_modelId_fkey" FOREIGN KEY ("modelId") REFERENCES "Model"("modelId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Review" ADD CONSTRAINT "Review_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Analysis" ADD CONSTRAINT "Analysis_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Metric" ADD CONSTRAINT "Metric_analysisId_fkey" FOREIGN KEY ("analysisId") REFERENCES "Analysis"("analysisId") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Metric" ADD CONSTRAINT "Metric_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("productId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Metric" ADD CONSTRAINT "Metric_modelId_fkey" FOREIGN KEY ("modelId") REFERENCES "Model"("modelId") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the `Account` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `Session` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Analysis" DROP CONSTRAINT "Analysis_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Review" DROP CONSTRAINT "Review_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "user_preferences" DROP CONSTRAINT "user_preferences_userId_fkey";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "Account";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "Session";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "User";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "accounts" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"provider" TEXT NOT NULL,
|
||||||
|
"providerAccountId" TEXT NOT NULL,
|
||||||
|
"refresh_token" TEXT,
|
||||||
|
"access_token" TEXT,
|
||||||
|
"expires_at" INTEGER,
|
||||||
|
"token_type" TEXT,
|
||||||
|
"scope" TEXT,
|
||||||
|
"id_token" TEXT,
|
||||||
|
"session_state" TEXT,
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "sessions" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"session_token" TEXT NOT NULL,
|
||||||
|
"expires" TIMESTAMP(3) NOT NULL,
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"userId" SERIAL NOT NULL,
|
||||||
|
"name" TEXT,
|
||||||
|
"email" TEXT,
|
||||||
|
"emailVerified" TIMESTAMP(3),
|
||||||
|
"image" TEXT,
|
||||||
|
"password" TEXT,
|
||||||
|
"bio" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "users_pkey" PRIMARY KEY ("userId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "accounts_provider_providerAccountId_key" ON "accounts"("provider", "providerAccountId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "sessions_session_token_key" ON "sessions"("session_token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Review" ADD CONSTRAINT "Review_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("userId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Analysis" ADD CONSTRAINT "Analysis_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The `name` column on the `brands` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "brands" DROP COLUMN "name",
|
||||||
|
ADD COLUMN "name" TEXT;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[name]` on the table `brands` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Made the column `name` on table `brands` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "brands" ALTER COLUMN "name" SET NOT NULL;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "brands_name_key" ON "brands"("name");
|
||||||
|
|
@ -26,7 +26,7 @@ enum OS {
|
||||||
OTHER
|
OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Brand {
|
enum BrandName {
|
||||||
APPLE
|
APPLE
|
||||||
ASUS
|
ASUS
|
||||||
ACER
|
ACER
|
||||||
|
|
@ -49,8 +49,7 @@ enum Profession {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
userId Int
|
|
||||||
type String
|
type String
|
||||||
provider String
|
provider String
|
||||||
providerAccountId String
|
providerAccountId String
|
||||||
|
|
@ -61,17 +60,23 @@ model Account {
|
||||||
scope String?
|
scope String?
|
||||||
id_token String?
|
id_token String?
|
||||||
session_state String?
|
session_state String?
|
||||||
|
|
||||||
|
userId Int @map("user_id")
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([provider, providerAccountId])
|
@@unique([provider, providerAccountId])
|
||||||
|
@@map("accounts")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
sessionToken String @unique
|
sessionToken String @unique @map("session_token")
|
||||||
userId Int
|
|
||||||
expires DateTime
|
expires DateTime
|
||||||
|
|
||||||
|
userId Int @map("user_id")
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@map("sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
|
|
@ -83,7 +88,7 @@ model VerificationToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement()) @map("userId")
|
||||||
name String?
|
name String?
|
||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
|
|
@ -99,40 +104,50 @@ model User {
|
||||||
analyses Analysis[]
|
analyses Analysis[]
|
||||||
|
|
||||||
preference UserPreference?
|
preference UserPreference?
|
||||||
review Review[]
|
review Review[]
|
||||||
|
|
||||||
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserPreference {
|
model UserPreference {
|
||||||
id Int @id @default(autoincrement())
|
userPreferenceId Int @id @default(autoincrement())
|
||||||
profession Profession?
|
profession Profession?
|
||||||
preferredOS OS?
|
preferredOS OS?
|
||||||
preferredBrand Brand?
|
budgetMin Int?
|
||||||
budgetMin Int?
|
budgetMax Int?
|
||||||
budgetMax Int?
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
userId Int @unique
|
userId Int @unique
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
preferedBrandId Int?
|
||||||
|
brand Brand? @relation(fields: [preferedBrandId], references: [brandId], map: "user_pref_brand_fkey")
|
||||||
|
|
||||||
|
@@map("user_preferences")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Product {
|
model Product {
|
||||||
id Int @id @default(autoincrement())
|
productId Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
brand String?
|
url String @unique
|
||||||
url String @unique
|
image String?
|
||||||
image String?
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
reviews Review[]
|
reviews Review[]
|
||||||
analyses Analysis[]
|
metrics Metric[]
|
||||||
|
|
||||||
|
brandId Int?
|
||||||
|
brand Brand? @relation(fields: [brandId], references: [brandId], map: "product_brand_fkey")
|
||||||
|
|
||||||
|
@@map("products")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Review {
|
model Review {
|
||||||
id Int @id @default(autoincrement())
|
reviewId Int @id @default(autoincrement())
|
||||||
content String
|
content String
|
||||||
sentiment Sentiment
|
sentiment Sentiment
|
||||||
confidenceScore Float
|
confidenceScore Float
|
||||||
|
|
@ -142,53 +157,73 @@ model Review {
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
productId Int
|
productId Int
|
||||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
product Product @relation(fields: [productId], references: [productId], onDelete: Cascade)
|
||||||
|
|
||||||
modelId Int?
|
modelId Int?
|
||||||
model Model? @relation(fields: [modelId], references: [id])
|
model Model? @relation(fields: [modelId], references: [modelId])
|
||||||
|
|
||||||
userId Int?
|
userId Int?
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model Analysis {
|
model Analysis {
|
||||||
id Int @id @default(autoincrement())
|
analysisId Int @id @default(autoincrement())
|
||||||
|
|
||||||
targetProfession String
|
|
||||||
|
|
||||||
generalSentiment Float
|
|
||||||
compatibilityScore Float
|
|
||||||
verdict String
|
|
||||||
|
|
||||||
topKeywords String[]
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
userId Int
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
productId Int
|
|
||||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
modelId Int
|
|
||||||
model Model @relation(fields: [modelId], references: [id])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
model Model {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
modelName String
|
|
||||||
description String?
|
|
||||||
accuracy Float
|
|
||||||
macroF1 Float
|
|
||||||
f1Negative Float
|
|
||||||
f1Neutral Float
|
|
||||||
isActive Boolean @default(true)
|
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
analyses Analysis[]
|
userId Int
|
||||||
reviews Review[]
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
metric Metric[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Model {
|
||||||
|
modelId Int @id @default(autoincrement())
|
||||||
|
modelName String
|
||||||
|
description String?
|
||||||
|
accuracy Float
|
||||||
|
macroF1 Float
|
||||||
|
f1Negative Float
|
||||||
|
f1Neutral Float
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
metrics Metric[]
|
||||||
|
reviews Review[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Metric {
|
||||||
|
metricId Int @id @default(autoincrement())
|
||||||
|
generalSentiment Float
|
||||||
|
compatibilityScore Float
|
||||||
|
verdict String
|
||||||
|
topKeywords String[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
analysisId Int
|
||||||
|
analysis Analysis @relation(fields: [analysisId], references: [analysisId])
|
||||||
|
|
||||||
|
productId Int
|
||||||
|
product Product @relation(fields: [productId], references: [productId], onDelete: Cascade)
|
||||||
|
|
||||||
|
modelId Int
|
||||||
|
model Model @relation(fields: [modelId], references: [modelId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Brand {
|
||||||
|
brandId Int @id @default(autoincrement())
|
||||||
|
name String @unique
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
products Product[]
|
||||||
|
userPreferences UserPreference[]
|
||||||
|
|
||||||
|
@@map("brands")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,19 @@ export const authOptions = {
|
||||||
GoogleProvider({
|
GoogleProvider({
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||||
|
allowDangerousEmailAccountLinking: true,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
debug: true,
|
debug: true,
|
||||||
session: { strategy: "database" as const },
|
session: { strategy: "database" as const },
|
||||||
|
callbacks: {
|
||||||
|
async session({ session, user }: any) {
|
||||||
|
if (session.user) {
|
||||||
|
session.user.id = user.id;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const handler = NextAuth(authOptions);
|
const handler = NextAuth(authOptions);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { authOptions } from "../auth/[...nextauth]/route";
|
||||||
|
import { AnalysisWithMetric } from "@/src/hooks/useAnalyzeText";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.email) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userAnalysis = (await prisma.analysis.findFirst({
|
||||||
|
where: {
|
||||||
|
user: { email: session.user.email },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
metric: {
|
||||||
|
select: {
|
||||||
|
metricId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
})) as AnalysisWithMetric | null;
|
||||||
|
|
||||||
|
return NextResponse.json({ metricId: userAnalysis?.metric?.metricId });
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import { withActionAuth } from "@/lib/withAuth";
|
||||||
import { getAnalysisData } from "@/src/services/analyze.service";
|
import { getAnalysisData } from "@/src/services/analyze.service";
|
||||||
import { formatBrandStats } from "@/src/services/brand.service";
|
import { formatBrandStats } from "@/src/services/brand.service";
|
||||||
import { reportService } from "@/src/services/report.service";
|
import { reportService } from "@/src/services/report.service";
|
||||||
|
import { AnalysisData } from "@/src/types";
|
||||||
|
|
||||||
export const getClassificationReport = async () => {
|
export const getClassificationReport = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -20,13 +21,29 @@ export const getTotalBrandAnalysis = withActionAuth(async (session) => {
|
||||||
try {
|
try {
|
||||||
const email = session.user?.email as string;
|
const email = session.user?.email as string;
|
||||||
|
|
||||||
const userAnalysis = await getAnalysisData(email);
|
const rawAnalysis = await getAnalysisData(email);
|
||||||
|
|
||||||
|
const userAnalysis: AnalysisData[] = rawAnalysis.map((item: any) => ({
|
||||||
|
analysisId: item.analysisId,
|
||||||
|
userId: item.userId,
|
||||||
|
createdAt: item.createdAt,
|
||||||
|
updatedAt: item.updatedAt,
|
||||||
|
product:
|
||||||
|
item.metric && item.metric[0]?.product
|
||||||
|
? {
|
||||||
|
productId: item.metric[0].product.productId, // Gunakan productId, bukan id
|
||||||
|
brandName: item.metric[0].product.brand?.name || "Generic", // Gunakan brandName, bukan brand
|
||||||
|
name: item.metric[0].product.name,
|
||||||
|
reviewCount: item.metric[0].product._count?.reviews || 0, // Gunakan reviewCount, bukan _count.reviews
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
}));
|
||||||
|
|
||||||
const formattedBrands = formatBrandStats(userAnalysis);
|
const formattedBrands = formatBrandStats(userAnalysis);
|
||||||
|
|
||||||
return { formattedBrands, userAnalysis };
|
return { formattedBrands, userAnalysis };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal mengambil data review:", error);
|
console.error("Gagal mengambil data review:", error);
|
||||||
return [];
|
return { formattedBrands: [], userAnalysis: [] };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,12 @@ import { getAnotherUserDataService } from "@/src/services/users.service";
|
||||||
|
|
||||||
export const getAnotherUserData = withActionAuth(async (session) => {
|
export const getAnotherUserData = withActionAuth(async (session) => {
|
||||||
try {
|
try {
|
||||||
const email = (await session.user?.email) as string;
|
const email = session.user?.email;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
console.warn("No email found in session");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const userData = await getAnotherUserDataService(email);
|
const userData = await getAnotherUserDataService(email);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const profileSchema = z.object({
|
||||||
name: z.string().min(2, "Nama minimal 2 karakter"),
|
name: z.string().min(2, "Nama minimal 2 karakter"),
|
||||||
bio: z.string().min(10, "Bio minimal 10 karakter"),
|
bio: z.string().min(10, "Bio minimal 10 karakter"),
|
||||||
profession: professionEnum,
|
profession: professionEnum,
|
||||||
preferredBrand: brandEnum,
|
preferredBrand: z.string().min(2, "Merek laptop minimal 2 karakter"),
|
||||||
preferredOS: osEnum,
|
preferredOS: osEnum,
|
||||||
budgetMin: z.coerce.number().min(0),
|
budgetMin: z.coerce.number().min(0),
|
||||||
budgetMax: z.coerce.number().min(0),
|
budgetMax: z.coerce.number().min(0),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
MessageSquareText,
|
MessageSquareText,
|
||||||
Smile,
|
Smile,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
TrendingUp,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { StatCard } from "./StatCard";
|
import { StatCard } from "./StatCard";
|
||||||
import { ModelInfoSkeleton } from "../skeletons/ModelInfoSkeleton";
|
import { ModelInfoSkeleton } from "../skeletons/ModelInfoSkeleton";
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import { Button } from "../ui/button";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
import { formatRupiah, toTitleCase } from "@/src/utils/datas";
|
import { formatRupiah, toTitleCase } from "@/src/utils/datas";
|
||||||
import { brandItems, OSItems, professionItems } from "@/src/utils/const";
|
import { brandItems, OSItems, professionItems } from "@/src/utils/const";
|
||||||
import { useProfileClient } from "@/src/hooks/useProfileClient";
|
|
||||||
import { ProfileModal } from "./ProfileModal";
|
import { ProfileModal } from "./ProfileModal";
|
||||||
|
import { useProfileClient } from "@/src/hooks/useProfileClient";
|
||||||
|
|
||||||
export default function ProfileCard(props: ProfileClientProps) {
|
export default function ProfileCard(props: ProfileClientProps) {
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default async function ProfileClient() {
|
||||||
name={user?.name || ""}
|
name={user?.name || ""}
|
||||||
bio={user?.bio || "None"}
|
bio={user?.bio || "None"}
|
||||||
profession={user?.preference?.profession || ""}
|
profession={user?.preference?.profession || ""}
|
||||||
preferenceBrand={user?.preference?.preferredBrand || ""}
|
preferenceBrand={user?.preference?.brand?.name || ""}
|
||||||
preferenceOS={user?.preference?.preferredOS || ""}
|
preferenceOS={user?.preference?.preferredOS || ""}
|
||||||
budgetMax={user?.preference?.budgetMax || 0}
|
budgetMax={user?.preference?.budgetMax || 0}
|
||||||
budgetMin={user?.preference?.budgetMin || 0}
|
budgetMin={user?.preference?.budgetMin || 0}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ export function ReviewTable() {
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="inline-flex items-center rounded-md bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary/20">
|
<span className="inline-flex items-center rounded-md bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary/20">
|
||||||
{review.product?.brand || "Generic"}
|
{/* Tambahkan .name di sini */}
|
||||||
|
{review.product?.brand?.name || "Generic"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium leading-tight text-foreground line-clamp-2">
|
<span className="text-sm font-medium leading-tight text-foreground line-clamp-2">
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,18 @@ import {
|
||||||
} from "../services/analyze.service";
|
} from "../services/analyze.service";
|
||||||
import { analyzeSchema } from "../app/validation/analyze.schema"; // Sesuaikan path-nya
|
import { analyzeSchema } from "../app/validation/analyze.schema"; // Sesuaikan path-nya
|
||||||
import { getAnotherUserData } from "../app/profile/lib/action";
|
import { getAnotherUserData } from "../app/profile/lib/action";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { getMetricId } from "../services/metric.service";
|
||||||
|
|
||||||
export type AnalyzeFormData = z.infer<typeof analyzeSchema>;
|
export type AnalyzeFormData = z.infer<typeof analyzeSchema>;
|
||||||
|
|
||||||
|
export interface AnalysisWithMetric {
|
||||||
|
metric: {
|
||||||
|
metricId: number;
|
||||||
|
name: string;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export const useAnalyseText = () => {
|
export const useAnalyseText = () => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
@ -121,9 +130,19 @@ export const useAnalyseText = () => {
|
||||||
reviews: res.data.reviews,
|
reviews: res.data.reviews,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const aiResult = await getAIRecommendation({
|
const metricIdValue = await getMetricId();
|
||||||
|
|
||||||
|
console.log("Payload to AI:", {
|
||||||
user_email: session.user.email,
|
user_email: session.user.email,
|
||||||
|
metric_id: metricIdValue,
|
||||||
|
candidateCount: candidates.length,
|
||||||
|
totalReviews: candidates.reduce((acc, c) => acc + c.reviews.length, 0),
|
||||||
|
});
|
||||||
|
|
||||||
|
const aiResult = await getAIRecommendation({
|
||||||
|
user_email: session.user.email as string,
|
||||||
candidates: candidates,
|
candidates: candidates,
|
||||||
|
metric_id: metricIdValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
setResult(aiResult);
|
setResult(aiResult);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const useProfileClient = (props: ProfileClientProps) => {
|
||||||
(newData.profession as Profession) || prev.preference.profession,
|
(newData.profession as Profession) || prev.preference.profession,
|
||||||
|
|
||||||
preferredBrand:
|
preferredBrand:
|
||||||
(newData.preferredBrand as Brand) || prev.preference.preferredBrand,
|
(newData.preferredBrand ) || prev.preference.preferredBrand,
|
||||||
|
|
||||||
preferredOS: (newData.preferredOS as OS) || prev.preference.preferredOS,
|
preferredOS: (newData.preferredOS as OS) || prev.preference.preferredOS,
|
||||||
|
|
||||||
|
|
@ -74,7 +74,9 @@ export const useProfileClient = (props: ProfileClientProps) => {
|
||||||
profession,
|
profession,
|
||||||
} = preference;
|
} = preference;
|
||||||
|
|
||||||
const { brands } = brandFormat({ preferenceBrand });
|
const { brands } = brandFormat({
|
||||||
|
preferenceBrand: preferenceBrand as any,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
session,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export const useReviewTable = (
|
||||||
const filteredData = selectedBrand
|
const filteredData = selectedBrand
|
||||||
? data.filter(
|
? data.filter(
|
||||||
(review) =>
|
(review) =>
|
||||||
review.product?.brand?.toLowerCase() ===
|
review.product?.brand?.name.toLowerCase() ===
|
||||||
selectedBrand.toLowerCase(),
|
selectedBrand.toLowerCase(),
|
||||||
)
|
)
|
||||||
: data;
|
: data;
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,13 @@ export const useSentiment = () => {
|
||||||
let confidence: number;
|
let confidence: number;
|
||||||
|
|
||||||
if (positiveScore > negativeScore) {
|
if (positiveScore > negativeScore) {
|
||||||
sentiment = "positif";
|
sentiment = "POSITIVE";
|
||||||
confidence = 0.75 + Math.random() * 0.2;
|
confidence = 0.75 + Math.random() * 0.2;
|
||||||
} else if (negativeScore > positiveScore) {
|
} else if (negativeScore > positiveScore) {
|
||||||
sentiment = "negatif";
|
sentiment = "NEGATIVE";
|
||||||
confidence = 0.75 + Math.random() * 0.2;
|
confidence = 0.75 + Math.random() * 0.2;
|
||||||
} else {
|
} else {
|
||||||
sentiment = "netral";
|
sentiment = "NEUTRAL";
|
||||||
confidence = 0.6 + Math.random() * 0.2;
|
confidence = 0.6 + Math.random() * 0.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,21 +92,21 @@ export const useSentiment = () => {
|
||||||
|
|
||||||
const getSentimentDisplay = (sentiment: AnalysisResult["sentiment"]) => {
|
const getSentimentDisplay = (sentiment: AnalysisResult["sentiment"]) => {
|
||||||
const config = {
|
const config = {
|
||||||
positif: {
|
POSITIVE: {
|
||||||
icon: ThumbsUp,
|
icon: ThumbsUp,
|
||||||
label: "Positif",
|
label: "Positif",
|
||||||
bgClass: "bg-sentiment-positive-light",
|
bgClass: "bg-sentiment-positive-light",
|
||||||
textClass: "text-sentiment-positive",
|
textClass: "text-sentiment-positive",
|
||||||
borderClass: "border-sentiment-positive/30",
|
borderClass: "border-sentiment-positive/30",
|
||||||
},
|
},
|
||||||
negatif: {
|
NEGATIVE: {
|
||||||
icon: ThumbsDown,
|
icon: ThumbsDown,
|
||||||
label: "Negatif",
|
label: "Negatif",
|
||||||
bgClass: "bg-sentiment-negative-light",
|
bgClass: "bg-sentiment-negative-light",
|
||||||
textClass: "text-sentiment-negative",
|
textClass: "text-sentiment-negative",
|
||||||
borderClass: "border-sentiment-negative/30",
|
borderClass: "border-sentiment-negative/30",
|
||||||
},
|
},
|
||||||
netral: {
|
NEUTRAL: {
|
||||||
icon: Minus,
|
icon: Minus,
|
||||||
label: "Netral",
|
label: "Netral",
|
||||||
bgClass: "bg-sentiment-neutral-light",
|
bgClass: "bg-sentiment-neutral-light",
|
||||||
|
|
|
||||||
|
|
@ -19,22 +19,6 @@ export const scrapeProduct = async (url: string) => {
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const getAIRecommendation = async (payload: {
|
|
||||||
// user_email: string;
|
|
||||||
// // profession: string;
|
|
||||||
// candidates: { name: string; url: string; reviews: any[] }[];
|
|
||||||
// }) => {
|
|
||||||
// const aiRes = await fetch("http://localhost:8000/recommend", {
|
|
||||||
// method: "POST",
|
|
||||||
// headers: { "Content-Type": "application/json" },
|
|
||||||
// body: JSON.stringify(payload),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!aiRes.ok) throw new Error("Gagal melakukan analisis AI");
|
|
||||||
|
|
||||||
// return await aiRes.json();
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const getAnalysisData = async (email: string) => {
|
export const getAnalysisData = async (email: string) => {
|
||||||
const userAnalyses = await prisma.analysis.findMany({
|
const userAnalyses = await prisma.analysis.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -43,16 +27,24 @@ export const getAnalysisData = async (email: string) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
product: {
|
metric: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
product: {
|
||||||
brand: true,
|
|
||||||
_count: {
|
|
||||||
select: {
|
select: {
|
||||||
reviews: {
|
productId: true,
|
||||||
where: {
|
brand: {
|
||||||
user: {
|
select:{
|
||||||
email: email,
|
name: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
reviews: {
|
||||||
|
where: {
|
||||||
|
user: {
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -67,8 +59,10 @@ export const getAnalysisData = async (email: string) => {
|
||||||
|
|
||||||
export const getAIRecommendation = async (payload: {
|
export const getAIRecommendation = async (payload: {
|
||||||
user_email: string;
|
user_email: string;
|
||||||
|
metric_id: number | 1;
|
||||||
candidates: { name: string; url: string; reviews: string[] }[];
|
candidates: { name: string; url: string; reviews: string[] }[];
|
||||||
}): Promise<AIRecommendationResponse> => {
|
}): Promise<AIRecommendationResponse> => {
|
||||||
|
console.log("Fetching to FastAPI...");
|
||||||
const aiRes = await fetch("http://localhost:8000/recommend", {
|
const aiRes = await fetch("http://localhost:8000/recommend", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -77,7 +71,16 @@ export const getAIRecommendation = async (payload: {
|
||||||
|
|
||||||
if (!aiRes.ok) {
|
if (!aiRes.ok) {
|
||||||
const errorData = await aiRes.json();
|
const errorData = await aiRes.json();
|
||||||
throw new Error(errorData.detail || "Gagal melakukan analisis AI");
|
// DEBUG: Munculkan di console agar bisa dibaca strukturnya
|
||||||
|
console.error(
|
||||||
|
"DETAILED VALIDATION ERROR:",
|
||||||
|
JSON.stringify(errorData, null, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ambil pesan error pertama dari list validation FastAPI
|
||||||
|
const errorMessage =
|
||||||
|
errorData.detail?.[0]?.msg || "Gagal melakukan analisis AI";
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await aiRes.json();
|
return await aiRes.json();
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ export const formatBrandStats = (userAnalysis: AnalysisData[]) => {
|
||||||
|
|
||||||
const brandCounts = userAnalysis.reduce(
|
const brandCounts = userAnalysis.reduce(
|
||||||
(acc: Record<string, number>, analysis) => {
|
(acc: Record<string, number>, analysis) => {
|
||||||
const productId = analysis.product?.id;
|
const productId = analysis.product?.productId;
|
||||||
const rawBrand = analysis.product?.brand || "Unknown";
|
const rawBrand = analysis.product?.brandName || "Unknown";
|
||||||
const reviewCount = analysis.product?._count?.reviews || 0;
|
const reviewCount = analysis.product?.reviewCount || 0;
|
||||||
|
|
||||||
if (productId && countedProductIds.has(productId)) {
|
if (productId && countedProductIds.has(productId)) {
|
||||||
return acc;
|
return acc;
|
||||||
|
|
@ -20,7 +20,7 @@ export const formatBrandStats = (userAnalysis: AnalysisData[]) => {
|
||||||
const formattedBrand = rawBrand
|
const formattedBrand = rawBrand
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
.replace(/\b\w/g, (char: any) => char.toUpperCase());
|
||||||
|
|
||||||
if (!acc[formattedBrand]) {
|
if (!acc[formattedBrand]) {
|
||||||
acc[formattedBrand] = 0;
|
acc[formattedBrand] = 0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const getMetricId = async () => {
|
||||||
|
const response = await fetch("/api/user-metric");
|
||||||
|
if (!response.ok) return null;
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.metricId;
|
||||||
|
};
|
||||||
|
|
@ -8,9 +8,11 @@ export const productService = async (email: string) => {
|
||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
const totalProducts = await prisma.analysis.count({
|
const totalProducts = await prisma.metric.count({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
analysis: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,11 @@ export const userService = {
|
||||||
include: {
|
include: {
|
||||||
preference: {
|
preference: {
|
||||||
select: {
|
select: {
|
||||||
preferredBrand: true,
|
brand: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
preferredOS: true,
|
preferredOS: true,
|
||||||
profession: true,
|
profession: true,
|
||||||
budgetMax: true,
|
budgetMax: true,
|
||||||
|
|
@ -54,25 +58,38 @@ export const userService = {
|
||||||
bio: data.bio,
|
bio: data.bio,
|
||||||
preference: {
|
preference: {
|
||||||
upsert: {
|
upsert: {
|
||||||
|
where: { userId: user.id },
|
||||||
update: {
|
update: {
|
||||||
profession: data.profession,
|
profession: data.profession,
|
||||||
preferredBrand: data.preferredBrand,
|
|
||||||
preferredOS: data.preferredOS,
|
preferredOS: data.preferredOS,
|
||||||
budgetMin,
|
budgetMin,
|
||||||
budgetMax,
|
budgetMax,
|
||||||
|
brand: {
|
||||||
|
connectOrCreate: {
|
||||||
|
where: { name: data.preferredBrand },
|
||||||
|
create: { name: data.preferredBrand },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
profession: data.profession,
|
profession: data.profession,
|
||||||
preferredBrand: data.preferredBrand,
|
|
||||||
preferredOS: data.preferredOS,
|
preferredOS: data.preferredOS,
|
||||||
budgetMin,
|
budgetMin,
|
||||||
budgetMax,
|
budgetMax,
|
||||||
|
brand: {
|
||||||
|
connectOrCreate: {
|
||||||
|
where: { name: data.preferredBrand },
|
||||||
|
create: { name: data.preferredBrand },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
preference: true,
|
preference: {
|
||||||
|
include: { brand: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export const getReviewService = async (email: string) => {
|
||||||
createdAt: "asc",
|
createdAt: "asc",
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
reviewId: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
confidenceScore: true,
|
confidenceScore: true,
|
||||||
sentiment: true,
|
sentiment: true,
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,13 @@ export const getAnotherUserDataService = async (email: string) => {
|
||||||
bio: true,
|
bio: true,
|
||||||
preference: {
|
preference: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
userPreferenceId: true,
|
||||||
profession: true,
|
profession: true,
|
||||||
preferredBrand: true,
|
brand:{
|
||||||
|
select:{
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
},
|
||||||
preferredOS: true,
|
preferredOS: true,
|
||||||
budgetMin: true,
|
budgetMin: true,
|
||||||
budgetMax: true,
|
budgetMax: true,
|
||||||
|
|
@ -20,6 +24,5 @@ export const getAnotherUserDataService = async (email: string) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return userData;
|
return userData;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { LucideIcon } from "lucide-react";
|
import { LucideIcon } from "lucide-react";
|
||||||
import { OS, Profession, Sentiment, Brand } from "@prisma/client";
|
import { OS, Profession, Sentiment } from "@prisma/client";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { profileSchema } from "../app/validation/profile.schema";
|
import { profileSchema } from "../app/validation/profile.schema";
|
||||||
import { Session } from "next-auth";
|
import { Session } from "next-auth";
|
||||||
|
|
@ -18,7 +18,7 @@ export interface ModelDB {
|
||||||
export interface ProfileClientProps {
|
export interface ProfileClientProps {
|
||||||
name: string;
|
name: string;
|
||||||
bio?: string;
|
bio?: string;
|
||||||
preferenceBrand: string;
|
preferenceBrand: string
|
||||||
preferenceOS: string;
|
preferenceOS: string;
|
||||||
budgetMin: number;
|
budgetMin: number;
|
||||||
budgetMax: number;
|
budgetMax: number;
|
||||||
|
|
@ -130,6 +130,33 @@ export interface UseStatCardProps {
|
||||||
delay?: number;
|
delay?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export interface ReviewItem {
|
||||||
|
// reviewId: number;
|
||||||
|
// content: string;
|
||||||
|
// sentiment: Sentiment;
|
||||||
|
// confidenceScore: number;
|
||||||
|
// createdAt: string;
|
||||||
|
// keywords: string[];
|
||||||
|
// product: {
|
||||||
|
// name: string;
|
||||||
|
// brand?: Brand;
|
||||||
|
// } | null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface Brand {
|
||||||
|
brandId: number;
|
||||||
|
name: string;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
productId: number; // Pastikan sesuai dengan schema.prisma (productId bukan id)
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
brand: Brand | null; // <--- Ubah dari string ke Brand object
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReviewItem {
|
export interface ReviewItem {
|
||||||
id: number;
|
id: number;
|
||||||
content: string;
|
content: string;
|
||||||
|
|
@ -137,10 +164,7 @@ export interface ReviewItem {
|
||||||
confidenceScore: number;
|
confidenceScore: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
product: {
|
product: Product | null;
|
||||||
name: string;
|
|
||||||
brand?: string;
|
|
||||||
} | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiResponse {
|
export interface ApiResponse {
|
||||||
|
|
@ -307,14 +331,25 @@ export type ServerActionHandler<T, Args extends any[] = any[]> = (
|
||||||
...args: Args
|
...args: Args
|
||||||
) => Promise<T>;
|
) => Promise<T>;
|
||||||
|
|
||||||
|
// export type AnalysisData = {
|
||||||
|
// product?: {
|
||||||
|
// id: number;
|
||||||
|
// brand: string | null;
|
||||||
|
// _count?: {
|
||||||
|
// reviews: number;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
export type AnalysisData = {
|
export type AnalysisData = {
|
||||||
product?: {
|
analysisId: number;
|
||||||
id: number;
|
createdAt: string | Date;
|
||||||
brand: string | null;
|
product: {
|
||||||
_count?: {
|
productId: number;
|
||||||
reviews: number;
|
brandName: string | null;
|
||||||
};
|
name: string;
|
||||||
};
|
reviewCount: number;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BodyData = (req: Request, body: any) => Promise<NextResponse>;
|
export type BodyData = (req: Request, body: any) => Promise<NextResponse>;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue