source: database/drizzle/queries/components.ts@ 8fe7f1a

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

Refactor queries and telefunctions

  • Property mode set to 100644
File size: 31.6 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, 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, 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, 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 if (existing.cooler?.details?.cpuSockets) {
365 const coolerSockets = existing.cooler.details.cpuSockets.map((s: any) => s.socket);
366 return cpus.filter(cpu => coolerSockets.includes(cpu.socket));
367 }
368
369 return cpus;
370}
371
372async function getCompatibleMotherboards(db: Database, existing: any, pciExpressSlotsUsed: number, limit?: number, sortCondition?: any) {
373 const conditions = [eq(componentsTable.type, 'motherboard')];
374
375 if (existing.cpu) {
376 conditions.push(
377 eq(motherboardsTable.socket, existing.cpu.details.socket)
378 );
379 }
380
381 if (existing.memory.length > 0) {
382 const ramType = existing.memory[0].details.type;
383 conditions.push(
384 eq(motherboardsTable.ramType, ramType)
385 );
386 }
387
388 if (pciExpressSlotsUsed > 0) {
389 conditions.push(
390 sql`${motherboardsTable.pciExpressSlots} >= ${pciExpressSlotsUsed}`
391 );
392 }
393
394 const motherboards = await db
395 .select({
396 id: componentsTable.id,
397 name: componentsTable.name,
398 brand: componentsTable.brand,
399 price: componentsTable.price,
400 imgUrl: componentsTable.imgUrl,
401 type: componentsTable.type,
402 socket: motherboardsTable.socket,
403 chipset: motherboardsTable.chipset,
404 formFactor: motherboardsTable.formFactor,
405 ramType: motherboardsTable.ramType,
406 numRamSlots: motherboardsTable.numRamSlots,
407 maxRamCapacity: motherboardsTable.maxRamCapacity,
408 pciExpressSlots: motherboardsTable.pciExpressSlots,
409 })
410 .from(componentsTable)
411 .innerJoin(
412 motherboardsTable,
413 eq(componentsTable.id, motherboardsTable.componentId)
414 )
415 .where(
416 and(
417 ...conditions
418 )
419 )
420 .orderBy(
421 sortCondition
422 )
423 .limit(limit || 100);
424
425 const compatibleMotherboards = motherboards.filter(mobo => {
426 if (existing.memory.length > 0) {
427 const totalModules = existing.memory.reduce((sum: number, m: any) =>
428 sum + Number(m.details.modules), 0
429 );
430 const totalCapacity = existing.memory.reduce((sum: number, m: any) =>
431 sum + Number(m.details.capacity), 0
432 );
433
434 if (totalModules > Number(mobo.numRamSlots)) return false;
435 if (totalCapacity > Number(mobo.maxRamCapacity)) return false;
436 }
437
438 return true;
439 });
440
441 if (existing.case?.details?.moboFormFactors) {
442 const caseFormFactors = existing.case.details.moboFormFactors.map((f: any) => f.formFactor);
443 return compatibleMotherboards.filter(mobo => caseFormFactors.includes(mobo.formFactor));
444 }
445
446 return compatibleMotherboards;
447}
448
449async function getCompatibleCoolers(db: Database, existing: any, limit?: number, sortCondition?: any) {
450 const conditions = [eq(componentsTable.type, 'cooler')];
451
452 if (existing.cpu) {
453 conditions.push(
454 sql`${coolersTable.maxTdpSupported} >= ${existing.cpu.details.tdp}`
455 );
456 }
457
458 if (existing.case) {
459 conditions.push(
460 sql`${coolersTable.height} <= ${existing.case.details.coolerMaxHeight}`
461 );
462 }
463
464 const coolers = await db
465 .select({
466 id: componentsTable.id,
467 name: componentsTable.name,
468 brand: componentsTable.brand,
469 price: componentsTable.price,
470 imgUrl: componentsTable.imgUrl,
471 type: componentsTable.type,
472 coolerType: coolersTable.type,
473 height: coolersTable.height,
474 maxTdpSupported: coolersTable.maxTdpSupported,
475 })
476 .from(componentsTable)
477 .innerJoin(
478 coolersTable,
479 eq(componentsTable.id, coolersTable.componentId)
480 )
481 .where(
482 and(
483 ...conditions
484 )
485 )
486 .orderBy(
487 sortCondition
488 )
489 .limit(limit || 100);
490
491
492 if(existing.cpu && coolers.length > 0) {
493 const coolerIds = coolers.map(c => c.id);
494
495 const allSockets = await db
496 .select({
497 coolerId: coolerCPUSocketsTable.coolerId,
498 socket: coolerCPUSocketsTable.socket
499 })
500 .from(coolerCPUSocketsTable)
501 .where(
502 inArray(coolerCPUSocketsTable.coolerId, coolerIds)
503 );
504 const socketsByCooler = allSockets.reduce((acc, { coolerId, socket }) => {
505 if (!acc[coolerId]) acc[coolerId] = [];
506 acc[coolerId].push(socket);
507 return acc;
508 }, {} as Record<number, string[]>);
509
510 return coolers.filter(cooler => {
511 const sockets = socketsByCooler[cooler.id] || [];
512 return sockets.includes(existing.cpu.details.socket);
513 });
514 }
515
516 return coolers;
517}
518
519async function getCompatibleGPUs(db: Database, existing: any, pciExpressSlotsUsed: number, limit?: number, sortCondition?: any) {
520 const conditions = [eq(componentsTable.type, 'gpu')];
521
522 if (existing.case) {
523 conditions.push(
524 sql`${GPUTable.length} <= ${existing.case.details.gpuMaxLength}`
525 );
526 }
527
528 const gpus = await db
529 .select({
530 id: componentsTable.id,
531 name: componentsTable.name,
532 brand: componentsTable.brand,
533 price: componentsTable.price,
534 imgUrl: componentsTable.imgUrl,
535 type: componentsTable.type,
536 vram: GPUTable.vram,
537 tdp: GPUTable.tdp,
538 baseClock: GPUTable.baseClock,
539 boostClock: GPUTable.boostClock,
540 chipset: GPUTable.chipset,
541 length: GPUTable.length,
542 })
543 .from(componentsTable)
544 .innerJoin(
545 GPUTable,
546 eq(componentsTable.id, GPUTable.componentId)
547 )
548 .where(
549 and(
550 ...conditions
551 )
552 )
553 .orderBy(
554 sortCondition
555 )
556 .limit(limit || 100);
557
558 if (existing.motherboard) {
559 const availableSlots = Number(existing.motherboard.details.pciExpressSlots);
560 return gpus.filter(() => (pciExpressSlotsUsed + 1) <= availableSlots);
561 }
562
563 return gpus;
564}
565
566async function getCompatibleMemory(db: Database, existing: any, limit?: number, sortCondition?: any) {
567 const conditions = [eq(componentsTable.type, 'memory')];
568
569 if (existing.motherboard) {
570 conditions.push(
571 eq(memoryTable.type, existing.motherboard.details.ramType)
572 );
573 }
574
575 const memory = await db
576 .select({
577 id: componentsTable.id,
578 name: componentsTable.name,
579 brand: componentsTable.brand,
580 price: componentsTable.price,
581 imgUrl: componentsTable.imgUrl,
582 type: componentsTable.type,
583 memoryType: memoryTable.type,
584 speed: memoryTable.speed,
585 capacity: memoryTable.capacity,
586 modules: memoryTable.modules,
587 })
588 .from(componentsTable)
589 .innerJoin(
590 memoryTable,
591 eq(componentsTable.id, memoryTable.componentId)
592 )
593 .where(
594 and(
595 ...conditions
596 )
597 )
598 .orderBy(
599 sortCondition
600 )
601 .limit(limit || 100);
602
603 if (existing.motherboard) {
604 const existingModules = existing.memory.reduce((sum: number, m: any) =>
605 sum + Number(m.details.modules), 0
606 );
607 const existingCapacity = existing.memory.reduce((sum: number, m: any) =>
608 sum + Number(m.details.capacity), 0
609 );
610 const maxSlots = Number(existing.motherboard.details.numRamSlots);
611 const maxCapacity = Number(existing.motherboard.details.maxRamCapacity);
612
613 return memory.filter(mem => {
614 const newModules = existingModules + Number(mem.modules);
615 const newCapacity = existingCapacity + Number(mem.capacity);
616 return newModules <= maxSlots && newCapacity <= maxCapacity;
617 });
618 }
619
620 return memory;
621}
622
623async function getCompatibleStorage(db: Database, existing: any, limit?: number, sortCondition?: any) {
624 const storage = await db
625 .select({
626 id: componentsTable.id,
627 name: componentsTable.name,
628 brand: componentsTable.brand,
629 price: componentsTable.price,
630 imgUrl: componentsTable.imgUrl,
631 type: componentsTable.type,
632 storageType: storageTable.type,
633 capacity: storageTable.capacity,
634 formFactor: storageTable.formFactor,
635 })
636 .from(componentsTable)
637 .innerJoin(
638 storageTable,
639 eq(componentsTable.id, storageTable.componentId
640 )
641 )
642 .where(
643 eq(componentsTable.type, 'storage')
644 )
645 .orderBy(
646 sortCondition
647 )
648 .limit(limit || 100);
649
650 if (existing.case?.details?.storageFormFactors) {
651 return storage.filter(stor => {
652 const caseStorageFormFactor = existing.case.details.storageFormFactors.find(
653 (sf: any) => sf.formFactor === stor.formFactor
654 );
655
656 if (!caseStorageFormFactor) return false;
657
658 const usedSlots = existing.storage.filter(
659 (s: any) => s.details.formFactor === stor.formFactor
660 ).length;
661
662 return usedSlots < Number(caseStorageFormFactor.numSlots);
663 });
664 }
665
666 return storage;
667}
668
669async function getCompatibleCases(db: Database, existing: any, limit?: number, sortCondition?: any) {
670 const conditions = [eq(componentsTable.type, 'case')];
671
672 if (existing.gpu) {
673 conditions.push(
674 sql`${pcCasesTable.gpuMaxLength} >= ${existing.gpu.details.length}`
675 );
676 }
677
678 if (existing.cooler) {
679 conditions.push(
680 sql`${pcCasesTable.coolerMaxHeight} >= ${existing.cooler.details.height}`
681 );
682 }
683
684 const cases = await db
685 .select({
686 id: componentsTable.id,
687 name: componentsTable.name,
688 brand: componentsTable.brand,
689 price: componentsTable.price,
690 imgUrl: componentsTable.imgUrl,
691 type: componentsTable.type,
692 coolerMaxHeight: pcCasesTable.coolerMaxHeight,
693 gpuMaxLength: pcCasesTable.gpuMaxLength,
694 })
695 .from(componentsTable)
696 .innerJoin(
697 pcCasesTable,
698 eq(componentsTable.id, pcCasesTable.componentId)
699 )
700 .where(
701 and(
702 ...conditions)
703 )
704 .orderBy(
705 sortCondition
706 )
707 .limit(limit || 100);
708
709 if(cases.length === 0) return [];
710
711 const caseIds = cases.map(c => c.id);
712
713 const [allStorageFF, allPsFF, allMoboFF] = await Promise.all([
714 db.select().from(caseStorageFormFactorsTable)
715 .where(
716 inArray(caseStorageFormFactorsTable.caseId, caseIds)
717 ),
718 db.select().from(casePsFormFactorsTable)
719 .where(
720 inArray(casePsFormFactorsTable.caseId, caseIds)
721 ),
722 db.select().from(caseMoboFormFactorsTable)
723 .where(
724 inArray(caseMoboFormFactorsTable.caseId, caseIds)
725 )
726 ]);
727
728 const storageByCase = allStorageFF.reduce((acc, ff) => {
729 if (!acc[ff.caseId]) acc[ff.caseId] = [];
730 acc[ff.caseId].push(ff);
731 return acc;
732 }, {} as Record<number, any[]>);
733
734 const psByCase = allPsFF.reduce((acc, ff) => {
735 if (!acc[ff.caseId]) acc[ff.caseId] = [];
736 acc[ff.caseId].push(ff);
737 return acc;
738 }, {} as Record<number, any[]>);
739
740 const moboByCase = allMoboFF.reduce((acc, ff) => {
741 if (!acc[ff.caseId]) acc[ff.caseId] = [];
742 acc[ff.caseId].push(ff);
743 return acc;
744 }, {} as Record<number, any[]>);
745
746 const casesWithDetails = cases.map(pcCase => ({
747 ...pcCase,
748 storageFormFactors: storageByCase[pcCase.id] || [],
749 psFormFactors: psByCase[pcCase.id] || [],
750 moboFormFactors: moboByCase[pcCase.id] || []
751 }));
752
753 let filteredCases = casesWithDetails;
754
755 if (existing.motherboard) {
756 filteredCases = filteredCases.filter(pcCase =>
757 pcCase.moboFormFactors.some((f: any) => f.formFactor === existing.motherboard.details.formFactor)
758 );
759 }
760
761 if (existing.psu) {
762 filteredCases = filteredCases.filter(pcCase =>
763 pcCase.psFormFactors.some((f: any) => f.formFactor === existing.psu.details.formFactor)
764 );
765 }
766
767 if (existing.storage.length > 0) {
768 filteredCases = filteredCases.filter(pcCase => {
769 return existing.storage.every((stor: any) => {
770 const storageFF = pcCase.storageFormFactors.find(
771 (sf: any) => sf.formFactor === stor.details.formFactor
772 );
773 if (!storageFF) return false;
774
775 const usedSlots = existing.storage.filter(
776 (s: any) => s.details.formFactor === stor.details.formFactor
777 ).length;
778
779 return usedSlots <= Number(storageFF.numSlots);
780 });
781 });
782 }
783
784 return filteredCases;
785}
786
787async function getCompatiblePSUs(db: Database, existing: any, existingTDP: number, limit?: number, sortCondition?: any) {
788 const conditions = [eq(componentsTable.type, 'power_supply')];
789
790 const minWattage = existingTDP * 1.2;
791 conditions.push(
792 sql`${powerSupplyTable.wattage} >= ${minWattage}`
793 );
794
795 const psus = await db
796 .select({
797 id: componentsTable.id,
798 name: componentsTable.name,
799 brand: componentsTable.brand,
800 price: componentsTable.price,
801 imgUrl: componentsTable.imgUrl,
802 type: componentsTable.type,
803 psuType: powerSupplyTable.type,
804 wattage: powerSupplyTable.wattage,
805 formFactor: powerSupplyTable.formFactor,
806 })
807 .from(componentsTable)
808 .innerJoin(
809 powerSupplyTable,
810 eq(componentsTable.id, powerSupplyTable.componentId)
811 )
812 .where(
813 and(
814 ...conditions
815 )
816 )
817 .orderBy(
818 sortCondition
819 )
820 .limit(limit || 100);
821
822 if (existing.case?.details?.psFormFactors) {
823 const casePSFormFactors = existing.case.details.psFormFactors.map((f: any) => f.formFactor);
824 return psus.filter(psu => casePSFormFactors.includes(psu.formFactor));
825 }
826
827 return psus;
828}
829
830async function getCompatiblePCIeComponents(db: Database, existing: any, componentType: string, pciExpressSlotsUsed: number, limit?: number, sortCondition?: any) {
831 let table: any;
832 let selectFields: any = {
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 };
840
841 switch (componentType) {
842 case 'network_card':
843 table = networkCardsTable;
844 selectFields = {
845 ...selectFields,
846 numPorts: networkCardsTable.numPorts,
847 speed: networkCardsTable.speed,
848 interface: networkCardsTable.interface,
849 };
850 break;
851 case 'network_adapter':
852 table = networkAdaptersTable;
853 selectFields = {
854 ...selectFields,
855 wifiVersion: networkAdaptersTable.wifiVersion,
856 interface: networkAdaptersTable.interface,
857 numAntennas: networkAdaptersTable.numAntennas,
858 };
859 break;
860 case 'sound_card':
861 table = soundCardsTable;
862 selectFields = {
863 ...selectFields,
864 sampleRate: soundCardsTable.sampleRate,
865 bitDepth: soundCardsTable.bitDepth,
866 chipset: soundCardsTable.chipset,
867 interface: soundCardsTable.interface,
868 channel: soundCardsTable.channel,
869 };
870 break;
871 default:
872 return [];
873 }
874
875 const components = await db
876 .select(selectFields)
877 .from(componentsTable)
878 .innerJoin(
879 table,
880 eq(componentsTable.id, table.componentId)
881 )
882 .where(
883 eq(componentsTable.type, componentType)
884 )
885 .orderBy(
886 sortCondition
887 )
888 .limit(limit || 100);
889
890 if (existing.motherboard) {
891 const availableSlots = Number(existing.motherboard.details.pciExpressSlots);
892 return components.filter(() => (pciExpressSlotsUsed + 1) <= availableSlots);
893 }
894
895 return components;
896}
897
898export async function addComponentToBuild(db: Database, userId: number, buildId: number, componentId: number) {
899 return db.transaction(async (tx) => {
900 const [build] = await tx
901 .select()
902 .from(buildsTable)
903 .where(
904 and(
905 eq(buildsTable.id, buildId),
906 eq(buildsTable.userId, userId)
907 )
908 )
909 .limit(1);
910
911 if(!build || build.isApproved) return null;
912
913 const existing = await tx
914 .select()
915 .from(buildComponentsTable)
916 .where(
917 and(
918 eq(buildComponentsTable.buildId, buildId),
919 eq(buildComponentsTable.componentId, componentId)
920 )
921 )
922 .limit(1);
923
924 if (existing.length) return null;
925
926 await tx
927 .insert(buildComponentsTable)
928 .values({
929 buildId,
930 componentId
931 });
932
933 const buildComponents = await tx
934 .select({
935 price: componentsTable.price,
936 })
937 .from(buildComponentsTable)
938 .innerJoin(
939 componentsTable,
940 eq(buildComponentsTable.componentId, componentsTable.id)
941 )
942 .where(
943 eq(buildComponentsTable.buildId, buildId)
944 );
945
946 const totalPrice = buildComponents.reduce((sum, c) => sum + Number(c.price), 0);
947
948 await tx
949 .update(buildsTable)
950 .set({
951 totalPrice: totalPrice.toFixed(2)
952 })
953 .where(
954 eq(buildsTable.id, buildId)
955 );
956
957 return buildId;
958 })
959}
960
961export async function removeComponentFromBuild(db: Database, userId: number, buildId: number, componentId: number) {
962 return db.transaction(async (tx) => {
963 const [build] = await tx
964 .select()
965 .from(buildsTable)
966 .where(
967 and(
968 eq(buildsTable.id, buildId),
969 eq(buildsTable.userId, userId)
970 )
971 )
972 .limit(1);
973
974 if(!build || build.isApproved) return null;
975
976 const result = await tx
977 .delete(buildComponentsTable)
978 .where(
979 and(
980 eq(buildComponentsTable.buildId, buildId),
981 eq(buildComponentsTable.componentId, componentId)
982 )
983 );
984
985 if(result.rowCount === 0) return null;
986
987 const buildComponents = await tx
988 .select({
989 price: componentsTable.price,
990 })
991 .from(buildComponentsTable)
992 .innerJoin(
993 componentsTable,
994 eq(buildComponentsTable.componentId, componentsTable.id)
995 )
996 .where(
997 eq(buildComponentsTable.buildId, buildId)
998 );
999
1000 const totalPrice = buildComponents.reduce((sum, c) => sum + Number(c.price), 0);
1001
1002 await tx
1003 .update(buildsTable)
1004 .set({
1005 totalPrice: totalPrice.toFixed(2)
1006 })
1007 .where(
1008 eq(buildsTable.id, buildId)
1009 );
1010
1011 return result;
1012 })
1013}
Note: See TracBrowser for help on using the repository browser.