<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aidityas Adhakim</title>
    <description>The latest articles on DEV Community by Aidityas Adhakim (@aidityasadhakim).</description>
    <link>https://dev.to/aidityasadhakim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2899051%2Fd695767b-3213-4425-aaf5-d0951474bfd0.jpeg</url>
      <title>DEV Community: Aidityas Adhakim</title>
      <link>https://dev.to/aidityasadhakim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aidityasadhakim"/>
    <language>en</language>
    <item>
      <title>Workspace Shares Made Easy with Permit IO! LeetCall: Master LeetCode with Spaced Repetition</title>
      <dc:creator>Aidityas Adhakim</dc:creator>
      <pubDate>Thu, 01 May 2025 08:33:58 +0000</pubDate>
      <link>https://dev.to/aidityasadhakim/workspace-shares-made-easy-with-permit-io-leetcall-master-leetcode-with-spaced-repetition-2i8h</link>
      <guid>https://dev.to/aidityasadhakim/workspace-shares-made-easy-with-permit-io-leetcall-master-leetcode-with-spaced-repetition-2i8h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/permit_io"&gt;Permit.io Authorization Challenge&lt;/a&gt;: Permissions Redefined&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;LeetCall is built for DEV Challenge, but is open to the public to try! &lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
What I Built: LeetCall
&lt;/li&gt;
&lt;li&gt;
Sharing Workspace Securely
&lt;/li&gt;
&lt;li&gt;
Prerequisites
&lt;/li&gt;
&lt;li&gt;How Does LeetCall Work?&lt;/li&gt;
&lt;li&gt;
Demo &amp;amp; Project Access

&lt;ul&gt;
&lt;li&gt;5.1 Try the Demo
&lt;/li&gt;
&lt;li&gt;5.2 Project Repository
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
My Journey Building LeetCall

&lt;ul&gt;
&lt;li&gt;6.1 Designing the Authorization Schema
&lt;/li&gt;
&lt;li&gt;6.2 Challenges with External Authorization
&lt;/li&gt;
&lt;li&gt;6.3 How Can External Authorization Help Building an Entire Application
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Using Permit.io for Authorization

&lt;ul&gt;
&lt;li&gt;7.1 Workspace Sharing Model
&lt;/li&gt;
&lt;li&gt;7.2 Setting Up Authorization with Permit CLI
&lt;/li&gt;
&lt;li&gt;7.3 &lt;code&gt;permitio-migration.js&lt;/code&gt; Walkthrough
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Related Articles&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The idea is I wanted to start doing leetcode and I find a way to learn it by utilizing the Space Repetition method, basically working on the problem with a space repetition theory. I can manage and schedule the space repetition in spreadsheet manually, but why don’t I create the web app and let other users who wanted to use this method in leet code access this also — currently there’s no similar space repetition app made for programmer.&lt;/p&gt;

&lt;p&gt;LeetCall lets you input LeetCode problems, which are then reviewed later using spaced repetition, all the you need to do is add LeetCode problem to be tracked in LeetCall and let LeetCall reminds you  with our Spaced Repetition Simplified Algorithm. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoh3hyyvydy9zi0v5u54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoh3hyyvydy9zi0v5u54.png" alt="Space Repetition Theory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing Progress
&lt;/h3&gt;

&lt;p&gt;At LeetCall, we've made sharing your progress effortless. You can share your tracked LeetCode problems and current progress with anyone you choose — whether it's friends, mentors, or study partners. Let others see how far you've come and stay motivated together. Thanks to Permit IO that made ReBAC (Relationship-Based Access Control) easy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Permit IO Account.&lt;/li&gt;
&lt;li&gt;Deployed PDP if you want to deploy the app locally — either local or a vps. Since we are working with ReBAC, we need to start our own PDP. You can check my article to deploy PDP in heroku under 5 minutes here.&lt;/li&gt;
&lt;li&gt;Supabase if you want to deploy the app locally.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How Does LeetCall Works?
&lt;/h2&gt;

&lt;p&gt;LeetCall offers an automated spaced repetition system to support your LeetCode journey, helping you revisit solved problems and strengthen your memory of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. User Add LeetCode Problem to be Tracked
&lt;/h3&gt;

&lt;p&gt;The first step is to add your initial LeetCode problems to be tracked by LeetCall. After that, you can review your progress on the problems you've solved. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. How Does the Space Repetition Algorithm Works?
&lt;/h3&gt;

&lt;p&gt;LeetCall uses a simplified spaced repetition algorithm — it's a minimal viable formula designed to launch quickly, yet effective enough to support meaningful review sessions. Here’s how we define the formula&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LeetCall scoring is range from 0-3 with

&lt;ul&gt;
&lt;li&gt;0: Again(Blackout), complete blackout about the problem’s solution&lt;/li&gt;
&lt;li&gt;1: Hard, still need to peek for public solution&lt;/li&gt;
&lt;li&gt;2: Good, correct but with some difficulty&lt;/li&gt;
&lt;li&gt;3: Easy, perfect response&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Simplified Formula(SF)

&lt;ul&gt;
&lt;li&gt;SF(n) = round(n * (score / 3) * learningStep);&lt;/li&gt;
&lt;li&gt;With n is repetition&lt;/li&gt;
&lt;li&gt;3 is maximum score&lt;/li&gt;
&lt;li&gt;Learning Step is [1, 3, 7] in days&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;If its your first time reviewing the problem — in this case we will use &lt;a href="https://leetcode.com/problems/two-sum/description/" rel="noopener noreferrer"&gt;Two Sum Problem&lt;/a&gt;. Then any score you pick you will still have to review it tomorrow.&lt;/li&gt;

&lt;li&gt;If its your second time reviewing the problem, then it will follow our Simplified Formula with learning step is 3 days.&lt;/li&gt;

&lt;li&gt;If its your third or more time reviewing the problem, then it will follow Simplified Formula with learning step 7 days.&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;You can sign up to LeetCall and start tracking your leetcode progress, would be happy if you can give some review too! Currently LeetCall is in development, so you don't need to confirm your email on sign up!&lt;/p&gt;

&lt;p&gt;I built this app with the help of AI, if you are curious go to my other articles here where I write down the whole journey from creating the PRD and Database Model with the help of AI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use this demo account if you don't want to sign up&lt;br&gt;
email: &lt;a href="mailto:alice@leetcall.com"&gt;alice@leetcall.com&lt;/a&gt;&lt;br&gt;
password: 2025DEVChallenge&lt;/p&gt;

