Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions api/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {
fetchMergedPullRequests,
fetchUserTotalStars,
fetchRecentPRLinesChanged,
fetchOpenPullRequests // Added this
fetchOpenPullRequests
} = require("../src/github");
const { getTheme, applyColorOverrides } = require("../src/themes");
const { generateMasterCardSVG } = require("../src/svg-master");
Expand All @@ -40,7 +40,7 @@ module.exports = async (req, res) => {
}

try {
const cacheKey = `master_full_v3:${username.toLowerCase()}`;
const cacheKey = `master_ultimate_v1:${username.toLowerCase()}`;

if (refresh === "true") {
clearCache(cacheKey);
Expand Down Expand Up @@ -85,6 +85,13 @@ module.exports = async (req, res) => {
else tempStreak = 0;
}

// Commit Ranking logic (by day of week)
const weekMap = { 0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0 };
days.forEach(d => {
const dayOfWeek = new Date(d.date).getDay();
weekMap[dayOfWeek] += d.count;
});

const repoMap = {};
(prs || []).slice(0, 50).forEach(pr => {
if (pr.repository_url) {
Expand All @@ -98,6 +105,7 @@ module.exports = async (req, res) => {

data = {
username: profile?.login || username,
name: profile?.name || profile?.login || username,
totalPRs: prs?.length || 0,
openPRs: openPRCount || 0,
mergedPRs: mergedPRCount || 0,
Expand All @@ -113,18 +121,19 @@ module.exports = async (req, res) => {
openIssues: openIssues || 0,
closedIssues: closedIssues || 0,
totalStars: totalStars || 0,
linesChanged: linesChanged || 0
linesChanged: linesChanged || 0,
weekMap
};

setCache(cacheKey, data, CACHE_TTL);
} catch (fetchErr) {
console.error("Mastercard Optimized Fetch error:", fetchErr.message);
console.error("Mastercard Ultimate Fetch error:", fetchErr.message);
data = {
username: username, totalPRs: 0, openPRs: 0, mergedPRs: 0,
username: username, name: username, totalPRs: 0, openPRs: 0, mergedPRs: 0,
repoCount: 0, languages: [], contributions: 0, repoList: [],
contributionDays: [], currentStreak: 0, longestStreak: 0,
totalCommits: 0, totalIssues: 0, openIssues: 0, closedIssues: 0,
totalStars: 0, linesChanged: 0
totalStars: 0, linesChanged: 0, weekMap: {}
};
}
}
Expand All @@ -146,8 +155,8 @@ module.exports = async (req, res) => {
};

function errorSVG(msg) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="820" height="120" viewBox="0 0 820 120">
<rect width="820" height="120" fill="#0d1117" rx="8"/>
<text x="410" y="65" text-anchor="middle" font-family="-apple-system,sans-serif" font-size="13" fill="#f85149">${msg}</text>
return `<svg xmlns="http://www.w3.org/2000/svg" width="830" height="120" viewBox="0 0 830 120">
<rect width="830" height="120" fill="#0d1117" rx="8"/>
<text x="415" y="65" text-anchor="middle" font-family="-apple-system,sans-serif" font-size="13" fill="#f85149">${msg}</text>
</svg>`;
}
169 changes: 86 additions & 83 deletions src/svg-master.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Master Card SVG generation - Complete Profile Dashboard (820px).
* A unique, high-impact card for GitHub README.
* Master Card SVG generation - Ultimate Profile Dashboard (830px).
* Designed to cover the full width of a GitHub README with a premium look.
*/

function getContributionColorByLevel(level) {
Expand All @@ -14,16 +14,16 @@ function escapeXml(s) {

function generateMasterCardSVG(options) {
const {
username, totalPRs = 0, openPRs = 0, mergedPRs = 0, repoCount = 0,
username, name, totalPRs = 0, openPRs = 0, mergedPRs = 0, repoCount = 0,
languages = [], contributions = 0, totalCommits = 0, repoList = [],
contributionDays = [], currentStreak = 0, longestStreak = 0,
totalIssues = 0, openIssues = 0, closedIssues = 0, totalStars = 0,
linesChanged = 0, colors, hideBorder, cardWidth = 820
linesChanged = 0, weekMap = {}, colors, hideBorder, cardWidth = 830
} = options;

const pad = 28;
const pad = 30;
const innerW = cardWidth - pad * 2;
const hba = hideBorder ? `rx="14"` : `rx="14" stroke="#30363d" stroke-width="1.5"`;
const hba = hideBorder ? `rx="16"` : `rx="16" stroke="#30363d" stroke-width="2"`;

const accentColor = (colors && colors.accent_color) || "58a6ff";
const titleColor = (colors && colors.title_color) || "58a6ff";
Expand All @@ -32,143 +32,146 @@ function generateMasterCardSVG(options) {
let y = 0;
let content = "";

// --- HEADER SECTION ---
// --- TOP HEADER ---
content += `
<g transform="translate(${pad}, 36)">
<text x="0" y="26" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="30" font-weight="900" fill="#${titleColor}">${escapeXml(username)}'s Profile</text>
<rect x="0" y="42" width="70" height="4" fill="#${accentColor}" rx="2"/>
<text x="${innerW}" y="26" text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" font-weight="600" fill="#8b949e" style="text-transform:uppercase;letter-spacing:1.5px">Master Dashboard</text>
<g transform="translate(${pad}, 40)">
<text x="0" y="24" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="32" font-weight="900" fill="#${titleColor}">${escapeXml(name || username)}</text>
<text x="0" y="46" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14" font-weight="600" fill="#8b949e">@${escapeXml(username.toLowerCase())} &bull; Full Profile Dashboard</text>
<rect x="0" y="58" width="80" height="5" fill="#${accentColor}" rx="2.5"/>
</g>`;
y = 110;
y = 125;

// --- HERO METRICS (5 Key stats) ---
const statW = Math.floor(innerW / 5);
// --- HERO STATS ROW (6 metrics) ---
const statW = Math.floor(innerW / 6);
const heroStats = [
{ label: "Commits", value: totalCommits.toLocaleString(), color: accentColor },
{ label: "Stars", value: totalStars.toLocaleString(), color: "eab308" },
{ label: "Contributions", value: contributions.toLocaleString(), color: "39d353" },
{ label: "Total PRs", value: totalPRs, color: "8b5cf6" },
{ label: "Total Issues", value: totalIssues, color: "f85149" },
{ label: "PRs", value: totalPRs, color: "8b5cf6" },
{ label: "Issues", value: totalIssues, color: "f85149" },
{ label: "Contribs", value: contributions.toLocaleString(), color: "39d353" },
{ label: "Lines +/-", value: linesChanged.toLocaleString(), color: "fb923c" },
];

heroStats.forEach((s, i) => {
content += `
<g transform="translate(${pad + i * statW}, ${y})">
<text x="${statW / 2}" y="22" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="24" font-weight="800" fill="#${s.color}">${s.value}</text>
<text x="${statW / 2}" y="42" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11" font-weight="600" fill="#8b949e" style="text-transform:uppercase;letter-spacing:1px">${s.label}</text>
<text x="${statW / 2}" y="20" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="24" font-weight="800" fill="#${s.color}">${s.value}</text>
<text x="${statW / 2}" y="40" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11" font-weight="600" fill="#8b949e" style="text-transform:uppercase;letter-spacing:1px">${s.label}</text>
</g>`;
});
y += 85;

content += `<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="#30363d" stroke-width="1" opacity="0.3"/>`;
y += 35;
content += `<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="#30363d" stroke-width="1.5" opacity="0.3"/>`;
y += 40;

// --- INSIGHTS GRID (Middle row) ---
const leftColW = Math.floor(innerW * 0.55);
const rightColW = innerW - leftColW - 30;
// --- MIDDLE GRID: Languages (Left) & Commit Ranking (Right) ---
const midGridW = (innerW - 40) / 2;

// Languages (Left)
// Top Languages
content += `<g transform="translate(${pad}, ${y})">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="16" font-weight="700" fill="#${titleColor}">Top Languages</text>
<g transform="translate(0, 22)">`;
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18" font-weight="800" fill="#${titleColor}">Language Mastery</text>
<g transform="translate(0, 25)">`;

const { getLanguageColor } = require("./languages");
const topLangs = languages.slice(0, 6);
const topLangs = languages.slice(0, 7);
topLangs.forEach((lang, i) => {
const ly = i * 28;
const barMax = leftColW - 130;
const barMax = midGridW - 140;
const barW = (lang.percentage / 100) * barMax;
content += `
<g transform="translate(0, ${ly})">
<circle cx="6" cy="10" r="5" fill="#${getLanguageColor(lang.name)}"/>
<text x="18" y="14" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" fill="#${textColor}">${escapeXml(lang.name)}</text>
<rect x="100" y="6" width="${barMax}" height="8" rx="4" fill="#30363d" opacity="0.4"/>
<rect x="100" y="6" width="${barW}" height="8" rx="4" fill="#${getLanguageColor(lang.name)}"/>
<text x="${leftColW - 10}" y="14" text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11" fill="#8b949e">${lang.percentage.toFixed(1)}%</text>
<text x="20" y="14" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" fill="#${textColor}">${escapeXml(lang.name)}</text>
<rect x="110" y="7" width="${barMax}" height="8" rx="4" fill="#30363d" opacity="0.4"/>
<rect x="110" y="7" width="${barW}" height="8" rx="4" fill="#${getLanguageColor(lang.name)}"/>
<text x="${midGridW}" y="14" text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" font-weight="600" fill="#8b949e">${lang.percentage.toFixed(1)}%</text>
</g>`;
});
content += `</g></g>`;

// Streaks & Activity (Right)
content += `<g transform="translate(${pad + leftColW + 30}, ${y})">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="16" font-weight="700" fill="#${titleColor}">Performance</text>
<g transform="translate(0, 22)">
<rect width="${rightColW}" height="54" rx="10" fill="#39d353" opacity="0.08"/>
<text x="14" y="20" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="10" fill="#8b949e" style="text-transform:uppercase">Current Streak</text>
<text x="14" y="42" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22" font-weight="800" fill="#39d353">${currentStreak} Days</text>

<g transform="translate(0, 68)">
<rect width="${rightColW}" height="54" rx="10" fill="#f97316" opacity="0.08"/>
<text x="14" y="20" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="10" fill="#8b949e" style="text-transform:uppercase">Longest Streak</text>
<text x="14" y="42" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="22" font-weight="800" fill="#f97316">${longestStreak} Days</text>
</g>
// Commits Ranking / Streaks
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const maxWeekly = Math.max(...Object.values(weekMap), 1);
content += `<g transform="translate(${pad + midGridW + 40}, ${y})">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18" font-weight="800" fill="#${titleColor}">Weekly Activity</text>
<g transform="translate(0, 25)">`;

<g transform="translate(0, 136)">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" font-weight="600" fill="#${textColor}">Lines Changed <tspan fill="#8b949e" font-weight="400" font-size="11">(recent)</tspan></text>
<text x="0" y="24" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="20" font-weight="800" fill="#fb923c">${linesChanged.toLocaleString()}</text>
</g>
</g>
</g>`;
daysOfWeek.forEach((day, i) => {
const ly = i * 28;
const count = weekMap[i] || 0;
const barW = (count / maxWeekly) * (midGridW - 80);
content += `
<g transform="translate(0, ${ly})">
<text x="0" y="14" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" fill="#8b949e">${day}</text>
<rect x="40" y="7" width="${midGridW - 80}" height="8" rx="4" fill="#30363d" opacity="0.4"/>
<rect x="40" y="7" width="${barW}" height="8" rx="4" fill="#39d353" opacity="0.8"/>
<text x="${midGridW}" y="14" text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" font-weight="700" fill="#39d353">${count.toLocaleString()}</text>
</g>`;
});
content += `</g></g>`;

y += 205;
content += `<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="#30363d" stroke-width="1" opacity="0.3"/>`;
y += 35;
y += 240;
content += `<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="#30363d" stroke-width="1.5" opacity="0.3"/>`;
y += 40;

// --- REPOSITORIES & STATUS (Middle-Bottom row) ---
const breakW = (innerW - 30) / 2;
// --- THIRD ROW: PR & ISSUE BREAKDOWN + STREAKS ---
content += `<g transform="translate(${pad}, ${y})">
<g>
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="15" font-weight="700" fill="#${titleColor}">Projects Activity</text>
<g transform="translate(0, 12)">`;
repoList.slice(0, 3).forEach((repo, i) => {
content += `<text x="0" y="${i * 20 + 10}" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" fill="#8b949e">&bull; ${escapeXml(repo.name.length > 25 ? repo.name.substring(0, 22) + '...' : repo.name)} <tspan fill="#${accentColor}" font-weight="700">(${repo.count} PRs)</tspan></text>`;
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18" font-weight="800" fill="#${titleColor}">Projects &amp; PRs</text>
<g transform="translate(0, 20)">`;
repoList.slice(0, 4).forEach((repo, i) => {
const ly = i * 24;
content += `<text x="0" y="${ly + 14}" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="13" fill="#8b949e">&bull; ${escapeXml(repo.name.length > 32 ? repo.name.substring(0, 29) + '...' : repo.name)} <tspan fill="#${accentColor}" font-weight="800">(${repo.count} PRs)</tspan></text>`;
});
content += `</g></g>
<g transform="translate(${breakW + 30}, 0)">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="15" font-weight="700" fill="#${titleColor}">Detailed Status</text>
<g transform="translate(0, 12)">
<text x="0" y="10" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" fill="#8b949e">Merged PRs: <tspan fill="#8b5cf6" font-weight="700">${mergedPRs}</tspan></text>
<text x="0" y="32" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" fill="#8b949e">Open PRs: <tspan fill="#3fb950" font-weight="700">${openPRs}</tspan></text>
<text x="0" y="54" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="12" fill="#8b949e">Closed Issues: <tspan fill="#f85149" font-weight="700">${closedIssues}</tspan></text>
<g transform="translate(${midGridW + 40}, 0)">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18" font-weight="800" fill="#${titleColor}">Performance &amp; Streaks</text>
<g transform="translate(0, 20)">
<rect width="${midGridW}" height="45" rx="10" fill="#39d353" opacity="0.08"/>
<text x="14" y="27" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14" font-weight="700" fill="#39d353">🔥 Current Streak: ${currentStreak} Days</text>
<g transform="translate(0, 55)">
<rect width="${midGridW}" height="45" rx="10" fill="#f97316" opacity="0.08"/>
<text x="14" y="27" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="14" font-weight="700" fill="#f97316">🏆 Best Streak: ${longestStreak} Days</text>
</g>
</g>
</g>
</g>`;
y += 90;
y += 130;

content += `<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="#30363d" stroke-width="1" opacity="0.3"/>`;
y += 35;
content += `<line x1="${pad}" y1="${y}" x2="${cardWidth - pad}" y2="${y}" stroke="#30363d" stroke-width="1.5" opacity="0.3"/>`;
y += 40;

// --- CONTRIBUTION HEATMAP (Bottom) ---
// --- FOOTER: CONTRIBUTION HEATMAP (Full width) ---
content += `<g transform="translate(${pad}, ${y})">
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="16" font-weight="700" fill="#${titleColor}">Contribution Activity (Last Year)</text>
<g transform="translate(0, 15)">`;
<text x="0" y="0" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="18" font-weight="800" fill="#${titleColor}">Contribution Activity (Last 12 Months)</text>
<g transform="translate(0, 20)">`;

const sortedDays = [...contributionDays].sort((a, b) => String(a.date).localeCompare(String(b.date)));
const recent = sortedDays.slice(-371);
const cell = 11, gap = 3.2, step = cell + gap;
const cell = 11.5, gap = 3.5, step = cell + gap;

recent.forEach((d, i) => {
const col = Math.floor(i / 7);
const row = i % 7;
content += `<rect x="${col * step}" y="${row * step}" width="${cell}" height="${cell}" rx="2.5" fill="#${getContributionColorByLevel(d.level)}"/>`;
content += `<rect x="${col * step}" y="${row * step}" width="${cell}" height="${cell}" rx="3" fill="#${getContributionColorByLevel(d.level)}"/>`;
});
content += `</g></g>`;
y += 130;
y += 145;

const cardHeight = y + 30;
const cardHeight = y + 40;

return `<svg xmlns="http://www.w3.org/2000/svg" width="${cardWidth}" height="${cardHeight}" viewBox="0 0 ${cardWidth} ${cardHeight}">
<defs>
<linearGradient id="headerGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#${accentColor};stop-opacity:0.12"/>
<stop offset="100%" style="stop-color:#${accentColor};stop-opacity:0"/>
<linearGradient id="fullGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#${accentColor};stop-opacity:0.08"/>
<stop offset="100%" style="stop-color:#0d1117;stop-opacity:0"/>
</linearGradient>
</defs>
<rect width="${cardWidth}" height="${cardHeight}" fill="#0d1117" ${hba}/>
<path d="M0 14 Q0 0 14 0 L${cardWidth - 14} 0 Q${cardWidth} 0 ${cardWidth} 14 L${cardWidth} 85 L0 85 Z" fill="url(#headerGrad)" opacity="0.6"/>
<rect width="${cardWidth}" height="4" fill="#${accentColor}" rx="14"/>
<rect width="${cardWidth}" height="${cardHeight}" fill="url(#fullGrad)" ${hba}/>
<rect width="${cardWidth}" height="6" fill="#${accentColor}" rx="16"/>
${content}
<text x="${cardWidth - pad}" y="${cardHeight - 15}" text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="10" fill="#8b949e" opacity="0.4">gitlyy full dashboard</text>
<text x="${cardWidth - pad}" y="${cardHeight - 18}" text-anchor="end" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif" font-size="11" fill="#8b949e" opacity="0.4">gitlyy ultimate dashboard &bull; generated with real data</text>
</svg>`;
}

Expand Down
Loading