SlideShare a Scribd company logo
1 of 22
Download to read offline
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
GitHub APIとfreshで遊ぼう



虎の穴ラボ 藤原 佳顕

1
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
アジェンダ

● 概要
● GitHub APIについて
● 構成
● freshについて
● 実装と作ったもの
2
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
自己紹介

3
● 名前

○ 藤原佳顕

● 仕事

○ FantiaとかCreatiaとか社内アプリとか

● 好み

○ Clojure、Rust

● 趣味

○ 格闘ゲーム

■ Melty Blood、Guilty Gear

○ STG(ダライアスとか)も好き

○ 祝エルデンリング発売

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
概要

4
● 諸事情でGitHub APIを叩いてコメントを抜いていくる必要がありました

● DenoでGitHub API叩いてコメント抜いてくることにしました

● せっかくなのでWebアプリにしてみました

○ https://github-comment.deno.dev/
○ https://github.com/zonuko/deno-github-comment
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
GitHub APIについて

5
● 何種類かある

○ v3: https://docs.github.com/ja/rest


■ REST API

■ 認証方法に種類がある

● ユーザー個別のトークン利用

● OAuth

● 今回はどちらも試す

○ v4: https://docs.github.com/ja/graphql


■ GraphQL API

■ 直感的に使うのは面倒そうだったので一旦見送り


Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
構成

6
● SDKっぽい物があるが今回は使わない(octokit)


○ https://github.com/octokit/octokit.js/


○ 単にREST API叩くだけなので問題はなさそう 

● 使うもの

○ Deno 1.19

○ dotenv

○ fresh(web用)

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(freshについて)

7
● freshについて

○ Denoの中の人Luca Casonato氏が作っている 

○ preactベースのWebフレームワーク 

○ deno deployで動作する 

○ ビルドがない

○ deno.land、lint.deno.landのページなどがこちらに置き換えられている 

■ →サンプルが豊富 

○ Next.js、Nuxt.js同様にディレクトリ構造ベースのルーティング 

○ まだまだ発展途上感もあり 

○ ドキュメント化されていない情報が多々あるので間違っているかもしれません 

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(cli+ユーザートークン)




8
import { config } from "./deps.ts";
const token = config().GITHUB_TOKEN;
const user = config().GITHUB_USER;
function buildConfig(): RequestInit {
return {
headers: {
Authorization: `token ${token}`,
Accept: "application/vnd.github.v3+json",
},
};
}
async function getRepos(): Promise<any> {
const res = await fetch(
`https://api.github.com/users/${user}/repos`,
buildConfig(),
);
return await res.json();
}
async function getPulls(repo: string): Promise<any> {
const res = await fetch(
`https://api.github.com/repos/${user}/${repo}/pulls?state=all`,
buildConfig(),
);
return await res.json();
}
async function getComments(repo: string, id: number): Promise<any> {
const res = await fetch(
`https://api.github.com/repos/${user}/${repo}/pulls/${id}/comments`,
buildConfig(),
);
}
const repos = await getRepos() as any[];
repos.forEach(async (repo) => {
console.log(await getPulls(repo.name));
});
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(ユーザートークン+cliツール)

9
● 単にfetch使っているだけなのでここまで余興

● 実際には次からのWebアプリ化が本題

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

10
● 実装方針

○ トークンはOAuthトークンを使う

■ まずはGitHub Loginを作る

○ ストレージの類は利用しない(RDB、Redisなど)


■ したがってセッション系の情報は暗号化してcookieに保存


● Rails知っている人はRailsの基本動作を想像してもらえると


○ 作るページはリポジトリ一覧、リポジトリのコメント一覧(、プルリク一覧)


Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

11
● page/index.tsx→ログインフォームがあるだけ

/** @jsx h */
import { Layout } from "../components/Layout.tsx";
import { h, PageConfig, PageProps, useData } from "../deps.ts";
export default function Home(props: PageProps) {
const errorQuery = useData("errorQuery", () => {
const q = props.url.searchParams.get("error");
if (q === "missing_code") {
return "Not found GitHub OAuth code";
}
if (q === "invalid_state") {
return "Invalid OAuth state";
}
if (q === "failed_to_get_token") {
return "Failed to get the access token";
}
});
return (
<Layout title="Login - GitHub Comments">
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">Login</span>
</div>
</nav>
{errorQuery &&
(
<div class="alert alert-danger" role="alert">
{errorQuery}
</div>
)}
<div class="position-relative text-center">
<a
type="button"
class="btn btn-outline-dark"
href="/login/github/auth"
>
<i class="bi bi-github"></i>
{" Login with GitHub"}
</a>
</div>
</Layout>
);
}
export const config: PageConfig = { runtimeJS: true };
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

12
● useData

○ サーバー側でレンダリング前にデータを取得できる


○ エラーでリダイレクトされてきた場合はSSRの時点で表示したい


● PageConfig

○ いわゆるオプションの設定

○ runtimeJS: クライアント側でもJS実行するかどうか


○ 他routeOverrideやcspなどオプションがある


Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

13
page/login/github/auth.ts

import { buildGithubUrl, tokenEncrypt } from "../../../logics/github.ts";
import { HandlerContext } from "../../../server_deps.ts";
export async function handler(_ctx: HandlerContext): Promise<Response> {
const oauthUrl = buildGithubUrl();
const githubRedirect = new Response(null, {
status: 302,
headers: {
location: oauthUrl,
"set-cookie": `state=${await tokenEncrypt(
new URL(oauthUrl).searchParams.get("state") || ""
)};HttpOnly;SameSite=Lax;Path=/;`,
},
});
return githubRedirect;
}
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

14
● handler関数

○ handler関数をexportすることでAPIを生やすことが出来ます


○ 今回は単純で、後の検証のためにstateを暗号化してcookieに書き込み


○ その後GitHubのログイン画面にリダイレクト


○ サーバーサイドの処理なのでcookieやリクエストヘッダーなどある程度は触れる


