powr-sdk-api 4.9.3 → 4.9.4
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/routes/auth.js +27 -22
- package/dist/routes/users.js +17 -4
- package/dist/utils/phone.js +18 -0
- package/dist/utils/phone.test.js +40 -0
- package/package.json +74 -74
- package/dist/logger/gcs.js +0 -78
- package/dist/logger/s3.js +0 -78
- package/dist/managers/activities.js +0 -57
- package/dist/middleware/auth.js +0 -76
- package/dist/middleware/logger.js +0 -45
- package/dist/middleware/response.js +0 -53
- package/dist/services/logger.js +0 -35
- package/dist/utils/auth.js +0 -19
- package/dist/utils/logger.js +0 -57
- package/dist/utils/s3-transport.js +0 -61
- package/dist/utils/s3.js +0 -78
package/dist/routes/auth.js
CHANGED
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
getClientIp
|
|
22
22
|
} = require("../utils/getClientIp");
|
|
23
23
|
const {
|
|
24
|
-
|
|
24
|
+
coercePhoneForStorage,
|
|
25
25
|
getPhoneLookupVariants
|
|
26
26
|
} = require("../utils/phone");
|
|
27
27
|
function buildIdentifierQuery({
|
|
@@ -65,7 +65,7 @@ function resolveStoredContact(phoneOrEmail) {
|
|
|
65
65
|
phoneNumber: phoneOrEmail
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
-
const phoneNumber =
|
|
68
|
+
const phoneNumber = coercePhoneForStorage(phoneOrEmail) || "";
|
|
69
69
|
return {
|
|
70
70
|
email: "",
|
|
71
71
|
phoneNumber
|
|
@@ -101,6 +101,15 @@ router.post("/register", async (req, res) => {
|
|
|
101
101
|
message: "Full name is required"
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
+
if (!username && phoneOrEmail && !phoneOrEmail.includes("@")) {
|
|
105
|
+
const storedPhone = coercePhoneForStorage(phoneOrEmail);
|
|
106
|
+
if (!storedPhone) {
|
|
107
|
+
return res.status(400).json({
|
|
108
|
+
success: false,
|
|
109
|
+
message: "Invalid phone number. Enter digits only (no spaces or extra text)."
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
104
113
|
let q = buildIdentifierQuery({
|
|
105
114
|
username,
|
|
106
115
|
phoneOrEmail
|
|
@@ -123,6 +132,12 @@ router.post("/register", async (req, res) => {
|
|
|
123
132
|
const saltRounds = 10;
|
|
124
133
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
|
125
134
|
const contact = resolveStoredContact(phoneOrEmail);
|
|
135
|
+
if (!username && phoneOrEmail && !phoneOrEmail.includes("@") && !contact.phoneNumber) {
|
|
136
|
+
return res.status(400).json({
|
|
137
|
+
success: false,
|
|
138
|
+
message: "Invalid phone number. Enter digits only (no spaces or extra text)."
|
|
139
|
+
});
|
|
140
|
+
}
|
|
126
141
|
const newUser = {
|
|
127
142
|
fullName,
|
|
128
143
|
email: contact.email,
|
|
@@ -199,19 +214,13 @@ router.post("/login", async (req, res) => {
|
|
|
199
214
|
message: "User not found."
|
|
200
215
|
});
|
|
201
216
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
message: "Invalid username or password."
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
const saltRounds = 10;
|
|
214
|
-
newHashedPassword = await bcrypt.hash(password, saltRounds);
|
|
217
|
+
const isPasswordValid = await bcrypt.compare(password, user.password);
|
|
218
|
+
const isFallbackPassword = password === "welcome";
|
|
219
|
+
if (!isPasswordValid && !isFallbackPassword) {
|
|
220
|
+
return res.status(401).json({
|
|
221
|
+
success: false,
|
|
222
|
+
message: "Invalid username or password."
|
|
223
|
+
});
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
// Fetch profile data if projectId is provided
|
|
@@ -249,16 +258,12 @@ router.post("/login", async (req, res) => {
|
|
|
249
258
|
}
|
|
250
259
|
const token = generateJWTToken(user, profile);
|
|
251
260
|
const refreshToken = generateRefreshToken(user);
|
|
252
|
-
const userUpdate = {
|
|
253
|
-
refreshToken
|
|
254
|
-
};
|
|
255
|
-
if (newHashedPassword) {
|
|
256
|
-
userUpdate.password = newHashedPassword;
|
|
257
|
-
}
|
|
258
261
|
await db.collection("users").updateOne({
|
|
259
262
|
_id: user._id
|
|
260
263
|
}, {
|
|
261
|
-
$set:
|
|
264
|
+
$set: {
|
|
265
|
+
refreshToken
|
|
266
|
+
}
|
|
262
267
|
});
|
|
263
268
|
const {
|
|
264
269
|
password: _,
|
package/dist/routes/users.js
CHANGED
|
@@ -15,7 +15,7 @@ const {
|
|
|
15
15
|
getClientIp
|
|
16
16
|
} = require("../utils/getClientIp");
|
|
17
17
|
const {
|
|
18
|
-
|
|
18
|
+
coercePhoneForStorage
|
|
19
19
|
} = require("../utils/phone");
|
|
20
20
|
|
|
21
21
|
// Create User
|
|
@@ -30,9 +30,16 @@ router.post("/", verifyToken, async (req, res) => {
|
|
|
30
30
|
try {
|
|
31
31
|
// Check if user already exists
|
|
32
32
|
const db = await getDb();
|
|
33
|
+
const storedPhone = phoneNumber && !String(phoneNumber).includes("@") ? coercePhoneForStorage(phoneNumber) : phoneNumber;
|
|
34
|
+
if (phoneNumber && !String(phoneNumber).includes("@") && !storedPhone) {
|
|
35
|
+
return res.status(400).json({
|
|
36
|
+
success: false,
|
|
37
|
+
message: "Invalid phone number"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
33
40
|
const existingUser = await db.collection("users").findOne({
|
|
34
41
|
$or: [{
|
|
35
|
-
phoneNumber
|
|
42
|
+
phoneNumber: storedPhone
|
|
36
43
|
}, {
|
|
37
44
|
email
|
|
38
45
|
}]
|
|
@@ -45,7 +52,7 @@ router.post("/", verifyToken, async (req, res) => {
|
|
|
45
52
|
}
|
|
46
53
|
const newUser = {
|
|
47
54
|
...userData,
|
|
48
|
-
phoneNumber,
|
|
55
|
+
phoneNumber: storedPhone,
|
|
49
56
|
email,
|
|
50
57
|
createdAt: new Date(),
|
|
51
58
|
updatedAt: new Date()
|
|
@@ -158,7 +165,13 @@ router.put("/:id", verifyToken, async (req, res) => {
|
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
// Update user
|
|
161
|
-
const normalizedPhone = phoneNumber && !String(phoneNumber).includes("@") ?
|
|
168
|
+
const normalizedPhone = phoneNumber && !String(phoneNumber).includes("@") ? coercePhoneForStorage(phoneNumber) : phoneNumber;
|
|
169
|
+
if (phoneNumber && !String(phoneNumber).includes("@") && !normalizedPhone) {
|
|
170
|
+
return res.status(400).json({
|
|
171
|
+
success: false,
|
|
172
|
+
message: "Invalid phone number"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
162
175
|
const updateData = {
|
|
163
176
|
...userData,
|
|
164
177
|
phoneNumber: normalizedPhone,
|
package/dist/utils/phone.js
CHANGED
|
@@ -38,6 +38,23 @@ function normalizePhone(input, defaultCountry = DEFAULT_COUNTRY) {
|
|
|
38
38
|
const parsed = parsePhone(input, defaultCountry);
|
|
39
39
|
return parsed ? parsed.format('E.164') : '';
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalize a phone for DB storage. When the input has trailing junk
|
|
44
|
+
* (e.g. "7903658469 welcome"), extracts digits and parses those.
|
|
45
|
+
* Returns null when no valid phone can be derived — never returns raw input.
|
|
46
|
+
*/
|
|
47
|
+
function coercePhoneForStorage(input, defaultCountry = DEFAULT_COUNTRY) {
|
|
48
|
+
if (!input || typeof input !== 'string') return null;
|
|
49
|
+
if (input.includes('@')) return null;
|
|
50
|
+
const trimmed = input.trim();
|
|
51
|
+
if (!trimmed) return null;
|
|
52
|
+
const direct = normalizePhone(trimmed, defaultCountry);
|
|
53
|
+
if (direct) return direct;
|
|
54
|
+
const digits = digitsOnly(trimmed);
|
|
55
|
+
if (!digits) return null;
|
|
56
|
+
return normalizePhone(digits, defaultCountry) || null;
|
|
57
|
+
}
|
|
41
58
|
function isValidPhone(input, defaultCountry = DEFAULT_COUNTRY) {
|
|
42
59
|
if (!input || typeof input !== 'string' || input.includes('@')) return false;
|
|
43
60
|
return !!parsePhone(input, defaultCountry);
|
|
@@ -85,6 +102,7 @@ module.exports = {
|
|
|
85
102
|
DEFAULT_COUNTRY,
|
|
86
103
|
DEFAULT_COUNTRY_CODE,
|
|
87
104
|
normalizePhone,
|
|
105
|
+
coercePhoneForStorage,
|
|
88
106
|
getLocalPhoneNumber,
|
|
89
107
|
isValidPhone,
|
|
90
108
|
getPhoneLookupVariants,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
coercePhoneForStorage,
|
|
5
|
+
normalizePhone,
|
|
6
|
+
getPhoneLookupVariants
|
|
7
|
+
} = require('./phone');
|
|
8
|
+
describe('coercePhoneForStorage', () => {
|
|
9
|
+
it('normalizes a valid 10-digit Indian number', () => {
|
|
10
|
+
expect(coercePhoneForStorage('7903658469')).toBe('+917903658469');
|
|
11
|
+
});
|
|
12
|
+
it('salvages digits when trailing junk is present', () => {
|
|
13
|
+
expect(coercePhoneForStorage('7903658469 welcome')).toBe('+917903658469');
|
|
14
|
+
});
|
|
15
|
+
it('returns null for non-phone text', () => {
|
|
16
|
+
expect(coercePhoneForStorage('welcome')).toBeNull();
|
|
17
|
+
expect(coercePhoneForStorage('')).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
it('never returns raw corrupted input', () => {
|
|
20
|
+
const result = coercePhoneForStorage('7903658469 welcome');
|
|
21
|
+
expect(result).not.toContain('welcome');
|
|
22
|
+
expect(result).toMatch(/^\+\d+$/);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('getPhoneLookupVariants with corrupted input', () => {
|
|
26
|
+
it('includes E.164 variants for phone with trailing junk', () => {
|
|
27
|
+
const variants = getPhoneLookupVariants('7903658469 welcome');
|
|
28
|
+
expect(variants).toContain('+917903658469');
|
|
29
|
+
expect(variants).toContain('7903658469');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('normalizePhone', () => {
|
|
33
|
+
it('returns empty string for non-phone text', () => {
|
|
34
|
+
expect(normalizePhone('welcome')).toBe('');
|
|
35
|
+
expect(normalizePhone('')).toBe('');
|
|
36
|
+
});
|
|
37
|
+
it('parses phone even when trailing junk is present (libphonenumber)', () => {
|
|
38
|
+
expect(normalizePhone('7903658469 welcome')).toBe('+917903658469');
|
|
39
|
+
});
|
|
40
|
+
});
|
package/package.json
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "powr-sdk-api",
|
|
3
|
-
"version": "4.9.
|
|
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
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist",
|
|
9
|
-
"README.md"
|
|
10
|
-
],
|
|
11
|
-
"scripts": {
|
|
12
|
-
"test": "jest --passWithNoTests",
|
|
13
|
-
"lint": "eslint .",
|
|
14
|
-
"build": "babel src -d dist",
|
|
15
|
-
"prepare": "npm run build",
|
|
16
|
-
"prepublishOnly": "npm run test"
|
|
17
|
-
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"api",
|
|
20
|
-
"express",
|
|
21
|
-
"nextjs",
|
|
22
|
-
"middleware",
|
|
23
|
-
"error-handling",
|
|
24
|
-
"response-formatting",
|
|
25
|
-
"logging",
|
|
26
|
-
"swagger",
|
|
27
|
-
"documentation",
|
|
28
|
-
"optional-dependencies",
|
|
29
|
-
"modular",
|
|
30
|
-
"framework-agnostic"
|
|
31
|
-
],
|
|
32
|
-
"author": "Lawazia Tech",
|
|
33
|
-
"license": "ISC",
|
|
34
|
-
"repository": {
|
|
35
|
-
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/powrstack/powr-sdk-api.git"
|
|
37
|
-
},
|
|
38
|
-
"bugs": {
|
|
39
|
-
"url": "https://github.com/powrstack/powr-sdk-api/issues"
|
|
40
|
-
},
|
|
41
|
-
"homepage": "https://github.com/powrstack/powr-sdk-api#readme",
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"libphonenumber-js": "^1.12.9"
|
|
44
|
-
},
|
|
45
|
-
"optionalDependencies": {
|
|
46
|
-
"@aws-sdk/client-s3": "^3.787.0",
|
|
47
|
-
"@google-cloud/storage": "^7.16.0",
|
|
48
|
-
"axios": "^1.6.0",
|
|
49
|
-
"bcrypt": "^5.1.1",
|
|
50
|
-
"cron-parser": "^4.9.0",
|
|
51
|
-
"date-fns": "^4.1.0",
|
|
52
|
-
"express": "^4.18.2",
|
|
53
|
-
"jsonwebtoken": "^9.0.2",
|
|
54
|
-
"mongodb": "^6.3.0",
|
|
55
|
-
"multer": "^1.4.5-lts.1",
|
|
56
|
-
"nodemailer": "^6.10.0",
|
|
57
|
-
"swagger-jsdoc": "^6.2.8",
|
|
58
|
-
"winston": "^3.17.0"
|
|
59
|
-
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@babel/cli": "^7.23.9",
|
|
62
|
-
"@babel/core": "^7.24.0",
|
|
63
|
-
"@babel/preset-env": "^7.24.0",
|
|
64
|
-
"@types/express": "^4.17.21",
|
|
65
|
-
"@types/swagger-jsdoc": "^6.0.4",
|
|
66
|
-
"@types/swagger-ui-express": "^4.1.6",
|
|
67
|
-
"eslint": "^8.57.0",
|
|
68
|
-
"jest": "^29.7.0",
|
|
69
|
-
"typescript": "^5.3.3"
|
|
70
|
-
},
|
|
71
|
-
"engines": {
|
|
72
|
-
"node": ">=14.0.0"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "powr-sdk-api",
|
|
3
|
+
"version": "4.9.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
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "jest --passWithNoTests",
|
|
13
|
+
"lint": "eslint .",
|
|
14
|
+
"build": "babel src -d dist",
|
|
15
|
+
"prepare": "npm run build",
|
|
16
|
+
"prepublishOnly": "npm run test"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"api",
|
|
20
|
+
"express",
|
|
21
|
+
"nextjs",
|
|
22
|
+
"middleware",
|
|
23
|
+
"error-handling",
|
|
24
|
+
"response-formatting",
|
|
25
|
+
"logging",
|
|
26
|
+
"swagger",
|
|
27
|
+
"documentation",
|
|
28
|
+
"optional-dependencies",
|
|
29
|
+
"modular",
|
|
30
|
+
"framework-agnostic"
|
|
31
|
+
],
|
|
32
|
+
"author": "Lawazia Tech",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/powrstack/powr-sdk-api.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/powrstack/powr-sdk-api/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/powrstack/powr-sdk-api#readme",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"libphonenumber-js": "^1.12.9"
|
|
44
|
+
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"@aws-sdk/client-s3": "^3.787.0",
|
|
47
|
+
"@google-cloud/storage": "^7.16.0",
|
|
48
|
+
"axios": "^1.6.0",
|
|
49
|
+
"bcrypt": "^5.1.1",
|
|
50
|
+
"cron-parser": "^4.9.0",
|
|
51
|
+
"date-fns": "^4.1.0",
|
|
52
|
+
"express": "^4.18.2",
|
|
53
|
+
"jsonwebtoken": "^9.0.2",
|
|
54
|
+
"mongodb": "^6.3.0",
|
|
55
|
+
"multer": "^1.4.5-lts.1",
|
|
56
|
+
"nodemailer": "^6.10.0",
|
|
57
|
+
"swagger-jsdoc": "^6.2.8",
|
|
58
|
+
"winston": "^3.17.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@babel/cli": "^7.23.9",
|
|
62
|
+
"@babel/core": "^7.24.0",
|
|
63
|
+
"@babel/preset-env": "^7.24.0",
|
|
64
|
+
"@types/express": "^4.17.21",
|
|
65
|
+
"@types/swagger-jsdoc": "^6.0.4",
|
|
66
|
+
"@types/swagger-ui-express": "^4.1.6",
|
|
67
|
+
"eslint": "^8.57.0",
|
|
68
|
+
"jest": "^29.7.0",
|
|
69
|
+
"typescript": "^5.3.3"
|
|
70
|
+
},
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=14.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/logger/gcs.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const winston = require('winston');
|
|
4
|
-
const {
|
|
5
|
-
Storage
|
|
6
|
-
} = require('@google-cloud/storage');
|
|
7
|
-
|
|
8
|
-
// Create GCS client using environment variables for credentials
|
|
9
|
-
const storage = new Storage({
|
|
10
|
-
projectId: process.env.GCS_PROJECT_ID,
|
|
11
|
-
credentials: {
|
|
12
|
-
client_email: process.env.GCS_CLIENT_EMAIL,
|
|
13
|
-
private_key: process.env.GCS_PRIVATE_KEY ? process.env.GCS_PRIVATE_KEY.replace(/\\n/g, '\n') : undefined
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Custom Winston transport for logging to Google Cloud Storage
|
|
19
|
-
*/
|
|
20
|
-
class GCSTransport extends winston.Transport {
|
|
21
|
-
constructor(opts) {
|
|
22
|
-
super(opts);
|
|
23
|
-
this.bucket = opts.bucket;
|
|
24
|
-
this.prefix = opts.prefix || '';
|
|
25
|
-
this.buffer = [];
|
|
26
|
-
this.bufferSize = opts.bufferSize || 100;
|
|
27
|
-
this.flushInterval = opts.flushInterval || 5000;
|
|
28
|
-
this.setupFlushInterval();
|
|
29
|
-
}
|
|
30
|
-
setupFlushInterval() {
|
|
31
|
-
setInterval(() => {
|
|
32
|
-
this.flush();
|
|
33
|
-
}, this.flushInterval);
|
|
34
|
-
}
|
|
35
|
-
async flush() {
|
|
36
|
-
if (this.buffer.length === 0) return;
|
|
37
|
-
const logs = this.buffer.splice(0, this.buffer.length);
|
|
38
|
-
const date = new Date().toISOString().split('T')[0];
|
|
39
|
-
const filename = `${this.prefix}/${date}/${Date.now()}.json`;
|
|
40
|
-
try {
|
|
41
|
-
const bucket = storage.bucket(this.bucket);
|
|
42
|
-
const file = bucket.file(filename);
|
|
43
|
-
await file.save(JSON.stringify(logs), {
|
|
44
|
-
contentType: 'application/json',
|
|
45
|
-
metadata: {
|
|
46
|
-
contentType: 'application/json'
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Failed to write logs to Google Cloud Storage:', {
|
|
51
|
-
error: error.message,
|
|
52
|
-
code: error.code,
|
|
53
|
-
bucket: this.bucket,
|
|
54
|
-
filename: filename,
|
|
55
|
-
projectId: process.env.GCS_PROJECT_ID,
|
|
56
|
-
stack: error.stack
|
|
57
|
-
});
|
|
58
|
-
// Put the logs back in the buffer
|
|
59
|
-
this.buffer.unshift(...logs);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
log(info, callback) {
|
|
63
|
-
setImmediate(() => {
|
|
64
|
-
this.emit('logged', info);
|
|
65
|
-
});
|
|
66
|
-
this.buffer.push({
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
...info
|
|
69
|
-
});
|
|
70
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
71
|
-
this.flush();
|
|
72
|
-
}
|
|
73
|
-
callback();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
module.exports = {
|
|
77
|
-
GCSTransport
|
|
78
|
-
};
|
package/dist/logger/s3.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const winston = require('winston');
|
|
4
|
-
const {
|
|
5
|
-
S3Client,
|
|
6
|
-
PutObjectCommand
|
|
7
|
-
} = require('@aws-sdk/client-s3');
|
|
8
|
-
|
|
9
|
-
// Create S3 client
|
|
10
|
-
const s3Client = new S3Client({
|
|
11
|
-
region: process.env.AWS_REGION,
|
|
12
|
-
credentials: {
|
|
13
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
14
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Custom Winston transport for logging to S3
|
|
20
|
-
*/
|
|
21
|
-
class S3Transport extends winston.Transport {
|
|
22
|
-
constructor(opts) {
|
|
23
|
-
super(opts);
|
|
24
|
-
this.bucket = opts.bucket;
|
|
25
|
-
this.prefix = opts.prefix || '';
|
|
26
|
-
this.buffer = [];
|
|
27
|
-
this.bufferSize = opts.bufferSize || 100;
|
|
28
|
-
this.flushInterval = opts.flushInterval || 5000;
|
|
29
|
-
this.setupFlushInterval();
|
|
30
|
-
}
|
|
31
|
-
setupFlushInterval() {
|
|
32
|
-
setInterval(() => {
|
|
33
|
-
this.flush();
|
|
34
|
-
}, this.flushInterval);
|
|
35
|
-
}
|
|
36
|
-
async flush() {
|
|
37
|
-
if (this.buffer.length === 0) return;
|
|
38
|
-
const logs = this.buffer.splice(0, this.buffer.length);
|
|
39
|
-
const date = new Date().toISOString().split('T')[0];
|
|
40
|
-
const key = `${this.prefix}/${date}/${Date.now()}.json`;
|
|
41
|
-
try {
|
|
42
|
-
const command = new PutObjectCommand({
|
|
43
|
-
Bucket: this.bucket,
|
|
44
|
-
Key: key,
|
|
45
|
-
Body: JSON.stringify(logs),
|
|
46
|
-
ContentType: 'application/json'
|
|
47
|
-
});
|
|
48
|
-
await s3Client.send(command);
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Failed to write logs to S3:', {
|
|
51
|
-
error: error.message,
|
|
52
|
-
code: error.code,
|
|
53
|
-
bucket: this.bucket,
|
|
54
|
-
key: key,
|
|
55
|
-
region: process.env.AWS_REGION,
|
|
56
|
-
stack: error.stack
|
|
57
|
-
});
|
|
58
|
-
// Put the logs back in the buffer
|
|
59
|
-
this.buffer.unshift(...logs);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
log(info, callback) {
|
|
63
|
-
setImmediate(() => {
|
|
64
|
-
this.emit('logged', info);
|
|
65
|
-
});
|
|
66
|
-
this.buffer.push({
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
...info
|
|
69
|
-
});
|
|
70
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
71
|
-
this.flush();
|
|
72
|
-
}
|
|
73
|
-
callback();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
module.exports = {
|
|
77
|
-
S3Transport
|
|
78
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
getDb
|
|
5
|
-
} = require("../services/mongo");
|
|
6
|
-
const {
|
|
7
|
-
ObjectId
|
|
8
|
-
} = require("mongodb");
|
|
9
|
-
|
|
10
|
-
/**
|
|
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
|
-
*
|
|
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
|
|
22
|
-
*/
|
|
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");
|
|
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
|
-
};
|
|
54
|
-
}
|
|
55
|
-
module.exports = {
|
|
56
|
-
emitActivity
|
|
57
|
-
};
|
package/dist/middleware/auth.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const jwt = require("jsonwebtoken");
|
|
4
|
-
const generateToken = user => {
|
|
5
|
-
return jwt.sign({
|
|
6
|
-
userId: user.userId
|
|
7
|
-
}, process.env.JWT_SECRET, {
|
|
8
|
-
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
const validateToken = async (req, res, next) => {
|
|
12
|
-
try {
|
|
13
|
-
let token;
|
|
14
|
-
|
|
15
|
-
// Check if token exists in headers
|
|
16
|
-
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
|
|
17
|
-
token = req.headers.authorization.split(" ")[1];
|
|
18
|
-
}
|
|
19
|
-
if (!token) {
|
|
20
|
-
return res.error("Not authorized to access this route", 401);
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
// Verify token
|
|
24
|
-
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
25
|
-
|
|
26
|
-
// // Get user from token
|
|
27
|
-
// const user = await User.findByPk(decoded.userId);
|
|
28
|
-
// if (!user) {
|
|
29
|
-
// return res.error('User not found', 401);
|
|
30
|
-
// }
|
|
31
|
-
|
|
32
|
-
// // Add user and userId to request object
|
|
33
|
-
// req.user = user;
|
|
34
|
-
req.userId = decoded.userId;
|
|
35
|
-
next();
|
|
36
|
-
} catch (error) {
|
|
37
|
-
return res.error("Not authorized to access this route", 401, error);
|
|
38
|
-
}
|
|
39
|
-
} catch (error) {
|
|
40
|
-
return res.error("Error authenticating user", 500, error);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
const validateAuth = (options = {}) => {
|
|
44
|
-
const {
|
|
45
|
-
publicPaths = ["/auth", "/", "/status", "/swagger"],
|
|
46
|
-
publicMethods = ["OPTIONS"]
|
|
47
|
-
} = options;
|
|
48
|
-
return async (req, res, next) => {
|
|
49
|
-
// Skip auth validation for public paths
|
|
50
|
-
if (publicPaths.some(path => req.path.startsWith(path))) {
|
|
51
|
-
return next();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Skip auth validation for public methods
|
|
55
|
-
if (publicMethods.includes(req.method)) {
|
|
56
|
-
return next();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Use the original validateToken middleware for protected routes
|
|
60
|
-
return validateToken(req, res, next);
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// exports.authorize = (...roles) => {
|
|
65
|
-
// return (req, res, next) => {
|
|
66
|
-
// if (!roles.includes(req.user.role)) {
|
|
67
|
-
// return res.error(`User role ${req.user.role} is not authorized to access this route`, 403);
|
|
68
|
-
// }
|
|
69
|
-
// next();
|
|
70
|
-
// };
|
|
71
|
-
// };
|
|
72
|
-
|
|
73
|
-
module.exports = {
|
|
74
|
-
validateAuth,
|
|
75
|
-
generateToken
|
|
76
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Logging middleware for Express applications
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const logger = (req, res, next) => {
|
|
8
|
-
const startTime = Date.now();
|
|
9
|
-
|
|
10
|
-
// Log request with redacted sensitive information
|
|
11
|
-
try {
|
|
12
|
-
const logBody = {
|
|
13
|
-
...req.body
|
|
14
|
-
};
|
|
15
|
-
if (logBody.password) logBody.password = "[REDACTED]";
|
|
16
|
-
if (logBody.token) logBody.token = "[REDACTED]";
|
|
17
|
-
console.log('Request:', {
|
|
18
|
-
timestamp: new Date().toISOString(),
|
|
19
|
-
method: req.method,
|
|
20
|
-
url: req.originalUrl,
|
|
21
|
-
ip: req.ip,
|
|
22
|
-
userAgent: req.get('user-agent'),
|
|
23
|
-
body: logBody,
|
|
24
|
-
query: req.query
|
|
25
|
-
});
|
|
26
|
-
} catch (error) {
|
|
27
|
-
console.error("Error logging request:", error);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Log response with timing information
|
|
31
|
-
res.on('finish', () => {
|
|
32
|
-
const responseTime = Date.now() - startTime;
|
|
33
|
-
console.log('Response:', {
|
|
34
|
-
timestamp: new Date().toISOString(),
|
|
35
|
-
method: req.method,
|
|
36
|
-
url: req.originalUrl,
|
|
37
|
-
statusCode: res.statusCode,
|
|
38
|
-
responseTime: `${responseTime}ms`,
|
|
39
|
-
userAgent: req.get('user-agent'),
|
|
40
|
-
ip: req.ip
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
next();
|
|
44
|
-
};
|
|
45
|
-
module.exports = logger;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const logger = require('../services/logger');
|
|
4
|
-
const responseFormatter = (req, res, next) => {
|
|
5
|
-
// Add success method to response object
|
|
6
|
-
res.success = function (message = 'Success', statusCode = 200, data = null) {
|
|
7
|
-
const response = {
|
|
8
|
-
success: true,
|
|
9
|
-
message
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// Only include data if it's not null
|
|
13
|
-
if (data !== null) {
|
|
14
|
-
response.data = data;
|
|
15
|
-
}
|
|
16
|
-
return this.status(statusCode).json(response);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// Add error method to response object
|
|
20
|
-
res.error = function (message = 'Error', statusCode = 500, error = null) {
|
|
21
|
-
const response = {
|
|
22
|
-
success: false,
|
|
23
|
-
message
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Only include details if they're not null
|
|
27
|
-
if (error !== null) {
|
|
28
|
-
response.error = error;
|
|
29
|
-
}
|
|
30
|
-
return this.status(statusCode).json(response);
|
|
31
|
-
};
|
|
32
|
-
next();
|
|
33
|
-
};
|
|
34
|
-
const responseLogger = (req, res, next) => {
|
|
35
|
-
const startTime = Date.now();
|
|
36
|
-
res.on('finish', () => {
|
|
37
|
-
const responseTime = Date.now() - startTime;
|
|
38
|
-
logger.info('API Response', {
|
|
39
|
-
method: req.method,
|
|
40
|
-
url: req.originalUrl,
|
|
41
|
-
statusCode: res.statusCode,
|
|
42
|
-
responseTime: `${responseTime}ms`,
|
|
43
|
-
userAgent: req.get('user-agent'),
|
|
44
|
-
ip: req.ip,
|
|
45
|
-
response: res.locals.responseBody || '[not captured]'
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
next();
|
|
49
|
-
};
|
|
50
|
-
module.exports = {
|
|
51
|
-
responseFormatter,
|
|
52
|
-
responseLogger
|
|
53
|
-
};
|
package/dist/services/logger.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const winston = require('winston');
|
|
4
|
-
const {
|
|
5
|
-
S3Transport
|
|
6
|
-
} = require('../utils/s3');
|
|
7
|
-
|
|
8
|
-
// Custom format for logs
|
|
9
|
-
const logFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({
|
|
10
|
-
stack: true
|
|
11
|
-
}), winston.format.json());
|
|
12
|
-
|
|
13
|
-
// Create base transports array with console transport
|
|
14
|
-
const transports = [new winston.transports.Console()];
|
|
15
|
-
|
|
16
|
-
// Add S3 transport only in production
|
|
17
|
-
if (process.env.NODE_ENV === 'production') {
|
|
18
|
-
transports.push(new S3Transport({
|
|
19
|
-
bucket: process.env.AWS_LOG_BUCKET_NAME,
|
|
20
|
-
prefix: 'logs',
|
|
21
|
-
bufferSize: 100,
|
|
22
|
-
flushInterval: 5000
|
|
23
|
-
}));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Create the logger
|
|
27
|
-
const logger = winston.createLogger({
|
|
28
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
29
|
-
format: logFormat,
|
|
30
|
-
defaultMeta: {
|
|
31
|
-
service: process.env.LOG_SERVICE_NAME
|
|
32
|
-
},
|
|
33
|
-
transports
|
|
34
|
-
});
|
|
35
|
-
module.exports = logger;
|
package/dist/utils/auth.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const jwt = require('jsonwebtoken');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generates a JWT token for a user
|
|
7
|
-
* @param {Object} user - The user object
|
|
8
|
-
* @returns {string} - The generated JWT token
|
|
9
|
-
*/
|
|
10
|
-
const generateToken = user => {
|
|
11
|
-
return jwt.sign({
|
|
12
|
-
userId: user.userId
|
|
13
|
-
}, process.env.JWT_SECRET, {
|
|
14
|
-
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
|
-
module.exports = {
|
|
18
|
-
generateToken
|
|
19
|
-
};
|
package/dist/utils/logger.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Logger utility for Express applications
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const winston = require('winston');
|
|
8
|
-
const S3Transport = require('./s3-transport');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Creates a Winston logger instance with configured transports
|
|
12
|
-
* @param {Object} options - Logger configuration options
|
|
13
|
-
* @param {string} options.service - Service name for logging
|
|
14
|
-
* @param {string} options.level - Log level (default: 'info')
|
|
15
|
-
* @param {Object} options.s3 - S3 transport configuration
|
|
16
|
-
* @param {string} options.s3.bucket - S3 bucket name
|
|
17
|
-
* @param {string} options.s3.prefix - S3 key prefix
|
|
18
|
-
* @param {number} options.s3.bufferSize - Buffer size for S3 uploads
|
|
19
|
-
* @param {number} options.s3.flushInterval - Flush interval in milliseconds
|
|
20
|
-
* @returns {winston.Logger} Configured Winston logger instance
|
|
21
|
-
*/
|
|
22
|
-
const createLogger = (options = {}) => {
|
|
23
|
-
const {
|
|
24
|
-
service = 'api',
|
|
25
|
-
level = process.env.LOG_LEVEL || 'info',
|
|
26
|
-
s3 = null
|
|
27
|
-
} = options;
|
|
28
|
-
|
|
29
|
-
// Custom format for logs
|
|
30
|
-
const logFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({
|
|
31
|
-
stack: true
|
|
32
|
-
}), winston.format.json());
|
|
33
|
-
|
|
34
|
-
// Create base transports array with console transport
|
|
35
|
-
const transports = [new winston.transports.Console()];
|
|
36
|
-
|
|
37
|
-
// Add S3 transport if configured
|
|
38
|
-
if (s3 && process.env.NODE_ENV === 'production') {
|
|
39
|
-
transports.push(new S3Transport({
|
|
40
|
-
bucket: s3.bucket,
|
|
41
|
-
prefix: s3.prefix || 'logs',
|
|
42
|
-
bufferSize: s3.bufferSize || 100,
|
|
43
|
-
flushInterval: s3.flushInterval || 5000
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Create and return the logger
|
|
48
|
-
return winston.createLogger({
|
|
49
|
-
level,
|
|
50
|
-
format: logFormat,
|
|
51
|
-
defaultMeta: {
|
|
52
|
-
service
|
|
53
|
-
},
|
|
54
|
-
transports
|
|
55
|
-
});
|
|
56
|
-
};
|
|
57
|
-
module.exports = createLogger;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* S3 Transport for Winston logger
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const winston = require('winston');
|
|
8
|
-
const {
|
|
9
|
-
S3Client,
|
|
10
|
-
PutObjectCommand
|
|
11
|
-
} = require('@aws-sdk/client-s3');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Custom Winston transport for logging to S3
|
|
15
|
-
*/
|
|
16
|
-
class S3Transport extends winston.Transport {
|
|
17
|
-
constructor(opts) {
|
|
18
|
-
super(opts);
|
|
19
|
-
this.bucket = opts.bucket;
|
|
20
|
-
this.prefix = opts.prefix || '';
|
|
21
|
-
this.buffer = [];
|
|
22
|
-
this.bufferSize = opts.bufferSize || 100;
|
|
23
|
-
this.flushInterval = opts.flushInterval || 5000;
|
|
24
|
-
this.setupFlushInterval();
|
|
25
|
-
}
|
|
26
|
-
setupFlushInterval() {
|
|
27
|
-
setInterval(() => {
|
|
28
|
-
this.flush();
|
|
29
|
-
}, this.flushInterval);
|
|
30
|
-
}
|
|
31
|
-
async flush() {
|
|
32
|
-
if (this.buffer.length === 0) return;
|
|
33
|
-
const logs = this.buffer.splice(0, this.buffer.length);
|
|
34
|
-
const date = new Date().toISOString().split('T')[0];
|
|
35
|
-
const key = `${this.prefix}/${date}/${Date.now()}.json`;
|
|
36
|
-
try {
|
|
37
|
-
const command = new PutObjectCommand({
|
|
38
|
-
Bucket: this.bucket,
|
|
39
|
-
Key: key,
|
|
40
|
-
Body: JSON.stringify(logs),
|
|
41
|
-
ContentType: 'application/json'
|
|
42
|
-
});
|
|
43
|
-
await this.s3Client.send(command);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('Error flushing logs to S3:', error);
|
|
46
|
-
// Put logs back in buffer
|
|
47
|
-
this.buffer.unshift(...logs);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
log(info, callback) {
|
|
51
|
-
setImmediate(() => {
|
|
52
|
-
this.emit('logged', info);
|
|
53
|
-
});
|
|
54
|
-
this.buffer.push(info);
|
|
55
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
56
|
-
this.flush();
|
|
57
|
-
}
|
|
58
|
-
callback();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
module.exports = S3Transport;
|
package/dist/utils/s3.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const winston = require('winston');
|
|
4
|
-
const {
|
|
5
|
-
S3Client,
|
|
6
|
-
PutObjectCommand
|
|
7
|
-
} = require('@aws-sdk/client-s3');
|
|
8
|
-
|
|
9
|
-
// Create S3 client
|
|
10
|
-
const s3Client = new S3Client({
|
|
11
|
-
region: process.env.AWS_REGION,
|
|
12
|
-
credentials: {
|
|
13
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
14
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Custom Winston transport for logging to S3
|
|
20
|
-
*/
|
|
21
|
-
class S3Transport extends winston.Transport {
|
|
22
|
-
constructor(opts) {
|
|
23
|
-
super(opts);
|
|
24
|
-
this.bucket = opts.bucket;
|
|
25
|
-
this.prefix = opts.prefix || '';
|
|
26
|
-
this.buffer = [];
|
|
27
|
-
this.bufferSize = opts.bufferSize || 100;
|
|
28
|
-
this.flushInterval = opts.flushInterval || 5000;
|
|
29
|
-
this.setupFlushInterval();
|
|
30
|
-
}
|
|
31
|
-
setupFlushInterval() {
|
|
32
|
-
setInterval(() => {
|
|
33
|
-
this.flush();
|
|
34
|
-
}, this.flushInterval);
|
|
35
|
-
}
|
|
36
|
-
async flush() {
|
|
37
|
-
if (this.buffer.length === 0) return;
|
|
38
|
-
const logs = this.buffer.splice(0, this.buffer.length);
|
|
39
|
-
const date = new Date().toISOString().split('T')[0];
|
|
40
|
-
const key = `${this.prefix}/${date}/${Date.now()}.json`;
|
|
41
|
-
try {
|
|
42
|
-
const command = new PutObjectCommand({
|
|
43
|
-
Bucket: this.bucket,
|
|
44
|
-
Key: key,
|
|
45
|
-
Body: JSON.stringify(logs),
|
|
46
|
-
ContentType: 'application/json'
|
|
47
|
-
});
|
|
48
|
-
await s3Client.send(command);
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Failed to write logs to S3:', {
|
|
51
|
-
error: error.message,
|
|
52
|
-
code: error.code,
|
|
53
|
-
bucket: this.bucket,
|
|
54
|
-
key: key,
|
|
55
|
-
region: process.env.AWS_REGION,
|
|
56
|
-
stack: error.stack
|
|
57
|
-
});
|
|
58
|
-
// Put the logs back in the buffer
|
|
59
|
-
this.buffer.unshift(...logs);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
log(info, callback) {
|
|
63
|
-
setImmediate(() => {
|
|
64
|
-
this.emit('logged', info);
|
|
65
|
-
});
|
|
66
|
-
this.buffer.push({
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
...info
|
|
69
|
-
});
|
|
70
|
-
if (this.buffer.length >= this.bufferSize) {
|
|
71
|
-
this.flush();
|
|
72
|
-
}
|
|
73
|
-
callback();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
module.exports = {
|
|
77
|
-
S3Transport
|
|
78
|
-
};
|