【新歓ではない】同人即売会用レジアプリを開発している話2
突然ですが、同人即売会用のレジアプリを開発しています。2
こんにちは,きら同技術部員4こと🌱🌿☘️🍀です(@cm_ayf, @cm-ayf).名前の読み方について公式見解を示す予定はありません.
まず初めに,先代がありました.
先代のアプリは素晴らしいもので,きらら同好会の即売会では,これによって在庫管理が容易になり,計算ミスは撲滅され,世界には平和が訪れました.
即売会中のUXは大幅に向上しましたが,一方で,主に準備段階において不便な点もいくつかありました:
- 特定のデプロイ先がない
- 先代はWebSocketを利用していたため,Vercelが使えませんでした
- Herokuは無料では使えなくなりました
- その都度EC2インスタンスを立てるなりして対処していました
- イベントと商品の情報を登録する手順が難しかった
- 認証機能が無かった
- なぜでしょうね……
- デプロイして常時利用可能にするためには必須です
- ついでに会員の中でも使える・使えないを制御できた方が嬉しいですね
これを解決すべく(というのは建前で単純にプログラミングの題材に飢えていたので),同人即売会用のレジアプリを開発しています.
要件
先代から引き継ぐ要件も含めて列挙します:
- タブレット・スマートフォンで簡単に利用可能にする
- サーバーを確保して常時利用可能にする
- イベントと商品の情報を簡単に登録できるようにする
- 認証を追加する
- レジ画面について
- 集計画面について
- 先代と同様の情報を表示できるようにする
- CSVをエクスポートできるようにする
新機能
イベントと商品の情報登録
イベントと商品の情報を登録する画面を作りました.ここでは例としてイベント編集画面を紹介します.
左上の白いカードをクリックすると,基本情報を更新できます(作成UIは当然別にあります).実装はMUIのDialogを使うだけです.簡単ですね.
イベントは複数の商品を持つようになっています(商品は複数のイベントに登録できます;n:mです).データベース的には,explicitなmany-to-many relationのテーブルDisplay
(「お品書き」)があります.「お品書き」の右のボタンから編集できるようになっています.
計算機については,Display
に単価を持たせることも考えましたが,結局JavaScriptを書いてもらうことにしました.セット価格などを表現するのに十分な構造を定義するコストが高いと考えたためです.
代わりと言ってはなんですが,TypeScript Playgroundのリンクを置いておくことで,計算機を実装しやすくしています.
認証
環境変数からDiscordのサーバーID(とoptionalでロールID)を登録することで,そのサーバーの(そのロールを持つ)ユーザーだけが利用できるようになります.
技術選定
前提として,先代のアーキテクチャを踏襲しています.先代の記事の「アーキテクチャの検討」の項もご参照ください.
Webアプリ
私はWebアプリしか作れません. PWAとして使えるように設定することでオフライン動作を可能にします.
TypeScript + React + Next.js
オタクはTypeScriptが好き.
next-pwa
PWA対応のため,next-pwa
を利用しています.
レジ画面に必要なpropsを得るAPIルートとフロントエンドをキャッシュさせ,レジ画面がmutationなしで動作するようにすることで,完全オフラインでの動作を実現しています.
MUI
React用のUIライブラリです.私はCSSが書けず,MUIコンポーネントのsx
propsしか使えないため,head内を除くすべてのJSX要素はMUIで書かれています.
SWR
React用のデータフェッチ・キャッシュライブラリです.最近v2が出てmutationも扱えるようになり,またisLoading
などの便利な機能も追加されました.
TypeBox
JSON Schemaビルダーです.中身のJSON Schemaでvalidationを提供しつつ,型定義と型レベルでの検査も提供します.API定義などに利用しています.
Prisma
オタクはTypeScriptが好き.
DBとしてPlanetScaleを使うにあたり,外部キー制約の確認をPrisma側で行ってくれる設定があるのは思わぬ幸運でした.
IndexedDB
先代と同様にidb
越しに利用しています.オタクはTypeScriptが好き.
Next.js API Route
Vercelへのデプロイを念頭に置いて開発したため,Next.jsのAPIルートを利用しています.他のフレームワークを利用する余地はありませんし,必要もそこまでありません.
機能面において,WebSocketがないことによってサーバーとの接続が利用可能かが直ちにはわからなくなってしまいます. 今回は,SWRの定期的にrevalidateする機能を利用することで(つまりポーリングを行うことで)代用しています.
Discord API - OAuth2
Discordでの認証などを提供するAPIです.Discordのユーザーに対応する情報だけでなく,サーバーID(Guild ID)を渡すと対応するMemberの情報も得ることができます;すなわち,そのサーバーにいるかどうかだったり,そのサーバーでどんなロールがついているかなどの情報も得ることができます.
また,Node.js向けのラッパーdiscord-oauth2
も利用しています.
JWT
署名によってstatelessに利用可能な「鍵」です.さすがにAPIルートを叩くごとにDiscord APIを叩いてアクセストークンを検証するのは忍びないので,補助的に利用しています.
ライブラリにはjsonwebtoken
を利用しています.また,アルゴリズムは共通鍵系のHS256を利用しています(まあテキトーでいいかなって).
API定義
実装の話に移ります.この手のものの実装はプログラムを快適に書けるようにするために形式的な部分と型パズルから始めるのがモチベーション上優れています.
バックエンドにTypeScriptを利用する意義の一つに,API定義が共通化できることがあります(それ以外はほとんどありません).そこで,TypeBoxを利用してそれぞれのルートを定義しています(GitHub):
export const readEvent = { method: "GET", path: "/api/events/[eventcode]", params: Type.Object({ eventcode: Code }), response: Event, // 一つのイベント情報に対応するJSONのスキーマ } satisfies Route;
これを利用して,APIルートの実装を行います(GitHub):
const readEventHandler = createHandler(readEvent, async (req, res) => { const token = verify(req); if (!token) { res.status(401).end(); return; } const event = await prisma.event.findUniqueOrThrow({ where: { code: req.query.eventcode }, include: eventInclude, }); res.status(200).json(toEvent(event)); });
そして,適切な型パズルを行ってクライアント側のSWRフックを定義します(GitHub):
export const useEvent = createUseRoute(readEvent);
すると, DBのデータが非常に簡単に利用できるようになります.eventcode
を渡す(当然型付き)とevent
には適切な型がついたデータが入り,isLoading
でロード中かどうかがわかります.また,fetch時に実行時型チェックも行っています.
const { data: event, isLoading } = useEvent({ eventcode });
PWA
先代では使われていませんでしたが,next-pwa
を利用すると,Service Worker関係はnext.config.js
をちょっといじるだけで実装できます(GitHub):
module.exports = nextPWA({ dest: "public", disable: process.env.NODE_ENV !== "production", cacheOnFrontEndNav: true, runtimeCaching: [ { urlPattern: "/api/users/me", method: "GET", handler: "NetworkOnly", }, ...nextPWACache, ], })({ reactStrictMode: true, });
フロントエンドについては,next-pwa
のデフォルト設定だと上で述べたポーリングによる通信確認が不可能になるため,ユーザー情報を表示するエンドポイントだけNetworkOnly
にしています.UIでも対応して,通信が確認できない場合はユーザー情報の部分にオフラインと表示されるようになっています.
認証
Discord APIでOAuth2フローを回すと,ユーザーの同意のもとでユーザーが入っているサーバー(Guild)とそのサーバーにおけるユーザーの情報(Member)を得ることができるアクセストークンを発行することができます.まずは,それとリフレッシュトークンをクッキーに焼きます.
しかしながら,このアクセストークンはただの一意識別子であり,JWT(JWS)のようにサーバーに問い合わせずにauthenticationに使えるものではありません.そこで,これとは独立に独自のJWTを発行して,それもクッキーに焼きます.
リフレッシュエンドポイントでは,まずDiscord APIのアクセストークンを使ってみて,使えればそれで更新し,使えなければリフレッシュトークンを利用してアクセストークンを更新したのち改めてアクセストークンで更新します.
デプロイ
Vercelを利用しました.
VercelはGitHubと接続すればよしなに自動デプロイを組んでくれます.Next.jsをそのまま使って作ったアプリですからデフォルト設定で動くため,本当にzero-configです.
Vercelはデータベースを提供してくれないため,Planetscaleを利用しています.PlanetscaleはサーバーレスなリレーショナルDBaaSで,容易にスケールすることができる代わりに外部キー制約を検証してくれません.Prismaは外部キー制約を自前で検証するように設定できるため,これを利用しています.
詳しくはREADMEをご覧ください.
ギャラリー




あとがき
そんなわけで,すでにあるものの個人的に不満な点を改善するためだけにフルスクラッチですべて書き換えた話でした.こちらのアプリもGitHubでソースコードを公開しておりますので,よろしければPull Requestなどお寄せください.
東京大学きらら同好会は5/13-14(土-日)の第96回五月祭で開催されるコミックアカデミー22にて「結ぶ、束ねる。」等の既刊を揃えてお待ちしております.私は当日スペースにいる可能性は低いですが,どうぞよろしくお願いいたします.
【新歓】東大きらら同好会公式ブログの個人的おすすめ記事7選
東大きらら同好会のK. 汝水(@tactfully28)です。この東大きらら同好会公式ブログでは、「アインシュタイン方程式が導く「まちカドまぞく」の宇宙観」「「妄想アカデミズム」紹介」「K. 汝水の没ネタ集」といった記事を書いてきました。
今回は、このブログに投稿されている記事の中で個人的なおすすめを7つ紹介します。
続きを読む【新歓】「妄想アカデミズム」にかこつけてε-δ論法の話をする
東大きらら同好会のK. 汝水(@tactfully28)です。
きらら本誌で連載中の「妄想アカデミズム」、皆さん読んでいらっしゃいますでしょうか。檜山ユキ先生の妄想アカデミズムは、高校2年生の主人公・湯島未春が片想い相手の室町莉子とともに東大合格を目指すというお話です。ニコニコ静画のきららベースなら無料で読めます。
以前からディープな受験ネタが際立つ作品でしたが、4月7日発売のきらら5月号に掲載された9話ではついにε-δ論法*1が登場し、話題を呼びました。というのも、ε-δ論法は「ディープな受験ネタ」では済まされない、学習指導要領の範囲を明らかに超える内容だったからです。ちょっと言及されるだけかと思いきや、ガッツリとページを割いて扱われた9話の本筋の内容だったため、私は度肝を抜かれました*2。
【きらら5月号】檜山ユキ先生「妄想アカデミズム」!
— まんがタイムきらら編集部 (@mangatimekirara) 2023年4月6日
全国模試の結果を振り返り、数学の強化を思い立つ未春。
途中で莉子のスイッチが入っちゃって…!?
≪きららベースで振り返り連載中!≫https://t.co/BP6LVtrWB2 #kirara pic.twitter.com/AtuIFHZK0J
というわけで、今回は妄想アカデミズムにかこつけてε-δ論法の話をしていきます。
*1:εはイプシロン、δはデルタと読みます。横棒はマイナスではなくハイフンで、読みません。
*2:こんなことをすると説明的な学習漫画になりかねないところですが、きちんと読んでいて楽しいきららに仕上がっているのがすごいところです。檜山ユキ先生のバランス感覚とネタのキレには毎話驚かされます。
ぼざろ合同誌「結ぶ、束ねる。」に寄稿しました
東大きらら同好会のK. 汝水(@tactfully28)です。
きらら同好会から、新刊「結ぶ、束ねる。」がリリースされます。「結ぶ、束ねる。」は「ぼっち・ざ・ろっく!」の合同誌で、3月18日(土)開催の即売会「ぼっち・ざ・おんりー!」で頒布される予定です。また、メロンブックス様での予約も開始しています。
私は3pの漫画「シーシュポスにも朝が降る」を寄稿しました。
「シーシュポスにも朝は降る」K. 汝水(@tactfully28)
— 東大きらら同好会@ぼざおんりーNo.37 (@UTKiraraCircle) 2023年3月11日
4コマ漫画5本の詰め合わせです。ぼっちちゃんがくしゃみをしたり加速器を建造したりします。用語解説付きでためになります。 #むすたば pic.twitter.com/ABrwqXOEXU
「シーシュポスにも朝が降る」は5本の4コマ漫画の詰め合わせです。全体を通したストーリーはなく、各4コマは独立しています。そのためタイトル決めは難航したのですが、入稿直前になってようやく決定しました。
「シーシュポスにも朝が降る」の中には、シーシュポスという人間は登場しません。では、このタイトルはどこから来たのでしょうか。実はこれは、私が作中で不条理な展開を描いたことに由来しているのです。今回の記事ではその話をしつつ、最後に本の宣伝をしようと思います。
ぼっちちゃんよくばりセットの利用を楽にするWebアプリ
- 元ネタ
- 画像の表示とツイート
- 画像URLの収集
- ご意見・ご要望
- さいごに
書いた人:kn1cht
『ぼっち・ざ・ろっく!(ぼざろ)』のアニメは、放送が終了してからも圧倒的な人気を誇っていますね。
どのくらい人気かというと、一発ネタの立て看板を作ったら万バズしてしまうほどです。
さて、ぼざろの原作コミックを出版している芳文社のアカウント「まんがタイムきらら編集部」では、放送中に多数のコマ画像が「ぼっちちゃんよくばりセット」と称して配布されていました。
バイトを辞める決心をした時にお使いください #ぼっち・ざ・ろっく#ぼっちちゃんよくばりセット
— まんがタイムきらら編集部 (@mangatimekirara) 2022年12月24日
【原作コミックス1~5巻好評発売中!】https://t.co/jEUHONPPY8 pic.twitter.com/iJs6LNgP3G
ぼざろのコマ画像は、アニメ化前からSNS等でネタ画像として目にする機会が多かった(「いいいいイキってすみません…」「憂鬱な月曜日が始まった」など)ので、その文化を拡大する形で作品の盛り上がりをより高める取り組みだったと思います。
配布画像は、放送中に投稿されたものだけでも69枚にのぼります。いざ投稿したいときにいちいち探すのもけっこう大変です。
pic.twitter.com/CApFH9p17G https://t.co/bhNcy6UbTg #ぼっち・ざ・ろっく!
— けいえぬ (@kn1cht) 2022年12月30日
そこで、bozaro.vercel.app(btr)というWebアプリを作って公開しました。
- 画面上の"btr"という文字を押すたびにランダムに画像が変わる
- 左のメニューから目的の画像を探せる
- tweetボタンから画像をツイートできる
といった機能があります。
技術的な詳細は、ぼざろオンリーに出す合同誌のネタにしたいので省略するとして、本記事ではこのアプリの特徴などを軽くご紹介します。
続きを読むこんばんは!きらら同好会です。
— 東大きらら同好会 (@UTKiraraCircle) 2022年12月30日
当会は3/18に綿商会館で開催の「ぼっち・ざ・おんりー!」( @BocchiTheOnly )に申し込みました。
受かればぼっち・ざ・ろっく!の本が出るはずですのでよろしくお願いいたします……!
K. 汝水の没ネタ集
きらら同好会のK. 汝水(@tactfully28)です。12月26日になりました。私はきらら同好会Advent Calendar 2022にいくつか記事を投稿していますが、カレンダーを埋めるべく、他にも色々なネタを検討してきました。今回は記事にならなかった没ネタの話をしようと思います。
このように、何かを没にするのは往々にして苦しみを伴う。
はんざわかおり(2015)「こみっくがーるず 1巻」p45より。
こみっくがーるずがクライマックスですね。みんなで読みましょう。
(1) 東大生進振り失敗猪瀬舞概念
「#FindOurStars Vol. 1」に、ふぁぼんさんの「東大生猪瀬舞概念」という名作記事があります。
これを受けて、「東大入学後成績不振に陥り、特に興味のない底割れ学科に進学した猪瀬舞」について書こうと思いました。
ですが、2015年に学部に入学した私の頃とは進学選択のアルゴリズムも変わり、進学選択の様相は大きく変化しています。これについてわざわざ調べて想像を膨らませていくのが面倒だったので、没にしました。私に代わって書きたい人がいたら誰か書いてみてください。
(2) 介護職鳩谷こはね
「アニマエール!」の鳩谷こはねは、困っている人を放っておくことができない性格の持ち主で、よく自分も顧みず人助けをしています。従って、こはねは大人になったら介護士になるのではないかと考えました。しかし、現実の介護職は薄給激務、老化で怒りやすくなった老人から理不尽な仕打ちを受けることもあるブラック労働です。そこで、ブラック労働で疲弊し、取り柄の笑顔の作り方も、なんで自分が人助けが好きだったのかも忘れてしまったこはねの姿について書こう思いました。
記事として成立するよう文章を膨らますことがうまくいかず、早々に「これ以上書くことがない」状態に陥ってしまったため、没にしました。これを記事にしようとしてみて、1つのアイデアから出発して考察を尽くし、ヒット記事に仕上げた「東大生猪瀬舞概念」は偉大だったのだなあということだけが分かりました。私に代わって書きたい人がいたら誰か書いてみてください。
(3) 「しずねちゃんは今日も眠れない」からの睡眠障害入門
Micare vol.1の名作記事として、kn1chtさんの「「星屑テレパス」からのモデルロケット入門」があります。kn1chtさんは、あとがきで「自分の趣味がきらら化(Kirarize)されるとめちゃくちゃ楽しい」と書かれていました。この一文を読んで、自分に関係するものできらら化されて嬉しかったものが何かあったかどうか、脳内で検索をかけてみました。そこでヒットしたのが「しずねちゃんは今日も眠れない」です。
別に趣味ではないのですが、私も睡眠障害を少々嗜んでおります。そこで、睡眠薬の分類を紹介したり、各種睡眠薬の感想を書いたり、睡眠薬で生じた幻覚をレポートしたりする記事を書こうと考えました。ですが、「しずねちゃんは今日も眠れない」には別に睡眠薬が出てくるわけではなく、かこつける先のきらら作品との関係性が薄すぎるという問題点があります。従って記事に需要があるとも思えず、没にしました。
ちなみに、睡眠薬は効き方がシャープなものほど副作用や依存性が強い傾向にあるようです。きらら漫画にも何かを盛られて寝込んでしまうというような描写が出てくることがありますが、割と心配になってしまいます。
(4) きららの中の自殺
Micare vol.2ではkn1chtさんの「きららの中の東大」という記事が掲載されています。そこで私も「きららの中の自殺」という記事を書こうと考えました。実は、自殺が関わるきらら作品はいくつかあるのです。
「この作品には自殺の要素が出てくる」ということ自体が作品の根幹に関わる重大なネタバレになってしまうため、没にしました。この記事は誰も書かなくていいと思います。
(5) K. 汝水の没ネタ集
今、あなたが読んでいるのは「K. 汝水の没ネタ集」という記事です。あなたがこの記事を読んでいる頃、既にアドベントカレンダーの時期は終わっていることでしょう。なぜならば、私はこの記事をアドカレの記事としては没にしたからです。
なぜこの記事を没にしたのか。それはこの記事を没にするためです。この記事には元々オチがありませんでしたが、この記事が没になったがゆえに、ここに「(5) K. 汝水の没ネタ集」という項目を立てることができました。あなたが今この文章を読んでいるのは、この記事が没になったからです。そしてこれがこの記事のオチです。この記事は没になりましたが、没になったが故にこの記事はオチを獲得し、こうして日の目を見ることができたということなのです。
終わりに
まだあるのですが、これくらいにしておきましょう。記事になったものは考えたアイデアのうち氷山の一角で、その背後には数倍の没ネタがあるということでした。
それでは皆様、良いお年を。
立て後藤を立てました
この記事は東京大学きらら同好会 Advent Calendar 2022の25日目の記事です。
昨日の記事は「ネタが思いつきませんでした【アドベントカレンダー2022 24日目】」でした。
計算ミスしている人がいるようにも見えますが、おそらく偶然65535や2147483647などの大きい数を思い浮かべたためバグってしまった人だと思います。
こんにちは、500mLです。「ぼっち・ざ・ろっく!」最終回、良かったですね〜(涙)。私もクソ寒い自室で凍えながら観ておりましたが、マジの大声が出てしまいました。寮の壁が厚くてよかった。
長くなるしネタバレになりそうなのでさっさと本題に行きましょう。はい。
#ぼっち・ざ・ろっく! pic.twitter.com/8RbvoDr4SD
— 東大きらら同好会 (@UTKiraraCircle) 2022年12月24日
ということで東京大学駒場キャンパス(下北沢から井の頭線で2駅のキャンパス)に立て看板になったぼっちちゃんを作りました。
下北から近いので聖地巡礼のついでに寄ってみていただいてもいいですし、そのまま入学していただいても構いません。
この記事では制作の裏話っぽいものをしていきます。
今回の立て看板制作にかかった期間は4日でした。結構速いですね。
すこし前から過去の立て看が割れてしまったので新しいものにしようという議論がDiscord上で行われていましたが、デザインをどうするか決まらないまま停滞していました。
そこに、この投稿が貼られて後藤ひとりさんの体は人間ではないため立て看板になってもよいのではないか?ということで作ることに決まりました。
こういう企画は勢いのままに完成させるに限るということで会議を行い、日程や髪飾り部分のディテールをどうするかなど検討を行いました。
24日に最終話が放送されるのでそれに間に合わせる形で制作することになりました。上の図面は提案の次の日に出てきたものです。速いねえ。
前日にはペンキや追加部分用木材の買い出しを行いました。
クリスマスイブの24日昼に偶然予定がなかった複数人で作業をしていきます。夜は当然みんな予定あります。ぼざろ最終回があるので。
続きを読む