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 +1 -3
- package/dist/managers/activities.js +41 -46
- package/dist/managers/index.js +1 -5
- package/dist/routes/activities.js +13 -132
- package/dist/routes/auth.js +16 -7
- package/dist/routes/users.js +5 -1
- package/dist/utils/getClientIp.js +40 -0
- package/package.json +1 -1
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
|
-
*
|
|
12
|
-
*
|
|
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}
|
|
15
|
-
* @
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
56
|
+
emitActivity
|
|
62
57
|
};
|
package/dist/managers/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
116
|
-
$exists: true
|
|
117
|
-
}
|
|
18
|
+
projectId
|
|
118
19
|
};
|
|
119
|
-
if (
|
|
120
|
-
|
|
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
|
|
54
|
+
projectId,
|
|
174
55
|
contentId,
|
|
175
56
|
activity,
|
|
176
57
|
createdAt: new Date()
|
package/dist/routes/auth.js
CHANGED
|
@@ -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:
|
|
84
|
-
|
|
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
|
-
|
|
164
|
+
const now = new Date();
|
|
160
165
|
await db.collection("profiles").updateOne({
|
|
161
166
|
_id: profile._id
|
|
162
167
|
}, {
|
|
163
168
|
$set: {
|
|
164
|
-
lastActiveAt:
|
|
169
|
+
lastActiveAt: now,
|
|
170
|
+
lastActiveFrom: getClientIp(req)
|
|
165
171
|
}
|
|
166
172
|
});
|
|
167
173
|
} else {
|
|
168
|
-
|
|
174
|
+
const now = new Date();
|
|
169
175
|
const newProfile = {
|
|
170
176
|
userId: user._id,
|
|
171
177
|
projectId: projectId,
|
|
172
|
-
createdAt:
|
|
173
|
-
|
|
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 = {
|
package/dist/routes/users.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|