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

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

Dockerize app

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