Skip to content

Commit

Permalink
Merge pull request #82 from jermspeaks/feature/content-ai
Browse files Browse the repository at this point in the history
Add amore content plus updated SEO
  • Loading branch information
jermspeaks authored May 29, 2023
2 parents 65eeabc + d048594 commit 4a577a0
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 10 deletions.
Binary file removed public/placeholder-about.jpg
Binary file not shown.
Binary file removed public/placeholder-hero.jpg
Binary file not shown.
Binary file added public/placeholder-hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/placeholder-social.jpg
Binary file not shown.
Binary file added public/placeholder-social.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 25 additions & 7 deletions src/components/BaseHead.astro
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '../styles/global.css';
import "../styles/global.css";
export interface Props {
title: string;
description: string;
image?: string;
title: string;
description: string;
image?: string;
twitterHandle?: string;
author?: string;
publishedDate?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/placeholder-social.jpg' } = Astro.props;
const defaultDate = new Date().toISOString()
const {
author = "Jeremy Wong",
description,
image = "/placeholder-social.png",
title,
twitterHandle = "@jermspeaks",
publishedDate = defaultDate,
} = Astro.props;
---

<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<link rel="sitemap" href="/sitemap-index.xml">
<link rel="sitemap" href="/sitemap-index.xml" />

<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
Expand All @@ -28,17 +39,24 @@ const { title, description, image = '/placeholder-social.jpg' } = Astro.props;
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta name="author" content={author} />

<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />
<meta property="og:image:alt" content="Craft By Zen Hero Image" />

<!-- Articles -->
<meta property="article:published_time" content={new Date(publishedDate).toISOString()} />
<meta property="article:author" content={author} />

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />
<meta property="twitter:site" content={twitterHandle} />
64 changes: 64 additions & 0 deletions src/content/blog/2022-03-01-deno-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: "Notes: Deno"
published: false
draft: false
categories: learning
tags: ["node", "deno"]
date: 2022-03-01
description: ""
pubDate: "2022-03-01"
heroImage: ""
postType: "learning"
---

I am following version 1.13.2: [Intro docs](https://deno.land/[email protected]/introduction) for Deno

## Hello World

[Tutorial](https://deno.land/[email protected]/examples/hello_world)

Running the following command in our tutorial folder uses deno as a runtime.

```sh
deno run 01_hello_world/index.ts
```

## Import and Exports

[Tutorial](https://deno.land/[email protected]/examples/import_export)

Local imports use relative paths. Add the extensions.

Remote imports use urls.

Example URL: `https://deno.land/x/[email protected]`.

You can find modules on [their website](https://deno.land/x).

Prepend export for constants, classes, functions and variables to expose them.

## Managing dependencies

[Tutorial](https://deno.land/[email protected]/examples/manage_dependencies)

There are two ways to handle dependencies.

1. Using URL link
2. Using `deps.ts`

To add a lockfile (useful for locking dependencies), use the `--lock` flag.

```sh
# Create/update the lock file "lock.json".
deno cache --lock=lock.json --lock-write deps.ts
```

[More on integrity checking](https://deno.land/[email protected]/linking_to_external_code/integrity_checking).

## Fetch data

[Tutorial](https://deno.land/[email protected]/examples/fetch_data)

## Left Off

- https://deno.land/[email protected]/examples/manage_dependencies
174 changes: 174 additions & 0 deletions src/content/blog/2023-02-08-supabase-remix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
title: "Build a Realtime Chat App with Remix and Supabase"
published: false
draft: false
categories: learning
tags: ["supabase", "remix", "react"]
date: 2023-02-08
description: "From an egghead course, notes about building a realtime chat app with remix and supabase"
pubDate: "2023-02-08"
heroImage: ""
postType: "learning"
---

The following are notes from an Egghead [course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618?_cio_id=89fb05009d5c9e5c&utm_campaign=Round%202%20-%20Build%20a%20realtime%20chat%20app%20with%20Remix%20and%20Supabase&utm_content=Build%20a%20realtime%20chat%20app%20with%20Remix%20and%20Supabase&utm_medium=email_action&utm_source=customer.io) I took.

## Create a Supabase Project with a Table and Example Data

- Created a new database on supabase called [Chatter](https://app.supabase.com/project/akhdfxiwrelzhhhofvly)
- In the table editor, create a new table `messages`, and add columns for `id`, `created_at`, and `content`.
- `id` should be a uuid
- `created_at` should default `now()` and never be null
- `content` is text and should never be null

## Setting Up a Remix Project

- Create a new remix project
- Choose "Just the basics"
- Choose Vercel as the service

```sh
npx create-remix chatter
```

- For the remix project, you can find the main file in `index.tsx`

## Query Supabase Data with Remix Loaders

- `npm i @supabase/supabase-js`
- Add supabase env vars to .env, which can be found in the _Project Settings > API_. [Link](https://app.supabase.com/project/akhdfxiwrelzhhhofvly/settings/api)

```
SUPABASE_URL={url}
SUPABASE_ANON_KEY={anon_key}
```

- Create a `utils/supabase.ts` file. Create `createClient` function
- A "!" can be used at the end of a variable so typescript doesn't give us errors, if we know those will be available at runtime, like env vars
- Supabase has row-level security enabled, meaning you have to write policies in order for the user to do CRUD operations (`SELECT`, `INSERT`, `UPDATE`, `DELETE`, and `ALL`).
- We added a policy to allow all users to read.
- Create the loader in the index page, using `import { useLoaderData } from "@remix-run/react";`, which will allow us to query supabase using the utils.
- `supabase.from("messages").select()` reminds me a lot like mongodb's client.

## Generate TypeScript Type Definitions with the Supabase CLI

- Add Type Safety checks using the [Supabase CLI](https://supabase.com/docs/guides/cli)

```sh
brew install supabase/tap/supabase
# Or call upgrade if you already have it
brew upgrade supabase
```

- [Create an account token](https://app.supabase.com/account/tokens)
- Then login to CLI tool using that access token

```sh
supabase login
```

- Generate types based our project for Typescript

```sh
supabase gen types typescript --project-id akhdfxiwrelzhhhofvly > db_types.ts
```

- We have to re-run this command every time we have DB updates
- Now we use the `db_types.ts` into our `supabase.ts` file by adding a type to the `createClient` function
- You can infer types by using `typeof` in Typescript. This is useful for showcasing what `data`'s type is in the `Index` functional component.
- To make sure the data is always present, or an empty array rather than of type `null`, we use a null coalescing operator on the original data `return { messages: data ?? [] };`

## Implement Authentication for Supabase with OAuth and Github

- Enable Github OAuth using Supabase
- In the supabase project, go to Authentication > Providers
- Choose Github
- In Github, go to Settings, Developer Settings > OAuth Apps
- Create "Chatter". Copy the Authorization callback URL
- In supabase, enter the Client ID, Client Secret, and the Redirect URL.
- The generated secret in Github goes away after a few minutes, so be quick
- Create the login component in `components/login` and then add two buttons for logging in and out.
- The handlers should be `supabase.auth.signInWithOAuth` and `supabase.auth.signOut`
- Add the login component back into the index component.
- You'll notice a `ReferenceError` in that `process` is not defined because that should only run on the server.
- Change the `supabase.ts` file to `supabase.server.ts` file. This shows that the supabase file should only be rendered on the server.
- The `root.tsx` component has an `Outlet` depending on the route based off the `routes` files (file-based routing)
- In the root component, we add the context in `Outlet` for the supabase instance.
- This can now be used in the `login` file using `useOutletContext`.
- Types can be added by exporting it from root.
- `type TypedSupabaseClient = SupabaseClient<Database>;`
- supabase uses Local Storage to store the OAuth tokens.
- You can also check [the users](https://app.supabase.com/project/akhdfxiwrelzhhhofvly/auth/users) in the supabase project

## Restrict Access to the Messages Table in a Database with Row Level Security (RLS) Policies

- Add a column to our database called `user_id` and add a foreign key to it, with `users` and the key being `id`.
- Disable _Allow Nullable_ by adding the logged in user id to the first two messages. This can be found in the users table.
- Re-run the db_types script

```sh
supabase gen types typescript --project-id akhdfxiwrelzhhhofvly > db_types.ts
```

- Update the policy by changing the target roles to be authenticated.
- Now only signed in users will be able to view the data.

## Make Cookies the User Session Single Source of Truth with Supabase Auth Helpers

- Auth tokens by default are stored in the client's session, not on the server.
- Remix is loading from the server's session, which is null
- `npm i @supabase/auth-helpers-remix`
- We need to change the mechanism for the token to use cookies
- Auth helpers allows us to use `createServerClient` and `createBrowserClient` to create the supabase instance correctly, based if it's on the client or server.
- You need `request` and `response` added in the `supabase.server.ts`
- We need to do the same thing in the loader in `root` and `index`

## Keep Data in Sync with Mutations Using Active Remix Loader Functions

- There's no update for pressing the button because the client doesn't update the information after the initial load.
- Remix has a revalidation hook.
- Supabase has a auth state change hook
- Combining these together, on server and client token change (either new token, or no longer has the token), then refetch data from loaders.

## Securely Mutate Supabase Data with Remix Actions

- To create a new message, we add `Form` from remix, which has a method `post`.
- This is reminiscent of how forms worked alongside the HTML spec before
- An action is created to insert the message, include the response headers from before (passing along the cookie)
- The message won't send yet until the supabase policy is set, so we add a policy for `INSERT` and make sure the user is authenticated and their user_id matches the one in supabase.

## Subscribe to Database Changes with Supabase Realtime

- Supabase sends out updates via websockets when there is a change to the database
- It's called [Replication](https://app.supabase.com/project/akhdfxiwrelzhhhofvly/database/replication)
- We create a new component, `RealtimeMessages` that can subscribe to those `INSERT` changes on all tables
- We set messages in a `useState` hook, and any changes we will change them with a `useEffect`
- A second `useEffect` subscribes to these supabase changes

```ts
const channel = supabase
.channel("*")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "messages" },
(payload) => {
const newMessage = payload.new as Message;

if (!messages.find((message) => message.id === newMessage.id)) {
setMessages([...messages, newMessage]);
}
}
)
.subscribe();
```

## Deploy a Remix Application to Vercel from a GitHub Repository

- Create a Github repo
- There's a [Github CLI tool](https://cli.github.com/), `gh`, that can handle this
- `gh repo create chatter --public`
- Init the repo, commit, and push
- Go to Vercel, add the project and env variables
- Go to Github and add the redirect URL
- Go to Supabase and add authentication url redirect
- [Final URL](https://chatter-omega.vercel.app/?index)
Loading

0 comments on commit 4a577a0

Please sign in to comment.