fix: profile_upsert payload binding
This commit is contained in:
@@ -203,6 +203,15 @@ CREATE TRIGGER IF NOT EXISTS footprint_au AFTER UPDATE OF search_text ON footpri
|
||||
END;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_footprint_start ON footprint(start_date);
|
||||
CREATE TABLE IF NOT EXISTS profile (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uuid TEXT,
|
||||
content TEXT NOT NULL DEFAULT '{}',
|
||||
deleted_at TEXT,
|
||||
origin TEXT,
|
||||
hlc TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_footprint_type ON footprint(type_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_footprint_group ON footprint(group_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_footprint_hlc ON footprint(hlc);
|
||||
|
||||
@@ -1107,6 +1107,34 @@ function listFootprints(d, query) {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
case 'profile_get': {
|
||||
// 个人信息(单行 JSON):供个人信息页与简历生成使用
|
||||
const row = d.prepare('SELECT * FROM profile WHERE deleted_at IS NULL ORDER BY id DESC LIMIT 1').get();
|
||||
let content = {};
|
||||
if (row) { try { content = JSON.parse(row.content || '{}'); } catch { content = {}; } }
|
||||
return { content };
|
||||
}
|
||||
|
||||
case 'profile_upsert': {
|
||||
const p = payload;
|
||||
// 单行语义:更新现有行或插入首行
|
||||
const content = JSON.stringify(p.content ?? {});
|
||||
const existing = d.prepare('SELECT * FROM profile WHERE deleted_at IS NULL ORDER BY id DESC LIMIT 1').get();
|
||||
withTx(d, () => {
|
||||
const h = hlcNext(d);
|
||||
if (existing) {
|
||||
d.prepare('UPDATE profile SET content=?, hlc=? WHERE id=?').run(content, h, existing.id);
|
||||
touchOplog(d, 'profile', existing.uuid, 'upsert', h);
|
||||
} else {
|
||||
const uuid = uuidv7();
|
||||
d.prepare('INSERT INTO profile (content, uuid, hlc, origin) VALUES (?,?,?,?)')
|
||||
.run(content, uuid, h, getDeviceId(d));
|
||||
touchOplog(d, 'profile', uuid, 'upsert', h);
|
||||
}
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
case 'view_list':
|
||||
return { items: d.prepare('SELECT * FROM saved_view WHERE deleted_at IS NULL ORDER BY id').all().map(v => ({
|
||||
...v, filter: JSON.parse(v.filter || '{}'), sort: JSON.parse(v.sort || '{}'), is_default: !!v.is_default
|
||||
|
||||
@@ -86,6 +86,16 @@ ok(tSet.body?.ok === true, 'attachment_set_thumb 回填缩略图', tSet.body);
|
||||
const tGet = await api(A, 'POST', '/api/attachment_thumb', { id: rawUp.b?.data?.item?.id }, T);
|
||||
ok(tGet.body?.data?.data_b64 === Buffer.from('RAWSUMB').toString('base64'), 'attachment_thumb 读回 raw 缩略图', tGet.body);
|
||||
|
||||
// 个人信息:upsert → get 往返
|
||||
const profileContent = { info: { name: '张三', school: '重庆城市科技学院', major: '网络工程' }, resume: { selfIntro: '热爱安全研究' } };
|
||||
const pUp = await api(A, 'POST', '/api/profile_upsert', { content: profileContent }, T);
|
||||
ok(pUp.body?.ok === true, 'profile_upsert', pUp.body);
|
||||
const pGet = await api(A, 'POST', '/api/profile_get', {}, T);
|
||||
ok(pGet.body?.data?.content?.info?.name === '张三' && pGet.body?.data?.content?.resume?.selfIntro === '热爱安全研究', 'profile_get 往返一致', pGet.body);
|
||||
const pUp2 = await api(A, 'POST', '/api/profile_upsert', { content: { ...profileContent, info: { ...profileContent.info, name: '李四' } } }, T);
|
||||
const pGet2 = await api(A, 'POST', '/api/profile_get', {}, T);
|
||||
ok(pGet2.body?.data?.content?.info?.name === '李四', 'profile 单行更新语义', pGet2.body);
|
||||
|
||||
// 删除整条记录 → 附件级联清理
|
||||
const fpDel = await api(A, 'POST', '/api/footprint_delete', { id: fpId }, T);
|
||||
ok(fpDel.body?.ok === true, '删除整条记录');
|
||||
|
||||
@@ -319,6 +319,20 @@ const server = http.createServer(async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 个人信息证件照:经图床网关直传 COS,返回访问 URL
|
||||
if (req.method === 'POST' && route === '/api/profile_photo') {
|
||||
const body = await readJson(req);
|
||||
if (!LSKY_API || !LSKY_TOKEN) return send(400, false, '未配置图床,无法上传证件照');
|
||||
try {
|
||||
const buf = Buffer.from(body.data_b64 || '', 'base64');
|
||||
if (!buf.length) return send(400, false, '空文件');
|
||||
const up = await lskyUpload(buf, body.file_name || 'profile-photo.png', body.mime || 'image/png');
|
||||
return send(200, true, { url: sanitizeImageUrl(up.url) });
|
||||
} catch (e) {
|
||||
return send(500, false, '证件照上传失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 附件上传(原始二进制 PUT):无 base64 膨胀;图床上传改为后台异步,保存立即返回
|
||||
if (req.method === 'PUT' && route === '/sync/attachment/raw') {
|
||||
const q = url.searchParams;
|
||||
|
||||
Reference in New Issue
Block a user