source: database/drizzle/queries/components.ts@ 1b5c18b

main
Last change on this file since 1b5c18b was e03e6fb, checked in by Tome <gjorgievtome@…>, 5 months ago

Fix missing attribute in buildComponents relation

  • Property mode set to 100644
File size: 34.5 KB
RevLine 
[1ebac59]1import type { Database } from "../db";
[ea09e98]2import {
[83fb5e2]3 buildComponentsTable,
[ea09e98]4 buildsTable, cablesTable, caseMoboFormFactorsTable, casePsFormFactorsTable, caseStorageFormFactorsTable,
5 componentsTable, coolerCPUSocketsTable, coolersTable,
6 CPUTable,
7 GPUTable, memoryCardsTable,
8 memoryTable, motherboardsTable,
9 networkAdaptersTable,
[83fb5e2]10 networkCardsTable, opticalDrivesTable, pcCasesTable, powerSupplyTable,
11 ratingBuildsTable, soundCardsTable, storageTable
[ea09e98]12} from "../schema";
[9854393]13import {and, asc, desc, eq, ilike, SQL, sql} from "drizzle-orm";
[dda6f51]14import {typeConfigMap, ComponentType, requiredFields} from "../util/componentFieldConfig";
[e7f1bfa]15import {AnyPgColumn} from "drizzle-orm/pg-core";
[9854393]16import {inArray} from "drizzle-orm/sql/expressions/conditions";
17export async function getAllComponents(db: Database, limit?: number, componentType?: string, sort?: string, q?: string) {
[c4dd17d]18 let queryConditions = [];
[9854393]19 let sortConditions = [];
[1ebac59]20
[c4dd17d]21 if (q) {
22 queryConditions.push(
23 ilike(componentsTable.name, `%${q}%`)
24 );
25 }
26
[9854393]27 switch(sort) {
28 case 'price_asc':
29 sortConditions.push(
[9c87509]30 asc(componentsTable.price)
[9854393]31 );
32 break;
33 case 'price_desc':
34 sortConditions.push(
[9c87509]35 desc(componentsTable.price)
[9854393]36 );
37 break;
38 default:
39 sortConditions.push(
[9c87509]40 desc(componentsTable.price)
[9854393]41 );
42 break;
43 }
44
[c4dd17d]45 if(componentType && componentType.trim() !== 'all') {
46 queryConditions.push(
47 eq(componentsTable.type, componentType.trim().toLowerCase())
48 );
49 }
[1ebac59]50
[e7f1bfa]51 const components = await db
[3216215]52 .select()
[c4dd17d]53 .from(componentsTable)
54 .where(
55 queryConditions.length > 0 ?
56 and (
57 ...queryConditions
58 )
59 : undefined
60 )
61 .orderBy(
[9854393]62 ...sortConditions
[c4dd17d]63 )
64 .limit(limit || 100); // 100 placeholder
[1ebac59]65
[e7f1bfa]66 return components;
[1ebac59]67}
68
[c4dd17d]69export async function getComponentDetails(db: Database, componentId: number) {
[ea09e98]70 const [component] = await db
[c4dd17d]71 .select()
72 .from(componentsTable)
73 .where(
74 eq(componentsTable.id, componentId)
75 )
76 .limit(1);
[1ebac59]77
[ea09e98]78 if(!component) return null;
79
[e7f1bfa]80 const config = typeConfigMap[component.type as ComponentType];
[ea09e98]81
[e7f1bfa]82 const [details] = await db
83 .select()
84 .from(config.table)
85 .where(
86 eq(config.table.componentId, componentId)
87 )
88 .limit(1);
89
90 if (component.type === 'case') {
91 details.storageFormFactors = await db.select().from(caseStorageFormFactorsTable).where(eq(caseStorageFormFactorsTable.caseId, componentId));
92 details.psFormFactors = await db.select().from(casePsFormFactorsTable).where(eq(casePsFormFactorsTable.caseId, componentId));
93 details.moboFormFactors = await db.select().from(caseMoboFormFactorsTable).where(eq(caseMoboFormFactorsTable.caseId, componentId));
94 }
95
96 if (component.type === 'cooler') {
97 details.cpuSockets = await db.select().from(coolerCPUSocketsTable).where(eq(coolerCPUSocketsTable.coolerId, componentId));
[ea09e98]98 }
99
100 return {
101 ...component,
[e7f1bfa]102 details: details
[ea09e98]103 };
104}
105
[dda6f51]106export async function getDetailsForNewComponent(componentType: string) {
107 const config = typeConfigMap[componentType as ComponentType];
108
109 return {
110 type: componentType as ComponentType,
111 requiredFields: requiredFields[componentType as ComponentType] ?? [],
112 multiTables: config.multiTables ?? {},
113 };
114}
115
[ea09e98]116export async function addNewComponent(db: Database, name: string, brand: string, price: number, imgUrl: string, type: string, specificData: any) {
117 return db.transaction(async (tx) => {
118 const [newComponent] = await tx
119 .insert(componentsTable)
120 .values({
121 name: name,
122 brand: brand,
123 price: price.toFixed(2),
124 imgUrl: imgUrl,
125 type: type
126 })
127 .returning({
128 id: componentsTable.id
129 });
130
131 const componentId = newComponent.id;
132
[e7f1bfa]133 const config = typeConfigMap[type as ComponentType];
[ea09e98]134
[e7f1bfa]135 await tx
136 .insert(config.table)
137 .values({
138 componentId: componentId,
139 ...specificData
140 });
[ea09e98]141
[e7f1bfa]142 if (type === 'case') {
143 if (specificData.storageFormFactors) {
144 await tx.insert(caseStorageFormFactorsTable).values(
145 specificData.storageFormFactors.map((sf: any) => ({
146 caseId: componentId,
147 formFactor: sf.formFactor,
148 numSlots: sf.numSlots,
149 }))
150 );
151 }
152 if (specificData.psFormFactors) {
153 await tx.insert(casePsFormFactorsTable).values(
154 specificData.psFormFactors.map((pf: any) => ({
155 caseId: componentId,
156 formFactor: pf.formFactor,
157 }))
158 );
159 }
160 if (specificData.moboFormFactors) {
161 await tx.insert(caseMoboFormFactorsTable).values(
162 specificData.moboFormFactors.map((mf: any) => ({
163 caseId: componentId,
164 formFactor: mf.formFactor,
165 }))
166 );
167 }
168 }
[ea09e98]169
[e7f1bfa]170 if (type === 'cooler' && specificData.cpuSockets) {
171 await tx.insert(coolerCPUSocketsTable).values(
172 specificData.cpuSockets.map((socket: any) => ({ coolerId: componentId, socket }))
173 );
174 }
[ea09e98]175
[e7f1bfa]176 return componentId;
177 });
178}
[ea09e98]179
[e7f1bfa]180export async function getBuildComponents(db: Database, buildId: number) {
181 const [build] = await db
182 .select({
183 buildId: buildsTable.id,
184 userId: buildsTable.userId,
185 })
186 .from(buildsTable)
187 .where(
188 eq(buildsTable.id, buildId)
189 )
190 .limit(1);
[ea09e98]191
[e7f1bfa]192 if(!build) return null;
[ea09e98]193
[e7f1bfa]194 const components = await db
195 .select({
196 id: componentsTable.id,
197 type: componentsTable.type,
[e03e6fb]198 quantity: buildComponentsTable.numComponents
[e7f1bfa]199 })
200 .from(buildComponentsTable)
201 .where(
202 eq(buildComponentsTable.buildId, buildId)
203 )
204 .innerJoin(
205 componentsTable,
206 eq(buildComponentsTable.componentId, componentsTable.id)
207 );
[ea09e98]208
[e7f1bfa]209 const componentsDetails = [];
[ea09e98]210
[e7f1bfa]211 for (const comp of components) {
212 const config = typeConfigMap[comp.type as ComponentType];
[ea09e98]213
[e7f1bfa]214 const [details] = await db
215 .select()
216 .from(config.table)
217 .where(
218 eq(config.table.componentId, comp.id)
219 )
220 .limit(1);
[ea09e98]221
[e7f1bfa]222 if (comp.type === 'case') {
223 details.storageFormFactors = await db.select().from(caseStorageFormFactorsTable).where(eq(caseStorageFormFactorsTable.caseId, comp.id));
224 details.psFormFactors = await db.select().from(casePsFormFactorsTable).where(eq(casePsFormFactorsTable.caseId, comp.id));
225 details.moboFormFactors = await db.select().from(caseMoboFormFactorsTable).where(eq(caseMoboFormFactorsTable.caseId, comp.id));
226 }
[ea09e98]227
[e7f1bfa]228 if (comp.type === 'cooler') {
229 details.cpuSockets = await db.select().from(coolerCPUSocketsTable).where(eq(coolerCPUSocketsTable.coolerId, comp.id));
[ea09e98]230 }
231
[e7f1bfa]232 componentsDetails.push({
233 ...comp,
[e03e6fb]234 quantity: comp.quantity,
[e7f1bfa]235 details: details
236 });
237 }
238
239 return componentsDetails;
[83fb5e2]240}
241
[9854393]242export async function getCompatibleComponents(db: Database, buildId: number, componentType: string, limit?: number, sort?: string) {
243 let sortCondition: any;
244
245 switch(sort) {
246 case 'price_asc':
247 sortCondition = asc(componentsTable.price);
248 break;
249 case 'price_desc':
250 sortCondition = desc(componentsTable.price);
251 break;
252 default:
253 sortCondition = desc(componentsTable.price);
254 break;
255 }
256
257 const existingComponents = await getBuildComponents(db, buildId);
258
259 if(!existingComponents) return null;
260
261 const existing = {
262 cpu: existingComponents.find(c => c.type === 'cpu'),
263 motherboard: existingComponents.find(c => c.type === 'motherboard'),
264 case: existingComponents.find(c => c.type === 'case'),
265 cooler: existingComponents.find(c => c.type === 'cooler'),
266 psu: existingComponents.find(c => c.type === 'power_supply'),
267 gpu: existingComponents.find(c => c.type === 'gpu'),
268 memory: existingComponents.filter(c => c.type === 'memory'),
269 storage: existingComponents.filter(c => c.type === 'storage'),
270 networkCards: existingComponents.filter(c => c.type === 'network_card'),
271 networkAdapters: existingComponents.filter(c => c.type === 'network_adapter'),
272 soundCards: existingComponents.filter(c => c.type === 'sound_card'),
273 };
274
275 const pciExpressSlotsUsed = [
276 existing.gpu,
277 ...existing.networkCards,
278 ...existing.networkAdapters,
279 ...existing.soundCards
[e03e6fb]280 ].reduce((sum: number, c: any) => {
281 if (!c) return sum;
282 return sum + (c.quantity || 1);
283 }, 0);
[9854393]284
285 const existingTDP = existingComponents.reduce((sum, c) => {
286 const tdp = c.details?.tdp ? Number(c.details.tdp) : 0;
[e03e6fb]287 return sum + (tdp * (c.quantity || 1));
[9854393]288 }, 0);
289
290
291 let compatibleComponents: any[] = [];
292
293 switch (componentType) {
294 case 'cpu':
[ae5d054]295 compatibleComponents = await getCompatibleCPUs(db, existing, existingTDP, limit, sortCondition);
[9854393]296 break;
297 case 'motherboard':
298 compatibleComponents = await getCompatibleMotherboards(db, existing, pciExpressSlotsUsed, limit, sortCondition);
299 break;
300 case 'cooler':
301 compatibleComponents = await getCompatibleCoolers(db, existing, limit, sortCondition);
302 break;
303 case 'gpu':
[ae5d054]304 compatibleComponents = await getCompatibleGPUs(db, existing, existingTDP, pciExpressSlotsUsed, limit, sortCondition);
[9854393]305 break;
306 case 'memory':
307 compatibleComponents = await getCompatibleMemory(db, existing, limit, sortCondition);
308 break;
309 case 'storage':
310 compatibleComponents = await getCompatibleStorage(db, existing, limit, sortCondition);
311 break;
312 case 'case':
313 compatibleComponents = await getCompatibleCases(db, existing, limit, sortCondition);
314 break;
315 case 'power_supply':
316 compatibleComponents = await getCompatiblePSUs(db, existing, existingTDP, limit, sortCondition);
317 break;
318 case 'network_card':
319 case 'network_adapter':
320 case 'sound_card':
321 compatibleComponents = await getCompatiblePCIeComponents(db, existing, componentType, pciExpressSlotsUsed, limit, sortCondition);
322 break;
323 default:
324 compatibleComponents = [];
325 }
326
327 if(compatibleComponents.length === 0) return null;
328
329 return compatibleComponents;
330}
331
332
333// Helper functions for checking component-specific compatibility
[ae5d054]334async function getCompatibleCPUs(db: Database, existing: any, existingTDP?: number, limit?: number, sortCondition?: any) {
[9854393]335 const conditions = [eq(componentsTable.type, 'cpu')];
336
337 if (existing.motherboard) {
338 conditions.push(
339 eq(CPUTable.socket, existing.motherboard.details.socket)
340 );
341 }
342
343 if (existing.cooler) {
344 conditions.push(
345 sql`${CPUTable.tdp} <= ${existing.cooler.details.maxTdpSupported}`
346 );
347 }
348
349 const cpus = await db
350 .select({
351 id: componentsTable.id,
352 name: componentsTable.name,
353 brand: componentsTable.brand,
354 price: componentsTable.price,
355 imgUrl: componentsTable.imgUrl,
356 type: componentsTable.type,
357 socket: CPUTable.socket,
358 cores: CPUTable.cores,
359 threads: CPUTable.threads,
360 baseClock: CPUTable.baseClock,
361 boostClock: CPUTable.boostClock,
362 tdp: CPUTable.tdp,
363 })
364 .from(componentsTable)
365 .innerJoin(
366 CPUTable,
367 eq(componentsTable.id, CPUTable.componentId)
368 )
369 .where(
370 and(
371 ...conditions
372 )
373 )
374 .orderBy(
375 sortCondition
376 )
377 .limit(limit || 100);
378
[ae5d054]379 let filteredCPUs = cpus;
380
[9854393]381 if (existing.cooler?.details?.cpuSockets) {
382 const coolerSockets = existing.cooler.details.cpuSockets.map((s: any) => s.socket);
[ae5d054]383 filteredCPUs = filteredCPUs.filter(cpu => coolerSockets.includes(cpu.socket));
384 }
385
386 if(existing.psu?.details?.wattage && existingTDP) {
387 const psuWattage = Number(existing.psu.details.wattage) || 0;
388
389 if (psuWattage > 0) {
390 const budget = psuWattage / 1.2;
391
392 const currentCpuTdp = Number(existing.cpu?.details?.tdp) || 0;
393 const baseWithoutCpu = existingTDP - currentCpuTdp;
394
395 filteredCPUs = filteredCPUs.filter(cpu => {
396 const cpuTdp = Number(cpu.tdp) || 0;
397 return (baseWithoutCpu + cpuTdp) <= budget;
398 });
399 }
[9854393]400 }
401
[ae5d054]402 return filteredCPUs;
[9854393]403}
404
405async function getCompatibleMotherboards(db: Database, existing: any, pciExpressSlotsUsed: number, limit?: number, sortCondition?: any) {
406 const conditions = [eq(componentsTable.type, 'motherboard')];
407
408 if (existing.cpu) {
409 conditions.push(
410 eq(motherboardsTable.socket, existing.cpu.details.socket)
411 );
412 }
413
414 if (existing.memory.length > 0) {
415 const ramType = existing.memory[0].details.type;
416 conditions.push(
417 eq(motherboardsTable.ramType, ramType)
418 );
419 }
420
421 if (pciExpressSlotsUsed > 0) {
422 conditions.push(
423 sql`${motherboardsTable.pciExpressSlots} >= ${pciExpressSlotsUsed}`
424 );
425 }
426
427 const motherboards = await db
428 .select({
429 id: componentsTable.id,
430 name: componentsTable.name,
431 brand: componentsTable.brand,
432 price: componentsTable.price,
433 imgUrl: componentsTable.imgUrl,
434 type: componentsTable.type,
435 socket: motherboardsTable.socket,
436 chipset: motherboardsTable.chipset,
437 formFactor: motherboardsTable.formFactor,
438 ramType: motherboardsTable.ramType,
439 numRamSlots: motherboardsTable.numRamSlots,
440 maxRamCapacity: motherboardsTable.maxRamCapacity,
441 pciExpressSlots: motherboardsTable.pciExpressSlots,
442 })
443 .from(componentsTable)
444 .innerJoin(
445 motherboardsTable,
446 eq(componentsTable.id, motherboardsTable.componentId)
447 )
448 .where(
449 and(
450 ...conditions
451 )
452 )
453 .orderBy(
454 sortCondition
455 )
456 .limit(limit || 100);
457
458 const compatibleMotherboards = motherboards.filter(mobo => {
459 if (existing.memory.length > 0) {
460 const totalModules = existing.memory.reduce((sum: number, m: any) =>
[e03e6fb]461 sum + (Number(m.details.modules) * m.quantity), 0
[9854393]462 );
463 const totalCapacity = existing.memory.reduce((sum: number, m: any) =>
[e03e6fb]464 sum + (Number(m.details.capacity) * m.quantity), 0
[9854393]465 );
466
467 if (totalModules > Number(mobo.numRamSlots)) return false;
468 if (totalCapacity > Number(mobo.maxRamCapacity)) return false;
469 }
470
471 return true;
472 });
473
474 if (existing.case?.details?.moboFormFactors) {
475 const caseFormFactors = existing.case.details.moboFormFactors.map((f: any) => f.formFactor);
476 return compatibleMotherboards.filter(mobo => caseFormFactors.includes(mobo.formFactor));
477 }
478
479 return compatibleMotherboards;
480}
481
482async function getCompatibleCoolers(db: Database, existing: any, limit?: number, sortCondition?: any) {
483 const conditions = [eq(componentsTable.type, 'cooler')];
484
485 if (existing.cpu) {
486 conditions.push(
487 sql`${coolersTable.maxTdpSupported} >= ${existing.cpu.details.tdp}`
488 );
489 }
490
491 if (existing.case) {
492 conditions.push(
493 sql`${coolersTable.height} <= ${existing.case.details.coolerMaxHeight}`
494 );
495 }
496
497 const coolers = await db
498 .select({
499 id: componentsTable.id,
500 name: componentsTable.name,
501 brand: componentsTable.brand,
502 price: componentsTable.price,
503 imgUrl: componentsTable.imgUrl,
504 type: componentsTable.type,
505 coolerType: coolersTable.type,
506 height: coolersTable.height,
507 maxTdpSupported: coolersTable.maxTdpSupported,
508 })
509 .from(componentsTable)
510 .innerJoin(
511 coolersTable,
512 eq(componentsTable.id, coolersTable.componentId)
513 )
514 .where(
515 and(
516 ...conditions
517 )
518 )
519 .orderBy(
520 sortCondition
521 )
522 .limit(limit || 100);
523
524
525 if(existing.cpu && coolers.length > 0) {
526 const coolerIds = coolers.map(c => c.id);
527
528 const allSockets = await db
529 .select({
530 coolerId: coolerCPUSocketsTable.coolerId,
531 socket: coolerCPUSocketsTable.socket
532 })
533 .from(coolerCPUSocketsTable)
534 .where(
535 inArray(coolerCPUSocketsTable.coolerId, coolerIds)
536 );
537 const socketsByCooler = allSockets.reduce((acc, { coolerId, socket }) => {
538 if (!acc[coolerId]) acc[coolerId] = [];
539 acc[coolerId].push(socket);
540 return acc;
541 }, {} as Record<number, string[]>);
542
543 return coolers.filter(cooler => {
544 const sockets = socketsByCooler[cooler.id] || [];
545 return sockets.includes(existing.cpu.details.socket);
546 });
547 }
548
549 return coolers;
550}
551
[ae5d054]552async function getCompatibleGPUs(db: Database, existing: any, existingTDP: number, pciExpressSlotsUsed: number, limit?: number, sortCondition?: any) {
[9854393]553 const conditions = [eq(componentsTable.type, 'gpu')];
554
555 if (existing.case) {
556 conditions.push(
557 sql`${GPUTable.length} <= ${existing.case.details.gpuMaxLength}`
558 );
559 }
560
561 const gpus = await db
562 .select({
563 id: componentsTable.id,
564 name: componentsTable.name,
565 brand: componentsTable.brand,
566 price: componentsTable.price,
567 imgUrl: componentsTable.imgUrl,
568 type: componentsTable.type,
569 vram: GPUTable.vram,
570 tdp: GPUTable.tdp,
571 baseClock: GPUTable.baseClock,
572 boostClock: GPUTable.boostClock,
573 chipset: GPUTable.chipset,
574 length: GPUTable.length,
575 })
576 .from(componentsTable)
577 .innerJoin(
578 GPUTable,
579 eq(componentsTable.id, GPUTable.componentId)
580 )
581 .where(
582 and(
583 ...conditions
584 )
585 )
586 .orderBy(
587 sortCondition
588 )
589 .limit(limit || 100);
590
[ae5d054]591 let filteredGPUs = gpus;
592
[9854393]593 if (existing.motherboard) {
594 const availableSlots = Number(existing.motherboard.details.pciExpressSlots);
[ae5d054]595 filteredGPUs = gpus.filter(() => (pciExpressSlotsUsed + 1) <= availableSlots);
596 }
597
598 if(existing.psu?.details?.wattage && existingTDP) {
599 const psuWattage = Number(existing.psu.details.wattage) || 0;
600
601 if (psuWattage > 0) {
602 const budget = psuWattage / 1.2;
603
604 const currentGpuTdp = Number(existing.cpu?.details?.tdp) || 0;
605 const baseWithoutGpu = existingTDP - currentGpuTdp;
606
607 filteredGPUs = filteredGPUs.filter(cpu => {
608 const gpuTdp = Number(cpu.tdp) || 0;
609 return (baseWithoutGpu + gpuTdp) <= budget;
610 });
611 }
[9854393]612 }
613
[ae5d054]614 return filteredGPUs;
[9854393]615}
616
617async function getCompatibleMemory(db: Database, existing: any, limit?: number, sortCondition?: any) {
618 const conditions = [eq(componentsTable.type, 'memory')];
619
620 if (existing.motherboard) {
621 conditions.push(
622 eq(memoryTable.type, existing.motherboard.details.ramType)
623 );
624 }
625
626 const memory = await db
627 .select({
628 id: componentsTable.id,
629 name: componentsTable.name,
630 brand: componentsTable.brand,
631 price: componentsTable.price,
632 imgUrl: componentsTable.imgUrl,
633 type: componentsTable.type,
634 memoryType: memoryTable.type,
635 speed: memoryTable.speed,
636 capacity: memoryTable.capacity,
637 modules: memoryTable.modules,
638 })
639 .from(componentsTable)
640 .innerJoin(
641 memoryTable,
642 eq(componentsTable.id, memoryTable.componentId)
643 )
644 .where(
645 and(
646 ...conditions
647 )
648 )
649 .orderBy(
650 sortCondition
651 )
652 .limit(limit || 100);
653
654 if (existing.motherboard) {
655 const existingModules = existing.memory.reduce((sum: number, m: any) =>
656 sum + Number(m.details.modules), 0
657 );
658 const existingCapacity = existing.memory.reduce((sum: number, m: any) =>
659 sum + Number(m.details.capacity), 0
660 );
661 const maxSlots = Number(existing.motherboard.details.numRamSlots);
662 const maxCapacity = Number(existing.motherboard.details.maxRamCapacity);
663
664 return memory.filter(mem => {
665 const newModules = existingModules + Number(mem.modules);
666 const newCapacity = existingCapacity + Number(mem.capacity);
667 return newModules <= maxSlots && newCapacity <= maxCapacity;
668 });
669 }
670
671 return memory;
672}
673
674async function getCompatibleStorage(db: Database, existing: any, limit?: number, sortCondition?: any) {
675 const storage = await db
676 .select({
677 id: componentsTable.id,
678 name: componentsTable.name,
679 brand: componentsTable.brand,
680 price: componentsTable.price,
681 imgUrl: componentsTable.imgUrl,
682 type: componentsTable.type,
683 storageType: storageTable.type,
684 capacity: storageTable.capacity,
685 formFactor: storageTable.formFactor,
686 })
687 .from(componentsTable)
688 .innerJoin(
689 storageTable,
690 eq(componentsTable.id, storageTable.componentId
691 )
692 )
693 .where(
694 eq(componentsTable.type, 'storage')
695 )
696 .orderBy(
697 sortCondition
698 )
699 .limit(limit || 100);
700
701 if (existing.case?.details?.storageFormFactors) {
702 return storage.filter(stor => {
703 const caseStorageFormFactor = existing.case.details.storageFormFactors.find(
704 (sf: any) => sf.formFactor === stor.formFactor
705 );
706
707 if (!caseStorageFormFactor) return false;
708
[e03e6fb]709 const usedSlots = existing.storage
710 .filter((s: any) => s.details.formFactor === stor.formFactor)
711 .reduce((sum: number, s: any) => sum + s.quantity, 0);
[9854393]712
713 return usedSlots < Number(caseStorageFormFactor.numSlots);
714 });
715 }
716
717 return storage;
718}
719
720async function getCompatibleCases(db: Database, existing: any, limit?: number, sortCondition?: any) {
721 const conditions = [eq(componentsTable.type, 'case')];
722
723 if (existing.gpu) {
724 conditions.push(
725 sql`${pcCasesTable.gpuMaxLength} >= ${existing.gpu.details.length}`
726 );
727 }
728
729 if (existing.cooler) {
730 conditions.push(
731 sql`${pcCasesTable.coolerMaxHeight} >= ${existing.cooler.details.height}`
732 );
733 }
734
735 const cases = await db
736 .select({
737 id: componentsTable.id,
738 name: componentsTable.name,
739 brand: componentsTable.brand,
740 price: componentsTable.price,
741 imgUrl: componentsTable.imgUrl,
742 type: componentsTable.type,
743 coolerMaxHeight: pcCasesTable.coolerMaxHeight,
744 gpuMaxLength: pcCasesTable.gpuMaxLength,
745 })
746 .from(componentsTable)
747 .innerJoin(
748 pcCasesTable,
749 eq(componentsTable.id, pcCasesTable.componentId)
750 )
751 .where(
752 and(
753 ...conditions)
754 )
755 .orderBy(
756 sortCondition
757 )
758 .limit(limit || 100);
759
760 if(cases.length === 0) return [];
761
762 const caseIds = cases.map(c => c.id);
763
764 const [allStorageFF, allPsFF, allMoboFF] = await Promise.all([
765 db.select().from(caseStorageFormFactorsTable)
766 .where(
767 inArray(caseStorageFormFactorsTable.caseId, caseIds)
768 ),
769 db.select().from(casePsFormFactorsTable)
770 .where(
771 inArray(casePsFormFactorsTable.caseId, caseIds)
772 ),
773 db.select().from(caseMoboFormFactorsTable)
774 .where(
775 inArray(caseMoboFormFactorsTable.caseId, caseIds)
776 )
777 ]);
778
779 const storageByCase = allStorageFF.reduce((acc, ff) => {
780 if (!acc[ff.caseId]) acc[ff.caseId] = [];
781 acc[ff.caseId].push(ff);
782 return acc;
783 }, {} as Record<number, any[]>);
784
785 const psByCase = allPsFF.reduce((acc, ff) => {
786 if (!acc[ff.caseId]) acc[ff.caseId] = [];
787 acc[ff.caseId].push(ff);
788 return acc;
789 }, {} as Record<number, any[]>);
790
791 const moboByCase = allMoboFF.reduce((acc, ff) => {
792 if (!acc[ff.caseId]) acc[ff.caseId] = [];
793 acc[ff.caseId].push(ff);
794 return acc;
795 }, {} as Record<number, any[]>);
796
797 const casesWithDetails = cases.map(pcCase => ({
798 ...pcCase,
799 storageFormFactors: storageByCase[pcCase.id] || [],
800 psFormFactors: psByCase[pcCase.id] || [],
801 moboFormFactors: moboByCase[pcCase.id] || []
802 }));
803
804 let filteredCases = casesWithDetails;
805
806 if (existing.motherboard) {
807 filteredCases = filteredCases.filter(pcCase =>
808 pcCase.moboFormFactors.some((f: any) => f.formFactor === existing.motherboard.details.formFactor)
809 );
810 }
811
812 if (existing.psu) {
813 filteredCases = filteredCases.filter(pcCase =>
814 pcCase.psFormFactors.some((f: any) => f.formFactor === existing.psu.details.formFactor)
815 );
816 }
817
818 if (existing.storage.length > 0) {
819 filteredCases = filteredCases.filter(pcCase => {
820 return existing.storage.every((stor: any) => {
821 const storageFF = pcCase.storageFormFactors.find(
822 (sf: any) => sf.formFactor === stor.details.formFactor
823 );
824 if (!storageFF) return false;
825
[e03e6fb]826 const usedSlots = existing.storage
827 .filter((s: any) => s.details.formFactor === stor.details.formFactor)
828 .reduce((sum: number, s: any) => sum + s.quantity, 0);;
[9854393]829
830 return usedSlots <= Number(storageFF.numSlots);
831 });
832 });
833 }
834
835 return filteredCases;
836}
837
838async function getCompatiblePSUs(db: Database, existing: any, existingTDP: number, limit?: number, sortCondition?: any) {
839 const conditions = [eq(componentsTable.type, 'power_supply')];
840
841 const minWattage = existingTDP * 1.2;
842 conditions.push(
843 sql`${powerSupplyTable.wattage} >= ${minWattage}`
844 );
845
846 const psus = await db
847 .select({
848 id: componentsTable.id,
849 name: componentsTable.name,
850 brand: componentsTable.brand,
851 price: componentsTable.price,
852 imgUrl: componentsTable.imgUrl,
853 type: componentsTable.type,
854 psuType: powerSupplyTable.type,
855 wattage: powerSupplyTable.wattage,
856 formFactor: powerSupplyTable.formFactor,
857 })
858 .from(componentsTable)
859 .innerJoin(
860 powerSupplyTable,
861 eq(componentsTable.id, powerSupplyTable.componentId)
862 )
863 .where(
864 and(
865 ...conditions
866 )
867 )
868 .orderBy(
869 sortCondition
870 )
871 .limit(limit || 100);
872
873 if (existing.case?.details?.psFormFactors) {
874 const casePSFormFactors = existing.case.details.psFormFactors.map((f: any) => f.formFactor);
875 return psus.filter(psu => casePSFormFactors.includes(psu.formFactor));
876 }
877
878 return psus;
879}
880
881async function getCompatiblePCIeComponents(db: Database, existing: any, componentType: string, pciExpressSlotsUsed: number, limit?: number, sortCondition?: any) {
882 let table: any;
883 let selectFields: any = {
884 id: componentsTable.id,
885 name: componentsTable.name,
886 brand: componentsTable.brand,
887 price: componentsTable.price,
888 imgUrl: componentsTable.imgUrl,
889 type: componentsTable.type,
890 };
891
892 switch (componentType) {
893 case 'network_card':
894 table = networkCardsTable;
895 selectFields = {
896 ...selectFields,
897 numPorts: networkCardsTable.numPorts,
898 speed: networkCardsTable.speed,
899 interface: networkCardsTable.interface,
900 };
901 break;
902 case 'network_adapter':
903 table = networkAdaptersTable;
904 selectFields = {
905 ...selectFields,
906 wifiVersion: networkAdaptersTable.wifiVersion,
907 interface: networkAdaptersTable.interface,
908 numAntennas: networkAdaptersTable.numAntennas,
909 };
910 break;
911 case 'sound_card':
912 table = soundCardsTable;
913 selectFields = {
914 ...selectFields,
915 sampleRate: soundCardsTable.sampleRate,
916 bitDepth: soundCardsTable.bitDepth,
917 chipset: soundCardsTable.chipset,
918 interface: soundCardsTable.interface,
919 channel: soundCardsTable.channel,
920 };
921 break;
922 default:
923 return [];
924 }
925
926 const components = await db
927 .select(selectFields)
928 .from(componentsTable)
929 .innerJoin(
930 table,
931 eq(componentsTable.id, table.componentId)
932 )
933 .where(
934 eq(componentsTable.type, componentType)
935 )
936 .orderBy(
937 sortCondition
938 )
939 .limit(limit || 100);
940
941 if (existing.motherboard) {
942 const availableSlots = Number(existing.motherboard.details.pciExpressSlots);
943 return components.filter(() => (pciExpressSlotsUsed + 1) <= availableSlots);
944 }
945
946 return components;
[83fb5e2]947}
948
[8fe7f1a]949export async function addComponentToBuild(db: Database, userId: number, buildId: number, componentId: number) {
950 return db.transaction(async (tx) => {
951 const [build] = await tx
952 .select()
953 .from(buildsTable)
954 .where(
955 and(
956 eq(buildsTable.id, buildId),
957 eq(buildsTable.userId, userId)
958 )
959 )
960 .limit(1);
[83fb5e2]961
[8fe7f1a]962 if(!build || build.isApproved) return null;
[83fb5e2]963
[8fe7f1a]964 await tx
965 .insert(buildComponentsTable)
966 .values({
967 buildId,
[e03e6fb]968 componentId,
969 numComponents: 1
970 })
971 .onConflictDoUpdate({
972 target: [buildComponentsTable.buildId, buildComponentsTable.componentId],
973 set: {
974 numComponents: sql`${buildComponentsTable.numComponents} + 1`
975 }
[8fe7f1a]976 });
[83fb5e2]977
[8fe7f1a]978 const buildComponents = await tx
979 .select({
980 price: componentsTable.price,
[e03e6fb]981 quantity: buildComponentsTable.numComponents
[8fe7f1a]982 })
983 .from(buildComponentsTable)
984 .innerJoin(
985 componentsTable,
986 eq(buildComponentsTable.componentId, componentsTable.id)
987 )
988 .where(
989 eq(buildComponentsTable.buildId, buildId)
990 );
[83fb5e2]991
[e03e6fb]992 const totalPrice = buildComponents.reduce((sum, c) =>
993 sum + (Number(c.price) * c.quantity), 0
994 );
[83fb5e2]995
[8fe7f1a]996 await tx
997 .update(buildsTable)
998 .set({
999 totalPrice: totalPrice.toFixed(2)
1000 })
1001 .where(
1002 eq(buildsTable.id, buildId)
1003 );
[83fb5e2]1004
[8fe7f1a]1005 return buildId;
1006 })
[83fb5e2]1007}
1008
[8fe7f1a]1009export async function removeComponentFromBuild(db: Database, userId: number, buildId: number, componentId: number) {
1010 return db.transaction(async (tx) => {
1011 const [build] = await tx
1012 .select()
1013 .from(buildsTable)
1014 .where(
1015 and(
1016 eq(buildsTable.id, buildId),
1017 eq(buildsTable.userId, userId)
1018 )
1019 )
1020 .limit(1);
[83fb5e2]1021
[8fe7f1a]1022 if(!build || build.isApproved) return null;
[83fb5e2]1023
[e03e6fb]1024 const [existing] = await tx
1025 .select({
1026 quantity: buildComponentsTable.numComponents
1027 })
1028 .from(buildComponentsTable)
[8fe7f1a]1029 .where(
1030 and(
1031 eq(buildComponentsTable.buildId, buildId),
1032 eq(buildComponentsTable.componentId, componentId)
1033 )
[e03e6fb]1034 )
1035 .limit(1);
[83fb5e2]1036
[e03e6fb]1037 if (!existing) return null;
1038
1039 if (existing.quantity > 1) {
1040 await tx
1041 .update(buildComponentsTable)
1042 .set({
1043 numComponents: sql`${buildComponentsTable.numComponents} - 1`
1044 })
1045 .where(
1046 and(
1047 eq(buildComponentsTable.buildId, buildId),
1048 eq(buildComponentsTable.componentId, componentId)
1049 )
1050 );
1051 } else {
1052 await tx
1053 .delete(buildComponentsTable)
1054 .where(
1055 and(
1056 eq(buildComponentsTable.buildId, buildId),
1057 eq(buildComponentsTable.componentId, componentId)
1058 )
1059 );
1060 }
[83fb5e2]1061
[8fe7f1a]1062 const buildComponents = await tx
1063 .select({
1064 price: componentsTable.price,
[e03e6fb]1065 quantity: buildComponentsTable.numComponents
[8fe7f1a]1066 })
1067 .from(buildComponentsTable)
1068 .innerJoin(
1069 componentsTable,
1070 eq(buildComponentsTable.componentId, componentsTable.id)
[83fb5e2]1071 )
[8fe7f1a]1072 .where(
1073 eq(buildComponentsTable.buildId, buildId)
1074 );
[83fb5e2]1075
[e03e6fb]1076 const totalPrice = buildComponents.reduce((sum, c) =>
1077 sum + (Number(c.price) * c.quantity), 0
1078 );
[83fb5e2]1079
[8fe7f1a]1080 await tx
1081 .update(buildsTable)
1082 .set({
1083 totalPrice: totalPrice.toFixed(2)
1084 })
1085 .where(
1086 eq(buildsTable.id, buildId)
1087 );
[83fb5e2]1088
[e03e6fb]1089 return componentId;
[8fe7f1a]1090 })
1091}
Note: See TracBrowser for help on using the repository browser.