I’m facing the issue that my webcrawler doesn’t put the data in the right structure to put it into my database. I don’t see the difference between the object I provide to specialFeatures and the object prisma wants.
This is the extractedValue I pass to create: extractedValue { specialFeatures: [ { feature: ‘AMD EXPO’ } ] }
logs
Fehler beim Einfügen in die Datenbank: PrismaClientValidationError:
Invalid `prisma.ram.create()` invocation:
{
data: {
name: "Corsair Vengeance RGB grau DIMM Kit 32GB, DDR5-6000, CL30-36-36-76, on-die ECC",
manufacturer: "Corsair",
popularity: 0,
releaseDate: new Date("2024-09-16T07:08:31.771Z"),
formFactor: "DIMM",
technology: "DDR5",
pinCount: 288,
eccSupportType: [
"on_die_ECC"
],
transmissionSpeed_MTs: 6000,
memoryClock_MHz: 375,
moduleCount: 2,
capacityPerModule_Gb: 16,
totalCapacity_Gb: 0,
casLatency: 30,
rowToColumnDelay: 36,
rowPrechargeTime: 36,
height_mm: 44,
caseRam: "Heatspreader",
specialFeatures: [
{
feature: "AMD EXPO"
}
],
~~~~~~~~~~~~~~~~~~~~~~~
warrantyMonths: 24
}
}
Argument `specialFeatures`: Invalid value provided. Expected SpecialFeaturesCreateNestedManyWithoutRamInput or SpecialFeaturesUncheckedCreateNestedManyWithoutRamInput, provided (Object).
at An (C:UsersenianDesktopUTP-ProjektUTPnode_modules@prismaclientruntimelibrary.js:114:7526)
at _n.handleRequestError (C:UsersenianDesktopUTP-ProjektUTPnode_modules@prismaclientruntimelibrary.js:121:7392)
at _n.handleAndLogRequestError (C:UsersenianDesktopUTP-ProjektUTPnode_modules@prismaclientruntimelibrary.js:121:7057)
at _n.request (C:UsersenianDesktopUTP-ProjektUTPnode_modules@prismaclientruntimelibrary.js:121:6741)
at async l (C:UsersenianDesktopUTP-ProjektUTPnode_modules@prismaclientruntimelibrary.js:130:9355)
at async $$ACTION_0 (webpack-internal:///(rsc)/./actions/save-component-to-db.ts:85:13)
at async GET (webpack-internal:///(rsc)/./app/api/components/route.ts:48:21)
at async C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistcompilednext-serverapp-route.runtime.dev.js:6:55038
at async ek.execute (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistcompilednext-serverapp-route.runtime.dev.js:6:45808)
at async ek.handle (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistcompilednext-serverapp-route.runtime.dev.js:6:56292)
at async doRender (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:1377:42)
at async cacheEntry.responseCache.get.routeKind (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:1599:28)
at async DevServer.renderToResponseWithComponentsImpl (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:1507:28)
at async DevServer.renderPageComponent (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:1931:24)
at async DevServer.renderToResponseImpl (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:1969:32)
at async DevServer.pipeImpl (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:920:25)
at async NextNodeServer.handleCatchallRenderRequest (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistservernext-server.js:272:17)
at async DevServer.handleRequestImpl (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverbase-server.js:816:17)
at async C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverdevnext-dev-server.js:339:20
at async Span.traceAsyncFn (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdisttracetrace.js:154:20)
at async DevServer.handleRequest (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverdevnext-dev-server.js:336:24)
at async invokeRender (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverlibrouter-server.js:174:21)
at async handleRequest (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverlibrouter-server.js:353:24)
at async requestHandlerImpl (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverlibrouter-server.js:377:13)
at async Server.requestListener (C:UsersenianDesktopUTP-ProjektUTPnode_modulesnextdistserverlibstart-server.js:141:13) {
clientVersion: '5.18.0'
}
model SpecialFeatures {
id Int @id @default(autoincrement())
feature String @unique
Gpu Gpu? @relation(fields: [gpuId], references: [id])
gpuId Int?
Ram Ram? @relation(fields: [ramId], references: [id])
ramId Int?
HDD HDD? @relation(fields: [hDDId], references: [id])
hDDId Int?
SSD SSD? @relation(fields: [sSDId], references: [id])
sSDId Int?
Mainboard Mainboard? @relation(fields: [mainboardId], references: [id])
mainboardId Int?
Psu Psu? @relation(fields: [psuId], references: [id])
psuId Int?
Case Case? @relation(fields: [caseId], references: [id])
caseId Int?
Fan Fan? @relation(fields: [fanId], references: [id])
fanId Int?
CoolerAio CoolerAio? @relation(fields: [coolerAioId], references: [id])
coolerAioId Int?
CoolerFan CoolerFan? @relation(fields: [coolerFanId], references: [id])
coolerFanId Int?
Cpu Cpu? @relation(fields: [cpuId], references: [id])
cpuId Int?
@@index([gpuId])
@@index([cpuId])
@@index([mainboardId])
@@index([caseId])
@@schema("components")
}
model Ram {
id Int @id @default(autoincrement())
imageUrl String?
Model3dUrl String?
name String @unique
manufacturer String
price PriceHistory[]
technology String //DDR3, DDR4, DDR5
formFactor String
pinCount Int //Pins
eccSupportType eccSupportType[]
transmissionSpeed_MTs Int //MT/s
memoryClock_MHz Int //MHz
moduleCount Int
color Color[]
capacityPerModule_Gb Int //GB
totalCapacity_Gb Int //GB
casLatency Int
rowToColumnDelay Int
rowPrechargeTime Int
height_mm Float //mm
caseRam String
lighting Lighting[]
popularity Int //1-10
specialFeatures SpecialFeatures[]
look Look[] //Modern, Gaming...
warrantyMonths Int //Monate
releaseDate DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
RamRequirements RamRequirements[]
@@index([manufacturer])
@@index([technology])
@@index([formFactor])
@@index([capacityPerModule_Gb])
@@index([totalCapacity_Gb])
@@index([transmissionSpeed_MTs])
@@index([releaseDate])
@@schema("components")
}
"use server";
import prisma from "@/lib/db";
import { extractSpec } from "@/lib/spec-extractors";
import { ComponentSpecs } from "@/types/component-map";
const ramModelFields = [
"name",
"manufacturer",
"popularity",
"releaseDate",
"imageUrl",
"Model3dUrl",
"technology",
"formFactor",
"pinCount",
"eccSupportType",
"transmissionSpeed_MTs",
"memoryClock_MHz",
"moduleCount",
"color",
"capacityPerModule_Gb",
"totalCapacity_Gb",
"casLatency",
"rowToColumnDelay",
"rowPrechargeTime",
"height_mm",
"caseRam", // Ändere caseRam zu case
"lighting",
"specialFeatures",
"look",
"warrantyMonths",
];
const cleanDbProduct = (dbProduct: any) => {
Object.keys(dbProduct).forEach((key) => {
if (!ramModelFields.includes(key)) {
delete dbProduct[key]; // Entferne das Feld, das nicht im Schema definiert ist
}
});
};
export const saveComponentToDb = async (component: ComponentSpecs, componentType: string) => {
const { name, manufacturer, specs } = component;
// Initialisiere dbProduct ohne verschachtelte specs
const dbProduct: any = {
name,
manufacturer,
popularity: 0,
releaseDate: new Date(),
};
if (componentType === "RAM" && specs) {
Object.keys(specs).forEach((specName) => {
const specValue = specs[specName];
// Verwende `extractSpec`, wenn nötig, oder speichere den Wert direkt
const extractedValue = extractSpec(specName, specValue, componentType) || specValue;
console.log("extractedValue", extractedValue);
if (extractedValue !== null && extractedValue !== undefined) {
if (specName === "Besonderheiten" && extractedValue.specialFeatures) {
dbProduct.specialFeatures = {
create: extractedValue.specialFeatures, // Hier wird Prisma-konforme Struktur erstellt
};
} else {
(dbProduct as any)[specName] = extractedValue;
}
}
// Alle Spezifikationen direkt auf die oberste Ebene verschieben, falls es ein Objekt ist
if (typeof extractedValue === "object" && !Array.isArray(extractedValue)) {
Object.assign(dbProduct, extractedValue);
} else {
dbProduct[specName] = extractedValue;
}
});
cleanDbProduct(dbProduct);
// Daten in die Datenbank einfügen
try {
await prisma.ram.create({
data: dbProduct,
});
} catch (e) {
console.error("Fehler beim Einfügen in die Datenbank:", e);
}
} else {
throw new Error("Komponententyp nicht gefunden oder keine Spezifikationen vorhanden");
}
return dbProduct;
};
/* eslint-disable camelcase */
import {
extractCapacityPerModule,
extractCase,
extractCasLatency,
extractEccSupportType,
extractFormFactor,
extractHeight,
extractMemoryClock,
extractModuleCount,
extractPinCount,
extractRowPrechargeTime,
extractRowToColumnDelay,
extractSpecialFeatures,
extractTechnology,
extractTransmissionSpeed,
extractWarranty,
} from "./extractors";
export const extractSpec = (specName: string, specValue: string, componentType: string): any => {
if (componentType === "RAM") {
switch (specName) {
case "Typ": {
const formFactor = extractFormFactor(specValue);
const technology = extractTechnology(specValue);
const pinCount = extractPinCount(specValue);
const eccSupportType = extractEccSupportType(specValue);
return {
formFactor,
technology,
pinCount,
eccSupportType,
};
}
case "Übertragung": {
const transmissionSpeed_MTs = extractTransmissionSpeed(specValue);
return {
transmissionSpeed_MTs,
};
}
case "Speichertakt": {
const memoryClock_MHz = extractMemoryClock(specValue);
return {
memoryClock_MHz,
};
}
case "Module": {
const moduleCount = extractModuleCount(specValue);
const capacityPerModule_Gb = extractCapacityPerModule(specValue);
const totalCapacity_Gb = 0;
return {
moduleCount,
capacityPerModule_Gb,
totalCapacity_Gb,
};
}
case "CAS Latency CL": {
const casLatency = extractCasLatency(specValue);
return {
casLatency,
};
}
case "Row-to-Column Delay tRCD": {
const rowToColumnDelay = extractRowToColumnDelay(specValue);
return {
rowToColumnDelay,
};
}
case "Row Precharge Time tRP": {
const rowPrechargeTime = extractRowPrechargeTime(specValue);
return {
rowPrechargeTime,
};
}
case "Modulhöhe": {
const height_mm = extractHeight(specValue);
return {
height_mm,
};
}
case "Gehäuse": {
const caseRam = extractCase(specValue);
return {
caseRam,
};
}
case "Besonderheiten": {
const specialFeatures = extractSpecialFeatures(specValue);
return {
specialFeatures,
};
}
case "Garantie": {
const warrantyMonths = extractWarranty(specValue);
return {
warrantyMonths,
};
}
default: {
console.error(`Unbekanntes RAM-Spezifikation: ${specName}`);
return null;
}
}
}
return null;
};
// RAM
export const extractTechnology = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
const technology = extractValue.split(" ")[0];
return (technology as string) || null;
};
export const extractFormFactor = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
const formFactor = extractValue.split(" ")[1];
return formFactor || null;
};
export const extractPinCount = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
const pinCount = extractValue.match(/(d+)-Pin/);
return pinCount ? parseInt(pinCount[1], 10) : null;
};
type ECCSupportType = "ECC" | "on_die_ECC" | "none";
export const extractEccSupportType = (extractValue: string): ECCSupportType[] => {
const eccTypes = (extractValue.match(/(?:ECC|on-die ECC)/g) as string[]) || [];
const eccSupport: ECCSupportType[] = []; // Stellt sicher, dass das Array den korrekten Typ hat
if (eccTypes.includes("ECC")) {
eccSupport.push("ECC");
}
if (eccTypes.includes("on-die ECC")) {
eccSupport.push("on_die_ECC"); // Hier ist die Typisierung korrekt
}
if (eccSupport.length === 0) {
eccSupport.push("none");
}
return eccSupport;
};
export const extractTransmissionSpeed = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
// Verwende RegEx, um die erste Zahl im String zu finden
const transmissionSpeed = extractValue.match(/b(d+)(?=MT/s)/);
return transmissionSpeed ? parseInt(transmissionSpeed[1], 10) : null;
};
export const extractMemoryClock = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
// Verwende RegEx, um die erste Zahl im String zu finden, die vor "MHz" steht
const memoryClock = extractValue.match(/b(d+)(?=MHz)/);
return memoryClock ? parseInt(memoryClock[1], 10) : null;
};
export const extractModuleCount = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
// Verwende RegEx, um die Zahl vor dem "x" zu finden
const moduleCount = extractValue.match(/(d+)(?=x)/);
return moduleCount ? parseInt(moduleCount[1], 10) : null;
};
export const extractCapacityPerModule = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
// Verwende RegEx, um die Zahl nach dem "x" zu finden
const capacityPerModule = extractValue.match(/xs*(d+)/);
return capacityPerModule ? parseInt(capacityPerModule[1], 10) : null;
};
export const extractCasLatency = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
const casLatency = parseInt(extractValue.split(" ")[0], 10);
return casLatency || null;
};
export const extractRowToColumnDelay = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
const rowToColumnDelay = parseInt(extractValue.split(" ")[0], 10);
return rowToColumnDelay || null;
};
export const extractRowPrechargeTime = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
const rowPrechargeTime = parseInt(extractValue.split(" ")[0], 10);
return rowPrechargeTime || null;
};
export const extractHeight = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
// Verwende RegEx, um die Zahl vor "mm" zu finden
const height = extractValue.match(/(d+(.d+)?)(?=mm)/);
return height ? parseInt(height[1], 10) : null;
};
export const extractCase = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
return extractValue || null;
};
export const extractSpecialFeatures = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return [];
}
// Verwende RegEx, um den String an jedem Komma zu trennen
const specialFeaturesArray = extractValue.split(/s*,s*/);
const specialFeatures = specialFeaturesArray.map((feature) => ({
feature,
}));
return specialFeatures;
};
export const extractWarranty = (extractValue: string) => {
if (!extractValue || extractValue === "N/A") {
return null;
}
// Verwende RegEx, um die Zahl vor "Monate" zu finden
const warranty = extractValue.match(/(d+)(?=s*Monate)/);
return warranty ? parseInt(warranty[1], 10) : null;
};