Skip to content

Commit 3980294

Browse files
feat: add HTML landing page for SVG cards with project footer
Implement content negotiation in all API endpoints via a new `sendResponse` utility. When a card URL is accessed via a browser (Accept: text/html), it now serves a styled HTML page that: - Centers the SVG card. - Adds a footer in the bottom-left with the text "This is a gitlyy a readme stats generator." - Links the word "gitlyy" to the project's GitHub repository. Non-browser requests continue to receive the raw SVG. Includes `Vary: Accept` header for correct CDN caching. Co-authored-by: Chintanpatel24 <216989679+Chintanpatel24@users.noreply.github.com>
1 parent 73b8d3a commit 3980294

11 files changed

Lines changed: 114 additions & 39 deletions

File tree

api/commits.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ const {
1414
} = require("../src/svg-commits");
1515
const { parseCardWidth } = require("../src/width");
1616
const { getCache, setCache, clearCache } = require("../src/cache");
17+
const { sendResponse } = require("../src/response");
1718

1819
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
1920

2021
module.exports = async (req, res) => {
2122
res.setHeader("Access-Control-Allow-Origin", "*");
2223
res.setHeader("Access-Control-Allow-Methods", "GET");
23-
res.setHeader("Content-Type", "image/svg+xml");
2424
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
2525

2626
const { username, theme, hide_border, layout, bg_color, title_color, text_color, border_color, title, width, refresh } = req.query;
2727

2828
if (!username) {
29-
res.status(400).send(errorSVG("Missing username"));
29+
sendResponse(req, res, errorSVG("Missing username"), 400);
3030
return;
3131
}
3232

@@ -66,10 +66,10 @@ module.exports = async (req, res) => {
6666
cardWidth: parseCardWidth(width, 460, 400, 1200),
6767
});
6868

69-
res.status(200).send(svg);
69+
sendResponse(req, res, svg);
7070
} catch (error) {
7171
console.error("Commits Error:", error.message);
72-
res.status(200).send(errorSVG("Failed to load commits data"));
72+
sendResponse(req, res, errorSVG("Failed to load commits data"), 200);
7373
}
7474
};
7575

api/contribution.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ const {
1313
generateContributionPulseSVG,
1414
} = require("../src/svg-contribution");
1515
const { getCache, setCache, clearCache } = require("../src/cache");
16+
const { sendResponse } = require("../src/response");
1617

1718
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
1819

1920
module.exports = async (req, res) => {
2021
res.setHeader("Access-Control-Allow-Origin", "*");
2122
res.setHeader("Access-Control-Allow-Methods", "GET");
22-
res.setHeader("Content-Type", "image/svg+xml");
2323
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
2424

2525
const { username, theme, hide_border, layout, bg_color, title_color, text_color, border_color, title, width, refresh } = req.query;
2626

2727
if (!username) {
28-
res.status(400).send(errorSVG("Missing username"));
28+
sendResponse(req, res, errorSVG("Missing username"), 400);
2929
return;
3030
}
3131

@@ -85,10 +85,10 @@ module.exports = async (req, res) => {
8585
hideBorder: hide_border === "true", title,
8686
});
8787

88-
res.status(200).send(svg);
88+
sendResponse(req, res, svg);
8989
} catch (error) {
9090
console.error("Contribution Error:", error.message);
91-
res.status(200).send(errorSVG("Failed to load contribution data"));
91+
sendResponse(req, res, errorSVG("Failed to load contribution data"), 200);
9292
}
9393
};
9494

api/issues.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ const { getTheme, applyColorOverrides } = require("../src/themes");
1010
const { generateIssuesSVG } = require("../src/svg-issues");
1111
const { parseCardWidth } = require("../src/width");
1212
const { getCache, setCache, clearCache } = require("../src/cache");
13+
const { sendResponse } = require("../src/response");
1314

1415
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
1516

1617
module.exports = async (req, res) => {
1718
res.setHeader("Access-Control-Allow-Origin", "*");
1819
res.setHeader("Access-Control-Allow-Methods", "GET");
19-
res.setHeader("Content-Type", "image/svg+xml");
2020
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
2121

2222
const { username, theme, hide_border, bg_color, title_color, text_color, border_color, width, refresh } = req.query;
2323

2424
if (!username) {
25-
res.status(400).send(errorSVG("Missing username"));
25+
sendResponse(req, res, errorSVG("Missing username"), 400);
2626
return;
2727
}
2828

@@ -76,10 +76,10 @@ module.exports = async (req, res) => {
7676
cardWidth: parseCardWidth(width, 460, 400, 1200),
7777
});
7878

79-
res.status(200).send(svg);
79+
sendResponse(req, res, svg);
8080
} catch (error) {
8181
console.error("Issues Stats Error:", error.message);
82-
res.status(200).send(errorSVG("Failed to load issues data"));
82+
sendResponse(req, res, errorSVG("Failed to load issues data"), 200);
8383
}
8484
};
8585

