1
0
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-07-01 20:09:23 +00:00

205 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Router } from "express";
const router = Router();
import kvStore from "../models/kvStore.js";
import checkSiteKey from "../middleware/auth.js";
import { v4 as uuidv4 } from "uuid";
import errors from "../utils/errors.js";
// 检查是否为受限UUID的中间件
const checkRestrictedUUID = (req, res, next) => {
const restrictedUUID = "00000000-0000-4000-8000-000000000000";
const namespace = req.params.namespace;
if (namespace === restrictedUUID) {
return next(errors.createError(403, "无权限访问此命名空间"));
}
next();
};
router.use(checkSiteKey);
/**
* GET /:namespace
* 获取指定命名空间下的所有键名及元数据列表
*/
router.get(
"/:namespace",
checkRestrictedUUID,
errors.catchAsync(async (req, res, next) => {
const { namespace } = req.params;
const { sortBy, sortDir, limit, skip } = req.query;
// 构建选项
const options = {
sortBy: sortBy || "key",
sortDir: sortDir || "asc",
limit: limit ? parseInt(limit) : 100,
skip: skip ? parseInt(skip) : 0,
};
const keys = await kvStore.list(namespace, options);
// 获取总记录数
const totalRows = await kvStore.count(namespace);
// 构建响应对象
const response = {
items: keys,
total_rows: totalRows,
};
// 如果还有更多数据添加load_more字段
const nextSkip = options.skip + options.limit;
if (nextSkip < totalRows) {
const baseUrl = `${req.baseUrl}/${namespace}`;
const queryParams = new URLSearchParams({
sortBy: options.sortBy,
sortDir: options.sortDir,
limit: options.limit,
skip: nextSkip,
}).toString();
response.load_more = `${baseUrl}?${queryParams}`;
}
return res.json(response);
})
);
/**
* GET /:namespace/:key
* 通过命名空间和键名获取键值
*/
router.get(
"/:namespace/:key",
checkRestrictedUUID,
errors.catchAsync(async (req, res, next) => {
const { namespace, key } = req.params;
// 否则只返回值
const value = await kvStore.get(namespace, key);
if (value === null) {
// 创建并传递错误,而不是抛出
return next(
errors.createError(
404,
`未找到命名空间 '${namespace}' 下键名为 '${key}' 的记录`
)
);
}
return res.json(value);
})
);
router.get(
"/:namespace/:key/metadata",
checkRestrictedUUID,
errors.catchAsync(async (req, res, next) => {
const { namespace, key } = req.params;
const metadata = await kvStore.getMetadata(namespace, key);
if (!metadata) {
return next(
errors.createError(
404,
`未找到命名空间 '${namespace}' 下键名为 '${key}' 的记录`
)
);
}
return res.json(metadata);
})
);
/**
* POST /:namespace/:key
* 更新指定命名空间下的键值,如果不存在则创建
*/
router.post(
"/:namespace/:key",
checkRestrictedUUID,
errors.catchAsync(async (req, res, next) => {
const { namespace, key } = req.params;
const value = req.body;
if (!value || Object.keys(value).length === 0) {
// 创建并传递错误,而不是抛出
return next(errors.createError(400, "请提供有效的JSON值"));
}
// 获取客户端IP
const creatorIp =
req.headers["x-forwarded-for"] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket?.remoteAddress ||
"";
const result = await kvStore.upsert(namespace, key, value, creatorIp);
return res.status(200).json({
namespace: result.namespace,
key: result.key,
created: result.createdAt.getTime() === result.updatedAt.getTime(),
updatedAt: result.updatedAt,
});
})
);
/**
* DELETE /:namespace
* 删除指定命名空间及其所有键值对
*/
router.delete(
"/:namespace",
checkRestrictedUUID,
errors.catchAsync(async (req, res, next) => {
const { namespace } = req.params;
const result = await kvStore.deleteNamespace(namespace);
if (!result) {
// 创建并传递错误,而不是抛出
return next(errors.createError(404, `未找到命名空间 '${namespace}'`));
}
// 204状态码表示成功但无内容返回
return res.status(204).end();
})
);
/**
* DELETE /:namespace/:key
* 删除指定命名空间下的键值对
*/
router.delete(
"/:namespace/:key",
checkRestrictedUUID,
errors.catchAsync(async (req, res, next) => {
const { namespace, key } = req.params;
const result = await kvStore.delete(namespace, key);
if (!result) {
// 创建并传递错误,而不是抛出
return next(
errors.createError(
404,
`未找到命名空间 '${namespace}' 下键名为 '${key}' 的记录`
)
);
}
// 204状态码表示成功但无内容返回
return res.status(204).end();
})
);
/**
* GET /uuid
* 生成并返回一个随机UUID可用作新命名空间
*/
router.get(
"/uuid",
errors.catchAsync(async (req, res) => {
const namespace = uuidv4();
res.json({ namespace });
})
);
export default router;