○ 拡張子をtsx(jsx)にすることで通常のレンダリングも同時に行うことができる


■ 試した限りでは現状handlerとjsx両方がある場合はhandler→ssrといった順番で実行さ
れる模様

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

15
page/login/github/callback.ts

export async function handler({req}: HandlerContext): Promise<Response> {
const url = new URL(req.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
if (!code) {
return Response.redirect(`${url.origin}/?error=missing_code`);
}
const cookieState = await tokenDecrypt(cookie.getCookies(req.headers)["state"]);
if (cookieState !== state) {
return Response.redirect(`${url.origin}/?error=invalid_state`);
}
const apiRes = await fetch("https://github.com/login/oauth/access_token", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: githubClientId(),
client_secret: githubClientSecret(),
code: code,
}),
});
if (apiRes.status !== 200) {
return Response.redirect(`${url.origin}/?error=failed_to_get_token`);
}
const resJson = await apiRes.json();
if (resJson["error"]) {
return Response.redirect(`${url.origin}/?error=failed_to_get_token`);
}
const successRes = new Response(null, {
status: 302,
headers: {
location: `${url.origin}/mypage/github`,
"set-cookie": `oauth_token=${await tokenEncrypt(
resJson["access_token"],
)};HttpOnly;SameSite=Lax;Path=/;`,
},
});
deleteCookie(successRes.headers, "state", {path: "/"});
return successRes;
}
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

16
● GitHubでのログイン後に帰ってくるパス


○ state及びcodeがつけられて帰ってくるので各々チェック 

■ state: cookieの値を復号化して一致するか(CSRF対策)


■ code: アクセストークンを取得するのに必要


● アクセストークン取得

○ 本来であればRDBやサーバー側セッションに持つべきだが今回はcookieに


○ 秘匿情報なのでサーバー側でしか復号化出来ないように暗号化して入れる


■ 今回はAES-GCMを採用

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

17
● 暗号化について

● 詳細はMDNで https://developer.mozilla.org/ja/docs/Web/API/SubtleCrypto


● まずはsecret.jsonを生成(アプリ起動前)


○ jwkという形式でjsonを作れるのでそのままファイルに保存


○ Git等に入れないように注意!


■ いわゆる秘密鍵なのでサーバー個別に持つべき


async function genKey() {
return await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
}
async function exportKey() {
const key = await crypto.subtle.exportKey(
"jwk",
await genKey(),
);
await Deno.writeTextFile("./secret.json", JSON.stringify(key));
console.log("generate secret.json. add to .gitignore");
}
await exportKey();
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

18
● 暗号化処理

async function loadSecret() {
const text = JSON.parse(await Deno.readTextFile("secret.json"));
return await crypto.subtle.importKey(
"jwk",
text,
{ name: "AES-GCM" },
false,
["encrypt", "decrypt"],
);
}
export async function tokenEncrypt(val: string): Promise<string> {
const ivBytes = iv();
const c = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: ivBytes },
await loadSecret(),
new TextEncoder().encode(val),
);
● iv

○ 暗号化のために必要なランダムな値


○ 秘密である必要はないが、一意である必要がある


○ GCMでは12byteが推奨なので注意


● loadSecret

○ 先程のsecret.jsonを読み込む


○ 本来ならば起動時に一度のみ読み込みたい


■ もしくはキャッシュ

● tokenEncrypt

○ 上記を合わせて引数の文字列を暗号化する


○ 後にcookieに書き込みたいのでちゃんと文字として値を返
す

return `${btoa(String.fromCharCode(...new Uint8Array(ivBytes)))}--${
btoa(String.fromCharCode(...new Uint8Array(c)))
}`;
}
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

19
● 復号処理

export async function tokenDecrypt(val: string): Promise<string> {
const [ivVal, token] = val.split("--");
const encryptedBytes = atob(token);
const ivBytes = atob(ivVal);
const encryptedData = Uint8Array.from(
encryptedBytes.split(""),
(char) => char.charCodeAt(0),
);
const ivData = Uint8Array.from(
ivBytes.split(""),
(char) => char.charCodeAt(0),
);
● 基本的には暗号化の逆を行う

○ iv及びsecretを使って復号化する

● 最終的にはトークンがほしいのでこちらも文字列に

● 基本的にはサーバー側でしか実行されない

○ Denoに生えているAPIも使える(はず)

const decryptedArrayBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: ivData },
await loadSecret(),
encryptedData,
);
return new TextDecoder().decode(new
Uint8Array(decryptedArrayBuffer));
}
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

20
トークンを使って情報取得(pages/api/github/repos.ts)


export async function handler(ctx: HandlerContext): Promise<Response> {
const cookieValue = getCookies(ctx.req.headers)["oauth_token"];
const url = new URL(ctx.req.url);
const page = url.searchParams.get("page") || "1";
const perPage = url.searchParams.get("per_page") || "30";
const res = await fetch(
`https://api.github.com/user/repos?page=${page}&per_page=${perPage}`,
{
headers: {
Authorization: `token ${await tokenDecrypt(cookieValue)}`,
},
},
);
const resJson = await res.json();
const link = res.headers.get("link") || "";
const pagenation = buildPagenation(link);
return new Response(JSON.stringify({
link: pagenation,
repos: resJson,
}));
}
● tokenの復号化などの処理がはいるので直接GitHub API
を叩くのではなく、サーバーを経由する

● 先程と同様にhandlerのみでルーティングする

● apiディレクトリに置くことでよりAPIらしく使える

○ fresh initしたときにも生成されるのでそちらも参
照

Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
実装と作ったもの(Webアプリ化)

21
API使う側(mypage/github.tsx)


普通のReactアプリなので特筆することはなし


→cookie等の都合でssr時のAPIコールがうまく行かない