api/languages.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ const {
1919
generateLanguageDonutByCommitsSVG,
2020
} = require("../src/svg-language");
2121
const { getCache, setCache, clearCache } = require("../src/cache");
22+
const { sendResponse } = require("../src/response");
2223

2324
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
2425

2526
module.exports = async (req, res) => {
2627
res.setHeader("Access-Control-Allow-Origin", "*");
2728
res.setHeader("Access-Control-Allow-Methods", "GET");
28-
res.setHeader("Content-Type", "image/svg+xml");
2929
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
3030

3131
const {
@@ -43,7 +43,7 @@ module.exports = async (req, res) => {
4343
} = req.query;
4444

4545
if (!username) {
46-
res.status(400).send(errorSVG("Missing username"));
46+
sendResponse(req, res, errorSVG("Missing username"), 400);
4747
return;
4848
}
4949

@@ -108,10 +108,10 @@ module.exports = async (req, res) => {
108108
});
109109
}
110110

111-
res.status(200).send(svg);
111+
sendResponse(req, res, svg);
112112
} catch (error) {
113113
console.error("Language Error:", error.message);
114-
res.status(200).send(errorSVG("Failed to load language data"));
114+
sendResponse(req, res, errorSVG("Failed to load language data"), 200);
115115
}
116116
};
117117

api/master.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const {
2222
const { getTheme, applyColorOverrides } = require("../src/themes");
2323
const { generateMasterCardSVG } = require("../src/svg-master");
2424
const { getCache, setCache, clearCache } = require("../src/cache");
25+
const { sendResponse } = require("../src/response");
2526

2627
const CACHE_TTL = 30 * 60 * 1000;
2728

@@ -38,13 +39,12 @@ const safe = async (promise, fallback) => {
3839
module.exports = async (req, res) => {
3940
res.setHeader("Access-Control-Allow-Origin", "*");
4041
res.setHeader("Access-Control-Allow-Methods", "GET");
41-
res.setHeader("Content-Type", "image/svg+xml");
4242
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
4343

4444
const { username, theme, hide_border, bg_color, title_color, text_color, border_color, refresh } = req.query;
4545

4646
if (!username) {
47-
res.status(400).send(errorSVG("Missing username"));
47+
sendResponse(req, res, errorSVG("Missing username"), 400);
4848
return;
4949
}
5050

@@ -166,10 +166,10 @@ module.exports = async (req, res) => {
166166
hideBorder: hide_border === "true",
167167
});
168168

169-
res.status(200).send(svg);
169+
sendResponse(req, res, svg);
170170
} catch (error) {
171171
console.error("Mastercard API Error:", error.message);
172-
res.status(200).send(errorSVG("Failed to load dashboard data"));
172+
sendResponse(req, res, errorSVG("Failed to load dashboard data"), 200);
173173
}
174174
};
175175

api/music.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
const { getTheme, applyColorOverrides } = require("../src/themes");
77
const { generateMusicSVG } = require("../src/svg-music");
8+
const { sendResponse } = require("../src/response");
89

910
module.exports = async (req, res) => {
1011
res.setHeader("Access-Control-Allow-Origin", "*");
1112
res.setHeader("Access-Control-Allow-Methods", "GET");
12-
res.setHeader("Content-Type", "image/svg+xml");
1313
res.setHeader("Cache-Control", "public, max-age=3600, s-maxage=3600, stale-while-revalidate=1800");
1414

1515
const {
@@ -40,10 +40,10 @@ module.exports = async (req, res) => {
4040
hideBorder: hide_border === "true",
4141
});
4242

43-
res.status(200).send(svg);
43+
sendResponse(req, res, svg);
4444
} catch (error) {
4545
console.error("Music Card Error:", error.message);
46-
res.status(200).send(errorSVG("Failed to load music card"));
46+
sendResponse(req, res, errorSVG("Failed to load music card"), 200);
4747
}
4848
};
4949

api/overview.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
const { getTheme, applyColorOverrides } = require("../src/themes");
1818
const { generateOverviewSVG } = require("../src/svg-overview");
1919
const { getCache, setCache, clearCache } = require("../src/cache");
20+
const { sendResponse } = require("../src/response");
2021

2122
const CACHE_TTL = 30 * 60 * 1000;
2223

@@ -35,13 +36,12 @@ function parseMaxPRs(value, defaultValue = 20, hardLimit = 50) {
3536
module.exports = async (req, res) => {
3637
res.setHeader("Access-Control-Allow-Origin", "*");
3738
res.setHeader("Access-Control-Allow-Methods", "GET");
38-
res.setHeader("Content-Type", "image/svg+xml");
3939
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
4040

4141
const { username, theme, hide_border, bg_color, title_color, text_color, border_color, refresh, lines_scope, max_prs } = req.query;
4242

4343
if (!username) {
44-
res.status(400).send(errorSVG("Missing username"));
44+
sendResponse(req, res, errorSVG("Missing username"), 400);
4545
return;
4646
}
4747

@@ -114,10 +114,10 @@ module.exports = async (req, res) => {
114114
hideBorder: hide_border === "true",
115115
});
116116

117-
res.status(200).send(svg);
117+
sendResponse(req, res, svg);
118118
} catch (error) {
119119
console.error("Overview Error:", error.message);
120-
res.status(200).send(errorSVG("Failed to load overview data"));
120+
sendResponse(req, res, errorSVG("Failed to load overview data"), 200);
121121
}
122122
};
123123

