feat(server): 新增云端缓存与同步服务端骨架

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Developer
2026-03-18 00:27:04 +08:00
parent df12a6ac72
commit 6764f4c53b
14 changed files with 2183 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
const db = require('../db');
const { requireAuth } = require('../auth');
function normalizeVaultItem(item) {
if (!item || typeof item !== 'object') {
return null;
}
var itemType = String(item.itemType || '').trim().slice(0, 64);
var itemKey = String(item.itemKey || '').trim().slice(0, 191);
var payloadCiphertext = String(item.payloadCiphertext || '').trim();
var payloadIv = String(item.payloadIv || '').trim().slice(0, 128);
var payloadTag = String(item.payloadTag || '').trim().slice(0, 128);
var payloadHash = String(item.payloadHash || '').trim().slice(0, 64);
var keyVersion = Math.max(1, Number(item.keyVersion) || 1);
if (!itemType || !itemKey || !payloadCiphertext || !payloadIv || !payloadTag || !payloadHash) {
return null;
}
return {
itemType: itemType,
itemKey: itemKey,
payloadCiphertext: payloadCiphertext,
payloadIv: payloadIv,
payloadTag: payloadTag,
payloadHash: payloadHash,
keyVersion: keyVersion
};
}
async function routes(fastify) {
fastify.addHook('preHandler', requireAuth);
fastify.post('/api/vault/push', async function (request, reply) {
var items = Array.isArray(request.body && request.body.items) ? request.body.items : [];
var normalized = items.map(normalizeVaultItem).filter(Boolean);
var index = 0;
var item = null;
if (normalized.length === 0) {
reply.code(400);
return { ok: false, error: '没有可保存的保险柜项目' };
}
for (index = 0; index < normalized.length; index++) {
item = normalized[index];
await db.execute(
'INSERT INTO vault_items (user_id, item_type, item_key, payload_ciphertext, payload_iv, payload_tag, payload_hash, key_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE payload_ciphertext = VALUES(payload_ciphertext), payload_iv = VALUES(payload_iv), payload_tag = VALUES(payload_tag), payload_hash = VALUES(payload_hash), key_version = VALUES(key_version), updated_at = CURRENT_TIMESTAMP',
[
request.authContext.user.id,
item.itemType,
item.itemKey,
item.payloadCiphertext,
item.payloadIv,
item.payloadTag,
item.payloadHash,
item.keyVersion
]
);
}
return { ok: true, savedCount: normalized.length };
});
fastify.post('/api/vault/pull', async function (request) {
var itemTypes = Array.isArray(request.body && request.body.itemTypes) ? request.body.itemTypes.map(function (itemType) {
return String(itemType || '').trim().slice(0, 64);
}).filter(Boolean) : [];
var sql = 'SELECT item_type, item_key, payload_ciphertext, payload_iv, payload_tag, payload_hash, key_version, updated_at FROM vault_items WHERE user_id = ?';
var params = [request.authContext.user.id];
if (itemTypes.length > 0) {
sql += ' AND item_type IN (' + itemTypes.map(function () { return '?'; }).join(',') + ')';
params = params.concat(itemTypes);
}
sql += ' ORDER BY updated_at DESC';
var rows = await db.query(sql, params);
return {
ok: true,
items: rows.map(function (row) {
return {
itemType: row.item_type,
itemKey: row.item_key,
payloadCiphertext: row.payload_ciphertext,
payloadIv: row.payload_iv,
payloadTag: row.payload_tag,
payloadHash: row.payload_hash,
keyVersion: Number(row.key_version || 1),
updatedAt: row.updated_at
};
})
};
});
}
module.exports = routes;