04-resultManager.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * 云函数:resultManager(结果库)
  3. * 替代:GET/POST/PUT/DELETE /api/results[/:id]
  4. * 支持 action:list | get | create | update | delete
  5. */
  6. const { requireSession, assertRequestedUserMatchesSession } = require('./_session');
  7. async function handler(request, response) {
  8. try {
  9. await ensureTable();
  10. const action = pickParam(request, 'action') || 'list';
  11. const session = await requireSession(request, Psql);
  12. assertRequestedUserMatchesSession(request, session);
  13. const userId = session.userId;
  14. if (action === 'list') return listResults(request, response, userId);
  15. if (action === 'get') return getResult(request, response, userId);
  16. if (action === 'create') return upsertResult(request, response, userId, 'create');
  17. if (action === 'update') return upsertResult(request, response, userId, 'update');
  18. if (action === 'delete') return deleteResult(request, response, userId);
  19. return response.json({ code: 400, success: false, error: `未知 action: ${action}` });
  20. } catch (error) {
  21. console.error('resultManager failed:', error && error.message);
  22. return response.json({ code: error.status || 500, success: false, error: error.message });
  23. }
  24. }
  25. async function ensureTable() {
  26. await Psql.query(`
  27. CREATE TABLE IF NOT EXISTS "VideoflowResult" (
  28. "objectId" VARCHAR(50) PRIMARY KEY,
  29. "bizId" VARCHAR(255),
  30. "data" JSONB NOT NULL DEFAULT '{}',
  31. "userId" VARCHAR(255) DEFAULT '',
  32. "type" VARCHAR(50) DEFAULT '',
  33. "status" VARCHAR(50) DEFAULT '',
  34. "createdAt" TIMESTAMPTZ DEFAULT NOW(),
  35. "updatedAt" TIMESTAMPTZ DEFAULT NOW()
  36. )
  37. `);
  38. await Psql.query(`DROP INDEX IF EXISTS idx_videoflow_result_biz`);
  39. await Psql.query(`CREATE UNIQUE INDEX IF NOT EXISTS idx_videoflow_result_user_biz ON "VideoflowResult" ("userId", "bizId")`);
  40. await Psql.query(`CREATE INDEX IF NOT EXISTS idx_videoflow_result_user ON "VideoflowResult" ("userId")`);
  41. await Psql.query(`CREATE INDEX IF NOT EXISTS idx_videoflow_result_type ON "VideoflowResult" ("type")`);
  42. }
  43. async function listResults(request, response, userId) {
  44. const conds = ['"userId" = $1'];
  45. const params = [userId];
  46. const type = pickParam(request, 'type');
  47. if (type) {
  48. params.push(type);
  49. conds.push(`"type" = $${params.length}`);
  50. }
  51. const status = pickParam(request, 'status');
  52. if (status) {
  53. params.push(status);
  54. conds.push(`"status" = $${params.length}`);
  55. }
  56. const limit = safeLimit(pickParam(request, 'limit'), 500, 2000);
  57. params.push(limit);
  58. const rows = await Psql.query(
  59. `SELECT * FROM "VideoflowResult" WHERE ${conds.join(' AND ')} ORDER BY "createdAt" DESC LIMIT $${params.length}`,
  60. params
  61. );
  62. return response.json({ code: 200, success: true, data: rows.map(rowToObj) });
  63. }
  64. async function getResult(request, response, userId) {
  65. const id = pickParam(request, 'id', 'bizId');
  66. if (!id) return response.json({ code: 400, success: false, error: '缺少 id' });
  67. const rows = await Psql.query(`SELECT * FROM "VideoflowResult" WHERE "bizId" = $1 AND "userId" = $2 LIMIT 1`, [id, userId]);
  68. if (!rows.length) return response.json({ code: 404, success: false, error: '未找到结果' });
  69. return response.json({ code: 200, success: true, data: rowToObj(rows[0]) });
  70. }
  71. async function upsertResult(request, response, userId, mode) {
  72. const body = pickParam(request, 'result', 'data', 'patch') || request.body || {};
  73. const requestedId = pickParam(request, 'id', 'bizId');
  74. const bizId = body.id || body.bizId || requestedId || generateId();
  75. const now = new Date().toISOString();
  76. const existing = await Psql.query(`SELECT * FROM "VideoflowResult" WHERE "bizId" = $1 AND "userId" = $2 LIMIT 1`, [bizId, userId]);
  77. const previous = existing.length ? rowToObj(existing[0]) : {};
  78. if (mode === 'update' && requestedId && requestedId !== bizId && existing.length) {
  79. return response.json({ code: 400, success: false, error: '结果 id 不一致' });
  80. }
  81. const merged = {
  82. ...previous,
  83. ...body,
  84. id: bizId,
  85. bizId,
  86. userId,
  87. created_at: previous.created_at || body.created_at || now,
  88. updated_at: now,
  89. };
  90. await Psql.query(
  91. `INSERT INTO "VideoflowResult" ("objectId","bizId","data","userId","type","status")
  92. VALUES ($1,$2,$3,$4,$5,$6)
  93. ON CONFLICT ("userId","bizId") DO UPDATE SET "data"=$3, "type"=$5, "status"=$6, "updatedAt"=NOW()`,
  94. [existing[0]?.objectId || generateId(), bizId, JSON.stringify(merged), userId, merged.type || '', merged.status || '']
  95. );
  96. return response.json({ code: 200, success: true, data: merged });
  97. }
  98. async function deleteResult(request, response, userId) {
  99. const id = pickParam(request, 'id', 'bizId');
  100. if (!id) return response.json({ code: 400, success: false, error: '缺少 id' });
  101. const result = await Psql.query(`DELETE FROM "VideoflowResult" WHERE "bizId" = $1 AND "userId" = $2 RETURNING "bizId"`, [id, userId]);
  102. return response.json({ code: 200, success: true, data: { id, deleted: result.length > 0 } });
  103. }
  104. function rowToObj(row) {
  105. const data = typeof row.data === 'string' ? JSON.parse(row.data) : (row.data || {});
  106. return { ...data, objectId: row.objectId, createdAt: row.createdAt, updatedAt: row.updatedAt };
  107. }
  108. function pickParam(request, ...names) {
  109. const sources = [request.params, request.body, request.query, request];
  110. for (const src of sources) {
  111. if (!src || typeof src !== 'object') continue;
  112. for (const name of names) {
  113. const value = src[name];
  114. if (value !== undefined && value !== null && value !== '') return value;
  115. }
  116. }
  117. return null;
  118. }
  119. function safeLimit(value, fallback, max) {
  120. const parsed = parseInt(value || fallback, 10);
  121. return Number.isFinite(parsed) ? Math.min(Math.max(parsed, 1), max) : fallback;
  122. }
  123. function generateId() {
  124. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  125. let value = '';
  126. for (let i = 0; i < 10; i += 1) value += chars.charAt(Math.floor(Math.random() * chars.length));
  127. return value;
  128. }