From 57e7e0756bd8c806d18fa7bc4c2f57fe4ae545c0 Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Wed, 28 Feb 2024 10:53:55 +0100 Subject: [PATCH 1/7] fix: only share errors and flash messages when it's an inertia request (#85) --- inertia-sails/private/inertia-middleware.js | 42 +++++++++++---------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/inertia-sails/private/inertia-middleware.js b/inertia-sails/private/inertia-middleware.js index a48d0e04..17e48b28 100644 --- a/inertia-sails/private/inertia-middleware.js +++ b/inertia-sails/private/inertia-middleware.js @@ -11,27 +11,29 @@ const getPartialData = require('./get-partial-data') const resolveValidationErrors = require('./resolve-validation-errors') function inertia(sails, { hook, sharedProps, sharedViewData, rootView }) { return function inertiaMiddleware(req, res, next) { - /** - * Flash messages stored in the session. - * @typedef {Object} FlashMessages - * @property {Array} message - Flash message(s). - * @property {Array} error - Error message(s). - * @property {Array} success - Success message(s). - */ - const flash = { - message: req.flash('message'), - error: req.flash('error'), - success: req.flash('success') - } - hook.share('flash', flash) // Share the flash object as props - /** - * Validation errors stored in the session, resolved and formatted for Inertia.js. - * @type {Object} - */ - const validationErrors = resolveValidationErrors(req) - req.flash('errors', validationErrors) // Flash the validation error so we can share it later + if (isInertiaRequest(req)) { + /** + * Flash messages stored in the session. + * @typedef {Object} FlashMessages + * @property {Array} message - Flash message(s). + * @property {Array} error - Error message(s). + * @property {Array} success - Success message(s). + */ + const flash = { + message: req.flash('message'), + error: req.flash('error'), + success: req.flash('success') + } + hook.share('flash', flash) // Share the flash object as props + /** + * Validation errors stored in the session, resolved and formatted for Inertia.js. + * @type {Object} + */ + const validationErrors = resolveValidationErrors(req) + req.flash('errors', validationErrors) // Flash the validation error so we can share it later - hook.share('errors', req.flash('errors')[0] || {}) // Share validation errors as props + hook.share('errors', req.flash('errors')[0] || {}) // Share validation errors as props + } hook.render = function (component, props = {}, viewData = {}) { const allProps = { From 60c921a7f7c8ca7ab96617710f6e391c1110f7fd Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Wed, 28 Feb 2024 10:56:07 +0100 Subject: [PATCH 2/7] chore(inertia-sails): release 0.1.9 --- inertia-sails/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inertia-sails/package.json b/inertia-sails/package.json index 3b01ceee..8a68cec1 100644 --- a/inertia-sails/package.json +++ b/inertia-sails/package.json @@ -1,6 +1,6 @@ { "name": "inertia-sails", - "version": "0.1.8", + "version": "0.1.9", "description": "The Sails adapter for Inertia.", "main": "index.js", "sails": { From 60a372182a2c7e7799f054a9ba09682187f6d3ae Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Thu, 29 Feb 2024 11:52:02 +0100 Subject: [PATCH 3/7] [feat]: use responses and exits (#87) * feat(inertia-sails): remove location and render method implementations * feat(inertia-sails): remove unneeded methos and have sharedProps as a property in sails.inertia --- inertia-sails/index.js | 30 +++---- inertia-sails/private/inertia-middleware.js | 90 ++------------------- 2 files changed, 18 insertions(+), 102 deletions(-) diff --git a/inertia-sails/index.js b/inertia-sails/index.js index 35e6dfc7..63e51db4 100644 --- a/inertia-sails/index.js +++ b/inertia-sails/index.js @@ -7,9 +7,6 @@ const inertia = require('./private/inertia-middleware') module.exports = function defineInertiaHook(sails) { let hook - let sharedProps = {} - let sharedViewData = {} - let rootView = 'app' const routesToBindInertiaTo = [ 'GET r|^((?![^?]*\\/[^?\\/]+\\.[^?\\/]+(\\?.*)?).)*$|', // (^^Leave out assets) @@ -22,37 +19,36 @@ module.exports = function defineInertiaHook(sails) { return { defaults: { inertia: { + rootView: 'app', version: 1 } }, initialize: async function () { hook = this sails.inertia = hook - + sails.inertia.sharedProps = {} + sails.inertia.sharedViewData = {} sails.on('router:before', function routerBefore() { routesToBindInertiaTo.forEach(function iterator(routeAddress) { - sails.router.bind( - routeAddress, - inertia(sails, { hook, sharedProps, sharedViewData, rootView }) - ) + sails.router.bind(routeAddress, inertia(hook)) }) }) }, - share: (key, value = null) => (sharedProps[key] = value), + share: (key, value = null) => (sails.inertia.sharedProps[key] = value), - getShared: (key = null) => sharedProps[key] ?? sharedProps, + getShared: (key = null) => + sails.inertia.sharedProps[key] ?? sails.inertia.sharedProps, flushShared: (key) => { - key ? delete sharedProps[key] : (sharedProps = {}) + key + ? delete sails.inertia.sharedProps[key] + : (sails.inertia.sharedProps = {}) }, - viewData: (key, value) => (sharedViewData[key] = value), - - getViewData: (key) => sharedViewData[key] ?? sharedViewData, - - setRootView: (newRootView) => (rootView = newRootView), + viewData: (key, value) => (sails.inertia.sharedViewData[key] = value), - getRootView: () => rootView + getViewData: (key) => + sails.inertia.sharedViewData[key] ?? sails.inertia.sharedViewData } } diff --git a/inertia-sails/private/inertia-middleware.js b/inertia-sails/private/inertia-middleware.js index 17e48b28..d2cd9c70 100644 --- a/inertia-sails/private/inertia-middleware.js +++ b/inertia-sails/private/inertia-middleware.js @@ -1,100 +1,20 @@ -const { encode } = require('querystring') const isInertiaRequest = require('./is-inertia-request') -const { - INERTIA, - PARTIAL_DATA, - PARTIAL_COMPONENT -} = require('./inertia-headers') - -const getPartialData = require('./get-partial-data') const resolveValidationErrors = require('./resolve-validation-errors') -function inertia(sails, { hook, sharedProps, sharedViewData, rootView }) { +function inertia(hook) { return function inertiaMiddleware(req, res, next) { if (isInertiaRequest(req)) { - /** - * Flash messages stored in the session. - * @typedef {Object} FlashMessages - * @property {Array} message - Flash message(s). - * @property {Array} error - Error message(s). - * @property {Array} success - Success message(s). - */ const flash = { message: req.flash('message'), error: req.flash('error'), success: req.flash('success') } - hook.share('flash', flash) // Share the flash object as props - /** - * Validation errors stored in the session, resolved and formatted for Inertia.js. - * @type {Object} - */ - const validationErrors = resolveValidationErrors(req) - req.flash('errors', validationErrors) // Flash the validation error so we can share it later - - hook.share('errors', req.flash('errors')[0] || {}) // Share validation errors as props - } - - hook.render = function (component, props = {}, viewData = {}) { - const allProps = { - ...sharedProps, - ...props - } - - const allViewData = { - ...sharedViewData, - ...viewData - } - - let url = req.url || req.originalUrl - const assetVersion = sails.config.inertia.version - const currentVersion = - typeof assetVersion == 'function' ? assetVersion() : assetVersion - - const page = { - component, - version: currentVersion, - props: allProps, - url - } + hook.share('flash', flash) - // Implements inertia partial reload. See https://inertiajs.com/partial-reload - if (req.get(PARTIAL_DATA) && req.get(PARTIAL_COMPONENT) === component) { - const only = req.get(PARTIAL_DATA).split(',') - page.props = only.length ? getPartialData(props, only) : page.props - } - - const queryParams = req.query - if (req.method == 'GET' && Object.keys(queryParams).length) { - // Keep original request query params - url += `?${encode(queryParams)}` - } + const validationErrors = resolveValidationErrors(req) + req.flash('errors', validationErrors) - if (isInertiaRequest(req)) { - res.set(INERTIA, true) - res.set('Vary', 'Accept') - return res.json(page) - } else { - // Implements full page reload - return sails.hooks.views.render(rootView, { - page, - viewData: allViewData - }) - } - } - /** - * Handle 303 and external redirects - * see https://inertiajs.com/redirects#303-response-code - * @param {string} url - The URL to redirect to. - */ - hook.location = function (url) { - if (isInertiaRequest(req)) { - res.set('X-Inertia-Location', url) - } - return res.redirect( - ['PUT', 'PATCH', 'DELETE'].includes(req.method) ? 303 : 409, - url - ) + hook.share('errors', req.flash('errors')[0] || {}) } return next() From cbf1d46315be24e1dc91e6b0dd73bac93cda90e1 Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Thu, 29 Feb 2024 12:24:31 +0100 Subject: [PATCH 4/7] [feat]: update mellow vue (#89) * feat(inertia-sails): remove location and render method implementations * feat(inertia-sails): remove unneeded methos and have sharedProps as a property in sails.inertia * feat(mellow-vue): add inertia and inertiaRedirect responses * feat(mellow-vue): replace calls to sails.inertia.render with inertia custom response and exit * feat(mellow-vue): replace call to sails.inertia.location with the inertiaRedirect custom response --- .../api/controllers/auth/view-check-email.js | 13 ++-- .../controllers/auth/view-forgot-password.js | 6 +- .../api/controllers/auth/view-link-expired.js | 6 +- .../api/controllers/auth/view-login.js | 6 +- .../controllers/auth/view-reset-password.js | 6 +- .../api/controllers/auth/view-signup.js | 6 +- .../api/controllers/auth/view-success.js | 17 +++-- .../mellow-vue/api/controllers/home/index.js | 8 ++- .../mellow-vue/api/controllers/user/logout.js | 6 +- .../api/controllers/user/view-profile.js | 6 +- templates/mellow-vue/api/responses/inertia.js | 72 +++++++++++++++++++ .../api/responses/inertiaRedirect.js | 20 ++++++ 12 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 templates/mellow-vue/api/responses/inertia.js create mode 100644 templates/mellow-vue/api/responses/inertiaRedirect.js diff --git a/templates/mellow-vue/api/controllers/auth/view-check-email.js b/templates/mellow-vue/api/controllers/auth/view-check-email.js index ad753995..891b9766 100644 --- a/templates/mellow-vue/api/controllers/auth/view-check-email.js +++ b/templates/mellow-vue/api/controllers/auth/view-check-email.js @@ -4,7 +4,9 @@ module.exports = { description: 'Display "Verify email" page.', exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function () { @@ -14,8 +16,11 @@ module.exports = { } else { message = `We sent an email verification link to ${this.req.session.userEmail}` } - return sails.inertia.render('check-email', { - message - }) + return { + page: 'check-email', + props: { + message + } + } } } diff --git a/templates/mellow-vue/api/controllers/auth/view-forgot-password.js b/templates/mellow-vue/api/controllers/auth/view-forgot-password.js index 53c019f1..d5896864 100644 --- a/templates/mellow-vue/api/controllers/auth/view-forgot-password.js +++ b/templates/mellow-vue/api/controllers/auth/view-forgot-password.js @@ -4,10 +4,12 @@ module.exports = { description: 'Display "Forgot password" page.', exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function () { - return sails.inertia.render('forgot-password') + return { page: 'forgot-password' } } } diff --git a/templates/mellow-vue/api/controllers/auth/view-link-expired.js b/templates/mellow-vue/api/controllers/auth/view-link-expired.js index 7792337f..a82cf1e3 100644 --- a/templates/mellow-vue/api/controllers/auth/view-link-expired.js +++ b/templates/mellow-vue/api/controllers/auth/view-link-expired.js @@ -4,10 +4,12 @@ module.exports = { description: 'Display "Link expired" page.', exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function () { - return sails.inertia.render('link-expired') + return { page: 'link-expired' } } } diff --git a/templates/mellow-vue/api/controllers/auth/view-login.js b/templates/mellow-vue/api/controllers/auth/view-login.js index 0ef59eb5..4f5838d1 100644 --- a/templates/mellow-vue/api/controllers/auth/view-login.js +++ b/templates/mellow-vue/api/controllers/auth/view-login.js @@ -4,10 +4,12 @@ module.exports = { description: 'Display "Login" page.', exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function () { - return sails.inertia.render('login') + return { page: 'login' } } } diff --git a/templates/mellow-vue/api/controllers/auth/view-reset-password.js b/templates/mellow-vue/api/controllers/auth/view-reset-password.js index 7ac0d42c..b773f6aa 100644 --- a/templates/mellow-vue/api/controllers/auth/view-reset-password.js +++ b/templates/mellow-vue/api/controllers/auth/view-reset-password.js @@ -10,7 +10,9 @@ module.exports = { } }, exits: { - success: {}, + success: { + responseType: 'inertia' + }, invalidOrExpiredToken: { responseType: 'expired', description: 'The provided token is expired, invalid, or already used up.' @@ -26,6 +28,6 @@ module.exports = { if (!user || user.passwordResetTokenExpiresAt <= Date.now()) { throw 'invalidOrExpiredToken' } - return sails.inertia.render('reset-password', { token }) + return { page: 'reset-password', props: { token } } } } diff --git a/templates/mellow-vue/api/controllers/auth/view-signup.js b/templates/mellow-vue/api/controllers/auth/view-signup.js index d9c845af..7d55fef4 100644 --- a/templates/mellow-vue/api/controllers/auth/view-signup.js +++ b/templates/mellow-vue/api/controllers/auth/view-signup.js @@ -3,10 +3,12 @@ module.exports = { description: 'Display "Signup" page.', exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function () { - return sails.inertia.render('signup') + return { page: 'signup' } } } diff --git a/templates/mellow-vue/api/controllers/auth/view-success.js b/templates/mellow-vue/api/controllers/auth/view-success.js index b561b421..4f2c6a0b 100644 --- a/templates/mellow-vue/api/controllers/auth/view-success.js +++ b/templates/mellow-vue/api/controllers/auth/view-success.js @@ -8,7 +8,9 @@ module.exports = { } }, exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function ({ operation }) { @@ -24,10 +26,13 @@ module.exports = { message = 'Password has been successful reset' pageHeading = 'Password reset successful' } - return sails.inertia.render('success', { - pageTitle, - pageHeading, - message - }) + return { + page: 'success', + props: { + pageTitle, + pageHeading, + message + } + } } } diff --git a/templates/mellow-vue/api/controllers/home/index.js b/templates/mellow-vue/api/controllers/home/index.js index e0595cfe..93bdb1d5 100644 --- a/templates/mellow-vue/api/controllers/home/index.js +++ b/templates/mellow-vue/api/controllers/home/index.js @@ -5,9 +5,13 @@ module.exports = { inputs: {}, - exits: {}, + exits: { + success: { + responseType: 'inertia' + } + }, fn: async function () { - return sails.inertia.render('index') + return { page: 'index' } } } diff --git a/templates/mellow-vue/api/controllers/user/logout.js b/templates/mellow-vue/api/controllers/user/logout.js index db27c004..fb5559a7 100644 --- a/templates/mellow-vue/api/controllers/user/logout.js +++ b/templates/mellow-vue/api/controllers/user/logout.js @@ -6,12 +6,14 @@ module.exports = { inputs: {}, exits: { - success: {} + success: { + responseType: 'inertiaRedirect' + } }, fn: async function () { sails.inertia.flushShared('loggedInUser') delete this.req.session.userId - return sails.inertia.location('/') + return '/' } } diff --git a/templates/mellow-vue/api/controllers/user/view-profile.js b/templates/mellow-vue/api/controllers/user/view-profile.js index f3dfd0bc..98d7d2ff 100644 --- a/templates/mellow-vue/api/controllers/user/view-profile.js +++ b/templates/mellow-vue/api/controllers/user/view-profile.js @@ -4,10 +4,12 @@ module.exports = { description: 'Display "Profile" page.', exits: { - success: {} + success: { + responseType: 'inertia' + } }, fn: async function () { - return sails.inertia.render('profile') + return { page: 'profile' } } } diff --git a/templates/mellow-vue/api/responses/inertia.js b/templates/mellow-vue/api/responses/inertia.js new file mode 100644 index 00000000..f5142989 --- /dev/null +++ b/templates/mellow-vue/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-vue/api/responses/inertiaRedirect.js b/templates/mellow-vue/api/responses/inertiaRedirect.js new file mode 100644 index 00000000..dc44d1f2 --- /dev/null +++ b/templates/mellow-vue/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 + ) +} From 770fa881868d6cb030d09f0604c258fdf055fa77 Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Thu, 29 Feb 2024 12:30:36 +0100 Subject: [PATCH 5/7] chore: update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98812fe7..4a67eb1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "boring-stack", - "version": "0.1.3", + "version": "0.2.0", "private": "true", "description": "The Boring JavaScript Stack 🥱 - an opinionated project starter for fullstack JavaScript", "scripts": { From 45639586d54eeee18a7adc6e64c6ffff1e3c6f2d Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Thu, 29 Feb 2024 12:30:55 +0100 Subject: [PATCH 6/7] chore(inertia-sails): update version to 0.2.0 --- inertia-sails/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inertia-sails/package.json b/inertia-sails/package.json index 8a68cec1..6f777edd 100644 --- a/inertia-sails/package.json +++ b/inertia-sails/package.json @@ -1,6 +1,6 @@ { "name": "inertia-sails", - "version": "0.1.9", + "version": "0.2.0", "description": "The Sails adapter for Inertia.", "main": "index.js", "sails": { From ab359044c89a951a99ac4820354aebedbba64e22 Mon Sep 17 00:00:00 2001 From: Kelvin Oghenerhoro Omereshone Date: Thu, 29 Feb 2024 12:31:11 +0100 Subject: [PATCH 7/7] chore(mellow-vue): update inertia-sails to latest version --- templates/mellow-vue/package-lock.json | 8 ++++---- templates/mellow-vue/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/mellow-vue/package-lock.json b/templates/mellow-vue/package-lock.json index 39914faa..8714ebcd 100644 --- a/templates/mellow-vue/package-lock.json +++ b/templates/mellow-vue/package-lock.json @@ -13,7 +13,7 @@ "@sailshq/connect-redis": "^3.2.1", "@sailshq/lodash": "^3.10.3", "@sailshq/socket.io-redis": "^5.2.0", - "inertia-sails": "^0.1.8", + "inertia-sails": "^0.2.0", "nodemailer": "^6.9.4", "sails": "^1.5.2", "sails-flash": "^0.0.1", @@ -4002,9 +4002,9 @@ "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" }, "node_modules/inertia-sails": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/inertia-sails/-/inertia-sails-0.1.8.tgz", - "integrity": "sha512-oypB8CmZkKZEjHgL6v8/f/TTvDItTaJaCMJPIhGew6W5r5pdpU8q1lJvKeX9y2P9OW9fx96KK2pEBTkBkaf/1w==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/inertia-sails/-/inertia-sails-0.2.0.tgz", + "integrity": "sha512-z3Ah6Nzz2L0LcSGJfgKvcwfOUUNt62JSYA+gj3WgFB99nZPdd1Lds1/Qpjcw4k0utUfb9o81Kxu/v5B3H+6cHg==", "peerDependencies": { "sails": ">=1", "sails-flash": ">=0.0.1" diff --git a/templates/mellow-vue/package.json b/templates/mellow-vue/package.json index 0d02301b..e08a7c76 100644 --- a/templates/mellow-vue/package.json +++ b/templates/mellow-vue/package.json @@ -10,7 +10,7 @@ "@sailshq/connect-redis": "^3.2.1", "@sailshq/lodash": "^3.10.3", "@sailshq/socket.io-redis": "^5.2.0", - "inertia-sails": "^0.1.8", + "inertia-sails": "^0.2.0", "nodemailer": "^6.9.4", "sails": "^1.5.2", "sails-flash": "^0.0.1",