Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
35f8578
fea: add jsr support + @m3o/auth
marco-souza Jan 27, 2025
ae55958
fea: redirect to gh login
marco-souza Jan 27, 2025
8ba07b3
ref: bun fmt
marco-souza Jan 27, 2025
122103b
fea: add auth admin callback
marco-souza Jan 27, 2025
9ee7b6e
enh: add missing auth urls
marco-souza Jan 27, 2025
7d8a6f5
fea: improve admin routing layer
marco-souza Jan 27, 2025
ed6db77
fea: logout
marco-souza Jan 27, 2025
cf5c4d0
enh: add refesh token for admin auth
marco-souza Jan 27, 2025
c734d08
fea: add admin dashboard
marco-souza Jan 27, 2025
b5be320
fix: admin login redirect
marco-souza Jan 27, 2025
da92641
fix: update credentials
marco-souza Jan 28, 2025
7754956
fea: upgrade tailwindcss and daisyui
marco-souza Jan 28, 2025
6115883
fix: css issues
marco-souza Jan 28, 2025
10c0bee
fea: add admin tabs
marco-souza Jan 28, 2025
ac1c2f2
ref: create a factory function for db connection
marco-souza Jan 28, 2025
5c94302
fea: add konami code
marco-souza Jan 28, 2025
f990102
fix: testimonial styles
marco-souza Jan 28, 2025
2182b02
fix: bun fmt
marco-souza Jan 28, 2025
63690f9
fea: list testimonials to admin
marco-souza Jan 28, 2025
05dd1e3
fea: list users
marco-souza Jan 29, 2025
984b1b7
fea: make user listing beautiful
marco-souza Jan 29, 2025
f402e63
ref: rename admin.users to admin.members
marco-souza Jan 29, 2025
764a40b
fix: hide not used content
marco-souza Jan 29, 2025
36ee8ae
fix: update credentials
marco-souza Jan 29, 2025
c7a32db
fix: update dependencies
marco-souza Jan 29, 2025
45e41bf
enh: add icons to social networks of members
marco-souza Jan 29, 2025
54e2efc
fix: avoid generating cloudflare types when deploying
marco-souza Jan 29, 2025
37413c2
fea: check valid emails
marco-souza Jan 29, 2025
efbc5e0
fix: update todo.md
marco-souza Jan 29, 2025
a6916d8
fea: address code review comments
marco-souza Jan 29, 2025
8082095
enh: reuse ADMIN_ROUTES
marco-souza Jan 29, 2025
6fc8f0e
fix: add missing constant
marco-souza Jan 29, 2025
fb5740b
fix: move all auth route to constants.ts
marco-souza Jan 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .env.gpg
Binary file not shown.
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ projects: "webapp"

## Descrição

A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is. Ex. I'm always
frustrated when [...]

