Load an installable hook before native hooks - javascript

I am using sails-hook-sequelize to load sequelize as the ORM in my sails app. However, my controllers and policies setup (i.e. just creating their methods) is dependent on the models. I need to run the sails-hook-sequelize installable hook before I run controllers and policies hooks (currently it is running it after and the controllers/policies are failing to load). How can I do this? T
Thanks in advance.
Edit: Here is some code to illustrate what I am trying to accomplish:
UserController.js
let Endpoint = require('../classes/Endpoint');
let endpoint = new Endpoint(User);
Object.assign(endpoint, {
find
});
module.exports = endpoint;
function find(req, res, next) {
User.findAll(
{
where: req.query,
include: [
{
model: Privilege,
include: [
{
model: Account,
where: {
accountPkey: {
$in: AuthorizationService.accountsForPrivileges(req.tokenData.privileges, ['ADMINISTRATOR', 'OFFICE MANAGER'])
}
}
}
]
}
]
})
.then(users => res.ok(users))
.catch(err => res.serverError(err));
}
basically, I have a default Endpoint class that I instantiate and then add methods to. You can see that the Endpoint class takes a model argument. However, when this hook runs, the models don't exist yet because they are defined by a third party hook (using sequelize).

There's currently no way to run a third-party hook before the core hooks in Sails.
Often times when I see questions like this, it's from someone who's trying to create a Wordpress-like platform, and they're making the assumption that for every new "entity type" that an end-user creates (i.e. blog, article, comment) they need a new model. An alternative is to create an Entity model, with a contents attribute that is an association to a Content or EntityAttribute model which is more or less just a key/value store. Then you can use wildcard routes to have the EntityController actions load the correct type of entity.

Related

How to intercept the creation of an entity in strapi?

I know that under the src/api folder are the entity folders each allowing 4 folders content-types, controllers, routes, and services. If I want to extend or change the behavior of an API then I intervene in the file under the controllers folder, possibly services as well. But this is only good for external calls.
For example if I want to do it when I use an external API I do the following under the foder /controllers => my-entity-name.js:
"use strict";
/**
* my-entity-name controller
*/
const { createCoreController } = require("#strapi/strapi").factories;
module.exports = createCoreController("api::my-entity-name.my-entity-name", ({ strapi }) => ({
async create(ctx) {
//Do my stuff here
//create manually the entity or super.create(ctx)
}
}))
I need to intercept the creation of an entity when the latter is done from the backoffice. Basically I need to invoke an external service before creating it. I noticed that it does not go through the same endpoint as the external API but it uses an internal one made like this: /content-manager/collection-types/api::my-entity.my-entity
What should I do to intercept it?
you have to do model lifecycle:
src/api/contenType/models/contentType/lifecycles.js
module.exports = {
async beforeCreate(event) {
…
}
}
If you need to stop creation due to error you can throw new ApplicationError(‘message’)
reference

Using sequelize-auto generates init-models.js. Is my app utilizing this for associations?

