source: database/drizzle/queries/components.ts@ 80f6cdb

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

Add imgUrl to components query

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