fix:新增功能并优化
This commit is contained in:
85
server.js
85
server.js
@@ -2,6 +2,7 @@ const express = require("express");
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
const statsDb = require("./stats-db");
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 5180;
|
||||
@@ -37,6 +38,11 @@ async function thumbFileExists(id) {
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFilenameBase(name) {
|
||||
const s = String(name || "shader").trim() || "shader";
|
||||
return s.replace(/[\\/:*?"<>|]/g, "_").slice(0, 120);
|
||||
}
|
||||
|
||||
function decodePngBase64(s) {
|
||||
if (!s || typeof s !== "string") return null;
|
||||
const t = s.trim();
|
||||
@@ -333,7 +339,6 @@ app.get("/api/shaders", async (_req, res) => {
|
||||
try {
|
||||
await ensureThumbnailsDir();
|
||||
let shaders = await readDb();
|
||||
// Runtime safeguard: always return normalized code for display layer.
|
||||
shaders = shaders.map((item) => {
|
||||
const raw = String(item.code || "");
|
||||
const { error, normalized } = normalizeIncomingCode(raw);
|
||||
@@ -343,13 +348,15 @@ app.get("/api/shaders", async (_req, res) => {
|
||||
if (isAngleLike(raw)) next.sourceFormat = "angle-metal-auto-converted";
|
||||
return next;
|
||||
});
|
||||
const visitorId = String(_req.get("x-visitor-id") || "").trim();
|
||||
const out = await Promise.all(
|
||||
shaders.map(async (item) => {
|
||||
const has = await thumbFileExists(item.id);
|
||||
const thumbnailUrl = has
|
||||
? `/api/shaders/${encodeURIComponent(item.id)}/thumbnail`
|
||||
: null;
|
||||
return { ...item, thumbnailUrl };
|
||||
const merged = statsDb.mergeItem({ ...item, thumbnailUrl }, visitorId);
|
||||
return merged;
|
||||
})
|
||||
);
|
||||
res.json(out);
|
||||
@@ -358,6 +365,31 @@ app.get("/api/shaders", async (_req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/shaders/:id/download", async (req, res) => {
|
||||
try {
|
||||
const shaders = await readDb();
|
||||
const item = shaders.find((it) => it.id === req.params.id);
|
||||
if (!item) {
|
||||
return res.status(404).json({ error: "未找到该 shader" });
|
||||
}
|
||||
const raw =
|
||||
item.sourceGlsl != null && String(item.sourceGlsl).length > 0
|
||||
? String(item.sourceGlsl)
|
||||
: String(item.code || "");
|
||||
const base = downloadFilenameBase(item.name);
|
||||
const fullName = `${base}.glsl`;
|
||||
const asciiName = `${base.replace(/[^\x20-\x7e]/g, "_")}.glsl`;
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(fullName)}`
|
||||
);
|
||||
res.send(raw);
|
||||
} catch {
|
||||
res.status(500).json({ error: "导出失败" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/shaders", async (req, res) => {
|
||||
const { name, author = "unknown", code } = req.body || {};
|
||||
if (!name || typeof name !== "string") {
|
||||
@@ -370,11 +402,13 @@ app.post("/api/shaders", async (req, res) => {
|
||||
|
||||
try {
|
||||
const shaders = await readDb();
|
||||
const sourceGlsl = typeof code === "string" ? code : "";
|
||||
const item = {
|
||||
id: crypto.randomUUID(),
|
||||
name: name.trim(),
|
||||
author: String(author || "unknown").trim() || "unknown",
|
||||
code: normalized,
|
||||
sourceGlsl,
|
||||
views: Math.floor(3000 + Math.random() * 22000),
|
||||
likes: Math.floor(40 + Math.random() * 700),
|
||||
createdAt: new Date().toISOString(),
|
||||
@@ -382,12 +416,45 @@ app.post("/api/shaders", async (req, res) => {
|
||||
};
|
||||
shaders.unshift(item);
|
||||
await writeDb(shaders);
|
||||
res.status(201).json(item);
|
||||
await statsDb.insertStats(item.id, item.views, item.likes);
|
||||
res.status(201).json(statsDb.mergeItem(item, ""));
|
||||
} catch {
|
||||
res.status(500).json({ error: "保存失败" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/shaders/:id/view", async (req, res) => {
|
||||
try {
|
||||
const shaders = await readDb();
|
||||
const item = shaders.find((it) => it.id === req.params.id);
|
||||
if (!item) {
|
||||
return res.status(404).json({ error: "未找到该 shader" });
|
||||
}
|
||||
const row = await statsDb.incrementView(item.id, item);
|
||||
res.json({ views: row.views, likes: row.likes });
|
||||
} catch {
|
||||
res.status(500).json({ error: "更新失败" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/shaders/:id/like", async (req, res) => {
|
||||
const visitorId = String((req.body && req.body.visitorId) || "").trim();
|
||||
if (!visitorId) {
|
||||
return res.status(400).json({ error: "visitorId 必填" });
|
||||
}
|
||||
try {
|
||||
const shaders = await readDb();
|
||||
const item = shaders.find((it) => it.id === req.params.id);
|
||||
if (!item) {
|
||||
return res.status(404).json({ error: "未找到该 shader" });
|
||||
}
|
||||
const out = await statsDb.tryLike(item.id, visitorId, item);
|
||||
res.json(out);
|
||||
} catch {
|
||||
res.status(500).json({ error: "点赞失败" });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/shaders/:id/thumbnail", async (req, res) => {
|
||||
try {
|
||||
const p = thumbPath(req.params.id);
|
||||
@@ -438,6 +505,7 @@ app.delete("/api/shaders/:id", async (req, res) => {
|
||||
return res.status(404).json({ error: "未找到该 shader" });
|
||||
}
|
||||
await writeDb(next);
|
||||
await statsDb.deleteStats(req.params.id);
|
||||
await ensureThumbnailsDir();
|
||||
try {
|
||||
await fs.unlink(thumbPath(req.params.id));
|
||||
@@ -450,11 +518,20 @@ app.delete("/api/shaders/:id", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
ensureDb().then(async () => {
|
||||
async function start() {
|
||||
await ensureDb();
|
||||
await statsDb.load();
|
||||
const initialList = await readDb();
|
||||
await statsDb.migrateIfEmpty(initialList);
|
||||
try {
|
||||
await autoNormalizeStoredShaders();
|
||||
} catch (_) {}
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running: http://localhost:${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user