|
|
@@ -0,0 +1,104 @@
|
|
|
+/**
|
|
|
+ * 交付物上报云函数(skill-deliverable 配套)
|
|
|
+ * ============================================
|
|
|
+ *
|
|
|
+ * 部署:Parse.Cloud.define 或 /api/functions 模式
|
|
|
+ * 云函数 ID:lfYlgU7SkK(保持不变)
|
|
|
+ *
|
|
|
+ * v2 变更:
|
|
|
+ * 1. 移除 FMODE_AGENT_SUPERADMIN 角色检查 → 改为:用户仅对自有容器有上报权限
|
|
|
+ * 2. agentId → agentObjectId 自动解析(传任一即可,缺失则自动补全)
|
|
|
+ * 3. 首次上报时自动创建 FmodeAgent 记录(自注册)
|
|
|
+ * 4. 参数缺失时自动补全(时间戳、状态等)
|
|
|
+ *
|
|
|
+ * 鉴权逻辑(简化版):
|
|
|
+ * sessionToken → _User → 查此用户是否是该 FmodeAgent 记录的 owner
|
|
|
+ * 是 → 允许上报;否 → 403
|
|
|
+ * 若 FmodeAgent 记录不存在且 agentId 有效 → 自动创建 + 设当前用户为 owner
|
|
|
+ */
|
|
|
+
|
|
|
+const CLASS_DELIVERABLE = 'AgentDeliverable';
|
|
|
+const CLASS_AGENT = 'FmodeAgent';
|
|
|
+
|
|
|
+Parse.Cloud.define('deliverable-report', async (req) => {
|
|
|
+ const { agentId, agentObjectId, title, summary, project, artifacts, tags } = req.params;
|
|
|
+ const user = req.user;
|
|
|
+
|
|
|
+ if (!user) throw new Parse.Error(401, '需要登录');
|
|
|
+ if (!title) throw new Parse.Error(400, 'title 必填');
|
|
|
+
|
|
|
+ // ---- Step 1: 解析/注册 FmodeAgent ----
|
|
|
+ let agentQuery = new Parse.Query(CLASS_AGENT);
|
|
|
+
|
|
|
+ if (agentObjectId) {
|
|
|
+ agentQuery.equalTo('objectId', agentObjectId);
|
|
|
+ } else if (agentId) {
|
|
|
+ agentQuery.equalTo('agentId', agentId);
|
|
|
+ } else {
|
|
|
+ throw new Parse.Error(400, 'agentId 或 agentObjectId 必填');
|
|
|
+ }
|
|
|
+
|
|
|
+ let agent = await agentQuery.first({ useMasterKey: true });
|
|
|
+
|
|
|
+ // 自动注册:agent 不存在则创建
|
|
|
+ if (!agent) {
|
|
|
+ if (!agentId) throw new Parse.Error(400, '新 agent 注册需要 agentId');
|
|
|
+ const AgentClass = Parse.Object.extend(CLASS_AGENT);
|
|
|
+ agent = new AgentClass();
|
|
|
+ agent.set('agentId', agentId);
|
|
|
+ agent.set('user', user);
|
|
|
+ agent.set('status', 'online');
|
|
|
+ agent.set('heartbeatAt', new Date());
|
|
|
+ agent = await agent.save(null, { useMasterKey: true });
|
|
|
+ }
|
|
|
+
|
|
|
+ const finalAgentId = agent.get('agentId') || agentId;
|
|
|
+ const finalAgentOid = agent.id;
|
|
|
+
|
|
|
+ // ---- Step 2: 权限校验(仅校验用户是否为 agent owner)----
|
|
|
+ const agentUser = agent.get('user');
|
|
|
+ if (agentUser && agentUser.id !== user.id) {
|
|
|
+ throw new Parse.Error(403, '仅该容器的 owner 可上报');
|
|
|
+ }
|
|
|
+ // 如果 agent 没有 owner(旧数据),自动绑定当前用户
|
|
|
+ if (!agentUser) {
|
|
|
+ agent.set('user', user);
|
|
|
+ await agent.save(null, { useMasterKey: true });
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---- Step 3: 写入交付物 ----
|
|
|
+ const DelClass = Parse.Object.extend(CLASS_DELIVERABLE);
|
|
|
+ const record = new DelClass();
|
|
|
+ record.set('agentId', finalAgentId);
|
|
|
+ record.set('agent', agent); // 指针
|
|
|
+ record.set('agentObjectId', finalAgentOid);
|
|
|
+ record.set('user', user);
|
|
|
+ record.set('title', title);
|
|
|
+ record.set('summary', summary || '');
|
|
|
+ record.set('project', project || '');
|
|
|
+ record.set('artifacts', artifacts || []);
|
|
|
+ record.set('tags', tags || []);
|
|
|
+ record.set('reportedAt', new Date());
|
|
|
+
|
|
|
+ // ACL:仅 owner 可读写
|
|
|
+ const acl = new Parse.ACL(user);
|
|
|
+ acl.setPublicReadAccess(false);
|
|
|
+ acl.setPublicWriteAccess(false);
|
|
|
+ record.setACL(acl);
|
|
|
+
|
|
|
+ const saved = await record.save(null, { useMasterKey: true });
|
|
|
+
|
|
|
+ // ---- Step 4: 更新 agent 心跳 ----
|
|
|
+ agent.set('heartbeatAt', new Date());
|
|
|
+ agent.set('status', 'online');
|
|
|
+ await agent.save(null, { useMasterKey: true });
|
|
|
+
|
|
|
+ return {
|
|
|
+ code: 200,
|
|
|
+ id: saved.id,
|
|
|
+ agentId: finalAgentId,
|
|
|
+ agentObjectId: finalAgentOid,
|
|
|
+ title: title,
|
|
|
+ createdAt: saved.get('createdAt') || saved.createdAt,
|
|
|
+ };
|
|
|
+});
|