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 { NextResponse } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 0;
|
||||
|
||||
export function withAuth(handler: ApiHandler) {
|
||||
return async (req: Request, context: any) => {
|
||||
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
|
||||
}
|
||||
|
||||
enum Brand {
|
||||
enum BrandName {
|
||||
APPLE
|
||||
ASUS
|
||||
ACER
|
||||
|
|
@ -49,8 +49,7 @@ enum Profession {
|
|||
}
|
||||
|
||||
model Account {
|
||||
id Int @id @default(autoincrement())
|
||||
userId Int
|
||||
id Int @id @default(autoincrement())
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String
|
||||
|
|
@ -61,17 +60,23 @@ model Account {
|
|||
scope String?
|
||||
id_token String?
|
||||
session_state String?
|
||||
|
||||
userId Int @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
@@map("accounts")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id Int @id @default(autoincrement())
|
||||
sessionToken String @unique
|
||||
userId Int
|
||||
sessionToken String @unique @map("session_token")
|
||||
expires DateTime
|
||||
|
||||
userId Int @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("sessions")
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
|
|
@ -83,7 +88,7 @@ model VerificationToken {
|
|||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
id Int @id @default(autoincrement()) @map("userId")
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
|
|
@ -99,40 +104,50 @@ model User {
|
|||
analyses Analysis[]
|
||||
|
||||
preference UserPreference?
|
||||
review Review[]
|
||||
review Review[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model UserPreference {
|
||||
id Int @id @default(autoincrement())
|
||||
profession Profession?
|
||||
preferredOS OS?
|
||||
preferredBrand Brand?
|
||||
budgetMin Int?
|
||||
budgetMax Int?
|
||||
userPreferenceId Int @id @default(autoincrement())
|
||||
profession Profession?
|
||||
preferredOS OS?
|
||||
budgetMin Int?
|
||||
budgetMax Int?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
userId Int @unique
|
||||
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 {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
brand String?
|
||||
url String @unique
|
||||
image String?
|
||||
productId Int @id @default(autoincrement())
|
||||
name String
|
||||
url String @unique
|
||||
image String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
reviews Review[]
|
||||
analyses Analysis[]
|
||||
reviews Review[]
|
||||
metrics Metric[]
|
||||
|
||||
brandId Int?
|
||||
brand Brand? @relation(fields: [brandId], references: [brandId], map: "product_brand_fkey")
|
||||
|
||||
@@map("products")
|
||||
}
|
||||
|
||||
model Review {
|
||||
id Int @id @default(autoincrement())
|
||||
reviewId Int @id @default(autoincrement())
|
||||
content String
|
||||
sentiment Sentiment
|
||||
confidenceScore Float
|
||||
|
|
@ -142,53 +157,73 @@ model Review {
|
|||
updatedAt DateTime @updatedAt
|
||||
|
||||
productId Int
|
||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||||
product Product @relation(fields: [productId], references: [productId], onDelete: Cascade)
|
||||
|
||||
modelId Int?
|
||||
model Model? @relation(fields: [modelId], references: [id])
|
||||
|
||||
model Model? @relation(fields: [modelId], references: [modelId])
|
||||
|
||||
userId Int?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
|
||||
}
|
||||
|
||||
model Analysis {
|
||||
id 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)
|
||||
analysisId Int @id @default(autoincrement())
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
analyses Analysis[]
|
||||
reviews Review[]
|
||||
userId Int
|
||||
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({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
}),
|
||||
],
|
||||
debug: true,
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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 { formatBrandStats } from "@/src/services/brand.service";
|
||||
import { reportService } from "@/src/services/report.service";
|
||||
import { AnalysisData } from "@/src/types";
|
||||
|
||||
export const getClassificationReport = async () => {
|
||||
try {
|
||||
|
|
@ -20,13 +21,29 @@ export const getTotalBrandAnalysis = withActionAuth(async (session) => {
|
|||
try {
|
||||
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);
|
||||
|
||||
return { formattedBrands, userAnalysis };
|
||||
} catch (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) => {
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const profileSchema = z.object({
|
|||
name: z.string().min(2, "Nama minimal 2 karakter"),
|
||||
bio: z.string().min(10, "Bio minimal 10 karakter"),
|
||||
profession: professionEnum,
|
||||
preferredBrand: brandEnum,
|
||||
preferredBrand: z.string().min(2, "Merek laptop minimal 2 karakter"),
|
||||
preferredOS: osEnum,
|
||||
budgetMin: z.coerce.number().min(0),
|
||||
budgetMax: z.coerce.number().min(0),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
MessageSquareText,
|
||||
Smile,
|
||||
Sparkles,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
import { StatCard } from "./StatCard";
|
||||
import { ModelInfoSkeleton } from "../skeletons/ModelInfoSkeleton";
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import { Button } from "../ui/button";
|
|||
import { Separator } from "../ui/separator";
|
||||
import { formatRupiah, toTitleCase } from "@/src/utils/datas";
|
||||
import { brandItems, OSItems, professionItems } from "@/src/utils/const";
|
||||
import { useProfileClient } from "@/src/hooks/useProfileClient";
|
||||
import { ProfileModal } from "./ProfileModal";
|
||||
import { useProfileClient } from "@/src/hooks/useProfileClient";
|
||||
|
||||
export default function ProfileCard(props: ProfileClientProps) {
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default async function ProfileClient() {
|
|||
name={user?.name || ""}
|
||||
bio={user?.bio || "None"}
|
||||
profession={user?.preference?.profession || ""}
|
||||
preferenceBrand={user?.preference?.preferredBrand || ""}
|
||||
preferenceBrand={user?.preference?.brand?.name || ""}
|
||||
preferenceOS={user?.preference?.preferredOS || ""}
|
||||
budgetMax={user?.preference?.budgetMax || 0}
|
||||
budgetMin={user?.preference?.budgetMin || 0}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ export function ReviewTable() {
|
|||
<div className="flex flex-col gap-1.5">
|
||||
<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">
|
||||
{review.product?.brand || "Generic"}
|
||||
{/* Tambahkan .name di sini */}
|
||||
{review.product?.brand?.name || "Generic"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium leading-tight text-foreground line-clamp-2">
|
||||
|
|
|
|||
|
|
@ -10,9 +10,18 @@ import {
|
|||
} from "../services/analyze.service";
|
||||
import { analyzeSchema } from "../app/validation/analyze.schema"; // Sesuaikan path-nya
|
||||
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 interface AnalysisWithMetric {
|
||||
metric: {
|
||||
metricId: number;
|
||||
name: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const useAnalyseText = () => {
|
||||
const { data: session } = useSession();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -121,9 +130,19 @@ export const useAnalyseText = () => {
|
|||
reviews: res.data.reviews,
|
||||
}));
|
||||
|
||||
const aiResult = await getAIRecommendation({
|
||||
const metricIdValue = await getMetricId();
|
||||
|
||||
console.log("Payload to AI:", {
|
||||
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,
|
||||
metric_id: metricIdValue,
|
||||
});
|
||||
|
||||
setResult(aiResult);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export const useProfileClient = (props: ProfileClientProps) => {
|
|||
(newData.profession as Profession) || prev.preference.profession,
|
||||
|
||||
preferredBrand:
|
||||
(newData.preferredBrand as Brand) || prev.preference.preferredBrand,
|
||||
(newData.preferredBrand ) || prev.preference.preferredBrand,
|
||||
|
||||
preferredOS: (newData.preferredOS as OS) || prev.preference.preferredOS,
|
||||
|
||||
|
|
@ -74,7 +74,9 @@ export const useProfileClient = (props: ProfileClientProps) => {
|
|||
profession,
|
||||
} = preference;
|
||||
|
||||
const { brands } = brandFormat({ preferenceBrand });
|
||||
const { brands } = brandFormat({
|
||||
preferenceBrand: preferenceBrand as any,
|
||||
});
|
||||
|
||||
return {
|
||||
session,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const useReviewTable = (
|
|||
const filteredData = selectedBrand
|
||||
? data.filter(
|
||||
(review) =>
|
||||
review.product?.brand?.toLowerCase() ===
|
||||
review.product?.brand?.name.toLowerCase() ===
|
||||
selectedBrand.toLowerCase(),
|
||||
)
|
||||
: data;
|
||||
|
|
|
|||
|
|
@ -72,13 +72,13 @@ export const useSentiment = () => {
|
|||
let confidence: number;
|
||||
|
||||
if (positiveScore > negativeScore) {
|
||||
sentiment = "positif";
|
||||
sentiment = "POSITIVE";
|
||||
confidence = 0.75 + Math.random() * 0.2;
|
||||
} else if (negativeScore > positiveScore) {
|
||||
sentiment = "negatif";
|
||||
sentiment = "NEGATIVE";
|
||||
confidence = 0.75 + Math.random() * 0.2;
|
||||
} else {
|
||||
sentiment = "netral";
|
||||
sentiment = "NEUTRAL";
|
||||
confidence = 0.6 + Math.random() * 0.2;
|
||||
}
|
||||
|
||||
|
|
@ -92,21 +92,21 @@ export const useSentiment = () => {
|
|||
|
||||
const getSentimentDisplay = (sentiment: AnalysisResult["sentiment"]) => {
|
||||
const config = {
|
||||
positif: {
|
||||
POSITIVE: {
|
||||
icon: ThumbsUp,
|
||||
label: "Positif",
|
||||
bgClass: "bg-sentiment-positive-light",
|
||||
textClass: "text-sentiment-positive",
|
||||
borderClass: "border-sentiment-positive/30",
|
||||
},
|
||||
negatif: {
|
||||
NEGATIVE: {
|
||||
icon: ThumbsDown,
|
||||
label: "Negatif",
|
||||
bgClass: "bg-sentiment-negative-light",
|
||||
textClass: "text-sentiment-negative",
|
||||
borderClass: "border-sentiment-negative/30",
|
||||
},
|
||||
netral: {
|
||||
NEUTRAL: {
|
||||
icon: Minus,
|
||||
label: "Netral",
|
||||
bgClass: "bg-sentiment-neutral-light",
|
||||
|
|
|
|||
|
|
@ -19,22 +19,6 @@ export const scrapeProduct = async (url: string) => {
|
|||
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) => {
|
||||
const userAnalyses = await prisma.analysis.findMany({
|
||||
where: {
|
||||
|
|
@ -43,16 +27,24 @@ export const getAnalysisData = async (email: string) => {
|
|||
},
|
||||
},
|
||||
include: {
|
||||
product: {
|
||||
metric: {
|
||||
select: {
|
||||
id: true,
|
||||
brand: true,
|
||||
_count: {
|
||||
product: {
|
||||
select: {
|
||||
reviews: {
|
||||
where: {
|
||||
user: {
|
||||
email: email,
|
||||
productId: true,
|
||||
brand: {
|
||||
select:{
|
||||
name: true
|
||||
}
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
reviews: {
|
||||
where: {
|
||||
user: {
|
||||
email: email,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -67,17 +59,28 @@ export const getAnalysisData = async (email: string) => {
|
|||
|
||||
export const getAIRecommendation = async (payload: {
|
||||
user_email: string;
|
||||
metric_id: number | 1;
|
||||
candidates: { name: string; url: string; reviews: string[] }[];
|
||||
}): Promise<AIRecommendationResponse> => {
|
||||
console.log("Fetching to FastAPI...");
|
||||
const aiRes = await fetch("http://localhost:8000/recommend", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!aiRes.ok) {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ export const formatBrandStats = (userAnalysis: AnalysisData[]) => {
|
|||
|
||||
const brandCounts = userAnalysis.reduce(
|
||||
(acc: Record<string, number>, analysis) => {
|
||||
const productId = analysis.product?.id;
|
||||
const rawBrand = analysis.product?.brand || "Unknown";
|
||||
const reviewCount = analysis.product?._count?.reviews || 0;
|
||||
const productId = analysis.product?.productId;
|
||||
const rawBrand = analysis.product?.brandName || "Unknown";
|
||||
const reviewCount = analysis.product?.reviewCount || 0;
|
||||
|
||||
if (productId && countedProductIds.has(productId)) {
|
||||
return acc;
|
||||
|
|
@ -20,7 +20,7 @@ export const formatBrandStats = (userAnalysis: AnalysisData[]) => {
|
|||
const formattedBrand = rawBrand
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
.replace(/\b\w/g, (char: any) => char.toUpperCase());
|
||||
|
||||
if (!acc[formattedBrand]) {
|
||||
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;
|
||||
|
||||
const totalProducts = await prisma.analysis.count({
|
||||
const totalProducts = await prisma.metric.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
analysis: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ export const userService = {
|
|||
include: {
|
||||
preference: {
|
||||
select: {
|
||||
preferredBrand: true,
|
||||
brand: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
preferredOS: true,
|
||||
profession: true,
|
||||
budgetMax: true,
|
||||
|
|
@ -54,25 +58,38 @@ export const userService = {
|
|||
bio: data.bio,
|
||||
preference: {
|
||||
upsert: {
|
||||
where: { userId: user.id },
|
||||
update: {
|
||||
profession: data.profession,
|
||||
preferredBrand: data.preferredBrand,
|
||||
preferredOS: data.preferredOS,
|
||||
budgetMin,
|
||||
budgetMax,
|
||||
brand: {
|
||||
connectOrCreate: {
|
||||
where: { name: data.preferredBrand },
|
||||
create: { name: data.preferredBrand },
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
profession: data.profession,
|
||||
preferredBrand: data.preferredBrand,
|
||||
preferredOS: data.preferredOS,
|
||||
budgetMin,
|
||||
budgetMax,
|
||||
brand: {
|
||||
connectOrCreate: {
|
||||
where: { name: data.preferredBrand },
|
||||
create: { name: data.preferredBrand },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
preference: true,
|
||||
preference: {
|
||||
include: { brand: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export const getReviewService = async (email: string) => {
|
|||
createdAt: "asc",
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
reviewId: true,
|
||||
createdAt: true,
|
||||
confidenceScore: true,
|
||||
sentiment: true,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ export const getAnotherUserDataService = async (email: string) => {
|
|||
bio: true,
|
||||
preference: {
|
||||
select: {
|
||||
id: true,
|
||||
userPreferenceId: true,
|
||||
profession: true,
|
||||
preferredBrand: true,
|
||||
brand:{
|
||||
select:{
|
||||
name: true
|
||||
}
|
||||
},
|
||||
preferredOS: true,
|
||||
budgetMin: true,
|
||||
budgetMax: true,
|
||||
|
|
@ -20,6 +24,5 @@ export const getAnotherUserDataService = async (email: string) => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
return userData;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 { profileSchema } from "../app/validation/profile.schema";
|
||||
import { Session } from "next-auth";
|
||||
|
|
@ -18,7 +18,7 @@ export interface ModelDB {
|
|||
export interface ProfileClientProps {
|
||||
name: string;
|
||||
bio?: string;
|
||||
preferenceBrand: string;
|
||||
preferenceBrand: string
|
||||
preferenceOS: string;
|
||||
budgetMin: number;
|
||||
budgetMax: number;
|
||||
|
|
@ -130,6 +130,33 @@ export interface UseStatCardProps {
|
|||
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 {
|
||||
id: number;
|
||||
content: string;
|
||||
|
|
@ -137,10 +164,7 @@ export interface ReviewItem {
|
|||
confidenceScore: number;
|
||||
createdAt: string;
|
||||
keywords: string[];
|
||||
product: {
|
||||
name: string;
|
||||
brand?: string;
|
||||
} | null;
|
||||
product: Product | null;
|
||||
}
|
||||
|
||||
export interface ApiResponse {
|
||||
|
|
@ -307,14 +331,25 @@ export type ServerActionHandler<T, Args extends any[] = any[]> = (
|
|||
...args: Args
|
||||
) => Promise<T>;
|
||||
|
||||
// export type AnalysisData = {
|
||||
// product?: {
|
||||
// id: number;
|
||||
// brand: string | null;
|
||||
// _count?: {
|
||||
// reviews: number;
|
||||
// };
|
||||
// };
|
||||
// };
|
||||
|
||||
export type AnalysisData = {
|
||||
product?: {
|
||||
id: number;
|
||||
brand: string | null;
|
||||
_count?: {
|
||||
reviews: number;
|
||||
};
|
||||
};
|
||||
analysisId: number;
|
||||
createdAt: string | Date;
|
||||
product: {
|
||||
productId: number;
|
||||
brandName: string | null;
|
||||
name: string;
|
||||
reviewCount: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type BodyData = (req: Request, body: any) => Promise<NextResponse>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue