Libraries in use: Express, Mongoose, Express-Restify-Mongoose
Problem: I am trying to figure out how to create a POST request that will provide the schema in the req.body. I want to simply create a new collection if it does not already exist and enforce that new schema.
when I use the following code:
app.use('/api/v1', function(req, res, next) {
if(req.path[0] === '/' && -1 === req.path.indexOf('/', 1) && req.method === 'POST') {
var collection_name = req.path.substr(1, req.path.length - 1).toLowerCase();
if(mongoose.modelNames().indexOf(collection_name) === -1) {
// only create if model does not exist
console.log(req.body);
var schema = new mongoose.Schema({}, { strict: false, collection: collection_name });
var model = mongoose.model(collection_name, schema);
restify.serve(app, model, { plural: false, name: collection_name });
}
}
next();
});
It also posts an empty document to that collection. If I change the code ever so slightly so that var schema uses the post's req.body to determine the schema the POST request does not go through:
var schema = new mongoose.Schema(req.body, { strict: false, collection: collection_name });
Where the req.body from the POST is:
{
"update_count": { "type": "String", "required": "false" },
"created_date": { "type": "String", "required": "false" },
"created_by": { "type": "String", "required": "false" },
"updated_date": { "type": "String", "required": "false" },
"updated_by": { "type": "String", "required": "false" }
}
Which returns an error and does not complete the POST request because it is also trying to use that same req.body to follow the schema I've just set AND it wants to use the req.body to enter into the document.
{
"message": "testcollection3 validation failed",
"name": "ValidationError",
"errors": {
"updated_by": {
"message": "Cast to String failed for value \"[object Object]\" at path \"updated_by\"",
"name": "CastError",
"kind": "String",
"value": {
"type": "String",
"required": "false"
},
"path": "updated_by"
},
..................................
How can I set the schema with my post and also prevent a document from being created?
As you've seen, Mongoose won't create a model's collection until it needs to save a document to it. However, you can create the collection explicitly using the Db#createCollection method from the native MongoDB driver that's accessible via mongoose.connection.db:
mongoose.connection.db.createCollection(collection_name, (err) => {...});
Mongoose seems to create a still missing collection any time it is needed for some data access operation. So for me, (using v4.5.9) this works:
mongoose.connection.db.findOne({}).then(...).catch(...);
Related
I'm trying to populate a specific relation, using the relation name (categories) in combination with the populate parameter but it doesn't populate the categories.
When I look at my schema, I see that the relational field is present in the attributes object. But I still only get the non-relational fields in my response.
I tried every combination mentioned on the Strapi documentation but none of them worked.
The find permission is also enabled for the content-types that are being populated which in this case is categories.
/api/products?populate=*
/api/products?populate[0]=categories
/api/products?populate[categories]=*
My Product schema
{
"kind": "collectionType",
"collectionName": "products",
"info": {
"singularName": "product",
"pluralName": "products",
"displayName": "Product",
"description": ""
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"title": {
"type": "string"
},
"images": {
"type": "media",
"multiple": true,
"required": false,
"allowedTypes": [
"images"
]
},
"categories": {
"type": "relation",
"relation": "oneToMany",
"target": "api::category.category"
}
}
}
System
Strapi version: 4.1.8
NPM version: 8.3.2
Node.js version: 16.13.2
Database: MySQL
You need to enable find permission on the product as well. You have to set the find permission on all the relationships (or sub-tables).
Use the "role" option.
Edit: Other choice is to use populate deep from strapi marketplace, just install, configure the default depth and then on every request where you want deep just add populate: deep
you will have to populate inside your controller, as in Strapi documentation
Query Engine API: Populating
strapi.db.query('api::article.article').findMany({
populate: true,
});
Entity Service API: Populating
const entries = await strapi.entityService.findMany('api::article.article', {
populate: '*',
});
REST API: Population & Field Selection
const qs = require('qs');
const query = qs.stringify({
fields: ['title', 'body'],
}, {
encodeValuesOnly: true,
});
If you want to populate everything just use populate: '*'
If you want to populate a relation or more use populate: [relationOne, relationTwo]
As an Example If you want to fetch data from db based on some conditions and populate one relationship
const transactions = await strapi
.query("api::transaction.transaction")
.findMany({
where: { user: userId },
populate: { offer: true },
});
here the result
If you want to populate all relations
const transactions = await strapi
.query("api::transaction.transaction")
.findMany({
where: { user: userId },
populate: "*",
});
I'm using loopback to create an API. So far I've been able to set up all the models that I require. How can we set up these endpoints so that they show the data for only the user that has been logged in?
For example, user A adds some data in the database and user B adds some other data in the database. Now if user A is logged in, I only want to get the data that was added by A. As of now I'm getting all the data present in the database together.
My model JSON is as follows:
{
"name": "IPs",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"identifier": {
"type": "string"
},
"IP": {
"type": "array"
},
"type": {
"type": "string"
},
"plugins": {
"type": "array"
}
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "DENY"
}
],
"methods": {}
}
and JS is as follows:
module.exports = function(IPs) {
};
Change principalId of the model from $unauthenticated to $owner
Note from loopback documentation:
To qualify a $owner, the target model needs to have a belongsTo relation to the User model (or a model that extends User) and property matching the foreign key of the target model instance. The check for $owner is performed only for a remote method that has ‘:id’ on the path, for example, GET /api/users/:id.
I would like to get a modified response object. For example I dont know how to get the user object without the roles.
The default response is:
{
"id": 6,
"username": "username",
"email": "user#email.com",
"provider": "local",
"confirmed": true,
"blocked": false,
"role": {
"id": 2,
"name": "Authenticated",
"description": "Default role given to authenticated user.",
"type": "authenticated"
}
}
Now I want to get the same response without the role attribute.
{
"id": 6,
"username": "username",
"email": "user#email.com",
"provider": "local",
"confirmed": true,
"blocked": false
}
Currently you cannot do this in the Rest API unless you change the UserController provided by permissions plugin, which is not recommended.
What you can do then is to use the GraphQL plugin provided by Strapi, so you can query only the fields you need on client side.
The docs about how to use GraphQL plugin are here.
For anyone still struggling with this problem:
The latest versions of strapi do support custom queries, you can pass an array containing all the names of relations you wish to populate (only relations!).
If you don't want to populate any relationships, you can keep it empty, your controller would then look something like this:
module.exports = {
UserWithoutRoles: ctx => {
return strapi.query('user').findOne({ id: ctx.params.id }, ['']);
}
}
If you do wish to populate it, it would be like this:
module.exports = {
UserWithoutRoles: ctx => {
return strapi.query('user').findOne({ id: ctx.params.id }, ['role']);
}
}
Also see:
[https://strapi.io/documentation/3.0.0-beta.x/concepts/queries.html#api-reference][1]
Trying to very reference a property in with loopback and considering how much time I have tried to do this, I am clearly missing some fundamental concept.
Very simply, I have a Question model which has a points as a integer property and what I would like to do is simply print out the points property to the console.
module.exports = function(Question) {
Question.observe('after save', function(ctx, next) {
console.log(Question.prototype.points)
next();
});
};
When I do the above, it prints out undefined
Considering that this is such a simple operation to do, what am I missing?
json file:
{
"name": "Question",
"plural": "Questions",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"text": {
"type": "string",
"required": true
},
"points": {
"type": "number",
"required": true
}
},
}
You are almost there. Use context object that you get on after save.
module.exports = function(Question) {
Question.observe('after save', function(ctx, next) {
console.log(ctx.instance.points);
next();
});
};
Operation hooks documentation.
I am trying to create an MS SQL db with breeze-breeze sequelize and i like to generate the ids on the db server. My solution is oriented on the tempHire example from the breeze samples repo
My Metadata.json looks like this:
{
"metadataVersion": "1.0.5",
"namingConvetion": "camelCase",
"localQueryComparisonOptions": "caseInsensitiveSQL",
"dataServices": [{
"serviceName": "breeze/",
"hasServerMetadata": true,
"useJsonp": false
}],
"structuralTypes": [{
"shortName": "User",
"namespace": "Model",
"autoGeneratedKeyType": "Identity",
"defaultResourceName": "Users",
"dataProperties": [{
"nameOnServer": "id",
"dataType": "Int32",
"isPartOfKey": true,
"isNullable": false
}, {
"name": "firstName",
"dataType": "String"
}, {
"name": "lastName",
"dataType": "String"
}, {
"name": "userName",
"dataType": "String",
"isNullable": false,
"maxLength": 64,
"validators": [{
"name": "required"
}, {
"maxLength": 64,
"name": "maxLength"
}]
}, {
"name": "email",
"dataType": "String"
}]
}],
"resourceEntityTypeMap": {
"Users": "User:#Model"
}
}
though this will not create an identity id column.
the created table looks like the following create script:
CREATE TABLE [User] (
[id] INTEGER NOT NULL ,
[firstName] NVARCHAR(255) DEFAULT NULL,
[lastName] NVARCHAR(255) DEFAULT NULL,
[userName] NVARCHAR(64) NOT NULL DEFAULT '',
[email] NVARCHAR(255) DEFAULT NULL,
PRIMARY KEY ([id])
)
In addition here are some breeze server side implementations:
var dbConfig = {
user: 'user',
password: 'secret',
dbName: 'dbname'
};
var sequelizeOptions = {
host: 'hostname',
dialect: 'mssql',
port: 1433
};
function createSequelizeManager() {
var metadata = readMetadata();
var sm = new SequelizeManager(dbConfig, sequelizeOptions);
sm.importMetadata(metadata);
return sm;
}
var _sequelizeManager = createSequelizeManager();
_sequelizeManager.authenticate();
_sequelizeManager.sync(false /* createDb */)
.then(seed)
.then(function () {
console.log('db init successful');
});
Do i have a wrong configuration? Is the Identity not available with the mssql dialect? Am i doing something wrong?
With the configuration is nothing wrong i guess.
I just found out that there is a bug in the MetadataMapper from breeze-sequelize. I tested it with the sequelize version 2.1.3 and 3.x.
The autoIncrement attribute for sequelize will never get set. The if statement will never be true. I'll report this on github. ;)
The fix would be the following code in the MetadataMapper.js at line 134:
if (attributes.type.key == "INTEGER" || attributes.type.key =="BIGINT") {
attributes.autoIncrement = true;
}
In the original code the if statement is attributes.type== "INTEGER" || attributes.type=="BIGINT" where the type actually never is a string.