Skip to content

Commit

Permalink
v0.6.1 - working on GItHub oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
cletqui committed Jul 2, 2024
1 parent 0a64b9c commit ab6f608
Show file tree
Hide file tree
Showing 15 changed files with 547 additions and 153 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ npm run deploy
## TODO

- [ ] Write `README.md`
- [ ] Change the document title dynamically (with nested `Suspense`)
- [ ] Improve GUI
- [x] add additional info about the repo
- [x] implement dark/light themes
Expand All @@ -26,16 +25,38 @@ npm run deploy
- [x] add reload button
- [x] fix header hidden on small screens
- [ ] add colors for languages
- [ ] refactor code wit [shadcn](https://ui.shadcn.com/) UI
- [ ] fix cookie storage/deletion (and interferences with GUI)
- [ ] Change the document title dynamically (with nested `Suspense`)
- [ ] use nested renderer to render multiple components
- [ ] use `useRequestContext` to have conditional render
- [ ] implement Swagger with API
- [ ] add "Browse API" in addition to "Browse GitHub repositories"
- [ ] Define unit tests
- [ ] Fix interfaces and type definitions
- [ ] Setup GitHub Actions
- [ ] to deploy the website to GitHub
- [x] to run tests automatically
- [ ] fix GitHub security detection on `tests` files
- [ ] Make sure the project is compatible with multiple deployment types
- [x] Cloudflare
- [ ] GitHub
- [ ] Heroku
- [ ] Setup GitHub App (to avoid refreshing PAT every year)
- [ ] Setup GitHub App
- [x] fix oauth workflow
- [ ] define default demo/GitHub choice page
- [ ] error handling when `access_token` is not defined (go back to authentication or use default token)
- [ ] display error messages in a page
- [ ] validate the token used before using them
- [ ] fix `access_token`/`refresh_token` cookie storage issue
- [ ] optimize performance
- [ ] change GitHub App icon
- [x] handle `state` verification
- [ ] handle `access_token` refresh with `refresh_token`
- [ ] Use middlewares
- [ ] to declare the octokit instance
- [x] to add auth header if access_token is valid (to allow API use when `access_token` is defined)
- [x] fix API by adding 404 not found if no repo by id is found
- [x] Add icons in footer
- [x] Find `MAX_ID` dynamically and store in Cookies
- [x] Require GitHub API (Bearer Auth) for `/api/*` requests
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "petithub",
"version": "0.6.0",
"version": "0.6.1",
"private": false,
"description": "PetitHub - Explore obscure GitHub repositories",
"author": {
Expand Down Expand Up @@ -30,7 +30,7 @@
"deploy": "$npm_execpath run build && wrangler pages deploy"
},
"dependencies": {
"hono": "^4.4.9",
"hono": "^4.4.10",
"octokit": "^4.0.2"
},
"devDependencies": {
Expand Down
7 changes: 1 addition & 6 deletions public/static/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ function getCookie(name) {
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
return match ? match[1] : undefined;
}

const maxIdCookie = getCookie("max_id");
const maxIdCookie = getCookie(`__Secure-max_id`);
if (!maxIdCookie) {
fetch("/id");
}

const refresh = () => {
window.location.reload();
};
159 changes: 63 additions & 96 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,127 +1,94 @@
import { Context, Hono } from "hono";
import { Suspense } from "hono/jsx";
import { prettyJSON } from "hono/pretty-json";
import { bearerAuth } from "hono/bearer-auth";
import { cors } from "hono/cors";
import { logger } from "hono/logger";

import {
renderer,
Loader,
Container,
RepositoryContainer,
} from "./utils/renderer";
import {
getOctokitInstance,
verifyToken,
getRepos,
getRepository,
getRandomRepository,
getMaxId,
} from "./utils/octokit";
import { getCookieId, setCookieId } from "./utils/cookie";
import { renderer, Loader, RepositoryContainer, Login } from "./utils/renderer";
import { getOctokitInstance } from "./utils/octokit";
import { handleMaxId, handleTokens } from "./utils/cookie";
import { generateState, handleState } from "./utils/state";

import api from "./routes/api";
import id from "./routes/id";
import template from "./routes/template";
import petithub from "./routes/petithub";
import github from "./routes/github";
import welcome from "./routes/welcome";

/* TYPES */
export type Bindings = {
GITHUB_TOKEN: string;
CLIENT_ID: string;
CLIENT_SECRET: string;
};

/* APP */
const app = new Hono<{ Bindings: Bindings }>();
export type Variables = {
max_id: number;
access_token?: string;
refresh_token?: string;
state?: string;
};

/* GLOBAL VARIABLES */
const MAX_ID = 815471592; // TBU
/* APP */
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();

/* MIDDLEWARES */
app.use(logger());
app.use(renderer);
app.use(prettyJSON());
app.use("/api/*", cors({ origin: "*", allowMethods: ["GET"] }));
app.use("/api/*", bearerAuth({ verifyToken }));

/* ROUTES */
app.get(
"/api",
async (c: Context<{ Bindings: Bindings }>): Promise<Response> => {
const octokit = getOctokitInstance(c);
try {
const repository = await getRandomRepository(octokit, Number(MAX_ID));
return c.json(repository);
} catch (error: any) {
console.error("Error fetching repository data:", error);
return c.json({ error: "Failed to fetch repository data" }, 500);
app.use(handleMaxId());
app.use(handleTokens());
app.use("/", async (c, next) => {
const { access_token, refresh_token } = c.var;
console.log(access_token, refresh_token);
if (!access_token) {
// TODO allow some use without access_token and only redirect when out of free API usage
if (!refresh_token) {
// return c.redirect("/login", 302);
} else {
return c.redirect(
`/github/access_token?refresh_token=${refresh_token}&callback_url=/`
);
}
}
);

app.get(
"/api/:id",
async (c: Context<{ Bindings: Bindings }>): Promise<Response> => {
const { id } = c.req.param();
const octokit = getOctokitInstance(c);
try {
const repository = await getRepository(octokit, Number(id));
return c.json(repository);
} catch (error: any) {
console.error("Error fetching repository data:", error);
return c.json({ error: "Failed to fetch repository data" }, 500);
}
}
);
await next();
}); // TODO handle token verification
app.use("/github/login", generateState());
app.use("/github/callback", handleState());

app.get(
"/id",
async (c: Context<{ Bindings: Bindings }>): Promise<Response> => {
const octokit = getOctokitInstance(c);
const cookieId = getCookieId(c);
const id = await getMaxId(octokit, Number(cookieId || MAX_ID));
setCookieId(c, id);
const timestamp = new Date();
return c.json({ id, timestamp });
}
);
/* ROUTES */
app.route("/api", api);
app.route("/id", id);
app.route("/template", template);
app.route("/petithub", petithub);
app.route("/github", github);
app.route("/welcome", welcome);

app.get(
"/template",
async (c: Context<{ Bindings: Bindings }>): Promise<Response> => {
const octokit = getOctokitInstance(c);
const { data: repository } = await getRepos(
octokit,
"octocat",
"Hello-World"
);
return c.render(
<Suspense fallback={<Loader />}>
<Container repository={repository} />
</Suspense>,
{ title: "PetitHub - octocat/Hello-world" }
);
"/login",
(c: Context<{ Bindings: Bindings; Variables: Variables }>): Response => {
return c.render(<Login message="" />, {
title: "PetitHub - login",
});
}
);
); // TODO suppress login route and add specific button to login

/* ROOT */
app.get(
"/petithub",
async (c: Context<{ Bindings: Bindings }>): Promise<Response> => {
"/",
async (
c: Context<{ Bindings: Bindings; Variables: Variables }>
): Promise<Response> => {
const octokit = getOctokitInstance(c);
const { data: repository } = await getRepos(octokit, "cletqui", "petithub");
const { max_id } = c.var;
return c.render(
<Suspense fallback={<Loader />}>
<Container repository={repository} />
<RepositoryContainer octokit={octokit} maxId={max_id} />
</Suspense>,
{ title: "PetitHub - cletqui/petithub" }
{ title: "PetitHub" } // TODO change this title dynamically
);
}
);

app.get("/", async (c: Context<{ Bindings: Bindings }>): Promise<Response> => {
const octokit = getOctokitInstance(c);
const cookieId = getCookieId(c);
const maxId = cookieId || MAX_ID;
return c.render(
<Suspense fallback={<Loader />}>
<RepositoryContainer octokit={octokit} maxId={maxId} />
</Suspense>,
{ title: "PetitHub" } // TODO change this title dynamically
);
});

/* DEFAULT */
app.get("*", (c) => {
return c.redirect("/", 301);
});
Expand Down
66 changes: 66 additions & 0 deletions src/routes/api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Context, Hono } from "hono";
import { poweredBy } from "hono/powered-by";
import { prettyJSON } from "hono/pretty-json";
import { cors } from "hono/cors";

import { Bindings, Variables } from "..";
import {
apiAuth,
getOctokitInstance,
getRandomRepository,
getRepository,
} from "../utils/octokit";

const app = new Hono<{ Bindings: Bindings; Variables: Variables }>();

/* MIDDLEWARES */
app.use(apiAuth());
app.use(poweredBy());
app.use(prettyJSON());
app.use(cors({ origin: "*", allowMethods: ["GET"], credentials: true }));

/* ENDPOINTS */
app.get(
"",
async (
c: Context<{ Bindings: Bindings; Variables: Variables }>
): Promise<Response> => {
const octokit = getOctokitInstance(c);
const { max_id } = c.var;
try {
const repository = await getRandomRepository(octokit, max_id);
return c.json(repository);
} catch (error: any) {
return c.json({ error: "Failed to fetch repository data" }, 500);
}
}
); // TODO fix request to "/api" that redirects to "/"

app.get(
"/:id",
async (
c: Context<{ Bindings: Bindings; Variables: Variables }>
): Promise<Response> => {
const id = c.req.param("id");
const octokit = getOctokitInstance(c);
try {
const repository = await getRepository(octokit, Number(id));
const { id: repositoryId } = repository;
if (Number(id) === repositoryId) {
return c.json(repository);
} else {
return c.json(
{
message: `Repository id:${id} not found.`,
nextId: repositoryId,
},
404
);
}
} catch (error: any) {
return c.json({ error: "Failed to fetch repository data" }, 500);
}
}
);

export default app;
Loading

0 comments on commit ab6f608

Please sign in to comment.