<?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>Forem: Chit</title>
    <description>The latest articles on Forem by Chit (@chits_programming_blog).</description>
    <link>https://forem.com/chits_programming_blog</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%2F871173%2F6dfa7c0d-052e-4d13-85ce-0bc08becc79b.png</url>
      <title>Forem: Chit</title>
      <link>https://forem.com/chits_programming_blog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/chits_programming_blog"/>
    <language>en</language>
    <item>
      <title>Set up a Svelte todo list on self-hosted Supabase + Email sign up + Google, Facebook Auth + host on GitHub pages</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Fri, 06 Jan 2023 13:21:09 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/set-up-a-svelte-todo-list-on-self-hosted-supabase-email-sign-up-google-facebook-auth-host-on-github-pages-ljf</link>
      <guid>https://forem.com/chits_programming_blog/set-up-a-svelte-todo-list-on-self-hosted-supabase-email-sign-up-google-facebook-auth-host-on-github-pages-ljf</guid>
      <description>&lt;p&gt;In this tutorial. I will guide you through creating a to-do list web app using Svelte as the front end hosted on GitHub pages, and Supabase as the back end. I will also discuss how to set up an email authentication, as well as google and Facebook third-party OAuth. The outcome would be like &lt;a href="https://sveltesupabasetodoexample.cpbprojects.me/" rel="noopener noreferrer"&gt;this&lt;/a&gt; from &lt;a href="https://github.com/chit-uob/svelte_supabase_todo_example" rel="noopener noreferrer"&gt;this Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important information
&lt;/h2&gt;

&lt;p&gt;This is my second time making a tutorial, if there is anything unclear, feel free to ask in the comments and I'll try to reply to them.&lt;/p&gt;

&lt;p&gt;This tutorial assumes you are using a self-hosted Supabase. If you are using a managed Supabase you should follow the instructions on &lt;a href="https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list" rel="noopener noreferrer"&gt;the GitHub page&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;If you don't already have a self-hosted Supabase, check out my other tutorial &lt;a href="https://blog.cpbprojects.me/how-to-self-host-supabase-a-complete-guide" rel="noopener noreferrer"&gt;How to Self-host Supabase&lt;/a&gt; and come back to this tutorial afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a GitHub repository for the Svelte App
&lt;/h2&gt;

&lt;p&gt;We will host our svelte app on GitHub pages. It is a static site hosting service that host files straight out of a repository on GitHub. It has the benefit of being free and has pretty good bandwidth since it is hosted by a big company.&lt;/p&gt;

&lt;p&gt;We first need to create a GitHub repository. if you &lt;strong&gt;don't&lt;/strong&gt; have GitHub Pro, you must create a public repository in order to share your site with GitHub pages, but with GitHub Pro, both private and public repositories support GitHub pages.&lt;/p&gt;

&lt;p&gt;For the repository name, you can choose any name you want unless you want to page to be on your main GitHub page (Main GitHub page means the website on your {github_username}.github.io). If so, you need to name your repository as &lt;code&gt;{github_username}.github.io&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you name your repository other things, your website would have a link of &lt;code&gt;{github_username}.github.io/{project_name}&lt;/code&gt; . In my case, my website would be on &lt;a href="https://chit-uob.github.io/svelte_supabase_todo_example" rel="noopener noreferrer"&gt;https://chit-uob.github.io/svelte_supabase_todo_example&lt;/a&gt;.&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%2Fpjhr6jnp6moszroetzi3.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%2Fpjhr6jnp6moszroetzi3.png" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then click &lt;strong&gt;Create Repository&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone To-do list example
&lt;/h2&gt;

&lt;p&gt;Then we clone the Svelte to-do list example from the Supabase GitHub. We choose a folder to put the project, and then we clone it with the sparse setting, this clones the repository without downloading every single file. Then we can go inside the repository and spare-checkout the to-do list example.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make sure you have Git installed, install from &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you don't already&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To open the command prompt on windows, you can right-click an empty space on the folder, and choose &lt;strong&gt;Open in Windows Terminal&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone --depth 1 --filter=blob:none --sparse https://github.com/supabase/supabase
cd supabase
git sparse-checkout set examples/todo-list/sveltejs-todo-list

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fajxinp69vzted4v4y547.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%2Fajxinp69vzted4v4y547.png" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can go in &lt;code&gt;supabase -&amp;gt; examples -&amp;gt; todo-list&lt;/code&gt; , and copy the &lt;code&gt;sveltejs-todo-list&lt;/code&gt; folder to another folder you use for the project. I am going to use the same example folder. Then we open the terminal inside the &lt;code&gt;sveltejs-todo-list&lt;/code&gt; folder, and run the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/{github_username}/{repo_name}.git
git push -u origin main

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If you haven't already logged in to GitHub, it may prompt you to log in with your credentials&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%2Ftlzmdnbc49rsu9ilh1uf.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%2Ftlzmdnbc49rsu9ilh1uf.png" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure front end
&lt;/h2&gt;

&lt;p&gt;Then we install the GitHub pages npm package&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need Node.js installed for this, install it from &lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;here&lt;/a&gt; if not already
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install gh-pages --save-dev

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set up package.json
&lt;/h3&gt;

