Unable to call lifecycle methods in Strapi models? - strapi

I have a Strapi project with a MongoDB database and a simple Post model. This model has, among others, a slug field with the following attributes:
type: string,
unique: true,
required: true
For testing purposes, I am attempting to modify this field's value before committing it to the DB, via one of Strapi's lifecycle methods:
module.exports = {
// Before saving a value.
// Fired before an `insert` or `update` query.
beforeSave: async (model) => {
// Set the password.
const newslug = model.slug + '-test';
model.slug = newslug;
},
};
But the method just doesn't seem to get fired as expected when I save a post on my admin page. The post, along with its slug, gets upserted to the DB without the modification shown in the code above. Am I misunderstanding the functionality?

If you are using NoSQL database (Mongo)
beforeSave: async (model) => {
if (model.content) {
model.wordCount = model.content.length;
}
},
beforeUpdate: async (model) => {
if (model.getUpdate().content) {
model.update({
wordCount: model.getUpdate().content.length
});
}
},
If you are using SQL (SQLite, Postgres, MySQL)
beforeSave: async (model, attrs, options) => {
if (attrs.content) {
attrs.wordCount = attrs.content.length;
}
},

Related

Implement cursor based pagination using pothos and prisma

Is there any way to implement cursor-based pagination using the pothos Prisma plugin, I'm referring to this plugin .but there is no clear example of how to do the pagination. The documentation is somewhat difficult to understand. this is the Prisma model I want for the pagination
Prisma Model
model user {
id String #id #map("_id")
factories factory[]
##map("user")
}
Pothos Model
builder.prismaObject("user", {
fields: (t) => ({
id: t.exposeID("id"),
factories:t.relation("factories")
}),
});
I figured out the way to do the cursor-based pagination using pothos. you just have to add the prismaConnection to the query field
builder.queryField("users", (t) =>
t.prismaConnection({
type: "user",
cursor: "id",
resolve: async (query, _root, _args, _ctx, _infu) => {
// add the relevant cursors-based Prisma pagination logic
// here.you can refer to the Prisma
// doc for more info
return await db.user.findMany({ ...query });
},
})
);
this will automatically add all the cursours,edges,nodes and pageInfo Objects to the response

Query on page not refetching once navigating back to page

In my nextjs page I have the following hook (generated by using graphql-codegen) that fetches a graphql query.
const { data, error, loading, fetchMore, refetch, variables } = useGetShortlistQuery({
notifyOnNetworkStatusChange: true, // updates loading value
defaultOptions: {
variables: {
offset: undefined,
filterBy: undefined,
sortBy: SortBy.RecentlyAdded,
sortDirection: SortDirection.Desc,
},
},
});
This is the useGetShortlistQuery hook that is generated by graphql-codegen
export function useGetShortlistQuery(
baseOptions?: Apollo.QueryHookOptions<GetShortlistQuery, GetShortlistQueryVariables>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetShortlistQuery, GetShortlistQueryVariables>(GetShortlistDocument, options);
}
my component is wrapped in a HOC to enable Apollo Client
export default withApollo({ ssr: true })(Index);
The withApollo HOC uses #apollo/client and the cache property of the apollo client is as follows.
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
getShortlist: {
keyArgs: [],
merge(existing: PaginatedProperties | undefined, incoming: PaginatedProperties): PaginatedProperties {
return {
...incoming,
properties: [...(existing?.properties || []), ...(incoming?.properties || [])],
};
},
},
},
},
},
}),
The problem I am having is that on this page I update the variables on the useGetShortlistQuery using refetch which, in turn, updates the data.
However, if I navigate to another page, then come back to this page using this component. It doesn't seem to retrigger the graphql query so returns the previous data.
If you are using getStaticProps (or getServerSideProps) with pre rendered pages, it is a known behavior. It is due to optimisation by Next.js not re-rendering components between page navigations, with pages like [id].js.
The trick is to have a key on components that you want to see refreshing. You have multiple ways to do so. Having a different key on components tells React that it should be re-rendering the components, and thus will trigger again the hooks.
Practical example:
export const getStaticProps: GetStaticProps = async ({ params }) => {
const data = getData() //something that fetches your data here
return {
props: {
// some other data you want to return...
// some unique value that will be on each page
key: data.key
},
}
}
const MyPage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (props) => {
<div key={props.key} />
}

How to organize GraphQL resolver for additional fields