- [ ] add subtasks with these checkboxes
- [ ] ...
Expand All @@ -21,5 +22,4 @@ A clear and concise description of what the problem is. Ex. I'm always frustrate
- [Choc UI](https://www.notion.so/podcodar/Sprint-Planing-2-203c837559494a9887af633792c095ee#3fbfcf4f78d142978b8a29b190f0f17a)
- [Eng wiki > Links uteis](https://www.notion.so/podcodar/Chakra-1e4429a361a844f9a5d5db6790dd154b#e624ab829d754dc591ee3c6bfd1b1d76)

**Piloto**: Ada King
**Co-piloto**: Alan Turing
**Piloto**: Ada King **Co-piloto**: Alan Turing
8 changes: 4 additions & 4 deletions .github/workflows/deploy-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ on:
push:
branches: [main]
paths:
- 'docs/**'
- '.github/workflows/**'
- "docs/**"
- ".github/workflows/**"

jobs:
build:
Expand Down Expand Up @@ -34,8 +34,8 @@ jobs:
runs-on: ubuntu-latest

permissions:
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source

environment:
name: github-pages
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/quality-gateway-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on: [pull_request]

jobs:
quality_gateway:
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
if: "${{ github.event.pull_request.head.repo.full_name == github.repository }}"
runs-on: ubuntu-latest
environment: Development
env:
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/tag-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ jobs:
with:
version-file: package.json
token: ${{ secrets.GITHUB_TOKEN }}

2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
# all branches
branches:
- '*'
- "*"
push:
branches: [main]

Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@jsr:registry=https://npm.jsr.io
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Welcome to PodCodar WebApp!

This WebApp is the main project of PodCodar, a learning community about programming and technology.
This WebApp is the main project of PodCodar, a learning community about
programming and technology.

- 📖 [React Router docs](https://reactrouter.com/start/home)
- 🧑‍💻 [PodCodar Engineering docs](https://podcodar.github.io/webapp)
Expand All @@ -19,7 +20,8 @@ This WebApp is the main project of PodCodar, a learning community about programm

### System Dependencies

To have a consistent development environment, we recommend using the following tools:
To have a consistent development environment, we recommend using the following
tools:

- [Bun](https://bun.sh)
- [direnv](https://direnv.net/)
Expand Down Expand Up @@ -61,7 +63,8 @@ Now you'll need to pick a host to deploy it to.

### DIY

If you're familiar with deploying Node applications, the built-in Remix app server is production-ready.
If you're familiar with deploying Node applications, the built-in Remix app
server is production-ready.

Make sure to deploy the output of `bun run build`

Expand All @@ -70,4 +73,8 @@ Make sure to deploy the output of `bun run build`

## Styling

This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information.
This template comes with [Tailwind CSS](https://tailwindcss.com/) already
configured for a simple default starting experience. You can use whatever css
framework you prefer. See the
[Vite docs on css](https://vitejs.dev/guide/features.html#css) for more
information.
2 changes: 1 addition & 1 deletion app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async function handleRequest(
signal: controller.signal,
onError(error: unknown) {
if (!controller.signal.aborted) {
// Log streaming rendering errors from inside the shell
// NOTE: Log streaming rendering errors from inside the shell
console.error(error);
}
status = 500;
Expand Down
2 changes: 2 additions & 0 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export function Layout({ children }: React.PropsWithChildren) {
<ScrollRestoration />
<Scripts />
</body>

<script src="/js/konami.js" type="application/javascript" />
</html>
);
}
Expand Down
7 changes: 2 additions & 5 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import TestimonialSection from "@packages/components/TestimonialSection";
import WhyItWorksSection from "@packages/components/WhyItWorksSection";

import { description, title } from "@packages/config/site";
import { Database } from "@packages/repositories/db";
import { getDatabase } from "@packages/repositories/db";
import {
type LoaderFunctionArgs,
type MetaFunction,
Expand All @@ -19,10 +19,7 @@ export const meta: MetaFunction = () => {
};

export async function loader({ context }: LoaderFunctionArgs) {
const db = new Database(
context.cloudflare.env.TURSO_CONNECTION_URL,
context.cloudflare.env.TURSO_AUTH_TOKEN,
);
const db = getDatabase(context);
const testimonials = await db.testimonials;
return { testimonials };
}
Expand Down
57 changes: 57 additions & 0 deletions app/routes/admin.auth.callback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { ADMIN_ROUTES, VALID_EMAILS } from "@packages/contants";
import { getAuth } from "@packages/services/auth";
import { authCookie, refreshCookie } from "@packages/services/auth.server";
import { type LoaderFunctionArgs, redirect } from "react-router";

export async function loader({ request, context }: LoaderFunctionArgs) {
const url = new URL(request.url);
const params = new URLSearchParams(url.search);
const code = params.get("code");
const state = params.get("state");

const auth = getAuth(context);

if (!code) {
return new Response("Missing code", { status: 400 });
}

if (!state) {
return new Response("Missing state", { status: 400 });
}

// FIXME: validate state
// assert(auth.isValidState(state), "Invalid state");

const token = await auth.fetchAccessToken(code);
if (!token) {
return new Response("Failed to fetch access token", { status: 401 });
}

const profile = await auth.fetchAuthenticatedUser(token.access_token);
if (!VALID_EMAILS.some((reEmail) => reEmail.test(profile.email))) {
// NOTE: this log is required to get information on people trying to access our app without authorization
console.debug({ profile, status: "401" });
return new Response("Unauthorized user, your attempt will be reported", {
status: 401,
statusText: "Unauthorized",
});
}

const redirectUrl = request.headers.get("redirect") ?? ADMIN_ROUTES.dashboard;
const [authHeader, refreshHeader] = await Promise.all([
authCookie.serialize(token.access_token, { maxAge: token.expires_in }),
refreshCookie.serialize(token.refresh_token, {
maxAge: token.refresh_token_expires_in,
}),
]);

// check if its a valid user
const headers = new Headers();
headers.append("Set-Cookie", refreshHeader);
headers.append("Set-Cookie", authHeader);

// TODO: create user
// TODO: set user info in cookies

return redirect(redirectUrl, { headers });
}
17 changes: 17 additions & 0 deletions app/routes/admin.auth.logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ADMIN_ROUTES } from "@packages/contants";
import { authCookie, refreshCookie } from "@packages/services/auth.server";
import type { LoaderFunctionArgs } from "react-router";
import { redirect } from "react-router";

export async function loader({ context }: LoaderFunctionArgs) {
const [authToken, refreshToken] = await Promise.all([
authCookie.serialize("", { maxAge: -1 }),
refreshCookie.serialize("", { maxAge: -1 }),
]);

const headers = new Headers();
headers.append("Set-Cookie", authToken);
headers.append("Set-Cookie", refreshToken);

return redirect(ADMIN_ROUTES.signIn, { headers });
}
31 changes: 31 additions & 0 deletions app/routes/admin.auth.refresh.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ADMIN_ROUTES } from "@packages/contants";
import { getAuth } from "@packages/services/auth";
import { authCookie, refreshCookie } from "@packages/services/auth.server";
import { type LoaderFunctionArgs, redirect } from "react-router";

export async function loader({ request, context }: LoaderFunctionArgs) {
const cookies = request.headers.get("Cookie");
const [authToken, refreshToken] = await Promise.all([
authCookie.parse(cookies),
refreshCookie.parse(cookies),
]);

if (authToken) return redirect(ADMIN_ROUTES.dashboard);
if (!refreshToken) return redirect(ADMIN_ROUTES.signIn);

const token = await getAuth(context).refreshAccessToken(refreshToken);

const redirectUrl = request.headers.get("redirect") ?? ADMIN_ROUTES.dashboard;
const [authHeader, refreshHeader] = await Promise.all([
authCookie.serialize(token.access_token, { maxAge: token.expires_in }),
refreshCookie.serialize(token.refresh_token, {
maxAge: token.refresh_token_expires_in,
}),
]);

const headers = new Headers();
headers.append("Set-Cookie", refreshHeader);
headers.append("Set-Cookie", authHeader);

return redirect(redirectUrl, { headers });
}
11 changes: 11 additions & 0 deletions app/routes/admin.dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function AdminDashboard() {
return (
<>
<h3 className="text-xl">📊 Dashboard</h3>

<h4 className="text-sm font-light">
The dashboard is where you can have an overview of the app
</h4>
</>
);
}
46 changes: 16 additions & 30 deletions app/routes/admin.login.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,21 @@
import { GithubIcon } from "@packages/components/icons/GithubIcon";
import {
type ActionFunction,
type ActionFunctionArgs,
Form,
useActionData,
useLoaderData,
} from "react-router";
import { getAuth } from "@packages/services/auth";
import { type LoaderFunctionArgs, useLoaderData } from "react-router";

const REDIRECT_URI = "http://localhost:3000/api/auth/callback/github";
export function loader() {
return { title: "Login", redirectUri: REDIRECT_URI };
}

export async function action({ request }: ActionFunctionArgs) {
export function loader({ request, context }: LoaderFunctionArgs) {
const url = new URL(request.url);
// TODO: setup a redirect to GitHub provider
return { title: "Signed In", redirectUri: url };
const redirectUri = getAuth(context).generateAuthUrl(url.origin);

return { title: "Login", redirectUri };
}

export default function LoginPage({ errors = "" }) {
const initialState = useLoaderData<typeof loader>();
const currentState = useActionData<typeof action>();

console.log({ initialState, currentState });

const githubButton = (
<div className="flex items-center gap-2">
<GithubIcon />
<span>{currentState?.title ?? initialState.title} with GitHub</span>
<span>{initialState.title} with GitHub</span>
</div>
);

Expand All @@ -44,18 +32,16 @@ export default function LoginPage({ errors = "" }) {
</h2>
</div>

<Form className="grid gap-4" method="post">
<button
className="btn btn-outline btn-secondary w-full"
type="submit"
>
{githubButton}
</button>
<a
href={initialState.redirectUri}
className="btn btn-outline btn-secondary w-full"
>
{githubButton}
</a>

<p className="text-xs text-gray-500">
We use GitHub for authentication to keep things simple and secure.
</p>
</Form>
<p className="text-xs text-gray-500">
We use GitHub for authentication to keep things simple and secure.
</p>

{errors && <p className="text-red-500">{errors}</p>}
</div>
Expand Down
Loading
Loading