Problems running KeystoneJS without MongoDB - node.js

I would like to try running KeystoneJS without MongoDB.
There's a short blog post explaining how to do it at http://ifrederik.com/blog/2014/11/cms-without-db-running-keystonejs-without-mongodb/
Basically, it explains how to replace MondgoDB with TingoDB and using a Tungus driver.
The advice is to put the following into the top of the keystone.js file
global.TUNGUS_DB_OPTIONS = { nativeObjectID: true, searchInArray: true };
var tungus = require('tungus');
var mongoose = require('mongoose');
And later to set mongo database url to TingoDB.
keystone.set('mongo', 'tingodb://'+__dirname+'/data');
By doing this I got KeystoneJS up and running. By inspecting the contect of data/users file in TingoDB I can even see that the default user gets created, but I was not able to log in. It always reports that username / password combination is not ok.
What am I missing? How do I debug the problem to find out what exactly is the problem here?

Ok, to asnwer to myself, the problems seems to be because User.modele.findOne({email: emailRegExp}) doesn't work in TingoDB/Tungus.
When replaced it with lookup.email, without using regex-es, it semms to work.
But who knows if and what else will break because of incompatibility.

Related

Strapi & react-admin : I'd like to set 'Content-Range' header dynamically when any fetchAll query fires

I'm still a novice web developer, so please bear with me if I miss something fundamental !
I'm creating a backoffice for a Strapi backend, using react-admin.
React-admin library uses a 'data provider' to link itself with an API. Luckily someone already wrote a data provider for Strapi. I had no problem with step 1 and 2 of this README, and I can authenticate to Strapi within my React app.
I now want to fetch and display my Strapi data, starting with Users. In order to do that, quoting Step 3 of this readme : 'In controllers I need to set the Content-Range header with the total number of results to build the pagination'.
So far I tried to do this in my User controller, with no success.
What I try to achieve:
First, I'd like it to simply work with the ctx.set('Content-Range', ...) hard-coded in the controller like aforementioned Step 3.
Second, I've thought it would be very dirty to c/p this logic in every controller (not to mention in any future controllers), instead of having some callback function dynamically appending the Content-Range header to any fetchAll request. Ultimately that's what I aim for, because with ~40 Strapi objects to administrate already and plenty more to come, it has to scale.
Technical infos
node -v: 11.13.0
npm -v: 6.7.0
strapi version: 3.0.0-alpha.25.2
uname -r output: Linux 4.14.106-97.85.amzn2.x86_64
DB: mySQL v2.16
So far I've tried accessing the count() method of User model like aforementioned step3, but my controller doesn't look like the example as I'm working with users-permissions plugin.
This is the action I've tried to edit (located in project/plugins/users-permissions/controllers/User.js)
find: async (ctx) => {
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
// Send 200 `ok`
ctx.send(data);
},
From what I've gathered on Strapi documentation (here and also here), context is a sort of wrapper object. I only worked with Express-generated APIs before, so I understood this snippet as 'use fetchAll method of the User model object, with ctx.query as an argument', but I had no luck logging this ctx.query. And as I can't log stuff, I'm kinda blocked.
In my exploration, I naively tried to log the full ctx object and work from there:
// Send 200 `ok`
ctx.send(data);
strapi.log.info(ctx.query, ' were query');
strapi.log.info(ctx.request, 'were request');
strapi.log.info(ctx.response, 'were response');
strapi.log.info(ctx.res, 'were res');
strapi.log.info(ctx.req, 'were req');
strapi.log.info(ctx, 'is full context')
},
Unfortunately, I fear I miss something obvious, as it gives me no input at all. Making a fetchAll request from my React app with these console.logs print this in my terminal:
[2019-09-19T12:43:03.409Z] info were query
[2019-09-19T12:43:03.410Z] info were request
[2019-09-19T12:43:03.418Z] info were response
[2019-09-19T12:43:03.419Z] info were res
[2019-09-19T12:43:03.419Z] info were req
[2019-09-19T12:43:03.419Z] info is full context
[2019-09-19T12:43:03.435Z] debug GET /users?_sort=id:DESC&_start=0&_limit=10& (74 ms)
While in my frontend I get the good ol' The Content-Range header is missing in the HTTP Response message I'm trying to solve.
After writing this wall of text I realize the logging issue is separated from my original problem, but if I was able to at least log ctx properly, maybe I'd be able to find the solution myself.
Trying to summarize:
Actual problem is, how do I set my Content-Range properly in my strapi controller ? (partially answered cf. edit 3)
Collateral problem n°1: Can't even log ctx object (cf. edit 2)
Collateral problem n°2: Once I figure out the actual problem, is it feasible to address it dynamically (basically some callback function for index/fetchAll routes, in which the model is a variable, on which I'd call the appropriate count() method, and finally append the result to my response header)? I'm not asking for the code here, just if you think it's feasible and/or know a more elegant way.
Thank you for reading through and excuse me if it was confuse; I wasn't sure which infos would be relevant, so I thought the more the better.
/edit1: forgot to mention, in my controller I also tried to log strapi.plugins['users-permissions'].services.user object to see if it actually has a count() method but got no luck with that either. Also tried the original snippet (Step 3 of aforementioned README), but failed as expected as afaik I don't see the User model being imported anywhere (the only import in User.js being lodash)
/edit2: About the logs, my bad, I just misunderstood the documentation. I now do:
ctx.send(data);
strapi.log.info('ctx should be : ', {ctx});
strapi.log.info('ctx.req = ', {...ctx.req});
strapi.log.info('ctx.res = ', {...ctx.res});
strapi.log.info('ctx.request = ', {...ctx.request});
ctrapi.log.info('ctx.response = ', {...ctx.response});
Ctx logs this way; also it seems that it needs the spread operator to display nested objects ({ctx.req} crash the server, {...ctx.req} is okay). Cool, because it narrows the question to what's interesting.
/edit3: As expected, having logs helps big time. I've managed to display my users (although in the dirty way). Couldn't find any count() method, but watching the data object that is passed to ctx.send(), it's equivalent to your typical 'res.data' i.e a pure JSON with my user list. So a simple .length did the trick:
let data = await strapi.plugins['users-permissions'].services.user.fetchAll(ctx.query);
data.reduce((acc, user) => {
acc.push(_.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']));
return acc;
}, []);
ctx.set('Content-Range', data.length) // <-- it did the trick
// Send 200 `ok`
ctx.send(data);
Now starting to work on the hard part: the dynamic callback function that will do that for any index/fetchAll call. Will update once I figure it out
I'm using React Admin and Strapi together and installed ra-strapi-provider.
A little boring to paste Content-Range header into all of my controllers, so I searched for a better solution. Then I've found middleware concept and created one that fits my needs. It's probably not the best solution, but do its job well:
const _ = require("lodash");
module.exports = strapi => {
return {
// can also be async
initialize() {
strapi.app.use(async (ctx, next) => {
await next();
if (_.isArray(ctx.response.body))
ctx.set("Content-Range", ctx.response.body.length);
});
}
};
};
I hope it helps
For people still landing on this page:
Strapi has been updated from #alpha to #beta. Care, as some of the code in my OP is no longer valid; also some of their documentation is not up to date.
I failed to find a "clever" way to solve this problem; in the end I copy/pasted the ctx.set('Content-Range', data.length) bit in all relevant controllers and it just worked.
If somebody comes with a clever solution for that problem I'll happily accept his answer. With the current Strapi version I don't think it's doable with policies or lifecycle callbacks.
The "quick & easy fix" is still to customize each relevant Strapi controller.
With strapi#beta you don't have direct access to controller's code: you'll first need to "rewrite" one with the help of this doc. Then add the ctx.set('Content-Range', data.length) bit. Test it properly with RA, so for the other controllers, you'll just have to create the folder, name the file, copy/paste your code + "Search & Replace" on model name.
The "longer & cleaner fix" would be to dive into the react-admin source code and refactorize so the lack of "Content-Range" header doesn't break pagination.
You'll now have to maintain your own react-admin fork, so make sure you're already committed into this library and have A LOT of tables to manage through it (so much that customizing every Strapi controller will be too tedious).
Before forking RA, please remember all the stuff you can do with the Strapi backoffice alone (including embedding your custom React app into it) and ensure it will be worth the trouble.

node populatedb url not working

I am learning ExpressJs tutorial from https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose
Now I have created module for mongodb database and then create a file populatedb.js in the root directory. Now I am trying to connect to database using command (as describe at last on above link)
node populatedb mongodb://dbuser:dbpassword#ds133814.mlab.com:33814/local_library_tutorial
but terminal response nothing and there doesn't happen in database
My Brother.
I ran into the same problem, and when my search led me to your question i was even more sad when I saw no answer, however after searching around, I found an answer!
Make sure you replace the "dbuser:dbpassword" placeholders with actual data e.g "Elias:js44889" on your mongo url.
Also just make sure it's also the same as the one in app.js.
I think all should be fine as it was for me.
you must include the script in populatedb.js to get work done.
populatedb.js script link : https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js
After inserting the script in populatedb.js file from above link, then run the following commands to populate data in mongoDB.
npm install async (if not installed async module)
node populatedb <your mongodb url>
sometimes you may get an error if you use the same collection name in all of your models, this happened to me until i realised what i was doing was wrong.
for example don't do this
const authorModel = mongoose.model('author', AuthorSchema)
const bookModel = mongoose.model('author', BookSchema)
if you do it un-expeectedly it will throw this error
OverwriteModelError: Cannot overwrite author model once compiled.
instead in the commandline for "windows users" write
node populatedb mongodb+srv://username:password#yourcluster.mongodb.net/yourdatabase
and in your models make sure that the collections are not the same
const authorModel = mongoose.model('author', AuthorSchema)
const bookModel = mongoose.model('book', BookSchema)
I also ran into the same issue as I am doing the same tutorial. What I did to resolve this issue is go to the populatedb.js link they provide in the first step of the "Testing - create some items" or you can download it. Once I clicked on the link, they provide test data that you can paste into the populatedb.js file you've created in the root of your project then:
npm install async --save
node populatedb <your mongodb url>
Script should run and you should see the results or items as it creates them.
Followed the MDN tutorial, Here is what I fixed:
username:Jeff ; password : 12345
app.js, change to:
var mongoDB = 'mongodb://Jeff:123#ds149732.mlab.com:49732/local_library';
Command Line prompt, change to:
node populatedb mongodb://Jeff:12345#ds149732.mlab.com:49732/local_library
Posting here because this answer is the result I found googling the problem.
Had the same problem, the error ended up being the password I have set for the admin on the database - special characters were encoded (there was a warning displayed which I apparently ignored) which caused me to not be able to connect.
I had this issue for a couple of days with the error
zsh: no matches found: <my mongodb url>
First, I double checked I had followed the instructions in Testing — create some items in the tutorial.
Then I changed my original mongodb URL in the terminal, removing the end.
My original mongoURL =
mongodb+srv://username:password#cluster0.9f24o.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
My new mongoURL =
mongodb+srv://username:password#cluster0.9f24o.mongodb.net/myFirstDatabase
I ran this script while in my express-locallibrary tutorial folder.
node populatedb mongodb+srv://username:password#cluster0.9f24o.mongodb.net/myFirstDatabase
I'm not sure at this point if this means I need to change the link in app.js as well.
Hopefully this saves someone some time!

Node require executes code twice for mongoose Schemas

I am having trouble with require executing my code twice. Working on a standard Express app I build Mongoose Schemas, each in it's own files and export them.
//user.js
const User = mongoose.model('User', userSchema)
module.exports = User
//In other files
const User = require('../models/User')
Now I use this in two places in my application and get an error saying that
Cannot overwrite `User` model once compiled.
So the code above is getting called twice as it is the only code right now creating a model. However I would expect Node to only execute it once since it is required in my code.
The really strange part is that checking out an earlier version from Git I get the same error and people working with me on this get the same error. So I have no more ideas where to look for solutions.
Found the solution now.
Turns out I required the module once as models/user and once as model/User which in the cache of require creates two separate modules.
There have been many discussion about this:
one issue
another issue
old PR
It seems that this is due to Windows resolving paths case insensitive while other systems resolve paths case sensitive and node therefore doing it sensitive.
And a new module of'cause gets executed. Simply requiring is both times spelled in lowercase solved the issue.
I think the problem is in "const" that you use to declare the variable "User".
Try to use "var" instead of .
//user.js
var User = mongoose.model('User', userSchema)
module.exports = User
//In other files
var User = require('../models/User')
P/S: This is link that clarify more about "const" and "var":
Const in javascript? When to use it and is it necessary
Hope it helpful for you !

Sail. js / Waterline.js - find() not returning array

Problem: I'm getting unexpected output from code that previously worked.
Code Problem:
sails.models.user.find().then(function (users){...});
is currently returning { id: 1 }
but should return an array of User objects like [{id:x, name:y},...]
Code Alterations:
sails.models.user.find().exec(function (err, users){...}); does not contain an error and returns the same as using .then() like above.
sails.models.user.findOne(1).then(function (users){...}); correctly returns a User like {id:x, name:y}.
sails.models.venue.find().then(function (venues){...}); returns an array of venues, just as substituting any other class besides User.
Note:
This code was previously working (it's a pretty simple line), and the only changes I made between it working and not working was running npm install (but it was previously working on heroku where which installed, so I don't think that was a problem) and changing the schema of User to add a few columns (I did this by deleting the User table in the DB, updating the Sails User model, and lifting the app in create mode, so the table exactly matches the model). Neither of these should cause a problem, but we all know how "should" and coding don't mix :P
How do I fix this? And why did this happen? Thanks :)
Realized other code was calling the package sails-mock-models which was doing its job. Totally forgot about that code. Problem solved.

MongoDB does not seem to behave how it should as a whole

When I first used MongoDB I managed to connect successfully, however then I wanted to carry out the most basic query such as:
db.users.find()
I got an error saying TypeError: Cannot read property 'find' of undefined
Basically meaning I cannot use a collection as a property to the object db.
So i tried this:
var user_col = db.collection('users');
user.col.find();
which works absolutely fine.
Over the last few days I have kept having to look up other ways of doing things as the standard documented way doesn't seem to work. Just now I wanted to get the total users on the app, so like it says in the documentation I should do this:
var count = db.runCommand( { count: 'users' } );
console.log(count);
however this gave the error:
TypeError: undefined is not a function
Is there a problem with MongoDB you have seen like this before or am I just being stupid? I do not want to have to keep finding other, less efficient ways of doing things so finally I ask here what is going on.
Thank you.
It appears you are confusing the Mongo shell API with the node.js native driver API. While both are JavaScript, the shell is sync while node.js is async so they're totally different.

Resources