&lt;p&gt;email: &lt;a href="mailto:bob@leetcall.com"&gt;bob@leetcall.com&lt;/a&gt; &lt;br&gt;
password: 2025DEVChallenge&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://leetcall.vercel.app" rel="noopener noreferrer"&gt;LeetCall App&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;

&lt;p&gt;Heads up to the repo if you want to deploy it locally or contributing to the project!&lt;br&gt;
&lt;a href="https://github.com/aidityasadhakim/leetcall" rel="noopener noreferrer"&gt;LeetCall GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  LeetCall Demo Video
&lt;/h3&gt;


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/b7fab117bff64014b92a2ef7d1918b0a"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  My Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Designing Authorization Schema
&lt;/h3&gt;

&lt;p&gt;Designing the authorization schema get a little confusing in the beginning since this is my first time designing ReBAC authorization policy. But thanks to Permit IO that provide clear ReBAC documentation and examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem When Building With External Authorization
&lt;/h3&gt;

&lt;p&gt;We store user information in our database, including their workspace and tracked problems. However, we now also need to sync this data with Permit IO — meaning that whenever a user is created, updated, or deleted, the same action must be reflected in Permit IO. I guess that’s the price that we need to pay for a fine-grained authorization. However, it still very easy to setup since the API and SDK docs is very clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Can External Authorization Help Building an Entire Application?
&lt;/h3&gt;

&lt;p&gt;The highest benefit I felt when using external authorization is how simple it is to check user’s authorization. Instead of doing nested left join to many tables, now I just need to simply do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;await permit.check&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'authorization request details here'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I felt like this saved me a ton of time when building apps. Also Permit IO’s UI makes authorization super easy to manage — everything's laid out clearly, and I can handle access just by clicking a few buttons. No need to build extra user management pages or write complex logic for access control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using &lt;a href="http://permit.io/" rel="noopener noreferrer"&gt;Permit.io&lt;/a&gt; for Authorization
&lt;/h2&gt;

&lt;p&gt;In this section we will explore how do I design the authorization schema and how to build a similar schema using Permit CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Does the Sharing Workspace Designed?
&lt;/h3&gt;

