I am trying to create a simple server application in Node.js using the waterline-orientdb package where there are several users who can invoke several methods. Before a user can do anything, the user needs to authenticate with his username and password. Within this authentication the user object is given a token that will be piggybacked with the future requests.
When a user is given a token, an update query is invoked. When invoking the update request I get the following error:
ERROR err: { [OrientDB.RequestError: expression item ']' cannot be resolved because current record is NULL]
name: 'OrientDB.RequestError',
message: 'expression item \']\' cannot be resolved because current record is NULL',
data: {},
previous: [],
id: 1,
type: 'com.orientechnologies.orient.core.exception.OCommandExecutionException',hasMore: 0 }
The strange thing is that the update is executed, so this error doesn't have influence on the update request. But because I want to catch all errors, I can't just ignore this.
My model looks like this:
module.exports = {
tableName: 'User',
identity: 'dbuser',
schema: true,
attributes: {
id: {
type: 'string',
primaryKey: true,
columnName: '#rid'
},
username: {
type: 'string',
required: true,
unique: true
},
password: {
type: 'string',
required: false
},
token: {
type: 'string'
},
follows: {
collection: 'dbuser',
via: 'followed',
dominant: true
},
followed: {
collection : 'dbuser',
via: 'follows'
}
};
As you can see, I'm associating two users with eachother so that one user can follow the activities of the other user. When I delete the association (so follows and followed) the error also dissapears.
The piece of code where the updates happens looks like this:
user[0].token = generateToken(user[0])
dbuser.update({
id: user[0].id
}, user[0]).exec(function (error, data) {
if (error) res.json(401, {
code: 401,
error: "Token could not be updated"
})
res.json(user);
});
Does anyone has an idea on how to avoid this behavior or what the error even means?
It seems to be a bug in the adapter.
You could try using:
npm install appscot/waterline-orientdb#refactor_collection
Apparently will be resolved in v.0.10.40
More info about it: https://github.com/appscot/waterline-orientdb/issues/43#issuecomment-75890992
Related
I'm new to backend so I would like to make sure the data saved to the database is safe. I already got an answer, but I would still like a second opinion.
What the app does...
A user inputs some text into an input field and the app checks if there are any "typos" / misuse of the word and informs the user how to correct the errors.
The text user inputs is saved to the database along with uuid for tracking changes made to the text during that session.
The app currently has no other interaction with database.
I might have gone overboard, trying to use Sequelize.Op.eq, but it returned that an error shown below.
Current working example:
const UserText = sequelize.define('userText', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
uuidv4: {
type: Sequelize.TEXT,
},
userText_field: {
type: Sequelize.TEXT,
allowNull: false
}
});
async function addStringToTable(uuid, string ) {
try{
await UserText.create({
uuidv4: uuid ,
userText_field: string
});
} catch (e) {
console.log(e);
}
}
This code throws an error (is Sequelize.Op.eq needed?)
async function addStringToTable(uuid, string ) {
// Save the input string to the database
try{
await UserText.create({
uuidv4: { [Sequelize.Op.eq]: uuid },
userText_field: { [Sequelize.Op.eq]: string }
});
} catch (e) {
console.log(e);
}
}
//error:
ValidationErrorItem {
message: 'uuidv4 cannot be an array or an object',
type: 'string violation',
path: 'uuidv4',
value: [Object],
origin: 'CORE',
instance: [userText],
validatorKey: 'not_a_string',
validatorName: null,
validatorArgs: []
},
ValidationErrorItem {
message: 'userText_field cannot be an array or an object',
type: 'string violation',
path: 'userText_field',
value: [Object],
origin: 'CORE',
instance: [userText],
validatorKey: 'not_a_string',
validatorName: null,
validatorArgs: []
}
Follow up question is, since I have no other interaction with the database, is using ORM enough or should some other security measures be taken? Since there is no "admin" panel, no users, and in general no access to the database from the front end, is the database safe from tempering / unauthorised lookup? Is there anything else I should look into / take care of.
I know the followup question are too broad, but any advice would be welcome.
When sails fill default global attributes which we added on config/models.js ,
default settings looks like :
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
}
Now if we add sth like creatorId to this default attributes , how we should fill it once for all our models ?
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
creatorId: { type: 'number'}
}
After this change , all models have creatorId with 0 value , how I can set userId to all of my models creatorId before save without repeating my self?
In the controller you are creating the entry in the database this should be quite straight forward. Let's assume that you have two models, User, which comes with Sails built-in authentication, and a Thing, something that someone can own.
In the Thing model, I'd change the ownerId to be owner and associate it with the User model like so:
attributes: {
id: { ... },
createdAt: { ... },
updatedAt: { ... },
owner: {
model: 'User',
required: yes // Enable this when all the stuff in the db has this set
},
}
This creates an association or one-to-many relationship if you know database terminology.
Now in the controller where you create your object to be inserted:
Thing.create({
someAttribute: inputs.someValue,
someOtherAttribute: inputs.someOtherValue,
owner: this.req.me.id
});
If you want to use the created object right away, append .fetch() to the chain after .create({...}) like so:
var thing = await Thing.create({ ... }).fetch();
Let me know if something is unclear.
I'd actually recommend you invest the $9 in buying the SailsJS course. It's an official course, taught by the creator of SailsJS, Mike McNeil. It takes you from npm i sails -g to pushing to production on the Heroku cloud platform. It teaches basic Vue (parasails flavour), using MailGun, Stripe payments, and more. They link to the course on the site here
Update
Did some further digging, and was inspired by a couple of similar cases.
What you can do is expand your model with a custom method that wraps the .create() method. This method can receive the request object from your controllers, but doing this, rather than the previous suggestion, will probably be more work than just adding ownerId: this.req.me.id, to existing calls. I1ll demonstrate anyway.
// Your model
module.exports = {
attributes: { ... },
proxyCreate(req, callback) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id // or req.user.id, cant remember
// which works here
}
Thing.create(request.body, callback);
}
}
And in your controller:
...
// Change from:
Thing.create(req.body);
// To:
Thing.proxyCreate(req);
...
Update #2
Another idea I had was adding the middleware on a per-route basis. I don't know the complexity of your routes, but you can create a custom middleware for only those routes.
In router.js you edit your routes (I'll show one for brevity):
....
'POST /api/v1/things/upload-thing': [
{ action: 'helpers/add-userid-to-ownerid' },
{ action: 'new-thing' }
],
....
In helpers/add-userid-to-ownerid:
module.exports: {
fn: function(req, res) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id;
}
}
}
I am building a schema in mongoose (v4.13.8) with an Array of Mixed values. I have come up with the following Schema:
var deviceConfigSchema = new mongoose.Schema({
capabilities: {
type: [capabilitySchema],
required: true,
validator: [isValidCapabilities, "Not a valid capability array"]
},
services: {
type: [{}],
required: true,
validator: [isValidServices, "Not a valid service array"]
}
});
The problem is that I get a validation error saying that services: Path 'services' is required. when I try to submit data. What is strange is that the data I send for the 'capabilities' works fine and the only difference is that I specify a schema explicitly.
Removing the required: true from services causes there to be an empty array object in the returned values.
I am submitting the data using an API POST request with the data in the body of the request. I am using Postman to submit the request, with x-www-form-urlencoded checked. This is copied from the body key-value input
capabilities[0][field_map][field]:pressure
capabilities[0][field_map][type]:float
capabilities[0][field_map][format]:hPa
services[0][name]:rest
services[0][receive][0][capability_id]:0
services[0][receive][0][path]:/api/relay/0
Update:
I'd like to apologise as this was a mistake on my part. I dynamically create a configuration based on the request and at one point the copied services were being made null, doh!
However, having got the required: true validation to pass, the custom validator is still not being executed. I also can't find any documentation about the order in which validators and are executed which would be very useful. Below is the validator snippet for reference:
function isValidServices(services) {
for (const service of services) {
if (typeof service.name !== 'string') return false;
}
return true;
}
Having experimented with various approaches and looking in more detail on the mongoose API docs, I found that there is a validate option for schemas too. http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate
I changed my Schema from this:
var deviceConfigSchema = new mongoose.Schema({
capabilities: {
type: [capabilitySchema],
required: true,
validator: [isValidCapabilities, "Not a valid capability array"]
},
services: {
type: [{}],
required: true,
validator: [isValidServices, "Not a valid service array"]
}
});
To this [notice the validate instead of validator]...
var deviceConfigSchema = new mongoose.Schema({
capabilities: {
type: [capabilitySchema],
required: true,
validate: [isValidCapabilities, "Not a valid capability array"]
},
services: {
type: [{}],
required: true,
validate: [isValidServices, "Not a valid service array"]
}
});
After this my validator functions were being executed without any issues. Hopefully this helps someone.
Lets say i have the following model schema described as below to add some chat functionality between 2 users in my sails app. For instance if i have user A sending a message to user B, then user B will reply back to user A which will always create 2 conversations between both users. So my question is how can i query both conversations to get messages from A and B. I tried something like this but maybe theres a simple logic.
// User Model
attributes: {
username: {
type: 'string',
required: true,
unique: true,
},
conversations_sender: {
collection: conversation,
via: 'sender'
},
conversations_recipient: {
collection: conversation,
via: 'recipient'
}
}
// Conversation model
attributes: {
sender: {
model: user
},
recipient: {
model: user
},
messages: {
collection: 'message',
via: 'conversation'
}
}
// Message model
attributes: {
text: {
type: 'string'
},
conversation: {
model: 'conversation'
},
}
// Conversation Controller
get: function(req, res) {
var params = {
or : [
{
sender: req.param('sender'),
recipient: req.param('recipient')
},
{
sender: req.param('recipient'),
recipient: req.param('sender')
}
]
}
Conversation.find(params) ...
}
You should rethink your schema a bit, see this link that has a good database design for your needs:
http://www.9lessons.info/2013/05/message-conversation-database-design.html
You should be able then to fetch all the messages with the 2 user ids like this:
Conversation.findAll({sender: ..., receiver: ...})
Also you will need a timestamp for the messages, in the future you'll want to sort them somehow and also make the nice 'Read yesterday' feature
I've created an OData Endpoint with Node by using odata-server module by JayData in this way:
require("odata-server");
$data.Entity.extend("Service", {
Id: {type: "id", key: true, computed: true, nullable: false},
Name: {type: "string", nullable: false, maxLength: 50}
});
$data.EntityContext.extend("marketplace", {
Services: {type: $data.EntitySet, elementType: Service}
});
$data.createODataServer(marketplace, "/marketplace", 8081, "localhost");
console.log("Marketplace OData Endpoint created... Listening at 8081.");
Then, still with Node, I've created an Express web application which receives some commands through GET request, connects to the OData Endpoint (still by using JayData) and receives some data from there, then sends back the result to the client (in the following code it just sends 200), in this way (by defining a route):
require("jaydata");
...
app.get("/addCompare/:id", function(req, res) {
console.log("Comparison request for: " + req.params.id);
$data.Entity.extend("Service", {
Id: {type: "id", key: true, computed: true, nullable: false},
Name: {type: "string", nullable: false, maxLength: 50}
});
$data.EntityContext.extend("marketplace", {
Services: {type: $data.EntitySet, elementType: Service}
});
db = new marketplace("http://localhost:8081/marketplace");
db.onReady(function() {
var arr = db.Services.filter(function(s) {return s.Name.startsWith("Serv");}).toArray();
console.dir(arr);
});
res.send(200);
});
The problem is that when I try this code (by using this GET request for example: http://www.localhost:8080/addCompare/NTM0M2ZkNjU2YjljNWMwODRiOGYyYTU5), I always get this error in the server and after that it crashes. Here's the error:
TypeError: Value '$data.Object' not convertable to '$data.ObjectID'
{ name: 'TypeError',
message: 'Value \'$data.Object\' not convertable to \'$data.ObjectID\'',
data:
{ __metadata:
{ type: 'Service',
id: 'http://localhost:8081/marketplace/Services(\'NTM0M2ZkNjU2YjljNWMwODRiOGYyYTU5\')',
uri: 'http://localhost:8081/marketplace/Services(\'NTM0M2ZkNjU2YjljNWMwODRiOGYyYTU5\')' },
Id: 'NTM0M2ZkNjU2YjljNWMwODRiOGYyYTU5',
Name: 'Service51' } }
Where am I wrong? Thanks...
As the behavior was explained in OData - Strange index with MongoDB [Mongoose: Cast Error], the id - NTM0M2ZkNjU2YjljNWMwODRiOGYyYTU5 – should be base-64 decoded (for example 5343fd656b9c5c084b8f2a70 is a valid format).
Although the declaration of JayData model is correct, it will be re-defined every single time when a request arrives to your server. You can improve the current implementation by moving your $data.Entity.extend and $data.EntityContext.extend blocks outside the app.get – after require("jaydata");.