refactor: optimize business logic into service layer.

This commit is contained in:
Mahen 2026-02-22 07:36:17 +07:00
parent cac55f1805
commit c42e719381
12 changed files with 388 additions and 321 deletions

View File

@ -1,5 +1,5 @@
import { authOptions } from "@/src/app/api/auth/[...nextauth]/route";
import { ApiHandler } from "@/src/types";
import { ApiHandler, ServerActionHandler } from "@/src/types";
import { getServerSession } from "next-auth";
import { NextResponse } from "next/server";
@ -17,3 +17,18 @@ export function withAuth(handler: ApiHandler) {
return handler(req, context, session);
};
}
export function withActionAuth<T, Args extends any[] = any[]>(
handler: ServerActionHandler<T, Args>,
) {
return async (...args: Args): Promise<T | null> => {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
console.log("Unauthorized: User belum login");
return null;
}
return handler(session, ...args);
};
}

14
lib/withBody.ts Normal file
View File

@ -0,0 +1,14 @@
import { BodyData } from "@/src/types";
import { NextResponse } from "next/server";
export function withBody(handler: BodyData) {
return async (req: Request) => {
const body = await req.json();
const { url } = body;
if (!url || !url.includes("tokopedia.com")) {
return NextResponse.json({ error: "URL tidak valid" }, { status: 400 });
}
return handler(req, body);
};
}

View File