const PER_PAGE = 30;
export default function Github(props: PageProps) {
const [repos, setRepos] = useState({
repos: [],
});
const initUrl = useData("repoInitUrl", () => props.url);
const pageParams = useData(
"repoInitPageNo",
() => {
const p = parseInt(props.url.searchParams.get("page") || "1");
if (p <= 0) return 1;
return p;
},
);
const [url, setUrl] = useState(initUrl);
const [page, setPage] = useState(pageParams);
const [link, setLink] = useState<{ link: Pagenation }>({
link: { hasNext: false, hasPrev: false },
});
useEffect(() => {
const call = async () => {
const res = await fetch(
`/api/github/repos?page=${page}&per_page=${PER_PAGE}`,
);
const json = await res.json();
if (json.repos) {
setRepos({ repos: json.repos });
}
if (json.link) {
setLink({ link: json.link });
}
};
call();
const newUrl = new URL(url);
newUrl.searchParams.set("page", page.toString());
window.history.pushState(null, "", newUrl);
setUrl(newUrl);
}, [page]);
const onPrevPage = () => {
if (link.link.hasPrev && link.link.prev) {
setPage(link.link.prev);
}
};
const onNextPage = () => {
if (link.link.hasNext && link.link.next) {
setPage(link.link.next);
}
};
Copyright  (C) 2021 Toranoana Inc. All Rights Reserved.
まとめ

● denoでGitHubにログイン〜APIコールを行ってみました


● 付随して暗号化なども実施しました


● freshを使ってWebアプリにしました


● freshは全く枯れてないのでご利用にはご注意を


● フィルタなどの実装は今後

○ handlerとjsx組み合わせればできる(気がする)


22

More Related Content

What's hot

忙しい人の5分で分かるDocker 2017年春Ver
忙しい人の5分で分かるDocker 2017年春Ver忙しい人の5分で分かるDocker 2017年春Ver
忙しい人の5分で分かるDocker 2017年春VerMasahito Zembutsu
 
実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjug実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjugYahoo!デベロッパーネットワーク
 
MQTTとAMQPと.NET
MQTTとAMQPと.NETMQTTとAMQPと.NET
MQTTとAMQPと.NETterurou
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
VyOSで作るIPv4 Router/IPv6 Bridge
VyOSで作るIPv4 Router/IPv6 BridgeVyOSで作るIPv4 Router/IPv6 Bridge
VyOSで作るIPv4 Router/IPv6 BridgeKLab Inc. / Tech
 
OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)
OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)
OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)VirtualTech Japan Inc.
 
がっつりMongoDB事例紹介
がっつりMongoDB事例紹介がっつりMongoDB事例紹介
がっつりMongoDB事例紹介Tetsutaro Watanabe
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことBIGLOBE Inc.
 
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)NTT DATA Technology & Innovation
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話Kumazaki Hiroki
 
マイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチマイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチ増田 亨
 
DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」
DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」 DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」
DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」 Koichiro Matsuoka
 
WebSocketのキホン
WebSocketのキホンWebSocketのキホン
WebSocketのキホンYou_Kinjoh
 
Docker Compose入門~今日から始めるComposeの初歩からswarm mode対応まで
Docker Compose入門~今日から始めるComposeの初歩からswarm mode対応までDocker Compose入門~今日から始めるComposeの初歩からswarm mode対応まで
Docker Compose入門~今日から始めるComposeの初歩からswarm mode対応までMasahito Zembutsu
 
世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]
世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]
世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]Google Cloud Platform - Japan
 
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]Koichiro Matsuoka
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Kohei Tokunaga
 
暗号技術の実装と数学
暗号技術の実装と数学暗号技術の実装と数学
暗号技術の実装と数学MITSUNARI Shigeo
 
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとはコンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとはksk_ha
 

What's hot (20)

忙しい人の5分で分かるDocker 2017年春Ver
忙しい人の5分で分かるDocker 2017年春Ver忙しい人の5分で分かるDocker 2017年春Ver
忙しい人の5分で分かるDocker 2017年春Ver
 
実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjug実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjug
 
MQTTとAMQPと.NET
MQTTとAMQPと.NETMQTTとAMQPと.NET
MQTTとAMQPと.NET
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
VyOSで作るIPv4 Router/IPv6 Bridge
VyOSで作るIPv4 Router/IPv6 BridgeVyOSで作るIPv4 Router/IPv6 Bridge
VyOSで作るIPv4 Router/IPv6 Bridge
 
OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)
OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)
OpenStackで始めるクラウド環境構築入門(Horizon 基礎編)
 
がっつりMongoDB事例紹介
がっつりMongoDB事例紹介がっつりMongoDB事例紹介
がっつりMongoDB事例紹介
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
 
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
PostgreSQLをKubernetes上で活用するためのOperator紹介!(Cloud Native Database Meetup #3 発表資料)
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話
 
マイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチマイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチ
 
DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」
DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」 DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」
DDDオンライン勉強会#2 「集約・境界付けられたコンテキスト」
 
WebSocketのキホン
WebSocketのキホンWebSocketのキホン
WebSocketのキホン
 
Docker Compose入門~今日から始めるComposeの初歩からswarm mode対応まで
Docker Compose入門~今日から始めるComposeの初歩からswarm mode対応までDocker Compose入門~今日から始めるComposeの初歩からswarm mode対応まで
Docker Compose入門~今日から始めるComposeの初歩からswarm mode対応まで
 
世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]
世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]
世界を跨ぐリアルタイム PvP 対戦への挑戦 [Google Cloud INSIDE Games & Apps]
 
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
ドメイン駆動設計のプラクティスでカバーできること、できないこと[DDD]
 
Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行
 
Docker Compose 徹底解説
Docker Compose 徹底解説Docker Compose 徹底解説
Docker Compose 徹底解説
 
暗号技術の実装と数学
暗号技術の実装と数学暗号技術の実装と数学
暗号技術の実装と数学
 
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとはコンテナを止めるな!  PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
コンテナを止めるな! PacemakerによるコンテナHAクラスタリングとKubernetesとの違いとは
 

Similar to GitHub APIとfreshで遊ぼう

