From 42506304ba458ab2df4e4576172d97fbe2853a22 Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Tue, 26 Mar 2024 18:21:47 +0100 Subject: [PATCH] feat(mellow-react): move syntax to latest inertia-sails --- .../api/controllers/example/index.js | 15 ++- .../api/controllers/home/index.js | 15 ++- .../mellow-react/api/responses/badRequest.js | 95 +++++++++++++++++++ .../mellow-react/api/responses/expired.js | 27 ++++++ .../mellow-react/api/responses/inertia.js | 72 ++++++++++++++ .../api/responses/inertiaRedirect.js | 20 ++++ templates/mellow-react/package-lock.json | 28 +++++- templates/mellow-react/package.json | 3 +- 8 files changed, 261 insertions(+), 14 deletions(-) create mode 100644 templates/mellow-react/api/responses/badRequest.js create mode 100644 templates/mellow-react/api/responses/expired.js create mode 100644 templates/mellow-react/api/responses/inertia.js create mode 100644 templates/mellow-react/api/responses/inertiaRedirect.js diff --git a/templates/mellow-react/api/controllers/example/index.js b/templates/mellow-react/api/controllers/example/index.js index 8fa66a22..118af332 100644 --- a/templates/mellow-react/api/controllers/example/index.js +++ b/templates/mellow-react/api/controllers/example/index.js @@ -5,11 +5,18 @@ module.exports = { inputs: {}, - exits: {}, + exits: { + success: { + responseType: 'inertia' + } + }, fn: async function () { - return sails.inertia.render('example', { - quote: "You can shine no matter what you're made of - Bigweld" - }) + return { + page: 'example', + props: { + quote: "You can shine no matter what you're made of - Bigweld" + } + } } } diff --git a/templates/mellow-react/api/controllers/home/index.js b/templates/mellow-react/api/controllers/home/index.js index be3c2b7b..5aee250e 100644 --- a/templates/mellow-react/api/controllers/home/index.js +++ b/templates/mellow-react/api/controllers/home/index.js @@ -5,11 +5,18 @@ module.exports = { inputs: {}, - exits: {}, + exits: { + success: { + responseType: 'inertia' + } + }, fn: async function () { - return sails.inertia.render('index', { - name: 'Inertia' - }) + return { + page: 'index', + props: { + name: 'Inertia' + } + } } } diff --git a/templates/mellow-react/api/responses/badRequest.js b/templates/mellow-react/api/responses/badRequest.js new file mode 100644 index 00000000..142fcd6b --- /dev/null +++ b/templates/mellow-react/api/responses/badRequest.js @@ -0,0 +1,95 @@ +/** + * badRequest.js + * + * A custom response. + * + * Example usage: + * ``` + * return res.badRequest(); + * // -or- + * return res.badRequest(optionalData); + * ``` + * + * Or with actions2: + * ``` + * exits: { + * somethingHappened: { + * responseType: 'badRequest' + * } + * } + * ``` + * + * ``` + * throw 'somethingHappened'; + * // -or- + * throw { somethingHappened: optionalData } + * ``` + */ + +module.exports = function badRequest(optionalData) { + // Get access to `req` and `res` + const req = this.req + const res = this.res + + // Define the status code to send in the response. + const statusCodeToSet = 400 + + // Check if it's an Inertia request + if (req.header('X-Inertia')) { + if (optionalData && optionalData.problems) { + const errors = {} + optionalData.problems.forEach((problem) => { + if (typeof problem === 'object') { + Object.keys(problem).forEach((propertyName) => { + const sanitizedProblem = problem[propertyName].replace(/\.$/, '') // Trim trailing dot + if (!errors[propertyName]) { + errors[propertyName] = [sanitizedProblem] + } else { + errors[propertyName].push(sanitizedProblem) + } + }) + } else { + const regex = /"(.*?)"/ + const matches = problem.match(regex) + + if (matches && matches.length > 1) { + const propertyName = matches[1] + const sanitizedProblem = problem + .replace(/"([^"]+)"/, '$1') + .replace('\n', '') + .replace('·', '') + .trim() + if (!errors[propertyName]) { + errors[propertyName] = [sanitizedProblem] + } else { + errors[propertyName].push(sanitizedProblem) + } + } + } + }) + req.session.errors = errors + return res.redirect(303, 'back') + } + } + + // If not an Inertia request, perform the normal badRequest response + if (optionalData === undefined) { + sails.log.info('Ran custom response: res.badRequest()') + return res.sendStatus(statusCodeToSet) + } else if (_.isError(optionalData)) { + sails.log.info( + 'Custom response `res.badRequest()` called with an Error:', + optionalData + ) + + if (!_.isFunction(optionalData.toJSON)) { + if (process.env.NODE_ENV === 'production') { + return res.sendStatus(statusCodeToSet) + } else { + return res.status(statusCodeToSet).send(optionalData.stack) + } + } + } else { + return res.status(statusCodeToSet).send(optionalData) + } +} diff --git a/templates/mellow-react/api/responses/expired.js b/templates/mellow-react/api/responses/expired.js new file mode 100644 index 00000000..95f33cb8 --- /dev/null +++ b/templates/mellow-react/api/responses/expired.js @@ -0,0 +1,27 @@ +/** + * expired.js + * + * A custom response that content-negotiates the current request to either: + * • serve an HTML error page about the specified token being invalid or expired + * • or send back 498 (Token Expired/Invalid) with no response body. + * + * Example usage: + * ``` + * return res.expired(); + * ``` + * + * Or with actions2: + * ``` + * exits: { + * badToken: { + * description: 'Provided token was expired, invalid, or already used up.', + * responseType: 'expired' + * } + * } + * ``` + */ +module.exports = function expired() { + sails.log.verbose('Ran custom response: res.expired()') + + return this.res.status(400).redirect('/link-expired') +} diff --git a/templates/mellow-react/api/responses/inertia.js b/templates/mellow-react/api/responses/inertia.js new file mode 100644 index 00000000..f5142989 --- /dev/null +++ b/templates/mellow-react/api/responses/inertia.js @@ -0,0 +1,72 @@ +// @ts-nocheck +const { encode } = require('querystring') +module.exports = function inertia(data) { + const req = this.req + const res = this.res + const sails = req._sails + + const sharedProps = sails.inertia.sharedProps + const sharedViewData = sails.inertia.sharedViewData + const rootView = sails.config.inertia.rootView + + const allProps = { + ...sharedProps, + ...data.props + } + + const allViewData = { + ...sharedViewData, + ...data.viewData + } + + let url = req.url || req.originalUrl + const assetVersion = sails.config.inertia.version + const currentVersion = + typeof assetVersion === 'function' ? assetVersion() : assetVersion + + const page = { + component: data.page, + version: currentVersion, + props: allProps, + url + } + + // Implements inertia partial reload. See https://inertiajs.com/partial-reload + if ( + req.get(inertiaHeaders.PARTIAL_DATA) && + req.get(inertiaHeaders.PARTIAL_COMPONENT) === page.component + ) { + const only = req.get(inertiaHeaders.PARTIAL_DATA).split(',') + page.props = only.length ? getPartialData(data.props, only) : page.props + } + + const queryParams = req.query + if (req.method === 'GET' && Object.keys(queryParams).length) { + // Keep original request query params + url += `?${encode(queryParams)}` + } + + if (req.get(inertiaHeaders.INERTIA)) { + res.set(inertiaHeaders.INERTIA, true) + res.set('Vary', 'Accept') + return res.json(page) + } else { + // Implements full page reload + return res.view(rootView, { + page, + viewData: allViewData + }) + } +} + +function getPartialData(props, only = []) { + return Object.assign({}, ...only.map((key) => ({ [key]: props[key] }))) +} + +const inertiaHeaders = { + INERTIA: 'X-Inertia', + VERSION: 'X-Inertia-Version', + PARTIAL_DATA: 'X-Inertia-Partial-Data', + PARTIAL_COMPONENT: 'X-Inertia-Partial-Component', + LOCATION: 'X-Inertia-Location' +} diff --git a/templates/mellow-react/api/responses/inertiaRedirect.js b/templates/mellow-react/api/responses/inertiaRedirect.js new file mode 100644 index 00000000..dc44d1f2 --- /dev/null +++ b/templates/mellow-react/api/responses/inertiaRedirect.js @@ -0,0 +1,20 @@ +// @ts-nocheck + +const inertiaHeaders = { + INERTIA: 'X-Inertia', + LOCATION: 'X-Inertia-Location' +} + +module.exports = function inertiaRedirect(url) { + const req = this.req + const res = this.res + + if (req.get(inertiaHeaders.INERTIA)) { + res.set(inertiaHeaders.LOCATION, url) + } + + return res.redirect( + ['PUT', 'PATCH', 'DELETE'].includes(req.method) ? 303 : 409, + url + ) +} diff --git a/templates/mellow-react/package-lock.json b/templates/mellow-react/package-lock.json index 4afba60a..36b2a404 100644 --- a/templates/mellow-react/package-lock.json +++ b/templates/mellow-react/package-lock.json @@ -12,10 +12,11 @@ "@sailshq/connect-redis": "^3.2.1", "@sailshq/lodash": "^3.10.3", "@sailshq/socket.io-redis": "^5.2.0", - "inertia-sails": "^0.1.7", + "inertia-sails": "^0.2.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sails": "^1.5.2", + "sails-flash": "^0.0.1", "sails-hook-orm": "^4.0.0", "sails-hook-sockets": "^2.0.0" }, @@ -1395,6 +1396,14 @@ "node": ">= 0.10.0" } }, + "node_modules/connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha512-2rcfELQt/ZMP+SM/pG8PyhJRaLKp+6Hk2IUBNkEit09X+vwn3QsAL3ZbYtxUn7NVPzbMTSLRDhqe0B/eh30RYA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/connect/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2488,11 +2497,12 @@ "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" }, "node_modules/inertia-sails": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/inertia-sails/-/inertia-sails-0.1.7.tgz", - "integrity": "sha512-9iElK2egQFHfukCkMO1KYgDLNoe/4IMBl1925FKjQEoZ/jIDAAkHlOlZ5xlgeCUx1EAYZEVLZqGiHdtlqHrhuw==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/inertia-sails/-/inertia-sails-0.2.2.tgz", + "integrity": "sha512-I4Zk6BrgriV+qCFb3ZVl/48uNC9mwNTOzL15I7yUvGBxe1jxgBYOuE+ygnNLRkPO7qhZOqJwMvE9tk2KudCIuA==", "peerDependencies": { - "sails": ">=1" + "sails": ">=1", + "sails-flash": ">=0.0.1" } }, "node_modules/inflight": { @@ -3901,6 +3911,14 @@ "machinepack-fs": "^12.0.1" } }, + "node_modules/sails-flash": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/sails-flash/-/sails-flash-0.0.1.tgz", + "integrity": "sha512-8F9h8U/YFCnJR23Q6nR1I2TH2Si1dfQvPqO2YW0VoS15QleTWk2NTzfrAyNasGU182BRXNx+W4Tv33dc5cNJ+A==", + "dependencies": { + "connect-flash": "^0.1.1" + } + }, "node_modules/sails-generate": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/sails-generate/-/sails-generate-2.0.8.tgz", diff --git a/templates/mellow-react/package.json b/templates/mellow-react/package.json index 916ad1a5..f19ce686 100644 --- a/templates/mellow-react/package.json +++ b/templates/mellow-react/package.json @@ -9,10 +9,11 @@ "@sailshq/connect-redis": "^3.2.1", "@sailshq/lodash": "^3.10.3", "@sailshq/socket.io-redis": "^5.2.0", - "inertia-sails": "^0.1.7", + "inertia-sails": "^0.2.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sails": "^1.5.2", + "sails-flash": "^0.0.1", "sails-hook-orm": "^4.0.0", "sails-hook-sockets": "^2.0.0" },