I am trying to modify the behaviour of the "passwordless" strapi plugin so that it would generate the verification code only with digits. So for this I need to override a function createToken in the plugin's service.
I created a folder with the name of the plugin inside extensions folder as specified in Strapi docs. And inside this folder(strapi-plugin-passwordless) I created a file strapi-server.js in which I export a function:
module.exports = (plugin) => {
plugin.services.passwordless.createToken = async (email, context) => {
const settings = await this.settings();
const tokensService = strapi.query("plugin::passwordless.token");
tokensService.update({ where: { email }, data: { is_active: false } });
const body = customAlphabet("1234567890", 6);
const tokenInfo = {
email,
body,
context: JSON.stringify(context),
};
return tokensService.create({ data: tokenInfo });
};
return plugin;
};
The only difference between original createToken function the custom one is that the variable body is in the form of 6 digits.
However, Strapi doesn't run this function, instead it runs the original one on node_modules?
What may I possibly be doing wrong ?
Related
Let's say we are creating a module called app by constructing a new vm.SourceTextModule object:
const context = {
exports: {},
console, // custom console object
};
const sandbox = vm.createContext(context);
const app = new vm.SourceTextModule(
`import path from 'path';
console.log(path.resolve('./src'));`,
{
context: sandbox,
}
);
According to the Node.js documentation to obtain the default export from path module we should "link" the imported dependencies of app module to it.
To achieve this we should pass linker callback to app.link method:
async function linker(specifier, referencingModule) {
// the desired logic...
}
await app.link(linker);
How to implement linker function properly so that we could import path module in newly created app module and use it:
await app.evaluate(); // => /home/user/Documents/project/src
P.S. We are using TypeScript, so I checked if we have installed types for path package.
package.json:
"#types/node": "^17.0.31",
I found https://github.com/nodejs/node/issues/35848 where someone posted a code snippet.
From there I've adapted the following linker callback:
const imports = new Map();
async function linker(specifier, referencingModule) {
if (imports.has(specifier))
return imports.get(specifier);
const mod = await import(specifier);
const exportNames = Object.keys(mod);
const imported = new vm.SyntheticModule(
exportNames,
() => {
// somehow called with this === undefined?
exportNames.forEach(key => imported.setExport(key, mod[key]));
},
{ identifier: specifier, context: referencingModule.context }
);
imports.set(specifier, imported);
return imported;
}
The code snippet from the GitHub issue didn't work for me on Node 18.7.0 as is, because the evaluator callback passed to the constructor of SyntheticModule is somehow called with this set to undefined. This may be a Node bug.
I also cached the imported SyntheticModules in a Map because if they have internal state, creating a new SyntheticModule every time will reset that state.
Well, I have a plugin with some functions in it, functions should update URL queries.
The problem is, everytime I execute $global.changePage(2) or $global.changeLimit(2) the console.log(query) returns an empty object and didn't return updated queries in URL.
I know NuxtJS plugins execute once we load the page but is there any way to have updated query each time I use those functions?
export default (ctx: NuxtConfig, inject: Inject) => {
const { app, query } = ctx;
const global = {
changePage (page: number) {
console.log(query);
app.router.push({
query: { ...query, page }
})
},
changeLimit(limit: number) {
console.log(query);
app.router.push({
query: { ...query, limit }
})
},
}
inject('global', global)
ctx.$global = global
}
Simply remove ctx.$global = global from the last line of your plugin.
The inject method provided by Nuxt takes care of making your global object available where it's needed, so there's really no need for you to overwrite the ctx.$global property.
I'm trying to use Environment Variables via plugin method (https://docs.cypress.io/api/plugins/configuration-api#Switch-between-multiple-configuration-files), this has worked previously but recently has stopped working for me, any pointers would be great.
plugin/index.js
const fs = require('fs-extra')
const path = require('path')
function getConfigurationByFile(file) {
const pathToConfigFile = path.resolve('cypress', 'config', `${file}.json`)
return fs.readJson(pathToConfigFile)
}
module.exports = (on, config) => {
const file = config.env.configFile || 'build'
return getConfigurationByFile(file)
}
ConfigFile > build.json
{
"env": {
"StudentPortal": "https://www.google.co.uk"
}
}
Usage
cy.visit(Cypress.env('StudentPortal'));
As I said, this used to work and would visit the URL within the configFile, now I just get the following error:
CypressError
cy.visit() must be called with a url or an options object containing a url as its 1st argumentLearn more
cypress/support/commands.js:17:8
15 | Cypress.Commands.add('StudentPortalLogin', (email, password) => {
16 |
17 | cy.visit(Cypress.env('StudentPortal'));
It appears the baseURL may be missing. cy.visit() is looking for something in reference to the baseURL. I don't see one defined in the build.json file, that might fix the error to add a baseURL, "https://www.google.co.uk", then put authentication parameters inside the env{} part, per the example in your link.
I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.
I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.
modules/dynamicRoutesGenerator.js
const generator = function () {
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
generator.generateRoutes(await generateDynamicRoutes())
})
}
let generateDynamicRoutes = async function() {
//...
return routes
}
export default generator
Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.
I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:
If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.
This is exactly what I need so I'm trying to use it with the following code:
store/index.js
export const actions = {
nuxtServerInit (context, nuxtContext) {
context.commit("dynamicRoutes/addRoute", nuxtContext)
}
}
store/dynamicRoutes.js
export const state = () => ({
navMenuNivel0: {}
})
export const mutations = {
addRoute (state, { ssrContext }) {
//Ignore static generated routes
if (!ssrContext.payload || !ssrContext.payload.entrada) return
//If we match this condition then it's a nivel0 route
if (!ssrContext.payload.navMenuNivel0) {
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Store nivel0 route, we could use url only but only _id is guaranteed to be unique
state.navMenuNivel0[ssrContext.payload._id] = {
url: ssrContext.url,
entrada: ssrContext.payload.entrada,
navMenuNivel1: []
}
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Nivel1 route
} else {
//...
}
}
}
export const getters = {
navMenuNivel0: state => state.navMenuNivel0
}
The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:
{}
{
"5fc2f4f15a691a0fe8d6d7e5": {
"url": "/A",
"entrada": "A",
"navMenuNivel1": []
}
}
{}
{
"5fc2f5115a691a0fe8d6d7e6": {
"url": "/B",
"entrada": "B",
"navMenuNivel1": []
}
}
I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.
My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.
Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.
If you made it this far thanks for your time!
I came up a with solution but I don't find it very elegant.
The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.
I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.
dynamicRoutesGenerator.js
const generator = function () {
//Add hook before build to create our static API files
this.nuxt.hook('build:before', async (plugins) => {
//Fetch the routes and pages from API
let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
let pages = await APIService.fetchQuery(QueryService.paginasQuery())
//Cache the queries results into staticAPI file
APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
APIService.saveStaticAPIData("pages", pages)
})
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
console.log('generate:before')
generator.generateRoutes(await generateDynamicRoutes())
})
}
//Here I can't find a way to access via $staticAPI
let generateDynamicRoutes = async function() {
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
//...
}
The plugin staticAPI.js:
import APIService from '../services/APIService'
let fetchPage = function(fetchUrl) {
return this.pages.find(p => { return p.url === fetchUrl})
}
export default async (context, inject) => {
//Get routes and files from the files
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
let pages = APIService.getStaticAPIData("pages")
//Put the objects and functions in the $staticAPI property
inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
}
The APIService helper to save/load data to the file:
//...
let fs = require('fs');
let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
}
let getStaticAPIData = function (fileName = '{}') {
let staticData = {};
try {
staticData = require("../static-api-data/" + fileName + ".json");
} catch (ex) {}
return staticData;
}
module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
nuxt.config.js
build: {
//Enable 'fs' module
extend (config, { isDev, isClient }) {
config.node = { fs: 'empty' }
}
},
plugins: [
{ src: '~/plugins/staticAPI.js', mode: 'server' }
],
buildModules: [
'#nuxtjs/style-resources',
'#/modules/staticAPIGenerator',
'#/modules/dynamicRoutesGenerator'
]
I'm trying to write a Vue plugin that's a simple abstraction to manage auth state across my app. This will need to access other Vue plugins, namely vuex, vue-router and vue-apollo (at the moment).
I tried extending Vue.prototype but when I try to access the plugin's properties how I would normally - eg. this.$apollo - I get the scope of the object, and therefore an undefined error. I also tried adding vm = this and using vm.$apollo, but this only moves the scope out further, but not to the Vue object - I guess this is because there is no instance of the Vue object yet?
export const VueAuth = {
install (Vue, _opts) {
Vue.prototype.$auth = {
test () {
console.log(this.$apollo)
}
}
}
}
(The other plugins are imported and added via. Vue.use() in the main app.js)
Alternatively, I tried...
// ...
install (Vue, { router, store, apollo })
// ...
but as a novice with js, I'm not sure how this works in terms of passing a copy of the passed objects, or if it will mutate the originals/pass by ref. And it's also very explicit and means more overhead if my plugin is to reach out to more plugins further down the line.
Can anyone advise on a clean, manageable way to do this? Do I have to instead alter an instance of Vue instead of the prototype?
In the plugin install function, you do not have access to the Vue instance (this), but you can access other plugins via the prototype. For example:
main.js:
Vue.use(Apollo)
Vue.use(VueAuth) // must be installed after vue-apollo
plugin.js:
export const VueAuth = {
install (Vue) {
Vue.prototype.$auth = {
test () {
console.log(Vue.prototype.$apollo)
}
}
}
}
I found a simple solution for this issue:
In plugin installer you need to add value to not just prototype, but Vue itself to be able to use it globally.
There is a code example:
Installer:
import apiService from "../services/ApiService";
// Service contains 'post' method
export default {
install(Vue) {
Vue.prototype.$api = apiService;
Vue.api = apiService;
}
};
Usage in other plugin:
import Vue from "vue";
...
const response = await Vue.api.post({
url: "/login",
payload: { email, password }
});
Usage in component:
const response = await this.$api.post({
url: "/login",
payload: { email, password }
});
I'm not sure if that's a good solution, but that made my scenario work perfectly.
So, I got around this by converting my property from a plain ol' object into a closure that returns an object, and this seems to have resolved my this scoping issue.
Honestly, I've jumped into Vue with minimal JS-specific knowledge and I don't fully understand how functions and the likes are scoped (and I'm not sure I want to look under that rock just yet......).
export const VueAuth = {
install (Vue, opts) {
Vue.prototype.$auth = function () {
let apollo = this.$apolloProvider.defaultClient
let router = this.$router
return {
logIn: function (email, password) {
apollo.mutate({
mutation: LOGIN_MUTATION,
variables: {
username: email,
password: password,
},
}).then((result) => {
// Result
console.log(result)
localStorage.setItem('token', result.data.login.access_token)
router.go(router.currentRoute.path)
}).catch((error) => {
// Error
console.error('Error!')
console.error(error)
})
},
logOut: function () {
localStorage.removeItem('token')
localStorage.removeItem('refresh-token')
router.go()
console.log('Logged out')
},
}
}
It's a rudimental implementation at the moment, but it'll do for testing.