<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>boringSQL | Supercharge your SQL &amp; PostgreSQL powers - postgrest</title>
    <subtitle>Learn practical SQL &amp; PostgreSQL techniques. Build rock-solid data systems with &#x27;boring&#x27; database solutions that deliver reliability without the drama.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://boringsql.com/tags/postgrest/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://boringsql.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2024-06-06T00:00:00+00:00</updated>
    <id>https://boringsql.com/tags/postgrest/atom.xml</id>
    <entry xml:lang="en">
        <title>Deep Dive into PostgREST - Time Off Manager (Part 3)</title>
        <published>2024-06-06T00:00:00+00:00</published>
        <updated>2024-06-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Radim Marek
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://boringsql.com/posts/postgrest-tutorial-part3/"/>
        <id>https://boringsql.com/posts/postgrest-tutorial-part3/</id>
        
        <content type="html" xml:base="https://boringsql.com/posts/postgrest-tutorial-part3/">&lt;p&gt;This is the third and final instalment of &quot;Deep Dive into PostgREST&quot;. In the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;boringsql.com&#x2F;posts&#x2F;postgrest-tutorial-part1&#x2F;&quot;&gt;first part&lt;&#x2F;a&gt;, we explored basic CRUD functionalities. In the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;boringsql.com&#x2F;posts&#x2F;postgrest-tutorial-part2&#x2F;&quot;&gt;second part&lt;&#x2F;a&gt;, we moved forward with abstraction and used the acquired knowledge to create a simple request&#x2F;approval workflow.&lt;&#x2F;p&gt;
