Filtering results at single object endpoint

System Information
  • Strapi Version: 4.10.something
  • Operating System: node:16-bullseye docker image
  • Database: postgres
  • Node Version: 16.latest
  • NPM Version:
  • Yarn Version:

So I’m trying to limit what some endpoints output and I managed to do filtering based on some examples pretty well:

const contentType = "api::address.address";
module.exports = createCoreController(contentType,
  ({
     strapi
   }) => ({
    async find(ctx) {
      const { user } = ctx.state;
      const {
        filters
      } = ctx.query;
      if (user && typeof user !== 'undefined') {
        ctx.query = {
          ...ctx.query,
          filters: {
            ...filters,
            owner: {
              id: user.id,
            }
          }
        };
        return await super.find(ctx);
      } else {
        const sanitizedResults = await this.sanitizeOutput([], ctx);
        return this.transformResponse(sanitizedResults, {});
      }
    },

but the single endpoint does not work as well, when I try to do filtering:

    async findOne(ctx) {
      const { user } = ctx.state;
      const { id } = ctx.params;
      const { query } = ctx;
      if (user && typeof user !== 'undefined') {
        const entity = await strapi.service(contentType).findOne(id, { where: { owner: user.id }, populate: { owner: true }, ...query });
        const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
        return this.transformResponse(sanitizedEntity);
      } else {
        const sanitizedResults = await this.sanitizeOutput({}, ctx);
        return this.transformResponse(sanitizedResults, {});
      }
    },

It seems where portion of the query has no effect past the entered ID. So I’m limited to populating the owner and checking if the ID matches in the code. Yes. I can do that, but it’s more efficient to do it on the database level, and I’m wondering why that part does not work. Am I doing something wrong?

I guess (based on [v4] Filters in `findOne` controller doesn't work · Issue #12208 · strapi/strapi · GitHub) the findOne just does not support filtering.

My current solution was to do this:


    async findOne(ctx) {
      const { user } = ctx.state;
      const { id } = ctx.params;
      if (user && typeof user !== 'undefined') {
        const entity = await strapi.db.query(contentType).findMany({
          where: {
            id,
            owner: user.id
          }
        });
        const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
        return this.transformResponse(sanitizedEntity);
      } else {
        const sanitizedResults = await this.sanitizeOutput({}, ctx);
        return this.transformResponse(sanitizedResults, {});
      }
    },

But that is not ideal either. I would rather return 404 response in the else part, but I’m not sure how to do that exactly.

I guess, I got the result I wanted:

const { createCoreController } = require("@strapi/strapi").factories;

const utils = require('@strapi/utils');
const { NotFoundError, UnauthorizedError } = utils.errors;

const contentType = "api::address.address";
module.exports = createCoreController(contentType,
  ({
     strapi
   }) => ({
    async findOne(ctx) {
      const { user } = ctx.state;
      const { id } = ctx.params;
      if (user && typeof user !== 'undefined') {
        const entity = await strapi.db.query(contentType).findMany({
          where: {
            id,
            owner: user.id
          }
        });
        if (entity && entity.length) {
          const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
          return this.transformResponse(sanitizedEntity);
        }
        throw new NotFoundError('Not Found');
      } else {
        throw new UnauthorizedError('Access Forbidden');
      }
    },
  }));

I did try doing the same thing with policies, but that did not work quite as I expected. I created this policy:

'use strict';

/**
 * `user-is-owner` policy.
 */

module.exports = () => {
  // Add authenticated user to filter.
  return async (ctx, next) => {
    ctx.query.filters = {
      ...ctx.query.filters,
      owner: ctx.state.user.id,
    };

    await next();
  };
};

Added it to routes

const { createCoreRouter } = require('@strapi/strapi').factories;

module.exports = createCoreRouter("api::address.address", {
  config: {
    findOne: {
      policies: ["global::user-is-owner"]
    }
  }
});

But then even the places my user was owner started raising 403 errors… Not sure what was going on there.