&lt;p&gt;As you have seen before, LeetCall utilize Permit IO ReBAC authorization, which we will have roles, resource and resource instances in this project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top Level Roles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the top level authorization, basically there’s just two roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admin, which the admin that own the app — basically me lol.&lt;/li&gt;
&lt;li&gt;User, other users that sign up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is only one resource which is &lt;strong&gt;workspace&lt;/strong&gt;, each user will have their own workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Instances&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every unique &lt;strong&gt;workspace&lt;/strong&gt; is considered as a resource instances, with uuid as the key i.e. (&lt;strong&gt;workspace#uuid-here&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instance Roles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s the roles that exist within the &lt;strong&gt;workspace&lt;/strong&gt; in Permit IO this is called as &lt;a href="https://docs.permit.io/how-to/build-policies/rebac/overview#role-assignment" rel="noopener noreferrer"&gt;Instance Roles&lt;/a&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Viewer, which can only view your progress and tracked problems.&lt;/li&gt;
&lt;li&gt;Reviewer, can review your tracked problems but cannot add more tracked problems.&lt;/li&gt;
&lt;li&gt;Editor, which can edit your entire workspace, add more tracked problems and review it for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6boku9stmx00grf2d057.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6boku9stmx00grf2d057.png" alt="Authorization Schema"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Set Up Your Authorization Schema Using the Permit CLI
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;In order to complete this section you need to pull the project repository &lt;a href="https://github.com/aidityasadhakim/leetcall" rel="noopener noreferrer"&gt;here&lt;/a&gt;, so you can get the schema template&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For Permit IO Devs, I have tried using permit CLI to deploy the ReBAC authorization, but I just can’t figure out. There is no documentation about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a resource instances, the only resource I can create is a resource, but not with resource instance&lt;/li&gt;
&lt;li&gt;Create an instance role, I also check the code and seems like it just doesn’t support it yet.&lt;/li&gt;
&lt;li&gt;Assign an instance role to a user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And with that problem, we still need Permit IO Node JS SDK to add a resource instance and assign instance role to a user, lets support the devs to maintain the CLI — I also consider to contribute to their permit CLI, but I don’t have any contributing experience, if you guys have suggestions my dm is open.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Permit CLI&lt;/strong&gt;
Check out the installation guides here &lt;a href="https://github.com/permitio/permit-cli" rel="noopener noreferrer"&gt;Permit CLI Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup Permit CLI&lt;/strong&gt;
First you need to login to the permit cli by doing &lt;code&gt;permit login&lt;/code&gt; and it will open up a browser to login to your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run Permit Policy Decision Point (PDP)&lt;/strong&gt;
Since we need ReBAC policy, we need to run our own local PDP, if you want to deploy it in Heroku, I wrote another article for it here &lt;a href=""&gt;Deploy PermitIO PDP to Heroku Under 5 Mins&lt;/a&gt;. 
In this case, we can run it locally by doing &lt;code&gt;permit pdp run&lt;/code&gt; and you will get the port where the pdp is running, by default the pdp runs at &lt;code&gt;http://localhost:7766&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Now lets initiate the schema&lt;/strong&gt;
Make sure you already clone the LeetCall repository, you will find &lt;code&gt;permitio-migration.js&lt;/code&gt; which consist of the schema initialization of LeetCall authorization. All you need to do is to run &lt;code&gt;node permitio-migration.js --permit_pdp="the default url or your deployed one" --permit_api_key="your api key"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;And if success you will see a couple initial check on the users permissions in the output.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Lets talk about the &lt;code&gt;permitio-migration.js&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;The first step is to create the resource with its instance roles and what action is available to the resource&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createResource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;review&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reviewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next lets define the top level roles which is admin and user&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, we can create users and assign the user role to them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;syncUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then lets each user’s workspace and assign them as the owner of their own workspace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice-workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource_instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`workspace:alice-workspace`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceInstances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob-workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource_instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:bob-workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I will assign &lt;code&gt;alice -&amp;gt; viewer -&amp;gt; bob-workspace&lt;/code&gt; and &lt;code&gt;bob -&amp;gt; editor -&amp;gt; alice-workspace&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource_instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:alice-workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assignRole&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource_instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;workspace:bob-workspace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And lets test each other roles on each other workspace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check if alice is bob's workspace editor&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aliceRoleOfBobWorkspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`workspace:bob-workspace`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAliceEditorOfBob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;aliceRoleOfBobWorkspace&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`workspace:bob-workspace`&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAliceViewerOfBob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;aliceRoleOfBobWorkspace&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`workspace:bob-workspace`&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Is Alice an editor of Bob's workspace?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAliceEditorOfBob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Is Alice a viewer of Bob's workspace?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAliceViewerOfBob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check if bob is alice's workspace editor&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bobRoleOfAliceWorkspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserPermissions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`workspace:alice-workspace`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isBobEditorOfAlice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;bobRoleOfAliceWorkspace&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`workspace:alice-workspace`&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Is Bob an editor of Alice's workspace?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isBobEditorOfAlice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there you go, now we have already set up our authorization schema and you just need to implement it within your application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building LeetCall has been a rewarding journey—combining the power of spaced repetition with LeetCode problem-solving to help developers retain what they’ve learned. Through this project, I also explored how Permit.io’s ReBAC authorization can simplify complex access control requirements, especially in collaborative features like sharing workspaces. While there were challenges—particularly around syncing user data and working with Permit IO External Authorization—the experience taught me a lot about designing fine-grained permissions in a real-world application. I'm excited to keep improving LeetCall and invite others to contribute, use the app, or just give feedback. Let’s make studying algorithms smarter, together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Articles
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/aidityasadhakim/deploy-permitio-pdp-to-heroku-under-5-mins-video-included-153l"&gt;Deploy PDP to Heroku under 5 Minutes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aidityasadhakim/building-project-with-ai-assisted-from-scratch-leetcall-4ell"&gt;Building Project With AI Assisted From Scratch: LeetCall&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>permitchallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Deploy Permit.IO PDP To Heroku Under 5 Mins! [Video Included]</title>
      <dc:creator>Aidityas Adhakim</dc:creator>
      <pubDate>Thu, 01 May 2025 05:59:10 +0000</pubDate>
      <link>https://dev.to/aidityasadhakim/deploy-permitio-pdp-to-heroku-under-5-mins-video-included-153l</link>
      <guid>https://dev.to/aidityasadhakim/deploy-permitio-pdp-to-heroku-under-5-mins-video-included-153l</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Deploying Permit.io PDP to Heroku
&lt;/h2&gt;

&lt;p&gt;Let’s deploy your &lt;strong&gt;Permit.io Policy Decision Point (PDP)&lt;/strong&gt; to &lt;strong&gt;Heroku&lt;/strong&gt; — the easy way. Follow along!&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we get started, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧾 A &lt;strong&gt;Heroku account&lt;/strong&gt; — I'm using the &lt;strong&gt;free tier&lt;/strong&gt; via &lt;a href="https://education.github.com/pack" rel="noopener noreferrer"&gt;GitHub Student Pack&lt;/a&gt;. Thanks GitHub! 🙌. Make sure you create an app that is the target of your deployment.&lt;/li&gt;
&lt;li&gt;🛠️ Installed &lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚫 No need for Docker Desktop installed locally&lt;/li&gt;
&lt;li&gt;🐶 A &lt;strong&gt;Permit.io account&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🧑‍💻 Git installed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a working directory&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure Heroku CLI is installed:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Login to Heroku:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku login
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authenticate with the Heroku Container Registry:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku container:login
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure you’ve created an app on Heroku&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set environment variables:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku config:set &lt;span class="nv"&gt;PDP_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-permit-api-key&amp;gt; &lt;span class="nt"&gt;-a&lt;/span&gt; your-app-name
heroku config:set &lt;span class="nv"&gt;PDP_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True &lt;span class="nt"&gt;-a&lt;/span&gt; your-app-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set your app to use the container stack:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku stack:set container
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚙️ Configuration File
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create a Dockerfile
&lt;/h3&gt;

&lt;p&gt;Inside your directory, create a &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ./created-directory/Dockerfile

FROM permitio/pdp-v2:latest

CMD uvicorn horizon.main:app --host 0.0.0.0 --port $PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This ensures Permit PDP uses the correct port that Heroku assigns dynamically.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Create a &lt;code&gt;heroku.yml&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;This tells Heroku how to build and deploy your Docker container:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./created-directory/heroku.yml&lt;/span&gt;

&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚢 Deployment
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Initialize a Git repository:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initialize Heroku config"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connect your repo to Heroku:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku git:remote &lt;span class="nt"&gt;-a&lt;/span&gt; yourapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Push to Heroku to build and deploy:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push heroku master
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open your app in the browser:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;heroku open
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  🎉 Success!
&lt;/h2&gt;

&lt;p&gt;You should see this response:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"online"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Your &lt;a href="https://permit.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Permit.io&lt;/strong&gt;&lt;/a&gt; PDP is now live on Heroku! 🔥&lt;/p&gt;
&lt;h2&gt;
  
  
  Tutorial Video!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.loom.com/share/cf512bbce16b4b58b23ed0981acf096c?sid=a78fa4b8-66ee-4acc-8980-6a7ed3c7bc96" rel="noopener noreferrer"&gt;Tutorial Video&lt;/a&gt;&lt;/p&gt;


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/cf512bbce16b4b58b23ed0981acf096c"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;



</description>
      <category>permitio</category>
      <category>heroku</category>
      <category>authorization</category>
    </item>
    <item>
      <title>Building Project With AI Assisted From Scratch: LeetCall</title>
      <dc:creator>Aidityas Adhakim</dc:creator>
      <pubDate>Wed, 30 Apr 2025 04:08:55 +0000</pubDate>
      <link>https://dev.to/aidityasadhakim/building-project-with-ai-assisted-from-scratch-leetcall-4ell</link>
      <guid>https://dev.to/aidityasadhakim/building-project-with-ai-assisted-from-scratch-leetcall-4ell</guid>
      <description>&lt;p&gt;In the advanced world of AI, it makes me build things easier, since now AI can cover my skill issue, but how can I build a project from scratch with the help of AI? So I try to build a project from scratch with the help of AI from create Product Requirement Document, Database Modelling and even writing the code! Lets start&lt;/p&gt;

&lt;h4&gt;
  
  
  Link to the final product: &lt;a href="https://leetcall.vercel.app" rel="noopener noreferrer"&gt;LeetCall&lt;/a&gt;
&lt;/h4&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;The first thing I have is only the idea of what I want to build, the idea is I wanted to start doing leetcode and I find a way to learn it by utilizing the Space Repetition method, basically working on the problem in a repetitive spaced interval. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkb76y02kxwtyvedv5c3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkb76y02kxwtyvedv5c3m.png" alt="Space repetition image" width="742" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can calculate and schedule the task manually in spreadsheet or notion, but why don’t build a web and overengineered it if I can, duh? So here I am try to create a project that can do this for me. I got no name for the project nor the prd or database modelling, basically I’m blind. And here’s how the AI can help&lt;/p&gt;

&lt;h2&gt;
  
  
  Create A PRD
&lt;/h2&gt;

&lt;p&gt;I wanted to create a PRD, but I don’t even know what to prompt the AI — in this case I use Google AI Studio with Gemini 2.5 Pro model. So I’m just being honest to the AI and tell that I know nothing about PRD and what to prompt and tell the AI to give me initial instruction that I can use to instruct the AI itself. And it works like a charm, here’s the AI response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Key Information Needed:
Product Vision &amp;amp; Goal:
What is the overall product or feature you want to build? &lt;span class="o"&gt;(&lt;/span&gt;Give me the elevator pitch.&lt;span class="o"&gt;)&lt;/span&gt;
What specific problem is it solving &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;users&lt;/span&gt;?
What are the primary business goals? &lt;span class="o"&gt;(&lt;/span&gt;e.g., Increase revenue, improve user engagement, reduce support costs, enter a new market, &lt;span class="nb"&gt;test &lt;/span&gt;a hypothesis?&lt;span class="o"&gt;)&lt;/span&gt;
Target Audience:
Who are the primary &lt;span class="nb"&gt;users&lt;/span&gt;? &lt;span class="o"&gt;(&lt;/span&gt;e.g., Small business owners, teenagers, internal employees, specific job roles?&lt;span class="o"&gt;)&lt;/span&gt;
Are there different types of &lt;span class="nb"&gt;users&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;personas&lt;span class="o"&gt;)&lt;/span&gt; with different needs?
What are their key pain points or needs related to the problem you&lt;span class="s1"&gt;'re solving?
Core Idea / Proposed Solution:
At a high level, how do you envision solving the problem? What will the product do?
Are there any key capabilities or functionalities that are absolutely essential (MVP scope)?
Are there any existing products (competitors or internal tools) that are relevant? What works well/poorly with them?
Context &amp;amp; Constraints:
Is this a brand new product, or an enhancement to an existing one?
Are there any known technical limitations or requirements? (e.g., Must run on mobile, needs to integrate with Salesforce, specific technology stack preference?)
Are there any budget or timeline constraints I should be aware of?
Are there any specific stakeholders whose needs are critical?
Initial Thoughts (Optional but helpful):
Do you have any early ideas about how you might measure success?
Have you identified any obvious risks or dependencies already?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, based on the instruction, of course I am too lazy to write that much detail and I don’t even know that much detail of what I’m trying to build 😀 So its just an iterative steps until the final PRD. And I finally know what to name it, I call it LeetCall! — don’t judge my naming skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  The final PRD
&lt;/h3&gt;

