Auto-fill created_by field

Hi, new to strapi and trying to get a “feel” for the framework before deciding whether to go for it or not.

I am using the “Users & permissions” plugin to allow users to login to my app and call various endpoints. The problem is that the created_by and updated_by fields are not automatically populated with the authenticated (via JWT through Autherntication header) user id

I know that when i created something from the actual CMS (/admin) created_by and updated_by will be populated by the CMS user, but i also want to have these fields, preferably automatically, populated by e.g. ctx.state.user.id for end-users that create entities

Is there any way to configure this, e.g. pass something to strapi.services.entity.create() that would also set created_by and updated_by respectively - or am i forced to do this for every route that wants this behavior? I know i can manually add e.g. a “created_by” relation to the users table, but i’d rather reuse created_by/updated_by to avoid confusion and added complexity

Thanks

Of course, right after posting this i found a post touching on the same subject. https://github.com/strapi/strapi/discussions/6871

I see the comment made by giacomocerquone 10 days ago, but i would like to avoid having to do manual labor in my controllers unless i really have to. I’d prefer to always have the state be passed to the hook, or have some other kind of middleware that allows me to set these fields myself. As long as i can stay DRY i’m happy.

@linde12 thank you for posting on the forum, we are planning to get rid of GH discussions in early 2021 so at this point I’m going to “migrate” the content of this discussion to this thread.

1 Like

Content migrated from: https://github.com/strapi/strapi/discussions/6871


Original Post

eporroa

I’ve a requirement to store the user id from multiple content types and was looking for a way to use lifecycle hooks: afterCreate , afterUpdate , afterDelete .
Looks like the lifecycle hook arguments only returns data from the specific model (entity and request params) but is there a way to access ctx.state.user from these functions?

I’ll appreciate all feedback! … Thanks!


Comments - Thread 1

derrickmehaffy

Collaborator

@alexandrebodin is ctx no longer passed to lifecycle callbacks? (I know it was previously before the changes made in stable).

If not, was there a specific reason we aren’t passing the full ctx over, or is it just not documented?

eporroa

Author

I even tried this:

    async afterCreate(result, data) {
      strapi.app.use(async ctx => {
        ctx; // is the Context
        console.log("ctx.request", ctx.request);
        console.log("ctx.response", ctx.response);
      });
      console.log(strapi.services.history.ACTIONS.CREATED);
      console.log('afterCreate', result, data);
    },

With no luck, is there any way to get the ctx object inside a hook?

alexandrebodin

Maintainer

Hi, the ctx is on the HTTP JSON API layer it is not possible to pass it down to the DB lifecycle layer automatically. The db layer can be called from anywhere and is not related to any current query.

What you can do is extend your controllers to update the input data with the ctx.state.user if you wish to.

FYI @derrickmehaffy it wasn’t in the previous lifecycles we just changed :slight_smile:

derrickmehaffy

Collaborator

I’m thinking we should at least pass down the state though like ctx.state.user as this is going to be a fairly common usecase I think (considering that lifecycles are called not only via the generated routes but also the admin) assuming that the admin also has a ctx.state.user which I have never really looked at.

derrickmehaffy

Collaborator

Looking through the old docs from beta.19.5 I guess the other options were never really clearly defined as to what they did beforeCreate: async (model, attrs, options)

alexandrebodin

Maintainer

DB lifecycles will not get request context. this should be handled by a layer an top of those lifecycles like a request lifecycle that will set those informations before calling the DB layer.

derrickmehaffy

Collaborator

DB lifecycles will not get request context. this should be handled by a layer an top of those lifecycles like a request lifecycle that will set those informations before calling the DB layer.

Are you hinting towards controller lifecycles the main reason I suppose why I try to avoid directing people to controller modification is for updates. Though not really a larger problem now as it was in the Alpha/Beta days where those would change often. Old habits die hard I suppose.


Comments - Thread 2

darron1217

Contributor

Is there any update on this issue?
For example, I want to send an email after User is created.
But the request should return error when sending email has failed.

derrickmehaffy

Collaborator

We do not have an update currently

darron1217

Contributor

I solved it by creating custom controller method.
Thank you anyway


Comments - Thread 3

giacomocerquone

This is a “dupe” of https://github.com/strapi/strapi/issues/3309

Honestly, with strapi many times you find yourself reimplementing controllers with few lines of code and therefore I don’t see any problem with this issue in particular honestly.
Just replace your create and then call strapi.services.model.create({ userObj }) passing in whatever object you want to have available in your lifecycle hook.

A slightly extended snippet:

module.exports = {
  async create(ctx) {
    const body = ctx.request.body;
    const user = ctx.state.user;

    if (!user) {
      return ctx.badRequest(null, [
        { messages: [{ id: "No authorization header was found" }] },
      ]);
    }

    const entity = await strapi.services.note.create({
      description: body.description,
      user: user,
    });

    return sanitizeEntity(entity, { model: strapi.models.note });
  },
};

and then in the model


module.exports = {
  lifecycles: {
    async beforeCreate(data) {
      console.log(data.userObj);
    }
  },
};

linde12

Just encountered a similar problem (where i want to auto-fill created_by and updated_by with end-user ids via the “Users & permissions” plugin)

I guess this is the best solution, but with this i can’t stay DRY, instead i have to implement each controller (even if they’re just plain CRUD controllers) and always pass ctx.state.user manually.

Maybe instead of using a global strapi.services.note.create we would have to have some sort of services-object which contains functions that are bound to the ctx.state passed to the actual controller, e.g.

async create(ctx, services) {
  services.note.create(ctx.request.body)
  ...
}

Maybe some sort of controller middleware could do this by leveraging the Proxy API, so that when calling services.note.create the state would be passed automatically as a second parameter to strapi.services.note.create

I realize that this is quite the large change, and there are probably better ways to do it, but currently it seems very inflexible :frowning:

derrickmehaffy

Collaborator

@alexandrebodin @Convly this was what I mentioned as well regarding some of the user feedback I’ve seen in slack. When we added RBAC we assume that all content is created in the admin, but in many users cases it could be coming from the user APIs.

Everyone else, I’ve created an actual feature request here: #8193
This is a topic we have discussed internally as well and quite a few of us internally agree about the topic. (Passing ctx into the lifecycles)

Related image of a sample we are planning in the new request layer I think will fit the role of what you guys are looking for, just note this isn’t set in stone:

image

1 Like

Sorry to post this here, but the question arises and maybe you have the solution (I’m new to Strapi). This is the reason why when using POST (with a new admin user) and later GET to consult the item created, those fields (created_by, updated_by) always appear in null? Could you tell me what the solution could be to obtain the user who created items?. Thanks.

Those fields are only populated if the entry was created in the Admin panel (it’s a relation to the strapi_administrators model that comes from the strapi-admin package). For REST/GraphQL you’ll need to add your own fields, and likewise probably customize the controller (or create a route policy) to populate them.

I am trying to find an easy way to automatically set the author of an article and I am finding this very difficult. All the solutions for editing customer controllers only work when a user is consuming the API, not using the admin panel. When a user creates a new article through the admin panel, how can I automatically set the author field? if you can give a demonstration of the files requiring editing and the code required I would really appreciate it. I am somewhat new. Thank you.