diff --git a/lib/withAuth.ts b/lib/withAuth.ts index 2bd5bb4..dfaa3a2 100644 --- a/lib/withAuth.ts +++ b/lib/withAuth.ts @@ -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); diff --git a/prisma/migrations/20260306013640_fix_brand_relations/migration.sql b/prisma/migrations/20260306013640_fix_brand_relations/migration.sql new file mode 100644 index 0000000..0d5b5b9 --- /dev/null +++ b/prisma/migrations/20260306013640_fix_brand_relations/migration.sql @@ -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; diff --git a/prisma/migrations/20260306023434_fix_custom_id_relation/migration.sql b/prisma/migrations/20260306023434_fix_custom_id_relation/migration.sql new file mode 100644 index 0000000..ef4a517 --- /dev/null +++ b/prisma/migrations/20260306023434_fix_custom_id_relation/migration.sql @@ -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; diff --git a/prisma/migrations/20260306035117_fix_name_rows_type/migration.sql b/prisma/migrations/20260306035117_fix_name_rows_type/migration.sql new file mode 100644 index 0000000..23105d8 --- /dev/null +++ b/prisma/migrations/20260306035117_fix_name_rows_type/migration.sql @@ -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; diff --git a/prisma/migrations/20260306065823_fix_name_rows_contraint_value/migration.sql b/prisma/migrations/20260306065823_fix_name_rows_contraint_value/migration.sql new file mode 100644 index 0000000..f0959a1 --- /dev/null +++ b/prisma/migrations/20260306065823_fix_name_rows_contraint_value/migration.sql @@ -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"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1fb535c..17aa4de 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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") } diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 2b72140..8a0aa5a 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -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); diff --git a/src/app/api/user-metric/route.ts b/src/app/api/user-metric/route.ts new file mode 100644 index 0000000..addcab8 --- /dev/null +++ b/src/app/api/user-metric/route.ts @@ -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 }); +} diff --git a/src/app/dashboard/lib/actions.ts b/src/app/dashboard/lib/actions.ts index e433665..6450c11 100644 --- a/src/app/dashboard/lib/actions.ts +++ b/src/app/dashboard/lib/actions.ts @@ -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: [] }; } }); diff --git a/src/app/profile/lib/action.ts b/src/app/profile/lib/action.ts index f57ea2e..46824dd 100644 --- a/src/app/profile/lib/action.ts +++ b/src/app/profile/lib/action.ts @@ -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); diff --git a/src/app/validation/profile.schema.ts b/src/app/validation/profile.schema.ts index 8d40c7a..3c42200 100644 --- a/src/app/validation/profile.schema.ts +++ b/src/app/validation/profile.schema.ts @@ -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), diff --git a/src/components/dashboards/DashboardClient.tsx b/src/components/dashboards/DashboardClient.tsx index a297323..998fb32 100644 --- a/src/components/dashboards/DashboardClient.tsx +++ b/src/components/dashboards/DashboardClient.tsx @@ -9,7 +9,6 @@ import { MessageSquareText, Smile, Sparkles, - TrendingUp, } from "lucide-react"; import { StatCard } from "./StatCard"; import { ModelInfoSkeleton } from "../skeletons/ModelInfoSkeleton"; diff --git a/src/components/dashboards/ProfileCard.tsx b/src/components/dashboards/ProfileCard.tsx index f218600..f26fbf4 100644 --- a/src/components/dashboards/ProfileCard.tsx +++ b/src/components/dashboards/ProfileCard.tsx @@ -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 { diff --git a/src/components/dashboards/ProfileClient.tsx b/src/components/dashboards/ProfileClient.tsx index d842a84..0d785ab 100644 --- a/src/components/dashboards/ProfileClient.tsx +++ b/src/components/dashboards/ProfileClient.tsx @@ -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} diff --git a/src/components/dashboards/ReviewTable.tsx b/src/components/dashboards/ReviewTable.tsx index 1edf9c8..ad8d63e 100644 --- a/src/components/dashboards/ReviewTable.tsx +++ b/src/components/dashboards/ReviewTable.tsx @@ -88,7 +88,8 @@ export function ReviewTable() {