api/pr-stats.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ const { getTheme, applyColorOverrides } = require("../src/themes");
1010
const { generatePRCardSVG, generatePRSummarySVG } = require("../src/svg-pr");
1111
const { parseCardWidth } = require("../src/width");
1212
const { getCache, setCache, clearCache } = require("../src/cache");
13+
const { sendResponse } = require("../src/response");
1314

1415
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
1516

1617
module.exports = async (req, res) => {
1718
res.setHeader("Access-Control-Allow-Origin", "*");
1819
res.setHeader("Access-Control-Allow-Methods", "GET");
19-
res.setHeader("Content-Type", "image/svg+xml");
2020
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
2121

2222
const { username, theme, hide_border, layout, bg_color, title_color, text_color, border_color, title, width, refresh } = req.query;
2323

2424
if (!username) {
25-
res.status(400).send(errorSVG("Missing username"));
25+
sendResponse(req, res, errorSVG("Missing username"), 400);
2626
return;
2727
}
2828

@@ -100,10 +100,10 @@ module.exports = async (req, res) => {
100100
cardWidth: parseCardWidth(width, 460, 400, 1200),
101101
});
102102

103-
res.status(200).send(svg);
103+
sendResponse(req, res, svg);
104104
} catch (error) {
105105
console.error("PR Stats Error:", error.message);
106-
res.status(200).send(errorSVG("Failed to load PR data"));
106+
sendResponse(req, res, errorSVG("Failed to load PR data"), 200);
107107
}
108108
};
109109

api/profile.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ const { getTheme, applyColorOverrides } = require("../src/themes");
1111
const { generateProfileSVG } = require("../src/svg-profile");
1212
const { parseCardWidth } = require("../src/width");
1313
const { getCache, setCache, clearCache } = require("../src/cache");
14+
const { sendResponse } = require("../src/response");
1415

1516
const CACHE_TTL = 2 * 60 * 60 * 1000; // 2 hours
1617

1718
module.exports = async (req, res) => {
1819
res.setHeader("Access-Control-Allow-Origin", "*");
1920
res.setHeader("Access-Control-Allow-Methods", "GET");
20-
res.setHeader("Content-Type", "image/svg+xml");
2121
res.setHeader("Cache-Control", "public, max-age=7200, s-maxage=7200, stale-while-revalidate=3600");
2222

2323
const { username, theme, hide_border, bg_color, title_color, text_color, border_color, width, refresh } = req.query;
2424

2525
if (!username) {
26-
res.status(400).send(errorSVG("Missing username"));
26+
sendResponse(req, res, errorSVG("Missing username"), 400);
2727
return;
2828
}
2929

@@ -83,10 +83,10 @@ module.exports = async (req, res) => {
8383
cardWidth: parseCardWidth(width, 460, 420, 1200),
8484
});
8585

86-
res.status(200).send(svg);
86+
sendResponse(req, res, svg);
8787
} catch (error) {
8888
console.error("Profile Error:", error.message);
89-
res.status(200).send(errorSVG("Failed to load profile data"));
89+
sendResponse(req, res, errorSVG("Failed to load profile data"), 200);
9090
}
9191
};
9292

api/streak.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ const { fetchContributionData, fetchTotalCommitCount } = require("../src/github"
1010
const { getTheme, applyColorOverrides } = require("../src/themes");
1111
const { generateStreakSVG } = require("../src/svg-streak");
1212
const { getCache, setCache, clearCache } = require("../src/cache");
13+
const { sendResponse } = require("../src/response");
1314

1415
const CACHE_TTL = 30 * 60 * 1000;
1516

1617
module.exports = async (req, res) => {
1718
res.setHeader("Access-Control-Allow-Origin", "*");
1819
res.setHeader("Access-Control-Allow-Methods", "GET");
19-
res.setHeader("Content-Type", "image/svg+xml");
2020
res.setHeader("Cache-Control", "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600");
2121

2222
const { username, theme, hide_border, bg_color, title_color, text_color, border_color, refresh } = req.query;
2323

2424
if (!username) {
25-
res.status(400).send(errorSVG("Missing username"));
25+
sendResponse(req, res, errorSVG("Missing username"), 400);
2626
return;
2727
}
2828

@@ -79,10 +79,10 @@ module.exports = async (req, res) => {
7979
hideBorder: hide_border === "true",
8080
});
8181

82-
res.status(200).send(svg);
82+
sendResponse(req, res, svg);
8383
} catch (error) {
8484
console.error("Streak Error:", error.message);
85-
res.status(200).send(errorSVG("Failed to load streak data"));
85+
sendResponse(req, res, errorSVG("Failed to load streak data"), 200);
8686
}
8787
};
8888

0 commit comments

Comments
 (0)