&lt;p&gt;Inside the file &lt;code&gt;package.json&lt;/code&gt;, we add a "homepage" property and set it as &lt;code&gt;https://{github_username}.github.io/{project_name}&lt;/code&gt;, and a "deploy" inside the "script" property and set it as &lt;code&gt;gh-pages -d dist&lt;/code&gt;, so lines 4-10 should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "version": "2.0.0",
  "type": "module",
  "homepage": "https://chit-uob.github.io/svelte_supabase_todo_example",
  "scripts": {
    "deploy": "gh-pages -d dist",
    "dev": "concurrently \"npm run dev:css\" \"vite\"",
    "dev:css": "tailwindcss -w -i ./src/tailwind.css -o src/assets/app.css",

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we copy the &lt;code&gt;.env.example&lt;/code&gt; file, and rename it to &lt;code&gt;.env&lt;/code&gt; . Inside there we fill in the backend URL and anon key. If you followed &lt;a href="https://blog.cpbprojects.me/how-to-self-host-supabase-a-complete-guide" rel="noopener noreferrer"&gt;my last tutorial&lt;/a&gt;, &lt;code&gt;VITE_SUPABASE_URL&lt;/code&gt; would be your backend domain name. Otherwise, it would be whatever you set your &lt;code&gt;API_EXTERNAL_URL&lt;/code&gt; inside the &lt;code&gt;.env&lt;/code&gt; file inside the Supabase docker folder. As for &lt;code&gt;VITE_SUPABASE_ANON_KEY&lt;/code&gt; , it is the value of &lt;code&gt;ANON_KEY&lt;/code&gt; in the Supabase docker folder &lt;code&gt;.env&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
JWT_SECRET=...
ANON_KEY={THIS IS THE ANON KEY}
SERVICE_ROLE_KEY=...
...
## General
...
DISABLE_SIGNUP=...
API_EXTERNAL_URL={THIS IS THE SUPABASE URL}
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You should then add &lt;code&gt;.env&lt;/code&gt; to the &lt;code&gt;.gitignore&lt;/code&gt; file, in order to not commit your secret keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  If not a user site nor custom domain: set up vite.config.ts
&lt;/h3&gt;

&lt;p&gt;If you are not using the user site (&lt;a href="http://username.github.io" rel="noopener noreferrer"&gt;username.github.io&lt;/a&gt;), or you are not using a custom domain, then you would need to configure the &lt;code&gt;vite.config.ts&lt;/code&gt; , and set &lt;code&gt;base&lt;/code&gt; to your repository name, so lines 5-8 look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// https://vitejs.dev/config/
export default defineConfig({
  base: '/svelte_supabase_todo_example/',
  plugins: [svelte()],
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because normally Vite looks for files in the main URL, which would be &lt;code&gt;username.github.io/{whatever_file}&lt;/code&gt; , but since we want it to look for files inside &lt;code&gt;username.github.io/{project_name}/{whatever_file}&lt;/code&gt; , we need to set the base to the project name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change front-end authentication
&lt;/h3&gt;

&lt;p&gt;The original code uses GitHub and Google auth, but we want to use Facebook and Google auth instead, so inside the &lt;code&gt;src/lib/Auth.svelte&lt;/code&gt; file, in line 114, we change it into &lt;code&gt;on:click={() =&amp;gt; handleOAuthLogin("github")}&lt;/code&gt; . In line 118, we change it into &lt;code&gt;Facebook&lt;/code&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional: Mapping a custom domain to the GitHub page
&lt;/h3&gt;

&lt;p&gt;if you have a custom domain, you have to create a file called &lt;code&gt;CNAME&lt;/code&gt; inside the public folder, with the content being the custom domain. And change the homepage inside the package.json to be the custom domain.&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%2Fukwp9ugez707gq1e0882.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%2Fukwp9ugez707gq1e0882.png" width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to your DNS provider and create a &lt;code&gt;CNAME&lt;/code&gt; record that points your subdomain to the default domain for your site. For example, if you want to use the subdomain &lt;code&gt;subdomain.example.com&lt;/code&gt; for your user site, create a &lt;code&gt;CNAME&lt;/code&gt; record that points &lt;code&gt;subdomain.example.com&lt;/code&gt; to &lt;code&gt;&amp;lt;user&amp;gt;.github.io&lt;/code&gt;. Also, add &lt;code&gt;A Record&lt;/code&gt; with host &lt;code&gt;@&lt;/code&gt; and value &lt;code&gt;185.199.108.153&lt;/code&gt;, &lt;code&gt;185.199.109.153&lt;/code&gt;, &lt;code&gt;185.199.110.153&lt;/code&gt;, &lt;code&gt;185.199.111.153&lt;/code&gt;. So your record looks like this:&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%2Fmrs6tm341tb3v4c6kd1a.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%2Fmrs6tm341tb3v4c6kd1a.png" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Side note: when I was testing this, GitHub seems to dislike having dashes &lt;code&gt;-&lt;/code&gt; or underscore &lt;code&gt;_&lt;/code&gt; inside the subdomain&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you used a user site &lt;code&gt;&amp;lt;user&amp;gt;.github.io&lt;/code&gt; , every other project site you use afterwards, will start with your custom domain, instead of &lt;code&gt;&amp;lt;user&amp;gt;.github.io&lt;/code&gt; .&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploy with GitHub pages
&lt;/h2&gt;

&lt;p&gt;We can install the necessary packages, build the project, and then deploy it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
npm run build
npm run deploy

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then visit the repository on GitHub and go to Settings -&amp;gt; Pages, select Deploy from a branch, and select &lt;code&gt;gh-pages&lt;/code&gt; as the branch. Then we can optionally enable HTTPS too.&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%2Fgwsjnm4qxpii2rrm9pkc.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%2Fgwsjnm4qxpii2rrm9pkc.png" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you used a custom domain, the page would like something like this:&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%2Fnx9gu3oc3aqr33n6o876.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%2Fnx9gu3oc3aqr33n6o876.png" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations!&lt;/strong&gt; When you go to the URL stated above, you will be able to visit the website.&lt;/p&gt;

&lt;p&gt;However, only the front end is working for now, in order to have a full-fletch web application, we need the backend to work as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the database table
&lt;/h2&gt;

&lt;p&gt;The initial Supabase database given to us only contains the auth tables, we need to create the profiles table and todo table on our own. In the hosted version, the snippets are provided, but since we are self-hosting, we will have to copy them from there.&lt;/p&gt;

&lt;p&gt;I created a Supabase account, created a project, and then copied the snippet. For you, you can just copy the following code to the run panel and run it.&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%2Feehuqhhl921rsxwajjv9.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%2Feehuqhhl921rsxwajjv9.png" width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the Supabase dashboard, inside the default project -&amp;gt; SQL editor, we can paste the following code there, and click run to run them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Create a table for public profiles
create table profiles (
  id uuid references auth.users not null primary key,
  updated_at timestamp with time zone,
  username text unique,
  full_name text,
  avatar_url text,
  website text,

  constraint username_length check (char_length(username) &amp;gt;= 3)
);
-- Set up Row Level Security (RLS)
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
alter table profiles
  enable row level security;

create policy "Public profiles are viewable by everyone." on profiles
  for select using (true);

create policy "Users can insert their own profile." on profiles
  for insert with check (auth.uid() = id);

create policy "Users can update own profile." on profiles
  for update using (auth.uid() = id);

-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
create function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, full_name, avatar_url)
  values (new.id, new.raw_user_meta_data-&amp;gt;&amp;gt;'full_name', new.raw_user_meta_data-&amp;gt;&amp;gt;'avatar_url');
  return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();

-- Set up Storage!
insert into storage.buckets (id, name)
  values ('avatars', 'avatars');

-- Set up access controls for storage.
-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
create policy "Avatar images are publicly accessible." on storage.objects
  for select using (bucket_id = 'avatars');

create policy "Anyone can upload an avatar." on storage.objects
  for insert with check (bucket_id = 'avatars');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then for the todo list&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--
-- For use with:
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list or
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/react-todo-list or
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list or
-- https://github.com/supabase/supabase/tree/master/examples/todo-list/vue3-ts-todo-list
--

create table todos (
  id bigint generated by default as identity primary key,
  user_id uuid references auth.users not null,
  task text check (char_length(task) &amp;gt; 3),
  is_complete boolean default false,
  inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table todos enable row level security;
create policy "Individuals can create todos." on todos for
    insert with check (auth.uid() = user_id);
create policy "Individuals can view their own todos. " on todos for
    select using (auth.uid() = user_id);
create policy "Individuals can update their own todos." on todos for
    update using (auth.uid() = user_id);
create policy "Individuals can delete their own todos." on todos for
    delete using (auth.uid() = user_id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Email sign up
&lt;/h2&gt;

&lt;p&gt;To enable email sign-up, we need our backend to be able to send emails. Email servers are very difficult to host, so we will use an SMTP relay service, and have them send the emails for us. There are many choices, I chose to use Sendinblue because it has the biggest free tier email sending limit. It can send up to 300 emails per day, which means up to 9000 emails per month. You can use whatever service you want, you just need to get the required credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the credentials from Sendinblue
&lt;/h3&gt;

&lt;p&gt;Create an account in &lt;a href="https://www.sendinblue.com/" rel="noopener noreferrer"&gt;Sendinblue&lt;/a&gt;. In the dashboard, click on the Profile in the upper right corner -&amp;gt; &lt;strong&gt;SMTP and API&lt;/strong&gt;.&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%2Fsm60i732qn55ld0325cb.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%2Fsm60i732qn55ld0325cb.png" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside that page, we need the SMTP Server, Port, Login. We also need to generate an SMTP key, which would act as the password.&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%2F55au8srkhxduh79uctc7.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%2F55au8srkhxduh79uctc7.png" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fill in the credentials in the self-hosted Supabase
&lt;/h3&gt;

&lt;p&gt;Going back to the Supabase backend, in the &lt;code&gt;supabase/docker/&lt;/code&gt; directory inside the &lt;code&gt;.env&lt;/code&gt; file, we can paste the respective values inside the respective field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=false
SMTP_ADMIN_EMAIL={The email address you want the email to be from}
SMTP_HOST=smtp-relay.sendinblue.com
SMTP_PORT={The port given to you}
SMTP_USER={Your login}
SMTP_PASS={Your SMTP key}
SMTP_SENDER_NAME={The name of the sender of your choosing}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Social OAuth
&lt;/h2&gt;

&lt;p&gt;We also want to enable Open Authentication login, because it skips the step of creating an account for the user, so users will more likely be inclined to sign up to your site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google
&lt;/h3&gt;

&lt;p&gt;Supabase already has &lt;a href="https://supabase.com/docs/guides/auth/social-login/auth-google" rel="noopener noreferrer"&gt;a guide on this&lt;/a&gt;, however, that is for the managed version, for the self-hosted version, there is something we need to do differently.&lt;/p&gt;

&lt;p&gt;First, go to &lt;a href="https://cloud.google.com/" rel="noopener noreferrer"&gt;https://cloud.google.com/&lt;/a&gt;, Sign in to google if not already, and then click console. It will prompt you to accept the terms of service.&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%2Fwn59dntx1xjzjs97kfed.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%2Fwn59dntx1xjzjs97kfed.png" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside there, click on &lt;code&gt;Select a Project&lt;/code&gt; at the top left and click &lt;code&gt;new project&lt;/code&gt;. Fill in your app information then click &lt;code&gt;Create&lt;/code&gt;, this will take you to the dashboard for the new project.&lt;/p&gt;

&lt;p&gt;In the search bar at the top labelled &lt;code&gt;Search products and resources&lt;/code&gt; type &lt;code&gt;OAuth&lt;/code&gt;. Click on &lt;code&gt;OAuth consent screen&lt;/code&gt; from the list of results. On the &lt;code&gt;OAuth consent screen&lt;/code&gt; page select &lt;code&gt;External&lt;/code&gt;. Click &lt;code&gt;Create&lt;/code&gt;.&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%2Fx9di44im0nwjuovma9ou.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%2Fx9di44im0nwjuovma9ou.png" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;Edit app registration&lt;/code&gt; page fill out your app information. The Application home page is your Svelte website, and The Authorised domain is the domain of your website. Click &lt;code&gt;Save and continue&lt;/code&gt; at the bottom.&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Credentials&lt;/code&gt; at the left to go to the &lt;code&gt;Credentials&lt;/code&gt; page on the Google Cloud Platform console. Click &lt;code&gt;Create Credentials&lt;/code&gt; near the top then select &lt;code&gt;OAuth client ID .&lt;/code&gt; On the &lt;code&gt;Create OAuth client ID&lt;/code&gt; page, select your application type. If you're not sure, choose &lt;code&gt;Web application&lt;/code&gt;. Fill in your app name. At the bottom, under &lt;code&gt;Authorized redirect URIs&lt;/code&gt; click &lt;code&gt;Add URI&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your URI should be what you set as the &lt;code&gt;API_EXTERNAL_URL&lt;/code&gt; inside the supabase docker &lt;code&gt;.env&lt;/code&gt; file + &lt;code&gt;/auth/v1/callback&lt;/code&gt;. For example, &lt;code&gt;https://mybackend.com/auth/v1/callback&lt;/code&gt;. Enter your callback URI under &lt;code&gt;Authorized redirect URIs&lt;/code&gt; at the bottom. Enter your callback URI in the &lt;code&gt;Valid OAuth Redirect URIs&lt;/code&gt; box. Click &lt;code&gt;Save Changes&lt;/code&gt; at the bottom right. Click &lt;code&gt;Create&lt;/code&gt;. Then we need to copy and save the values under &lt;code&gt;Your Client ID&lt;/code&gt; and &lt;code&gt;Your Client Secret&lt;/code&gt;.&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%2Fnszi2l9bpnn5i65ouqkc.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%2Fnszi2l9bpnn5i65ouqkc.png" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we can go back to the supabase docker &lt;code&gt;.env&lt;/code&gt; file, and add the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Google auth
ENABLE_GOOGLE_SIGNUP=true
GOOGLE_CLIENT_ID={the_client_id_given_to_you}
GOOGLE_CLIENT_SECRET={the_client_secret_given_to_you}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the supabase docker &lt;code&gt;docker-compose.yml&lt;/code&gt; file, under the auth environment section, (around line 87), we fill in the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${ENABLE_GOOGLE_SIGNUP}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_CLIENT_SECRET}
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: {the_callback_uri}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Around this part is a lot of GOTRUE_..., so it should be pretty easy to find. Keep in mind that the ${} things should be taken literally, you shouldn't replace the string inside ${}, but do replace {the callback uri} with the actual callback uri.&lt;/p&gt;

&lt;h3&gt;
  
  
  Facebook
&lt;/h3&gt;

&lt;p&gt;The Facebook guide can also be found on &lt;a href="https://supabase.com/docs/guides/auth/social-login/auth-facebook" rel="noopener noreferrer"&gt;the Supabase guide&lt;/a&gt;, I am going to briefly go through what you need to do, and the self-hosted instructions not included in their guide.&lt;/p&gt;

&lt;p&gt;We first create and configure a Facebook Application on the &lt;a href="https://developers.facebook.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Facebook Developers Site&lt;/strong&gt;&lt;/a&gt;. We first login. Click on &lt;code&gt;My Apps&lt;/code&gt; at the top right. Click &lt;code&gt;Create App&lt;/code&gt; near the top right. Select your app type and click &lt;code&gt;Continue&lt;/code&gt; (Do NOT choose "business", it complicates things). Fill in your app information, then click &lt;code&gt;Create App&lt;/code&gt;. This should bring you to the screen: &lt;code&gt;Add Products to Your App&lt;/code&gt;. (Alternatively you can click on &lt;code&gt;Add Product&lt;/code&gt; in the left sidebar to get to this screen.)&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;Add Products to your App&lt;/code&gt; screen, we can click &lt;code&gt;Setup&lt;/code&gt; under &lt;code&gt;Facebook Login&lt;/code&gt; . Skip the Quickstart screen, instead, in the left sidebar, click &lt;code&gt;Settings&lt;/code&gt; under &lt;code&gt;Facebook Login&lt;/code&gt; . Enter your callback URI under &lt;code&gt;Valid OAuth Redirect URIs&lt;/code&gt; on the &lt;code&gt;Facebook Login Settings&lt;/code&gt; page. Enter this in the &lt;code&gt;Valid OAuth Redirect URIs&lt;/code&gt; box. Click &lt;code&gt;Save Changes&lt;/code&gt; at the bottom right.&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%2Ffuvocfli1ol8xote4v65.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%2Ffuvocfli1ol8xote4v65.png" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Be aware that you have to set the right access levels on your Facebook App to enable 3rd party applications to read the email address. From the &lt;code&gt;App Review -&amp;gt; Permissions and Features&lt;/code&gt; screen: Click the button &lt;code&gt;Request Advanced Access&lt;/code&gt; on the right side of &lt;code&gt;public_profile&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; .&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%2F2pl5p3z2fti79lqabufz.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%2F2pl5p3z2fti79lqabufz.png" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Settings / Basic&lt;/code&gt; in the left sidebar. Copy your App ID from the top of the &lt;code&gt;Basic Settings&lt;/code&gt; page. Under &lt;code&gt;App Secret&lt;/code&gt; click &lt;code&gt;Show&lt;/code&gt; then copy your secret. Make sure all required fields are completed on this screen.&lt;/p&gt;

&lt;p&gt;Then similarly, we go to the supabase docker &lt;code&gt;.env&lt;/code&gt; file, and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Facebook auth
ENABLE_FACEBOOK_SIGNUP=true
FACEBOOK_CLIENT_ID={the_id}
FACEBOOK_CLIENT_SECRET={the_secret}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the supabase docker &lt;code&gt;docker-compose.yml&lt;/code&gt; file, under the google stuff we just added, we add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOTRUE_EXTERNAL_FACEBOOK_ENABLED: ${ENABLE_FACEBOOK_SIGNUP}
GOTRUE_EXTERNAL_FACEBOOK_CLIENT_ID: ${FACEBOOK_CLIENT_ID}
GOTRUE_EXTERNAL_FACEBOOK_SECRET: ${FACEBOOK_CLIENT_SECRET}
GOTRUE_EXTERNAL_FACEBOOK_REDIRECT_URI: {the_callback_uri}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Note that both Google and Facebook Auth are in testing mode, to use them in production, you need to change them to production mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Replace site URL
&lt;/h3&gt;

&lt;p&gt;also inside the supabase docker &lt;code&gt;.env&lt;/code&gt; file, you can change the &lt;code&gt;SITE_URL=http://localhost:3000&lt;/code&gt; , and set the URL to the URL of your front-end website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restart Supabase
&lt;/h2&gt;

&lt;p&gt;Now that everything is set, you can restart Supabase. If you followed &lt;a href="https://blog.cpbprojects.me/how-to-self-host-supabase-a-complete-guide" rel="noopener noreferrer"&gt;my last tutorial&lt;/a&gt;, Supabase would be running inside a screen, do &lt;code&gt;screen -r supabase&lt;/code&gt; , then &lt;code&gt;ctrl + c&lt;/code&gt; to stop the already running docker, then &lt;code&gt;docker compose up&lt;/code&gt; to start the docker again. If it is not already running, you can just do &lt;code&gt;docker compose up&lt;/code&gt; inside the supabase docker directory to start Supabase.&lt;/p&gt;

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

&lt;p&gt;Now you should get a fully functional to-do list web app, you can try the different logins, from email sign-up and sending an email to confirm your account, to one-click OAuth with Google and Facebook. After logging in, you can also add and remove to-do items, and they will stay there when you log in with different devices.&lt;/p&gt;

&lt;p&gt;This is my second touch on making a tutorial, if there are any steps unclear, feel free to leave a comment and I'll try to answer them. hope this tutorial helps.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>svelte</category>
      <category>oauth</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Self-host Supabase: A complete guide</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Sat, 24 Dec 2022 22:35:42 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/how-to-self-host-supabase-a-complete-guide-3ono</link>
      <guid>https://forem.com/chits_programming_blog/how-to-self-host-supabase-a-complete-guide-3ono</guid>
      <description>&lt;p&gt;In this guide, I will explain how to self-host Supabase. Including how to purchase and secure a virtual private server (VPS), install and set up Supabase, Set up a reverse proxy using Nginx, and get a secure socket layer (SSL) certificate to accept HTTPS traffic.&lt;/p&gt;

&lt;p&gt;This is my first time writing a complementary guide, so if there is anything unclear, or you have any feedback, please leave it in the comment below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will achieve
&lt;/h2&gt;

&lt;p&gt;If you follow the steps, you will end up having a password-protected Supabase server hosted on a VPS, a reverse proxy that redirects traffic from the domain name to the respective endpoint on the Supabase back end with an SSL certificate.&lt;/p&gt;

&lt;p&gt;To continue, you will need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A domain name, free or paid, that you can set the A or CNAME record&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A VPS running Ubuntu 20+, if you don't already this guild will also introduce one&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Buy a VPS and set up
&lt;/h2&gt;

&lt;p&gt;To self-host a Supabase back end, obviously, you need a VPS. If you already have one, you can move on to the section about securing the VPS and see if there is more you can do to secure your VPS. If not, the following guide will talk about how to purchase one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Purchasing a VPS
&lt;/h3&gt;

&lt;p&gt;I will be using OxideHost VPS because they are relatively cheap. I've been using them and they are pretty reliable. For those interested, this is my &lt;a href="https://billing.oxide.host/aff.php?aff=166" rel="noopener noreferrer"&gt;affiliate link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After choosing the VPS and purchasing it with Ubuntu 22.04 installed, an email is sent to give me information about how to connect to my VPS. The most important part is the hostname, IP, and default password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect to it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh {username}@{ip-address}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To connect to the VPS I just purchased, the simplest way is to use the &lt;code&gt;ssh&lt;/code&gt; command in the command line. However, using this method, we will have to type this command and enter the password every time. A better method would be to use SSH clients such as &lt;a href="https://www.putty.org/" rel="noopener noreferrer"&gt;PuTTY&lt;/a&gt; which is free. I will be using &lt;a href="https://termius.com/" rel="noopener noreferrer"&gt;Termius&lt;/a&gt; since it has a nice interface and is free for students.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbilling.oxide.host%2Fimages%2Fkb%2F3_d144a7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbilling.oxide.host%2Fimages%2Fkb%2F3_d144a7.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will create a new host, inputting the hostname given by the email, and username which is &lt;code&gt;root&lt;/code&gt; by default, and the password was given to me. It will ask you a question about do you want to connect to it, click &lt;code&gt;yes&lt;/code&gt;. Then I will have connected to the VPS for the first time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671622141798%2FyidZnFtC6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671622141798%2FyidZnFtC6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Securing the VPS
&lt;/h3&gt;

&lt;p&gt;Now that we are connected to our VPS, we want to make sure we are the only people having access to it, so we need to take measures to secure it.&lt;/p&gt;

&lt;h4&gt;
  
  
  update its software
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt update
apt dist-upgrade

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first task is to update it since updated software usually has its security vulnerabilities patches. This also upgrades the Linux distribution. There will be a few questions the command prompt asks you, you can type &lt;code&gt;y&lt;/code&gt; and then press enters to say yes to them. and if it is a multiple choice, you can press &lt;code&gt;tab&lt;/code&gt; to navigate around, and press enters to confirm the selection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671622242369%2F4-gFu4AlO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671622242369%2F4-gFu4AlO.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Add another user
&lt;/h4&gt;

&lt;p&gt;Now that you acquired your VPS, hackers also want to get access to it. There are tons of robots on the internet cracking passwords of random VPS, they usually try to log in as root since it is the default configuration. So we will add a new user, pass the root permission to it, and disable root login so those bad robots won't be able to log in to our VPS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adduser {username}
usermod -aG sudo {username}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The username cannot have spaces within, so you can use an underscore like &lt;code&gt;learn_supabase&lt;/code&gt; or &lt;code&gt;learnsupabase&lt;/code&gt; . Then it will ask us some questions, we can input blank if we don't want to answer them. But we need to fill in a &lt;strong&gt;secure password&lt;/strong&gt; , I recommend inputting a randomly generated password using some &lt;a href="https://www.dashlane.com/features/password-generator" rel="noopener noreferrer"&gt;secure password generators&lt;/a&gt; so that there is no way for anyone to guess it. Then we can give the user Sudo privileges. And &lt;strong&gt;try to log in using the new username and password&lt;/strong&gt; by creating a new host and connecting it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Disable the root login
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano /etc/ssh/sshd_config

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671623556148%2F9wNCAihMU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671623556148%2F9wNCAihMU.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to change the &lt;code&gt;Port&lt;/code&gt; to some random number to make it harder to guess, the number must be smaller than 65535. And we also change the link &lt;code&gt;PermitRootLogin&lt;/code&gt; to &lt;code&gt;no&lt;/code&gt;, so we can no longer log in as root.&lt;/p&gt;

&lt;p&gt;After making the change, we can do &lt;code&gt;ctrl + o&lt;/code&gt; to write out the changes, and &lt;code&gt;ctrl + x&lt;/code&gt; to exit nano. Then we do the following command to restart the &lt;code&gt;sshd&lt;/code&gt; service, so the new configuration will come into effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemctl restart sshd

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Supabase
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install docker
&lt;/h3&gt;

&lt;p&gt;We need docker for this, a tutorial about how to install docker can be found &lt;a href="https://www.linode.com/docs/guides/installing-and-using-docker-on-ubuntu-and-debian/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In this tutorial, I will summarize what commands we need to run. This command assumes you are using Ubuntu, if you are using other distros, please refer to the above tutorial link.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io
sudo apt install docker-compose-plugin

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we also need to enable the user to use docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG docker {username}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clone Supabase
&lt;/h3&gt;

&lt;p&gt;Then we want to clone the Supabase from Github.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone --depth 1 https://github.com/supabase/supabase

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set up Supabase secrets
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd supabase/docker/
cp .env.example .env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we get inside the docker directory and copy the file for the environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate passwords
&lt;/h3&gt;

&lt;p&gt;We open the &lt;code&gt;.env&lt;/code&gt; file, and we will see a lot of settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano .env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671721310426%2Fb16d8cd9-f1d9-4496-b799-fe3712756a21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671721310426%2Fb16d8cd9-f1d9-4496-b799-fe3712756a21.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want to set the passwords for the above fields. For &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; , you can use a randomly generated password. For the JWT secret, we have to use the &lt;a href="https://supabase.com/docs/guides/self-hosting#api-keys" rel="noopener noreferrer"&gt;custom key generator&lt;/a&gt;. you can copy the JWT secret and paste it into the file, and then we can select &lt;code&gt;ANON_KEY&lt;/code&gt; , hit generate, copy it to the &lt;code&gt;ANON_KEY&lt;/code&gt; field, then the &lt;code&gt;SERVICE_KEY&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671721891892%2F5901353a-8c78-4fe6-b4ba-251fa0de343e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671721891892%2F5901353a-8c78-4fe6-b4ba-251fa0de343e.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano volumes/api/kong.yml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we also copy the same key to the kong config, pasting the new &lt;code&gt;anon&lt;/code&gt; key and &lt;code&gt;service&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671722061768%2F11c398ed-6533-45ef-bd6c-0c6024e06edb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671722061768%2F11c398ed-6533-45ef-bd6c-0c6024e06edb.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Start docker
&lt;/h3&gt;

&lt;p&gt;Now we open a new screen and name it so that we can connect to it later&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screen

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you will see the introduction of the screen, hit &lt;code&gt;enter&lt;/code&gt; . Then we can do &lt;code&gt;ctrl + a&lt;/code&gt; then &lt;code&gt;ctrl + d&lt;/code&gt; to disconnect from the screen. In the console, you will see your screen number &lt;code&gt;[detached from 3775963.pts-0.hostname]&lt;/code&gt; , the &lt;code&gt;3775963&lt;/code&gt; number is the screen number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screen -S {screen_number} -X sessionname supabase
screen -r supabase

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have reconnected to screen &lt;code&gt;supabase&lt;/code&gt; , when we disconnect from it, we use &lt;code&gt;ctrl + a&lt;/code&gt; then &lt;code&gt;ctrl + d&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;cd supabase/docker/
docker compose up

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the supabase docker is started, it will download a bunch of things, and you can connect to your domain in port 3000.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain mapping
&lt;/h2&gt;

&lt;p&gt;Now I want to point a custom domain to the backend. I used Namecheap, and I create an A record, pointing a subdomain to the IP address of my VPS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671722678692%2F01578d23-97a3-4d36-8b61-42bab6955e57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671722678692%2F01578d23-97a3-4d36-8b61-42bab6955e57.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse proxy
&lt;/h2&gt;

&lt;p&gt;Reverse proxy points the connections to the correct port. The part is explained very well in the &lt;a href="https://www.linode.com/docs/guides/installing-supabase/#using-a-reverse-proxy" rel="noopener noreferrer"&gt;Linode blog post&lt;/a&gt;, so I am only going to briefly talk about what commands needed to be run, you can refer to &lt;a href="https://www.linode.com/docs/guides/installing-supabase/#using-a-reverse-proxy" rel="noopener noreferrer"&gt;their blog post&lt;/a&gt; for a more detailed explanation.&lt;/p&gt;

&lt;p&gt;We first install Nginx for reverse proxy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install nginx
sudo systemctl status nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we connect to our domain, we will see the Nginx welcome page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671724075251%2Ff835e84f-cfec-42d6-8f7d-b1087629ddf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671724075251%2Ff835e84f-cfec-42d6-8f7d-b1087629ddf3.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/sites-available/{your_domain_name}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then we copy the following content (from the &lt;a href="https://www.linode.com/docs/guides/installing-supabase/#using-a-reverse-proxy" rel="noopener noreferrer"&gt;Linode guide&lt;/a&gt;) into the file, replacing the &lt;code&gt;example.com&lt;/code&gt; with your custom domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream supabase {
    server localhost:3000;
}

upstream kong {
    server localhost:8000;
}

server {
    listen 80;
    server_name example.com;

    # REST
    location ~ ^/rest/v1/(.*)$ {
        proxy_set_header Host $host;
        proxy_pass http://kong;
        proxy_redirect off;
    }

    # AUTH
    location ~ ^/auth/v1/(.*)$ {
        proxy_set_header Host $host;
        proxy_pass http://kong;
        proxy_redirect off;
    }

    # REALTIME
    location ~ ^/realtime/v1/(.*)$ {
        proxy_redirect off;
        proxy_pass http://kong;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
    }

    # STUDIO
    location / {
        proxy_set_header Host $host;
        proxy_pass http://supabase;
        proxy_redirect off;
        proxy_set_header Upgrade $http_upgrade;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, we can do &lt;code&gt;ctrl + o&lt;/code&gt; and &lt;code&gt;ctrl + x&lt;/code&gt; to write and exit. Then we can create a symbolic link of the config from sites-available to sites-enabled, link the default config, and then restart Nginx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/{your_domain_name} /etc/nginx/sites-enabled/{your_domain_name}
sudo unlink /etc/nginx/sites-enabled/default
sudo systemctl restart nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can connect to Supabase using the domain name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get SSL
&lt;/h2&gt;

&lt;p&gt;In this part, we will use Certbot to give our Supabase an SSL certificate. Their official website have pretty good instructions, so you can follow that. But I'll also let you know what commands I used.&lt;/p&gt;

&lt;p&gt;We first install &lt;code&gt;core&lt;/code&gt;, then &lt;code&gt;certbot&lt;/code&gt;. Then we configure it, and at last try how it auto-renews, and then restart Nginx for it to take effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx
sudo certbot renew --dry-run
sudo systemctl restart nginx

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671894627444%2F62293b4c-8763-4e17-86ea-bc827d8b8504.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671894627444%2F62293b4c-8763-4e17-86ea-bc827d8b8504.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure Supabase
&lt;/h2&gt;

&lt;p&gt;The Supabase dashboard everyone can connect to, to secure it, we do this to generate the password. It will ask you to type in the password two times, so it is best you generate a password from a secure password generator and then paste it there twice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo htpasswd -c /etc/apache2/.htpasswd {admin_username}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can check the password with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /etc/apache2/.htpasswd

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we edit the Nginx config to password protect&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/nginx/sites-available/{domain name}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and make it so that in the studio&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # STUDIO
    location / {
        proxy_set_header Host $host;
        proxy_pass http://supabase;
        proxy_redirect off;
        proxy_set_header Upgrade $http_upgrade;

        auth_basic "Administrators Area";
        auth_basic_user_file /etc/apache2/.htpasswd;
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;at last restart&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service nginx restart

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671895136816%2F2a9eeac2-07cd-4144-8be1-9fca76ed2f11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1671895136816%2F2a9eeac2-07cd-4144-8be1-9fca76ed2f11.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can fill in the username as the &lt;code&gt;{admin_username}&lt;/code&gt; you put in, and the password as the password you filled in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconfigure Supabase
&lt;/h2&gt;

&lt;p&gt;Now, we want to set the domain for the Supabase backend and external API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screen -r supabase

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we press &lt;code&gt;ctrl + c&lt;/code&gt; to end the Supabase docker instance. Then we edit the &lt;code&gt;.env&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano .env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to change the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
API_EXTERNAL_URL=https://{your_domain}
...
SUPABASE_PUBLIC_URL=https://{your_domain} # replace if you intend to use Studio outside of localhost

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can do &lt;code&gt;ctrl + o&lt;/code&gt; and &lt;code&gt;ctrl + x&lt;/code&gt; to save the changes, then do the following to get Supabase back up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can exit the screen by doing &lt;code&gt;ctrl + a&lt;/code&gt; then &lt;code&gt;ctrl + d&lt;/code&gt; to disconnect without terminating the process inside.&lt;/p&gt;

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

&lt;p&gt;Congratulations, you now have a working Supabase backend, protected by a password, inside a certified domain. Hopefully, my explanation is clear, is there are any questions or comments, feel free to leave a comment. Your next step would be to develop your front end, which I will cover in &lt;a href="https://blog.cpbprojects.me/set-up-a-svelte-todo-list-on-self-hosted-supabase-email-sign-up-google-facebook-auth-host-on-github-pages" rel="noopener noreferrer"&gt;the next part of this guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>tutorial</category>
      <category>nginx</category>
      <category>ssl</category>
    </item>
    <item>
      <title>What I learned from self-hosting a Supabase Svelte project: Part 2</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Thu, 15 Dec 2022 17:17:03 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/what-i-learned-from-self-hosting-a-supabase-svelte-project-part-2-4jk5</link>
      <guid>https://forem.com/chits_programming_blog/what-i-learned-from-self-hosting-a-supabase-svelte-project-part-2-4jk5</guid>
      <description>&lt;p&gt;In &lt;a href="https://blog.cpbprojects.me/what-i-learned-from-self-hosting-a-supabase-svelte-project-part-1"&gt;part 1&lt;/a&gt;, we went over self-hosting supabase, setting up Svelte, enabling third-party authentication and email sending. In this blog post, I will talk about what I learned about routing, SSL, domain name, local storage, and hosting svelte using GitHub pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing with Nginx
&lt;/h2&gt;

&lt;p&gt;Currently, there are lots of programs running on various ports of my VPS. Supabase studio on 3000, Svelte on 5173, PostgreSQL running on port 5432, and Kong running on 8000 and 8443 for HTTP and HTTPS respectively. How do I take the users that want to connect to my front end (Svelte) to port 5173, and secure my Supabase studio?&lt;/p&gt;

&lt;p&gt;The answer is routing, we can set up a router, or a reverse proxy, to take users that are connecting from HTTP and HTTPS, which is port 80 and port 443, and route them to port 5173, my Svelte front end. To do that, I will use Nginx, because that is the most popular one. In the following, I'm going to summarize what I did to get it working, If you want a more detailed explanation or code examples, I recommend you check out &lt;a href="https://www.linode.com/docs/guides/installing-supabase/"&gt;this article from Linode&lt;/a&gt;, I find it very helpful.&lt;/p&gt;

&lt;p&gt;In Nginx, we create a &lt;code&gt;server&lt;/code&gt; to listen to a single port, then we tell it how to redirect based on &lt;code&gt;location&lt;/code&gt;. Locations are the URL path after the domain name, the location for &lt;code&gt;example.com&lt;/code&gt; is &lt;code&gt;/&lt;/code&gt;, and &lt;code&gt;example.com/terms/&lt;/code&gt; is &lt;code&gt;^terms/(.*)$&lt;/code&gt; . Then in the location, we can ask it to redirect to a certain port, such as &lt;code&gt;proxy_pass http://localhost:5173&lt;/code&gt; , this would redirect the users connecting from this port at this location to this address instead.&lt;/p&gt;

&lt;p&gt;An important thing for Nginx is that we have to configure which rules are active. There is a directory called &lt;code&gt;sites-available&lt;/code&gt;, and one called &lt;code&gt;sites-enabled&lt;/code&gt;. What we have to do it to configure the settings in &lt;code&gt;sites-available&lt;/code&gt;, then created a symbolic link to the &lt;code&gt;sites-enabled&lt;/code&gt; directory. Then we have to restart the Nginx service to apply changes. An important thing to do is to unlink sites you aren't using, I once forgot to unlink the default rules and was wondering how the default rules were applying for so long.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting SSL
&lt;/h2&gt;

&lt;p&gt;After that, we can redirect HTTP traffic, but we need an SSL certificate if we want HTTPS traffic. They provide credibility and encrypt traffic. There are 3 ways to get a certificate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Purchase it from a trusted certificate authority (CA), the cost can vary from 5 a year, up to who knows how much&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let a proxy like &lt;a href="https://www.cloudflare.com/ssl/"&gt;Cloudflare&lt;/a&gt; do it for you, users will connect to Cloudflare with the certificate, and then Cloudflare redirects the traffic to your site, and the users see the certificate from Cloudflare. This is free for personal or hobby projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate your own using a service like &lt;a href="https://certbot.eff.org/"&gt;Certbot&lt;/a&gt;, it generates the certificate for you and you don't have to pay a penny. But the downside is that you will have to configure it yourself.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I went with Certbot since it is free. &lt;a href="https://certbot.eff.org/"&gt;The website&lt;/a&gt; contains very detailed instructions about installing it. After getting it, I need to configure Nginx to use the certificate, redirect HTTPS traffic to my front end, and ask HTTP traffic to upgrade to HTTPS traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain name
&lt;/h2&gt;

&lt;p&gt;Then I needed a domain name for my website, you can think of a domain name as basically a link for the website. I went over the three providers that offer free 1-year in Github student pack, namely &lt;em&gt;Namecheap&lt;/em&gt;, &lt;em&gt;Name.com&lt;/em&gt; and &lt;em&gt;.TECH&lt;/em&gt;. I analyzed what service they provide and how much the domain is going to cost after the 1-year free trial. Namecheap has the cheapest domain name after the free trial, so I decided to use it.&lt;/p&gt;

&lt;p&gt;Now I have to configure it to redirect traffic from the domain name to the IP address of my VPS. There are &lt;a href="https://www.namecheap.com/support/knowledgebase/article.aspx/579/2237/which-record-type-option-should-i-choose-for-the-information-im-about-to-enter/"&gt;different types of records&lt;/a&gt;, the ones that you're most likely to use are an A record or a CNAME record. &lt;em&gt;A records&lt;/em&gt; allow you to associate a domain name or a subdomain to an IP address, &lt;em&gt;CNAME&lt;/em&gt; records allow you to point your domain name or subdomain to a hostname.&lt;/p&gt;

&lt;p&gt;I pointed the subdomain I wanted to the IP address of my VPS, then pointed the www version of the domain to the domain without the www part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing data
&lt;/h2&gt;

&lt;p&gt;For the encryption function of the website, I want people to be able to generate an encryption key locally and have it stored locally. So the data they post to the server will be encrypted. This makes it so that even if the website got hacked, the hackers couldn't read the encrypted content.&lt;/p&gt;

&lt;p&gt;Users don't want to enter the key every time they visit the website, I need a way to store the encryption key locally. I was thinking to use cookies for this task, but then I found out that cookies get sent back to the web server for every request, which isn't secure at all. So I searched that there is Local Storage in browsers now, and the information there will not be sent to the web server, so I decided to use it.&lt;/p&gt;

&lt;p&gt;It is very easy to use, to write something to local storage, you do &lt;code&gt;localStorage.setItem("name", "Chris");&lt;/code&gt;, and to read something, you do &lt;code&gt;let myName = localStorage.getItem("name");&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Svelte can be hosted statically
&lt;/h2&gt;

&lt;p&gt;I don't know how I didn't know this, I always thought Svelte has to be hosted on a VPS, maybe because I developed Django apps before and they require a dedicated host. I found this out because I was talking to a friend about how I am buying a VPS to host the website, and he asked me why didn't I use Github pages instead. I said the website needed to be hosted, but he said it doesn't. I looked into it, and turns out he was right! Svelte doesn't have to be hosted dynamically. When we run &lt;code&gt;npm run dev&lt;/code&gt;, it runs the developer version, which is dynamically hosted because it reacts to changes. But if we do &lt;code&gt;npm run build&lt;/code&gt;, the page is built and the &lt;code&gt;dist&lt;/code&gt; directory. Then we can deploy through any static site hosts, including Github pages.&lt;/p&gt;

&lt;p&gt;GitHub pages can be used for public repositories for free accounts and private repositories for Pro accounts, which I have from the GitHub student pack. Moreover, GitHub is a big website, so I'm sure loading a front end there is very fast, as they would have good content delivery. Security isn't a big concern either, since only the front end is hosted there, and the important data is secured in the back end. Therefore I will be using GitHub pages to host my site on second thoughts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting on GitHub Pages
&lt;/h2&gt;

&lt;p&gt;To host on Github pages, if we want to use the user GitHub page (&lt;code&gt;username.github.io&lt;/code&gt;), instead of &lt;code&gt;username.github.io/reponame&lt;/code&gt;, we need to have the website in the repository with the name &lt;code&gt;username.github.io&lt;/code&gt; . Then we can control what to deploy, we can either set up a GitHub workflow, which is GitHub doing something for you automatically, or just deploy from a branch.&lt;/p&gt;

&lt;p&gt;To automate this process, there is an npm package called &lt;a href="https://www.npmjs.com/package/gh-pages"&gt;gh-pages&lt;/a&gt;. It enables you to deploy your new version of the build, which is inside the &lt;code&gt;dist&lt;/code&gt; folder for Svelte projects, to a new branch. Then if you configure your GitHub page to deploy that branch, this would automatically be deployed.&lt;/p&gt;

&lt;p&gt;There are some important things to set up. We have to configure the homepage to the GitHub page website in &lt;code&gt;package.json&lt;/code&gt;, as well as set up a new run config called &lt;code&gt;deploy&lt;/code&gt;, which runs &lt;code&gt;gh-pages -d dist&lt;/code&gt; . If we are using a custom domain, we also need to create a &lt;code&gt;CNAME&lt;/code&gt; file inside the &lt;code&gt;public/&lt;/code&gt; directory with the content being the custom domain name. Then when we run &lt;code&gt;npm run build&lt;/code&gt; then &lt;code&gt;npm run deploy&lt;/code&gt;, our GitHub page will be deployed.&lt;/p&gt;

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

&lt;p&gt;In this blog post, I discussed my experience with self-hosting a Supabase Svelte project. I covered setting up routing with Nginx, obtaining an SSL certificate with Certbot, securing a domain name with Namecheap, and hosting the project on GitHub Pages. Overall, I found that self-hosting my project was a valuable learning experience that allowed me to gain a deeper understanding of web hosting and the tools involved.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>ssl</category>
      <category>domain</category>
      <category>githubpages</category>
    </item>
    <item>
      <title>What I learned from self-hosting a Supabase Svelte project: Part 1</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Wed, 14 Dec 2022 10:32:46 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/what-i-learned-from-self-hosting-a-supabase-svelte-project-part-1-4efa</link>
      <guid>https://forem.com/chits_programming_blog/what-i-learned-from-self-hosting-a-supabase-svelte-project-part-1-4efa</guid>
      <description>&lt;p&gt;In this blog post, I will talk about what I learned from making a Supabase Svelte project. The plan for making this project is explained in &lt;a href="https://blog.cpbprojects.me/my-plan-for-building-a-prayer-tracker-website" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt;. I will go over the obstacles I've overcome when trying the different building blocks of the project, namely self-hosting supabase, setting up Svelte, enabling third-party authentication and email sending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self hosting Supabase
&lt;/h2&gt;

&lt;p&gt;The advantage of Supabase is that it supports self-host, so I don't have to worry about limits and bills. I went ahead and searched for the &lt;a href="https://supabase.com/docs/guides/hosting/overview" rel="noopener noreferrer"&gt;official tutorial&lt;/a&gt;. I expected a very complete tutorial that covers every step, but what I got was a very high-level overview of how to do it, talking about architecture, configuration, database etc. Then there is another tutorial on &lt;a href="https://supabase.com/docs/guides/hosting/docker" rel="noopener noreferrer"&gt;self-hosting with Docker&lt;/a&gt;. To their credit, there is a very concise script for getting the docker running, but that's all you get in terms of a hands-on tutorial. The rest of the article is yet another high-level overview of important things to do. This is fine for experienced developers, but as a new developer, I would have appreciated better guidance.&lt;/p&gt;

&lt;p&gt;At that time, I didn't know what is docker-compose, how I deploy the docker from the local environment to the production environment (my VPS), and how to set up the secrets, so I kept on searching for more helpful tutorials.&lt;/p&gt;

&lt;p&gt;Luckily, I stumbled upon this &lt;a href="https://www.youtube.com/watch?v=0bqxrm4PnMA" rel="noopener noreferrer"&gt;Youtube tutorial&lt;/a&gt;, huge shout out to &lt;a href="https://www.youtube.com/@silkodyssey" rel="noopener noreferrer"&gt;Kelvin Pompey&lt;/a&gt;. He demonstrated the entire process of self-hosting Supabase with an Ubuntu Server on Digital Ocean, which I was able to follow step by step, and reproduce the working Supabase backend. I didn't use Digital Ocean, I used a VPS from &lt;a href="https://billing.oxide.host/aff.php?aff=166" rel="noopener noreferrer"&gt;Oxide Host&lt;/a&gt; since I've been using them for a few years starting from hosting a Discord bot, and the service is reliable.&lt;/p&gt;

&lt;p&gt;I learned how to set up supabase, &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-20-04" rel="noopener noreferrer"&gt;how to install docker-compose&lt;/a&gt; and that docker-compose starts the processes inside the &lt;code&gt;docker-compose.yml&lt;/code&gt; file, and to update the secrets inside the &lt;code&gt;.env&lt;/code&gt; file before deploying the Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Svelte front end
&lt;/h2&gt;

&lt;p&gt;Now that I've set up the backend, I want some example frontend code to test if the backend is working. I was originally thinking about making a React app as it is pretty popular. But I found an &lt;a href="https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list" rel="noopener noreferrer"&gt;officially maintained to-do list app&lt;/a&gt; written in Svelte, since it is very similar to a prayer tracker, I've decided to use that as the skeleton code and build on that.&lt;/p&gt;

&lt;p&gt;The code is within the &lt;a href="https://github.com/supabase/supabase" rel="noopener noreferrer"&gt;Supabase repo&lt;/a&gt;, and the Supabase repo is huge, so I don't want to clone the entire thing, just the part I need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only cloning a part of a repo
&lt;/h3&gt;

&lt;p&gt;I've learned this from &lt;a href="https://stackoverflow.com/questions/600079/how-do-i-clone-a-subdirectory-only-of-a-git-repository" rel="noopener noreferrer"&gt;this StackOverflow post&lt;/a&gt;. We need the latest version of Git, I failed at first because I was using an older version of git that doesn't support sparse checkout.&lt;/p&gt;

&lt;p&gt;I am going to use the Supabase repo as an example, the &lt;code&gt;--depth 1&lt;/code&gt; flag stops git from cloning any files inside folders. and the &lt;code&gt;--filter=blob:none&lt;/code&gt; will filter out all blobs (file contents) until needed by Git, and &lt;code&gt;--sparse&lt;/code&gt; just tells git you plan to git checkout later.&lt;/p&gt;

&lt;p&gt;Then we &lt;code&gt;cd&lt;/code&gt; into the directory, and sparse checkout, with &lt;code&gt;set&lt;/code&gt; as the path of the content we want to check out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone --depth 1 --filter=blob:none --sparse https://github.com/supabase/supabase
cd supabase
git sparse-checkout set examples/todo-list/sveltejs-todo-list

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I just needed to run &lt;code&gt;npm install&lt;/code&gt; to install the dependencies and &lt;code&gt;npm run dev&lt;/code&gt; to run the Svelte project. I also installed &lt;code&gt;nvm&lt;/code&gt; to manage the version of Node js.&lt;/p&gt;

&lt;p&gt;I also needed to update the &lt;code&gt;.env&lt;/code&gt; file to paste the Supabase host address, and the &lt;code&gt;anon_key&lt;/code&gt; used in Supabase. Side note: I used the default credentials the first time I used Supabase, when I changed the credentials, the database no longer recognised me, I had to do &lt;code&gt;docker-compose down --volumes&lt;/code&gt; to reset everything after I updated the &lt;code&gt;.env&lt;/code&gt; credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling third-party authentication
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://supabase.com/docs/guides/auth" rel="noopener noreferrer"&gt;Supabase supports third-party authentication&lt;/a&gt;, and it does that by recognising the email address. So let's say you used the same email address &lt;code&gt;example@example.com&lt;/code&gt; on Google and Facebook, signing in with either will lead you to log in to the same account.&lt;/p&gt;

&lt;p&gt;These third parties use an authentication standard Open Authentication (OAuth). OAuth is a protocol that allows applications to access user information without requiring their password. This allows users to securely share their information with third-party applications, minimizing the risk of a security breach.&lt;/p&gt;

&lt;p&gt;Basically, instead of identifying a user using a password, the app asks Google, "do you trust this guy?". If Google trusts them, the app trusts them too.&lt;/p&gt;

&lt;p&gt;To enable this, we have to create an account on the OAuth provider. I was able to do it for Google and Facebook. For Apple, the OAuth requires me to create an Apple App, and that is locked behind a paywall. I need to pay 79 pounds a year to access that, so I won't be using that.&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%2F174ncrb61kp1lq3ur9ea.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%2F174ncrb61kp1lq3ur9ea.png" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem with Twitter OAuth
&lt;/h3&gt;

&lt;p&gt;When I try to do it on Twitter, the funniest/most annoying issue happened. On the sign-up page, there is a button that asks do I want to receive marketing emails from them, I said no. Then they said they have sent a confirmation email to my email account, I didn't receive it, so I clicked have it resent. Guess what they said: "There was a problem resending the confirmation email, User has disabled email notification from Twitter...". I checked my Twitter setting and email notifications are on.&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%2F1pcfb8u1x45qliize8om.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%2F1pcfb8u1x45qliize8om.png" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then registered from the Twitter developer account on another Twitter account, this time ticking the marketing email field, this time I was able to get the confirmation screen and do it. So I think this was the issue. The funniest thing is that I was unable to fix this issue, I cannot find the setting to re-enable marketing email on the Twitter developer page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Email sending
&lt;/h2&gt;

&lt;p&gt;For email sending, originally I thought I can send emails from my VPS. So I searched for &lt;em&gt;How to send emails through VPS&lt;/em&gt;. Every result uses Gmail or another managed email service, apparently, we can't simply use a VPS to send an email, I needed something called an SMTP server.&lt;/p&gt;

&lt;p&gt;So I went on and search for &lt;em&gt;How to host an SMTP server on a VPS&lt;/em&gt;. I found &lt;a href="https://www.socketlabs.com/blog/setup-smtp-server/" rel="noopener noreferrer"&gt;an article&lt;/a&gt; that says hosting a mail server is like building a jet, the article isn't trustworthy since it is from an email-sending company and they have a monetary incentive to say this. I wanted to find some open-source solutions that I can install in 1 step and be done, but there aren't any. Therefore I concluded the article was right and I gave up making my own SMTP server.&lt;/p&gt;

&lt;p&gt;I had to find an email-sending service. I looked at different ones, SendInBlue has the largest free-email sending quota, 300 emails per day, which means 9000 emails per month. Of course, this is assuming 300 people sign up per day, which is highly unlikely, the number usually fluctuates. Still, this is the largest quota, so I went with them.&lt;/p&gt;

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

&lt;p&gt;In this blog post, I talked about the process of creating a Supabase Svelte project. Including challenges I faced when self-hosting Supabase, setting up Svelte, enabling third-party authentication and email sending. I also wanted to talk about routing, SSL, domain name and storing information inside the web browser, but I realized this article already got very long. I've decided I'll write about them in part 2 of this blog post. So stay tuned.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>oauth</category>
      <category>git</category>
      <category>email</category>
    </item>
    <item>
      <title>My Plan for building a prayer tracker website</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Tue, 13 Dec 2022 20:30:45 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/my-plan-for-building-a-prayer-tracker-website-4bg7</link>
      <guid>https://forem.com/chits_programming_blog/my-plan-for-building-a-prayer-tracker-website-4bg7</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;As Christians, we should pray. it is a way for us to communicate with God and develop a deeper relationship with Him. In the Parable of the Unjust Judge, Jesus taught us not to lose heart when praying. But sometimes, we forget to pray, forget what to pray about, and forget how God answered our prayers. Imagine if our friend forgot to talk to us, or even worse, forgot what we did for them.&lt;/p&gt;

&lt;p&gt;That's why we should keep track of what we pray about. It can help us see how God is answering our prayers, as well as help us stay accountable and faithful in our prayer life.&lt;/p&gt;

&lt;p&gt;Using a paper journal or a note-taking app to do so is fine, but searching through paper journals is hard, and note-taking apps may not be as convenient as they are not tailor-made for this purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I want to make it
&lt;/h2&gt;

&lt;p&gt;Firstly, I see the need for it. While there are some great options out there, such as &lt;a href="https://www.prayermate.net/app" rel="noopener noreferrer"&gt;PrayerMate&lt;/a&gt; or the &lt;a href="https://www.youversion.com/the-bible-app/" rel="noopener noreferrer"&gt;YouVersion Bible app&lt;/a&gt;, they are limited to smartphones only, you cannot access them with a computer. Moreover, they are very complex with many functionalities. It would be nice to have a simpler one. Because&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.&lt;/p&gt;

&lt;p&gt;Antoine de Saint-Exupry, Airman's Odyssey&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Secondly, I am interested in trying out different technologies, and this sounds like a cool challenge. I can see this actually being helpful for others. I'm sure I'd learn a lot from doing this.&lt;/p&gt;

&lt;p&gt;That's why I've decided to develop it and make it available to the public.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features of the prayer tracker website
&lt;/h2&gt;

&lt;p&gt;I want to build a website where users can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Store prayer items, including the ability to add, edit, and delete items.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Give users a daily prayer list based on the frequency set for each prayer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Record how has God answered their prayer, or when the prayer was not granted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Encrypt the prayer items optionally&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since it is a website, we can assume the prayer items will be synced, and there will be account control. I choose a website over an app because a website can be accessed by any device connected to the internet, whereas apps must be downloaded and installed on a specific device.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I plan to build it
&lt;/h2&gt;

&lt;p&gt;I've been following &lt;a href="https://www.youtube.com/@Fireship" rel="noopener noreferrer"&gt;Fireship&lt;/a&gt; on Youtube, so I've got some technologies on my radar that I want to try out. &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; is one of them, it claims to be an open-source Firebase alternative Backend as a Service (BaaS). I'm happy to see that they have a self-host option, which means I wouldn't be charged a large sum overnight, as I am hosting it myself, I can control how many resources to give it.&lt;/p&gt;

&lt;p&gt;For the front end, I'm planning to use &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;, simply because it is the javascript framework used in the Supabase to-do list example. I'm assuming it is a good framework for the official example to be built on that. I've also looked up the Fireship video on Svelte, and it looks promising.&lt;/p&gt;

&lt;p&gt;For hosting the back end and front end, I'm going to use a VPS provider that limits my CPU and connection speed, but doesn't incur any additional cost nor have a data transmission limit, in my case, &lt;a href="https://billing.oxide.host/aff.php?aff=166" rel="noopener noreferrer"&gt;Oxide Host&lt;/a&gt;. It is much less stressful knowing that I won't be charged extra even if something went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the other building blocks?
&lt;/h2&gt;

&lt;p&gt;I've decided to use Supabase, Svelte and a VPS as the foundation of my project. Now I have to choose the various building blocks that I'm going to use to build my website.&lt;/p&gt;

&lt;p&gt;Before that, I want to talk about the two approaches to building something. The first approach is to start doing first and plan as you go, this leads to quicker development if nothing goes wrong, but it is also possible that you'll encounter a problem that sets you back a lot. The second approach is to plan everything before starting, this reduces the risk of finding problems mid-project and having to redo everything, but this slows down the progress, and there is only so much we can plan for, we may still find unexpected problems. I will use the latter approach and test each individual building block before using them to build the website.&lt;/p&gt;

&lt;p&gt;At the time of writing this, I've already tested out all the technologies. The process is very difficult, I've been stuck at problems after problems. This process took me several days, and over 24 hours of figuring things out, so I'm going to spare the details and just explain what I'll be using. Later on, I'll write another blog post to talk about how I tested them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Functionality&lt;/th&gt;
&lt;th&gt;Technology used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Third-party authentication&lt;/td&gt;
&lt;td&gt;Google and Facebook sign-in, as they are the most popular ones. I refrained from using Apple sign-in because it is locked behind a paywall, you need to pay to join their developer program to use it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing&lt;/td&gt;
&lt;td&gt;Nginx, since it is very popular.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Https, SSL&lt;/td&gt;
&lt;td&gt;Certbot, since it is free and easy to set up.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain name&lt;/td&gt;
&lt;td&gt;NameCheap, since it is cheaper than others and offers a 1-year free domain for students.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storing encryption key locally&lt;/td&gt;
&lt;td&gt;Local Storage in the browser, because unlike cookies, they do not get sent back to the server every request.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email Authentication&lt;/td&gt;
&lt;td&gt;MailInBlue, a relatively large email limit in the free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;This is my plan to build this project. I will make a blog talking about my experience with testing the above building blocks, as well as a complete tutorial on how to set up a Supabase Svelte project.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>supabase</category>
      <category>svelte</category>
      <category>planning</category>
    </item>
    <item>
      <title>Python script to find broken links in word document</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Sun, 11 Dec 2022 22:22:22 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/python-script-to-find-broken-links-in-word-document-2jgf</link>
      <guid>https://forem.com/chits_programming_blog/python-script-to-find-broken-links-in-word-document-2jgf</guid>
      <description>&lt;p&gt;I wrote a Python script to find broken links in a word document, the &lt;a href="https://github.com/chit-uob/findBrokenLink" rel="noopener noreferrer"&gt;GitHub link is here&lt;/a&gt;. If you want to use it you can go to the GitHub page and the instructions are there. Below I will explain how it works and how I came up with this solution.&lt;/p&gt;

&lt;p&gt;This program was inspired by helping a friend. The friend has to click on links inside documents one by one to check if they still work. Hearing that, I thought to myself, that sounds like a task that can be automated. Therefore I asked for some sample word documents and started testing the concepts.&lt;/p&gt;

&lt;p&gt;In face of this big task, I decided to break the task into a few steps, figuring out a way to do each, and then piece them together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Finding links from texts
&lt;/h2&gt;

&lt;p&gt;If I want to extract some patterns from text, the first thing that pops into my mind is Regular Expression. They are a way to find patterns in texts and are often deemed difficult and feared by developers. Therefore I did what any sane developer would do: search for this problem online and copied the Regex from stack overflow.&lt;/p&gt;

&lt;p&gt;The regex is &lt;code&gt;(https?:\/\/\S+)&lt;/code&gt;, which is pretty easy to understand&lt;/p&gt;

&lt;p&gt;| regex | meaning |&lt;br&gt;
| http | http |&lt;br&gt;
| s? | s for zero to one times |&lt;br&gt;
| :// | ://, / stands for an escaped / |&lt;br&gt;
| \S | any non-whitespace character |&lt;br&gt;
| + | the previous token, \S, from zero to infinite times |&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem 1: the succeeding full-stop is also matched
&lt;/h3&gt;

&lt;p&gt;Let's say I have a sentence that ends in a link, such as &lt;a href="http://chit.hashnode.com" rel="noopener noreferrer"&gt;http://chit.hashnode.com&lt;/a&gt;. If I parse this text in the above regex, the last full-stop (.) will also be included, because it is a non-whitespace character.&lt;/p&gt;

&lt;p&gt;the solution is to use another regex, &lt;code&gt;http[s]?:\/\/[^\s]+[^.]&lt;/code&gt;, here we have a &lt;code&gt;[^.]&lt;/code&gt;, which means: Match a single character not period (.) and not whitespace ( ), so it solves the problem of it catching the trailing period.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem 2: having a newline character at the end
&lt;/h3&gt;

&lt;p&gt;The previous regex solves the problem of having a period at the end of the line. But what if there is a newline character after it?&lt;/p&gt;
&lt;h4&gt;
  
  
  Knowledge dump: What is a newline character
&lt;/h4&gt;

&lt;p&gt;The newline character is the invisible character that tells the text editor to go to the next line. For example, let's say &lt;code&gt;\n&lt;/code&gt; is the newline character, &lt;code&gt;I am a line.\nI am another line&lt;/code&gt; will become&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I am a line.
I am another line

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's use the website &lt;a href="https://regex101.com/" rel="noopener noreferrer"&gt;regex101&lt;/a&gt; to test it, when we have &lt;code&gt;A sentence ending in https://google.com.&lt;/code&gt;, the &lt;code&gt;https://google.com&lt;/code&gt; will be correctly captured.&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%2F5533jlg6dbb34qjpo5dp.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%2F5533jlg6dbb34qjpo5dp.png" width="494" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But with a newline character after it, it will be caught as well, since the newline character was not excluded in &lt;code&gt;[^.]&lt;/code&gt;.&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%2Fv2v9o2yyigvc76skwnt6.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%2Fv2v9o2yyigvc76skwnt6.png" width="510" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I am writing this blog post, I figured the solution was to exclude the newline character as well, resulting in the regex &lt;code&gt;http[s]?:\/\/[^\s]+[^.]&lt;/code&gt;, but I was not this clear-minded when I was programming, the solution I came up with is to replace all the newline characters with the space bar. Then do the regex matching, which also worked, because there are no longer newline characters.&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%2Fpidgovcvupiistvt5xnv.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%2Fpidgovcvupiistvt5xnv.png" width="502" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: Different types of links
&lt;/h3&gt;

&lt;p&gt;The third problem is with links with no &lt;code&gt;http&lt;/code&gt;, our regex only matches links starting with &lt;code&gt;http&lt;/code&gt;, but some links start with &lt;code&gt;www&lt;/code&gt;, or maybe even no &lt;code&gt;www&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I am sure more clever regexs can accommodate this, but I didn't want to spend too much time at this stage, so I just used a python library to do the work for me. That's the beauty/problem with Python, there are so many libraries that you can just use one, and not care how it is implemented.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from urlextract import URLExtract
urlextracter = URLExtract()
urls = urlextracter.gen_urls(s)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 Check the links
&lt;/h2&gt;

&lt;p&gt;Now that we have all the URLs from a text extracted, we want to check them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 4: links without a protocol specified
&lt;/h3&gt;

&lt;p&gt;But remember how some links don't start with HTTP? I need to add an HTTP in front of them, or else the python library request will have a hard time knowing what protocol is required, so I used the following code to achieve that&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%2Frhjka6qgyk935g90nn89.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%2Frhjka6qgyk935g90nn89.png" width="800" height="217"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import re
def formaturl(url):
    if not re.match('(?:http|ftp|https)://', url):
        return 'http://{}'.format(url)
    return url

urls = [formaturl(url) for url in urls]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here I try to match the regex &lt;code&gt;(?:http|ftp|https)://&lt;/code&gt;, it tests if the URL starts with http/ftp/https, if not, we append &lt;code&gt;http://&lt;/code&gt; in front of it&lt;/p&gt;

&lt;p&gt;then we use list comprehension to do it on the entire url list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Actually checking the link
&lt;/h3&gt;

&lt;p&gt;Now we get to the step of actually checking the link, we do that using the &lt;code&gt;requests&lt;/code&gt; library. We wrap the requests.get() inside a try-catch block so that if other issues happen, the program will not crash, it will simply return false. and if the status_code of the response isn't 200, then we return false too, else we return true.&lt;/p&gt;

&lt;p&gt;200 means all good, so if the website returns standard content, it will return all good.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def check_link(url):
    print(f"checking {url}")

    # Try and see if url have inherit problem
    try:
        response = requests.get(url)
    except:
        return False

    # See if not 200
    if not response.status_code == 200:
        return False

    return True

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Reading word document text
&lt;/h2&gt;

&lt;p&gt;Now I have a function that extracts URLs, and another to check if the url's website is working. I have to extra the text from a word document.&lt;/p&gt;

&lt;p&gt;To do that, I can simply use the save-as function inside word and call it a day. That would save the document in a text format and we can read the text file with the program and get the result.&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%2Fzz7ufv2ju533im4dbt1k.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%2Fzz7ufv2ju533im4dbt1k.png" width="745" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But that would mean the user has to do more, so I was thinking, is it possible to read the word document directly with Python?&lt;/p&gt;

&lt;p&gt;The answer is yes because actually, word documents are just zip files. To know if I'm telling you the truth, install &lt;a href="https://www.7-zip.org/" rel="noopener noreferrer"&gt;7zip&lt;/a&gt; and unzip a word document, and you will see the following result. A word document is just a zip file containing a lot of XML files.&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%2Fjsbs08t98ha2w65glwem.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%2Fjsbs08t98ha2w65glwem.png" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A way to solve this problem is to read the word document as a zip file and find the links inside the XML files. But since I'm using Python, I decided to find if there are any libraries that can do this for me. I found &lt;code&gt;docx&lt;/code&gt;, &lt;code&gt;docx2txt&lt;/code&gt;, &lt;code&gt;docx2python&lt;/code&gt;. After testing them all, the one I decided to use is &lt;code&gt;docx2python&lt;/code&gt;, because it extracts the main text, headers, footers, and even footnotes. The syntax is as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from docx2python import docx2python

# extract docx content
def get_text_by_docx2python(path):
    text = docx2python(path).text
    return text

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 Piecing them all together
&lt;/h2&gt;

&lt;p&gt;Now that all parts of the puzzle are here, it is time to implement the main logic, we first extract the text by &lt;code&gt;use_docx2python.get_text_by_docx2python&lt;/code&gt;, then &lt;code&gt;urlextracter.gen_urls&lt;/code&gt; to get the URLs, after adding &lt;code&gt;http&lt;/code&gt; to them, we use &lt;code&gt;check_links.check_link&lt;/code&gt; to check the link, and print a helpful message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem: some links show a different result when we request it from the program than when we actually click the site
&lt;/h3&gt;

&lt;p&gt;This is because some sites do not like robots, but since Python requests have the default user agent as &lt;code&gt;python-requests/2.25&lt;/code&gt;, the website sees this and forbids the program from getting the result. What we need to do to fix it is to use another user agent, I copied the user agent on my web browser, &lt;code&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36&lt;/code&gt;, then I used &lt;code&gt;response = requests.get(url, headers=HEADERS, stream=True)&lt;/code&gt;, now the website responds correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 Doing it asynchronously
&lt;/h2&gt;

&lt;p&gt;Now waiting for the program to do all the things is fine, but it is also too slow. The slowest part is waiting for the websites to be fetched. Since we have to wait for each website to give us the result. This would be faster if we use concurrency, which means doing things at the same time.&lt;/p&gt;

&lt;p&gt;Think of it as, burger shop A gives you a burger in 5 minutes, and Boba shop B gives you a boba in 3 minutes. You can get a burger and then a boba in 8 minutes. If you order both at the same time and then collect them when they are ready, you'll only need 5 minutes.&lt;/p&gt;

&lt;p&gt;Concurrency code is a bit more complex so I'm not going to explain it here, you can visit &lt;a href="https://realpython.com/python-concurrency/" rel="noopener noreferrer"&gt;RealPython&lt;/a&gt; on this topic, they have a great tutorial on this topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 Warp up
&lt;/h2&gt;

&lt;p&gt;At last, I added a code to show a file dialogue to further simplify this for users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import tkinter as tk
from tkinter import filedialog
file_path = filedialog.askopenfilename()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;That's how I did it and I hope you learned something from it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>microsoftword</category>
      <category>regex</category>
    </item>
    <item>
      <title>Where's my Voi scooter: [Conclusion] What I learned from Voi scooter data this summer</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Tue, 20 Sep 2022 09:48:21 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/wheres-my-voi-scooter-conclusion-what-i-learned-from-voi-scooter-data-this-summer-18bd</link>
      <guid>https://forem.com/chits_programming_blog/wheres-my-voi-scooter-conclusion-what-i-learned-from-voi-scooter-data-this-summer-18bd</guid>
      <description>&lt;p&gt;This blog post concludes what I learned from the data collected in the &lt;a href="https://chit.hashnode.dev/series/where-is-my-voi-scooter"&gt;Where's my Voi scooter series&lt;/a&gt;. I started this study at the beginning of the summer aiming to find ways to locate a Voi scooter by collecting data on Voi scooters. I tracked the scooter location and the battery level of scooters in my city at one-minute intervals using the Application Program Interface (&lt;a href="https://aws.amazon.com/what-is/api/"&gt;API&lt;/a&gt;) on their mobile app. Below is a summary of what I have learned from the study.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mean battery level of scooters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Graph for the entire duration
&lt;/h3&gt;

&lt;p&gt;This is the mean battery level of all scooters around the city during the entire duration of the project. The graphs may look small, you can right-click the image and click &lt;code&gt;open image in new tab&lt;/code&gt; to enlarge them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tsQuCjXY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663335774138/2M_uyA5tQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tsQuCjXY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663335774138/2M_uyA5tQ.png" alt="1.png" width="880" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In general, the mean battery level fluctuated between 55% and 60%.&lt;/p&gt;

&lt;p&gt;We can observe that before 7 July, there were flat lines every day. This was because back then scooters went offline at 10 pm, and online at 6 am. During that time my program could not collect data and it assumed the battery level didn't change. After 7 July, you can see that the transitions in the graph were smooth again with no jumps.&lt;/p&gt;

&lt;p&gt;A scooter "goes offline" when the API no longer returns information about the scooter. The API that I used is the API that tells users where the idle scooters are. So when someone is using the scooter, when the battery of the scooter is being charged, or when the scooter is sent back to the warehouse, the scooter is considered "offline".&lt;/p&gt;

&lt;p&gt;We can also see errors in the data collection. There were two sloped straight lines during June, as back then my program would crash if the API server didnt return information. This was fixed afterwards by adding a try-catch and retrying after failures. The second error was on 1 July, the graph broke into half. This is because I calculated the mean battery level each month, and the program guesses the battery levels of offline scooters by forward filling, ie. if the scooter went offline with 70% battery, the program assumes it would stay at 70% while being offline. When the new month started, the scooters with unknown battery levels were not included in the calculation, so there was a jump in the value. The reason I used forward filling despite this disadvantage is given in &lt;a href="https://chit.hashnode.dev/wheres-my-voi-scooter-7-analysis-of-scooter-battery-with-graphs"&gt;the 7th blog in this series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can also see a sharp decline in battery levels around 7 July. This might be because scooters no longer went offline at night and the scooter service area was expanded, so the battery drained even during the night, and with a wider area, it was more difficult to reach them and replace the battery of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hsOp5YBu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663337429768/PW0XSt1lV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hsOp5YBu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663337429768/PW0XSt1lV.png" alt="2.png" width="865" height="758"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph for weeks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HRq_SKv3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663337919371/ETDIxOF8M.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HRq_SKv3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663337919371/ETDIxOF8M.png" alt="3.png" width="880" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These graphs were weeks starting from 10 July, they all started on Sundays. There was a general trend that the battery level was higher in the second half of the week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph for days
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wGfTH7Le--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663338279692/e23KK63Ql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wGfTH7Le--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663338279692/e23KK63Ql.png" alt="4.png" width="880" height="757"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These graphs were some days, they all started at around 0 am. There was a general trend that the battery level would increase in the first half of the day, usually, at 10 am, then start decreasing. This was strange, an increase in battery level should mean someone was swapping out the batteries, or new scooters with a full battery was being introduced. But it was hard to imagine people swapping batteries in the middle of the night.&lt;/p&gt;

&lt;h2&gt;
  
  
  Available scooter count
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Graph for the entire duration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bXVXIKmy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663345088354/b9EPikZP5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bXVXIKmy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663345088354/b9EPikZP5.png" alt="5.png" width="880" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same as the mean battery level, the days before 7 July had the count drop to zero at night because they were offline. And the number of available scooters also sharply declined from around 1150 scooters to under 500 scooters on 11 July. Presumably, it was because of the change in operation too.&lt;/p&gt;

&lt;p&gt;We can also observe that more scooters were put into operation from 26 July, I believed it was due to the Commonwealth Games 2022 on 28 Jul 2022 8 Aug 2022. When the Commonwealth Games started, the number of available scooters, along with the mean scooter battery level dropped. It should be because of the increased ridership. This can be confirmed on &lt;a href="https://www.intelligenttransport.com/transport-news/138525/voi-ridership-2022-commonwealth-games/"&gt;the news&lt;/a&gt; where it said Voi sees record e-scooter ridership during 2022 Commonwealth Games at over 66,000 journeys.&lt;/p&gt;

&lt;p&gt;Then the count returned to normal until 16 Aug but the count increased again on 19 Aug when the number of scooters exceeded 1750 scooters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph for weeks
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wvzCdH4t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663346756796/H4cBJ5Nd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wvzCdH4t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663346756796/H4cBJ5Nd4.png" alt="6.png" width="880" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are graphs of weeks starting from Sunday. There seemed to be a slight trend where the number of scooters in the second half of the week was higher.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph for days
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xy4C9K4b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663347012233/PLwDWH4JL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xy4C9K4b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663347012233/PLwDWH4JL.png" alt="7.png" width="880" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are graphs of days starting at 0 am, usually, the number of scooters increases to about the middle of the day, then decreases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ridership
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Total graph
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_mNp6Wp8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663534469329/gxJbKZYZN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_mNp6Wp8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663534469329/gxJbKZYZN.png" alt="8.png" width="880" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This graph corresponded positively to the available scooter count graph, when more scooters became available, more rides happened too. In July, there are fewer rides, but in July and August, the number of rides increased.&lt;/p&gt;

&lt;p&gt;At the beginning of the graph, there were two places where the count hit close to zero, that was because of the error of my data collection program. My program crashed during that two times so I wasn't able to recognise rides during that two periods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commonwealth games
&lt;/h3&gt;

&lt;p&gt;With this data, we can answer the question: did ridership increase during the Commonwealth Games 2022? The answer is it did. From 28 Jul 2022 to 8 Aug 2022, the ridership peaked at around 6400 rides per day, but the increase didn't stop there, it seemed even after the Commonwealth Games and the increase in scooter count and coverage, the increased ridership was maintained.&lt;/p&gt;

&lt;p&gt;The sum of the rides during the Commonwealth Games calculated in my program is 65951, which was super close to 66000 said in &lt;a href="https://www.intelligenttransport.com/transport-news/138525/voi-ridership-2022-commonwealth-games/"&gt;this article&lt;/a&gt;. There was a discrepancy in the number due to the logic of my program. A ride was counted in my program when a scooter became offline and then online again within 45 minutes. But it did this at a one-minute interval. So if a rider parked a scooter and another rider rented it within a minute, my program would count it as one ride when there were supposed to be two. Another problem was that when a scooter temporarily went offline for a battery swap, my program still considered it a ride since the scooter went offline and then online.&lt;/p&gt;

&lt;p&gt;I limited the maximum number of minutes in a ride because scooters might be taken to the warehouse, fixed, and re-released, so I could not count all instances of scooters being offline and then online again as a ride, so I chose 45 minutes as it was the maximum riding duration for a Voi pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per week
&lt;/h3&gt;

&lt;p&gt;Now I want to answer the question, does the ridership correspond with weekdays?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y-PoO36g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663536657898/TsWtslP_-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y-PoO36g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663536657898/TsWtslP_-.png" alt="9.png" width="880" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These weeks were the same as the ones with available scooter count. They generally corresponded with each other, days with more available scooters generally result in more ridership. There was no clear pattern in which weekdays had greater ridership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per day
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WoBqN-60--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663537403311/8Jt15q5tN.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WoBqN-60--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663537403311/8Jt15q5tN.png" alt="10.png" width="880" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same days were chosen as the ones in the available scooter count graphs. These graphs were generally the opposite of those graphs, the times with the least available scooter were the times with most rides, which makes sense because people were using scooters so there are fewer of them available.&lt;/p&gt;

&lt;p&gt;We can also see on most days the ride count was minimum in the morning around 5 am, then increased to peak at 8 am, then fell back again, then increased again from 12 pm to around 8 pm, then slowly decreased. I think the first peak was when people get to work, and the second peak was for people leaving work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heatmap
&lt;/h2&gt;

&lt;h3&gt;
  
  
  day
&lt;/h3&gt;

&lt;p&gt;I figured it made little sense to measure this over weeks or the entire period, so I decided to only measure this over a day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QaQ3VHtb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663586408033/OMPBdRRmF.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QaQ3VHtb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663586408033/OMPBdRRmF.gif" alt="11.gif" width="864" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d_hTHZe0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663586564548/SGwHBl6dt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d_hTHZe0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663586564548/SGwHBl6dt.gif" alt="12.gif" width="864" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scooters generally became more concentrated on certain spots on the map from 0 to 12 pm, then became more spread out after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Battery consumption
&lt;/h2&gt;

&lt;h3&gt;
  
  
  During ride
&lt;/h3&gt;

&lt;p&gt;The way I calculated this was the same as ridership, so this analysis suffered from the same problems. However, I think these results would still be very close to the true value, as the calculation only fails in a few edge cases. The average level of battery drop was 0.461% per minute when unlocked. The battery consumption varied in each ride since scooters could be idle while unlocked, or be used in battery-consuming activities such as going up slopes.&lt;/p&gt;

&lt;p&gt;I also filtered the battery consumption based on which month the data was collected, and found a surprising result. The battery consumption was decreasing. This is strange since I was taking on average all rides, so there shouldn't be much difference. Some possible explanation for this phenomenon was a change in rider behaviour, a change in battery capacity, or Voi figuring out a way to use less battery on its scooters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u9hHvZba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663611599390/-ZPG2pLei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u9hHvZba--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663611599390/-ZPG2pLei.png" alt="13.png" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also filtered the results based on the starting battery level, and there are some interesting observations too. The results for the first half make sense, it is the same for our phones, the battery generally drains slower at a higher battery level. However, the battery drain decreased when it is below 40%, which is strange, usually, the battery drains the quickest when it is closest to zero.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qsFoBMXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663612238244/tSV9pgRt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qsFoBMXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663612238244/tSV9pgRt8.png" alt="14.png" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  When idle
&lt;/h3&gt;

&lt;p&gt;I also wanted to know what is the battery draining speed when the scooter is sitting there idle. The average battery drained, while the scooter was idle, was 0.006830% per minute.&lt;/p&gt;

&lt;p&gt;Starting at different battery levels, the draining speed was different too, the less battery there was, the faster the battery drains, this was what I had expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6FcZUo8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663614258451/mKSKEvBea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6FcZUo8U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663614258451/mKSKEvBea.png" alt="15.png" width="880" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my experience, lithium batteries, used in smartphones, usually drain faster when the battery level is lower. I wasn't able to pin down why, but I saw some explanations on &lt;a href="https://www.reddit.com/r/askscience/comments/buttbd/does_each_percent_of_your_phone_battery_last_the/"&gt;the&lt;/a&gt; &lt;a href="https://www.quora.com/Why-does-my-phone-battery-drop-from-20-to-1-in-about-1-minute-but-then-will-rest-on-1-for-a-good-amount-of-time"&gt;internet&lt;/a&gt; what I would like to share, one has to do with the lithium discharge voltage curve, in a lower stage of charge the voltage decreases and therefore the current has to be increased to maintain the power output, therefore the battery drains quicker. But I wasn't able to find solid proof of this, so if you know more, please let me know in the comments down below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--knuZn1u0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663613962183/mozSUFjcX.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--knuZn1u0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1663613962183/mozSUFjcX.png" alt="16.png" width="594" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This brings us to another question, why during rides, the battery drained on 0-20% wasnt the quickest, but during idle, the battery drained on 0-20% was the quickest? It could be an error in my data processing. It could also be that Voi's display battery level was not the actual battery level, it displayed a lower level when the battery was near zero so that it could travel further during the supposed near zero battery, to prevent the scooter from running out of battery mid-ride, but this is just a theory.&lt;/p&gt;

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

&lt;p&gt;This project started out with the goal of locating a scooter in the city, which I am now able to do with the data I collected. But I found greater interest in studying the broader trend of scooters around my city. So below are my findings:&lt;/p&gt;

&lt;p&gt;I can conclude that the scooter business is doing well. The ridership increased during the Commonwealth Games 2022 and the usage didn't drop back down after the games. The batteries are being replaced in time so their mean battery level and available scooter count are stable. The scooter's battery drain when idle is about 65 times slower than when on a ride, so leaving scooters on the street isn't costing them a lot.&lt;/p&gt;

&lt;p&gt;Other than what I learned from the data, I also learned a lot about data analysis in the project, like how to collect and organize data, how to analyse them, and some handy Python modules to make it easier.&lt;/p&gt;

</description>
      <category>voi</category>
      <category>programming</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Where's my Voi scooter: [10] Analysis of data in July 2022</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Wed, 07 Sep 2022 11:03:11 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/wheres-my-voi-scooter-10-analysis-of-data-in-july-2022-2lma</link>
      <guid>https://forem.com/chits_programming_blog/wheres-my-voi-scooter-10-analysis-of-data-in-july-2022-2lma</guid>
      <description>&lt;p&gt;In this blog, I will analyse the data of Voi scooters in my area in July using the Python program I built previously. All the messy code will not be shown here, but you can see the beautiful plots and graphs generated from the data. If you are interested in the code behind this, you can check out my &lt;a href="https://chit.hashnode.dev/series/where-is-my-voi-scooter"&gt;Where's my Voi scooter series&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downloading the data
&lt;/h2&gt;

&lt;p&gt;I have been using a VPS (virtual private server) to collect the data regularly and automatically. Therefore I have to first download the data from it using a tool called &lt;a href="https://termius.com/"&gt;Termius&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1OBN4Lp9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118562471/9vRTXYyBo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1OBN4Lp9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118562471/9vRTXYyBo.png" alt="1.png" width="880" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I selected all the data items from the start of July and copied them to my local machine. There are about 2GB of data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recap of what data I collect
&lt;/h3&gt;

&lt;p&gt;I collect the data of all Voi scooters around my city every minute by using the API provided by the Voi mobile app. The data includes each available scooter's id, battery level and location. I modify it into a JSON list. Then I save the result to a JSON file every hour. Therefore the data I have looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OiucwGi9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118568024/vahv3V2yp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OiucwGi9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118568024/vahv3V2yp.png" alt="2.png" width="880" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing the data and plotting the graph
&lt;/h2&gt;

&lt;p&gt;All data files are in the same folder, so I generate a list of all file paths inside the folder and sort them in ascending order. Then I sanitise the data by trying to parse it using Python's try-catch. If the data file is correct, Python would not catch any error, so if Python caught an error, I know I have to fix the data file. Once that's done, I read all the files in ascending order and stored the time stamp and vehicle count in two separate numpy arrays. I then plot it using matplotlib. The graph looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sk3ZIuXL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118572386/8iSGGlzNn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sk3ZIuXL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118572386/8iSGGlzNn.png" alt="3.png" width="880" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can observe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before 7/7, the scooter count always goes to zero at the end of the day. Because scooters are disabled at night, after that, Voi decided that scooters would be available 24/7, therefore the scooter counts never dropped to zero again.&lt;/li&gt;
&lt;li&gt;Near 12/7, the scooter count dropped to very low, that is just after the change, so maybe Voi did some changes in its workforce so that fewer workers are replacing batteries on scooters, or they are doing some checking on scooters so a lot is brought back for checking. Either way, the scooter count increased and returned to normal.&lt;/li&gt;
&lt;li&gt;After 30/7, the number of available scooters seems to plummet, I suspect that is because of the Commonwealth games.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Finding the set of all available scooters
&lt;/h2&gt;

&lt;p&gt;Then I run through all files, running the set union function from Python, to find the set of all scooters available throughout July. I do that by the list of all available scooters at each time stamp to a set, and take the union of the set of all scooters registered. So in the end, the union of all sets is the total amount of available scooters.&lt;/p&gt;

&lt;p&gt;After running that code, I found out there were 2544 scooters of unique id that were available at a point in July.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get all dates list
&lt;/h2&gt;

&lt;p&gt;What I want is a gigantic pandas table with scooter ids as columns, and time stamps as rows. I already know the ids, so I have to find the time stamps, I do so by iterating through all the data items and adding the time stamps to a list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fill all the tables
&lt;/h2&gt;

&lt;p&gt;I run the code to fill all the tables, it does this by iterating through all the data files, in each individual data item, and adding them to the corresponding pandas table. This code is extremely slow because we have 44499 data of individual time stamps. And in each data, there are 1000 scooters, and for each scooter, we are storing their latitude, longitude and battery level, so we are adding 44499 &lt;em&gt;1000&lt;/em&gt; 3 items to tables, which is over one hundred million. The code wasn't able to finish in 2 hours, therefore I had to stop it, and continue the code from where I left off the other day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting individual scooters
&lt;/h2&gt;

&lt;p&gt;Some scooters are more active, like&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yUGB_fx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118580013/Ob_b7kEo3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yUGB_fx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118580013/Ob_b7kEo3.png" alt="4.png" width="880" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some are decommissioned for some time, maybe for fixing, or just recycling scooters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7lJLJZhW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118588230/WDU1Krs_6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7lJLJZhW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118588230/WDU1Krs_6.png" alt="5.png" width="880" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But keep in mind that we only know the id of the scooter, the same scooter could have its id switched, we don't know what the id means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting the mean battery count
&lt;/h2&gt;

&lt;p&gt;now I wish to plot the mean scooter battery count. I do so by first dropping columns that were all null values. then I use the method of forwarding filling to fill in null values. Then at last I plot the graph using the plot function and the mean function with axis = 1. there are null values in my code reasons: when a scooter is rented, it is no longer available, so the value becomes null. when the scooter is called back for fixing or other purposes, it is not available. Before scooters are available all day, at night, all scooters are not available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L_gYUxww--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118594549/9K5JhsETO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L_gYUxww--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1662118594549/9K5JhsETO.png" alt="6.png" width="880" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting location heatmap
&lt;/h2&gt;

&lt;p&gt;Then I generate the heatmaps using seaborn and matplotlib, I generate one for each hour, then piece them together as a gif file. The negative of having it as a gif file is that a gif file generally has a bigger file size as it compresses images individually, while mp4 is capable of storing the change between images, hence if the images in the video are similar, a lot of storage space can be saved. I convert the gif to mp4 using the answer suggested by &lt;a href="https://stackoverflow.com/a/40726572/16929051"&gt;this StackOverflow answer&lt;/a&gt; by using moviepy. and the result is as below.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EljqGsqNzd4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary and what's next
&lt;/h2&gt;

&lt;p&gt;There are a lot of interesting observations that can be made using these data. But for the length of this blog, I am just going to show the data collected and fewer observations. Again if you are interested in how the data was collected, you can check out &lt;a href="https://chit.hashnode.dev/series/where-is-my-voi-scooter"&gt;Where's my Voi scooter series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think there are many conclusions to be drawn, such as where will scooters be concentrated at what time, and around what time most scooters are available.&lt;/p&gt;

</description>
      <category>voi</category>
      <category>programming</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Python program running at regular time interval tutorial using time and datetime module</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Wed, 24 Aug 2022 10:57:28 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/python-program-running-at-regular-time-interval-tutorial-using-time-and-datetime-module-19o2</link>
      <guid>https://forem.com/chits_programming_blog/python-program-running-at-regular-time-interval-tutorial-using-time-and-datetime-module-19o2</guid>
      <description>&lt;p&gt;This tutorial will teach you how to set up a Python program to run at regular time intervals using the time and datetime module. This is useful if you want to check something constantly, so you have a python program running in the background doing the checking for you at regular intervals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;I will put the solution on top. If you are interested in how I found it, you can read about it below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple solution for if what you do is lightweight
&lt;/h3&gt;

&lt;p&gt;Lightweight means that it doesn't use much CPU power and time, like a simple print function.&lt;/p&gt;

&lt;p&gt;Assuming you want to run the code every 10 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import time
from datetime import datetime

def do_things():
    print(datetime.now())

while True:
    do_things()
    time.sleep(10)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you should get something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-08-24 10:59:35.247516
2022-08-24 10:59:45.259224
2022-08-24 10:59:55.272715
2022-08-24 11:00:05.284097
2022-08-24 11:00:15.290823

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you have it, you can replace the sleep time if you want a different interval, and you can replace the content of the do_things() function with what you want the program to do.&lt;/p&gt;

&lt;h4&gt;
  
  
  Problem with the simple solution
&lt;/h4&gt;

&lt;p&gt;But if you are like me, what you wish to do every interval takes time, whether it is waiting for I/O from another program, or it is doing something very CPU intensive. Then if we still do what we did, we will get inaccuracies in wait time like the below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import time
from datetime import datetime

def do_things():
    number = 50_000_000
    sum(i*i for i in range(number))
    print(datetime.now())

while True:
    do_things()
    time.sleep(10)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I used &lt;code&gt;sum(i*i for i in range(number))&lt;/code&gt; on a huge number to force the CPU to do a lot of work, don't mind the underscore in 50_000_000, it is the same as 50000000. The result of this program is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-08-24 11:07:54.377208
2022-08-24 11:08:07.599538
2022-08-24 11:08:20.838108
2022-08-24 11:08:34.043164
2022-08-24 11:08:47.255688

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we want the program to execute every 10 seconds, but it took the program about 14 seconds each loop, this is because some extra time is spent computing. This is why you need the advanced solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced solution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import time
from datetime import datetime, timedelta

def do_things():
    number = 50_000_000
    sum(i*i for i in range(number))
    print(datetime.now())

while True:
    previous_datetime = datetime.now()
    do_things()
    time_took_running_do_things = datetime.now() - previous_datetime
    remaining_time_in_secs = (timedelta(seconds=10) - time_took_running_do_things).total_seconds()
    time.sleep(remaining_time_in_secs)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-08-24 11:14:40.821099
2022-08-24 11:14:50.797054
2022-08-24 11:15:00.806950
2022-08-24 11:15:10.834795
2022-08-24 11:15:20.845102

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Explanation
&lt;/h4&gt;

&lt;p&gt;Here you can see that the codes run every 10 seconds even though we are doing the heavy lifting. This is because we compensated for the time taken to run do_things() by waiting for less afterwards. If it took the program 3 seconds to run do_things(), we only wait 7 seconds afterwards, so the program still do things every 10 seconds.&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitation
&lt;/h4&gt;

&lt;p&gt;This solution is limited by, the action you want to do must to take longer than the interval you want to loop. If you want to do an operation that takes 10 seconds every 1 second, this solution cannot help you. You might want to look into &lt;a href="https://realpython.com/python-concurrency/#how-to-speed-up-a-cpu-bound-program"&gt;Concurrency from this RealPython article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I got there
&lt;/h2&gt;

&lt;p&gt;Now I will talk about how I figured this out.&lt;/p&gt;

&lt;p&gt;I am working on&lt;a href="https://github.com/chit-uob/usageTracker"&gt;a program that sees if I'm playing computer games at regular intervals to remind myself how long I'm playing&lt;/a&gt;, but the problem is that it is very inaccurate. I would play a game for 45 minutes, then the program will tell me that I only played for 30 minutes.&lt;/p&gt;

&lt;p&gt;I first thought this is a problem with time.sleep(), maybe because the CPU is doing a lot of work while gaming, the wait is wrong.&lt;/p&gt;

&lt;p&gt;But this is proven wrong by when I'm not playing games, the program still runs at incorrect intervals.&lt;/p&gt;

&lt;p&gt;Then I commented out the things the program does within each interval, only leaving the print time statement, and then the problem was fixed. It turns out the problem is what I do inside the interval is costing above 2 seconds!&lt;/p&gt;

&lt;p&gt;I then immediately went on to try to over-engineer the problem. I thought to myself, maybe I want the loop and the do_things() function in separate threads via threading or asyncio, or maybe even separate CPU using multiprocessing. I watched the entire tutorial on &lt;a href="https://realpython.com/python-concurrency/#how-to-speed-up-a-cpu-bound-program"&gt;Speed Up Your Python Program With Concurrency by Jim Anderson on RealPython&lt;/a&gt; until I drew this graph.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0l5zfJiQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1661337810919/qmXSDdtnU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0l5zfJiQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1661337810919/qmXSDdtnU.png" alt="graph.png" width="710" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then realize, what if I just do them all linearly, then compute how much time I need to wait? It is then that it dawned on me, that the problem was never this complicated. So I fixed the problem and decided to write this article to help others like me. So there you have it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datetime</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Where's my Voi scooter: [9] Masking scooter heatmap on the map using Python PIL module</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Wed, 03 Aug 2022 22:00:41 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/wheres-my-voi-scooter-9-masking-scooter-heatmap-on-the-map-using-python-pil-module-19jl</link>
      <guid>https://forem.com/chits_programming_blog/wheres-my-voi-scooter-9-masking-scooter-heatmap-on-the-map-using-python-pil-module-19jl</guid>
      <description>&lt;p&gt;In this blog, I plan to incorporate the map into the heatmap made in the &lt;a href="https://chit.hashnode.dev/wheres-my-voi-scooter-8-creating-heatmap-for-distribution-of-scooters"&gt;previous blog post&lt;/a&gt;, because, with only the heatmap, you can see which coordinates have a lot of scooters, but you have to reference the map with the coordinates, which is inconvenient. So I want to put the information on the Heatmap directly on the map so that the information is shown clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heatmap
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Overlapping image
&lt;/h3&gt;

&lt;p&gt;To overlap the image of the map and the heatmap, I need to find out how to have a half-transparent image on top of another image.&lt;/p&gt;

&lt;p&gt;I first have to make an image transparent, I do that by using the &lt;code&gt;putalpha(128)&lt;/code&gt; to make an image half transparent. It works by putting an alpha, or transparency level of 128 on that image. An alpha level of 0 means completely transparent, and level 255 means completely opaque. So 128 is in the middle, hence half-transparent.&lt;/p&gt;

&lt;p&gt;Then I tried using &lt;code&gt;image.paste(another_image)&lt;/code&gt; to put the half-transparent image on top of the other. but what happened is the image complete overwrites the other. what I needed was &lt;code&gt;alpha_composite&lt;/code&gt;. For testing, I alpha composited the half-transparent map on top of the sat.&lt;/p&gt;

&lt;p&gt;I created this &lt;code&gt;map_img&lt;/code&gt; by getting the four coordinates I used for the range of the heatmap. and Taking a screenshot with exactly the four corners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i3qHev7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563154225/M_BzlDdL2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3qHev7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563154225/M_BzlDdL2.png" alt="1.png" width="729" height="838"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;sat_map &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rV-jJ1A2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563160588/uNQMG9P1O.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rV-jJ1A2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563160588/uNQMG9P1O.png" alt="2.png" width="729" height="838"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from PIL import Image
map_img = Image.open('img/map.png')
map_img.putalpha(128)
sat_map = Image.open('img/sat_map.png')
sat_map.alpha_composite(map_img)
sat_map.save('img/result.png')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;result.png &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6IiFG7xK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563168516/ecPnxvMXK.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6IiFG7xK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563168516/ecPnxvMXK.png" alt="3.png" width="729" height="838"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Masking
&lt;/h3&gt;

&lt;p&gt;Now I want to mask the heatmap image on the map image so that we can see the areas with a lot of scooters on the map. I wanted to use alpha composite like the last example, but I figured that using masking would be better. Image masking is putting image A on image B using image C as a guideline. I plan to put a dark image over the map, using the heatmap as a guideline. Places with more scooters will be darker so that we can tell where the scooters are on the map.&lt;/p&gt;

&lt;p&gt;The function to do the masking is &lt;code&gt;Image.composite()&lt;/code&gt;, it requires all images to have the same size. So I plan to use the size width 600 height 600 for all. I first make a background image of all white, resize the map image to the size of the heatmap using the &lt;code&gt;thumbnail&lt;/code&gt; function, and paste it into the background. then I read the heatmap image and fix its size and convert it to &lt;code&gt;L&lt;/code&gt;, which means greyscale for some reason. then I make a red image of all red, and I composite all of them together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bg_img = Image.new("RGBA", (600, 600), 0)
map_img = Image.open('img/map.png')
map_img.thumbnail((474, 546), Image.ANTIALIAS)
bg_img.paste(map_img, (36, 21))
heatmap_img = Image.open('img/heatmap.png').resize((600, 600)).convert("L")
bk_img = Image.new("RGB", (600, 600), 255).convert("RGBA")
final_img = Image.composite(bk_img, bg_img, heatmap_img)
final_img.save('img/result2.png')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;heatmap_img &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RNSDHLL---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563175814/BCW-fOdjO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RNSDHLL---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563175814/BCW-fOdjO.png" alt="4.png" width="596" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is, for the places with more scooters, a red overlay will be on them, so we can tell where the scooters are focused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qg5Y0_zw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563873772/Awb-PCnn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qg5Y0_zw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659563873772/Awb-PCnn9.png" alt="5.png" width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;In the next blog, I plan to analyse the Voi scooter data from July, since my data collecting program has been running for over a month already. I'm sure there will be interesting data collected.&lt;/p&gt;

</description>
      <category>voi</category>
      <category>programming</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Project Exposure: [1] Creating Social Media Presence and Cross-posting on Dev and Medium</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Thu, 28 Jul 2022 10:50:48 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/project-exposure-1-creating-social-media-presence-and-cross-posting-on-dev-and-medium-2h6k</link>
      <guid>https://forem.com/chits_programming_blog/project-exposure-1-creating-social-media-presence-and-cross-posting-on-dev-and-medium-2h6k</guid>
      <description>&lt;p&gt;In this blog, I continue my journey of increasing my exposure by designing my brand and making social media presence for my blog on Twitter, Instagram, Facebook, and Reddit. As well as cross-posting on other blogging platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  My brand
&lt;/h2&gt;

&lt;p&gt;To make social media accounts, I need a brand. I am just going to call it "Chit's Programming Blog". Then I also need an email for all the social media, but I don't want to use my personal email or work email because I don't want to get my emails entangled. So I made a new Gmail account since it is the most trusted. Popular social media may blacklist other email providers because they are less secured and may be used to create spam bots etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profile picture
&lt;/h3&gt;

&lt;p&gt;I need a profile picture for social media. I don't want to use my photos, I'm also concerned about copyright and trademarks, so I cannot just snap an image online and use it. Therefore I need to design my own profile picture.&lt;/p&gt;

&lt;p&gt;There are a handful of tools I can use. There are tools where I have to make everything from scratch, like Microsoft Paint, and Photoshop. There are also tools that give stock images and fonts for you, like Canva. There are also tools that claim to use AI to generate your logo if you tell it about what you want, like &lt;a href="https://www.tailorbrands.com/logo-maker"&gt;Tailor Brands&lt;/a&gt;, &lt;a href="https://designs.ai/en/logomaker"&gt;Designs AI&lt;/a&gt;, and &lt;a href="https://www.adobe.com/express/create/logo"&gt;Adobe Express&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I used Adobe Express since it is the bigger brand, and it says free to use forever on their website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wbBn389Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005041637/HD0rSvgfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wbBn389Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005041637/HD0rSvgfw.png" alt="1.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Social Media Presence
&lt;/h2&gt;

&lt;p&gt;Then I created my social media page on different platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Twitter
&lt;/h3&gt;

&lt;p&gt;Once I created my basic Twitter account, I get to upgrade it into a &lt;a href="https://business.twitter.com/en/basics/get-your-business-started-with-twitter.html"&gt;Twitter Business Account&lt;/a&gt;, where I have to tell Twitter what company I am. Since there is no choice for content creator or blogger, I chose Information Technology Company. It also asks me if I am a shop or a creator, which I chose creator.&lt;/p&gt;

&lt;p&gt;Then my Twitter account is finished. It gave me a handle "@BlogChit", which I am not so happy with, then I found out on &lt;a href="https://help.twitter.com/en/managing-your-account/change-twitter-handle#:~:text=Navigate%20to%20Settings%20and%20privacy%20and%20tap%20Account.&amp;amp;text=Tap%20Username%20and%20update%20the,prompted%20to%20choose%20another%20one.&amp;amp;text=Tap%20Done."&gt;their website&lt;/a&gt; that I can change the handle. I want the handle to be memorable and easy to type out. Looking at popular Twitter users, most of them use the same as their display name, so I think I will do that too.&lt;/p&gt;

&lt;p&gt;But I discovered my name is too long, so at last, I decided to go with "ChitProgramming"&lt;/p&gt;

&lt;h3&gt;
  
  
  Instagram
&lt;/h3&gt;

&lt;p&gt;At first, I thought I must use my real name on Instagram. But it turns out I can use my business name too, so I used it for searchability.&lt;/p&gt;

&lt;p&gt;When I thought that I'm done making my Instagram account, I found a button to "Switch to professional account". I found &lt;a href="https://www.socialmediatoday.com/news/6-reasons-why-you-need-to-switch-to-an-instagram-business-profile/519704/"&gt;this article&lt;/a&gt; talking about why should I switch to a professional account, and the point Instagram Business Profiles Can Share Links in Instagram Stories sold me, it means that I'll be able to share my blog post with a single click. So I clicked the button, selected "Creator" to describe me better than "Business", choose "Personal Blog" as my category, and then I have a professional Instagram account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Facebook
&lt;/h3&gt;

&lt;p&gt;This is basically the same with Instagram since it is owned by the same company, Meta. But one annoying thing is that it makes posts that I don't want. After I created the profile and added images, it made a post about my birthday, how I updated my profile picture and updated my cover photo.&lt;/p&gt;

&lt;p&gt;Facebook seems to force you to use your real name to make an account, so I was confused why there are so many business pages on Facebook, it turns out that peoples use "profile", while businesses use "page", and "profile" to create "pages".&lt;/p&gt;

&lt;p&gt;So I created indicating that my category is "Information Technology Company", added my blog's information, then it was done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reddit
&lt;/h3&gt;

&lt;p&gt;Reddit is the easiest of all, I just have to select a username, and enter my blog's info. I don't have to set up a professional account or anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-posting
&lt;/h2&gt;

&lt;p&gt;I thought I had to make a program to automatically post my blog on other websites, then make a canonical link back to the original one. But turns out, that both Dev and Medium have features that help you publish articles from other sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dev.To
&lt;/h3&gt;

&lt;p&gt;Inside the Settings -&amp;gt; Extensions page, there is an option where I can publish to the Dev community from RSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KdcSyScu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005049430/IOgTTCnaO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KdcSyScu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005049430/IOgTTCnaO.png" alt="2.png" width="767" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I get my RSS on the upper right corner of my blog, then I paste it on Dev.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--70ymJV_k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005053338/fRJg31KMZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--70ymJV_k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005053338/fRJg31KMZ.png" alt="3.png" width="428" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then all my posts from Hashnode end up there, that's so cool and useful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OIK_4b78--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005056515/xdKYyJMTd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OIK_4b78--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005056515/xdKYyJMTd.png" alt="4.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have to add tags manually, I have to check what tags are trending on Dev. I check this on their &lt;a href="https://dev.to/tags"&gt;tags page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XW0jAOSL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005059077/xh4g1EIXZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XW0jAOSL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005059077/xh4g1EIXZ.png" alt="5.png" width="880" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Medium
&lt;/h3&gt;

&lt;p&gt;As for Medium, I cross-post by going to Settings -&amp;gt; Stories -&amp;gt; Import a story.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--brr2Zdj4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005064116/VpC4W-aej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--brr2Zdj4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005064116/VpC4W-aej.png" alt="6.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I input the link to my article and click import.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HdLJguVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005066940/N3qZxeZtY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HdLJguVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005066940/N3qZxeZtY.png" alt="7.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one gave me some extra spacing between lines, which I have to remove.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4JLAOISH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005070710/r7PjA4uQh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4JLAOISH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1659005070710/r7PjA4uQh.png" alt="8.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Medium doesn't share what tags are trending, it only recommended some &lt;a href="https://medium.com/me/following/suggestions"&gt;topics&lt;/a&gt;, so I have to find out in &lt;a href="https://humbaa.com/60-most-popular-tags-on-medium/"&gt;a third-party website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;In this article, I created social media accounts and cross-posting accounts. Now I can share my blog posts on social media and engage in discussions. And I will cross-post my previous blog posts at a regular interval, to avoid being spammy. As for SEO, since it takes time for Google to index my blog posts, I think I'll have to wait, and post an update once I can find myself on Google.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>programming</category>
      <category>blogging</category>
      <category>views</category>
    </item>
    <item>
      <title>Project Exposure: [0] How do I increase my blog's view count</title>
      <dc:creator>Chit</dc:creator>
      <pubDate>Mon, 25 Jul 2022 20:22:43 +0000</pubDate>
      <link>https://forem.com/chits_programming_blog/project-exposure-0-how-do-i-increase-my-blogs-view-count-1fn9</link>
      <guid>https://forem.com/chits_programming_blog/project-exposure-0-how-do-i-increase-my-blogs-view-count-1fn9</guid>
      <description>&lt;p&gt;I've been writing this blog for about 2 months now and averages around 4 views per day, which is fair, considering how much competition is on &lt;a href="https://hashnode.com/"&gt;Hashnode&lt;/a&gt;. In light of this, I aim to increase my blog view in this project - Project Exposure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qXsDMKjT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779448923/iF1BafF0V.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qXsDMKjT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779448923/iF1BafF0V.png" alt="1.png" width="450" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible reasons for low viewer count
&lt;/h2&gt;

&lt;p&gt;I start by investigating the shortcomings of my blog, to see how I can improve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time on the platform
&lt;/h3&gt;

&lt;p&gt;I've only written several blog posts. There is a small chance in each of my blog posts to be appreciated and attract followers. So the longer I am on this platform, the higher chance this happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  My writing skills
&lt;/h3&gt;

&lt;p&gt;I haven't written a blog before, and seldom write essays since I study Computer Science. Therefore my writing may wordy, unclear, and unpleasant to read. To fix this, I need to write more, as well as read good blog posts to learn.&lt;/p&gt;

&lt;h3&gt;
  
  
  My writing topics
&lt;/h3&gt;

&lt;p&gt;Looking at the trending blog posts on Hashnode, it is clear the trending ones teach you something or tell you something you don't know. But the ones I write document a programming journey, so it is not what most people look for.&lt;/p&gt;

&lt;p&gt;However, I do not intend to change my article topics, as my blog is for documenting my programming journey and my target audience is people interested or wanting to learn from my experience.&lt;/p&gt;

&lt;p&gt;I also notice a lot of popular blog posts have a number in them, like &lt;a href="https://danyal.hashnode.dev/6-habits-i-have-picked-up-from-working-in-tech-for-3-years"&gt;&lt;strong&gt;6&lt;/strong&gt; Habits I have picked up from working in tech for &lt;strong&gt;3&lt;/strong&gt; years&lt;/a&gt;, &lt;a href="https://jamesqquick.hashnode.dev/15-common-beginner-javascript-mistakes"&gt;&lt;strong&gt;15&lt;/strong&gt; Common Beginner JavaScript Mistakes&lt;/a&gt;. I thought about using AI, feeding in article topics and views, to see if there is any correlation with it, but I'll save this idea for later.&lt;/p&gt;

&lt;h3&gt;
  
  
  People lack a chance to see my articles
&lt;/h3&gt;

&lt;p&gt;I watched a video from Youtuber &lt;a href="https://www.youtube.com/c/HowMoneyWorks"&gt;How Money Works&lt;/a&gt;, called &lt;a href="https://www.youtube.com/watch?v=tNtnlzmvAw0"&gt;Why Finance "Gurus" Want You To Hate Them - How Money Works&lt;/a&gt;. In that video, the author made an interesting point. The reason why fake gurus - people who give bad financial advice, act so unbearable, is because "any publicity is good publicity", working in conjunction with the sales funnel.&lt;/p&gt;

&lt;h4&gt;
  
  
  The sales funnel
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JetDzObd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779457924/emjCaUtVT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JetDzObd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779457924/emjCaUtVT.png" alt="2.png" width="880" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.crazyegg.com/blog/sales-funnel/#:~:text=The%20sales%20funnel%20is%20each%20step%20that%20someone%20has%20to%20take%20in%20order%20to%20become%20your%20customer."&gt;The sales funnel is each step that someone has to take in order to become your customer&lt;/a&gt;, as visualised above. Actually, it is more like a sieve or a filter, since a funnel keeps everything poured in, but some customers will be lost in the process. For the sake of recognisability, I'll keep calling it a sales funnel.&lt;/p&gt;

&lt;p&gt;Let's say in each step of my sales funnel, I keep 10% of the customers. If I want more people to enter the last step, follow me, I can: a) do better in each stage to keep more customers, such as making clickbait titles and writing a more interesting first paragraph of my article to increase the percentage of people staying in each stage. b) put more people in the initial stage of the sales funnel, so that more people will enter the final stage.&lt;/p&gt;

&lt;p&gt;That's why I came up with the idea of making a program which automatically cross-posts my blogs and also creates social media presence to promote my blog to increase my exposure and put more people inside my sales funnel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Popular blogging sites
&lt;/h3&gt;

&lt;p&gt;To do that I will have to know where should I cross-post my blog, below are websites that I found online.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hashnode, where I'm on&lt;/li&gt;
&lt;li&gt;HackerNoon and FreeCodeCamp, have dedicated editors to review blog posts, so I may only submit high-quality blog posts&lt;/li&gt;
&lt;li&gt;Dev.To, simular to Hashnode&lt;/li&gt;
&lt;li&gt;Medium, a website for all kinds of articles&lt;/li&gt;
&lt;li&gt;Google Blogger / WordPress / Wix..., these sites while giving me a lot of freedom, there is no way for people to find me there, as there is no &lt;code&gt;explore&lt;/code&gt; button, so I will not consider these, as at this stage, I want to be discovered&lt;/li&gt;
&lt;li&gt;LinkedIn, a place with professionals and experts, so I need to be careful what I post there&lt;/li&gt;
&lt;li&gt;Tumblr, a mini blogging website with built-in social media capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Social media where I can create a presence
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Twitter&lt;/li&gt;
&lt;li&gt;Reddit&lt;/li&gt;
&lt;li&gt;Instagram&lt;/li&gt;
&lt;li&gt;Facebook&lt;/li&gt;
&lt;li&gt;Snapchat&lt;/li&gt;
&lt;li&gt;Youtube and TikTok, if I ever consider making videos&lt;/li&gt;
&lt;li&gt;Pinterest, seems to be image-based but I don't think I have any images to show&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Search Engine Optimization
&lt;/h3&gt;

&lt;p&gt;Ideally, my blog posts should be searchable from Google, so there is an additional way for people to visit my blog. But currently, my blogs cannot be searched on Google.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8QlBwVvz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779463515/GNYcl5ILi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8QlBwVvz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779463515/GNYcl5ILi.png" alt="3.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first reason is that I have an apostrophe in my blog title and that "chit" is usually used with "chit chat", so Google thinks it was a typo. If I force Google to search "Chit's", my blog appears on top.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_989wRCt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779467081/P7KMvdqAS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_989wRCt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779467081/P7KMvdqAS.png" alt="4.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The other reason is that I recently changed the name from "Chit's tech blog" to "Chit's programming blog" because I realized that a tech blog is a blog that talks about new technology, which is not what I cover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FUJQiEGU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779471046/UcN04Hj19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FUJQiEGU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779471046/UcN04Hj19.png" alt="5.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I search for the title of my first article, I cannot find the result. It is because the past me thought it would be a good idea to make a custom SEO title. If I have time, I want to edit all my past blog posts' SEO titles and descriptions, for Google to better index my blog posts.&lt;/p&gt;

&lt;p&gt;Also, notice that my article published on DevTo was indexed instead. This is undesirable because my articles on different platforms are fighting for views, what I should do instead is to use a canonical link, to tell Google, that the article you found on DevTo, comes from Hashnode, so Google knows where to send people.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h3cM0Rsk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779474452/pPdWSSMvy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h3cM0Rsk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779474452/pPdWSSMvy.png" alt="6.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I search for newer articles, they do not appear in the result, I assume it is because it takes time for Google to index my blog posts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FvEvfGDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779481210/myyCrn0_q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FvEvfGDt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779481210/myyCrn0_q.png" alt="7.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also designed an image for social media sharing, so it looks prettier when I share my blog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DJzgJcH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779484314/avHwvH69E.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DJzgJcH3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779484314/avHwvH69E.png" alt="8.png" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have to say, &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt; is really easy to use, and has some nice templates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DlYEzI1V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779488200/_ZqCRMlTb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DlYEzI1V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1658779488200/_ZqCRMlTb.png" alt="9.png" width="628" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now All I have to do is wait for Google to index my new and improved blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action plan
&lt;/h2&gt;

&lt;p&gt;In the next blog, I want to create a social media presence on the above social media platforms, as well as investigate how can I cross-post to the other blogging platforms.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>programming</category>
      <category>blogging</category>
      <category>views</category>
    </item>
  </channel>
</rss>