【とらラボLT】go言語でのweb apiの作り方3選
【とらラボLT】go言語でのweb apiの作り方3選【とらラボLT】go言語でのweb apiの作り方3選
【とらラボLT】go言語でのweb apiの作り方3選虎の穴 開発室
 
Rails on GKEで運用するWebアプリケーションの紹介
Rails on GKEで運用するWebアプリケーションの紹介Rails on GKEで運用するWebアプリケーションの紹介
Rails on GKEで運用するWebアプリケーションの紹介Makoto Haruyama
 
【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現UnityTechnologiesJapan002
 
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Yoshifumi Kawai
 
Inside mobage platform
Inside mobage platformInside mobage platform
Inside mobage platformToru Yamaguchi
 
Google container builderと友だちになるまで
Google container builderと友だちになるまでGoogle container builderと友だちになるまで
Google container builderと友だちになるまでlestrrat
 
PlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC Webアプリケーション
PlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC WebアプリケーションPlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC Webアプリケーション
PlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC WebアプリケーションKazuhiro Hara
 
【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜
【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜	【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜
【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜 虎の穴 開発室
 
「AROW」お披露目(実用編)
「AROW」お披露目(実用編)「AROW」お披露目(実用編)
「AROW」お披露目(実用編)Drecom Co., Ltd.
 
NextGen Server/Client Architecture - gRPC + Unity + C#
NextGen Server/Client Architecture - gRPC + Unity + C#NextGen Server/Client Architecture - gRPC + Unity + C#
NextGen Server/Client Architecture - gRPC + Unity + C#Yoshifumi Kawai
 
Terraform with Bitbucket pipeline
Terraform with Bitbucket pipelineTerraform with Bitbucket pipeline
Terraform with Bitbucket pipelineMasatomo Ito
 
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Next2Dで始めるゲーム開発  - Game Development Starting with Next2DNext2Dで始めるゲーム開発  - Game Development Starting with Next2D
Next2Dで始めるゲーム開発 - Game Development Starting with Next2DToshiyuki Ienaga
 
Play framework 2.0のおすすめと1.2からのアップグレード
Play framework 2.0のおすすめと1.2からのアップグレードPlay framework 2.0のおすすめと1.2からのアップグレード
Play framework 2.0のおすすめと1.2からのアップグレードKazuhiro Hara
 
CodeIgniterによるPhwittr
CodeIgniterによるPhwittrCodeIgniterによるPhwittr
CodeIgniterによるPhwittrkenjis
 
Sohu邮箱的python经验
Sohu邮箱的python经验Sohu邮箱的python经验
Sohu邮箱的python经验Ryan Poy
 
GitLab + Dokku で作る CI/CD 環境
GitLab + Dokku で作る CI/CD 環境GitLab + Dokku で作る CI/CD 環境
GitLab + Dokku で作る CI/CD 環境Kazuhiro Nishiyama
 
Cryogenでサイトつくろーじぇん
CryogenでサイトつくろーじぇんCryogenでサイトつくろーじぇん
CryogenでサイトつくろーじぇんKazuhiro Hara
 
配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack
配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack
配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hackyut148atgmaildotcom
 
ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用Yatabe Terumasa
 
Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋Mori Shingo
 

Similar to GitHub APIとfreshで遊ぼう (20)

【とらラボLT】go言語でのweb apiの作り方3選
【とらラボLT】go言語でのweb apiの作り方3選【とらラボLT】go言語でのweb apiの作り方3選
【とらラボLT】go言語でのweb apiの作り方3選
 
Rails on GKEで運用するWebアプリケーションの紹介
Rails on GKEで運用するWebアプリケーションの紹介Rails on GKEで運用するWebアプリケーションの紹介
Rails on GKEで運用するWebアプリケーションの紹介
 
【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
【Unity道場京都スペシャル4】Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
 
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
 
Inside mobage platform
Inside mobage platformInside mobage platform
Inside mobage platform
 
Google container builderと友だちになるまで
Google container builderと友だちになるまでGoogle container builderと友だちになるまで
Google container builderと友だちになるまで
 
PlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC Webアプリケーション
PlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC WebアプリケーションPlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC Webアプリケーション
PlayFramework 2.0 Javaと WebSocketでつくる リアルタイムMVC Webアプリケーション
 
【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜
【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜	【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜
【とらのあなラボ Tech Day #3】新規システムにおける技術選定〜GoとgRPCを採用した話〜
 
「AROW」お披露目(実用編)
「AROW」お披露目(実用編)「AROW」お披露目(実用編)
「AROW」お披露目(実用編)
 
NextGen Server/Client Architecture - gRPC + Unity + C#
NextGen Server/Client Architecture - gRPC + Unity + C#NextGen Server/Client Architecture - gRPC + Unity + C#
NextGen Server/Client Architecture - gRPC + Unity + C#
 
Terraform with Bitbucket pipeline
Terraform with Bitbucket pipelineTerraform with Bitbucket pipeline
Terraform with Bitbucket pipeline
 
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
Next2Dで始めるゲーム開発  - Game Development Starting with Next2DNext2Dで始めるゲーム開発  - Game Development Starting with Next2D
Next2Dで始めるゲーム開発 - Game Development Starting with Next2D
 
Play framework 2.0のおすすめと1.2からのアップグレード
Play framework 2.0のおすすめと1.2からのアップグレードPlay framework 2.0のおすすめと1.2からのアップグレード
Play framework 2.0のおすすめと1.2からのアップグレード
 
CodeIgniterによるPhwittr
CodeIgniterによるPhwittrCodeIgniterによるPhwittr
CodeIgniterによるPhwittr
 
Sohu邮箱的python经验
Sohu邮箱的python经验Sohu邮箱的python经验
Sohu邮箱的python经验
 
GitLab + Dokku で作る CI/CD 環境
GitLab + Dokku で作る CI/CD 環境GitLab + Dokku で作る CI/CD 環境
GitLab + Dokku で作る CI/CD 環境
 
Cryogenでサイトつくろーじぇん
CryogenでサイトつくろーじぇんCryogenでサイトつくろーじぇん
Cryogenでサイトつくろーじぇん
 
