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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,4 @@ dist
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vercel
3 changes: 3 additions & 0 deletions api/collage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function handler(req, res) {
res.status(200).send('API is running');
}
62 changes: 62 additions & 0 deletions api/frames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import axios from 'axios';
import sharp from 'sharp';
import fs from 'fs';
import path from 'path';

export default async function handler(req, res) {
try {
// Get username from query parameter
const username = req.query.username;
const theme = req.query.theme || 'base';
const size = Math.max(64, Math.min(Number(req.query.size || 256), 1024));

// Validate username
if (!username || typeof username !== 'string' || username.trim() === '') {
return res.status(400).json({ error: 'Username is required' });
}

console.log(`Fetching avatar for username=${username}, theme=${theme}, size=${size}`);

// Fetch GitHub avatar
const avatarUrl = `https://github.com/${username}.png?size=${size}`;
let avatarResponse;

try {
avatarResponse = await axios.get(avatarUrl, {
responseType: 'arraybuffer',
timeout: 10000,
validateStatus: (status) => status === 200
});
} catch (axiosError) {
if (axiosError.response?.status === 404) {
return res.status(404).json({ error: 'GitHub user not found' });
}
throw axiosError;
}

const avatarBuffer = Buffer.from(avatarResponse.data);

// Locate theme frame
const themePath = path.join(process.cwd(), 'public', 'frames', theme, 'frame.png');
if (!fs.existsSync(themePath)) {
return res.status(404).json({ error: `Theme '${theme}' not found` });
}
const frameBuffer = fs.readFileSync(themePath);

// Resize and overlay
const avatarResized = await sharp(avatarBuffer).resize(size, size).png().toBuffer();
const frameResized = await sharp(frameBuffer).resize(size, size).png().toBuffer();

const finalImage = await sharp(avatarResized)
.composite([{ input: frameResized, gravity: 'center' }])
.png()
.toBuffer();

res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'public, max-age=3600');
res.send(finalImage);
} catch (error) {
console.error('Error processing avatar:', error);
res.status(500).json({ error: 'Something went wrong.' });
}
}
37 changes: 37 additions & 0 deletions api/themes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import fs from 'fs';
import path from 'path';

export default function handler(req, res) {
try {
const framesDir = path.join(process.cwd(), 'public', 'frames');

if (!fs.existsSync(framesDir)) {
return res.status(500).json({ error: "Frames directory not found" });
}

const themes = fs.readdirSync(framesDir).filter(folder =>
fs.existsSync(path.join(framesDir, folder, 'frame.png'))
);

const result = themes.map(theme => {
const metadataPath = path.join(framesDir, theme, 'metadata.json');
let metadata = { name: theme, description: `${theme} frame theme` };

if (fs.existsSync(metadataPath)) {
try {
const fileContent = fs.readFileSync(metadataPath, 'utf-8');
metadata = { ...metadata, ...JSON.parse(fileContent) };
} catch (parseError) {
console.warn(`Invalid metadata.json for theme ${theme}`);
}
}

return { theme, ...metadata };
});

res.json(result);
} catch (error) {
console.error('Error listing themes:', error);
res.status(500).json({ error: "Failed to list themes" });
}
}
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"devDependencies": {
"@types/express": "^4.17.23",
"@types/node": "^20.19.17",
"@types/node": "^24.5.2",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.9.2"
Expand Down