powr-sdk-api 4.7.3 → 4.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,8 +22,7 @@ const {
22
22
  initializeTools,
23
23
  executeTasks,
24
24
  executeTool,
25
- createTask,
26
- createActivityFeedItem
25
+ createTask
27
26
  } = require("./managers");
28
27
  const {
29
28
  verifyToken
@@ -45,6 +44,5 @@ module.exports = {
45
44
  executeTasks,
46
45
  executeTool,
47
46
  createTask,
48
- createActivityFeedItem,
49
47
  getPowrDb
50
48
  };
@@ -8,55 +8,50 @@ const {
8
8
  } = require("mongodb");
9
9
 
10
10
  /**
11
- * Create a feed activity entry in the activities collection.
12
- * Used by host apps (e.g. Spriny) in-process and by POST /activities (feed shape).
11
+ * Emit an activity to PowrBase. Inserts one document into the activities collection.
12
+ * Does not send notification; an Atlas trigger on the collection handles that.
13
13
  *
14
- * @param {Object} params - action, userId, projectId?, targetType?, targetId?, metadata?, projectName?, projectSlug?
15
- * @returns {Promise<{ success: boolean, id?: string, message?: string }>}
14
+ * @param {Object} options
15
+ * @param {string} options.projectId - Project context (required)
16
+ * @param {string} options.action - e.g. "task_created", "task_assigned", "enquiry_converted"
17
+ * @param {string|ObjectId} options.userId - Who performed the action
18
+ * @param {string} [options.targetType] - e.g. "task", "project", "enquiry"
19
+ * @param {string|ObjectId} [options.targetId] - ID of the target entity
20
+ * @param {Object} [options.metadata] - Extra payload (e.g. recipientIds, title, body, url for notification)
21
+ * @returns {Promise<{ insertedId: ObjectId }>} The inserted document's _id
16
22
  */
17
- async function createActivityFeedItem(params) {
18
- try {
19
- const {
20
- action,
21
- userId,
22
- projectId,
23
- targetType,
24
- targetId,
25
- metadata = {},
26
- projectName,
27
- projectSlug
28
- } = params;
29
- if (!action || !userId) {
30
- return {
31
- success: false,
32
- message: "action and userId are required"
33
- };
34
- }
35
- const doc = {
36
- action,
37
- userId: new ObjectId(userId.toString()),
38
- projectId: projectId ? new ObjectId(projectId.toString()) : null,
39
- targetType: targetType || null,
40
- targetId: targetId ? new ObjectId(targetId.toString()) : null,
41
- metadata: metadata || {},
42
- projectName: projectName || null,
43
- projectSlug: projectSlug || null,
44
- createdAt: new Date()
45
- };
46
- const db = await getDb();
47
- const result = await db.collection("activities").insertOne(doc);
48
- return {
49
- success: true,
50
- id: result.insertedId.toString()
51
- };
52
- } catch (error) {
53
- console.error("createActivityFeedItem error:", error);
54
- return {
55
- success: false,
56
- message: error.message
57
- };
23
+ async function emitActivity(options) {
24
+ const {
25
+ projectId,
26
+ action,
27
+ userId,
28
+ targetType,
29
+ targetId,
30
+ metadata = {}
31
+ } = options;
32
+ if (!projectId || !action) {
33
+ throw new Error("emitActivity: projectId and action are required");
58
34
  }
35
+ const normalizeId = id => {
36
+ if (id == null) return null;
37
+ if (typeof id === "string" && ObjectId.isValid(id)) return new ObjectId(id);
38
+ return id;
39
+ };
40
+ const doc = {
41
+ projectId,
42
+ action,
43
+ userId: normalizeId(userId),
44
+ targetType: targetType || null,
45
+ targetId: normalizeId(targetId),
46
+ metadata,
47
+ createdAt: new Date()
48
+ };
49
+ const db = await getDb();
50
+ const result = await db.collection("activities").insertOne(doc);
51
+ return {
52
+ insertedId: result.insertedId
53
+ };
59
54
  }
60
55
  module.exports = {
61
- createActivityFeedItem
56
+ emitActivity
62
57
  };
@@ -3,9 +3,6 @@
3
3
  const functionsManager = require('./functions');
4
4
  const toolsManager = require('./tools');
5
5
  const scheduledTasksManager = require('./tasks');
6
- const {
7
- createActivityFeedItem
8
- } = require('./activities');
9
6
 
10
7
  // Async Functions initialization function
11
8
  const initializeFunctions = async (options = {}) => {
@@ -36,6 +33,5 @@ module.exports = {
36
33
  initializeTools,
37
34
  executeTasks,
38
35
  executeTool,
39
- createTask,
40
- createActivityFeedItem
36
+ createTask
41
37
  };
@@ -5,119 +5,21 @@ const router = express.Router();
5
5
  const {
6
6
  getDb
7
7
  } = require("../services/mongo");
8
- const {
9
- ObjectId
10
- } = require("mongodb");
11
- const {
12
- createActivityFeedItem
13
- } = require("../managers/activities");
14
8
 
15
- /**
16
- * GET /activities
17
- * - Content-scoped (existing): ?contentId= → returns docs with projectId + contentId (shape: activity, createdAt).
18
- * - Feed: ?projectId=...&from=&to=&action=&limit= → returns feed docs (action, userId, metadata, etc.) from same collection.
19
- * Feed docs have "action" field; content-scoped docs have "contentId" and "activity".
20
- */
9
+ // Get all activities
21
10
  router.get("/", async (req, res) => {
22
11
  try {
23
12
  const {
24
- contentId,
25
- from,
26
- to,
27
- action,
28
- limit
13
+ contentId
29
14
  } = req.query;
30
- const projectId = req.projectId;
31
- const isFeedRequest = from !== undefined || to !== undefined || req.query.projectId && contentId === undefined;
32
- if (isFeedRequest) {
33
- const projectIds = [].concat(req.query.projectId || []).filter(Boolean);
34
- if (projectIds.length === 0) {
35
- return res.json({
36
- success: true,
37
- activities: []
38
- });
39
- }
40
- const query = {
41
- projectId: {
42
- $in: projectIds.map(id => new ObjectId(id))
43
- },
44
- action: {
45
- $exists: true
46
- }
47
- };
48
- if (from || to) {
49
- query.createdAt = {};
50
- if (from) query.createdAt.$gte = new Date(from);
51
- if (to) query.createdAt.$lte = new Date(to);
52
- }
53
- if (action) query.action = action;
54
- const db = await getDb();
55
- const activities = await db.collection("activities").find(query).sort({
56
- createdAt: -1
57
- }).limit(Math.min(parseInt(limit, 10) || 50, 100)).toArray();
58
- const actorIds = [...new Set(activities.map(a => {
59
- var _a$userId;
60
- return (_a$userId = a.userId) === null || _a$userId === void 0 ? void 0 : _a$userId.toString();
61
- }).filter(Boolean))];
62
- let actorDetails = {};
63
- if (actorIds.length > 0) {
64
- const actors = await db.collection("users").find({
65
- _id: {
66
- $in: actorIds.map(id => new ObjectId(id))
67
- }
68
- }).project({
69
- _id: 1,
70
- fullName: 1
71
- }).toArray();
72
- actorDetails = actors.reduce((acc, u) => {
73
- acc[u._id.toString()] = u.fullName || "Unknown";
74
- return acc;
75
- }, {});
76
- }
77
- const assigneeIds = activities.filter(a => {
78
- var _a$metadata;
79
- return (_a$metadata = a.metadata) === null || _a$metadata === void 0 ? void 0 : _a$metadata.assigneeId;
80
- }).map(a => a.metadata.assigneeId);
81
- let assigneeDetails = {};
82
- if (assigneeIds.length > 0) {
83
- const assignees = await db.collection("users").find({
84
- _id: {
85
- $in: assigneeIds.map(id => new ObjectId(id))
86
- }
87
- }).project({
88
- _id: 1,
89
- fullName: 1
90
- }).toArray();
91
- assigneeDetails = assignees.reduce((acc, u) => {
92
- acc[u._id.toString()] = u.fullName || "Unknown";
93
- return acc;
94
- }, {});
95
- }
96
- const enriched = activities.map(a => {
97
- var _a$userId2, _a$projectId, _a$userId3, _a$metadata2;
98
- return {
99
- ...a,
100
- _id: a._id.toString(),
101
- userId: (_a$userId2 = a.userId) === null || _a$userId2 === void 0 ? void 0 : _a$userId2.toString(),
102
- projectId: (_a$projectId = a.projectId) === null || _a$projectId === void 0 ? void 0 : _a$projectId.toString(),
103
- actorName: actorDetails[(_a$userId3 = a.userId) === null || _a$userId3 === void 0 ? void 0 : _a$userId3.toString()] || "Unknown",
104
- assigneeName: (_a$metadata2 = a.metadata) !== null && _a$metadata2 !== void 0 && _a$metadata2.assigneeId ? assigneeDetails[a.metadata.assigneeId] || "Unknown" : null,
105
- projectName: a.projectName || null,
106
- projectSlug: a.projectSlug || null
107
- };
108
- });
109
- return res.json({
110
- success: true,
111
- activities: enriched
112
- });
113
- }
15
+ const projectId = req.projectId; // Use middleware-injected projectId
16
+
114
17
  const query = {
115
- activity: {
116
- $exists: true
117
- }
18
+ projectId
118
19
  };
119
- if (projectId) query.projectId = new ObjectId(projectId);
120
- if (contentId) query.contentId = contentId;
20
+ if (contentId) {
21
+ query.contentId = contentId;
22
+ }
121
23
  const db = await getDb();
122
24
  const activities = await db.collection("activities").find(query).toArray();
123
25
  return res.json({
@@ -133,36 +35,15 @@ router.get("/", async (req, res) => {
133
35
  }
134
36
  });
135
37
 
136
- /**
137
- * POST /activities
138
- * - Feed: body has action, userId → insert feed doc into activities collection.
139
- * - Content-scoped (existing): body has contentId, activity → insert content-scoped doc.
140
- */
38
+ // Create new activity
141
39
  router.post("/", async (req, res) => {
142
40
  try {
143
- const body = req.body;
144
- const projectId = req.projectId;
145
- if (body.action && body.userId) {
146
- const payload = {
147
- ...body,
148
- projectId: body.projectId || projectId
149
- };
150
- const result = await createActivityFeedItem(payload);
151
- if (!result.success) {
152
- return res.status(400).json({
153
- success: false,
154
- message: result.message
155
- });
156
- }
157
- return res.status(201).json({
158
- success: true,
159
- id: result.id
160
- });
161
- }
162
41
  const {
163
42
  contentId,
164
43
  activity
165
- } = body;
44
+ } = req.body;
45
+ const projectId = req.projectId; // Use middleware-injected projectId
46
+
166
47
  if (!contentId || !activity) {
167
48
  return res.status(400).json({
168
49
  success: false,
@@ -170,7 +51,7 @@ router.post("/", async (req, res) => {
170
51
  });
171
52
  }
172
53
  const activityData = {
173
- projectId: projectId ? new ObjectId(projectId) : undefined,
54
+ projectId,
174
55
  contentId,
175
56
  activity,
176
57
  createdAt: new Date()
@@ -17,6 +17,9 @@ const {
17
17
  const {
18
18
  config
19
19
  } = require("../config");
20
+ const {
21
+ getClientIp
22
+ } = require("../utils/getClientIp");
20
23
 
21
24
  // Register User
22
25
  router.post("/register", async (req, res) => {
@@ -77,11 +80,13 @@ router.post("/register", async (req, res) => {
77
80
  };
78
81
  const result = await db.collection("users").insertOne(newUser);
79
82
  if (projectId) {
83
+ const now = new Date();
80
84
  const newProfile = {
81
85
  userId: result.insertedId,
82
86
  projectId: projectId,
83
- createdAt: new Date(),
84
- updatedAt: new Date()
87
+ createdAt: now,
88
+ lastActiveAt: now,
89
+ lastActiveFrom: getClientIp(req)
85
90
  };
86
91
  await db.collection("profiles").insertOne(newProfile);
87
92
  }
@@ -156,21 +161,23 @@ router.post("/login", async (req, res) => {
156
161
  projectId: projectId
157
162
  });
158
163
  if (profile) {
159
- // Update lastActiveAt
164
+ const now = new Date();
160
165
  await db.collection("profiles").updateOne({
161
166
  _id: profile._id
162
167
  }, {
163
168
  $set: {
164
- lastActiveAt: new Date()
169
+ lastActiveAt: now,
170
+ lastActiveFrom: getClientIp(req)
165
171
  }
166
172
  });
167
173
  } else {
168
- // Create profile if it doesn't exist - only essential fields
174
+ const now = new Date();
169
175
  const newProfile = {
170
176
  userId: user._id,
171
177
  projectId: projectId,
172
- createdAt: new Date(),
173
- updatedAt: new Date()
178
+ createdAt: now,
179
+ lastActiveAt: now,
180
+ lastActiveFrom: getClientIp(req)
174
181
  };
175
182
  const profileResult = await db.collection("profiles").insertOne(newProfile);
176
183
  profile = {
@@ -204,6 +211,8 @@ router.post("/login", async (req, res) => {
204
211
  projectId,
205
212
  createdAt,
206
213
  updatedAt,
214
+ lastActiveAt,
215
+ lastActiveFrom,
207
216
  ...profileData
208
217
  } = profile;
209
218
  mergedUser = {
@@ -11,6 +11,9 @@ const {
11
11
  const {
12
12
  verifyToken
13
13
  } = require("../middleware/jwtToken");
14
+ const {
15
+ getClientIp
16
+ } = require("../utils/getClientIp");
14
17
 
15
18
  // Create User
16
19
  router.post("/", verifyToken, async (req, res) => {
@@ -52,7 +55,8 @@ router.post("/", verifyToken, async (req, res) => {
52
55
  projectId: projectId,
53
56
  access: userData.access || 1,
54
57
  createdAt: new Date(),
55
- updatedAt: new Date()
58
+ lastActiveAt: new Date(),
59
+ lastActiveFrom: getClientIp(req)
56
60
  };
57
61
  await db.collection("profiles").insertOne(newProfile);
58
62
  return res.status(201).json({
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Best-effort client IP from the incoming HTTP request
5
+ * (uses X-Forwarded-For, X-Real-IP, req.ip, or socket address).
6
+ */
7
+ function getClientIp(req) {
8
+ const forwarded = req.headers["x-forwarded-for"];
9
+ const realIp = req.headers["x-real-ip"];
10
+ if (typeof forwarded === "string" && forwarded.length > 0) {
11
+ const first = forwarded.split(",")[0].trim();
12
+ if (first) {
13
+ console.log("[getClientIp] using x-forwarded-for:", first);
14
+ return first;
15
+ }
16
+ }
17
+ if (typeof realIp === "string" && realIp.trim()) {
18
+ const v = realIp.trim();
19
+ console.log("[getClientIp] using x-real-ip:", v);
20
+ return v;
21
+ }
22
+ if (req.ip) {
23
+ console.log("[getClientIp] using req.ip:", req.ip);
24
+ return req.ip;
25
+ }
26
+ if (req.socket && req.socket.remoteAddress) {
27
+ console.log("[getClientIp] using socket.remoteAddress:", req.socket.remoteAddress);
28
+ return req.socket.remoteAddress;
29
+ }
30
+ console.log("[getClientIp] no IP found", {
31
+ "x-forwarded-for": forwarded,
32
+ "x-real-ip": realIp,
33
+ reqIp: req.ip,
34
+ remoteAddress: req.socket && req.socket.remoteAddress
35
+ });
36
+ return null;
37
+ }
38
+ module.exports = {
39
+ getClientIp
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "powr-sdk-api",
3
- "version": "4.7.3",
3
+ "version": "4.8.1",
4
4
  "description": "Shared API core library for PowrStack projects. Zero dependencies - works with Express, Next.js API routes, and other frameworks. All features are optional and install only what you need.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",