@ -1,16 +1,12 @@
import { withBody } from "@/lib/withBody";
import { scrapeTokopediaProduct } from "@/src/services/scrape.service";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
export const POST = withBody(async (_req, body) => {
try {
const body = await request.json();
const { url } = body;
const result = await scrapeTokopediaProduct(body.url);
if (!url || !url.includes("tokopedia.com")) {
return NextResponse.json({ error: "URL tidak valid" }, { status: 400 });
}
const result = await scrapeTokopediaProduct(url);
console.log(result);
return NextResponse.json({
success: true,
@ -19,4 +15,4 @@ export async function POST(request: Request) {
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
});

View File

@ -1,29 +1,13 @@
"use server";
import prisma from "@/lib/prisma";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import { authOptions } from "../../api/auth/[...nextauth]/route";
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";
export const getClassificationReport = async () => {
try {
const response = await prisma.model.findMany({
select: {
modelName: true,
description: true,
accuracy: true,
macroF1: true,
f1Negative: true,
f1Neutral: true,
isActive: true,
},
orderBy: {
createdAt: "desc",
},
});
if (!response || response.length === 0) {
return notFound();
}
const response = await reportService();
return response;
} catch (error) {
@ -32,85 +16,17 @@ export const getClassificationReport = async () => {
}
};
export const getTotalBrandAnalysis = async () => {
export const getTotalBrandAnalysis = withActionAuth(async (session) => {
try {
const session = await getServerSession(authOptions);
const email = session.user?.email as string;
if (!session?.user?.email) {
console.log("User belum login");
return null;
}
const userAnalysis = await getAnalysisData(email);
const userAnalyses = await prisma.analysis.findMany({
where: {
user: {
email: session.user.email,
},
},
include: {
product: {
select: {
id: true,
brand: true,
_count: {
select: {
reviews: {
where: {
user: {
email: session.user.email,
},
},
},
},
},
},
},
},
});
const formattedBrands = formatBrandStats(userAnalysis);
const countedProductIds = new Set<number>();
const brandCounts = userAnalyses.reduce(
(acc: Record<string, number>, analysis) => {
const productId = analysis.product?.id;
const rawBrand = analysis.product?.brand || "Unknown";
const reviewCount = analysis.product?._count?.reviews || 0;
if (productId && countedProductIds.has(productId)) {
return acc;
}
if (productId) {
countedProductIds.add(productId);
}
const formattedBrand = rawBrand
.trim()
.toLowerCase()
.replace(/\b\w/g, (char) => char.toUpperCase());
if (!acc[formattedBrand]) {
acc[formattedBrand] = 0;
}
acc[formattedBrand] += reviewCount;
return acc;
},
{},
);
const formattedBrands = Object.entries(brandCounts).map(
([name, count]) => ({
name,
count,
}),
);
formattedBrands.sort((a, b) => b.count - a.count);
return formattedBrands;
return { formattedBrands, userAnalysis };
} catch (error) {
console.error("Gagal mengambil data review:", error);
return [];
}
};
});

View File

@ -1,193 +1,193 @@
// Sample data for the sentiment analysis dashboard
// Based on Tokopedia laptop reviews analysis
// // Sample data for the sentiment analysis dashboard
// // Based on Tokopedia laptop reviews analysis
import { CheckCircle2, Cpu, Target, Zap } from "lucide-react";
// import { CheckCircle2, Cpu, Target, Zap } from "lucide-react";
export const sentimentDistribution = [
{ name: "Positif", value: 2456, color: "hsl(158, 64%, 42%)" },
{ name: "Negatif", value: 607, color: "hsl(0, 72%, 51%)" },
{ name: "Netral", value: 382, color: "hsl(43, 74%, 49%)" },
];
// export const sentimentDistribution = [
// { name: "Positif", value: 2456, color: "hsl(158, 64%, 42%)" },
// { name: "Negatif", value: 607, color: "hsl(0, 72%, 51%)" },
// { name: "Netral", value: 382, color: "hsl(43, 74%, 49%)" },
// ];
export const trendData = [
{ date: "Jan", positif: 580, negatif: 220, netral: 150 },
{ date: "Feb", positif: 620, negatif: 245, netral: 175 },
{ date: "Mar", positif: 750, negatif: 280, netral: 190 },
{ date: "Apr", positif: 690, negatif: 310, netral: 165 },
{ date: "Mei", positif: 820, negatif: 265, netral: 210 },
{ date: "Jun", positif: 780, negatif: 240, netral: 195 },
{ date: "Jul", positif: 850, negatif: 290, netral: 220 },
{ date: "Agu", positif: 720, negatif: 255, netral: 180 },
{ date: "Sep", positif: 680, negatif: 230, netral: 165 },
{ date: "Okt", positif: 540, negatif: 195, netral: 145 },
{ date: "Nov", positif: 520, negatif: 175, netral: 150 },
{ date: "Des", positif: 474, negatif: 140, netral: 136 },
];
// export const trendData = [
// { date: "Jan", positif: 580, negatif: 220, netral: 150 },
// { date: "Feb", positif: 620, negatif: 245, netral: 175 },
// { date: "Mar", positif: 750, negatif: 280, netral: 190 },
// { date: "Apr", positif: 690, negatif: 310, netral: 165 },
// { date: "Mei", positif: 820, negatif: 265, netral: 210 },
// { date: "Jun", positif: 780, negatif: 240, netral: 195 },
// { date: "Jul", positif: 850, negatif: 290, netral: 220 },
// { date: "Agu", positif: 720, negatif: 255, netral: 180 },
// { date: "Sep", positif: 680, negatif: 230, netral: 165 },
// { date: "Okt", positif: 540, negatif: 195, netral: 145 },
// { date: "Nov", positif: 520, negatif: 175, netral: 150 },
// { date: "Des", positif: 474, negatif: 140, netral: 136 },
// ];
export const wordCloudData = [
{ text: "barang", value: 1086, sentiment: "positive" as const },
{ text: "sesuai", value: 570, sentiment: "positive" as const },
{ text: "bagus", value: 523, sentiment: "positive" as const },
{ text: "seller", value: 478, sentiment: "positive" as const },
{ text: "cepat", value: 471, sentiment: "positive" as const },
{ text: "aman", value: 453, sentiment: "positive" as const },
{ text: "laptop", value: 451, sentiment: "positive" as const },
{ text: "kirim", value: 449, sentiment: "positive" as const },
{ text: "baik", value: 396, sentiment: "positive" as const },
{ text: "mulus", value: 364, sentiment: "positive" as const },
{ text: "barang", value: 181, sentiment: "negative" as const },
{ text: "kirim", value: 177, sentiment: "negative" as const },
{ text: "laptop", value: 129, sentiment: "negative" as const },
{ text: "beli", value: 124, sentiment: "negative" as const },
{ text: "lebih", value: 72, sentiment: "negative" as const },
{ text: "jual", value: 62, sentiment: "negative" as const },
{ text: "baru", value: 60, sentiment: "negative" as const },
{ text: "lalu", value: 59, sentiment: "negative" as const },
{ text: "sesuai", value: 56, sentiment: "negative" as const },
{ text: "tahun", value: 56, sentiment: "negative" as const },
{ text: "kirim", value: 106, sentiment: "neutral" as const },
{ text: "barang", value: 96, sentiment: "neutral" as const },
{ text: "laptop", value: 78, sentiment: "neutral" as const },
{ text: "sesuai", value: 48, sentiment: "neutral" as const },
{ text: "bagus", value: 47, sentiment: "neutral" as const },
{ text: "lebih", value: 45, sentiment: "neutral" as const },
{ text: "kurang", value: 44, sentiment: "neutral" as const },
{ text: "beli", value: 44, sentiment: "neutral" as const },
{ text: "baik", value: 33, sentiment: "neutral" as const },
{ text: "baru", value: 32, sentiment: "neutral" as const },
];
// export const wordCloudData = [
// { text: "barang", value: 1086, sentiment: "positive" as const },
// { text: "sesuai", value: 570, sentiment: "positive" as const },
// { text: "bagus", value: 523, sentiment: "positive" as const },
// { text: "seller", value: 478, sentiment: "positive" as const },
// { text: "cepat", value: 471, sentiment: "positive" as const },
// { text: "aman", value: 453, sentiment: "positive" as const },
// { text: "laptop", value: 451, sentiment: "positive" as const },
// { text: "kirim", value: 449, sentiment: "positive" as const },
// { text: "baik", value: 396, sentiment: "positive" as const },
// { text: "mulus", value: 364, sentiment: "positive" as const },
// { text: "barang", value: 181, sentiment: "negative" as const },
// { text: "kirim", value: 177, sentiment: "negative" as const },
// { text: "laptop", value: 129, sentiment: "negative" as const },
// { text: "beli", value: 124, sentiment: "negative" as const },
// { text: "lebih", value: 72, sentiment: "negative" as const },
// { text: "jual", value: 62, sentiment: "negative" as const },
// { text: "baru", value: 60, sentiment: "negative" as const },
// { text: "lalu", value: 59, sentiment: "negative" as const },
// { text: "sesuai", value: 56, sentiment: "negative" as const },
// { text: "tahun", value: 56, sentiment: "negative" as const },
// { text: "kirim", value: 106, sentiment: "neutral" as const },
// { text: "barang", value: 96, sentiment: "neutral" as const },
// { text: "laptop", value: 78, sentiment: "neutral" as const },
// { text: "sesuai", value: 48, sentiment: "neutral" as const },
// { text: "bagus", value: 47, sentiment: "neutral" as const },
// { text: "lebih", value: 45, sentiment: "neutral" as const },
// { text: "kurang", value: 44, sentiment: "neutral" as const },
// { text: "beli", value: 44, sentiment: "neutral" as const },
// { text: "baik", value: 33, sentiment: "neutral" as const },
// { text: "baru", value: 32, sentiment: "neutral" as const },
// ];
export const brandData = [
{ name: "ASUS", count: 3245 },
{ name: "Lenovo", count: 2890 },
{ name: "HP", count: 2456 },
{ name: "Acer", count: 2134 },
{ name: "Dell", count: 1725 },
];
// export const brandData = [
// { name: "ASUS", count: 3245 },
// { name: "Lenovo", count: 2890 },
// { name: "HP", count: 2456 },
// { name: "Acer", count: 2134 },
// { name: "Dell", count: 1725 },
// ];
export const reviewData = [
{
id: "1",
product: "ASUS VivoBook 15 X515EA Intel Core i5-1135G7",
brand: "ASUS",
review:
"Laptop sangat bagus, performa cepat untuk kerja kantoran. Layar jernih dan keyboard nyaman dipakai mengetik seharian. Pengiriman juga cepat dan aman.",
rating: 5,
sentiment: "positif" as const,
date: "2 hari yang lalu",
confidence: 0.945,
},
{
id: "2",
product: "Lenovo IdeaPad Slim 3 AMD Ryzen 5 5500U",
brand: "Lenovo",
review:
"Produk sesuai deskripsi. Build quality oke, performa lancar untuk multitasking ringan. Baterai awet bisa 6-7 jam pemakaian normal.",
rating: 4,
sentiment: "positif" as const,
date: "3 hari yang lalu",
confidence: 0.887,
},
{
id: "3",
product: "HP 14s-dq5001TU Intel Core i5-1235U",
brand: "HP",
review:
"Kecewa dengan produk ini. Baru dipakai 2 minggu sudah sering hang dan restart sendiri. Kipas juga berisik sekali padahal hanya buka browser.",
rating: 2,
sentiment: "negatif" as const,
date: "4 hari yang lalu",
confidence: 0.923,
},
{
id: "4",
product: "Acer Aspire 5 A515-57 Intel Core i5-1235U",
brand: "Acer",
review:
"Laptop oke lah untuk harga segini. Tidak terlalu cepat tapi juga tidak lemot. Cocok untuk mahasiswa dengan budget terbatas.",
rating: 3,
sentiment: "netral" as const,
date: "5 hari yang lalu",
confidence: 0.812,
},
{
id: "5",
product: "Dell Inspiron 15 3520 Intel Core i3-1215U",
brand: "Dell",
review:
"Sangat puas dengan pembelian ini! Laptop premium dengan harga terjangkau. Build quality solid, keyboard backlit, dan layar anti-glare sangat membantu.",
rating: 5,
sentiment: "positif" as const,
date: "1 minggu yang lalu",
confidence: 0.956,
},
{
id: "6",
product: "ASUS TUF Gaming F15 FX506HF RTX 2050",
brand: "ASUS",
review:
"Gaming laptop yang worth it! Main game AAA lancar di medium-high setting. Thermal management bagus, tidak terlalu panas saat gaming marathon.",
rating: 5,
sentiment: "positif" as const,
date: "1 minggu yang lalu",
confidence: 0.934,
},
{
id: "7",
product: "Lenovo V14 G3 AMD Ryzen 3 5300U",
brand: "Lenovo",
review:
"Laptop datang dalam kondisi rusak, layar ada garis horizontal. Sudah komplain ke seller tapi respon lambat. Sangat mengecewakan.",
rating: 1,
sentiment: "negatif" as const,
date: "1 minggu yang lalu",
confidence: 0.967,
},
{
id: "8",
product: "HP Pavilion 14-dv2045TX Intel Core i5",
brand: "HP",
review:
"Desain elegan dan performa mumpuni. Cocok untuk pekerja mobile yang butuh laptop stylish. Speaker B&O juga keren suaranya.",
rating: 4,
sentiment: "positif" as const,
date: "2 minggu yang lalu",
confidence: 0.891,
},
];
// export const reviewData = [
// {
// id: "1",
// product: "ASUS VivoBook 15 X515EA Intel Core i5-1135G7",
// brand: "ASUS",
// review:
// "Laptop sangat bagus, performa cepat untuk kerja kantoran. Layar jernih dan keyboard nyaman dipakai mengetik seharian. Pengiriman juga cepat dan aman.",
// rating: 5,
// sentiment: "positif" as const,
// date: "2 hari yang lalu",
// confidence: 0.945,
// },
// {
// id: "2",
// product: "Lenovo IdeaPad Slim 3 AMD Ryzen 5 5500U",
// brand: "Lenovo",
// review:
// "Produk sesuai deskripsi. Build quality oke, performa lancar untuk multitasking ringan. Baterai awet bisa 6-7 jam pemakaian normal.",
// rating: 4,
// sentiment: "positif" as const,
// date: "3 hari yang lalu",
// confidence: 0.887,
// },
// {
// id: "3",
// product: "HP 14s-dq5001TU Intel Core i5-1235U",
// brand: "HP",
// review:
// "Kecewa dengan produk ini. Baru dipakai 2 minggu sudah sering hang dan restart sendiri. Kipas juga berisik sekali padahal hanya buka browser.",
// rating: 2,
// sentiment: "negatif" as const,
// date: "4 hari yang lalu",
// confidence: 0.923,
// },
// {
// id: "4",
// product: "Acer Aspire 5 A515-57 Intel Core i5-1235U",
// brand: "Acer",
// review:
// "Laptop oke lah untuk harga segini. Tidak terlalu cepat tapi juga tidak lemot. Cocok untuk mahasiswa dengan budget terbatas.",
// rating: 3,
// sentiment: "netral" as const,
// date: "5 hari yang lalu",
// confidence: 0.812,
// },
// {
// id: "5",
// product: "Dell Inspiron 15 3520 Intel Core i3-1215U",
// brand: "Dell",
// review:
// "Sangat puas dengan pembelian ini! Laptop premium dengan harga terjangkau. Build quality solid, keyboard backlit, dan layar anti-glare sangat membantu.",
// rating: 5,
// sentiment: "positif" as const,
// date: "1 minggu yang lalu",
// confidence: 0.956,
// },
// {
// id: "6",
// product: "ASUS TUF Gaming F15 FX506HF RTX 2050",
// brand: "ASUS",
// review:
// "Gaming laptop yang worth it! Main game AAA lancar di medium-high setting. Thermal management bagus, tidak terlalu panas saat gaming marathon.",
// rating: 5,
// sentiment: "positif" as const,
// date: "1 minggu yang lalu",
// confidence: 0.934,
// },
// {
// id: "7",
// product: "Lenovo V14 G3 AMD Ryzen 3 5300U",
// brand: "Lenovo",
// review:
// "Laptop datang dalam kondisi rusak, layar ada garis horizontal. Sudah komplain ke seller tapi respon lambat. Sangat mengecewakan.",
// rating: 1,
// sentiment: "negatif" as const,
// date: "1 minggu yang lalu",
// confidence: 0.967,
// },
// {
// id: "8",
// product: "HP Pavilion 14-dv2045TX Intel Core i5",
// brand: "HP",
// review:
// "Desain elegan dan performa mumpuni. Cocok untuk pekerja mobile yang butuh laptop stylish. Speaker B&O juga keren suaranya.",
// rating: 4,
// sentiment: "positif" as const,
// date: "2 minggu yang lalu",
// confidence: 0.891,
// },
// ];
export const modelData = {
baseline: {
name: "Model XGBoost (Baseline)",
metrics: [
{ label: "Accuracy", value: "80.0%", icon: Target },
{ label: "Macro F1-Score", value: "56.0%", icon: Cpu },
{ label: "F1-Negatif", value: "61.0%", icon: CheckCircle2 },
{ label: "F1-Netral", value: "16.0%", icon: Zap },
],
description:
"Model awal menggunakan parameter default XGBoost (learning_rate=0.3, max_depth=6) pada dataset yang tidak seimbang.",
},
tuned: {
name: "Model XGBoost (Tuned)",
metrics: [
{ label: "Accuracy", value: "81.0%", icon: Target },
{ label: "Macro F1-Score", value: "58.0%", icon: Cpu },
{ label: "F1-Negatif", value: "65.0%", icon: CheckCircle2 },
{ label: "F1-Netral", value: "17.0%", icon: Zap },
],
description:
"Model dengan optimasi Hyperparameter menggunakan Grid Search untuk mencari kombinasi learning_rate dan max_depth terbaik.",
},
optimized: {
name: "Model XGBoost (Optimized)",
metrics: [
{ label: "Accuracy", value: "82.0%", icon: Target },
{ label: "Macro F1-Score", value: "61.0%", icon: Cpu },
{ label: "F1-Negatif", value: "65.0%", icon: CheckCircle2 },
{ label: "F1-Netral", value: "27.0%", icon: Zap },
],
description:
"Model final menggunakan teknik SMOTE untuk menyeimbangkan kelas, seleksi fitur Chi-Square, dan optimasi Grid Search.",
},
};
// export const modelData = {
// baseline: {
// name: "Model XGBoost (Baseline)",
// metrics: [
// { label: "Accuracy", value: "80.0%", icon: Target },
// { label: "Macro F1-Score", value: "56.0%", icon: Cpu },
// { label: "F1-Negatif", value: "61.0%", icon: CheckCircle2 },
// { label: "F1-Netral", value: "16.0%", icon: Zap },
// ],
// description:
// "Model awal menggunakan parameter default XGBoost (learning_rate=0.3, max_depth=6) pada dataset yang tidak seimbang.",
// },
// tuned: {
// name: "Model XGBoost (Tuned)",
// metrics: [
// { label: "Accuracy", value: "81.0%", icon: Target },
// { label: "Macro F1-Score", value: "58.0%", icon: Cpu },
// { label: "F1-Negatif", value: "65.0%", icon: CheckCircle2 },
// { label: "F1-Netral", value: "17.0%", icon: Zap },
// ],
// description:
// "Model dengan optimasi Hyperparameter menggunakan Grid Search untuk mencari kombinasi learning_rate dan max_depth terbaik.",
// },
// optimized: {
// name: "Model XGBoost (Optimized)",
// metrics: [
// { label: "Accuracy", value: "82.0%", icon: Target },
// { label: "Macro F1-Score", value: "61.0%", icon: Cpu },
// { label: "F1-Negatif", value: "65.0%", icon: CheckCircle2 },
// { label: "F1-Netral", value: "27.0%", icon: Zap },
// ],
// description:
// "Model final menggunakan teknik SMOTE untuk menyeimbangkan kelas, seleksi fitur Chi-Square, dan optimasi Grid Search.",
// },
// };

View File

@ -1,37 +1,17 @@
"use server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../../api/auth/[...nextauth]/route";
import prisma from "@/lib/prisma";
export const getAnotherUserData = async () => {
import { withActionAuth } from "@/lib/withAuth";
import { getAnotherUserDataService } from "@/src/services/users.service";
export const getAnotherUserData = withActionAuth(async (session) => {
try {
const session = await getServerSession(authOptions);
const email = (await session.user?.email) as string;
if (!session?.user?.email) return null;
const userData = await prisma.user.findUnique({
where: {
email: session.user.email,
},
select: {
name: true,
bio: true,
preference: {
select: {
id: true,
profession: true,
preferredBrand: true,
preferredOS: true,
budgetMin: true,
budgetMax: true,
},
},
},
});
const userData = await getAnotherUserDataService(email);
return userData;
} catch (error) {
console.error("Error fetching user data:", error);
return null;
}
};
});

View File

@ -11,8 +11,10 @@ export const useBrandFilter = () => {
const fetchBrands = async () => {
try {
const data = await getTotalBrandAnalysis();
if (data) {
setBrands(data);
if (data && "formattedBrands" in data) {
setBrands(data.formattedBrands);
} else {
setBrands([]);
}
} catch (error) {
console.error("Gagal memuat filter brand", error);

View File

@ -1,3 +1,5 @@
import prisma from "@/lib/prisma";
export const scrapeProduct = async (url: string) => {
const res = await fetch("/api/scrape", {
method: "POST",
@ -31,3 +33,34 @@ export const getAIRecommendation = async (payload: {
return await aiRes.json();
};
export const getAnalysisData = async (email: string) => {
const userAnalyses = await prisma.analysis.findMany({
where: {
user: {
email: email,
},
},
include: {
product: {
select: {
id: true,
brand: true,
_count: {
select: {
reviews: {
where: {
user: {
email: email,
},
},
},
},
},
},
},
},
});
return userAnalyses;
};

View File

@ -0,0 +1,44 @@
import { AnalysisData } from "../types";
export const formatBrandStats = (userAnalysis: AnalysisData[]) => {
const countedProductIds = new Set<number>();
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;
if (productId && countedProductIds.has(productId)) {
return acc;
}
if (productId) {
countedProductIds.add(productId);
}
const formattedBrand = rawBrand
.trim()
.toLowerCase()
.replace(/\b\w/g, (char) => char.toUpperCase());
if (!acc[formattedBrand]) {
acc[formattedBrand] = 0;
}
acc[formattedBrand] += reviewCount;
return acc;
},
{},
);
const formattedBrands = Object.entries(brandCounts).map(([name, count]) => ({
name,
count,
}));
formattedBrands.sort((a, b) => b.count - a.count);
return formattedBrands;
};

View File

@ -0,0 +1,25 @@
import prisma from "@/lib/prisma";
import { notFound } from "next/navigation";
export const reportService = async () => {
const response = await prisma.model.findMany({
select: {
modelName: true,
description: true,
accuracy: true,
macroF1: true,
f1Negative: true,
f1Neutral: true,
isActive: true,
},
orderBy: {
createdAt: "desc",
},
});
if (!response || response.length === 0) {
return notFound();
}
return response;
};

View File

@ -0,0 +1,25 @@
import prisma from "@/lib/prisma";
export const getAnotherUserDataService = async (email: string) => {
const userData = await prisma.user.findUnique({
where: {
email: email,
},
select: {
name: true,
bio: true,
preference: {
select: {
id: true,
profession: true,
preferredBrand: true,
preferredOS: true,
budgetMin: true,
budgetMax: true,
},
},
},
});
return userData;
};

View File

@ -268,3 +268,20 @@ export type WordCloudReview = {
keywords: string[];
sentiment: Sentiment;
};
export type ServerActionHandler<T, Args extends any[] = any[]> = (
session: Session,
...args: Args
) => Promise<T>;
export type AnalysisData = {
product?: {
id: number;
brand: string | null;
_count?: {
reviews: number;
};
};
};
export type BodyData = (req: Request, body: any) => Promise<NextResponse>;