Skip to content

Commit d0b5621

Browse files
committed
refactor: randomPartnerList utils
1 parent 864ef6c commit d0b5621

2 files changed

Lines changed: 44 additions & 78 deletions

File tree

Lines changed: 22 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,36 @@
1-
import type { PartnerCategory, Partners } from '#site/types/partners.js';
1+
import crypto from 'node:crypto';
2+
3+
import type {
4+
RandomPartnerListConfig,
5+
Partners,
6+
} from '#site/types/partners.js';
27

38
function randomPartnerList(
49
partners: Array<Partners>,
5-
config: {
6-
/**
7-
* Number of partners to pick from the list.
8-
* If null, all partners will be returned.
9-
*/
10-
pick?: number | null;
11-
/**
12-
* Date seed to use for the randomization.
13-
* This is used to ensure that the same partners are returned for the same date.
14-
*/
15-
dateSeed?: number;
16-
/**
17-
* Category of partners to filter by.
18-
* If not provided, all partners will be returned.
19-
*/
20-
category?: PartnerCategory;
21-
/**
22-
* Whether to randomize the partners or not.
23-
*/
24-
sort?: 'name' | 'weight' | null;
25-
}
10+
config: RandomPartnerListConfig
2611
) {
27-
const { pick = 4, dateSeed = 5, category, sort = 'weight' } = config;
28-
29-
const filteredPartners = [...partners].filter(partner => {
30-
return !category || partner.categories.includes(category);
31-
});
12+
const { pick = 4, dateSeed = 5, category } = config;
3213

33-
if (sort === null) {
34-
return pick !== null ? filteredPartners.slice(0, pick) : filteredPartners;
35-
}
14+
// Generate a deterministic seed based on current time that changes every X minutes
15+
const seed = Math.floor(Date.now() / (dateSeed * 60 * 1000));
3616

37-
if (sort === 'name') {
38-
const shuffled = [...filteredPartners].sort((a, b) =>
39-
a.name.localeCompare(b.name)
40-
);
17+
// Filter by category if provided
18+
const filtered = category
19+
? partners.filter(p => p.categories.includes(category))
20+
: partners;
4121

42-
return pick !== null ? shuffled.slice(0, pick) : shuffled;
43-
}
22+
// Remove duplicate partners
23+
const unique = Array.from(new Set(filtered));
4424

45-
const now = new Date();
46-
const minutes = Math.floor(now.getUTCMinutes() / dateSeed) * dateSeed;
25+
// Create a hash from the seed to use for consistent randomization
26+
const hash = crypto.createHash('sha256').update(String(seed)).digest();
4727

48-
const fixedTime = new Date(
49-
Date.UTC(
50-
now.getUTCFullYear(),
51-
now.getUTCMonth(),
52-
now.getUTCDate(),
53-
now.getUTCHours(),
54-
minutes,
55-
0,
56-
0
57-
)
28+
// Sort partners using the hash to ensure same results for the same seed
29+
const sorted = [...unique].sort(
30+
(a, b) => hash[a.name.charCodeAt(0) % 32] - hash[b.name.charCodeAt(0) % 32]
5831
);
5932

60-
// We create a seed from the rounded date (timestamp in ms)
61-
const seed = fixedTime.getTime();
62-
const rng = mulberry32(seed);
63-
64-
const weightedPartners = filteredPartners.flatMap(partner => {
65-
const weight = partner.weight;
66-
return Array(weight).fill(partner);
67-
});
68-
69-
// Create a copy of the array to avoid modifying the original
70-
const shuffled = [...weightedPartners].sort(() => rng() - 0.5);
71-
72-
// Remove duplicates while preserving order
73-
const unique = Array.from(new Set(shuffled));
74-
75-
if (pick !== null) {
76-
return unique.slice(0, pick);
77-
}
78-
79-
return unique;
80-
}
81-
82-
// This function returns a random list of partners based on a fixed time seed
83-
function mulberry32(seed: number) {
84-
return function () {
85-
let t = (seed += 0x6d2b79f5);
86-
t = Math.imul(t ^ (t >>> 15), t | 1);
87-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
88-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
89-
};
33+
return sorted.slice(0, pick ?? sorted.length);
9034
}
9135

9236
export { randomPartnerList };

apps/site/types/partners.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,25 @@ export type Supporters = {
2424
url: string;
2525
source: 'opencollective' | 'github';
2626
};
27+
28+
export type RandomPartnerListConfig = {
29+
/**
30+
* Number of partners to pick from the list.
31+
* If null, all partners will be returned.
32+
*/
33+
pick?: number | null;
34+
/**
35+
* Date seed to use for the randomization.
36+
* This is used to ensure that the same partners are returned for the same date.
37+
*/
38+
dateSeed?: number;
39+
/**
40+
* Category of partners to filter by.
41+
* If not provided, all partners will be returned.
42+
*/
43+
category?: PartnerCategory;
44+
/**
45+
* Whether to randomize the partners or not.
46+
*/
47+
sort?: 'name' | 'weight' | null;
48+
};

0 commit comments

Comments
 (0)