source: database/drizzle/queries/components.ts@ ae5d054

main
Last change on this file since ae5d054 was ae5d054, checked in by Tome <gjorgievtome@…>, 7 months ago

Fix compatibility issues

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