powr-sdk-api 4.3.12 → 4.3.13

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.
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+
3
+ const {
4
+ getDb
5
+ } = require("./mongo");
6
+ const crypto = require('crypto');
7
+ const config = require('../config');
8
+ class FunctionsManager {
9
+ constructor() {
10
+ this.compiledFunctions = new Map();
11
+ this.isInitialized = false;
12
+ this.isCentralService = false;
13
+ this.isEnabled = false; // Default disabled
14
+ }
15
+ async initialize(options = {}) {
16
+ this.isCentralService = options.isCentralService || false;
17
+ this.isEnabled = options.enableFunctions === true; // Default false unless explicitly enabled
18
+
19
+ if (!this.isEnabled) {
20
+ console.log("🚫 Functions is disabled");
21
+ this.isInitialized = true;
22
+ return;
23
+ }
24
+ if (this.isCentralService) {
25
+ // Central service: Don't pre-load, load dynamically
26
+ console.log("🔄 Central service mode - loading functions dynamically");
27
+ this.isInitialized = true;
28
+ } else {
29
+ // Individual API: Load only this project's functions
30
+ const projectId = config.projectId;
31
+ console.log(`📦 Loading functions for project: ${projectId}`);
32
+ try {
33
+ const db = await getDb();
34
+ const routes = await db.collection("functions").find({
35
+ projectId
36
+ }).toArray();
37
+ routes.forEach(route => {
38
+ this.compileAndCache(route);
39
+ });
40
+ this.isInitialized = true;
41
+ console.log(`✅ Pre-compiled ${routes.length} functions for project ${projectId}`);
42
+ } catch (error) {
43
+ console.error("❌ Failed to initialize functions:", error);
44
+ this.isInitialized = true; // Still mark as initialized to prevent blocking
45
+ }
46
+ }
47
+ }
48
+ compileAndCache(route) {
49
+ const cacheKey = `${route.projectId}:${route.route}`;
50
+ try {
51
+ // Security validation
52
+ if (!this.validateCode(route.code)) {
53
+ console.error(`❌ Invalid code in function: ${route.route}`);
54
+ return;
55
+ }
56
+
57
+ // Pre-compile function
58
+ const compiledFunction = new Function("params", route.code);
59
+ this.compiledFunctions.set(cacheKey, {
60
+ function: compiledFunction,
61
+ metadata: {
62
+ route: route.route,
63
+ projectId: route.projectId,
64
+ compiledAt: new Date(),
65
+ codeHash: this.generateHash(route.code)
66
+ }
67
+ });
68
+ console.log(`✅ Compiled: ${route.route}`);
69
+ } catch (error) {
70
+ console.error(`❌ Failed to compile ${route.route}:`, error);
71
+ }
72
+ }
73
+ async execute(projectId, route, params) {
74
+ if (!this.isEnabled) {
75
+ return {
76
+ success: false,
77
+ message: "Functions is disabled",
78
+ data: null
79
+ };
80
+ }
81
+ const cacheKey = `${projectId}:${route}`;
82
+ const cached = this.compiledFunctions.get(cacheKey);
83
+ if (!cached) {
84
+ if (this.isCentralService) {
85
+ // Try to load only the specific function dynamically
86
+ await this.loadSpecificRoute(projectId, route);
87
+ const retryCached = this.compiledFunctions.get(cacheKey);
88
+ if (retryCached) {
89
+ return retryCached.function(params);
90
+ }
91
+ }
92
+ return {
93
+ success: false,
94
+ message: `Function not found: ${route}`,
95
+ data: null
96
+ };
97
+ }
98
+ try {
99
+ const result = cached.function(params);
100
+ return {
101
+ success: true,
102
+ message: "Function executed successfully",
103
+ data: result
104
+ };
105
+ } catch (error) {
106
+ return {
107
+ success: false,
108
+ message: `Error executing function: ${error.message}`,
109
+ data: null,
110
+ error: error.message
111
+ };
112
+ }
113
+ }
114
+
115
+ // Load specific function for central service
116
+ async loadSpecificRoute(projectId, route) {
117
+ if (!this.isCentralService) {
118
+ throw new Error("Dynamic loading only available in central service mode");
119
+ }
120
+ try {
121
+ const db = await getDb();
122
+ const routeData = await db.collection("functions").findOne({
123
+ projectId,
124
+ route
125
+ });
126
+ if (routeData) {
127
+ this.compileAndCache(routeData);
128
+ console.log(`✅ Loaded function: ${route} for project ${projectId}`);
129
+ } else {
130
+ console.log(`❌ Function not found: ${route} for project ${projectId}`);
131
+ }
132
+ } catch (error) {
133
+ console.error(`❌ Failed to load function ${route} for project ${projectId}:`, error);
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ // Dynamic loading for central service
139
+ async loadProjectRoutes(projectId) {
140
+ if (!this.isCentralService) {
141
+ throw new Error("Dynamic loading only available in central service mode");
142
+ }
143
+ try {
144
+ const db = await getDb();
145
+ const routes = await db.collection("functions").find({
146
+ projectId
147
+ }).toArray();
148
+ routes.forEach(route => {
149
+ this.compileAndCache(route);
150
+ });
151
+ console.log(`✅ Loaded ${routes.length} functions for project ${projectId}`);
152
+ } catch (error) {
153
+ console.error(`❌ Failed to load functions for project ${projectId}:`, error);
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ // Security validation
159
+ validateCode(code) {
160
+ const dangerousPatterns = [/process\./, /require\(/, /eval\(/, /setTimeout\(/, /setInterval\(/, /global\./, /__dirname/, /__filename/];
161
+ return !dangerousPatterns.some(pattern => pattern.test(code));
162
+ }
163
+
164
+ // Generate hash for change detection
165
+ generateHash(code) {
166
+ return crypto.createHash('md5').update(code).digest('hex');
167
+ }
168
+
169
+ // Update function (re-compile)
170
+ async updateRoute(projectId, route, newCode) {
171
+ const routeData = {
172
+ projectId,
173
+ route,
174
+ code: newCode
175
+ };
176
+ this.compileAndCache(routeData);
177
+
178
+ // Optionally save to database
179
+ try {
180
+ const db = await getDb();
181
+ await db.collection("functions").updateOne({
182
+ projectId,
183
+ route
184
+ }, {
185
+ $set: {
186
+ code: newCode,
187
+ updatedAt: new Date()
188
+ }
189
+ });
190
+ } catch (error) {
191
+ console.error(`❌ Failed to update function in database: ${route}`, error);
192
+ }
193
+ }
194
+
195
+ // Get function statistics
196
+ getStats() {
197
+ return {
198
+ totalFunctions: this.compiledFunctions.size,
199
+ isInitialized: this.isInitialized,
200
+ isCentralService: this.isCentralService,
201
+ isEnabled: this.isEnabled,
202
+ cacheKeys: Array.from(this.compiledFunctions.keys())
203
+ };
204
+ }
205
+
206
+ // Clear cache (for testing or maintenance)
207
+ clearCache() {
208
+ this.compiledFunctions.clear();
209
+ console.log("🧹 Function cache cleared");
210
+ }
211
+
212
+ // Enable/Disable Functions
213
+ enable() {
214
+ this.isEnabled = true;
215
+ console.log("✅ Functions enabled");
216
+ }
217
+ disable() {
218
+ this.isEnabled = false;
219
+ console.log("🚫 Functions disabled");
220
+ }
221
+
222
+ // Toggle Functions
223
+ toggle() {
224
+ this.isEnabled = !this.isEnabled;
225
+ console.log(this.isEnabled ? "✅ Functions enabled" : "🚫 Functions disabled");
226
+ return this.isEnabled;
227
+ }
228
+ }
229
+ module.exports = new FunctionsManager();
@@ -0,0 +1,35 @@
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;
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+
3
+ const {
4
+ getDb
5
+ } = require("./mongo");
6
+ const crypto = require('crypto');
7
+ const config = require('../config');
8
+ class PlexxManager {
9
+ constructor() {
10
+ this.compiledFunctions = new Map();
11
+ this.isInitialized = false;
12
+ this.isCentralService = false;
13
+ this.isEnabled = false; // Default disabled
14
+ }
15
+ async initialize(options = {}) {
16
+ this.isCentralService = options.isCentralService || false;
17
+ this.isEnabled = options.enablePlexx === true; // Default false unless explicitly enabled
18
+
19
+ if (!this.isEnabled) {
20
+ console.log("🚫 Plexx is disabled");
21
+ this.isInitialized = true;
22
+ return;
23
+ }
24
+ if (this.isCentralService) {
25
+ // Central service: Don't pre-load, load dynamically
26
+ console.log("🔄 Central service mode - loading routes dynamically");
27
+ this.isInitialized = true;
28
+ } else {
29
+ // Individual API: Load only this project's routes
30
+ const projectId = config.projectId;
31
+ console.log(`📦 Loading routes for project: ${projectId}`);
32
+ try {
33
+ const db = await getDb();
34
+ const routes = await db.collection("routes").find({
35
+ projectId
36
+ }).toArray();
37
+ routes.forEach(route => {
38
+ this.compileAndCache(route);
39
+ });
40
+ this.isInitialized = true;
41
+ console.log(`✅ Pre-compiled ${routes.length} routes for project ${projectId}`);
42
+ } catch (error) {
43
+ console.error("❌ Failed to initialize routes:", error);
44
+ this.isInitialized = true; // Still mark as initialized to prevent blocking
45
+ }
46
+ }
47
+ }
48
+ compileAndCache(route) {
49
+ const cacheKey = `${route.projectId}:${route.route}`;
50
+ try {
51
+ // Security validation
52
+ if (!this.validateCode(route.code)) {
53
+ console.error(`❌ Invalid code in route: ${route.route}`);
54
+ return;
55
+ }
56
+
57
+ // Pre-compile function
58
+ const compiledFunction = new Function("params", route.code);
59
+ this.compiledFunctions.set(cacheKey, {
60
+ function: compiledFunction,
61
+ metadata: {
62
+ route: route.route,
63
+ projectId: route.projectId,
64
+ compiledAt: new Date(),
65
+ codeHash: this.generateHash(route.code)
66
+ }
67
+ });
68
+ console.log(`✅ Compiled: ${route.route}`);
69
+ } catch (error) {
70
+ console.error(`❌ Failed to compile ${route.route}:`, error);
71
+ }
72
+ }
73
+ async execute(projectId, route, params) {
74
+ if (!this.isEnabled) {
75
+ return {
76
+ success: false,
77
+ message: "Plexx is disabled",
78
+ data: null
79
+ };
80
+ }
81
+ const cacheKey = `${projectId}:${route}`;
82
+ const cached = this.compiledFunctions.get(cacheKey);
83
+ if (!cached) {
84
+ if (this.isCentralService) {
85
+ // Try to load only the specific route dynamically
86
+ await this.loadSpecificRoute(projectId, route);
87
+ const retryCached = this.compiledFunctions.get(cacheKey);
88
+ if (retryCached) {
89
+ return retryCached.function(params);
90
+ }
91
+ }
92
+ return {
93
+ success: false,
94
+ message: `Route not found: ${route}`,
95
+ data: null
96
+ };
97
+ }
98
+ try {
99
+ const result = cached.function(params);
100
+ return {
101
+ success: true,
102
+ message: "Route executed successfully",
103
+ data: result
104
+ };
105
+ } catch (error) {
106
+ return {
107
+ success: false,
108
+ message: `Error executing route: ${error.message}`,
109
+ data: null,
110
+ error: error.message
111
+ };
112
+ }
113
+ }
114
+
115
+ // Load specific route for central service
116
+ async loadSpecificRoute(projectId, route) {
117
+ if (!this.isCentralService) {
118
+ throw new Error("Dynamic loading only available in central service mode");
119
+ }
120
+ try {
121
+ const db = await getDb();
122
+ const routeData = await db.collection("routes").findOne({
123
+ projectId,
124
+ route
125
+ });
126
+ if (routeData) {
127
+ this.compileAndCache(routeData);
128
+ console.log(`✅ Loaded route: ${route} for project ${projectId}`);
129
+ } else {
130
+ console.log(`❌ Route not found: ${route} for project ${projectId}`);
131
+ }
132
+ } catch (error) {
133
+ console.error(`❌ Failed to load route ${route} for project ${projectId}:`, error);
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ // Dynamic loading for central service
139
+ async loadProjectRoutes(projectId) {
140
+ if (!this.isCentralService) {
141
+ throw new Error("Dynamic loading only available in central service mode");
142
+ }
143
+ try {
144
+ const db = await getDb();
145
+ const routes = await db.collection("routes").find({
146
+ projectId
147
+ }).toArray();
148
+ routes.forEach(route => {
149
+ this.compileAndCache(route);
150
+ });
151
+ console.log(`✅ Loaded ${routes.length} routes for project ${projectId}`);
152
+ } catch (error) {
153
+ console.error(`❌ Failed to load routes for project ${projectId}:`, error);
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ // Security validation
159
+ validateCode(code) {
160
+ const dangerousPatterns = [/process\./, /require\(/, /eval\(/, /setTimeout\(/, /setInterval\(/, /global\./, /__dirname/, /__filename/];
161
+ return !dangerousPatterns.some(pattern => pattern.test(code));
162
+ }
163
+
164
+ // Generate hash for change detection
165
+ generateHash(code) {
166
+ return crypto.createHash('md5').update(code).digest('hex');
167
+ }
168
+
169
+ // Update route (re-compile)
170
+ async updateRoute(projectId, route, newCode) {
171
+ const routeData = {
172
+ projectId,
173
+ route,
174
+ code: newCode
175
+ };
176
+ this.compileAndCache(routeData);
177
+
178
+ // Optionally save to database
179
+ try {
180
+ const db = await getDb();
181
+ await db.collection("routes").updateOne({
182
+ projectId,
183
+ route
184
+ }, {
185
+ $set: {
186
+ code: newCode,
187
+ updatedAt: new Date()
188
+ }
189
+ });
190
+ } catch (error) {
191
+ console.error(`❌ Failed to update route in database: ${route}`, error);
192
+ }
193
+ }
194
+
195
+ // Get route statistics
196
+ getStats() {
197
+ return {
198
+ totalRoutes: this.compiledFunctions.size,
199
+ isInitialized: this.isInitialized,
200
+ isCentralService: this.isCentralService,
201
+ isEnabled: this.isEnabled,
202
+ cacheKeys: Array.from(this.compiledFunctions.keys())
203
+ };
204
+ }
205
+
206
+ // Clear cache (for testing or maintenance)
207
+ clearCache() {
208
+ this.compiledFunctions.clear();
209
+ console.log("🧹 Route cache cleared");
210
+ }
211
+
212
+ // Enable/Disable Plexx
213
+ enable() {
214
+ this.isEnabled = true;
215
+ console.log("✅ Plexx enabled");
216
+ }
217
+ disable() {
218
+ this.isEnabled = false;
219
+ console.log("🚫 Plexx disabled");
220
+ }
221
+
222
+ // Toggle Plexx
223
+ toggle() {
224
+ this.isEnabled = !this.isEnabled;
225
+ console.log(this.isEnabled ? "✅ Plexx enabled" : "🚫 Plexx disabled");
226
+ return this.isEnabled;
227
+ }
228
+ }
229
+ module.exports = new PlexxManager();
@@ -0,0 +1,19 @@
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
+ };
@@ -0,0 +1,57 @@
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;
@@ -0,0 +1,61 @@
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;
@@ -0,0 +1,78 @@
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
+ };