配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack
配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack
配布用Dotcloudによるすぐ始めるtwitterwebアプリ開発#twtr hack
 
ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用ソーシャルアプリ勉強会(第一回資料)配布用
ソーシャルアプリ勉強会(第一回資料)配布用
 
Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋Node予備校 vol.1 名古屋
Node予備校 vol.1 名古屋
 

More from 虎の穴 開発室

Railsのデバッグ どうやるかを改めて確認する
Railsのデバッグ どうやるかを改めて確認するRailsのデバッグ どうやるかを改めて確認する
Railsのデバッグ どうやるかを改めて確認する虎の穴 開発室
 
虎の穴ラボ エンジニア採用説明資料 .pdf
虎の穴ラボ エンジニア採用説明資料 .pdf虎の穴ラボ エンジニア採用説明資料 .pdf
虎の穴ラボ エンジニア採用説明資料 .pdf虎の穴 開発室
 
Deno Deployと組み合わせるのに Upstashをおすすめしたい.pdf
Deno Deployと組み合わせるのに Upstashをおすすめしたい.pdfDeno Deployと組み合わせるのに Upstashをおすすめしたい.pdf
Deno Deployと組み合わせるのに Upstashをおすすめしたい.pdf虎の穴 開発室
 
toranoana.deno #6 アジェンダ 採用説明
toranoana.deno #6 アジェンダ 採用説明toranoana.deno #6 アジェンダ 採用説明
toranoana.deno #6 アジェンダ 採用説明虎の穴 開発室
 
Deno 向け WEB 開発用のツールを作ったので 紹介します
Deno 向け WEB 開発用のツールを作ったので 紹介しますDeno 向け WEB 開発用のツールを作ったので 紹介します
Deno 向け WEB 開発用のツールを作ったので 紹介します虎の穴 開発室
 
Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –
Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –
Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –虎の穴 開発室
 
【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと
【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと
【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと虎の穴 開発室
 
通販開発部の西田さん「通販開発マネジメントの5ルール」
通販開発部の西田さん「通販開発マネジメントの5ルール」通販開発部の西田さん「通販開発マネジメントの5ルール」
通販開発部の西田さん「通販開発マネジメントの5ルール」虎の穴 開発室
 
社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!
社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!
社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!虎の穴 開発室
 
セキュリティを強化しよう!CloudArmorの機能解説
セキュリティを強化しよう!CloudArmorの機能解説セキュリティを強化しよう!CloudArmorの機能解説
セキュリティを強化しよう!CloudArmorの機能解説虎の穴 開発室
 
JavaScript LT会 〜 React.js Node.js歓迎 〜 Deno で やってみるweb開発
JavaScript LT会 〜 React.js   Node.js歓迎 〜 Deno で やってみるweb開発JavaScript LT会 〜 React.js   Node.js歓迎 〜 Deno で やってみるweb開発
JavaScript LT会 〜 React.js Node.js歓迎 〜 Deno で やってみるweb開発虎の穴 開発室
 
Amplify Studioを使ってみた
Amplify Studioを使ってみたAmplify Studioを使ってみた
Amplify Studioを使ってみた虎の穴 開発室
 
いいテスト会 (スプリントレビュー) をやろう!
いいテスト会 (スプリントレビュー) をやろう!いいテスト会 (スプリントレビュー) をやろう!
いいテスト会 (スプリントレビュー) をやろう!虎の穴 開発室
 
【Saitama.js】Denoのすすめ
【Saitama.js】Denoのすすめ【Saitama.js】Denoのすすめ
【Saitama.js】Denoのすすめ虎の穴 開発室
 
虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について
虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について
虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について虎の穴 開発室
 
虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント
虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント 虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント
虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント 虎の穴 開発室
 
【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る
【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る
【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る虎の穴 開発室
 
【20220120 toranoana.deno#4】denoでffiの続き
【20220120 toranoana.deno#4】denoでffiの続き【20220120 toranoana.deno#4】denoでffiの続き
【20220120 toranoana.deno#4】denoでffiの続き虎の穴 開発室
 

More from 虎の穴 開発室 (20)

FizzBuzzで学ぶJavaの進化
FizzBuzzで学ぶJavaの進化FizzBuzzで学ぶJavaの進化
FizzBuzzで学ぶJavaの進化
 
Railsのデバッグ どうやるかを改めて確認する
Railsのデバッグ どうやるかを改めて確認するRailsのデバッグ どうやるかを改めて確認する
Railsのデバッグ どうやるかを改めて確認する
 
虎の穴ラボ エンジニア採用説明資料 .pdf
虎の穴ラボ エンジニア採用説明資料 .pdf虎の穴ラボ エンジニア採用説明資料 .pdf
虎の穴ラボ エンジニア採用説明資料 .pdf
 
Deno Deployと組み合わせるのに Upstashをおすすめしたい.pdf
Deno Deployと組み合わせるのに Upstashをおすすめしたい.pdfDeno Deployと組み合わせるのに Upstashをおすすめしたい.pdf
Deno Deployと組み合わせるのに Upstashをおすすめしたい.pdf
 
toranoana.deno #6 アジェンダ 採用説明
toranoana.deno #6 アジェンダ 採用説明toranoana.deno #6 アジェンダ 採用説明
toranoana.deno #6 アジェンダ 採用説明
 
Deno 向け WEB 開発用のツールを作ったので 紹介します
Deno 向け WEB 開発用のツールを作ったので 紹介しますDeno 向け WEB 開発用のツールを作ったので 紹介します
Deno 向け WEB 開発用のツールを作ったので 紹介します
 
Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –
Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –
Supabase Edge Functions と Netlify Edge Functions を使ってみる – 機能とその比較 –
 
GCPの画像認識APIの紹介
GCPの画像認識APIの紹介 GCPの画像認識APIの紹介
GCPの画像認識APIの紹介
 
【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと
【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと
【エンジニアの勉強法ハックLT- vol.7】ゲームから学んだ勉強のこと
 
