Skip to content

Commit

Permalink
Merge pull request #1774 from fireship-io/deno
Browse files Browse the repository at this point in the history
content: deno course
codediodeio authored Nov 8, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents c07f7c8 + 47cdc1f commit cfcd491
Showing 40 changed files with 1,871 additions and 1 deletion.
5 changes: 5 additions & 0 deletions app/stores/products.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export const products = {
'deno': {
id: 'deno',
price: 'price_1QIkM0BF7AptWZlctgYjiOTL',
amount: 20,
},
'linux': {
id: 'linux',
price: 'price_1PY9DmBF7AptWZlcpkAbgZlP',
62 changes: 62 additions & 0 deletions content/courses/deno/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
lastmod: 2024-11-07T11:11:30-09:00
title: Deno Full Course
description: Master TypeScript and Backend WebDev with Deno
weight: 0
type: courses
vimeo: 1027549123
author: Jeff Delaney
tags:
- typescript
- deno
- pro

stack:
- deno
- ts
- js
---

**Deno - The Full Course** is a hands-on tutorial where you will build a complete web app with [Deno](https://kit.svelte.dev/), using zero 3rd-party denpendices to master TypeScript and Web Platform APIs.

## What will I learn?

- 🦕 Everything you need to be productive with Deno
- 💪 TypeScript fundamentals in fast to-the-point videos
- 🕸️ Web Platform APIs that work everywhere
- 💥 Build a full-stack web app with zero-dependencies
- 🧪 Test-Driven Development & Benchmarking
- 🍪 Roll your own cookie-based user authentication
- 🎹 Manage data with Deno KV
- ⚡ Stream database changes in realtime
- ⚛️ Server-rendered HTML with JSX
- 🚀 Deno Deployment


## 🦄 What will I build?

You will build a **Realtime Link Shortener** inspired by [🌴 Bit.ly](https://bit.ly/) where users can create sharable links and. The goal of this project is to help you master web development fundamentals and learn advanced TypeScript patterns.

### 🚀 Try it out!

Visit the demo app and give it a test drive before you enroll.

<div>
<a href="https://link.fireship.app" class="btn btn-orange">Link Shortener Live Demo</a>
</div>

## 🤔 Is this Course Right for Me?

<div class="box box-blue">
This course is intermediate level 🟦 and expects some familiarity with JavaScript and web development. The content is fast-paced and similar to my style on YouTube, but far more in-depth and should be followed in a linear format.
</div>


## When was the course last updated?

<span class="tag tag-sm tag-pro">Updated November 7th, 2024</span> <span class="tag tag-sm tag-deno">Deno 2</span>

## How do I enroll?

The first few videos are *free*, so just give it try. When you reach a paid module, you will be asked to pay for a single course or upgrade to PRO.

97 changes: 97 additions & 0 deletions content/courses/deno/app-atomic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: Atomic Writes
description: ACID compliant transactions
weight: 49
lastmod: 2024-11-05T11:11:30-09:00
draft: false
vimeo: 1027526766
emoji: 💥
video_length: 3:25
---



## Query Records by Username


{{< file "ts" "main.ts" >}}
```typescript
export async function storeShortLink(
longUrl: string,
shortCode: string,
userId: string,
) {
const shortLinkKey = ["shortlinks", shortCode];
const data: ShortLink = {
shortCode,
longUrl,
userId,
createdAt: Date.now(),
clickCount: 0,
};

const userKey = [userId, shortCode];

const res = await kv.atomic()
.set(shortLinkKey, data)
.set(userKey, shortCode)
.commit()


return res;
}

export async function getUserLinks(userId: string) {

const list = kv.list<string>({ prefix: [userId]});
const res = await Array.fromAsync(list);
const userShortLinkKeys = res.map((v) => ['shortlinks', v.value]);

const userRes = await kv.getMany<ShortLink[]>(userShortLinkKeys)
const userShortLinks = await Array.fromAsync(userRes)

return userShortLinks.map(v => v.value);
}
```

## Increment a Count

{{< file "ts" "main.ts" >}}
```typescript
export async function incrementClickCount(
shortCode: string,
data?: Partial<ClickAnalytics>,
) {
const shortLinkKey = ["shortlinks", shortCode];
const shortLink = await kv.get(shortLinkKey);
const shortLinkData = shortLink.value as ShortLink;

const newClickCount = shortLinkData?.clickCount + 1;

const analyicsKey = ["analytics", shortCode, newClickCount];
const analyticsData = {
shortCode,
createdAt: Date.now(),
...data,
// ipAddress: "192.168.1.1",
// userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
// country: "United States"
};

const res = await kv.atomic()
.check(shortLink)
.set(shortLinkKey, {
...shortLinkData,
clickCount: shortLinkData?.clickCount + 1,
})
.set(analyicsKey, analyticsData)
.commit();
if (res.ok) {
console.log("Logged click");
} else {
console.error("Not logged");
}

return res;
}
```
175 changes: 175 additions & 0 deletions content/courses/deno/app-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
title: User Auth
description: Roll your own user authentication flow
weight: 47
lastmod: 2024-11-05T11:11:30-09:00
draft: false
vimeo: 1027526785
emoji: 🫂
video_length: 5:35
---



## Setup Environment Variables

{{< file "cog" ".env" >}}
```bash
GITHUB_CLIENT_ID=abc
GITHUB_CLIENT_SECRET=xyz
REDIRECT_URI=http://localhost:8000/oauth/callback
```

Update the dev task to include the `--env` flag to load the env vars:

{{< file "deno" "deno.json" >}}
```json
{
"tasks": {
"dev": "deno serve --watch --unstable-kv --env -A src/main.ts",
},
}
```

## Authentication with User Profile


Add database logic to store and get the user profile from the Deno KV database.

{{< file "ts" "db.ts" >}}
```typescript
export type GitHubUser = {
login: string; // username
avatar_url: string;
html_url: string;
};


export async function storeUser(sessionId: string, userData: GitHubUser) {
const key = ["sessions", sessionId];
const res = await kv.set(key, userData);
return res;
}

export async function getUser(sessionId: string) {
const key = ["sessions", sessionId];
const res = await kv.get<GitHubUser>(key);
return res.value;
}
```

Create a new file to handle authentication logic

{{< file "ts" "auth.ts" >}}
```typescript
import { createGitHubOAuthConfig, createHelpers } from "jsr:@deno/kv-oauth";
import { pick } from "jsr:@std/collections/pick";
import { type GitHubUser, getUser, storeUser } from "./db.ts";

const oauthConfig = createGitHubOAuthConfig();
const {
handleCallback,
getSessionId,
} = createHelpers(oauthConfig);


export async function getCurrentUser(req: Request) {
const sessionId = await getSessionId(req);
console.log(sessionId)
return sessionId ? await getUser(sessionId) : null;
}

export async function getGitHubProfile(accessToken: string) {
const response = await fetch("https://api.github.com/user", {
headers: { authorization: `Bearer ${accessToken}` },
});

if (!response.ok) {
response.body?.cancel();
throw new Error("Failed to fetch GitHub user");
}

return response.json() as Promise<GitHubUser>;
}

export async function handleGithubCallback(req: Request) {
const { response, tokens, sessionId } = await handleCallback(req);
const userData = await getGitHubProfile(tokens?.accessToken);
const filteredData = pick(userData, ["avatar_url", "html_url", "login"]);
await storeUser(sessionId, filteredData);
return response;
}

```

Add the current user as a property on the router

{{< file "ts" "router.ts" >}}
```typescript
import type { GitHubUser } from "./db.ts";
import { getCurrentUser } from "./auth.ts";

export class Router {
#routes: Route[] = [];

currentUser?: GitHubUser | null; // <-- HERE

#addRoute(method: string, path: string, handler: Handler) {
const pattern = new URLPattern({ pathname: path });

this.#routes.push({
pattern,
method,
handler: async (req, info, params) => {
try {
this.currentUser = await getCurrentUser(req); // <-- HERE
return await handler(req, info!, params!);
} catch (error) {
console.error("Error handling request:", error);
return new Response("Internal Server Error", { status: 500 });
}
},
});
}

get handler() {
return route(this.#routes, () => new Response("Not Found", { status: 404 }))
}

}
```

Configure the OAuth 2.0 routes

{{< file "ts" "main.ts" >}}
```typescript
import { createGitHubOAuthConfig, createHelpers } from "jsr:@deno/kv-oauth";
import { handleGithubCallback } from "./auth.ts";

const app = new Router();

const oauthConfig = createGitHubOAuthConfig({
redirectUri: Deno.env.get('REDIRECT_URI')
});
const {
signIn,
signOut,
} = createHelpers(oauthConfig);


app.get("/oauth/signin", (req: Request) => signIn(req));
app.get("/oauth/signout", signOut);
app.get("/oauth/callback", handleGithubCallback);


app.get("/", () => {
return new Response(
render(HomePage({ user: app.currentUser })),
{
status: 200,
headers: {
"content-type": "text/html",
},
});
});
```
Loading

0 comments on commit cfcd491

Please sign in to comment.