I'm struggling with what should be a simple case, but I can't seem to get to the bottom of it: I am building a very simple CRUD app with nextjs using MongoDB.
I am following the official nextjs "with-mongodb-mongoose" as a guide (https://github.com/vercel/next.js/tree/canary/examples/with-mongodb-mongoose)
While all should be simple, my code fails to properly create a document with the form data to be sent to MongoDB. In other words, all my entries in MongoDB are empty documents:
{ _id: new ObjectId("63a5aa1317a618f4d3eefab8"), __v: 0 }
Taking it to step by step:
I have created a very simple model that only takes "name" as a paremeter:
import mongoose from 'mongoose'
const raceresultSchema = new mongoose.Schema({
name: String,
});
const Raceresult = mongoose.models.Raceresult || mongoose.model('Raceresult', raceresultSchema)
export default Raceresult
My POSt API route is exactly the same as the one in the GitHub repo from nextjs:
case 'POST':
try {
const newResultDoc = await Raceresult.create(req.body);
res.status(201).json({ success: true, data: newResultDoc })
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
I use ThunderClient in VSCode to simplify, and I make a POST request to the right API route with the following body:
{
"name": "test"
}
I systematically get the following answer:
{
"success": true,
"data": {
"_id": "63a5b71917a618f4d3eefae3",
"__v": 0
}
}
As I'm following the syntax from the Github repo, I don't see what I'm doing wrong.
The DB connection works fine. These empty documents get saved on my MongoDB, with no problem.
When I add elements manually in MongoDB Atlas, that works fine and the items get saved properly, with no problem.
The problem happens whenever I try to use the API route to save a document. So I guess the problem must come from the API POST code or from the body format. However, I can't figure out what is wrong as I'm following the syntax from the official GitHub repo...
I have seen another StackOverflow post with a similar type of issue, but the solution did not seem applicable to my case.
Appreciate your help!
EDIT: I copy/pastd the code from the nextjs github repo (Pet model, and Pet API route) and made a POST request in Thunder Client to save a pet in my database (which is not the point) and... it worked, the document is saved in my database with the data from the req body... I'll keep trying and update this post if I succeed with my own model
So I seem to have solved it like this:
Connected to my MongoDB database in my terminal and dropped all existing collections
stopped the app and restarted running npm run dev
tried the API calls again and... it now works.
I guess it had something to do with saved Schema Types that were not being validated somewhere... I don't know more, but this now works.
Related
I am trying to follow the Strapi v4.0.0 guide on https://docs.strapi.io/developer-docs/latest/developer-resources/plugin-api-reference/server.html#entry-file for extending the users-permission plugin to add a custom route/controller, but so far have been unsuccessful. I add the custom files as stated in the docs, but there is no change in the UI.
I managed to get this to work for normal API highlighted in yellow, but was unable to do so for the users-permission plugin
In the previous version 3.6.8 this functionality was allowed through the extensions folder.
Am I missing something from the new guide, I even tried copying the files from node_modules > #strapi > plugin-users-permission and adding a new route and method to the exiting controller file but it still does not reflect the change in the section where we assign different route permission to roles. The user-permission plugin still shows the original routes, with no change.
Thanks,
I ran into this thread while researching pretty much the same issue, and I wanted to share my solution.
First of all, I found this portion of the documentation more useful than the one you referenced: https://docs.strapi.io/developer-docs/latest/development/plugins-extension.html
My goal was the write a new route to validate JWT tokens based on the comment made here: https://github.com/strapi/strapi/issues/3601#issuecomment-510810027 but updated for Strapi v4.
The solution turned out to be simple:
Create a new folder structure: ./src/extensions/user-permissions if it does not exist.
Create a new file ./src/extensions/user-permissions/strapi-server.js if it does not exist.
Add the following to the file:
module.exports = (plugin) => {
plugin.controllers.<controller>['<new method>'] = async (ctx) => {
// custom logic here
}
plugin.routes['content-api'].routes.push({
method: '<method>',
path: '/your/path',
handler: '<controller>.<new method>',
config: {
policies: [],
prefix: '',
},
});
return plugin;
};
If you're unsure what controllers are available, you can always check the API documentation or console.log(plugin) or console.log(plugin.controllers).
After the admin server restarts, you should see your new route under the user-permissions section as you would expect, and you can assign rights to it as you see fit.
My full strapi-server.js file including the logic to validate JWT:
module.exports = (plugin) => {
plugin.controllers.auth['tokenDecrypt'] = async (ctx) => {
// get token from the POST request
const {token} = ctx.request.body;
// check token requirement
if (!token) {
return ctx.badRequest('`token` param is missing')
}
try {
// decrypt the jwt
const obj = await strapi.plugin('users-permissions').service('jwt').verify(token);
// send the decrypted object
return obj;
} catch (err) {
// if the token is not a valid token it will throw and error
return ctx.badRequest(err.toString());
}
}
plugin.routes['content-api'].routes.push({
method: 'POST',
path: '/token/validation',
handler: 'auth.tokenDecrypt',
config: {
policies: [],
prefix: '',
},
});
return plugin;
};
When exporting routes you need to export the type, either content-api or admin. Look at the Strapi email plugin in node_modules for example, change the folder and file structure in your routes folder to match that and then you will be able to set permissions in the admin panel.
If your Strapi server is using Typescript, make sure that you name your extension files accordingly. So instead of strapi-server.js, you would need to name your file strapi-server.ts.
I'm hoping someone can tell me if I'm barking up the wrong tree. I have built a basic web app using Vue CLI and included the PWA support. Everything seems to work fine, I get the install prompt etc.
What I want to do, is cache various pages (routes) that user hasn't visited before, but so that they can when offline.
The reason here is that I'm planning to build an app for an airline and part of that app will act as an in flight magazine, allowing users to read various articles, however the aircrafts do not have wifi so the users need to download the app in the boarding area and my goal is to then pre cache say the top 10 articles so they can read them during the flight.
Is this possible? and is PWA caching the right way to go about it? Has anyone does this sort of thing before?
Thanks in advance
To "convert" your website to an PWA, you just need few steps.
You need to know that the service worker is not running on the main thread and you cant access for example the DOM inside him.
First create an serviceworker.
For example, go to your root directory of your project and add a javascript file called serviceworker.js this will be your service worker.
Register the service worker.
To register the service worker, you will need to check if its even possible in this browser, and then register him:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/serviceworker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope');
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
In vue.js you can put this inside mounted() or created() hook.
If you would run this code it will say that the service worker is successfully registered even if we havent wrote any code inside serviceworker.js
The fetch handler
Inside of serviceworker.js its good to create a variable for example CACHE_NAME. This will be the name of your cache where the cached content will be saved at.
var CACHE_NAME = "mycache_v1";
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
Everytime you make a network request your request runs through the service worker fetch handler here first. You need to response with event.respondWith()
Next step is you first open your cache called mycache_v1 and take a look inside if there is a match with your request.
Remember: cache.match() wont get rejected if there is no match, it just returns undefined because of that there is a || operator at the return statement.
If there is a match available return the match out of the cache, if not then fetch() the event request.
In the fetch() you save the response inside the cache AND return the response to the user.
This is called cache-first approach because you first take a look inside the cache and in case there is no match you make a fallback to the network.
Actually you could go a step further by adding a catch() at your fetch like this:
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
})
.catch(err => {
return fetch("/offline.html")
});
In case there is nothing inside the cache AND you also have no network error you could response with a offline page.
You ask yourself maybe: "Ok, no cache available and no internet, how is the user supposed to see the offline page, it requires internet connection too to see it right?"
In case of that you can pre-cache some pages.
First you create a array with routes that you want to cache:
var PRE_CACHE = ["/offline.html"];
In our case its just the offline.html page. You are able to add css and js files aswell.
Now you need the install handler:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(PRE_CACHE);
})
);
});
The install is just called 1x whenever a service worker gets registered.
This just means: Open your cache, add the routes inside the cache. Now if you register you SW your offline.html is pre-cached.
I suggest to read the "Web fundamentals" from the google guys: https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook
There are other strategies like: network-first
To be honest i dont know exactly how the routing works with SPAs because SPA is just 1 index.html file that is shipped to the client and the routing is handled by javascript you will need to check it out witch is the best strategie for your app.
Good time of the day!
After a few weeks of development of my project, i've decided to migrate from plain Vue to Nuxt.
Mainly because i need SSR, although i know that Google can execute JS presented on the page and therefore generate appropriate content for their search bot.
Another reason is a general workflow of the project development. I like idea with automatic instantiation of routes, store, etc.
I've faced, however, a pretty strange behavior of the application when it is running in the mode: universal instead of mode: spa. And i don't want to switch to mode: spa since then i lose the SSR i was migrating for in the first place.
I' have an account module in the store - account.js which is responsible for handling any operations related to the account management, such as login/logout, get profile of authenticated user, store the token obtained from the login request as well as the logic for handling the 2FA TOTP procedure.
In my legacy application, i was able to get the token from the cookies by just using the following bit of the code
import Cookies from 'js-cookie';
export const state = {
user: null,
token: Cookies.get('token')
}
And save token after the successful authentication by executing the following mutation:
[types.ACCOUNT_SAVE_TOKEN] (state, { token, remember }) {
state.token = token;
Cookies.set('token', token, {
expires: 365,
httpOnly: true
});
}
But after migration to Nuxt.js, every time im loggin in, the token value in the state is getting populated, but no cookie is set, and after navigating to the other page inside the project (it is pwa, so no reloading, etc) token is reset back to null.
This issue however is gone if application is switched to the mode: spa from mode: universal and everything is working just fine.
I've read many issues on the github as well as done pretty big portion of crawling throught the websites which are trying to solve the same issue, though none of the suggestions are working for me.
I've even installed the cookie-universal-nuxt package from NPM which claims to be working with the SSR, but yet every time I'm trying to access this.$cookies.get('token') in the state, or anywhere else (mutations for example), I'm just getting error about using the method (get/set/remove) on null.
Does anyone know or have an idea on how to overcome this issue, at least if it is possible without going back to the mode: spa?
P.S. Running npm run build|generate yields same files as for the normal vue (without the content, just the structure until the target element is readched) while in mode: spa.
Okay, after around 12 hours trying to wrap my head around this issue, i've decided to go the 'dirty' way and create middleware which is doing, in my opinion, way to much processing on each request.
import CookieParser from 'cookieparser';
export default async function ({ store, req }) {
if (!store.getters['account/check']) {
if (!store.state.account.token) {
if (process.server) {
let requestCookies = CookieParser.parse(req.headers.cookie);
if (requestCookies.hasOwnProperty('token')) {
store.dispatch('account/saveToken', {
token: requestCookies.token,
remember: true
});
}
}
}
if (store.state.account.token) {
if (!store.state.account.user) {
try {
await store.dispatch('account/fetchUser');
} catch (error) { }
}
}
}
return Promise.resolve();
}
Seems like useCookie has been created for this use case
I'm trying out Prisma and React Native right now. Currently I'm trying to upload images to my db with the package _apollo-upload-client (https://github.com/jaydenseric/apollo-upload-client). But it's not going so well.
Currently I can select an image with the ImagePicker from Expo. And then I'm trying to do my mutation with the Apollo Client:
await this.props.mutate({
variables: {
name,
description,
price,
image,
},
});
But I get the following error:
Network error: JSON Parse error: Unexpected identifier "POST"
- node_modules/apollo-client/bundle.umd.js:76:32 in ApolloError
- node_modules/apollo-client/bundle.umd.js:797:43 in error
And I believe it's from these lines of code:
const image = new ReactNativeFile({
uri: imageUrl,
type: 'image/png',
name: 'i-am-a-name',
});
Which is almost identical from the their example, https://github.com/jaydenseric/apollo-upload-client#react-native.
imageUrl is from my state. And when I console.log image I get the following:
ReactNativeFile {
"name": "i-am-a-name",
"type": "image/png",
"uri": "file:///Users/martinnord/Library/Developer/CoreSimulator/Devices/4C297288-A876-4159-9CD7-41D75303D07F/data/Containers/Data/Application/8E899238-DE52-47BF-99E2-583717740E40/Library/Caches/ExponentExperienceData/%2540anonymous%252Fecommerce-app-e5eacce4-b22c-4ab9-9151-55cd82ba58bf/ImagePicker/771798A4-84F1-4130-AB37-9F382546AE47.png",
}
So something is popping out. But I can't get any further and I'm hoping I could get some tips from someone.
I also didn't include any code from the backend since I believe the problem lays on the frontend. But if anyone would like to take a look at the backend I can update the question, or you could take a look here: https://github.com/Martinnord/Ecommerce-server/tree/image_uploads.
Thanks a lot for reading! Cheers.
Update
After someone asked after the logic in the server I have decided to past it below:
Product.ts
// import shortid from 'shortid'
import { createWriteStream } from 'fs'
import { getUserId, Context } from '../../utils'
const storeUpload = async ({ stream, filename }): Promise<any> => {
// const path = `images/${shortid.generate()}`
const path = `images/test`
return new Promise((resolve, reject) =>
stream
.pipe(createWriteStream(path))
.on('finish', () => resolve({ path }))
.on('error', reject),
)
}
const processUpload = async upload => {
const { stream, filename, mimetype, encoding } = await upload
const { path } = await storeUpload({ stream, filename })
return path
}
export const product = {
async createProduct(parent, { name, description, price, image }, ctx: Context, info) {
// const userId = getUserId(ctx)
const userId = 1;
console.log(image);
const imageUrl = await processUpload(image);
console.log(imageUrl);
return ctx.db.mutation.createProduct(
{
data: {
name,
description,
price,
imageUrl,
seller: {
connect: { id: userId },
},
},
},
info
)
},
}
Solution has been found.
I am a little embarrassed that this was the problem that I faced and I don't know if I should even accept this answer because of awkward I felt when I fixed the issue. But....
There was nothing wrong with my code, but there was a problem with the dependencies versions. I tried to backtrack everything on my app, so I decided to start from the beginning and create a new account. I expected it to work just fine, but I got this error:
Error: Cannot use GraphQLNonNull "User!" from another module or realm.
Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.
https://yarnpkg.com/en/docs/selective-version-resolutions
Duplicate "graphql" modules cannot be used at the same time since different
versions may have different capabilities and behavior. The data from one
version used in the function from another could produce confusing and
spurious results.
Then I understand that something (that I didn't think of) was wrong. I checked my dependencies versions and compared them with Graphcool's example, https://github.com/graphcool/graphql-server-example/blob/master/package.json. And I noticed that my dependencies was outdated. So I upgraded them and everything worked! So that was what I had to do. Update my dependencies.
Moral of the story
Always, always check your damn dependencies versions...
Crawling through your code, I have found this repository, which must be the front-end code if I am not mistaken?
As you've mentioned, apollo-upload-server requires some additional set-up and same goes for the front-end part of your project. You can find more about it here.
As far as I know, the problematic part of your code must be the initialisation of the Apollo Client. From my observation, you've put everything Apollo requires inside of src/index folder, but haven't included Apollo Upload Client itself.
I have created a gist from one of my projects which initialises Apollo Upload Client alongside some other things, but I think you'll find yourself out.
https://gist.github.com/maticzav/86892448682f40e0bc9fc4d4a3acd93a
Hope this helps you! 🙂
I'm using Worklight 6.2 to build a simple example of data sync using JSONStore and a SQL Adapter.
Everything seems to be working the way I want, except for the "push" back to the adapter. My application pulls data via the "load()". I allow the user to select an item, change it, and then "replace()" inside the collection. However, when I call "push()", nothing happens. No errors - nada. I'm hoping this is a simple "typo" problem, but I'm stumped at this point.
Any input would be GREATLY appreciated!
-bob-
Things that are working:
WL.JSONStore.init(),
WL.JSONStore.load(),
WL.JSONStore.findAll(),
WL.JSONStore.find(),
WL.JSONStore.replace(),
WL.JSONStore.pushRequiredCount()
What's not working is the last and most important:
WL.JSONStore.push();
Here's my sync code:
function syncWithServer(){
// THIS FIRST PART WORKS - YAY!!!!!
console.log ("Check for dirty records");
WL.JSONStore.get(employeeCollectionName).pushRequiredCount()
.then(function (numberOfDirtyDocuments) {
alert("DIRTY: " + numberOfDirtyDocuments);
})
.fail(function (errorObject) {
alert (errorObject);
});
/ NOTHING HAPPENS AFTER THE LOG - JUST FALLS THRU TO THE LAST CONSOLE LOG
// NO ERRORS OR ANYTHING
console.log("Atempting to update server");
WL.JSONStore.get(employeeCollectionName).push()
.then ( function (res) {
alert("Server updated successfully");
})
.fail ( function (errorObject){
alert (errorObject);
});
console.log("HUmmmmmm .....");
}
Your application seems to be doing the right thing, so this could be a defect. However, the push() and load() API are deprecated as of 6.2, and I think that if you follow the new approach it will work for you.
The new approach is to call the adapter directly; this is all that JSONStore did anyways when you called push() or load(), while constraining you to what parameters you can send to it, so it makes more sense that you call the adapter directly. For push, you can edit your push call like this:
WL.JSONStore.get(employeeCollectionName).getAllDirty()
.then(function (dirtyDocs) {
return WL.Client.invokeProcedure({
adapter : 'people',
procedure : 'updatePeople',
parameters : [ dirtyDocs ]
});
})
.then(function (responseFromAdapter) {
// ... call markClean API after a valid response form the adapter
});
Making sure to change the parameters and procedure and adapter name to your adapter's.
Also, this answer is a very short summary of what is in the documentation for working with external data, including examples on how to do push and pull (load).
That said, even though push() and load() are deprecated, they should still work, so a defect has been created to look further into this.
I ran your test app from you GitHub account and I was able to run it fine using an HTTP adapter and the callbacks returning correctly. Which logs did you receive when using an SQL adapter, if any.
[EDIT ~ 8/6] I created an SQL adapter and was able to push and pull from the adapter.