&lt;p&gt;In Part 3, we will explore authentication and authorisation options to finish something that might resemble a real-world application.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;authentication-with-pgcrypto&quot;&gt;Authentication with pgcrypto&lt;a class=&quot;zola-anchor&quot; href=&quot;#authentication-with-pgcrypto&quot; aria-label=&quot;Anchor link for: authentication-with-pgcrypto&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s no authorisation without knowing user identity. So let&#x27;s start there. Our users table from the first part had an &lt;code&gt;email&lt;&#x2F;code&gt; to establish the identity, but no way to verify it. We will address this by adding a password column. Of course, nobody in their right mind would store passwords in plain text.&lt;&#x2F;p&gt;
&lt;p&gt;To securely store users&#x27; passwords, we are going to utilise PostgreSQL &lt;code&gt;pgcrypto&lt;&#x2F;code&gt; extension (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;pgcrypto.html&quot;&gt;documentation&lt;&#x2F;a&gt;). This built-in extension provides a suite of cryptographic functions for hashing, encryption, and more. In our case, we will leverage &lt;code&gt;crypt&lt;&#x2F;code&gt; with the &lt;code&gt;gen_salt&lt;&#x2F;code&gt; function to generate password hash using bcrypt algorithm.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s start with loading the extension and adding password column:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE&lt;&#x2F;span&gt;&lt;span&gt; EXTENSION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IF NOT EXISTS&lt;&#x2F;span&gt;&lt;span&gt; pgcrypto;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER TABLE&lt;&#x2F;span&gt;&lt;span&gt; users &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ADD&lt;&#x2F;span&gt;&lt;span&gt; COLUMN password_hash &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TEXT NOT NULL DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; gen_salt(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;bf&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;); &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The new column &lt;code&gt;password_hash&lt;&#x2F;code&gt; will accommodate the variable-length bcrypt hash, while default function &lt;code&gt;gen_salt(&#x27;bf&#x27;)&lt;&#x2F;code&gt; creates a unique bcrypt salt for every user.&lt;&#x2F;p&gt;
&lt;p&gt;Now that our table structure is set, let&#x27;s see how we can securely set and verify passwords using pgcrypto.&lt;&#x2F;p&gt;
&lt;p&gt;When a user sets or changes their password, we&#x27;ll hash it using crypt before storing it in the password_hash column.Here&#x27;s how:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;UPDATE users SET password_hash = crypt(&amp;#39;new_password&amp;#39;, gen_salt(&amp;#39;bf&amp;#39;)) WHERE email = &amp;#39;user@example.com&amp;#39;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and during login, we will verify the hash of the password the user enters with the stored password_hash:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; users &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; email &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;user@example.com&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span&gt; password_hash &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; crypt(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;entered_password&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, password_hash);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;jwt-authentication&quot;&gt;JWT Authentication&lt;a class=&quot;zola-anchor&quot; href=&quot;#jwt-authentication&quot; aria-label=&quot;Anchor link for: jwt-authentication&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;With the ability to securely verify users&#x27; identity, let&#x27;s move to the next step and build stateless authentication. The de-facto standard is JSON Web Tokens (JWTs). Represented by digitally signed information, they contain claims about user identity. The digital signature ensures the token&#x27;s integrity and verifies that it hasn&#x27;t been tampered with. You can find more in official &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jwt.io&#x2F;introduction&quot;&gt;Introduction to JSON Web Tokens&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;While PostgreSQL doesn&#x27;t have built-in JWT support, we will have to either rely on &lt;code&gt;pgjwt&lt;&#x2F;code&gt; extension (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;michelp&#x2F;pgjwt&quot;&gt;GitHub repo&lt;&#x2F;a&gt;)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CREATE EXTENSION IF NOT EXISTS pgjwt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or you can re-create the logic by including &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;michelp&#x2F;pgjwt&#x2F;blob&#x2F;master&#x2F;pgjwt--0.2.0.sql&quot;&gt;PL&#x2F;pgSQL that comes with it&lt;&#x2F;a&gt; (please, make sure you replace&#x2F;remove the &lt;code&gt;@extschema@&lt;&#x2F;code&gt; to match the schema you are using). In this article we will use schema &lt;code&gt;jwt&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To make the token signature, we need to re-configure PostgREST to decode JWT tokens and configure the secret inside the database (to be able to use it in our code). For security reasons, the key must be at least 32 characters long. You can either use your own method to generate (for example, your password manager) or add it using the following CLI commands&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;export&lt;&#x2F;span&gt;&lt;span&gt; LC_CTYPE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;C&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;jwt-secret = &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span&gt;LC_ALL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;C&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; tr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -dc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;A-Za-z0-9&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&#x2F;dev&#x2F;urandom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -c32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; &amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; postgrest.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And use the generated secret for the database level configuration parameter, using&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DATABASE&lt;&#x2F;span&gt;&lt;span&gt; time_off_manager &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SET&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;pgrst.jwt_secret&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;your-jwt-secret-generated-above1&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Please, make sure the database name is updated accordingly to set it correctly. And, I cannot stress it enough, &lt;strong&gt;make sure the secret is really 32 characters&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-web-user-role&quot;&gt;The web user role&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-web-user-role&quot; aria-label=&quot;Anchor link for: the-web-user-role&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;In previous parts of this guide, we have worked with two user roles. The first, in PostgREST terminology, called authenticator, is the role used in the &lt;code&gt;db-uri&lt;&#x2F;code&gt; parameter. It&#x27;s the role used to access the database and its job is to impersonate other users based on the authentication (or lack thereof) of the HTTP requests. In a real production application, this role should be configured to have limited access.&lt;&#x2F;p&gt;
&lt;p&gt;The second role, called anonymous, is used in &lt;code&gt;db-anon-role&lt;&#x2F;code&gt; parameter. This is the role impersonated for all unauthenticated HTTP requests.&lt;&#x2F;p&gt;
&lt;p&gt;The third role, or roles, representing authenticated web users. In our JWT tokens, we will use one specifically designed for PostgREST.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	&amp;quot;role&amp;quot;: &amp;quot;time_off_user&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When JWT is successfully validated, with a role claim, PostgREST will switch to the database role with the provided name for the duration of the HTTP request. While PostgREST is quite flexible, we will limit the impersonated role for authenticated requests to a single hard-coded role. For our application, it&#x27;s going to be called &lt;code&gt;time_off_user&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;let-s-generate-some-jwts&quot;&gt;Let&#x27;s generate some JWTs&lt;a class=&quot;zola-anchor&quot; href=&quot;#let-s-generate-some-jwts&quot; aria-label=&quot;Anchor link for: let-s-generate-some-jwts&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The first step is to implement logic to create a JWT token asserting all &lt;strong&gt;claims&lt;&#x2F;strong&gt; we need for our application - in the case of Time Off Manager, we will rely on user_id and role. As discussed in the previous section, we will use the hard-coded role &lt;code&gt;time_off_user&lt;&#x2F;code&gt;. Let&#x27;s create the role and grant our authenticator the permissions.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE ROLE&lt;&#x2F;span&gt;&lt;span&gt; time_off_user NOLOGIN;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT&lt;&#x2F;span&gt;&lt;span&gt; time_off_user &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_manager;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now create a function, users can call directly to verify the identity and generate the JWT token.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plsql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE&lt;&#x2F;span&gt;&lt;span&gt; FUNCTION api.login(email text, password text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  RETURNS text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  SECURITY DEFINER&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $function$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  user_record public.users;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  SELECT * INTO&lt;&#x2F;span&gt;&lt;span&gt; user_record&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  FROM&lt;&#x2F;span&gt;&lt;span&gt; public.users&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  WHERE&lt;&#x2F;span&gt;&lt;span&gt; users.email &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; login.email;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  IF&lt;&#x2F;span&gt;&lt;span&gt; user_record.password_hash &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; crypt(password, user_record.password_hash) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;	RETURN&lt;&#x2F;span&gt;&lt;span&gt; create_jwt(user_record.user_id, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;user_id&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  ELSE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	RAISE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; EXCEPTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;Invalid email or password&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$function$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Missing there is helper &lt;code&gt;create_jwt&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plsql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE&lt;&#x2F;span&gt;&lt;span&gt; FUNCTION public.create_jwt(user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span&gt;, role text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; RETURNS text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $function$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  payload JSON;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  payload &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;:=&lt;&#x2F;span&gt;&lt;span&gt; json_build_object(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;#39;user_id&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, user_id,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;#39;role&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;time_off_user&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;    &amp;#39;exp&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;extract&lt;&#x2F;span&gt;&lt;span&gt;(epoch &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; now()) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; -- 1-hour expiration&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  );&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;  RETURN&lt;&#x2F;span&gt;&lt;span&gt; jwt.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;sign&lt;&#x2F;span&gt;&lt;span&gt;(payload, current_setting(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;pgrst.jwt_secret&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;));  &lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;-- Use configuration value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$function$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Restart the PostgREST instance to apply the changes, and you can try to generate a token using cURL (assuming you have changed the password as demonstrated in the first section ):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POST &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;rpc&#x2F;login&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;{&amp;quot;email&amp;quot;:&amp;quot;manager2@example.com&amp;quot;, &amp;quot;password&amp;quot;: &amp;quot;new_password&amp;quot;}&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Content-Type: application&#x2F;json&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you set everything as expected you will get a base64 encoded token. To verify&#x2F;debug it, you can try to use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jwt.io&#x2F;&quot;&gt;JWT.io&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;prepare-the-permissions&quot;&gt;Prepare the permissions&lt;a class=&quot;zola-anchor&quot; href=&quot;#prepare-the-permissions&quot; aria-label=&quot;Anchor link for: prepare-the-permissions&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The next step is where things get really interesting. First, we need to clean up excessive permissions. Some obvious, some less so.&lt;&#x2F;p&gt;
&lt;p&gt;First let&#x27;s start with the revoking all the permissions for &lt;code&gt;time_off_anonymous&lt;&#x2F;code&gt; we provided in previous part.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE&lt;&#x2F;span&gt;&lt;span&gt; ALL PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span&gt; ALL TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; ALL FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and same we need to adjust the default privileges&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE SELECT ON&lt;&#x2F;span&gt;&lt;span&gt; TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which is the obvious part. The most likely surprising part is the need to remove &lt;code&gt;EXECUTE&lt;&#x2F;code&gt; permissions for &lt;code&gt;PUBLIC&lt;&#x2F;code&gt;. In PostgreSQL, granting usage on a schema also grants the ability to execute functions to &lt;code&gt;PUBLIC&lt;&#x2F;code&gt;. Unless you revoke these privileges, all users will be able to execute the functions.&lt;&#x2F;p&gt;
&lt;p&gt;For our purposes we want to REVOKE the access for &lt;code&gt;PUBLIC&lt;&#x2F;code&gt; and only grant &lt;code&gt;EXECUTE&lt;&#x2F;code&gt; on function &lt;code&gt;api.login()&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT&lt;&#x2F;span&gt;&lt;span&gt; USAGE &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;-- REVOKE EXECUTE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; PUBLIC;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; ALL FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; PUBLIC;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;-- GRANT EXECUTE &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT EXECUTE ON FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;login&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; to&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And finally, we need to establish the necessary permissions for &lt;code&gt;time_off_user&lt;&#x2F;code&gt;, the role which will be used for authenticated requests.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT ON&lt;&#x2F;span&gt;&lt;span&gt; ALL TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; public &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT ON&lt;&#x2F;span&gt;&lt;span&gt; ALL TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; ALL FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; public &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT ON&lt;&#x2F;span&gt;&lt;span&gt; TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT ON&lt;&#x2F;span&gt;&lt;span&gt; TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_user;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We will now use this to get an overview of only relevant vacation balances for the authenticated user.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;authorization-with-postgrest&quot;&gt;Authorization with PostgREST&lt;a class=&quot;zola-anchor&quot; href=&quot;#authorization-with-postgrest&quot; aria-label=&quot;Anchor link for: authorization-with-postgrest&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;With authentication working, let&#x27;s start with the implementation of fine-grained authorization in PostgREST. As a first step, the goal is to ensure that users only see the vacation balances they are supposed to. I.e. either their own (for the regular employees) or their own and the people the user manages (for the managers).&lt;&#x2F;p&gt;
&lt;p&gt;For this we will need to access JWT token claims, specifically &lt;code&gt;user_id&lt;&#x2F;code&gt;. To avoid relatively complex notation, let&#x27;s setup a helper&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.current_user_id()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; RETURNS integer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INTEGER&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; current_setting(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;request.jwt.claims&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, true)::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;json-&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;user_id&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IS NULL THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;User ID not found in JWT claims&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    RETURN&lt;&#x2F;span&gt;&lt;span&gt; user_id::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;PostgREST seamlessly integrates with PostgreSQL&#x27;s Row Level Security (RLS) to filter data based on user permissions. RLS policies are rules applied at the row level to determine if a user can access a particular row in a table.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER TABLE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_transactions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ENABLE ROW LEVEL SECURITY&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE POLICY&lt;&#x2F;span&gt;&lt;span&gt; select_own_balance &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_transactions&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FOR SELECT USING&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;current_user_id&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; user_id);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE POLICY&lt;&#x2F;span&gt;&lt;span&gt; select_supervised_balance &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_transactions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FOR SELECT USING&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;EXISTS&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; time_off_transactions&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;manager_user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;current_user_id&lt;&#x2F;span&gt;&lt;span&gt;()));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you are (and I do hope so) using PostgreSQL 15 and higher, you need to switch the view from the default security definer to invoker.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER VIEW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;vacation_balances&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; SET&lt;&#x2F;span&gt;&lt;span&gt; (security_invoker &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; true);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This way the view, and the underlying tables will be evaluated using the permissions of the user querying the view, not the view owner. This is the behaviour we want. For versions beyond PostgreSQL 15 and more complex use cases, you might need to implement function-based security instead.&lt;&#x2F;p&gt;
&lt;p&gt;But without further delay, let&#x27;s test &quot;the magic&quot; and (assuming you have authenticated as a manager) you might try to retrieve the vacation balances using cURL&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;vacation_balances&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Authorization: Bearer ${&lt;&#x2F;span&gt;&lt;span&gt;JWT_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;to get a result similar to&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;year&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;user_id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;8&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;total_amount&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;year&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;user_id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;9&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;total_amount&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;year&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;user_id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;total_amount&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;year&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;user_id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;11&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;total_amount&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;year&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;user_id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;12&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;total_amount&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;year&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2024&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;user_id&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;13&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;&amp;quot;total_amount&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;}]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Guess, it&#x27;s important to mention the Row Security Policies which are used are much more complex, and the whole topic would deserve another article. For now, I do recommend you to &lt;del&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;ddl-rowsecurity.html&quot;&gt;consult the documentation&lt;&#x2F;a&gt;&lt;&#x2F;del&gt;, to get more understanding. With the multi-role access you can implement with PostgREST, it might be interesting for you to focus on &lt;code&gt;BYPASSRLS&lt;&#x2F;code&gt; which might be applied to certain role(s).&lt;&#x2F;p&gt;
&lt;p&gt;As mentioned before, Row Level Security is just one way to solve this. The traditional approach would be to use functions to hide the separation logic.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wrapping-up-the-time-off-manager&quot;&gt;Wrapping up the Time Off Manager&lt;a class=&quot;zola-anchor&quot; href=&quot;#wrapping-up-the-time-off-manager&quot; aria-label=&quot;Anchor link for: wrapping-up-the-time-off-manager&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Before we wrap up our tutorial, let&#x27;s finish the core functionality of the Time Off Manager - the approval workflow. Given the basic concepts covered above, this is going to be a good exercise to use all of it.&lt;&#x2F;p&gt;
&lt;p&gt;Similar how we updated &lt;code&gt;time_off_transactions&lt;&#x2F;code&gt;, we will apply Row Level Security to &lt;code&gt;time_off_requests&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER TABLE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_requests&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ENABLE ROW LEVEL SECURITY&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE POLICY&lt;&#x2F;span&gt;&lt;span&gt; select_own_requests &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_requests&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FOR SELECT USING&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;current_user_id&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; user_id); &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE POLICY&lt;&#x2F;span&gt;&lt;span&gt; select_subordinate_requests &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FOR SELECT USING&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;EXISTS&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; time_off_requests&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;manager_user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;current_user_id&lt;&#x2F;span&gt;&lt;span&gt;()));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Don&#x27;t forget to switch the view to &lt;code&gt;security_invoker&lt;&#x2F;code&gt; model.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER VIEW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;pending_requests&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; SET&lt;&#x2F;span&gt;&lt;span&gt; (security_invoker &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; true);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And we wouldn&#x27;t be done with requests creation, if we wouldn&#x27;t update function &lt;code&gt;api.request_time_off&lt;&#x2F;code&gt; to take the advantage of the newly propagated &lt;code&gt;user_id&lt;&#x2F;code&gt; from authentication.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.request_time_off(leave_type &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt; daterange)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS integer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_request_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- original validation logic &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- Ensure the user is requesting for themselves&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;current_user_id&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;User can only request time off for themselves&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- the rest of the function&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The similar update then applies to the &lt;code&gt;api.update_request&lt;&#x2F;code&gt; function to ensure only managers can approve&#x2F;reject time off requests.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.update_request(request_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span&gt;, new_status &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS&lt;&#x2F;span&gt;&lt;span&gt; void&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_requested_user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_request_manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- the original code up to the retrival of the requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;current_user_id&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span&gt; v_request_manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Only the manager can approve or reject this request&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- update the request status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span&gt;$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that&#x27;s about it! Obviously, we can&#x27;t pretend Time Off Manager is anywhere complete, and the real-life application would require much more than what we have covered. But it should provide a good training platform to allow you to write a real API-based backends using just PostgREST.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;Correction 2024-06-11&lt;&#x2F;em&gt;: during the final edits the function &lt;code&gt;public.current_user_id&lt;&#x2F;code&gt; somehow got missing from the Markdown [FIXED].&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Deep Dive into PostgREST - Time Off Manager (Part 2)</title>
        <published>2024-05-18T00:00:00+00:00</published>
        <updated>2024-05-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Radim Marek
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://boringsql.com/posts/postgrest-tutorial-part2/"/>
        <id>https://boringsql.com/posts/postgrest-tutorial-part2/</id>
        
        <content type="html" xml:base="https://boringsql.com/posts/postgrest-tutorial-part2/">&lt;p&gt;Let&#x27;s recap the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;boringsql.com&#x2F;posts&#x2F;postgrest-tutorial-part1&#x2F;&quot;&gt;first part&lt;&#x2F;a&gt; of &quot;Deep Dive into PostgREST,&quot; where we explored the basic functionality to expose and query any table using an API, demonstrated using &lt;code&gt;cURL&lt;&#x2F;code&gt;. All it took was to set up a &lt;code&gt;db-schema&lt;&#x2F;code&gt; and give the &lt;code&gt;db-anon-role&lt;&#x2F;code&gt; some permissions. But unless you are creating the simplest of CRUD applications, this only scratches the surface.&lt;&#x2F;p&gt;
