Search code examples
mysqlrestapihttp-status-code-403

how to differentiate between 403 and 404 on a DB query


I am working on a REST API. When trying to access a resource : we want to either give a 403 (Forbidden) or 404(Not Found) error. Our tables are :

CREATE TABLE `Action` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `created_By_Id` int(10) unsigned NOT NULL,
      `name` varchar(60) NOT NULL,
      `updated_action_at` datetime(3) DEFAULT NULL,
      `created_At` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `notes` varchar(400) DEFAULT NULL,
       PRIMARY KEY (`id`),
       KEY `action_empId_fk` (`created_By_Id`),
       CONSTRAINT `action_empId_fk` FOREIGN KEY (`created_By_Id`)
       REFERENCES `Employee` (`id`) ON DELETE CASCADE,
       ) ENGINE=InnoDB AUTO_INCREMENT=502004 DEFAULT CHARSET=latin1


CREATE TABLE `ActionAssignedTo` (
    `action_Id` int(10) unsigned DEFAULT NULL,
    `assignee_Id` int(10) unsigned DEFAULT NULL,
     KEY `actionassignedto_emp_id_foreign` (`emp_Id`),
     KEY `actionassignedto_action_id_foreign` (`action_Id`),
     CONSTRAINT `ActionAssignedTo_ibfk_1` FOREIGN KEY (`assignee_Id`) 
     REFERENCES `Employee` (`id`) ON DELETE CASCADE,
     CONSTRAINT `ActionAssignedTo_ibfk_2` FOREIGN KEY (`action_Id`) 
     REFERENCES `Action` (`id`) ON DELETE CASCADE
     ) ENGINE=InnoDB DEFAULT CHARSET=latin1

CREATE TABLE `Employee` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `vendor_Id` int(10) unsigned DEFAULT NULL,
    `name` varchar(40) NOT NULL,
    `mobile_Number` varchar(15) NOT NULL,
    `active` tinyint(1) DEFAULT '1',
    `updated_At` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `created_At` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `employee_vendor_id_foreign` (`vendor_Id`),
    CONSTRAINT `employee_vendor_id_foreign` FOREIGN KEY (`vendor_Id`)
    REFERENCES `Vendor` (`vendor_Id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=511 DEFAULT CHARSET=latin1

We are running a query to fetch an Action with id: 17 by an creator employee with id: 9 and vendor Id: 1 and this employee has been created the action so he can view it(business rule). Action can be assigned to multiple employees.

select     Action.name,  
           group_concat(AssigneeNameTable.name) as assignedTo, 
           group_concat(AssigneeNameTable.id) as assignedToId, 
           ActionAssignedTo.action_Id as actionId
from       Action
inner join Employee
on         Action.created_By_Id = Employee.id
and        Employee.vendor_Id = 1 
inner join ActionAssignedTo 
on         Action.id = ActionAssignedTo.action_Id 
and        ActionAssignedTo.action_Id = 17 
inner join Employee as AssigneeNameTable 
on         ActionAssignedTo.assignee_Id = AssigneeNameTable.Id 
where      Action.created_By_Id = 9 
and        Action.deleted_at is null 
group by   Action.id 
limit       2

Now, lets say if the Action does not exist at all in the DB --> in that case the above query returns empty result set

the problem is we can not differentiate the query return empty set because 

1. either the action with id:17 did not exist(404- Not Found) 

2. or the business rule failed (as in the person requested the action was not    
at all related to the action(403 - Forbidden).

One of the solution that I can think of is : First Run a Small query like :

select * from Action where id = 17

if this query returns an empty set that means the action does not exist in the db.

After this I run the Bigger query

Different combinations of result set (the number in the array indicates records returned) :

Small Query | Big Query  | Interpretation
---------------------------------------
[0]         | [0]        | Resource Not Found(404)
[1]         | [0]        | Forbidden (403)

If the Small Query returns 0 result --> we can directly send 404 Error; else we execute the Big Query.


Solution

  • I used the concept of Left Outer Join as my friend suggested. Please find the new query below :

    select      *
    from
    
    (select     id
     from       Action
     where      id = 17) AS act1
    
    left Outer Join
    
    (select    Action.name,  
               group_concat(AssigneeNameTable.name) as assignedTo, 
               group_concat(AssigneeNameTable.id) as assignedToId, 
               ActionAssignedTo.action_Id as actionId
    from       Action
    inner join Employee
    on         Action.created_By_Id = Employee.id
    and        Employee.vendor_Id = 1 
    inner join ActionAssignedTo 
    on         Action.id = ActionAssignedTo.action_Id 
    and        ActionAssignedTo.action_Id = 17 
    inner join Employee as AssigneeNameTable 
    on         ActionAssignedTo.assignee_Id = AssigneeNameTable.Id 
    where      Action.created_By_Id = 9 
    and        Action.deleted_at is null 
    group by   Action.id 
    limit      2) AS act2
    
    on          act1.id = act2.actionId
    

    The concept is simple

    • if the output contains no result --> Object Not Found(404)

    • if the output contains the id field but does not contain any single field from second sub-query that means the entity exists in db but business rules does not permit and hence Forbidden(403).