I'm working on an express rest api using sequelize. I successfully generated my models using sequelize-auto (which created init-models.js) and haven't thought about it since. My tables have associations and they show up in the init-models.js file but I can't seem to use query associating to utilize the associations.
Here's the init-models.js that sequelize-auto generated:
function initModels(sequelize) {
...
product.belongsTo(manufacturer, { as: "manufacturer", foreignKey: "manufacturer_id" });
manufacturer.hasMany(product, { as: "products", foreignKey: "manufacturer_id"});
return { product, manufacturer }
}
module.exports = initModels;
module.exports.initModels = initModels;
module.exports.default = initModels
So my question is.. is this module getting loaded when my server starts and initializes everything? If not, could I just move my associations directly to the model init function as suggested in the documentation (I think I'd rather do this)?
You'll get a better idea how to register and initialize models and their associations if you look at my other answer here.
As for shown generated code I suppose it would be better to call initModels inside the entry-point or a special DB module right after you have a Sequelize instance initialized to pass it to the function. And if you import this generated module then by default you will only have access to initModels to be able to call it wherever you wish to initialize models and to get them returned.

How to customize default find, findOne, create and update actions in SailsJS?

I'm using SailJS to build a REST API.
I've a lot of nested models, such as:
A building has many Floors, which have many Apartments, which have an Owner.
The Sails populate implementation doesn't work as expected, and I can populate at max a single level nested model.
In order to make things work I would like to rewrite custom controllers like this:
controllers/BuildingController.js
module.exports = {
find: function (req, res) {
// Custom code with population
}
findOne: function (req, res) {
// Custom code with population
}
create: function (req, res) {
// Custom code with nested object creation and connection to parent object
}
...
}
If I call an endpoint through the blueprint my custom controller for that model is called, but if I call it from another point in the code (e.g. Apartment.find()) Sails use its default action, not mine.
Do you have some kind of hint or solution to this problem? Is this possible to implement?
Thanks in advance

Where to initOracleClient in REST MVC Architecture

I have Express Routing set up with multiple routes, each using a different Oracle connection. I have to call initOracleClient prior to getConnection, however I get an error (Error: NJS-077: Oracle Client library has already been initialized) when I try to initOracleClient in both routes. I've tried moving the initOracleClient to different locations in the structure; both at the app level and route level. Where in a REST MVC structure do you initialize the client?
A REST MVC application typically has some supporting infrastructure. That is to say, MVC is not a complete blueprint on how to structure the entirety of your program's code - only a general rule of thumb of how to assign certain responsibilities.
The library you're using needs initialization, and apparently this code should execute only once. There are several ways to go about it:
Initialize the client once before starting up the express server, and then pass in the ready-to-use client for use by route handlers. This may be the easiest to use, but must necessarily delay the .listen() call - so the time until your application starts responding to HTTP may be longer.
Use a pattern known as the Singleton to allow route handlers to initialize the client, but only execute the initialization once under the hood. Depending on how exactly the library is initialized (does it return a Promise? does it use a callback?), this may require some careful design - for example, you may need to store and return a Promise instance, so multiple consumers will be calling .then() on the same Promise.
I implemented the Singleton pattern as suggested:
import oracledb from 'oracledb';
class PrivateOraInitSingleton {
constructor() {
try {
oracledb.initOracleClient({libDir: '/usr/local/lib/instantclient_19_8'});
} catch (err) {
console.error(err);
process.exit(1);
}
}
}
class OraInitSingleton {
constructor() {
throw new Error('Use OraInitSingleton.getInstance()');
}
static getInstance() {
if (!OraInitSingleton.instance) {
OraInitSingleton.instance = new PrivateOraInitSingleton();
}
return OraInitSingleton.instance;
}
}
export default OraInitSingleton;
Usage:
const object = OraInitSingleton.getInstance();
try {
connectionPromise = oracledb.getConnection({
user : process.env.DB_USER,
password : process.env.DB_PASSWORD,
connectString : process.env.CONNECT_STRING
});
} catch (err) {
console.error(err);
process.exit(1);
}

Using Apollo, how can I distinguish newly created objects?

My use case is the following:
I have a list of comments that I fetch using a GraphQL query. When the user writes a new comment, it gets submitted using a GraphQL mutation. Then I'm using updateQueries to append the new comment to the list.
In the UI, I want to highlight the newly created comments. I tried to add a property isNew: true on the new comment in mutationResult, but Apollo removes the property before saving it to the store (I assume that's because the isNew field isn't requested in the gql query).
Is there any way to achieve this?
Depends on what do you mean by "newly created objects". If it is authentication based application with users that can login, you can compare the create_date of comment with some last_online date of user. If the user is not forced to create an account, you can store such an information in local storage or cookies (when he/she last time visited the website).
On the other hand, if you think about real-time update of comments list, I would recommend you take a look at graphql-subscriptions with use of websockets. It provides you with reactivity in your user interface with use of pub-sub mechanism. Simple use case - whenever new comment is added to a post, every user/viewer is notified about that, the comment can be appended to the comments list and highlighted in a way you want it.
In order to achieve this, you could create a subscription called newCommentAdded, which client would subscribe to and every time a new comment is being created, the server side of the application would notify (publish) about that.
Simple implementation of such a case could look like that
const Subscription = new GraphQLObjectType({
name: 'Subscription',
fields: {
newCommentAdded: {
type: Comment, // this would be your GraphQLObject type for Comment
resolve: (root, args, context) => {
return root.comment;
}
}
}
});
// then create graphql schema with use of above defined subscription
const graphQLSchema = new GraphQLSchema({
query: Query, // your query object
mutation: Mutation, // your mutation object
subscription: Subscription
});
The above part is only the graphql-js part, however it is necessary to create a SubscriptionManager which uses the PubSub mechanism.
import { SubscriptionManager, PubSub } from 'graphql-subscriptions';
const pubSub = new PubSub();
const subscriptionManagerOptions = {
schema: graphQLSchema,
setupFunctions: {
newCommentAdded: (options, args) => {
newCommentAdded: {
filter: ( payload ) => {
// return true -> means that the subscrition will be published to the client side in every single case you call the 'publish' method
// here you can provide some conditions when to publish the result, like IDs of currently logged in user to whom you would publish the newly created comment
return true;
}
}
},
pubsub: pubSub
});
const subscriptionManager = new SubscriptionManager(subscriptionManagerOptions);
export { subscriptionManager, pubSub };
And the final step is to publish newly created comment to the client side when it is necessary, via above created SubscriptionManager instance. You could do that in the mutation method creating new comment, or wherever you need
// here newComment is your comment instance
subscriptionManager.publish( 'newCommentAdded', { comment: newComment } );
In order to make the pub-sub mechanism with use of websockets, it is necessary to create such a server alongside your main server. You can use the subscriptions-transport-ws module.
The biggest advantage of such a solution is that it provides reactivity in your application (real-time changes applied to comments list below post etc.). I hope that this might be a good choice for your use case.
I could see this being done a couple of ways. You are right that Apollo will strip the isNew value because it is not a part of your schema and is not listed in the queries selection set. I like to separate the concerns of the server data that is managed by apollo and the front-end application state that lends itself to using redux/flux or even more simply by managing it in your component's state.
Apollo gives you the option to supply your own redux store. You can allow apollo to manage its data fetching logic and then manage your own front-end state alongside it. Here is a write up discussing how you can do this: http://dev.apollodata.com/react/redux.html.
If you are using React, you might be able to use component lifecycle hooks to detect when new comments appear. This might be a bit of a hack but you could use componentWillReceiveProps to compare the new list of comments with the old list of comments, identify which are new, store that in the component state, and then invalidate them after a period of time using setTimeout.
componentWillReceiveProps(newProps) {
// Compute a diff.
const oldCommentIds = new Set(this.props.data.allComments.map(comment => comment.id));
const nextCommentIds = new Set(newProps.data.allComments.map(comment => comment.id));
const newCommentIds = new Set(
[...nextCommentIds].filter(commentId => !oldCommentIds.has(commentId))
);
this.setState({
newCommentIds
});
// invalidate after 1 second
const that = this;
setTimeout(() => {
that.setState({
newCommentIds: new Set()
})
}, 1000);
}
// Then somewhere in your render function have something like this.
render() {
...
{
this.props.data.allComments.map(comment => {
const isNew = this.state.newCommentIds.has(comment.id);
return <CommentComponent isNew={isNew} comment={comment} />
})
}
...
}
The code above was right off the cuff so you might need to play around a bit. Hope this helps :)

Categories