&lt;p&gt;Link to the prompt history &lt;a href="https://aistudio.google.com/app/prompts?state=%7B%22ids%22:%5B%221yUWfC-9Hxqj-XocfQ0A5WoWO_aw4e0yB%22%5D,%22action%22:%22open%22,%22userId%22:%22114621537006128168656%22,%22resourceKeys%22:%7B%7D%7D&amp;amp;usp=sharing" rel="noopener noreferrer"&gt;This will lead to Gemini AI Studio&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Okay, here is the full Product Requirements Document Draft 1.2, incorporating all the refinements discussed:

Product Requirements Document: LeetCode Spaced Repetition &amp;amp; Progress Sharing App &lt;span class="o"&gt;(&lt;/span&gt;Draft 1.2&lt;span class="o"&gt;)&lt;/span&gt;

1. Introduction &amp;amp; Overview

Problem: Developers practicing on platforms like LeetCode struggle to retain solutions and problem-solving patterns over time. Manually tracking problems attempted and scheduling future reviews using general tools like spreadsheets or basic note-taking apps is inefficient, error-prone, difficult to maintain consistently, and lacks features tailored &lt;span class="k"&gt;for &lt;/span&gt;coding problem practice or collaborative learning.

Product Vision: To create a dedicated web application that empowers developers to effectively memorize LeetCode solutions and algorithmic patterns using the proven principles of spaced repetition &lt;span class="o"&gt;(&lt;/span&gt;specifically SM-2&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; The application will streamline the tracking process and &lt;span class="nb"&gt;enable users &lt;/span&gt;to privately share their learning journey and progress with selected peers or mentors.

Goal: Build an intuitive and focused tool that automates the spaced repetition scheduling &lt;span class="k"&gt;for &lt;/span&gt;LeetCode problems based on user self-assessment using the SM-2 algorithm. It aims to provide centralized tracking, improve long-term retention of concepts, and facilitate controlled progress sharing among registered &lt;span class="nb"&gt;users&lt;/span&gt;, fostering accountability and collaborative learning within a trusted circle.

2. Objectives

O1: Automate Review Scheduling: Eliminate manual tracking by automatically calculating and scheduling the next review &lt;span class="nb"&gt;date &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;LeetCode problems using the SM-2 algorithm based on user-provided quality ratings and review history.

O2: Centralize Problem Tracking: Provide a single, dedicated platform &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;users &lt;/span&gt;to log the LeetCode problems they are working on, their review &lt;span class="nb"&gt;history&lt;/span&gt;, and their self-assessed performance on each review.

O3: Improve Retention: Help &lt;span class="nb"&gt;users &lt;/span&gt;commit LeetCode solutions, patterns, and underlying concepts to long-term memory through consistent, algorithmically optimized review intervals.

O4: Facilitate Controlled Progress Sharing: Enable &lt;span class="nb"&gt;users &lt;/span&gt;to securely share their tracked problem list and progress &lt;span class="o"&gt;(&lt;/span&gt;read-only&lt;span class="o"&gt;)&lt;/span&gt; with specific, invited registered &lt;span class="nb"&gt;users &lt;/span&gt;of the platform.

O5: Achieve User Adoption &amp;amp; Engagement: Gain initial &lt;span class="nb"&gt;users&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;starting with the founder&lt;span class="o"&gt;)&lt;/span&gt;, grow the user base within the target audience, and encourage consistent usage &lt;span class="k"&gt;for &lt;/span&gt;tracking and reviewing problems.