Let's say I have a simple GraphQL type for a user:
type User {
id: ID!
name: String!
}
Query {
user(id:ID!)
}
and a resolver
user = (_, {id}, {api})=> api.getUser(id)
Now I have add a new field to the User called friends and added a new resolver for the User.friends field.
friends = ({id}, _, {api})=> api.getFriends(id)
So now I wonder when we made a query like this, how can I prevent the call to api.getUser but only call api.getFriends.
query {
user(id){
friends {
name
}
}
}
My understanding is that having a resolver defined for the user field in the Query type, it will always call this resolver first and after that all resolvers for fields in the User type.
This is a common problem and there is for example this solution out there: https://github.com/gajus/graphql-lazyloader
Check out the README of the project for a structured description of your problem.
Alternatively, you can implement your own class that contains a cached value making use of how GraphQL.js implements default resolvers:
class User {
constructor(id) {
this.id = id;
}
getInstance({ api }) {
if (!this.instance) {
this.instance = api.getUser(this.id);
}
return this.instance;
}
// notice how id is already a property of this class
name(args, ctx) {
return this.getInstance(ctx).then(instance => instance.name);
}
// do the same for other fields, user will only be fetched once.
friends(args, { api }) {
return api.getFriends(this.id);
}
}
const resolvers = {
Query: {
user: (args) => new User(args.id),
}
}
If you use dataloader you can even do this with even less code thanks to caching in dataloader:
// You probably have this function already somewhere in your apollo server creation
function createContext({ api }) {
return {
api,
loaders: {
user: new Dataloader((ids) => ids.map(id => api.getUser(id))),
},
}
}
const resolvers = {
Query: {
user: (parent, args) => ({ id: args.id }),
},
User: {
name: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.name),
otherProp: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.otherProp),
friends: ({ id }, args, { api })=> api.getFriends(id),
}
}
Dataloader will, even when called twice, only reach to the API once. An added benefit is, that it will cache the value. Ideally, you even provide a batch load function in the API to make the loader even more efficient.
Be aware, that user.fields.name now makes calls for every friend to the API. To avoid that, you could check if the property exists:
name: (parent, args, { loaders }) =>
parent.name ?? loaders.user.load(parent.id).then(user => user.name),

async update(params, data, { files } = {}) does not work

my project was created with Strapi 3.0.0-beta.18.6.
But in the service of the content type "Orders" the (new) "update" method does not work, only the (old) "edit" works.
Can someone give me a tip?
// ---------------- "update" does not work :( ------------------
async update(params, data, { files } = {}) {
const query = strapi.query('order');
const entry = await query.update(params, data);
if (files) {
// automatically uploads the files based on the entry and the model
await this.uploadFiles(entry, files, { model: strapi.models.order });
return this.findOne({ id: entry.id });
}
return entry;
},
by the way, query.update(params, data); cannot be executed, the process is canceled, but there is no error message.
// ---------------- old "edit" works ------------------
async edit (params, values) {
// Extract values related to relational data.
const relations = _.pick(values, Order.associations.map(ast => ast.alias));
const data = _.omit(values, Order.associations.map(ast => ast.alias));
// Create entry with no-relational data.
const entry = Order.forge(params).save(data);
// Create relational data and return the entry.
return Order.updateRelations(Object.assign(params, { values: relations }));
},
Thanks in advance!
Strapi generated controllers are based on the shadow core controller function.
If you want to customize the default function you can refer to this documentation : https://strapi.io/documentation/3.0.0-beta.x/concepts/controllers.html#core-controllers
I just tried it and it works fine.

TypeORM - Retrieve data through relation query in resolver

So I'm trying to retrieve elements out of a join relation in an Apollo resolver.
I've got a User table, a Notification table. And a Relation table named:
notification_recipients_user defined by a ManyToMany relation on both entity but hosted on Notification :
//entity/Notification.ts
#ManyToMany(type => User, recipient => recipient.notifications)
#JoinTable()
recipients: User[];
I can create relation without problem through this mutation :
addNotificationForUser: async (_, { id, data }) => {
const user = await User.findOne({ id });
if (user) {
const notification = await Notification.create({
template: data,
recipients: [user]
}).save()
return notification;
} else {
throw new Error("User does not exists!");
}
}
However I'm totally not succeeding in retrieving data for a specific User.
allNotificationsOfUser: (_, { user }, ___) => {
return Notification.find({
where: { userId: user },
relations: ['recipients'],
})
The method find is one of TypeORM native methods.
However I must be doing something wrong because it react as if there wasn't any filter.
Okay so the best way of doing it is by using relation between entity.
So to get Notification you'll go by User.notifications
allUnseenNotificationsOfUser: async (_, { userId }, __) => {
const user = await User.findOne({
relations: ["notifications"],
where: { id: userId }
});
if (user) {
const notifications = user.notifications.filter(notification => !notification.seen)
return notifications;
}
return null;
}
And for the record for anyone stumbeling upon this you can use filter on your result to do a query like resolver.
const notifications = user.notifications.filter(notification => !notification.seen)
It feels tricky but works like a charm.

Resources