&lt;p&gt;In Part 2, we will expand APIs, provide better abstraction, and implement the foundation of what can be considered business logic, all while extending the sample &quot;Time Off Manager&quot; application. While the previous instalment being introductiory only, make sure you don&#x27;t miss the important details in this one.&lt;&#x2F;p&gt;
&lt;p&gt;Before we move on, let&#x27;s do a bit of housekeeping and clean up the permissions setup from the first part. This way, we can start with a clean slate (when it comes to the permissions) and avoid any lingering rights that could confuse us later.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;REVOKE&lt;&#x2F;span&gt;&lt;span&gt; ALL PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span&gt; ALL TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; public &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;dedicated-schema-for-the-api&quot;&gt;Dedicated schema for the API&lt;a class=&quot;zola-anchor&quot; href=&quot;#dedicated-schema-for-the-api&quot; aria-label=&quot;Anchor link for: dedicated-schema-for-the-api&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Using the &lt;code&gt;public&lt;&#x2F;code&gt; schema (or any schema(s) where your core data model resides) is a fast way to get started. However, using a dedicated schema for the API is beneficial for several reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It provides options for better abstraction, which you might appreciate later when refactoring the original data model.&lt;&#x2F;li&gt;
&lt;li&gt;Data customisation is also a requirement unless you prefer building a &quot;fat&quot; client and thus transferring the majority of the business logic there. Combining data from multiple tables helps shield the consumer from complex queries and relations.&lt;&#x2F;li&gt;
&lt;li&gt;While we won&#x27;t explore it as a security feature, it can also provide relevant boundaries.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s get started with the creation of the schema itself, and expose the users using a view and setting basic permissions. We will do this by setting default permissions, so we don&#x27;t have to repeat the same for all objects as we create them. Please note that default privileges are applied only to new objects created.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE SCHEMA&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT&lt;&#x2F;span&gt;&lt;span&gt; USAGE &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT ON&lt;&#x2F;span&gt;&lt;span&gt; TABLES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ALTER DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; PRIVILEGES &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SCHEMA&lt;&#x2F;span&gt;&lt;span&gt; api &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT EXECUTE ON&lt;&#x2F;span&gt;&lt;span&gt; FUNCTIONS &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ok, let&#x27;s create our first view:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE VIEW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.users &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    m&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; manager_user_id,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    m&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; manager_email,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;created_at&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; u&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    LEFT JOIN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AS&lt;&#x2F;span&gt;&lt;span&gt; m &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;manager_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; m&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;deleted_at&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; is null&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This way, we established the foundation for listing users while providing much richer information about a person&#x27;s manager and preventing potential additional roundtrips between client and API. We also included simple business logic—by omitting the soft-deleted employees.&lt;&#x2F;p&gt;
&lt;p&gt;After creating the views, it&#x27;s important to update the &lt;code&gt;postgrest.conf&lt;&#x2F;code&gt; file to use the new &lt;code&gt;api&lt;&#x2F;code&gt; schema:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db-uri = &amp;quot;postgres:&#x2F;&#x2F;username:password@localhost&#x2F;time_off_manager&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db-schema = &amp;quot;api&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db-anon-role = &amp;quot;time_off_anonymous&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This change tells PostgREST to treat the &lt;code&gt;api&lt;&#x2F;code&gt; schema as the primary interface for API requests, rather than the public schema. Once done, feel free to restart the PostgREST server and test the new setup.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can further extend the example by provisioning a view with a summary of the vacation days available per calendar year (of course, for simplicity we won&#x27;t consider transfers between years, etc.).&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE VIEW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.vacation_balances &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    EXTRACT(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;YEAR FROM&lt;&#x2F;span&gt;&lt;span&gt; transaction_date) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS year&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    SUM&lt;&#x2F;span&gt;&lt;span&gt;(amount) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; total_amount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_transactions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;JOIN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; USING&lt;&#x2F;span&gt;&lt;span&gt; (user_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_types&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; WHERE&lt;&#x2F;span&gt;&lt;span&gt; label &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;vacation&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GROUP BY&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    EXTRACT(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;YEAR FROM&lt;&#x2F;span&gt;&lt;span&gt; transaction_date), user_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While this is nothing groundbreaking, we could (and should) have certainly used views in the first part. So what makes this important now? As mentioned above, the main use cases are abstraction, data access, and security. Additional benefits might include other concerns like derived columns (e.g., full name if we were using first and last name columns), enriching data (e.g., building full URLs from fragments), and other logic specific to the presentation layer.&lt;&#x2F;p&gt;
&lt;p&gt;For practice, we can expose possible time off types via a simple view:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE VIEW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.leave_types &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    label&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_types&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; deleted_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;is null&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;let-s-modify-the-data&quot;&gt;Let&#x27;s modify the data&lt;a class=&quot;zola-anchor&quot; href=&quot;#let-s-modify-the-data&quot; aria-label=&quot;Anchor link for: let-s-modify-the-data&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;While the simplicity of performing CRUD operations on tables in the public schema was straightforward in the first part of the tutorial, using VIEWs introduces certain complexities. One key limitation of VIEWs is their restricted capability for data modification. Let&#x27;s look at how to encapsulate the logic.&lt;&#x2F;p&gt;
&lt;p&gt;You might have guessed it—the most obvious way forward is to introduce a database stored procedure for any business logic we want to expose.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.add_user(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    email &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS integer AS&lt;&#x2F;span&gt;&lt;span&gt; $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_new_user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- check if email already exists&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    PERFORM &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; add_user&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; FOUND &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;The email address % is already in use&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;add_user&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;email&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            USING&lt;&#x2F;span&gt;&lt;span&gt; ERRCODE &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;unique_violation&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- sample business logic: all employees must have manager&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IS NULL THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;manager_id must be provided and cannot be null&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    INSERT INTO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span&gt; (email, manager_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    VALUES&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;add_user&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;add_user&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;manager_id&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    RETURNING user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INTO&lt;&#x2F;span&gt;&lt;span&gt; v_new_user_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    RETURN&lt;&#x2F;span&gt;&lt;span&gt; v_new_user_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ok, we have introduced a rather complex piece of logic there. While it might look simple (if you&#x27;ve ever seen a PL&#x2F;pgSQL function), it comes with a couple of important details.&lt;&#x2F;p&gt;
&lt;p&gt;First and foremost, the function &lt;code&gt;api.add_user&lt;&#x2F;code&gt; presents an example of how to implement business logic. In this case, it&#x27;s providing a custom error message should the employee&#x27;s email already exist and enforcing the logic that &lt;code&gt;manager_id&lt;&#x2F;code&gt; must be provided. You can probably think of more cases to enrich data.&lt;&#x2F;p&gt;
&lt;p&gt;Now for the more difficult part—you might not be familiar with the &lt;code&gt;SECURITY&lt;&#x2F;code&gt; attribute. Every function created can have two modes, &lt;code&gt;INVOKER&lt;&#x2F;code&gt; (which is the default) and &lt;code&gt;DEFINER&lt;&#x2F;code&gt;. If you look above, when we removed all the permissions for our &lt;code&gt;db-anon-role&lt;&#x2F;code&gt; from schema &lt;code&gt;public&lt;&#x2F;code&gt;, the user lost privileges to perform any operations there. Without modifying the security attribute, the function would result in an error message &lt;code&gt;permission denied for table users&lt;&#x2F;code&gt;. Using &lt;code&gt;SECURITY DEFINER&lt;&#x2F;code&gt; guarantees your application user&#x27;s (the one used for the creating the view) permissions will be used when calling the function.&lt;&#x2F;p&gt;
&lt;p&gt;This is the opposite of how VIEWs work in this example, as they come with the default option &lt;code&gt;security_invoker&lt;&#x2F;code&gt; disabled by default. You can refer to the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;sql-createview.html&quot;&gt;documentation&lt;&#x2F;a&gt; to learn more.&lt;&#x2F;p&gt;
&lt;p&gt;If you are not familiar with PL&#x2F;pgSQL, I recommend you check the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;plpgsql-statements.html&quot;&gt;basic statements&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;With the function in place, let&#x27;s call it using cURL:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;rpc&#x2F;add_user&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POST&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;{ &amp;quot;email&amp;quot;: &amp;quot;admin2@example.com&amp;quot;, &amp;quot;manager_id&amp;quot;: 2 }&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Content-Type: application&#x2F;json&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the second time we need to deconstruct the seemingly simple logic. Let&#x27;s start with the URL. The &lt;code&gt;&#x2F;rpc&#x2F;&lt;&#x2F;code&gt; prefix is important. Every stored procedure in the schema defined using &lt;code&gt;db-schema&lt;&#x2F;code&gt; is accessible under this prefix. The prefix helps to differentiate object types, as in PostgreSQL you are allowed to have the same name for a table&#x2F;view and a function.&lt;&#x2F;p&gt;
&lt;p&gt;The second part is the request method, in this case &lt;code&gt;POST&lt;&#x2F;code&gt;. While you can use both &lt;code&gt;GET&lt;&#x2F;code&gt; and &lt;code&gt;POST&lt;&#x2F;code&gt;, it&#x27;s important to understand the implications. Using the &lt;code&gt;GET&lt;&#x2F;code&gt; method sets the PostgREST transaction to read-only mode and is a powerful security measure—explicitly preventing the operation from performing any modification of the data (even indirectly via functions or triggers). Should you perform the function using &lt;code&gt;GET&lt;&#x2F;code&gt;, you would get a &lt;code&gt;cannot execute INSERT in a read-only transaction&lt;&#x2F;code&gt; error message to confirm it.&lt;&#x2F;p&gt;
&lt;p&gt;The third part of the call is the way the data is presented. In this example, it&#x27;s using JSON (declared as &lt;code&gt;Content-Type: application&#x2F;json&lt;&#x2F;code&gt; header) and passed within the request body. If required, you can choose other formats—like &lt;code&gt;application&#x2F;x-www-form-urlencoded&lt;&#x2F;code&gt;, &lt;code&gt;text&#x2F;xml&lt;&#x2F;code&gt;, &lt;code&gt;application&#x2F;octet-stream&lt;&#x2F;code&gt; for &lt;code&gt;bytea&lt;&#x2F;code&gt;, and more using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;postgrest.org&#x2F;en&#x2F;latest&#x2F;api.html#custom-media-type-handlers&quot;&gt;custom media type handlers&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Therefore, the same can be achieved using:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;rpc&#x2F;add_user&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POST&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 	-d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;email=admin2%40example.com&amp;amp;manager_id=2&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Content-Type: application&#x2F;x-www-form-urlencoded&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s omit the XML example completely [sic].&lt;&#x2F;p&gt;
&lt;p&gt;As we have introduced the function that modifies the data, we should explore the option to do the same with read-only functions.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.get_max_vacation_days()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS INTEGER&lt;&#x2F;span&gt;&lt;span&gt; STABLE &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    SELECT&lt;&#x2F;span&gt;&lt;span&gt; max_days&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_types&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    WHERE&lt;&#x2F;span&gt;&lt;span&gt; label &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;vacation&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE sql SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here, we defined the function &lt;code&gt;get_max_vacation_days&lt;&#x2F;code&gt;, which enables retrieving the current number of vacation days for all employees. To call the function, we can simply run:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;rpc&#x2F;get_max_vacation_days&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This summarises the basics of using a dedicated schema and VIEWs. While using stored procedures is the most obvious way, you can also use updatable views and INSTEAD OF TRIGGERS. This approach allows you to hide the possible transition from table to views and implement the insert logic using the trigger function. While this is a powerful technique, I recommend using direct function calls for clarity (at least when you are getting started).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;simple-workflow-management&quot;&gt;Simple workflow management&lt;a class=&quot;zola-anchor&quot; href=&quot;#simple-workflow-management&quot; aria-label=&quot;Anchor link for: simple-workflow-management&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;As we have introduced quite a lot of (potentially new) concepts, let&#x27;s practice more and get our hands dirty with the implementation of a simple workflow management system. In our case, it&#x27;s functionality for employees to request time off and manage approval flow. The premise is simple:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Employee can request a time off of a certain type&lt;&#x2F;li&gt;
&lt;li&gt;Employee can approve&#x2F;reject the time off request for their reports&lt;&#x2F;li&gt;
&lt;li&gt;The boss can do whatever they want&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s start with defining the table for time off requests. Please notice we are going to create it again in schema &lt;code&gt;public&lt;&#x2F;code&gt; and not directly expose it via the API.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE TABLE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.time_off_requests (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SERIAL PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT REFERENCES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span&gt;(user_id),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT REFERENCES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_types&lt;&#x2F;span&gt;&lt;span&gt;(leave_type_id),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    requested_date &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DATE&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    period&lt;&#x2F;span&gt;&lt;span&gt; DATERANGE,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    status TEXT CHECK&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;status IN&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;pending&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;approved&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;rejected&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    created_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TIMESTAMP WITH TIME ZONE DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; current_timestamp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We will add functionality to request time off via a function:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.request_time_off(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    leave_type &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TEXT&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    period&lt;&#x2F;span&gt;&lt;span&gt; DATERANGE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS INTEGER AS&lt;&#x2F;span&gt;&lt;span&gt; $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_request_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- validate the leave type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    SELECT&lt;&#x2F;span&gt;&lt;span&gt; leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INTO&lt;&#x2F;span&gt;&lt;span&gt; v_leave_type_id &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_types&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    WHERE&lt;&#x2F;span&gt;&lt;span&gt; label &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_type&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; v_leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IS NULL THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Invalid leave type: %&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_type&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- check if the user ID is valid&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    PERFORM &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF NOT&lt;&#x2F;span&gt;&lt;span&gt; FOUND &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Invalid user ID: %&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- insert the new time off request&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    INSERT INTO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_requests&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        user_id, leave_type_id, requested_date, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;VALUES&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;        request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		v_leave_type_id,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		CURRENT_DATE,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;		request_time_off&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;		&amp;#39;pending&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ) RETURNING request_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INTO&lt;&#x2F;span&gt;&lt;span&gt; v_request_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    RETURN&lt;&#x2F;span&gt;&lt;span&gt; v_request_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the purposes of this guide, let&#x27;s leave details out, and we would expect the &lt;code&gt;period&lt;&#x2F;code&gt; to be the applicable working days. To reiterate, the function above:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Provides basic application logic, setting the status to &#x27;pending&#x27;&lt;&#x2F;li&gt;
&lt;li&gt;Explicitly declares &lt;code&gt;SECURITY DEFINER&lt;&#x2F;code&gt; to facilitate permissions to access the newly created table.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We can call it using:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POST &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;rpc&#x2F;request_time_off&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;{&amp;quot;user_id&amp;quot;: 6, &amp;quot;leave_type&amp;quot;: &amp;quot;vacation&amp;quot;, &amp;quot;period&amp;quot;: &amp;quot;[2024-05-20,2024-05-21]&amp;quot;} &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Content-Type: application&#x2F;json&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the client to be able to display the pending requests, let&#x27;s introduce the view using the request fields and adding the manager ID of the employee, which we will utilise in the next step:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE VIEW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.pending_requests &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_type_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;requested_date&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;created_at&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;    u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;manager_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_requests&lt;&#x2F;span&gt;&lt;span&gt; r&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;JOIN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span&gt; u &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; u&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;pending&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ORDER BY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; r&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;created_at&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With this in place, we can move forward with implementing the function &lt;code&gt;api.update_request&lt;&#x2F;code&gt; to provide the necessary business logic to approve&#x2F;reject the requests.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.update_request(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    new_status &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TEXT&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS&lt;&#x2F;span&gt;&lt;span&gt; VOID &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_requested_user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_request_manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INT&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- validate the new status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; new_status &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;NOT IN&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;approved&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;rejected&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Invalid status: %. Only &amp;quot;approved&amp;quot; or &amp;quot;rejected&amp;quot; are allowed&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, new_status;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- retrieve the request together with the users associated with it&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    SELECT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; pr&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;pr&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;manager_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; INTO&lt;&#x2F;span&gt;&lt;span&gt; v_requested_user_id, v_request_manager_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    FROM&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; api&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;pending_requests&lt;&#x2F;span&gt;&lt;span&gt; pr&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; pr&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; update_request&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_id&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF NOT&lt;&#x2F;span&gt;&lt;span&gt; FOUND &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;There&amp;#39;&amp;#39;s no pending Time off request ID %&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, request_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- prevent users from self-approving their requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; v_requested_user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;User cannot approve or reject their own request&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- check if the user is either the requester’s manager or the boss&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span&gt; (v_request_manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IS NOT NULL AND&lt;&#x2F;span&gt;&lt;span&gt; user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;&amp;lt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; v_request_manager_id) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RAISE EXCEPTION &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Only the manager or The Boss can approve or reject the request&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- update the request status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    UPDATE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;time_off_requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    SET status =&lt;&#x2F;span&gt;&lt;span&gt; new_status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    WHERE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; time_off_requests&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; update_request&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;request_id&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;SECURITY&lt;&#x2F;span&gt;&lt;span&gt; DEFINER;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This time, the function shouldn&#x27;t have any surprises—just ensure you follow all the points addressed previously. With the functionality to approve&#x2F;reject the requests, we are missing the last crucial step: deducting the balance upon approval. There are two ways to go about this: either add the logic to the function above or implement triggers. While a full discussion goes beyond this guide, let&#x27;s provide a brief summary of when to choose which solution.&lt;&#x2F;p&gt;
&lt;p&gt;Choose a function if you have complex logic reusable by many functions (which does not prevent re-use in triggers), and use triggers if you prefer the automatic business rule enforcement and consistency on the database level. For this tutorial let&#x27;s do triggers to expand further the use of different SQL techniques (plus I would personally choose this solution anyway).&lt;&#x2F;p&gt;
&lt;p&gt;Before jumping in, let&#x27;s create a helper function that will calculate the number of days to deduct from the balance.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.days_in_daterange(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt; daterange) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;RETURNS INT AS&lt;&#x2F;span&gt;&lt;span&gt; $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    SELECT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; upper&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; lower&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE sql&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With it in place, we can create the function:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE OR REPLACE FUNCTION&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; public&lt;&#x2F;span&gt;&lt;span&gt;.create_transaction_on_approval()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; RETURNS&lt;&#x2F;span&gt;&lt;span&gt; TRIGGER &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    IF&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; NEW&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;approved&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; THEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        INSERT INTO&lt;&#x2F;span&gt;&lt;span&gt; time_off_transactions (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            user_id, &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            leave_type_id, &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            transaction_date, &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            time_off_period, &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            amount&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;VALUES&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            NEW&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            NEW&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_type_id&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            CURRENT_DATE,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;            NEW&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;days_in_daterange&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;NEW&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;period&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        );&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END IF&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    RETURN&lt;&#x2F;span&gt;&lt;span&gt; NEW;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$$ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;LANGUAGE&lt;&#x2F;span&gt;&lt;span&gt; plpgsql;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And set up the trigger itself:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE TRIGGER&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; create_transaction_on_approval&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;AFTER UPDATE ON&lt;&#x2F;span&gt;&lt;span&gt; time_off_requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;FOR&lt;&#x2F;span&gt;&lt;span&gt; EACH &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;ROW&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHEN&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;NEW&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;approved&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; AND&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; OLD&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;pending&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;EXECUTE FUNCTION&lt;&#x2F;span&gt;&lt;span&gt; create_transaction_on_approval();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With the approval logic in place, we can try the original function via API:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; POST &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;rpc&#x2F;update_request&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;{&amp;quot;request_id&amp;quot;: 1, &amp;quot;user_id&amp;quot;: 2, &amp;quot;new_status&amp;quot;: &amp;quot;approved&amp;quot;}&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;	-H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;Content-Type: application&#x2F;json&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you have used the correct IDs (both for request ID and user ID), you can now verify the balances using the pre-defined view &lt;code&gt;vacation_balances&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;vacation_balances&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This demonstrates the basic workflow and how it can be exposed using PostgREST as an API. If you want to continue practising more database logic, you can add the following logic:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Prevent negative vacation balances (intermediate)&lt;&#x2F;li&gt;
&lt;li&gt;Prevent employees of a single manager from requesting overlapping vacations (advanced)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;end-of-part-2&quot;&gt;End of Part 2&lt;a class=&quot;zola-anchor&quot; href=&quot;#end-of-part-2&quot; aria-label=&quot;Anchor link for: end-of-part-2&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;By introducing a dedicated API schema, we have added an abstraction layer that provides an effective boundary between the model&#x2F;logic itself and the API specifics. While this is a good practice, it&#x27;s up to you how to expose functionality. The main benefit of a dedicated schema is simplicity, allowing you to grant privileges on the schema in bulk rather than maintaining granular permissions. It also provides an easy way to expose existing databases with PostgREST.&lt;&#x2F;p&gt;
&lt;p&gt;While the current state of the sample API for Time Off Manager would work for small teams, in the next part, we will move towards authentication for added security and privacy.&lt;&#x2F;p&gt;
&lt;p&gt;And don&#x27;t forget you can find the source code in the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;boringSQL&#x2F;postgrest-tutorial&quot;&gt;GitHub repository&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Deep Dive into PostgREST - Time Off Manager (Part 1)</title>
        <published>2024-05-11T00:00:00+00:00</published>
        <updated>2024-05-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Radim Marek
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://boringsql.com/posts/postgrest-tutorial-part1/"/>
        <id>https://boringsql.com/posts/postgrest-tutorial-part1/</id>
        
        <content type="html" xml:base="https://boringsql.com/posts/postgrest-tutorial-part1/">&lt;p&gt;The primary motivation behind &lt;strong&gt;boringSQL&lt;&#x2F;strong&gt; is to explore the robust world of SQL and the PostgreSQL ecosystem, demonstrating how these &quot;boring&quot; tools can cut through the ever-increasing noise and complexity of modern software development. In this series, I&#x27;ll guide you through building a simple yet fully functional application—a Time Off Manager. The goal of this project is not only to demonstrate practical database&#x2F;SQL approaches but also to provide a complete, extendable solution that you can immediately build upon. Each part of this series will deliver a self-contained application, setting the stage for introducing more complex functionalities and practices.&lt;&#x2F;p&gt;
&lt;p&gt;The first part of this guide focuses mainly on the application logic and exposing raw data using &lt;strong&gt;postgREST&lt;&#x2F;strong&gt;, allowing you to grasp the concept.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;a class=&quot;zola-anchor&quot; href=&quot;#requirements&quot; aria-label=&quot;Anchor link for: requirements&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This tutorial assumes you can install PostgreSQL, connect to it using your preferred DB client, have permissions to create a new database, and understand the basics of schema operations and SQL. Similarly, you should be able to follow the installation instructions for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;postgrest.org&#x2F;en&#x2F;v12&#x2F;explanations&#x2F;install.html&quot;&gt;postgREST&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The complete source code for the guide is available at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;boringSQL&#x2F;postgrest-tutorial&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-time-off-manager&quot;&gt;The Time Off Manager&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-time-off-manager&quot; aria-label=&quot;Anchor link for: the-time-off-manager&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;When choosing a sample application for this tutorial, I was torn between the popular but simple TodoMVC and a slightly more complex application. In the end, a sample like Time Off Manager provides a much richer database scheme, going beyond the basics and offering a solution closer to real-life systems.&lt;&#x2F;p&gt;
&lt;p&gt;Time Off Manager is also an excellent example that combines simple business logic, workflows, and practicality. The main requirements are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Maintaining the list of users (employees) with their respective managers&lt;&#x2F;li&gt;
&lt;li&gt;Allowing the maintenance of the time off balance, with an audit history&lt;&#x2F;li&gt;
&lt;li&gt;Introducing an approval workflow and automation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;While the application is intended for learning purposes, the database and API can be easily exposed and built upon.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-database&quot;&gt;The Database&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-database&quot; aria-label=&quot;Anchor link for: the-database&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;To get started, you will need to create a database. You can do this in two ways, either using the CREATE DATABASE statement:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CREATE DATABASE time_off_manager;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;users-model&quot;&gt;Users model&lt;a class=&quot;zola-anchor&quot; href=&quot;#users-model&quot; aria-label=&quot;Anchor link for: users-model&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The foundation and first sample functionality exposed by postgREST is the users themselves. The table schema behind it represents the employees and manages hierarchical relations, which will become important in the second part when we set up the approval workflow.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE TABLE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; users&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    email &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;text NOT NULL UNIQUE&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int REFERENCES&lt;&#x2F;span&gt;&lt;span&gt; users(user_id),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    created_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;timestamp with time zone DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; current_timestamp,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    deleted_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;timestamp with time zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For testing purposes, let&#x27;s populate the data using a seed of 3 managers (one being &quot;the boss&quot; and hence not having a manager) and 10 employees.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DO $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_boss_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_manager_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- create &amp;quot;the boss&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    INSERT INTO&lt;&#x2F;span&gt;&lt;span&gt; users (email, manager_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    VALUES&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;owner@example.com&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    RETURNING user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INTO&lt;&#x2F;span&gt;&lt;span&gt; v_boss_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    -- setup 2 managers &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    FOR&lt;&#x2F;span&gt;&lt;span&gt; i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LOOP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        INSERT INTO&lt;&#x2F;span&gt;&lt;span&gt; users (email, manager_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        VALUES&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;manager&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span&gt; i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;||&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;@example.com&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, v_boss_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        RETURNING user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INTO&lt;&#x2F;span&gt;&lt;span&gt; v_manager_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        -- and 5 employees for each one of them&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        FOR&lt;&#x2F;span&gt;&lt;span&gt; j &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LOOP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            INSERT INTO&lt;&#x2F;span&gt;&lt;span&gt; users (email, manager_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;            VALUES&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;employee&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; j) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;||&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;@example.com&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, v_manager_id);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        END LOOP&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END LOOP&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt; $$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In case you are not aware, the seed data is produced by an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;sql-do.html&quot;&gt;anonymous block&lt;&#x2F;a&gt;. This way, we executed the logic to create the relationship required without the need to create (and later drop) the &lt;em&gt;PL&#x2F;pgSQL&lt;&#x2F;em&gt; function.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;expose-users-using-postgrest&quot;&gt;Expose users using postgREST&lt;a class=&quot;zola-anchor&quot; href=&quot;#expose-users-using-postgrest&quot; aria-label=&quot;Anchor link for: expose-users-using-postgrest&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Having the users table in place, together with sample data, we are ready to expose the data using the REST API. To start, you only need to create a very simple configuration for &lt;strong&gt;postgREST&lt;&#x2F;strong&gt; using the file postgrest.conf (you can place the file in the folder created for this sample project).&lt;&#x2F;p&gt;
&lt;p&gt;Sample &lt;code&gt;postgrest.conf&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db-uri = &amp;quot;postgres:&#x2F;&#x2F;username:password@localhost&#x2F;time_off_manager&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db-schema = &amp;quot;public&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;db-anon-role = &amp;quot;time_off_anonymous&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The most important part is the db-uri, which follows the standard PostgreSQL connection string format:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;postgres:&#x2F;&#x2F;username:password@host&#x2F;database_name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You need to replace:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;username&lt;&#x2F;code&gt; - with your actual database username. Ideally, you will use a separate user (for example, time_off_manager) for each application, rather than your personal account.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;password&lt;&#x2F;code&gt; - the password for the user&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;host&lt;&#x2F;code&gt; - either localhost (if running locally) or a remote server name or IP address&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;database_name&lt;&#x2F;code&gt; - in this case, time_off_manager we created earlier.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;em&gt;NOTE: We are using hardcoded data (which can potentially get stored in your source code repository), and this is mainly for learning purposes. Any deployment resembling a production environment should follow best practices to reduce security risks.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The other configuration options are &lt;code&gt;db-schema&lt;&#x2F;code&gt;, which we will leave pointing to the public schema for this part, and &lt;code&gt;db-anon-role&lt;&#x2F;code&gt;, configuring the role postgREST should use when executing requests on behalf of unauthenticated clients (effectively all requests in Part 1).&lt;&#x2F;p&gt;
&lt;p&gt;For PostgreSQL 15 and higher, please make sure your &lt;code&gt;username&lt;&#x2F;code&gt; is the owner of the database created (should you create it alternatively) in order to be able to create objects in &lt;code&gt;public&lt;&#x2F;code&gt; schema.&lt;&#x2F;p&gt;
&lt;p&gt;To set up the database role, you need to run the following commands:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE ROLE&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous NOLOGIN;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INSERT&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;UPDATE ON&lt;&#x2F;span&gt;&lt;span&gt; users &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; CURRENT_USER;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Please note you this example assumes &lt;code&gt;CURRENT_USER&lt;&#x2F;code&gt; is same as the username used in the &lt;code&gt;db-uri&lt;&#x2F;code&gt; configured above. The latter GRANT statement assigns the role time_off_anonymous to the configured user to execute the anonymous commands.&lt;&#x2F;p&gt;
&lt;p&gt;Once you have the configuration file ready (assuming it&#x27;s in the current directory), you can start the postgREST server and expose it locally on port 3000 (by default):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ postgrest postgrest.con&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Starting PostgREST 12.0.3...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Attempting to connect to the database...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Connection successful&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Listening on port 3000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Listening for notifications on the pgrst channel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Config reloaded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10&#x2F;May&#x2F;2024:22:06:12 +0200: Schema cache loaded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;exploring-the-users-model-using-curl&quot;&gt;Exploring the Users Model Using cURL&lt;a class=&quot;zola-anchor&quot; href=&quot;#exploring-the-users-model-using-curl&quot; aria-label=&quot;Anchor link for: exploring-the-users-model-using-curl&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;When you have the server successfully running, you can try to access the data using:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and get the list of all the sample data we created above. The API for reading data is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;postgrest.org&#x2F;en&#x2F;v12&#x2F;references&#x2F;api&#x2F;tables_views.html#read&quot;&gt;described in detail&lt;&#x2F;a&gt; in the documentation, but you can try different alternatives.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# get one specific user&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; curl http:&#x2F;&#x2F;localhost:3000&#x2F;users?user_id=eq.10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# query only active (i.e. non-deleted users)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users?deleted_at=is.null&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# or display users without any manager (i.e. boss)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users?manager_id=is.null&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Similarly, you can create, update, and delete users as required—giving you full CRUD functionality with rich filtering options.&lt;&#x2F;p&gt;
&lt;p&gt;Create user:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl -X POST &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       -H &amp;quot;Content-Type: application&#x2F;json&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       -d &amp;#39;{&amp;quot;email&amp;quot;: &amp;quot;admin1@example.com&amp;quot;, &amp;quot;manager_id&amp;quot;: 1}&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update user:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl -X PATCH &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users?user_id=eq.10&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       -H &amp;quot;Content-Type: application&#x2F;json&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       -d &amp;#39;{&amp;quot;email&amp;quot;: &amp;quot;updateduser@example.com&amp;quot;}&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or, delete one:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl -X DELETE &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;users?user_id=eq.10&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I haven&#x27;t mentioned CRUD by accident; that&#x27;s the primary use-case of postgREST—exposing CRUD operations on the tables within the configured schema (&lt;code&gt;public&lt;&#x2F;code&gt; in this part). You can delegate authorisation at the database level.&lt;&#x2F;p&gt;
&lt;p&gt;Please note, the IDs are hardcoded, and you might need to check the output of the commands to get the correct ones (should you have truncated the table before or otherwise manipulated the data).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;basic-models-for-managing-time-off&quot;&gt;Basic models for managing time off&lt;a class=&quot;zola-anchor&quot; href=&quot;#basic-models-for-managing-time-off&quot; aria-label=&quot;Anchor link for: basic-models-for-managing-time-off&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;With the User model set up and tested via the API, it&#x27;s time to return to the core of the Time Off Manager functionality: absence tracking itself. For these purposes, we will define two tables—leave types (identifying the reason or type of the leave) and the time off transactions (or updates) themselves.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE TABLE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; leave_types&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    label &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;text NOT NULL&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    description text&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    max_days &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    created_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;timestamp with time zone DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; current_timestamp,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    deleted_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;timestamp with time zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE TABLE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; time_off_transactions&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    transaction_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    created_at &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;timestamp with time zone DEFAULT&lt;&#x2F;span&gt;&lt;span&gt; current_timestamp,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    user_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int NOT NULL REFERENCES&lt;&#x2F;span&gt;&lt;span&gt; users(user_id),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    leave_type_id &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int NOT NULL REFERENCES&lt;&#x2F;span&gt;&lt;span&gt; leave_types(leave_type_id),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    transaction_date &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    time_off_period daterange,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    amount &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;int NOT NULL&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    description text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;CREATE INDEX&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B392F0;&quot;&gt; transaction_for_user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; ON&lt;&#x2F;span&gt;&lt;span&gt; time_off_transactions(user_id);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The tables should be self-explanatory, but for clarity, let&#x27;s delve into the detail of the transaction tracking. Time off is tracked for individual users, the time off transaction type to identify the reason for the change, the period during which the absence is recorded, and the amount of effective days (simplified logic to account for weekends, public holidays, and similar).&lt;&#x2F;p&gt;
&lt;p&gt;To get started we also need to seed the data:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INSERT INTO&lt;&#x2F;span&gt;&lt;span&gt; leave_types (label, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;, max_days) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;VALUES&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;vacation&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Annual vacation leave&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;25&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;sick-leave&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Leave for health reasons&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;unpaid-leave&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Leave without pay&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;sabbatical&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Extended leave for study or travel&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and create the initial time off balances (this is a simplified version, assuming the employee always has the full balance, ignoring possible mid-year joiners, etc.):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DO $$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;DECLARE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v_leave_type record;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    FOR&lt;&#x2F;span&gt;&lt;span&gt; v_leave_type &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;IN SELECT * FROM&lt;&#x2F;span&gt;&lt;span&gt; leave_types &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;WHERE&lt;&#x2F;span&gt;&lt;span&gt; label &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt; &amp;#39;vacation&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt; LOOP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        INSERT INTO&lt;&#x2F;span&gt;&lt;span&gt; time_off_transactions (user_id, leave_type_id, transaction_date, amount, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        SELECT&lt;&#x2F;span&gt;&lt;span&gt; user_id, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;v_leave_type&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;leave_type_id&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;2024-01-01&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;v_leave_type&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #79B8FF;&quot;&gt;max_days&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9ECBFF;&quot;&gt;&amp;#39;Initial balance for year 2024&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;        FROM&lt;&#x2F;span&gt;&lt;span&gt; users;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;    END LOOP&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt; $$;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This now gives us the ability to start tracking the leave of absence for users and keep a full audit log of it.&lt;&#x2F;p&gt;
&lt;p&gt;Before we can start using the table via the API, we need to complete the last important step: giving access to the configured db-anon-role for the tables. We can do this by assigning the grant to individual tables using:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INSERT&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;UPDATE ON&lt;&#x2F;span&gt;&lt;span&gt; leave_types &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;GRANT SELECT&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;INSERT&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;UPDATE ON&lt;&#x2F;span&gt;&lt;span&gt; time_off_transactions &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F97583;&quot;&gt;TO&lt;&#x2F;span&gt;&lt;span&gt; time_off_anonymous;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or modify the default privileges. Spoiler alert—we will move away from using the public schema in Part 2, so let&#x27;s not do more than we need at the moment :)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;working-with-absence&quot;&gt;Working with absence&lt;a class=&quot;zola-anchor&quot; href=&quot;#working-with-absence&quot; aria-label=&quot;Anchor link for: working-with-absence&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Similar to the users example, we can now track absence directly using the API. Before firing off cURL commands, there&#x27;s one last thing to remember. &lt;strong&gt;postgREST&lt;&#x2F;strong&gt; requires metadata about the database schema itself, and it might be expensive to perform on the fly, hence to avoid this, the server caches the schema.&lt;&#x2F;p&gt;
&lt;p&gt;To reload the schema (after making schema changes), it is required to reload the cache. This can be done via several basic options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;restarting the server&lt;&#x2F;li&gt;
&lt;li&gt;issuing a (SIGUSR1) signal to the server process&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;postgrest.org&#x2F;en&#x2F;v12&#x2F;references&#x2F;schema_cache.html#automatic-schema-cache-reloading&quot;&gt;more advanced ways&lt;&#x2F;a&gt; (outside Part 1 of this tutorial).&lt;&#x2F;p&gt;
&lt;p&gt;Once reloaded, you can start exploring more:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #E1E4E8; background-color: #24292E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# getting a time off transaction for particular user&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl &amp;quot;http:&#x2F;&#x2F;localhost:3000&#x2F;time_off_transactions?user_id=eq.1&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# submitting new leave of absence&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl -X POST http:&#x2F;&#x2F;localhost:3000&#x2F;time_off_transactions \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       -H &amp;quot;Content-Type: application&#x2F;json&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       -d &amp;#39;{&amp;quot;user_id&amp;quot;: 5, &amp;quot;leave_type_id&amp;quot;: 1, &amp;quot;transaction_date&amp;quot;: &amp;quot;2024-02-26&amp;quot;, &amp;quot;time_off_period&amp;quot;: &amp;quot;[2024-02-28,2024-03-04]&amp;quot;, &amp;quot;amount&amp;quot;: -4, &amp;quot;description&amp;quot;: &amp;quot;Vacation to avoid panic over leap year&amp;quot;}&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All these commands work as expected. Should you forget to reload the schema, you might face an HTTP Status 404 (Not Found) on the newly created objects.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;end-of-part-1&quot;&gt;End of Part 1&lt;a class=&quot;zola-anchor&quot; href=&quot;#end-of-part-1&quot; aria-label=&quot;Anchor link for: end-of-part-1&quot;&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;While not being very sophisticated, I hope this first part has demonstrated the CRUD capabilities &lt;strong&gt;postgREST&lt;&#x2F;strong&gt; can offer with minimal effort. In the next part, we will move away from accessing the database tables directly and provide more functionality via a new schema api, providing a per-user time off balance view and introducing workflow management.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