3. Target Audience

Primary: Individuals actively practicing coding problems on platforms like LeetCode, particularly those preparing &lt;span class="k"&gt;for &lt;/span&gt;technical interviews, learning data structures and algorithms, or finding manual retention methods challenging.

Secondary: More experienced developers seeking a structured review system&lt;span class="p"&gt;;&lt;/span&gt; coding bootcamp students&lt;span class="p"&gt;;&lt;/span&gt; Computer Science students using LeetCode &lt;span class="k"&gt;for &lt;/span&gt;coursework or practice.

Initial User: The project initiator.

4. Key Features &lt;span class="o"&gt;(&lt;/span&gt;Minimum Viable Product - MVP&lt;span class="o"&gt;)&lt;/span&gt;

F1: User Authentication: Ability &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;users &lt;/span&gt;to sign up using email/password and log &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Requires email verification to &lt;span class="nb"&gt;enable &lt;/span&gt;sharing features.

F2: Add LeetCode Problem: Functionality &lt;span class="k"&gt;for &lt;/span&gt;a logged-in user to &lt;span class="k"&gt;select &lt;/span&gt;a LeetCode problem from a searchable/browsable list fetched via LeetCode&lt;span class="s1"&gt;'s public API (displaying Title, Difficulty, ID). The app should store selected problem details locally (e.g., frontendQuestionId, title, titleSlug, difficulty, topicTags, paidOnly).

F3: Rate Problem Attempt: After reviewing/solving a tracked problem, allow the user to rate the quality of their response using a 0-5 scale, clearly defined in the UI (e.g., 5=perfect, 4=correct after hesitation, 3=correct with difficulty, 2=incorrect but known, 1=incorrect, 0=complete blackout). This rating is the primary input for the SM-2 algorithm.

F4: Spaced Repetition Scheduling (SM-2): Core backend logic implementing the SM-2 algorithm to calculate the next review date.

Uses the 0-5 quality rating (F3).

Maintains an E-Factor (Ease Factor) per tracked problem instance for each user, starting at 2.5, with a minimum value of 1.3.

Calculates the next inter-repetition interval I(n) based on the previous interval I(n-1) and the current E-Factor EF, using I(n) = I(n-1) * EF. Base cases: I(1)=1 day, I(2)=6 days. Intervals are rounded up to the nearest whole day.

Adjusts the E-Factor after each review based on the quality rating q using the formula: EF'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; EF + &lt;span class="o"&gt;(&lt;/span&gt;0.1 - &lt;span class="o"&gt;(&lt;/span&gt;5 - q&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.08 + &lt;span class="o"&gt;(&lt;/span&gt;5 - q&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 0.02&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Resets the repetition sequence &lt;span class="o"&gt;(&lt;/span&gt;interval calculation restarts from I&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;the quality rating q is less than 3, without changing the existing E-Factor.

&lt;span class="o"&gt;(&lt;/span&gt;Decision &lt;span class="k"&gt;for &lt;/span&gt;MVP: The SM-2 recommendation to repeat items scoring &amp;lt; 4 within the same study session will be deferred to a potential future release to simplify the initial implementation.&lt;span class="o"&gt;)&lt;/span&gt;

F5: Review Dashboard: A view displaying a list of all problems whose Next Review Date is on or before the current &lt;span class="nb"&gt;date&lt;/span&gt;, indicating they are due &lt;span class="k"&gt;for &lt;/span&gt;review. Missed reviews should remain on this list &lt;span class="k"&gt;until &lt;/span&gt;reviewed.

F6: All Problems List: A view allowing &lt;span class="nb"&gt;users &lt;/span&gt;to see all the LeetCode problems they have added to the system, potentially showing details like Title, Difficulty, Next Review Date, Last Reviewed Date, and current E-Factor.

F7: Manage Sharing: Functionality within a user&lt;span class="s1"&gt;'s account settings to invite other registered and verified users (by email) to view their tracked problem list. Users should also be able to see who currently has access and revoke that access.

F8: View Shared Lists: A dedicated section or mechanism for logged-in users to access and view the tracked problem lists that have been explicitly shared with them by other users (read-only view).

5. User Stories (MVP)

US1: As a logged-in user, I want to browse or search a list of available LeetCode problems (displaying Title, Difficulty) fetched from the official API and select one to add it to my personal tracking list, so that I can start applying spaced repetition to it.

US2: As a logged-in user, when I review a problem from my tracking list, I want to be prompted to rate my recall quality on a clear 0-5 scale (with descriptors), so that the system can accurately calculate the next optimal review date for that problem using the SM-2 algorithm.

US3: As a logged-in user, I want to access a dashboard that clearly lists all the problems that are currently due for review (today or earlier), so that I know what I need to study and can easily catch up on any missed reviews.

US4: As a logged-in user, I want to be able to view my complete list of tracked LeetCode problems, along with relevant status information (like the next review date), so I can get an overview of my learning progress and workload.

US5: As a new user, I want to be able to sign up for an account using my email address and a password, and then verify my email address via a confirmation link, so that I can securely save my progress and utilize features like sharing.

US6: As a logged-in user (Owner), I want to navigate to a sharing management section where I can enter the verified email address of another registered user and grant them read-only permission to view my tracked problem list, so I can share my progress privately with friends or mentors.

US7: As a logged-in user (Viewer), I want to have a section in the application where I can see and access the progress lists that other users have specifically shared with me, so I can follow their learning journey.

US8: As a logged-in user (Owner), I want to view a list of all users I have granted access to my progress list and have the ability to revoke access for any individual user, so I maintain control over who can see my data.

6. Technical Requirements

T1: Frontend/Backend: Full-stack Next.JS (using App Router).

T2: Database ORM: Prisma.

T3: Database: PostgreSQL.

T4: Hosting: Platform to be determined (e.g., Vercel, Fly.io, AWS Amplify, Railway).

T5: Spaced Repetition Algorithm (SM-2 Implementation):

Backend service/logic to implement the SM-2 algorithm precisely as defined in F4.