通販開発部の西田さん「通販開発マネジメントの5ルール」
通販開発部の西田さん「通販開発マネジメントの5ルール」通販開発部の西田さん「通販開発マネジメントの5ルール」
通販開発部の西田さん「通販開発マネジメントの5ルール」
 
社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!
社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!
社内DX推進!非エンジニア向けにプログラミング講座を実施してみた!
 
セキュリティを強化しよう!CloudArmorの機能解説
セキュリティを強化しよう!CloudArmorの機能解説セキュリティを強化しよう!CloudArmorの機能解説
セキュリティを強化しよう!CloudArmorの機能解説
 
JavaScript LT会 〜 React.js Node.js歓迎 〜 Deno で やってみるweb開発
JavaScript LT会 〜 React.js   Node.js歓迎 〜 Deno で やってみるweb開発JavaScript LT会 〜 React.js   Node.js歓迎 〜 Deno で やってみるweb開発
JavaScript LT会 〜 React.js Node.js歓迎 〜 Deno で やってみるweb開発
 
Amplify Studioを使ってみた
Amplify Studioを使ってみたAmplify Studioを使ってみた
Amplify Studioを使ってみた
 
いいテスト会 (スプリントレビュー) をやろう!
いいテスト会 (スプリントレビュー) をやろう!いいテスト会 (スプリントレビュー) をやろう!
いいテスト会 (スプリントレビュー) をやろう!
 
【Saitama.js】Denoのすすめ
【Saitama.js】Denoのすすめ【Saitama.js】Denoのすすめ
【Saitama.js】Denoのすすめ
 
虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について
虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について
虎の穴ラボ Tech day#3 チームで戦う!とらのあな通販冬の大感謝祭でのフロント開発について
 
虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント
虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント 虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント
虎の穴ラボ TechDay#3 フルリモート率100%!リモートワークを可能にするマネージメント
 
【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る
【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る
【20220120 toranoana.deno#4】deno を使って「ログイン」するサービスを作る
 
【20220120 toranoana.deno#4】denoでffiの続き
【20220120 toranoana.deno#4】denoでffiの続き【20220120 toranoana.deno#4】denoでffiの続き
【20220120 toranoana.deno#4】denoでffiの続き
 

Recently uploaded

論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...Toru Tamaki
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Yuma Ohgami
 
論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNetToru Tamaki
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Danieldanielhu54
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略Ryo Sasaki
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムsugiuralab
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdftaisei2219
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものですiPride Co., Ltd.
 
論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A surveyToru Tamaki
 

Recently uploaded (9)

論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
論文紹介:Content-Aware Token Sharing for Efficient Semantic Segmentation With Vis...
 
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
Open Source UN-Conference 2024 Kawagoe - 独自OS「DaisyOS GB」の紹介
 
論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet論文紹介:Automated Classification of Model Errors on ImageNet
論文紹介:Automated Classification of Model Errors on ImageNet
 
Postman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By DanielPostman LT Fukuoka_Quick Prototype_By Daniel
Postman LT Fukuoka_Quick Prototype_By Daniel
 
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
[DevOpsDays Tokyo 2024] 〜デジタルとアナログのはざまに〜 スマートビルディング爆速開発を支える 自動化テスト戦略
 
スマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システムスマートフォンを用いた新生児あやし動作の教示システム
スマートフォンを用いた新生児あやし動作の教示システム
 
TSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdfTSAL operation mechanism and circuit diagram.pdf
TSAL operation mechanism and circuit diagram.pdf
 
SOPを理解する 2024/04/19 の勉強会で発表されたものです
SOPを理解する       2024/04/19 の勉強会で発表されたものですSOPを理解する       2024/04/19 の勉強会で発表されたものです
SOPを理解する 2024/04/19 の勉強会で発表されたものです
 
論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey論文紹介:Semantic segmentation using Vision Transformers: A survey
論文紹介:Semantic segmentation using Vision Transformers: A survey
 

