Add an admin page for managing gallery images and artist names

Introduce a new admin route at `/admin/gallery` enabling users to set focal points and artist names for gallery images, with corresponding API endpoints and data management.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 70b6b9df-7da9-46f3-afd7-dbcd9aac1855
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/ncMMRQ9
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic
2026-03-06 10:48:43 +00:00
parent 8a9004d640
commit 9d8d29208a
7 changed files with 514 additions and 25 deletions
+1
View File
@@ -0,0 +1 @@
{}
+6
View File
@@ -0,0 +1,6 @@
{
"DSC07135.jpg": {
"x": 50,
"y": 30
}
}
+98
View File
@@ -362,6 +362,92 @@ export async function registerRoutes(
}
});
app.get("/api/gallery/focal-points", (_req, res) => {
try {
const fpPath = path.join(process.cwd(), "server/gallery-focal-points.json");
if (fs.existsSync(fpPath)) {
res.json(JSON.parse(fs.readFileSync(fpPath, "utf-8")));
} else {
res.json({});
}
} catch {
res.json({});
}
});
app.put("/api/gallery/focal-points/:fileName", (req, res) => {
try {
const { fileName } = req.params;
const { x, y } = req.body;
if (typeof x !== "number" || typeof y !== "number") {
return res.status(400).json({ message: "x and y must be numbers (0-100)" });
}
const fpPath = path.join(process.cwd(), "server/gallery-focal-points.json");
let data: Record<string, { x: number; y: number }> = {};
if (fs.existsSync(fpPath)) {
data = JSON.parse(fs.readFileSync(fpPath, "utf-8"));
}
data[fileName] = { x: Math.round(x), y: Math.round(y) };
fs.writeFileSync(fpPath, JSON.stringify(data, null, 2));
res.json({ ok: true, fileName, x: data[fileName].x, y: data[fileName].y });
} catch (err: any) {
res.status(500).json({ message: err.message });
}
});
app.delete("/api/gallery/focal-points/:fileName", (req, res) => {
try {
const { fileName } = req.params;
const fpPath = path.join(process.cwd(), "server/gallery-focal-points.json");
let data: Record<string, { x: number; y: number }> = {};
if (fs.existsSync(fpPath)) {
data = JSON.parse(fs.readFileSync(fpPath, "utf-8"));
}
delete data[fileName];
fs.writeFileSync(fpPath, JSON.stringify(data, null, 2));
res.json({ ok: true });
} catch (err: any) {
res.status(500).json({ message: err.message });
}
});
app.get("/api/gallery/artists", (_req, res) => {
try {
const aPath = path.join(process.cwd(), "server/gallery-artist-overrides.json");
if (fs.existsSync(aPath)) {
res.json(JSON.parse(fs.readFileSync(aPath, "utf-8")));
} else {
res.json({});
}
} catch {
res.json({});
}
});
app.put("/api/gallery/artists/:fileName", (req, res) => {
try {
const { fileName } = req.params;
const { artist } = req.body;
if (typeof artist !== "string") {
return res.status(400).json({ message: "artist must be a string" });
}
const aPath = path.join(process.cwd(), "server/gallery-artist-overrides.json");
let data: Record<string, string> = {};
if (fs.existsSync(aPath)) {
data = JSON.parse(fs.readFileSync(aPath, "utf-8"));
}
if (artist.trim() === "") {
delete data[fileName];
} else {
data[fileName] = artist.trim();
}
fs.writeFileSync(aPath, JSON.stringify(data, null, 2));
res.json({ ok: true, fileName, artist: artist.trim() });
} catch (err: any) {
res.status(500).json({ message: err.message });
}
});
// Gallery API - serves optimized photos from Dropbox/Cloudinary
app.get("/api/gallery", async (req, res) => {
try {
@@ -376,6 +462,18 @@ export async function registerRoutes(
data = JSON.parse(fs.readFileSync(galleryPath, "utf-8"));
}
const aPath = path.join(process.cwd(), "server/gallery-artist-overrides.json");
let artistOverrides: Record<string, string> = {};
if (fs.existsSync(aPath)) {
artistOverrides = JSON.parse(fs.readFileSync(aPath, "utf-8"));
}
data = data.map((img: any) => {
if (artistOverrides[img.fileName]) {
return { ...img, artist: artistOverrides[img.fileName] };
}
return img;
});
const shuffled = [...data];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));