Database schema must support storing per-user, per-problem state: current_interval_days, ease_factor, repetitions_count, next_review_date.

Requires persistent storage of individual review events: user_id, problem_id, review_timestamp, quality_rating_q, calculated_interval_before_review, ef_before_review.

Ensure calculations handle date arithmetic correctly, including rounding up intervals to whole days. Enforce EF minimum of 1.3. Implement the repetition reset logic for q &amp;lt; 3.

(Decision: Defer same-session reviews for q&amp;lt;4 for MVP).

T6: LeetCode Public API Integration:

Implement reliable fetching of the LeetCode problem list via their public GraphQL endpoint (or alternative public API if available/more suitable).

Parse and store required fields locally: frontendQuestionId (as unique problem identifier), title, titleSlug (for generating LC links), difficulty, topicTags (store as JSON or normalized relation), paidOnly.

Implement a caching strategy (e.g., periodic background job, cache-on-read with TTL) for the fetched LeetCode problem data to minimize external API calls and improve performance.

T7: Sharing Mechanism (Email-based Access Control):

Requires user accounts to have a verified email status.

Implement an invitation/permission system: Owner inputs potential Viewer'&lt;/span&gt;s email. System checks &lt;span class="k"&gt;if &lt;/span&gt;Viewer is registered and verified. If &lt;span class="nb"&gt;yes&lt;/span&gt;, creates a permission record.

