Changeset 9854393 for database/drizzle/queries/components.ts
- Timestamp:
- 12/27/25 02:27:36 (7 months ago)
- Branches:
- main
- Children:
- ad311c3
- Parents:
- 82aa6ae
- File:
-
- 1 edited
-
database/drizzle/queries/components.ts (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
database/drizzle/queries/components.ts
r82aa6ae r9854393 11 11 ratingBuildsTable, soundCardsTable, storageTable 12 12 } from "../schema"; 13 import {and, desc, eq, ilike} from "drizzle-orm";13 import {and, asc, desc, eq, ilike, SQL, sql} from "drizzle-orm"; 14 14 import {typeConfigMap, ComponentType} from "../config/componentFieldConfig"; 15 15 import {AnyPgColumn} from "drizzle-orm/pg-core"; 16 export async function getAllComponents(db: Database, limit?: number, componentType?: string, q?: string) { 16 import {inArray} from "drizzle-orm/sql/expressions/conditions"; 17 export async function getAllComponents(db: Database, limit?: number, componentType?: string, sort?: string, q?: string) { 17 18 let queryConditions = []; 19 let sortConditions = []; 18 20 19 21 if (q) { … … 21 23 ilike(componentsTable.name, `%${q}%`) 22 24 ); 25 } 26 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; 23 43 } 24 44 … … 45 65 ) 46 66 .orderBy( 47 desc(componentsTable.price)67 ...sortConditions 48 68 ) 49 69 .limit(limit || 100); // 100 placeholder … … 213 233 } 214 234 215 export async function getCompatibleComponents(db: Database, buildId: number, componentType: string) { 216 // TO BE IMPLEMENTED 217 return null; 235 export 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 324 async 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 377 async 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 454 async 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 524 async 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 571 async 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 628 async 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 674 async 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 792 async 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 835 async 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; 218 901 } 219 902
Note:
See TracChangeset
for help on using the changeset viewer.
