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

main
Last change on this file since 8a7f936 was 8a7f936, checked in by Mihail <mihail2.naumov@…>, 6 months ago

Added Forge Page, added suggest component, fixed styling

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