05-remixManager.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * 云函数:remixManager(视频重塑记录)
  3. * 替代:GET/POST/DELETE /api/remixes[/:videoId[/:remixId]]
  4. * 支持 action:
  5. * listAll
  6. * listByVideo
  7. * upsert
  8. * delete
  9. */
  10. const { requireSession, assertRequestedUserMatchesSession } = require('./_session');
  11. async function handler(request, response) {
  12. try {
  13. await ensureTable();
  14. const action = pickParam(request, 'action') || 'listAll';
  15. const session = await requireSession(request, Psql);
  16. assertRequestedUserMatchesSession(request, session);
  17. const userId = session.userId;
  18. if (action === 'listAll') return listAll(response, userId);
  19. if (action === 'listByVideo') return listByVideo(request, response, userId);
  20. if (action === 'upsert') return upsertRemix(request, response, userId);
  21. if (action === 'delete') return deleteRemix(request, response, userId);
  22. return response.json({ code: 400, success: false, error: `未知 action: ${action}` });
  23. } catch (error) {
  24. console.error('remixManager failed:', error && error.message);
  25. return response.json({ code: error.status || 500, success: false, error: error.message });
  26. }
  27. }
  28. async function ensureTable() {
  29. await Psql.query(`
  30. CREATE TABLE IF NOT EXISTS "Remix" (
  31. "objectId" VARCHAR(50) PRIMARY KEY,
  32. "videoId" VARCHAR(255) NOT NULL,
  33. "remixId" VARCHAR(255) NOT NULL,
  34. "data" JSONB NOT NULL DEFAULT '{}',
  35. "userId" VARCHAR(255) DEFAULT '',
  36. "createdAt" TIMESTAMPTZ DEFAULT NOW(),
  37. "updatedAt" TIMESTAMPTZ DEFAULT NOW()
  38. )
  39. `);
  40. await Psql.query(`DROP INDEX IF EXISTS idx_remix_pair`);
  41. await Psql.query(`CREATE UNIQUE INDEX IF NOT EXISTS idx_remix_user_pair ON "Remix" ("userId", "videoId", "remixId")`);
  42. await Psql.query(`CREATE INDEX IF NOT EXISTS idx_remix_video ON "Remix" ("videoId")`);
  43. await Psql.query(`CREATE INDEX IF NOT EXISTS idx_remix_user ON "Remix" ("userId")`);
  44. }
  45. async function listAll(response, userId) {
  46. const rows = await Psql.query(
  47. `SELECT * FROM "Remix" WHERE "userId" = $1 ORDER BY "createdAt" DESC LIMIT 5000`,
  48. [userId]
  49. );
  50. const grouped = {};
  51. for (const row of rows) {
  52. if (!grouped[row.videoId]) grouped[row.videoId] = [];
  53. grouped[row.videoId].push(rowToObj(row));
  54. }
  55. return response.json({ code: 200, success: true, data: grouped });
  56. }
  57. async function listByVideo(request, response, userId) {
  58. const videoId = pickParam(request, 'videoId');
  59. if (!videoId) return response.json({ code: 400, success: false, error: '缺少 videoId' });
  60. const rows = await Psql.query(
  61. `SELECT * FROM "Remix" WHERE "videoId" = $1 AND "userId" = $2 ORDER BY "createdAt" DESC`,
  62. [videoId, userId]
  63. );
  64. return response.json({ code: 200, success: true, data: rows.map(rowToObj) });
  65. }
  66. async function upsertRemix(request, response, userId) {
  67. const videoId = pickParam(request, 'videoId');
  68. const body = pickParam(request, 'remix', 'data') || request.body || {};
  69. const remixId = body.remixId || pickParam(request, 'remixId');
  70. if (!videoId || !remixId) {
  71. return response.json({ code: 400, success: false, error: '缺少 videoId 或 remixId' });
  72. }
  73. const now = new Date().toISOString();
  74. const existing = await Psql.query(
  75. `SELECT * FROM "Remix" WHERE "videoId"=$1 AND "remixId"=$2 AND "userId"=$3 LIMIT 1`,
  76. [videoId, remixId, userId]
  77. );
  78. const previous = existing.length ? rowToObj(existing[0]) : {};
  79. const merged = {
  80. ...previous,
  81. ...body,
  82. remixId,
  83. videoId,
  84. userId,
  85. created_at: previous.created_at || body.created_at || now,
  86. updated_at: now,
  87. };
  88. await Psql.query(
  89. `INSERT INTO "Remix" ("objectId","videoId","remixId","data","userId")
  90. VALUES ($1,$2,$3,$4,$5)
  91. ON CONFLICT ("userId","videoId","remixId") DO UPDATE SET "data"=$4, "updatedAt"=NOW()`,
  92. [existing[0]?.objectId || generateId(), videoId, remixId, JSON.stringify(merged), userId]
  93. );
  94. return response.json({ code: 200, success: true, data: merged });
  95. }
  96. async function deleteRemix(request, response, userId) {
  97. const videoId = pickParam(request, 'videoId');
  98. const remixId = pickParam(request, 'remixId');
  99. if (!videoId || !remixId) {
  100. return response.json({ code: 400, success: false, error: '缺少 videoId 或 remixId' });
  101. }
  102. const result = await Psql.query(
  103. `DELETE FROM "Remix" WHERE "videoId"=$1 AND "remixId"=$2 AND "userId"=$3 RETURNING "objectId"`,
  104. [videoId, remixId, userId]
  105. );
  106. return response.json({ code: 200, success: true, data: { videoId, remixId, deleted: result.length > 0 } });
  107. }
  108. function rowToObj(row) {
  109. const data = typeof row.data === 'string' ? JSON.parse(row.data) : (row.data || {});
  110. return { ...data, objectId: row.objectId, createdAt: row.createdAt, updatedAt: row.updatedAt };
  111. }
  112. function pickParam(request, ...names) {
  113. const sources = [request.params, request.body, request.query, request];
  114. for (const src of sources) {
  115. if (!src || typeof src !== 'object') continue;
  116. for (const name of names) {
  117. const value = src[name];
  118. if (value !== undefined && value !== null && value !== '') return value;
  119. }
  120. }
  121. return null;
  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. }