Copilot commented on code in PR #362:
URL: https://github.com/apache/kvrocks-website/pull/362#discussion_r2941485521


##########
src/data/people.json:
##########
@@ -0,0 +1,204 @@
+{
+  "blogAuthors": {
+    "hulk": {
+      "name": "Hulk Lin",
+      "title": "Apache Kvrocks PMC Member",
+      "url": "https://github.com/git-hulk";,
+      "githubId": "git-hulk"
+    },
+    "vmihailenco": {
+      "name": "Vladimir Mihailenco",
+      "title": "Grumpy Gopher",
+      "url": "https://github.com/vmihailenco";,
+      "githubId": "vmihailenco"
+    },
+    "twice": {
+      "name": "PragmaTwice",
+      "title": "Apache Kvrocks PMC Member",
+      "url": "https://github.com/pragmatwice";,
+      "githubId": "pragmatwice"
+    }
+  },
+  "committers": [
+    {
+      "name": "Aleks Lozoviuk",
+      "apacheId": "aleksraiden",
+      "githubId": "aleksraiden",
+      "isPMC": true
+    },
+    {
+      "name": "Donghui Liu",
+      "apacheId": "alfejik",
+      "githubId": "Alfejik",
+      "isPMC": true
+    },
+    {
+      "name": "Agnik Misra",
+      "apacheId": "agnik",
+      "githubId": "Jitmisra",
+      "isPMC": false
+    },
+    {
+      "name": "Beihao Zhou",
+      "apacheId": "beihao",
+      "githubId": "Beihao-Zhou",
+      "isPMC": false
+    },
+    {
+      "name": "Binbin Zhu",
+      "apacheId": "binbin",
+      "githubId": "enjoy-binbin",
+      "isPMC": false
+    },
+    {
+      "name": "Brad Lee",
+      "apacheId": "bradlee",
+      "githubId": "smartlee",
+      "isPMC": false
+    },
+    {
+      "name": "Pengbo Cai",
+      "apacheId": "caipengbo",
+      "githubId": "caipengbo",
+      "isPMC": true
+    },
+    {
+      "name": "Liang Chen",
+      "apacheId": "chenliang613",
+      "githubId": "chenliang613",
+      "isPMC": true
+    },
+    {
+      "name": "Chris Zou",
+      "apacheId": "chriszou",
+      "githubId": "ChrisZMF",
+      "isPMC": false
+    },
+    {
+      "name": "Colin Chamber",
+      "apacheId": "colinchamber",
+      "githubId": "ColinChamber",
+      "isPMC": false
+    },
+    {
+      "name": "Rongxing Xiao",
+      "apacheId": "deemo",
+      "githubId": "yezhizi",
+      "isPMC": false
+    },
+    {
+      "name": "Edward Xu",
+      "apacheId": "edwardxu",
+      "githubId": "LindaSummer",
+      "isPMC": false
+    },
+    {
+      "name": "Xiaoqiao He",
+      "apacheId": "hexiaoqiao",
+      "githubId": "Hexiaoqiao",
+      "isPMC": true
+    },
+    {
+      "name": "Hulk Lin",
+      "apacheId": "hulk",
+      "githubId": "git-hulk",
+      "isPMC": true
+    },
+    {
+      "name": "Jean-Baptiste Onofré",
+      "apacheId": "jbonofre",
+      "githubId": "jbonofre",
+      "isPMC": true
+    },
+    {
+      "name": "Jean Lai",
+      "apacheId": "jeanbone",
+      "githubId": "jeanbone",
+      "isPMC": false
+    },
+    {
+      "name": "Ji Huayu",
+      "apacheId": "jihuayu",
+      "githubId": "jihuayu",
+      "isPMC": false
+    },
+    {
+      "name": "Miuyong Liu",
+      "apacheId": "karelrooted",
+      "githubId": "karelrooted",
+      "isPMC": false
+    },
+    {
+      "name": "Xuwei Fu",
+      "apacheId": "maplefu",
+      "githubId": "mapleFU",
+      "isPMC": true
+    },
+    {
+      "name": "Shang Xiong",
+      "apacheId": "shang",
+      "githubId": "shangxiaoxiong",
+      "isPMC": false
+    },
+    {
+      "name": "SiLe Zhou",
+      "apacheId": "silezhou",
+      "githubId": "PokIsemaine",
+      "isPMC": false
+    },
+    {
+      "name": "Xiaojun Yuan",
+      "apacheId": "sryanyuan",
+      "githubId": "sryanyuan",
+      "isPMC": false
+    },
+    {
+      "name": "Ruixiang Tan",
+      "apacheId": "tanruixiang",
+      "githubId": "tanruixiang",
+      "isPMC": false
+    },
+    {
+      "name": "Zili Chen",
+      "apacheId": "tison",
+      "githubId": "tisonkun",
+      "isPMC": true
+    },
+    {
+      "name": "Yaroslav Stepanchuk",
+      "apacheId": "torwig",
+      "githubId": "torwig",
+      "isPMC": true
+    },
+    {
+      "name": "Mingyang Liu",
+      "apacheId": "twice",
+      "githubId": "PragmaTwice",

Review Comment:
   `people.committers` includes a `githubId` with different casing than the 
same person in `blogAuthors` (e.g., `PragmaTwice` vs `pragmatwice`). The avatar 
sync script writes files using `githubId` as the filename, so this can cause 
duplicate downloads and (on case-insensitive file systems) filename 
collisions/overwrites. Normalize `githubId` casing in this data (and ideally 
keep one canonical value across authors/committers).
   



##########
scripts/sync-github-avatars.js:
##########
@@ -0,0 +1,175 @@
+#!/usr/bin/env node
+
+const fs = require("fs/promises");
+const path = require("path");
+const http = require("http");
+const https = require("https");
+
+const people = require("../src/data/people.json");
+
+const rootDir = path.resolve(__dirname, "..");
+const avatarDir = path.join(rootDir, "static/generated/avatars/github");
+const generatedAuthorsPath = path.join(rootDir, "blog/authors.generated.yml");
+const placeholderAvatarPath = "/img/avatar-placeholder.svg";
+const forceRefresh = /^(1|true)$/i.test(process.env.FORCE_SYNC_AVATARS || "");
+
+function avatarPublicPath(githubId) {
+  return `/generated/avatars/github/${githubId}.png`;
+}
+
+function getGithubIds() {
+  const ids = new Set();
+
+  for (const committer of people.committers) {
+    ids.add(committer.githubId);
+  }
+
+  for (const author of Object.values(people.blogAuthors)) {
+    if (author.githubId) {
+      ids.add(author.githubId);
+    }
+  }
+
+  return [...ids].sort((left, right) => left.localeCompare(right));
+}

Review Comment:
   `getGithubIds()` collects raw `githubId` strings and later uses them as 
filenames. Because GitHub logins are case-insensitive, mixed-case IDs can lead 
to duplicate entries and (on case-insensitive file systems) avatar file 
collisions/overwrites. Consider normalizing IDs (e.g., `toLowerCase()`) for the 
cache key/filename, while optionally preserving original casing only for 
display.



##########
src/components/Committers/index.tsx:
##########
@@ -59,7 +34,12 @@ export default function Committers(): JSX.Element {
                 .map(v => (
                     <tr key={v.name}>
                         <td><img width={64} 
className={styles.contributorAvatar}
-                                 src={`https://github.com/${v.githubId}.png`} 
alt={v.name}/></td>
+                                 src={avatarPath(v.githubId)}
+                                 onError={(event) => {
+                                     event.currentTarget.onerror = null;
+                                     event.currentTarget.src = 
placeholderAvatar;
+                                 }}
+                                 alt={v.name}/></td>
                         <td>{v.isPMC ? <b>{v.name}</b> : v.name}</td>
                         <td>{v.apacheId}</td>
                         <td><a target={"_blank"} 
href={`https://github.com/${v.githubId}`}>{v.githubId}</a></td>

Review Comment:
   Links opened with `target="_blank"` should set `rel="noopener noreferrer"` 
to prevent reverse-tabnabbing and avoid leaking `window.opener` to the new page.
   



##########
src/components/Committers/index.tsx:
##########
@@ -59,7 +34,12 @@ export default function Committers(): JSX.Element {
                 .map(v => (

Review Comment:
   `.sort(...)` mutates the underlying `committers` array in-place during 
render. Since `committers` is module-level and sourced from 
`people.committers`, this introduces a side effect and does unnecessary work on 
every render. Use a non-mutating pattern (e.g., sort a copied array or memoize 
the sorted list) to keep render pure.



##########
scripts/sync-github-avatars.js:
##########
@@ -0,0 +1,175 @@
+#!/usr/bin/env node
+
+const fs = require("fs/promises");
+const path = require("path");
+const http = require("http");
+const https = require("https");
+
+const people = require("../src/data/people.json");
+
+const rootDir = path.resolve(__dirname, "..");
+const avatarDir = path.join(rootDir, "static/generated/avatars/github");
+const generatedAuthorsPath = path.join(rootDir, "blog/authors.generated.yml");
+const placeholderAvatarPath = "/img/avatar-placeholder.svg";
+const forceRefresh = /^(1|true)$/i.test(process.env.FORCE_SYNC_AVATARS || "");
+
+function avatarPublicPath(githubId) {
+  return `/generated/avatars/github/${githubId}.png`;
+}
+
+function getGithubIds() {
+  const ids = new Set();
+
+  for (const committer of people.committers) {
+    ids.add(committer.githubId);
+  }
+
+  for (const author of Object.values(people.blogAuthors)) {
+    if (author.githubId) {
+      ids.add(author.githubId);
+    }
+  }
+
+  return [...ids].sort((left, right) => left.localeCompare(right));
+}
+
+async function exists(filePath) {
+  try {
+    await fs.access(filePath);
+    return true;
+  } catch {
+    return false;
+  }
+}
+
+function download(url, redirects = 0) {
+  if (redirects > 5) {
+    return Promise.reject(new Error(`Too many redirects for ${url}`));
+  }
+
+  const client = url.startsWith("https:") ? https : http;
+
+  return new Promise((resolve, reject) => {
+    const request = client.get(
+      url,
+      {
+        headers: {
+          "user-agent": "kvrocks-website-avatar-sync",
+        },
+      },
+      (response) => {
+        const { statusCode = 0, headers } = response;
+
+        if (statusCode >= 300 && statusCode < 400 && headers.location) {
+          response.resume();
+          const nextUrl = new URL(headers.location, url).toString();
+          resolve(download(nextUrl, redirects + 1));
+          return;
+        }
+
+        if (statusCode !== 200) {
+          response.resume();
+          reject(new Error(`Unexpected status ${statusCode} for ${url}`));
+          return;
+        }
+
+        const chunks = [];
+        response.on("data", (chunk) => chunks.push(chunk));
+        response.on("end", () => resolve(Buffer.concat(chunks)));
+      }
+    );
+
+    request.on("error", reject);
+  });
+}

Review Comment:
   `download()` does not set any request/response timeout, so `yarn 
start`/`yarn build` (which run this script via prestart/prebuild) can hang 
indefinitely on stalled connections. Add a timeout and abort/destroy the 
request (and consider a per-download retry/backoff) so builds fail fast and 
predictably.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to