-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f2bc255
commit d4db96a
Showing
16 changed files
with
302 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,28 @@ | ||
env: | ||
es2021: true | ||
node: true | ||
extends: | ||
- "eslint:recommended" | ||
- "plugin:@typescript-eslint/recommended" | ||
- eslint:recommended | ||
- plugin:@typescript-eslint/recommended | ||
- plugin:react/recommended | ||
parser: "@typescript-eslint/parser" | ||
parserOptions: | ||
ecmaVersion: latest | ||
sourceType: module | ||
plugins: | ||
- "@typescript-eslint" | ||
root: true | ||
- "@typescript-eslint" | ||
- react | ||
rules: | ||
indent: | ||
- error | ||
- 4 | ||
linebreak-style: | ||
- error | ||
- unix | ||
quotes: | ||
- error | ||
- double | ||
semi: | ||
- error | ||
- always | ||
react/react-in-jsx-scope: "off" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
DISCORD_CLIENT_SECRET= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,5 +4,7 @@ dist | |
.wrangler | ||
|
||
# package | ||
wrangler.toml | ||
pnpm-lock.yaml | ||
|
||
# env | ||
.dev.vars |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
html { | ||
width: min(90svw, 60rem); | ||
margin: 0 auto; | ||
} | ||
|
||
p { | ||
font-size: 115%; | ||
line-height: 1.6; | ||
} | ||
|
||
button { | ||
cursor: pointer; | ||
display: block; | ||
padding: 18px; | ||
margin: 0 auto; | ||
font-size: 250%; | ||
font-weight: bold; | ||
border: 0; | ||
border-radius: 9999px; | ||
background-color: #222; | ||
color: #eee; | ||
box-shadow: #777 4px 4px 16px; | ||
} | ||
|
||
button:active { | ||
box-shadow: unset; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Cont, Promise } from "@mikuroxina/mini-fn"; | ||
|
||
import type { Repository } from "../services"; | ||
import type { User, Connection } from "../services/patch-members"; | ||
|
||
export const withDiscordRepository = | ||
<T>(token: string): Cont.ContT<T, Promise.PromiseHkt, Repository> => | ||
async (repoUser: (repo: Repository) => Promise<T>): Promise<T> => { | ||
const repo = newRepo(token); | ||
const result = await repoUser(repo); | ||
await revoke(token); | ||
return result; | ||
}; | ||
|
||
const DISCORD_API = "https://discord.com/api/v10"; | ||
|
||
const newRepo = (token: string): Repository => ({ | ||
async user(): Promise<User> { | ||
const meRes = await fetch(DISCORD_API + "/users/@me", { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}); | ||
return await meRes.json(); | ||
}, | ||
async connections(): Promise<Connection[]> { | ||
const connectionsRes = await fetch( | ||
DISCORD_API + "/users/@me/connections", | ||
{ | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}, | ||
); | ||
return await connectionsRes.json(); | ||
}, | ||
}); | ||
|
||
async function revoke(token: string) { | ||
await fetch(DISCORD_API + "/oauth2/token/revoke", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/x-www-form-urlencoded", | ||
}, | ||
body: new URLSearchParams({ | ||
token, | ||
}), | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { Store } from "../services/patch-members"; | ||
|
||
export class R2Store implements Store { | ||
constructor(private readonly bucket: R2Bucket) {} | ||
|
||
async put(id: string, entry: unknown): Promise<void> { | ||
await this.bucket.put(id, JSON.stringify(entry)); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { Result } from "@mikuroxina/mini-fn"; | ||
import { Hono } from "hono"; | ||
import { serveStatic } from "hono/cloudflare-workers"; | ||
|
||
import { withDiscordRepository } from "./adaptors/discord"; | ||
import { R2Store } from "./adaptors/r2"; | ||
import { patchMembers } from "./services/patch-members"; | ||
import { Index } from "./pages"; | ||
import { Done } from "./pages/done"; | ||
|
||
type Bindings = { | ||
ASSOC_BUCKET: R2Bucket; | ||
DISCORD_CLIENT_ID: string; | ||
DISCORD_CLIENT_SECRET: string; | ||
}; | ||
|
||
const app = new Hono<{ Bindings: Bindings }>(); | ||
|
||
app.use("/static/*", serveStatic({ root: "./" })); | ||
app.get("/", (c) => c.html(<Index requestUrl={c.req.url} />)); | ||
app.get("/done", (c) => c.html(<Done />)); | ||
app.get("/redirect", async (c) => { | ||
const code = c.req.query("code"); | ||
if (!code) { | ||
return c.text("Bad Request", 400); | ||
} | ||
|
||
const tokenRes = await fetch("https://discord.com/api/v10/oauth2/token", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/x-www-form-urlencoded", | ||
}, | ||
body: new URLSearchParams({ | ||
client_id: c.env.DISCORD_CLIENT_ID, | ||
client_secret: c.env.DISCORD_CLIENT_SECRET, | ||
grant_type: "authorization_code", | ||
code, | ||
redirect_uri: new URL("/redirect", c.req.url).toString(), | ||
}), | ||
}); | ||
const json = await tokenRes.json<{ access_token: string; }>(); | ||
|
||
|
||
const store = new R2Store(c.env.ASSOC_BUCKET); | ||
return await withDiscordRepository<Response>(json.access_token)(async (repo) => { | ||
const result = await patchMembers(repo, store); | ||
if (Result.isErr(result)) { | ||
console.error(result[1]); | ||
return c.text("Internal Server Error", 500); | ||
} | ||
return c.redirect("/done"); | ||
}); | ||
}); | ||
|
||
export default app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
const ASSOCIATION_TYPES = ["twitter", "github"] as const; | ||
|
||
export type AssociationType = (typeof ASSOCIATION_TYPES)[number]; | ||
export const checkAssociationType = (type: string): type is AssociationType => | ||
(ASSOCIATION_TYPES as readonly string[]).includes(type); | ||
|
||
export interface AssociatedLink { | ||
type: AssociationType; | ||
id: string; | ||
name: string; | ||
} | ||
export const checkAssociationLink = (link: { | ||
type: string; | ||
id: string; | ||
name: string; | ||
}): link is AssociatedLink => checkAssociationType(link.type); | ||
|
||
export interface Member { | ||
discordId: string; | ||
associatedLinks: AssociatedLink[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const Done = () => <html lang="ja"> | ||
<head> | ||
<meta charset="UTF-8"/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> | ||
<title>Approvers メンバー情報登録 - 登録完了</title> | ||
<link rel="stylesheet" href="/static/style.css" /> | ||
</head> | ||
<body> | ||
<h1>登録完了</h1> | ||
<p>登録できました. このタブは閉じてもらって構いません.</p> | ||
</body> | ||
</html>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
export const Index = ({ requestUrl }: { requestUrl: string; }): JSX.Element => { | ||
const redirectUrl = `https://discord.com/api/oauth2/authorize?client_id=1141210184505639003&redirect_uri=${ | ||
encodeURIComponent(new URL("/redirect", requestUrl).toString()) | ||
}&response_type=code&scope=identify%20guilds.members.read%20connections`; | ||
return ( | ||
<html lang="ja"> | ||
<head> | ||
<meta charset="UTF-8"/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> | ||
<title>Approvers メンバー情報登録</title> | ||
<link rel="stylesheet" href="/static/style.css" /> | ||
</head> | ||
<body> | ||
<h1>Approvers メンバー情報登録</h1> | ||
<h2>Approvers へようこそ</h2> | ||
<p>こちらではあなたの Discord アカウントに関連付けてある GitHub / X (旧 Twitter) アカウントの情報を登録できます. これは Discord の OAuth を利用しており, 下記のボタンから当サービスでのトークンの利用を認可していただくことで登録処理を開始できます.</p> | ||
<a href={redirectUrl} referrerPolicy="no-referrer" rel="noopener"> | ||
<button>アカウント情報を登録する</button> | ||
</a> | ||
</body> | ||
</html> | ||
);}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { type Repository as PatchMembersRepository } from "./services/patch-members"; | ||
|
||
export type Repository = PatchMembersRepository; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Result } from "@mikuroxina/mini-fn"; | ||
|
||
import { type Member, checkAssociationLink } from "../models"; | ||
|
||
export interface User { | ||
id: string; | ||
} | ||
export interface Connection { | ||
id: string; | ||
name: string; | ||
type: string; | ||
} | ||
|
||
export interface Repository { | ||
user(): Promise<User>; | ||
connections(): Promise<Connection[]>; | ||
} | ||
export interface Store { | ||
put(id: string, entry: unknown): Promise<void>; | ||
} | ||
|
||
export const patchMembers = async ( | ||
repository: Repository, | ||
store: Store, | ||
): Promise<Result.Result<Error, []>> => { | ||
const me = await repository.user(); | ||
const connections = await repository.connections(); | ||
|
||
const member = { | ||
discordId: me.id, | ||
associatedLinks: connections | ||
.map(({ type, id, name }) => ({ type, id, name })) | ||
.filter(checkAssociationLink), | ||
} satisfies Member; | ||
|
||
store.put(me.id, member); | ||
return Result.ok<[]>([]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ESNext", | ||
"module": "ESNext", | ||
"moduleResolution": "node", | ||
"esModuleInterop": true, | ||
"strict": true, | ||
"lib": ["esnext"], | ||
"types": ["@cloudflare/workers-types"], | ||
"jsx": "react-jsx", | ||
"jsxImportSource": "hono/jsx", | ||
"verbatimModuleSyntax": true | ||
} | ||
"compilerOptions": { | ||
"target": "ESNext", | ||
"module": "ESNext", | ||
"moduleResolution": "node", | ||
"esModuleInterop": true, | ||
"strict": true, | ||
"lib": ["esnext"], | ||
"types": ["@cloudflare/workers-types"], | ||
"jsx": "react-jsx", | ||
"jsxFragmentFactory": "Fragment", | ||
"jsxImportSource": "hono/jsx", | ||
"verbatimModuleSyntax": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
name = "members-assoc" | ||
main = "src/index.tsx" | ||
compatibility_date = "2023-06-28" | ||
|
||
r2_buckets = [ | ||
{ binding = "ASSOC_BUCKET", bucket_name = "members-assoc", preview_bucket_name = "members-assoc-test" }, | ||
] | ||
|
||
[site] | ||
bucket = "./assets" | ||
|
||
[vars] | ||
DISCORD_CLIENT_ID = "1141210184505639003" | ||
|
||
[dev] | ||
port = 3000 |