Database schema required &lt;span class="k"&gt;for &lt;/span&gt;access control &lt;span class="o"&gt;(&lt;/span&gt;e.g., a ListShares table with ownerUserId, viewerUserId, permissionType &lt;span class="o"&gt;(&lt;/span&gt;e.g., &lt;span class="s1"&gt;'read'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, grantedTimestamp&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Backend authorization logic must enforce these permissions when accessing user problem lists.

Frontend UI &lt;span class="k"&gt;for &lt;/span&gt;Owners to input emails, view current viewers, and trigger revocation.

T8: User Management &amp;amp; Authentication: Standard email/password sign-up, secure password hashing, login functionality, and an email verification flow &lt;span class="o"&gt;(&lt;/span&gt;sending a unique &lt;span class="nb"&gt;link&lt;/span&gt;/code&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

7. Success Metrics

SM1: User Signups: Total number of registered &lt;span class="nb"&gt;users &lt;/span&gt;with verified email addresses.

SM2: Active Users: Daily Active Users &lt;span class="o"&gt;(&lt;/span&gt;DAU&lt;span class="o"&gt;)&lt;/span&gt;, Weekly Active Users &lt;span class="o"&gt;(&lt;/span&gt;WAU&lt;span class="o"&gt;)&lt;/span&gt;, Monthly Active Users &lt;span class="o"&gt;(&lt;/span&gt;MAU&lt;span class="o"&gt;)&lt;/span&gt; – focusing on &lt;span class="nb"&gt;users who &lt;/span&gt;log &lt;span class="k"&gt;in &lt;/span&gt;AND perform a core action &lt;span class="o"&gt;(&lt;/span&gt;add problem, review problem&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

SM3: Problem Tracking Engagement:

Average number of problems tracked per active user.

Total number of problems tracked across the platform.

SM4: Review Completion Rate:

Total number of reviews completed &lt;span class="o"&gt;(&lt;/span&gt;ratings submitted&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Average number of reviews completed per active user per week/month.

SM5: User Retention: Week 1, Week 4, Month 3 retention rates &lt;span class="o"&gt;(&lt;/span&gt;percentage of new &lt;span class="nb"&gt;users &lt;/span&gt;returning&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

SM6: Sharing Feature Usage:

Total number of unique lists shared &lt;span class="o"&gt;(&lt;/span&gt;owner-viewer pairs&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Percentage of active &lt;span class="nb"&gt;users who &lt;/span&gt;have shared their list.

Percentage of active &lt;span class="nb"&gt;users who &lt;/span&gt;are viewing at least one shared list.

SM7 &lt;span class="o"&gt;(&lt;/span&gt;Operational/Insight Metric&lt;span class="o"&gt;)&lt;/span&gt;: Distribution of review quality ratings &lt;span class="o"&gt;(&lt;/span&gt;0-5&lt;span class="o"&gt;)&lt;/span&gt; submitted – helps understand user difficulty and algorithm effectiveness.

8. Potential Risks &amp;amp; Dependencies

R1: Algorithm Implementation Complexity: Correctly implementing SM-2 state management &lt;span class="o"&gt;(&lt;/span&gt;EF, intervals, resets&lt;span class="o"&gt;)&lt;/span&gt; per user/problem requires careful design and testing. Off-by-one errors or incorrect state updates can degrade user experience.

R2: User Motivation &amp;amp; Churn: The app&lt;span class="s1"&gt;'s value is tied to the user'&lt;/span&gt;s motivation to practice LeetCode. If &lt;span class="nb"&gt;users &lt;/span&gt;stop practicing, they will likely stop using the app.

R3: Sharing Feature Complexity: Implementing secure, email-based sharing &lt;span class="o"&gt;(&lt;/span&gt;invites, permissions management, revocation, UI&lt;span class="o"&gt;)&lt;/span&gt; is significantly more complex than public links and adds considerable development effort &lt;span class="k"&gt;for &lt;/span&gt;MVP.

R4: Scope Creep: High risk of wanting to add more features to sharing &lt;span class="o"&gt;(&lt;/span&gt;e.g., commenting, different permission levels&lt;span class="o"&gt;)&lt;/span&gt; or the SRS algorithm &lt;span class="o"&gt;(&lt;/span&gt;e.g., customizable intervals, different algorithms&lt;span class="o"&gt;)&lt;/span&gt; before validating the core MVP loop.

R5: LeetCode API Reliability &amp;amp; Changes: The application depends heavily on the availability, stability, and structure of LeetCode&lt;span class="s1"&gt;'s public API. Any changes, rate limiting, or deprecation could break core functionality (adding problems). Requires monitoring and potential adaptation.

R6: Scalability: As user numbers and tracked problems grow, database queries for dashboards, problem lists, and permission checks need to be performant. Efficient fetching/display of the global LeetCode problem list is also crucial.

R7: User Experience of 0-5 Scale: Users must easily understand the meaning of each numerical rating (0-5) for the SRS algorithm to function as intended. Requires clear labels, tooltips, or onboarding guidance.

D1: SRS Algorithm Fine-tuning: While SM-2 is defined, initial parameters (starting EF, base intervals if deviating slightly) and the exact handling of edge cases need final confirmation. Decision to defer same-session repeats needs to be firm for MVP scope.

D2: LeetCode API Exploration: Thoroughly investigate the specific GraphQL endpoint, its schema, rate limits, and any terms of service regarding its use in third-party applications.

D3: Sharing UI/UX Design: Carefully design the user flows for sending invitations, accepting/viewing shared lists, and managing permissions to be intuitive and secure.

This concludes Draft 1.2. Let me know when you'&lt;/span&gt;re ready to discuss the next steps, like data modeling.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we go, now I have a full PRD that I can use for the next step which is database modelling&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Modelling
&lt;/h2&gt;

&lt;p&gt;The steps for database modelling basically just provide the PRD to the AI and let it generate the Database Modelling, of course there will be some columns that I don’t want or I want to add, and then I just did a VERY SMALL changes, since the AI has covered all the PRD in the database model&lt;/p&gt;

&lt;h3&gt;
  
  
  Final database model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; User Authentication &amp;amp; Profile &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; Links to the master user record &lt;span class="k"&gt;in &lt;/span&gt;Supabase Auth &lt;span class="o"&gt;(&lt;/span&gt;or similar&lt;span class="o"&gt;)&lt;/span&gt;
DROP TABLE IF exists &lt;span class="nb"&gt;users &lt;/span&gt;cascade&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE &lt;span class="nb"&gt;users&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id &lt;/span&gt;UUID PRIMARY KEY NOT NULL REFERENCES auth.users&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE CASCADE,
    email TEXT, &lt;span class="nt"&gt;--&lt;/span&gt; Optional cache from auth.users
    name TEXT,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

create or replace &lt;span class="k"&gt;function &lt;/span&gt;handle_new_user&lt;span class="o"&gt;()&lt;/span&gt;
returns trigger as &lt;span class="nv"&gt;$$&lt;/span&gt;
begin
  insert into public.users
  values&lt;span class="o"&gt;(&lt;/span&gt;new.id, new.email, new.raw_user_meta_data-&amp;gt;&amp;gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;, new.created_at&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  insert into public.workspaces&lt;span class="o"&gt;(&lt;/span&gt;owner_user_id&lt;span class="o"&gt;)&lt;/span&gt;
  values&lt;span class="o"&gt;(&lt;/span&gt;new.id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;new&lt;span class="p"&gt;;&lt;/span&gt;
end&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$$&lt;/span&gt; language plpgsql security definer&lt;span class="p"&gt;;&lt;/span&gt;

create or replace trigger on_new_user
after insert on auth.users &lt;span class="k"&gt;for &lt;/span&gt;each row
execute procedure public.handle_new_user&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Workspaces &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; Represents a container &lt;span class="k"&gt;for &lt;/span&gt;tracked LeetCode problems.
&lt;span class="nt"&gt;--&lt;/span&gt; A user might have one or more workspaces &lt;span class="o"&gt;(&lt;/span&gt;though MVP might focus on one initially&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
DROP TABLE IF exists workspaces cascade&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE workspaces &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id &lt;/span&gt;UUID PRIMARY KEY DEFAULT gen_random_uuid&lt;span class="o"&gt;()&lt;/span&gt;, &lt;span class="nt"&gt;--&lt;/span&gt; Use built-in UUID generation
    owner_user_id UUID NOT NULL, &lt;span class="nt"&gt;--&lt;/span&gt; The user &lt;span class="nb"&gt;who &lt;/span&gt;created/owns the workspace
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, &lt;span class="nt"&gt;--&lt;/span&gt; Needs trigger &lt;span class="k"&gt;for &lt;/span&gt;auto-update

    &lt;span class="nt"&gt;--&lt;/span&gt; Relationship to the owner
    &lt;span class="nt"&gt;--&lt;/span&gt; Use RESTRICT initially: prevent deleting a user &lt;span class="k"&gt;if &lt;/span&gt;they still own workspaces.
    CONSTRAINT fk_workspaces_owner FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;owner_user_id&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE RESTRICT
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; Index &lt;span class="k"&gt;for &lt;/span&gt;fetching workspaces owned by a user
CREATE INDEX idx_workspaces_owner_user_id ON workspaces&lt;span class="o"&gt;(&lt;/span&gt;owner_user_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Canonical LeetCode Problem Data &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;No changes needed here&lt;span class="o"&gt;)&lt;/span&gt;
DROP TABLE IF exists leet_code_problems cascade&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE leet_code_problems &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id &lt;/span&gt;TEXT PRIMARY KEY, &lt;span class="nt"&gt;--&lt;/span&gt; Assuming CUID generated by application
    frontend_question_id TEXT NOT NULL UNIQUE,
    title TEXT NOT NULL,
    title_slug TEXT NOT NULL UNIQUE,
    difficulty VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;10&lt;span class="o"&gt;)&lt;/span&gt; NOT NULL,
    paid_only BOOLEAN NOT NULL,
    topic_tags JSONB,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX idx_leet_code_problems_frontend_question_id ON leet_code_problems&lt;span class="o"&gt;(&lt;/span&gt;frontend_question_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX idx_leet_code_problems_title_slug ON leet_code_problems&lt;span class="o"&gt;(&lt;/span&gt;title_slug&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Tracking a Specific LeetCode Problem within a Workspace &lt;span class="nt"&gt;---&lt;/span&gt;
DROP TABLE IF exists tracked_problems cascade&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE tracked_problems &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id &lt;/span&gt;bigint primary key generated always as identity, &lt;span class="nt"&gt;--&lt;/span&gt; Assuming CUID generated by application
    &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="k"&gt;****&lt;/span&gt; CHANGED &lt;span class="k"&gt;****&lt;/span&gt; Removed user_id, added workspace_id
    workspace_id UUID NOT NULL,
    problem_id TEXT NOT NULL,

    &lt;span class="nt"&gt;--&lt;/span&gt; SM-2 State &lt;span class="o"&gt;(&lt;/span&gt;unchanged&lt;span class="o"&gt;)&lt;/span&gt;
    ease_factor DOUBLE PRECISION NOT NULL DEFAULT 2.5,
    interval_days INTEGER NOT NULL DEFAULT 0,
    repetitions_count INTEGER NOT NULL DEFAULT 0,
    next_review_date TIMESTAMP WITH TIME ZONE NOT NULL,
    last_reviewed_at TIMESTAMP WITH TIME ZONE,

    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,

    &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="k"&gt;****&lt;/span&gt; UPDATED &lt;span class="k"&gt;****&lt;/span&gt; Foreign key constraint references workspaces table
    CONSTRAINT fk_tracked_problems_workspace FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;workspace_id&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES workspaces&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE CASCADE, &lt;span class="nt"&gt;--&lt;/span&gt; If workspace deleted, delete its tracked problems
    CONSTRAINT fk_tracked_problems_problem FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;problem_id&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES leet_code_problems&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE RESTRICT,

    &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="k"&gt;****&lt;/span&gt; UPDATED &lt;span class="k"&gt;****&lt;/span&gt; Ensure a problem is tracked only once PER WORKSPACE
    CONSTRAINT uq_tracked_problems_workspace_problem UNIQUE &lt;span class="o"&gt;(&lt;/span&gt;workspace_id, problem_id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="k"&gt;****&lt;/span&gt; UPDATED &lt;span class="k"&gt;****&lt;/span&gt; Indices reflecting the change
CREATE INDEX idx_tracked_problems_workspace_id ON tracked_problems&lt;span class="o"&gt;(&lt;/span&gt;workspace_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX idx_tracked_problems_problem_id ON tracked_problems&lt;span class="o"&gt;(&lt;/span&gt;problem_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX idx_tracked_problems_next_review_date ON tracked_problems&lt;span class="o"&gt;(&lt;/span&gt;next_review_date&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; Still needed &lt;span class="k"&gt;for &lt;/span&gt;review dashboard query

&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Record of a Single Review Event &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;No direct change needed, still links to tracked_problems&lt;span class="o"&gt;)&lt;/span&gt;
DROP TABLE IF exists reviews cascade&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE reviews &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id &lt;/span&gt;bigint primary key generated always as identity, &lt;span class="nt"&gt;--&lt;/span&gt; Assuming CUID generated by application
    tracked_problem_id bigint NOT NULL,
    quality_rating INTEGER NOT NULL,
    reviewed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT fk_reviews_tracked_problem FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;tracked_problem_id&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES tracked_problems&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE CASCADE
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX idx_reviews_tracked_problem_id ON reviews&lt;span class="o"&gt;(&lt;/span&gt;tracked_problem_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Workspace Sharing Permissions &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="k"&gt;****&lt;/span&gt; RENAMED and RESTRUCTURED &lt;span class="k"&gt;****&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Replaces list_shares&lt;span class="o"&gt;)&lt;/span&gt;
DROP TABLE IF exists workspace_shares cascade&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TYPE user_role AS ENUM &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'viewer'&lt;/span&gt;, &lt;span class="s1"&gt;'editor'&lt;/span&gt;, &lt;span class="s1"&gt;'reviewer'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE workspace_shares &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id &lt;/span&gt;UUID PRIMARY KEY DEFAULT gen_random_uuid&lt;span class="o"&gt;()&lt;/span&gt;, &lt;span class="nt"&gt;--&lt;/span&gt; Use built-in UUID generation
    &lt;span class="nt"&gt;--&lt;/span&gt; The workspace being shared
    workspace_id UUID NOT NULL,
    &lt;span class="nt"&gt;--&lt;/span&gt; The user being granted access
    shared_user_id UUID NOT NULL,

    role user_role NOT NULL,
    &lt;span class="nt"&gt;--&lt;/span&gt; Optional: Add permission level later &lt;span class="o"&gt;(&lt;/span&gt;e.g., &lt;span class="s1"&gt;'read'&lt;/span&gt;, &lt;span class="s1"&gt;'write'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;needed

    granted_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,

    &lt;span class="nt"&gt;--&lt;/span&gt; Relationships
    CONSTRAINT fk_workspace_shares_workspace FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;workspace_id&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES workspaces&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE CASCADE, &lt;span class="nt"&gt;--&lt;/span&gt; If workspace deleted, remove shares &lt;span class="k"&gt;for &lt;/span&gt;it
    CONSTRAINT fk_workspace_shares_viewer FOREIGN KEY &lt;span class="o"&gt;(&lt;/span&gt;shared_user_id&lt;span class="o"&gt;)&lt;/span&gt; REFERENCES &lt;span class="nb"&gt;users&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; ON DELETE CASCADE, &lt;span class="nt"&gt;--&lt;/span&gt; If viewer user deleted, remove shares granted TO them

    &lt;span class="nt"&gt;--&lt;/span&gt; Ensure a user has only one share entry per workspace
    CONSTRAINT uq_workspace_shares_workspace_viewer UNIQUE &lt;span class="o"&gt;(&lt;/span&gt;workspace_id, shared_user_id&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt; Indices &lt;span class="k"&gt;for &lt;/span&gt;looking up shares by workspace or viewer
CREATE INDEX idx_workspace_shares_workspace_id ON workspace_shares&lt;span class="o"&gt;(&lt;/span&gt;workspace_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE INDEX idx_workspace_shares_shared_user_id ON workspace_shares&lt;span class="o"&gt;(&lt;/span&gt;shared_user_id&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code Writing
&lt;/h2&gt;

&lt;p&gt;For the code writing, I use GitHub Copilot to help me, what I did is I create a GitHub Instructions file and Prompt file — Sources: &lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; &lt;a href="https://docs.github.com/en/get-started" rel="noopener noreferrer"&gt;GitHub Instructions&lt;/a&gt;. And it helps me so much since the PRD and database model is very clear already.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Honestly, building LeetCall from scratch using AI was quite the experience. From getting the initial idea down into a proper plan (the PRD 📝), sorting out the database structure 📊, to actually speeding up the coding with GitHub Copilot 💻, AI was a huge help. It wasn't just about plugging gaps in what I knew or handling boring tasks; it genuinely made the whole development process faster. If you ever feel stuck or a bit lost starting a project, particularly with stuff outside your main skills, I'd definitely say give AI tools a try. They can really help bring your ideas to life quicker than you might expect. 👍&lt;/p&gt;

&lt;p&gt;The conclusion above is written by A.I, here’s my own conclusion “AI covered my skill issue in a superbly good way”&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productrequirementdocument</category>
      <category>webdev</category>
      <category>database</category>
    </item>
  </channel>
</rss>
