@joystick.js/node-canary 0.0.0-canary.52 → 0.0.0-canary.54

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.
Files changed (2) hide show
  1. package/dist/app/index.js +673 -0
  2. package/package.json +1 -1
@@ -0,0 +1,673 @@
1
+ import fs from "fs";
2
+ import aws from "aws-sdk";
3
+ import EventEmitter from "events";
4
+ import * as WebSocket from "ws";
5
+ import queryString from "query-string";
6
+ import multer from "multer";
7
+ import cron from "node-cron";
8
+ import { execSync } from "child_process";
9
+ import initExpress from "./initExpress.js";
10
+ import handleProcessErrors from "./handleProcessErrors";
11
+ import registerGetters from "./registerGetters.js";
12
+ import registerSetters from "./registerSetters.js";
13
+ import connectMongoDB from "./databases/mongodb/index.js";
14
+ import connectPostgreSQL from "./databases/postgresql/index.js";
15
+ import accounts from "./accounts";
16
+ import formatAPIError from "../lib/formatAPIError";
17
+ import hasLoginTokenExpired from "./accounts/hasLoginTokenExpired.js";
18
+ import { isObject } from "../validation/lib/typeValidators.js";
19
+ import supportedHTTPMethods from "../lib/supportedHTTPMethods.js";
20
+ import getAPIURLComponent from "./getAPIURLComponent";
21
+ import validateUploaderOptions from "./validateUploaderOptions.js";
22
+ import log from "../lib/log.js";
23
+ import validateUploads from "./validateUploads";
24
+ import runUploader from "./runUploader";
25
+ import generateId from "../lib/generateId.js";
26
+ import getOutput from "./getOutput.js";
27
+ import defaultUserOutputFields from "./accounts/defaultUserOutputFields.js";
28
+ import createPostgreSQLAccountsTables from "./databases/postgresql/createAccountsTables";
29
+ import createPostgreSQLAccountsIndexes from "./databases/postgresql/createAccountsIndexes";
30
+ import loadSettings from "../settings/load.js";
31
+ import Queue from "./queues/index.js";
32
+ import readDirectory from "../lib/readDirectory.js";
33
+ import getBuildPath from "../lib/getBuildPath.js";
34
+ import generateMachineId from "../lib/generateMachineId.js";
35
+ import importFile from "../lib/importFile.js";
36
+ import emitWebsocketEvent from "../websockets/emitWebsocketEvent.js";
37
+ import getTargetDatabaseConnection from "./databases/getTargetDatabaseConnection.js";
38
+ process.setMaxListeners(0);
39
+ class App {
40
+ constructor(options = {}) {
41
+ this.setMachineId();
42
+ this.setJoystickProcessId();
43
+ handleProcessErrors(options?.events);
44
+ const HMRSessions = JSON.parse(process.env.HMR_SESSIONS || "{}");
45
+ this.sessions = new Map(HMRSessions ? Object.entries(HMRSessions) : []);
46
+ this.databases = [];
47
+ this.express = {};
48
+ this.options = options || {};
49
+ }
50
+ async start(options = {}) {
51
+ await this.invalidateCache();
52
+ this.databases = await this.loadDatabases();
53
+ this.express = initExpress(this.onStartApp, options, this);
54
+ this.initWebsockets(options?.websockets || {});
55
+ this.initDevelopmentRoutes();
56
+ this.initAccounts();
57
+ this.initTests();
58
+ this.initDeploy();
59
+ this.initAPI(options?.api);
60
+ this.initRoutes(options?.routes);
61
+ this.initUploaders(options?.uploaders);
62
+ this.initFixtures(options?.fixtures);
63
+ this.initQueues(options?.queues);
64
+ this.initCronJobs(options?.cronJobs);
65
+ if (process.env.NODE_ENV === "development") {
66
+ process.on("message", (message) => {
67
+ const parsedMessage = typeof message === "string" ? JSON.parse(message) : message;
68
+ if (parsedMessage?.type === " RESTART_SERVER") {
69
+ this.express?.server?.close();
70
+ }
71
+ });
72
+ }
73
+ }
74
+ async invalidateCache() {
75
+ const uiFiles = fs.existsSync(`${getBuildPath()}ui`) ? await readDirectory(`${getBuildPath()}ui`) : [];
76
+ const cacheFiles = uiFiles?.filter((filePath) => filePath?.includes("_cache"));
77
+ for (let i = 0; i < cacheFiles?.length; i += 1) {
78
+ await fs.promises.unlink(cacheFiles[i]);
79
+ }
80
+ return Promise.resolve();
81
+ }
82
+ async loadDatabases(callback) {
83
+ const settings = loadSettings();
84
+ const hasUsersDatabase = settings?.config?.databases?.some((database = {}) => {
85
+ return !!database?.users;
86
+ });
87
+ const hasQueuesDatabase = settings?.config?.databases?.some((database = {}) => {
88
+ return !!database?.queues;
89
+ });
90
+ const hasPostgreSQLUsersDatabase = settings?.config?.databases?.some((database = {}) => {
91
+ return database?.provider === "postgresql" && database?.users;
92
+ });
93
+ const databases = settings?.config?.databases?.map((database) => {
94
+ return {
95
+ provider: database?.provider,
96
+ settings: database
97
+ };
98
+ });
99
+ for (let databaseIndex = 0; databaseIndex < databases?.length; databaseIndex += 1) {
100
+ const database = databases[databaseIndex];
101
+ const hasMultipleOfProvider = databases?.filter((database2) => database2?.provider === database2?.provider)?.length > 1;
102
+ const databasePort = parseInt(process.env.PORT, 10) + 10 + databaseIndex;
103
+ if (database?.provider === "mongodb") {
104
+ const mongodb = await connectMongoDB(database?.settings, databasePort);
105
+ process.databases = {
106
+ ...process.databases || {},
107
+ mongodb: !hasMultipleOfProvider ? mongodb : {
108
+ ...process?.databases?.mongodb || {},
109
+ [database?.settings?.name || `mongodb_${databasePort}`]: mongodb
110
+ }
111
+ };
112
+ }
113
+ if (database?.provider === "postgresql") {
114
+ const postgresql = await connectPostgreSQL(database?.settings, databasePort);
115
+ process.databases = {
116
+ ...process.databases || {},
117
+ postgresql: !hasMultipleOfProvider ? {
118
+ ...postgresql?.pool,
119
+ query: postgresql?.query
120
+ } : {
121
+ ...process?.databases?.postgresql || {},
122
+ [database?.settings?.name || `postgresql_${databasePort}`]: {
123
+ ...postgresql?.pool,
124
+ query: postgresql?.query
125
+ }
126
+ }
127
+ };
128
+ }
129
+ }
130
+ if (hasUsersDatabase) {
131
+ process.databases._users = getTargetDatabaseConnection("users")?.connection;
132
+ }
133
+ if (hasQueuesDatabase) {
134
+ process.databases._queues = getTargetDatabaseConnection("queues")?.connection;
135
+ }
136
+ if (hasPostgreSQLUsersDatabase) {
137
+ await createPostgreSQLAccountsTables();
138
+ await createPostgreSQLAccountsIndexes();
139
+ }
140
+ return process.databases;
141
+ }
142
+ onStartApp(express = {}) {
143
+ process.on("message", (message) => {
144
+ if (typeof message === "string") {
145
+ process.BUILD_ERROR = JSON.parse(message);
146
+ }
147
+ });
148
+ if (process.env.NODE_ENV !== "test") {
149
+ console.log(`App running at: http://localhost:${express.port}`);
150
+ }
151
+ }
152
+ setMachineId() {
153
+ generateMachineId();
154
+ }
155
+ setJoystickProcessId() {
156
+ if (!fs.existsSync("./.joystick")) {
157
+ fs.mkdirSync("./.joystick");
158
+ }
159
+ if (!fs.existsSync("./.joystick/PROCESS_ID")) {
160
+ fs.writeFileSync("./.joystick/PROCESS_ID", `${generateId(32)}`);
161
+ }
162
+ }
163
+ initTests() {
164
+ if (process.env.NODE_ENV === "test") {
165
+ this.express.app.get("/api/_test/bootstrap", async (req, res) => {
166
+ const Component = req?.query?.pathToComponent ? await importFile(`./.joystick/build/${req?.query?.pathToComponent}`) : null;
167
+ if (Component) {
168
+ const componentInstance = Component();
169
+ console.log(componentInstance);
170
+ }
171
+ res.status(200).send({
172
+ ping: "pong",
173
+ query: req?.query
174
+ });
175
+ });
176
+ }
177
+ }
178
+ initDeploy() {
179
+ if (process.env.NODE_ENV === "production" && process.env.IS_PUSH_DEPLOYED) {
180
+ this.express.app.get("/api/_push/pre-version", async (req, res) => {
181
+ const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
182
+ if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
183
+ if (this.options?.events?.onBeforeDeployment && typeof this.options?.events?.onBeforeDeployment === "function") {
184
+ await this.options.events.onBeforeDeployment(req?.query?.instance || "", req?.query?.version);
185
+ return res.status(200).send("ok");
186
+ }
187
+ return res.status(200).send("ok");
188
+ }
189
+ return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
190
+ });
191
+ this.express.app.get("/api/_push/health", async (req, res) => {
192
+ const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
193
+ if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
194
+ return res.status(200).send("ok");
195
+ }
196
+ return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
197
+ });
198
+ this.express.app.get("/api/_push/logs", async (req, res) => {
199
+ const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
200
+ if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
201
+ const logs = execSync(`export NODE_ENV=production && instance logs${req?.query?.before ? ` --before ${req?.query?.before}` : ""}${req?.query?.after ? ` --after ${req?.query?.after}` : ""}`);
202
+ return res.status(200).send(logs);
203
+ }
204
+ return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
205
+ });
206
+ this.express.app.get("/api/_push/metrics", async (req, res) => {
207
+ const instanceToken = fs.readFileSync("/root/token.txt", "utf-8");
208
+ if (req?.headers["x-instance-token"] === instanceToken?.replace("\n", "")) {
209
+ const metrics = execSync(`export NODE_ENV=production && instance metrics`);
210
+ return res.status(200).send(metrics);
211
+ }
212
+ return res.status(403).send("Sorry, you must pass a valid instance token to access this endpoint.");
213
+ });
214
+ }
215
+ }
216
+ initAPI(api = {}) {
217
+ const getters = api?.getters;
218
+ const setters = api?.setters;
219
+ const options = api?.options;
220
+ const context = api?.context;
221
+ if (getters && isObject(getters) && Object.keys(getters).length > 0) {
222
+ registerGetters(this.express, Object.entries(getters), context, options, this);
223
+ }
224
+ if (setters && isObject(setters) && Object.keys(setters).length > 0) {
225
+ registerSetters(this.express, Object.entries(setters), context, options, this);
226
+ }
227
+ }
228
+ initRoutes(routes = {}) {
229
+ Object.entries(routes).forEach(([path, callback]) => {
230
+ const isObjectBasedRoute = path && callback && typeof callback === "object";
231
+ const isFunctionBasedRoute = path && callback && typeof callback === "function";
232
+ const method = callback?.method?.toLowerCase();
233
+ const methods = callback?.methods?.map((method2) => method2?.toLowerCase());
234
+ const methodsForRoute = method ? [method] : methods;
235
+ const invalidMethods = isObjectBasedRoute ? methodsForRoute.filter((method2) => !supportedHTTPMethods.includes(method2)) : [];
236
+ const isValidMethod = Array.isArray(methodsForRoute) && invalidMethods.length === 0;
237
+ const isValidHandler = isFunctionBasedRoute && typeof callback === "function" || isObjectBasedRoute && callback && callback.handler && typeof callback.handler === "function";
238
+ if (isFunctionBasedRoute && !isValidHandler) {
239
+ log(`Cannot register route ${path}. When defining a route using the function-based pattern, route must be set to a function.`, {
240
+ level: "danger",
241
+ docs: "https://github.com/cheatcode/joystick#defining-routes"
242
+ });
243
+ }
244
+ if (isObjectBasedRoute && !isValidHandler) {
245
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, the handler property must be set to a function.`, {
246
+ level: "danger",
247
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
248
+ });
249
+ }
250
+ if (isObjectBasedRoute && invalidMethods.length > 0) {
251
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, you can only set method (single HTTP method as a string) or methods (array of HTTP methods as strings), not both.`, {
252
+ level: "danger",
253
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
254
+ });
255
+ }
256
+ if (isObjectBasedRoute && method && methods) {
257
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, you can only set method (single HTTP method as a string) or methods (array of HTTP methods as strings), not both.`, {
258
+ level: "danger",
259
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
260
+ });
261
+ }
262
+ if (isObjectBasedRoute && !method && !methods) {
263
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, you must pass a method (single HTTP method as a string) or methods (array of HTTP methods as strings) for the route.`, {
264
+ level: "danger",
265
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
266
+ });
267
+ }
268
+ if (isObjectBasedRoute && method && !isValidMethod) {
269
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, the method property must be set to a valid HTTP method: ${supportedHTTPMethods.join(", ")}.`, {
270
+ level: "danger",
271
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
272
+ });
273
+ }
274
+ if (isObjectBasedRoute && methods && !isValidMethod) {
275
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, the methods property must be set to an array of valid HTTP methods: ${supportedHTTPMethods.join(", ")}.`, {
276
+ level: "danger",
277
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
278
+ });
279
+ }
280
+ if (isObjectBasedRoute && callback && !callback.handler) {
281
+ log(`Cannot register route ${path}. When defining a route using the object-based pattern, the handler property must be set to a function.`, {
282
+ level: "danger",
283
+ docs: "https://github.com/cheatcode/joystick#defining-routes-for-specific-http-methods"
284
+ });
285
+ }
286
+ if (isObjectBasedRoute && methodsForRoute && isValidMethod && callback && callback.handler) {
287
+ methodsForRoute.forEach((method2) => {
288
+ this.express.app[method2](path, ...[
289
+ ...Array.isArray(callback?.middleware) ? callback?.middleware : [],
290
+ async (req, res, next) => {
291
+ callback.handler(Object.assign(req, {
292
+ context: {
293
+ ...req?.context || {},
294
+ ifLoggedIn: (redirectPath = "", callback2 = null) => {
295
+ if (!!req?.context?.user && redirectPath) {
296
+ return res.redirect(redirectPath);
297
+ }
298
+ if (callback2) {
299
+ return callback2();
300
+ }
301
+ },
302
+ ifNotLoggedIn: (redirectPath = "", callback2 = null) => {
303
+ if (!req?.context?.user && redirectPath) {
304
+ return res.redirect(redirectPath);
305
+ }
306
+ if (callback2) {
307
+ return callback2();
308
+ }
309
+ },
310
+ ...process.databases || {}
311
+ }
312
+ }), res, next);
313
+ }
314
+ ]);
315
+ });
316
+ }
317
+ if (isFunctionBasedRoute) {
318
+ this.express.app.get(path, (req, res, next) => {
319
+ callback(Object.assign(req, {
320
+ context: {
321
+ ...req?.context || {},
322
+ ifLoggedIn: (redirectPath = "", callback2 = null) => {
323
+ if (!!req?.context?.user && redirectPath) {
324
+ return res.redirect(redirectPath);
325
+ }
326
+ if (callback2) {
327
+ return callback2();
328
+ }
329
+ },
330
+ ifNotLoggedIn: (redirectPath = "", callback2 = null) => {
331
+ if (!req?.context?.user && redirectPath) {
332
+ return res.redirect(redirectPath);
333
+ }
334
+ if (callback2) {
335
+ return callback2();
336
+ }
337
+ },
338
+ ...process.databases || {}
339
+ }
340
+ }), res, next);
341
+ });
342
+ }
343
+ });
344
+ }
345
+ initWebsockets(userWebsockets = {}) {
346
+ const websocketServers = {
347
+ uploaders: {
348
+ server: new WebSocket.WebSocketServer({
349
+ noServer: true,
350
+ path: "/api/_websockets/uploaders"
351
+ })
352
+ },
353
+ ...Object.entries(userWebsockets).reduce((definitions = {}, [userWebsocketName, userWebsocketDefinition]) => {
354
+ definitions[userWebsocketName] = {
355
+ server: new WebSocket.WebSocketServer({
356
+ noServer: true,
357
+ path: `/api/_websockets/${userWebsocketName}`
358
+ }),
359
+ onOpen: userWebsocketDefinition?.onOpen || null,
360
+ onMessage: userWebsocketDefinition?.onMessage || null,
361
+ onClose: userWebsocketDefinition?.onClose || null
362
+ };
363
+ return definitions;
364
+ }, {})
365
+ };
366
+ Object.entries(websocketServers).forEach(([websocketName, websocketDefinition]) => {
367
+ websocketDefinition.server.on("connection", function connection(websocketConnection, connectionRequest) {
368
+ try {
369
+ const [_path, params] = connectionRequest?.url?.split("?");
370
+ const connectionParams = queryString.parse(params);
371
+ const emitter = new EventEmitter();
372
+ const emitterId = connectionParams?.id ? `${websocketName}_${connectionParams?.id}` : websocketName;
373
+ if (joystick?.emitters[emitterId]) {
374
+ joystick.emitters[emitterId].push(emitter);
375
+ } else {
376
+ joystick.emitters = {
377
+ ...joystick?.emitters || {},
378
+ [emitterId]: [emitter]
379
+ };
380
+ }
381
+ const connection2 = Object.assign(websocketConnection, {
382
+ params: connectionParams
383
+ });
384
+ if (websocketDefinition?.onOpen) {
385
+ websocketDefinition.onOpen(connection2);
386
+ }
387
+ websocketConnection.on("message", (message) => {
388
+ const parsedMessage = JSON.parse(message);
389
+ if (websocketDefinition.onMessage) {
390
+ websocketDefinition.onMessage(parsedMessage, connection2);
391
+ }
392
+ });
393
+ websocketConnection.on("close", (code = 0, reason = "") => {
394
+ if (websocketDefinition?.onClose) {
395
+ websocketDefinition.onClose(code, reason?.toString(), connection2);
396
+ }
397
+ });
398
+ emitter.on("message", (message = {}) => {
399
+ websocketConnection.send(JSON.stringify(message));
400
+ });
401
+ emitter.on("progress", (progress = {}) => {
402
+ websocketConnection.send(JSON.stringify({ type: "PROGRESS", ...progress }));
403
+ });
404
+ } catch (exception) {
405
+ console.warn(exception);
406
+ }
407
+ });
408
+ });
409
+ this.express.server.on("upgrade", (request, socket, head) => {
410
+ if (!request?.url?.includes("/api/_websockets/")) {
411
+ return;
412
+ }
413
+ const websocketName = (request?.url?.replace("/api/_websockets/", "").split("?") || [])[0];
414
+ const websocket = websocketServers[websocketName];
415
+ if (websocket) {
416
+ websocket.server.handleUpgrade(request, socket, head, (socket2) => {
417
+ websocket.server.emit("connection", socket2, request);
418
+ });
419
+ }
420
+ });
421
+ }
422
+ initDevelopmentRoutes() {
423
+ if (["development", "test"].includes(process.env.NODE_ENV)) {
424
+ this.express.app.get("/api/_joystick/sessions", async (req, res) => {
425
+ const sessions = Array.from(this.sessions.entries())?.reduce((acc = {}, [key, value]) => {
426
+ acc[key] = value;
427
+ return acc;
428
+ }, {});
429
+ res.status(200).send(JSON.stringify(sessions));
430
+ });
431
+ }
432
+ }
433
+ initAccounts() {
434
+ this.express.app.get("/api/_accounts/authenticated", async (req, res) => {
435
+ const loginTokenHasExpired = await hasLoginTokenExpired(res, req?.cookies?.joystickLoginToken, req?.cookies?.joystickLoginTokenExpiresAt);
436
+ const status = !loginTokenHasExpired ? 200 : 401;
437
+ return res.status(status).send(JSON.stringify({ status, authenticated: !loginTokenHasExpired }));
438
+ });
439
+ this.express.app.get("/api/_accounts/user", async (req, res) => {
440
+ const loginTokenHasExpired = await hasLoginTokenExpired(res, req?.cookies?.joystickLoginToken, req?.cookies?.joystickLoginTokenExpiresAt);
441
+ const status = !loginTokenHasExpired ? 200 : 401;
442
+ const user = getOutput(req?.context?.user, req?.body?.output || defaultUserOutputFields);
443
+ return res.status(status).send(JSON.stringify({ status, user }));
444
+ });
445
+ this.express.app.post("/api/_accounts/signup", async (req, res) => {
446
+ try {
447
+ const signup = await accounts.signup({
448
+ emailAddress: req?.body?.emailAddress,
449
+ password: req?.body?.password,
450
+ metadata: req?.body?.metadata,
451
+ output: req?.body?.output || defaultUserOutputFields
452
+ });
453
+ accounts._setAuthenticationCookie(res, {
454
+ token: signup?.token,
455
+ tokenExpiresAt: signup?.tokenExpiresAt
456
+ });
457
+ res.status(200).send(JSON.stringify(signup?.user || {}));
458
+ } catch (exception) {
459
+ console.log(exception);
460
+ return res.status(500).send(JSON.stringify({
461
+ errors: [formatAPIError(exception, "server")]
462
+ }));
463
+ }
464
+ });
465
+ this.express.app.post("/api/_accounts/login", async (req, res) => {
466
+ try {
467
+ const login = await accounts.login({
468
+ emailAddress: req?.body?.emailAddress,
469
+ username: req?.body?.username,
470
+ password: req?.body?.password,
471
+ output: req?.body?.output || defaultUserOutputFields
472
+ });
473
+ accounts._setAuthenticationCookie(res, {
474
+ token: login?.token,
475
+ tokenExpiresAt: login?.tokenExpiresAt
476
+ });
477
+ res.status(200).send(JSON.stringify(login?.user || {}));
478
+ } catch (exception) {
479
+ console.log(exception);
480
+ return res.status(500).send(JSON.stringify({
481
+ errors: [formatAPIError(exception, "server")]
482
+ }));
483
+ }
484
+ });
485
+ this.express.app.post("/api/_accounts/logout", async (req, res) => {
486
+ try {
487
+ this.sessions.delete(req?.context?.user?._id || req?.context?.user?.user_id);
488
+ accounts._unsetAuthenticationCookie(res);
489
+ res.status(200).send(JSON.stringify({}));
490
+ } catch (exception) {
491
+ console.log(exception);
492
+ return res.status(500).send(JSON.stringify({
493
+ errors: [formatAPIError(exception, "server")]
494
+ }));
495
+ }
496
+ });
497
+ this.express.app.post("/api/_accounts/recover-password", async (req, res) => {
498
+ try {
499
+ await accounts.recoverPassword({
500
+ emailAddress: req?.body?.emailAddress
501
+ });
502
+ res.status(200).send(JSON.stringify({}));
503
+ } catch (exception) {
504
+ console.log(exception);
505
+ return res.status(500).send(JSON.stringify({
506
+ errors: [formatAPIError(exception, "server")]
507
+ }));
508
+ }
509
+ });
510
+ this.express.app.post("/api/_accounts/reset-password", async (req, res) => {
511
+ try {
512
+ const reset = await accounts.resetPassword({
513
+ token: req?.body?.token,
514
+ password: req?.body?.password,
515
+ output: req?.body?.output || defaultUserOutputFields
516
+ });
517
+ accounts._setAuthenticationCookie(res, {
518
+ token: reset?.token,
519
+ tokenExpiresAt: reset?.tokenExpiresAt
520
+ });
521
+ res.status(200).send(JSON.stringify(reset?.user || {}));
522
+ } catch (exception) {
523
+ console.log(exception);
524
+ return res.status(500).send(JSON.stringify({
525
+ errors: [formatAPIError(exception, "server")]
526
+ }));
527
+ }
528
+ });
529
+ this.express.app.post("/api/_accounts/verify-email", async (req, res) => {
530
+ try {
531
+ await accounts.verifyEmail({
532
+ token: req?.query?.token
533
+ });
534
+ res.redirect("/");
535
+ } catch (exception) {
536
+ console.log(exception);
537
+ return res.status(500).send(JSON.stringify({
538
+ errors: [formatAPIError(exception, "server")]
539
+ }));
540
+ }
541
+ });
542
+ }
543
+ initUploaders(uploaders = {}) {
544
+ const { app } = this.express;
545
+ Object.entries(uploaders).forEach(([uploaderName, uploaderOptions]) => {
546
+ const errors = validateUploaderOptions(uploaderOptions);
547
+ if (errors?.length > 0) {
548
+ log(errors, {
549
+ level: "danger",
550
+ docs: "https://github.com/cheatcode/joystick#uploaders"
551
+ });
552
+ return;
553
+ }
554
+ if (errors?.length === 0) {
555
+ const formattedUploaderName = getAPIURLComponent(uploaderName);
556
+ const upload = multer();
557
+ const multerMiddleware = upload.array("files", 12);
558
+ app.post(`/api/_uploaders/${formattedUploaderName}`, (req, res, next) => {
559
+ let progress = 0;
560
+ const fileSize = parseInt(req.headers["content-length"], 10);
561
+ const providers = uploaderOptions?.providers?.includes("local") ? uploaderOptions?.providers.length : uploaderOptions?.providers?.length + 1;
562
+ const totalFileSizeAllProviders = fileSize * providers;
563
+ req.on("data", (chunk) => {
564
+ progress += chunk.length;
565
+ const percentage = Math.round(progress / totalFileSizeAllProviders * 100);
566
+ emitWebsocketEvent(`uploaders_${req?.headers["x-joystick-upload-id"]}`, "progress", { provider: "uploadToServer", progress: percentage });
567
+ });
568
+ next();
569
+ }, multerMiddleware, async (req, res) => {
570
+ const input = req?.headers["x-joystick-upload-input"] ? JSON.parse(req?.headers["x-joystick-upload-input"]) : {};
571
+ validateUploads({
572
+ files: req?.files,
573
+ input,
574
+ uploaderName,
575
+ uploaderOptions
576
+ }).then(async (validatedUploads = []) => {
577
+ if (typeof uploaderOptions?.onBeforeUpload === "function") {
578
+ await uploaderOptions?.onBeforeUpload({
579
+ input,
580
+ req,
581
+ uploads: validatedUploads
582
+ });
583
+ }
584
+ const fileSize = parseInt(req.headers["content-length"], 10);
585
+ const providers = uploaderOptions?.providers?.includes("local") ? uploaderOptions?.providers.length : uploaderOptions?.providers?.length + 1;
586
+ const totalFileSizeAllProviders = fileSize * providers;
587
+ const uploads = await runUploader({
588
+ alreadyUploaded: fileSize,
589
+ totalFileSizeAllProviders,
590
+ uploads: validatedUploads,
591
+ input,
592
+ req
593
+ });
594
+ if (typeof uploaderOptions?.onAfterUpload === "function") {
595
+ await uploaderOptions?.onAfterUpload({
596
+ input,
597
+ req,
598
+ uploads
599
+ });
600
+ }
601
+ res.status(200).send(JSON.stringify({
602
+ status: 200,
603
+ uploads
604
+ }));
605
+ }).catch((errors2) => {
606
+ if (typeof errors2 === "string") {
607
+ return res.status(403).send(JSON.stringify({
608
+ errors: [formatAPIError(new Error(errors2))]
609
+ }));
610
+ }
611
+ if (errors2 instanceof Error) {
612
+ return res.status(403).send(JSON.stringify({
613
+ errors: [formatAPIError(errors2)]
614
+ }));
615
+ }
616
+ return res.status(403).send(JSON.stringify({
617
+ errors: (errors2 || []).map((error) => {
618
+ return formatAPIError(new Error(error?.message, "validation"));
619
+ })
620
+ }));
621
+ });
622
+ });
623
+ }
624
+ });
625
+ }
626
+ initFixtures(fixtures = null) {
627
+ if (fixtures && typeof fixtures === "function") {
628
+ fixtures();
629
+ }
630
+ }
631
+ initQueues(queues = null) {
632
+ if (queues && typeof queues === "object" && !Array.isArray(queues)) {
633
+ const queueDefinitions = Object.entries(queues);
634
+ for (let i = 0; i < queueDefinitions.length; i += 1) {
635
+ const [queueName, queueOptions] = queueDefinitions[i];
636
+ process.queues = {
637
+ ...process.queues || {},
638
+ [queueName]: new Queue(queueName, queueOptions)
639
+ };
640
+ }
641
+ }
642
+ }
643
+ initCronJobs(cronJobs = null) {
644
+ if (cronJobs && typeof cronJobs === "object" && !Array.isArray(cronJobs)) {
645
+ const cronJobDefinitions = Object.entries(cronJobs);
646
+ for (let i = 0; i < cronJobDefinitions.length; i += 1) {
647
+ const [cronJobName, cronJobOptions] = cronJobDefinitions[i];
648
+ if (cronJobOptions?.schedule && cronJobOptions?.run && typeof cronJobOptions?.run === "function") {
649
+ process.cron = {
650
+ ...process.queues || {},
651
+ [cronJobName]: cron.schedule(cronJobOptions?.schedule, () => {
652
+ if (cronJobOptions?.logAtRunTime && typeof cronJobOptions?.logAtRunTime === "string") {
653
+ console.log(cronJobOptions.logAtRunTime);
654
+ }
655
+ cronJobOptions.run();
656
+ })
657
+ };
658
+ }
659
+ }
660
+ }
661
+ }
662
+ }
663
+ var app_default = (options = {}) => {
664
+ return new Promise(async (resolve) => {
665
+ const app = new App(options);
666
+ await app.start(options);
667
+ return resolve(app.express);
668
+ });
669
+ };
670
+ export {
671
+ App,
672
+ app_default as default
673
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joystick.js/node-canary",
3
- "version": "0.0.0-canary.52",
3
+ "version": "0.0.0-canary.54",
4
4
  "type": "module",
5
5
  "description": "A Node.js framework for building web apps.",
6
6
  "main": "./dist/index.js",