override sails GET blueprint with many-to-one - node.js

caveat: new to web programming. I have the following in a sails project:
// in models/User.js
// user has many videos
module.exports = {
attributes: {
videos: {
collection: 'video',
via: 'owner'
}
}
}
// in models/Video.js
// video has one user
module.exports = {
attributes: {
owner: {
model: 'user'
}
}
}
I'm using the REST blueprints with a /v1 prefix. I'm trying to either override GET /v1/user/:id/video, or make a custom route to GET /v1/user/:id/myGetVideo
So I've tried the following:
find and findOne methods in VideoController.js and UserController.js. These don't trigger for GET /v1/user/:id/video - I guess because the many-to-one association is treated differently
creating a custom route
// in routes.js
module.exports.routes = {
'get /user/:id/myGetVideo': 'UserController.myGetVideo'
}
This doesn't trigger anything because of the namespace, but it does trigger /user/:id/myGetVideo as expected. I don't want to add /v1 namespace here b/c that will proliferate and become an issue to change versions.
So, how do I either override the method I want, or put it in the namespace? Overriding the first method seems cleaner, but idk.
Thanks for any help!
Edit: Any comments on what is better practice for a REST API?

Got it with wildcards:
// in routes.js
module.exports.routes = { 'get /v*/user/:id/myGetVideo':'UserController.myGetVideo' }
not bullet-proof if the prefix scheme changes, but good enough

Related

Strapi V4 filter where relation does not exist

I'm setting up a comments-like system where comments have a parent field which relations to another comment. I want to be able to query the top level comment by finding comments that have no parent set, but I cannot see in the documentation any way to do this.
I tried this, but it doesn't look like the $null filter works on joins.
const posts = await strapi.entityService.findMany('api::post.post', {
filters: {
thread: {
id: {
$eq: entity[0].id,
}
},
parent: {
$null: true,
}
}
});
And I get the error: Only $and, $or and $not can by used as root level operators. Found $null.
I can't find anything about this in the Strapi documentation but it seems like a pretty standard thing to do want to do.
So I found a solution to this using the Strapi query API rather than the entity service:
const posts = await strapi.db.query('api::post.post').findMany({
where: {
thread: entity[0].id,
parent: null
}
});
filters[relation_name][id][$null]=true
or
filters:{
relation_name:{
id:{
$null:true
}
}
}
This helped me to filter out the record where a relation is null/doesn't exist.

How to sanitize response body in Node.js and Typescript

I have a backend server written in typescript on node.js using nest.js as framework.
I want to automatically sanitize the response body of my controllers removing undeclared properties of my DTOs.
I have the following files:
class User {
_id: string
name: string
hashedPassword: string
}
class UserDTO {
_id: string
name: string
}
#ApiTags('users')
#Controller('users')
export class UsersController {
...
#Get(':id')
#UseGuards(JwtAuthGuard)
#ApiBearerAuth()
#ApiOperation({ summary: 'Find one user by id' })
#ApiResponse({
status: 200,
type: UserDto,
})
async findOne(#Param('id') id: string): Promise<UserDto> {
return await this.usersService.findById(id) as UserDto;
}
}
When I declare user as UserDTO I thought that will remove all undeclared properties as UserDTO (in this case the 'hashedPassword') that's not what happened, it still sending the 'hashedPassword', I want to remove it automatically without calling constructores or removing it manually using const { hashedPassword, ...result } = user; return result; on e each service.
It's a pain do this conversions every time I have to send a DTO. I'm looking for something that do this conversions automatically. Does exists something in typescript or in Nest.js that do this conversions automatically for me? Maybe some decorator in the DTO or calling some middleware?
In older projects I used to do this to automatically remove unwanted properties:
const UserSchema = new Schema({
hashedPassword: String,
...
}, {
timestamps: true,
toJSON: {
transform: (doc, ret, options) => {
delete ret.hashedPassword;
return ret;
},
virtuals: false,
}
});
Today I consider this a bad implementation because I adding business logic to my repository. I'm looking for something that sanitize my DTOs automatically. Is that possible?
Sounds like you might be looking for Serialization, which Nest has a setup with an interceptor and class-transformer calling classToPlain() to serialize the response. It sounds like you might be working with Mongo, so keep in mind you may need to do a bit extra in your services to get a true class instance that class-transformer can work with.
As for why your above attempt didn't work, as mentioned by jonrsharpe in the comments, type information doesn't exist at runtime. It's purely there for devs during development and compile time to help us catch bugs that could be easily avoided. Also, to take it a step further x as y tells the compiler "I know that x is of type y, whether you can read that from the code I've written or not". It's essentially telling the compiler you know more than it, so it shouldn't worry.