GitHub APIとfreshで遊ぼう

  • 1. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. GitHub APIとfreshで遊ぼう
 
 虎の穴ラボ 藤原 佳顕
 1
  • 2. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. アジェンダ
 ● 概要 ● GitHub APIについて ● 構成 ● freshについて ● 実装と作ったもの 2
  • 3. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 自己紹介
 3 ● 名前
 ○ 藤原佳顕
 ● 仕事
 ○ FantiaとかCreatiaとか社内アプリとか
 ● 好み
 ○ Clojure、Rust
 ● 趣味
 ○ 格闘ゲーム
 ■ Melty Blood、Guilty Gear
 ○ STG(ダライアスとか)も好き
 ○ 祝エルデンリング発売

  • 4. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 概要
 4 ● 諸事情でGitHub APIを叩いてコメントを抜いていくる必要がありました
 ● DenoでGitHub API叩いてコメント抜いてくることにしました
 ● せっかくなのでWebアプリにしてみました
 ○ https://github-comment.deno.dev/ ○ https://github.com/zonuko/deno-github-comment
  • 5. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. GitHub APIについて
 5 ● 何種類かある
 ○ v3: https://docs.github.com/ja/rest 
 ■ REST API
 ■ 認証方法に種類がある
 ● ユーザー個別のトークン利用
 ● OAuth
 ● 今回はどちらも試す
 ○ v4: https://docs.github.com/ja/graphql 
 ■ GraphQL API
 ■ 直感的に使うのは面倒そうだったので一旦見送り 

  • 6. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 構成
 6 ● SDKっぽい物があるが今回は使わない(octokit) 
 ○ https://github.com/octokit/octokit.js/ 
 ○ 単にREST API叩くだけなので問題はなさそう 
 ● 使うもの
 ○ Deno 1.19
 ○ dotenv
 ○ fresh(web用)

  • 7. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(freshについて)
 7 ● freshについて
 ○ Denoの中の人Luca Casonato氏が作っている 
 ○ preactベースのWebフレームワーク 
 ○ deno deployで動作する 
 ○ ビルドがない
 ○ deno.land、lint.deno.landのページなどがこちらに置き換えられている 
 ■ →サンプルが豊富 
 ○ Next.js、Nuxt.js同様にディレクトリ構造ベースのルーティング 
 ○ まだまだ発展途上感もあり 
 ○ ドキュメント化されていない情報が多々あるので間違っているかもしれません 

  • 8. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(cli+ユーザートークン) 
 
 8 import { config } from "./deps.ts"; const token = config().GITHUB_TOKEN; const user = config().GITHUB_USER; function buildConfig(): RequestInit { return { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json", }, }; } async function getRepos(): Promise<any> { const res = await fetch( `https://api.github.com/users/${user}/repos`, buildConfig(), ); return await res.json(); } async function getPulls(repo: string): Promise<any> { const res = await fetch( `https://api.github.com/repos/${user}/${repo}/pulls?state=all`, buildConfig(), ); return await res.json(); } async function getComments(repo: string, id: number): Promise<any> { const res = await fetch( `https://api.github.com/repos/${user}/${repo}/pulls/${id}/comments`, buildConfig(), ); } const repos = await getRepos() as any[]; repos.forEach(async (repo) => { console.log(await getPulls(repo.name)); });
  • 9. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(ユーザートークン+cliツール)
 9 ● 単にfetch使っているだけなのでここまで余興
 ● 実際には次からのWebアプリ化が本題

  • 10. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 10 ● 実装方針
 ○ トークンはOAuthトークンを使う
 ■ まずはGitHub Loginを作る
 ○ ストレージの類は利用しない(RDB、Redisなど) 
 ■ したがってセッション系の情報は暗号化してcookieに保存 
 ● Rails知っている人はRailsの基本動作を想像してもらえると 
 ○ 作るページはリポジトリ一覧、リポジトリのコメント一覧(、プルリク一覧) 

  • 11. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 11 ● page/index.tsx→ログインフォームがあるだけ
 /** @jsx h */ import { Layout } from "../components/Layout.tsx"; import { h, PageConfig, PageProps, useData } from "../deps.ts"; export default function Home(props: PageProps) { const errorQuery = useData("errorQuery", () => { const q = props.url.searchParams.get("error"); if (q === "missing_code") { return "Not found GitHub OAuth code"; } if (q === "invalid_state") { return "Invalid OAuth state"; } if (q === "failed_to_get_token") { return "Failed to get the access token"; } }); return ( <Layout title="Login - GitHub Comments"> <nav class="navbar navbar-light bg-light"> <div class="container-fluid"> <span class="navbar-brand mb-0 h1">Login</span> </div> </nav> {errorQuery && ( <div class="alert alert-danger" role="alert"> {errorQuery} </div> )} <div class="position-relative text-center"> <a type="button" class="btn btn-outline-dark" href="/login/github/auth" > <i class="bi bi-github"></i> {" Login with GitHub"} </a> </div> </Layout> ); } export const config: PageConfig = { runtimeJS: true };
  • 12. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 12 ● useData
 ○ サーバー側でレンダリング前にデータを取得できる 
 ○ エラーでリダイレクトされてきた場合はSSRの時点で表示したい 
 ● PageConfig
 ○ いわゆるオプションの設定
 ○ runtimeJS: クライアント側でもJS実行するかどうか 
 ○ 他routeOverrideやcspなどオプションがある 

  • 13. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 13 page/login/github/auth.ts
 import { buildGithubUrl, tokenEncrypt } from "../../../logics/github.ts"; import { HandlerContext } from "../../../server_deps.ts"; export async function handler(_ctx: HandlerContext): Promise<Response> { const oauthUrl = buildGithubUrl(); const githubRedirect = new Response(null, { status: 302, headers: { location: oauthUrl, "set-cookie": `state=${await tokenEncrypt( new URL(oauthUrl).searchParams.get("state") || "" )};HttpOnly;SameSite=Lax;Path=/;`, }, }); return githubRedirect; }
  • 14. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 14 ● handler関数
 ○ handler関数をexportすることでAPIを生やすことが出来ます 
 ○ 今回は単純で、後の検証のためにstateを暗号化してcookieに書き込み 
 ○ その後GitHubのログイン画面にリダイレクト 
 ○ サーバーサイドの処理なのでcookieやリクエストヘッダーなどある程度は触れる 
 ○ 拡張子をtsx(jsx)にすることで通常のレンダリングも同時に行うことができる 
 ■ 試した限りでは現状handlerとjsx両方がある場合はhandler→ssrといった順番で実行さ れる模様

  • 15. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 15 page/login/github/callback.ts
 export async function handler({req}: HandlerContext): Promise<Response> { const url = new URL(req.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); if (!code) { return Response.redirect(`${url.origin}/?error=missing_code`); } const cookieState = await tokenDecrypt(cookie.getCookies(req.headers)["state"]); if (cookieState !== state) { return Response.redirect(`${url.origin}/?error=invalid_state`); } const apiRes = await fetch("https://github.com/login/oauth/access_token", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ client_id: githubClientId(), client_secret: githubClientSecret(), code: code, }), }); if (apiRes.status !== 200) { return Response.redirect(`${url.origin}/?error=failed_to_get_token`); } const resJson = await apiRes.json(); if (resJson["error"]) { return Response.redirect(`${url.origin}/?error=failed_to_get_token`); } const successRes = new Response(null, { status: 302, headers: { location: `${url.origin}/mypage/github`, "set-cookie": `oauth_token=${await tokenEncrypt( resJson["access_token"], )};HttpOnly;SameSite=Lax;Path=/;`, }, }); deleteCookie(successRes.headers, "state", {path: "/"}); return successRes; }
  • 16. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 16 ● GitHubでのログイン後に帰ってくるパス 
 ○ state及びcodeがつけられて帰ってくるので各々チェック 
 ■ state: cookieの値を復号化して一致するか(CSRF対策) 
 ■ code: アクセストークンを取得するのに必要 
 ● アクセストークン取得
 ○ 本来であればRDBやサーバー側セッションに持つべきだが今回はcookieに 
 ○ 秘匿情報なのでサーバー側でしか復号化出来ないように暗号化して入れる 
 ■ 今回はAES-GCMを採用

  • 17. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 17 ● 暗号化について
 ● 詳細はMDNで https://developer.mozilla.org/ja/docs/Web/API/SubtleCrypto 
 ● まずはsecret.jsonを生成(アプリ起動前) 
 ○ jwkという形式でjsonを作れるのでそのままファイルに保存 
 ○ Git等に入れないように注意! 
 ■ いわゆる秘密鍵なのでサーバー個別に持つべき 
 async function genKey() { return await crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"], ); } async function exportKey() { const key = await crypto.subtle.exportKey( "jwk", await genKey(), ); await Deno.writeTextFile("./secret.json", JSON.stringify(key)); console.log("generate secret.json. add to .gitignore"); } await exportKey();
  • 18. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 18 ● 暗号化処理
 async function loadSecret() { const text = JSON.parse(await Deno.readTextFile("secret.json")); return await crypto.subtle.importKey( "jwk", text, { name: "AES-GCM" }, false, ["encrypt", "decrypt"], ); } export async function tokenEncrypt(val: string): Promise<string> { const ivBytes = iv(); const c = await crypto.subtle.encrypt( { name: "AES-GCM", iv: ivBytes }, await loadSecret(), new TextEncoder().encode(val), ); ● iv
 ○ 暗号化のために必要なランダムな値 
 ○ 秘密である必要はないが、一意である必要がある 
 ○ GCMでは12byteが推奨なので注意 
 ● loadSecret
 ○ 先程のsecret.jsonを読み込む 
 ○ 本来ならば起動時に一度のみ読み込みたい 
 ■ もしくはキャッシュ
 ● tokenEncrypt
 ○ 上記を合わせて引数の文字列を暗号化する 
 ○ 後にcookieに書き込みたいのでちゃんと文字として値を返 す
 return `${btoa(String.fromCharCode(...new Uint8Array(ivBytes)))}--${ btoa(String.fromCharCode(...new Uint8Array(c))) }`; }
  • 19. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 19 ● 復号処理
 export async function tokenDecrypt(val: string): Promise<string> { const [ivVal, token] = val.split("--"); const encryptedBytes = atob(token); const ivBytes = atob(ivVal); const encryptedData = Uint8Array.from( encryptedBytes.split(""), (char) => char.charCodeAt(0), ); const ivData = Uint8Array.from( ivBytes.split(""), (char) => char.charCodeAt(0), ); ● 基本的には暗号化の逆を行う
 ○ iv及びsecretを使って復号化する
 ● 最終的にはトークンがほしいのでこちらも文字列に
 ● 基本的にはサーバー側でしか実行されない
 ○ Denoに生えているAPIも使える(はず)
 const decryptedArrayBuffer = await crypto.subtle.decrypt( { name: "AES-GCM", iv: ivData }, await loadSecret(), encryptedData, ); return new TextDecoder().decode(new Uint8Array(decryptedArrayBuffer)); }
  • 20. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 20 トークンを使って情報取得(pages/api/github/repos.ts) 
 export async function handler(ctx: HandlerContext): Promise<Response> { const cookieValue = getCookies(ctx.req.headers)["oauth_token"]; const url = new URL(ctx.req.url); const page = url.searchParams.get("page") || "1"; const perPage = url.searchParams.get("per_page") || "30"; const res = await fetch( `https://api.github.com/user/repos?page=${page}&per_page=${perPage}`, { headers: { Authorization: `token ${await tokenDecrypt(cookieValue)}`, }, }, ); const resJson = await res.json(); const link = res.headers.get("link") || ""; const pagenation = buildPagenation(link); return new Response(JSON.stringify({ link: pagenation, repos: resJson, })); } ● tokenの復号化などの処理がはいるので直接GitHub API を叩くのではなく、サーバーを経由する
 ● 先程と同様にhandlerのみでルーティングする
 ● apiディレクトリに置くことでよりAPIらしく使える
 ○ fresh initしたときにも生成されるのでそちらも参 照

  • 21. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 実装と作ったもの(Webアプリ化)
 21 API使う側(mypage/github.tsx) 
 普通のReactアプリなので特筆することはなし 
 →cookie等の都合でssr時のAPIコールがうまく行かない 
 const PER_PAGE = 30; export default function Github(props: PageProps) { const [repos, setRepos] = useState({ repos: [], }); const initUrl = useData("repoInitUrl", () => props.url); const pageParams = useData( "repoInitPageNo", () => { const p = parseInt(props.url.searchParams.get("page") || "1"); if (p <= 0) return 1; return p; }, ); const [url, setUrl] = useState(initUrl); const [page, setPage] = useState(pageParams); const [link, setLink] = useState<{ link: Pagenation }>({ link: { hasNext: false, hasPrev: false }, }); useEffect(() => { const call = async () => { const res = await fetch( `/api/github/repos?page=${page}&per_page=${PER_PAGE}`, ); const json = await res.json(); if (json.repos) { setRepos({ repos: json.repos }); } if (json.link) { setLink({ link: json.link }); } }; call(); const newUrl = new URL(url); newUrl.searchParams.set("page", page.toString()); window.history.pushState(null, "", newUrl); setUrl(newUrl); }, [page]); const onPrevPage = () => { if (link.link.hasPrev && link.link.prev) { setPage(link.link.prev); } }; const onNextPage = () => { if (link.link.hasNext && link.link.next) { setPage(link.link.next); } };
  • 22. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. まとめ
 ● denoでGitHubにログイン〜APIコールを行ってみました 
 ● 付随して暗号化なども実施しました 
 ● freshを使ってWebアプリにしました 
 ● freshは全く枯れてないのでご利用にはご注意を 
 ● フィルタなどの実装は今後
 ○ handlerとjsx組み合わせればできる(気がする) 
 22