読者向けサイト
apps/web-storefront読者が訪れるサイトで、出版社自身のブランドで公開されます。作品の閲覧や検索、ランキング、フォロー、サインイン、ページをめくって読むビューアーを備えています。
Publiraは、ITリソースの限られた出版社がマンガのデジタル配信プラットフォームを自社ブランドで運営するためのマルチテナントSaaSです。編集者は作家から預かった作品を書誌情報とともに入稿し、読者はそれをWebやモバイルアプリで読みます。
開発中
Publiraは開発中で、最新のリリースであるv0.3.2もまだ本番環境で使える段階ではありません。このページで紹介している機能はすべて現時点で動作しており、リリースごとに公開されるイメージとひとつのComposeファイルで環境を立ち上げることもできます。ただし実際の出版社で運用するには、コードを読んで自分の環境に合わせ、0.xのマイナーリリースごとに入りうる変更にも追従する必要があります。完成した製品ではなく、動作を検証し、手を加え、開発に参加していただくためのプロジェクトとして公開しています。

Publiraの中心はプラットフォームです。開発の過程で必要になった部品のいくつかは独立したライブラリとして切り出し、プラットフォームがなくても誰でも使えるように公開しています。
出版社には読者向けのサイトとアプリ、編集用のコンソールが用意されます。環境全体を運用する人には、それらをまとめて管理するコンソールがあります。
出版社はそれぞれ自社のブランドとドメインでサイトを運営できます。その上位にあるプラットフォームコンソール (これを動かさない環境ではpubliractl) で、テナントとその読者、環境全体で共有する設定を管理します。
作品のカタログやページ、お知らせはひとつのコンソールで編集でき、公開待ちのものはキューで一覧できます。読者からのお問い合わせにもここから回答でき、各スタッフにはそのロールで使える操作だけが表示されます。
カラーや書体、ロゴ、アイコンは、公開サイトと同じ部品で組み立てたプレビューで実際の表示を確かめながら編集できます。保存するまで公開サイトの見た目は変わりません。
テナントはWebではStripeかPAY.JPで、アプリではApp StoreとGoogle Playでエピソードを販売します。購入が記録されるのはリダイレクトの時点ではなく、決済サービスから署名付きの通知が届いた時点で、読者には購入完了のメールが送られます。Webでの購入はスタッフがコンソールから返金でき、全額返金や異議申し立てがあると、読者はそのエピソードを再び読めなくなります。
有料エピソードは、設定した無料公開期間のあいだすべての読者が読めます。期間はエピソードごとにも、シリーズの複数のエピソードにまとめても設定できます。シリーズでは「待てば無料」も提供でき、サインインした読者はチケットを使って有料エピソードを1話ずつ無料で読めます。使ったチケットは一定時間待つと再び使えるようになります。
読者はメールアドレスのほか、サイトとアプリの両方で使えるパスキーや、テナントごとに設定したAppleとGoogleのアカウントでサインインできます。テナントのスタッフとプラットフォームのオペレーターは2段階認証を有効にでき (スタッフはパスキーでも認証できます)、プラットフォームはすべてのテナント管理者とオペレーターに2段階認証を必須にできます。
作品はWebだけ、アプリだけ、またはその両方に表示でき、エピソード単位でさらに絞り込めます。サーバーはWebサイトとアプリそれぞれからのリクエストに、そこで表示できる作品とエピソードだけを返します。販売先も同じように、テナントの既定値からエピソード単位まで設定できます。
クレジットされた著者ごとに、エピソードの売上に対する配分率を設定します。月は毎月手動で締めるか、テナントが選んだ日に自動で締めることができ、締めた月は明細としてCSVでダウンロードできます。読者アカウントと紐づけた著者は自分のエピソードを購入せずに読めますが、評価はできません。
R15とR18の作品は、どの一覧でもマークが付き、読者が確認するまで伏せて表示されます。閲覧はテナントが選んだ確認方法に従い、アカウントの生年月日で制限されます。ランキングもレーティングごとに分かれ、閲覧できる読者だけが開けます。
Next.jsで作った3つのWebアプリとFlutterアプリ、それらが送るメールです。どれも対象とする利用者が異なります。
読者が訪れるサイトで、出版社自身のブランドで公開されます。作品の閲覧や検索、ランキング、フォロー、サインイン、ページをめくって読むビューアーを備えています。
編集者が作業するコンソールです。カタログと公開キュー、決済とサインインのサービス、公開サイトのテーマとページ、読者からのメッセージ、読者の購入と返金、著者への支払額を扱い、これらへの変更はすべて記録されます。
プラットフォーム全体を運用するオペレーター向けのコンソールです。環境にまだ必要な設定、テナントの状態、テナントをまたぐイベント、全テナントの読者を管理し、オブジェクトストレージからセキュリティポリシーまで全テナント共通の設定もここで行います。
同じカタログをスマートフォンで読めるアプリです。AndroidとiOSをひとつのFlutterコードベースでまかない、テナントの名前とテーマで表示されます。サイトを経由せずに公開APIを直接呼び出し、メールアドレス、パスキー、Apple、Googleでサインインでき、エピソードはストアかサイトの決済で購入できます。読者が開いたエピソードや保存したエピソードは端末に保持されます。
アカウント関連のメールとスタッフへの通知です。専用のレンダラーがReactコンポーネントからHTMLを生成し、プレーンテキストのパートと一緒に送ります。テナントの読者やスタッフに届くメールにはPubliraではなくテナントの名前が入り、テナントの言語で書かれます。日本語、韓国語、中国語で英数字と隣り合う部分のスペースは、サイトと同じくメールクライアントの描画に任せています。
前面にNext.js、その背後にConnect RPCで通信するGoのサーバーとワーカーがあります。その下にあるのは、どれも自分で運用できる一般的なソフトウェアです。
publira serverのプロセスひとつが公開API、管理API、プラットフォームAPIの名前空間を受け持ち、読者向けとコンソール向けの両方のオリジンに画像を配信します。--role storefrontで起動したもうひとつのプロセスにテナントのドメインだけを担当させ、読者向けのデータベースログインだけを持たせることもできます。同じバイナリのpublira workerはメールとプッシュ通知を配信し、公開予定のエピソードの公開、無料期間の開始と終了の反映、テナントごとの日付の切り替え、ランキングの再集計、印税の月締め、保持期間を過ぎたデータの削除といった定期ジョブをすべて実行します。デプロイに必要なスケジューリングはこれを動かすだけで済み、1台のホストならpublira server --with-workerとして両方をひとつのプロセスで動かすこともできます。任意のemail-rendererはメールのHTMLパートを生成し、publiractlはデータベースのマイグレーションとロールの作成、環境の初期設定を行うほか、ワーカーのジョブを手動で実行することもできます。
すべての層がOpenTelemetryで計装されており、ブラウザからのひとつのリクエストをひとつのトレースとして追えます。Next.jsのルートスパンから、SSR中のConnect RPC、Go側の同名のスパン、その下のdb.queryまでがつながります。すでに運用しているコレクターに送るだけで使え、使わなければ無効のままにできます (既定では無効です)。Goの2つのプロセスはOpenTelemetryのメトリクスも出力でき、こちらも同じように有効にするまではオフです。
| 依存先 | 用途 |
|---|---|
| リバースプロキシ必須 | テナントのホストをそのサイトとコンソールに、公開APIと画像をサーバーに振り分けます。Traefik、nginx、Caddyの設定がリポジトリに同梱されています。 |
| PostgreSQL必須 | カタログ、テナント、ユーザー、購入、監査ログを保持します。各サーバーはそれぞれ専用のロールで接続します。 |
| Redis互換のキャッシュ必須 | Next.jsの共有キャッシュです。ロードバランサーの背後で複数のインスタンスを動かせるようにします。 |
| S3互換のオブジェクトストレージ必須 | エピソードのページやアイキャッチなどのアップロード先です。バケットは各プロセスの環境変数ではなく、環境の設定として保存します。 |
| SMTPリレー必須 | アカウントのメール、通知、スタッフへのお知らせを送ります。プラットフォームとテナントのそれぞれで設定します。 |
| OpenSearchまたはElasticsearch任意 | 関連度順のカタログ検索がほしい環境で使います。すべてのノードにanalysis-kuromojiとanalysis-icuのプラグインが必要です。なければ検索はPostgreSQLで動きます。 |
| 決済サービス任意 | テナントごとにひとつのアカウントで、有料エピソードにだけ必要です。WebではStripeかPAY.JP、アプリではApp StoreとGoogle Playを使います。購入を確定させるのは決済サービスからの署名付きの通知です。 |
| メール受信サービス任意 | 回答への読者の返信を、コンソールのお問い合わせメッセージの下に記録したいテナントだけが使います。SendGrid、Resend、Mailgun、Amazon SES、または受信したメールをHTTPSで転送できるメールサーバーをテナントごとに設定します。 |
| Firebaseプロジェクト任意 | テナントのアプリごとにひとつ必要です。モバイルアプリへのプッシュ通知をFirebase Cloud Messaging経由で送り、iOS向けにはAPNsへ中継されます。 |
| OpenTelemetryのコレクター任意 | Next.jsアプリとGoサーバーのトレースと、Goサーバーのメトリクスを受け取ります。有効にしない限りオフです。 |
環境はコンテナイメージとひとつのComposeファイルで動き、publiractlでデータベースと最初のテナントを使える状態にします。ホストに必要なのはDockerだけです。
PUBLIRA_VERSION=0.3.2
curl -fsSLO \
https://github.com/publira/publira/releases/download/v$PUBLIRA_VERSION/publira-deploy-$PUBLIRA_VERSION.tar.gz
tar -xzf publira-deploy-$PUBLIRA_VERSION.tar.gz
cd publira-deploy-$PUBLIRA_VERSION
# Write .env from .env.example, with every secret generated
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD":/work -w /work \
ghcr.io/publira/publiractl:$PUBLIRA_VERSION env generateアーカイブには、そのリリースのComposeファイル、プロキシのCaddyfile、.env.exampleが入っています。publiractl env generateはこの.env.exampleから.envを書き出し、シークレットをすべて生成して埋めます。
リリースごとに、ghcr.io/publira/publiractl:0.3.2のようにそのバージョンでイメージがGitHub Container Registryに公開されます。linux/amd64とlinux/arm64に対応し、サインインせずに取得できるので、ホストでは何もビルドしません。
.envのPUBLIRA_IMAGE_TAGにはすでにそのリリースのバージョンが入っているので、書き換えないでください。イメージは.envと同じディレクトリにあるファイルと同じリリースでなければなりません。publiractlはそのリリースのマイグレーションを持ち、各プロセスはそのリリースのスキーマを前提にし、プロキシはアーカイブのCaddyfileのルーティングで動くためです。
コードを変更する環境やほかのプラットフォームで動かす環境は、リポジトリのチェックアウトから自分でイメージをビルドしてそれを指定します。