Bookshelf.JS: Related Model is trying to be used before being required

I have two models that are related, Customers and Addresses. I first discovered this issue when I was trying to create a customer with a related address. For our purposes, a single customer can have multiple addresses, and when creating a new customer, we want to create an address at the same time as we create the customer.
I did some digging through the documentation and set up the relationship as best as I could, and this seemed to work well enough, but then I noticed that when I included both the models in modules together, (i.e. my routes/controllers), I was getting circular references.
Long story short, my research lead me to add the registry plugin to my bookshelf.js file. This worked at the time, but now it looks like my Address model isn't properly exported when being referenced in Customers.
Here's a snippet of my current configuration
// bookshelf.js
const bookshelf = require('bookshelf')(knex);
bookshelf.plugin([
'registry',
]);
module.exports = bookshelf;
// customers.js
const bookshelf = require('../bookshelf');
const Address = require('./address');
const Customer = bookshelf.Model.extend({
tableName: 'customers',
addresses: function () {
return this.hasMany('Address');
},
}, {
customCreate: function (attributes) {
return this.forge(attributes)
.save()
.tap(c => {
return Address.forge(attributes)
.save({
customer_id: c.get('id'),
});
})
}
});
module.exports = bookshelf.model('Customer', Customer);
// address.js
const bookshelf = require('../bookshelf');
const Customer = require('./customer');
const Address = bookshelf.Model.extend({
tableName: 'addresses',
customer: function () {
return this.belongsTo('Customer');
}
});
module.exports = bookshelf.model('Address', Address);
I started to notice that when I would run Customer.customCreate(), I got an error saying Address.forge is not a function. I threw some console logs into my customer.js file and saw that Address is an empty object ({}) when being referenced within customer.js. However, in other places, it's returning the proper Bookshelf model.
Looks to me like I'm trying to use my Address model in customers before it's properly required, which made me wonder if I'm structuring my project and models properly, or if there's any changes I need to make.
There's a circular reference problem alright. The best way to structure your models so that there are no such problems is to load them all during your app's initialization in a single file, e.g. index.js on your models' directory, attach each one to an object and export that object. Then you just require() that file and get access to all the models in a single place.
However, to solve your problem in a much easier way you just need to make a single change to your customCreate() method:
customCreate: function (attributes) {
return this.forge(attributes)
.save()
.tap(c => this.related('addresses').create(attributes))
}
}
This makes use of the Collection.create method to easily create a new model inside a collection, and since it's used on a relation it will also set the correct foreign key.
Note that the Registry plugin will not save you from circular dependency problems, but it will allow you to write your models in a way that avoids them.

Disable blueprint routes sails js

Hello I have model users, in which there is a foreign key of Orders model.
Now sails will automatically generate route /users/:id/orders. I have to disable this route. How to do this ? I have already tried to disable all routes of orders using: _config : { actions: false, rest: false, shortcuts: false } but it still doen't work
You can achieve this by adding custom routes, which will overwrite the blueprint action.
Use http://sailsjs.org/documentation/concepts/routes/custom-routes#?response-target-syntax
'/users/:id/orders': {response: 'forbidden'}
or http://sailsjs.org/documentation/concepts/routes/custom-routes#?function-target-syntax
'/users/:id/orders': function(req, res) {res.forbidden();}
You could control the access to this model through policies.
For blocking everything put the code below inside your /config/policies.js file:
Orders : {
'*': false
},
You could also overwrite the route
In /config/routes.js:
'/:collection/:id/:model': {response: 'forbidden'}
Or you could do the way you've done, disabling the rest routes on this model
Just make sure you put the whole block, including the export line:
module.exports = {
_config: {
rest: false
}
};

Is it possible somehow to set defaul joins/relations for model in Sequelize?

So I have model Alarms which is associated with Site model and others... Is it possible somehow set that by default when are required Alarm.findAll().then() I didn’t need to specify which associated models I need? It is necessary because Alarms table are using in many different situations and some different apps but in my case I need only entries which has site.
Or may be somehow I can add default joins to the model?
Usually when I encounter situations like this, I would just make a module which returns a promise of the query (with joins). So, for example you could make an alarm_util module
exports.getAlarm = function() {
return Alarms.findAll({
include: [{
model: Site,
include: [{
model: OtherModel
}]
}]
});
};
module.exports = exports;
And use it anywhere in your code like
alarm_util.getAlarm().then(alarm => {
// The rest of your logic here...
});

Resources