SNSなECサービスをつくった

# 作品販売プラットフォーム『Miseba (ミセバ)』
現在以下のリンクで公開中です！是非遊んでみてください！

[『Miseba (ミセバ)』 アクセスはこちらから！](https://miseba.syntck.com "Miseba (ミセバ)")

https://miseba.syntck.com

:::note warn
本サービスは学校の進級作品展の一環として制作・公開されたものです。実際の金銭の取引は行われず，コンセプトの検証や技術的な実装のデモンストレーションを目的としています。掲載されている商品情報や取引機能はすべて仮のものであり，いかなる請求も発生しません。
:::

## 目次
- [はじめに](#はじめに)
- [なにができるのか](#なにができるのか)
- [なんで作ったのか](#なんで作ったのか)
- [どうやって作ったのか](#どうやって作ったのか)
- [つまづいたポイント](#つまづいたポイント)
- [やらかしたこと](#やらかしたこと)
- [感想・思ったこと](#感想思ったこと)


## はじめに
みなさんはじめまして。τomoと申します。
IT系の専門学校に通う2年生の20歳です。

学校の進級制作作品として展示する用に "SNS×ECな作品公開/販売プラットフォーム" を制作・公開しました。
今年の進級作品制作はチーム制作です。
実際にどういうものをどうやって作ったのかという記録として記事に残そうと思います。

### サービス公開期間について
2025/02/28
最低でも3月末までは公開しますが，それ以降は未定です(学校のAzureアカウントを利用しているため)。

:::note alert
この記事は私が所属している学校とは関係ない非公式なものです。
学校に対する，本記事及び作品に関するお問い合わせ等はご遠慮ください。
:::



## なにができるのか
### 概要
デジタルデータを販売/公開ができるプラットフォームです。通常のSNSとして使用できる他，自分の制作物を販売することができます。

SNSに必要なリポスト(フォロワーのタイムラインに任意のツイートを流し込む)機能やリアクション(いいね)，引用リポストやコメント機能を備えており，SNSの拡散力を利用した商品販売が可能です。

![スクリーンショット 2025-02-24 0.57.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/04bdd35e-f1b6-48d9-82b4-937cac05d92c.png)

### タイムライン
ユーザーの投稿が表示される場所です。
初期設定では
- 最新の投稿
- フォロー中
- 商品投稿

の3つのタブがあり，
「最新の投稿」は全ユーザーの投稿，
「フォロー中」は自分を含むフォロー中のユーザーのみ，
「商品投稿」は後述の"商品投稿"のみ
にフィルタリングされています。

![スクリーンショット 2025-02-26 15.40.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/3d49aaa4-6c2f-4814-bdbe-5376da671e28.png)

このタブは，(タグ検索画面: タグをクリック から) 任意のタグを登録することができ，自分の興味のある投稿のみのタイムラインにカスタマイズすることができます。

![スクリーンショット 2025-02-26 15.45.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/7b4f8ab5-6b79-4471-9af8-51ab3ed8578d.png)

![スクリーンショット 2025-02-26 15.46.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/2b6aba80-9757-4abd-a2d9-64ded22c45fc.png)



### 通常投稿
通常のSNSと同様に短いテキストや画像を投稿することができます。

![スクリーンショット 2025-02-26 15.34.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/7660f6c5-1dbc-451d-9e04-a77fb48ef500.png)


### 商品投稿
商品には
- 商品名
- サムネイル/商品画像
- 商品データ
- 値段
- 商品説明
- タグ

を設定することができます。

商品データはzipまたは画像の単一ファイルが許可されています(どんなデータでもzip形式であればアップロードできるということです)。

![スクリーンショット 2025-02-26 15.28.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/4ece481e-aa83-49d6-bc5b-4604bce6455a.png)

![スクリーンショット 2025-02-26 15.28.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/8cfaafa1-605c-4bbf-a589-daa5a276840e.png)

(出品前にイメージをプレビューできます)
![スクリーンショット 2025-02-26 15.28.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/6d1721b9-cbc5-4765-b5f6-3dce33f5c50a.png)

### ライブ出品投稿
商品制作をライブ配信で行う際に，商品データや商品画像を無しで予約出品できる機能です。
YouTube LiveまたはTwitchを利用できます。
ライブ出品を利用すると，タイムライン上にライブのプレイヤーが表示されます。

![スクリーンショット 2025-02-26 16.08.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/b65ec8a8-b944-4333-81e5-9a3d0cbccf67.png)


![スクリーンショット 2025-02-26 16.09.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/2f8ed72c-b4a4-477a-b0b5-d5654a44de5e.png)


### 引用投稿
任意のユーザーのポストにコメントをつけてリポストすることができます。

![スクリーンショット 2025-02-26 15.39.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/c7284d86-ee9b-47df-b5e6-4335675f2497.png)

### 通知
ユーザーのリアクションなどがあると通知が表示されます。

![スクリーンショット 2025-02-26 15.51.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/109ed7fd-ad9a-44fc-a084-c8e58e353ea0.png)

### カート
商品のページから商品をカートに追加することができます。

![スクリーンショット 2025-02-26 15.53.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/22cc632f-b333-47eb-b2ee-34b344d413b1.png)

![スクリーンショット 2025-02-26 15.54.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/11e3f6ca-6fc9-4ac6-b89a-c3fd70007af2.png)

### 購入
カートに追加した商品は購入することができます。購入処理は行いますが，(都合上)金銭のやり取りは発生しません。
購入処理完了後は商品の評価が可能です(評価は後からでも可能です)。

![スクリーンショット 2025-02-26 15.55.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/b7c76fc6-1b7a-4704-b777-acf37f3da516.png)

![スクリーンショット 2025-02-26 15.55.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/f73f99ea-bd64-48a4-86ed-a2e258640803.png)

購入履歴として購入した商品は記録されます。
このページから商品の再評価や，商品データのダウンロードができます。

![スクリーンショット 2025-02-26 15.58.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/820492d0-e5d8-4c2f-a2cc-5251e29533b4.png)

### 検索
簡易的な検索を実装しています。
タグ("#"から始まる青文字)をクリックするとタグ検索になります。

![スクリーンショット 2025-02-26 16.04.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570882/e6aef3dd-491f-4d9f-9083-69cfd78303dd.png)


## なんで作ったのか
 ~~学校の進級作品展のテーマが『EC』だったというだけ...~~

近年，SNSを活用した個人クリエイターの作品販売が活発化していますが，原稿のプラットフォームにはいくつかの課題があります。例えば，TwitterやInstagramなどのSNSでは作品を多くの人に見てもらえますが，直接販売する仕組みがなく，外部サイトへ誘導しなければなりません（3Dモデルの販売を行っている友人も販売はBOOTHといったように，複数のプラットフォームにまたがって宣伝・販売を行う必要がありました）。
一方でECサイトでは商品の販売は容易ですが，作品単体やクリエイターの発信力が弱く，コミュニティが築かれないという問題があります。
さらに，リアルタイムでの制作過程の共有や予約販売がしづらい点も，クリエイターの活動の幅を狭めています。
そこで，前述の課題・問題を解決するためにSNSとECを融合させたサービスである『Miseba (ミセバ)』を開発しました。
このサービスでは，投稿と同時に作品を販売でき，ライブ配信による予約販売も可能です。クリエイターがより自由に活動し，ファンと直接繋がり，コミュニティを形成することによって，新しい価値の創造を支援することを目的としています。

## どうやって作ったのか
チームで開発をしています。また，テーマを告げられたのが発表の半年前であったため，他メンバーがある程度キャッチアップできる範囲で技術を選定する必要がありました。
そのため，DBには自分が触ったことのあるMariaDBを採用し，TypeScriptで開発を進めました。
フロントエンド部分は学校での授業内容を活かせる部分もあったため，メンバーみんなが授業で書いたことのあるJavaScript/Reactという構成になっています。

基本的には自分が大部分を制作し，渡せそうな仕事があれば都度分配する形を取っています(期日的にそうせざるを得なかった)。

システム構成的には，Linux上でDBサーバ/APIサーバ/WEBサーバを動かしています。

簡易的ですが，技術スタックは以下の通りです。

### API
- (TypeScript)
- Hono
- Zod
- Prisma ORM

### フロントエンド
- (JavaScript)
- Next.js
- Material UI

## つまづいたポイント
### Webアプリ(App Services)の作成 (API)
CI/CDにGitHub Actionsを指定して作成。
DB/Vnetも同時に作成した。
環境変数がセットされていないと例外を吐いて死ぬようにしたため，Application Errorが出ていた(ログストリームで確認)。

### Webアプリ(App Services)の作成 (フロントエンド)
基本はAPIと同じ，DBは作成せず，ネットワークはAPI作成時に作成したものと統合した。
Next.jsは`mode: standalone`に設定し，YAMLを編集，デプロイ用のzipを作成してデプロイ。
スタートアップコマンドは`node server.js` (workflowでstandaloneの.next/をルートディレクトリにコピーしておいた)

ログストリームに
```
##[debug]Deployment status: 1 'Building and Deploying '********-****-****-****-************'.'. retry after 5 seconds
```
という表示があり，うまくデプロイできていない様な感じがあるが，10回くらい失敗→リトライを自動で繰り返し勝手に成功していた。直後はApplication Errorのページが出ていたが複数回リロードしたら反映され，正常に起動した。

### API側 CORS
Hono の `cors()` の `origin` に ↑のURLを追加した(末尾の/が入りっぱなしでやり直した)。

### 環境変数
APIはDBとprivate link経由で接続
GitHub Actionsのworkflowからのマイグレーションがうまくいかない。しょうがないので一時的にパブリックにしてファイアウォールのIP指定を使った。

### Flexible MySQL サーバ
初期パスワードを忘れてしまったのでパスワードをリセットした。

### Storage Account
オブジェクトストレージ(Azure Blob Storage)を利用するために必要らしい。これを作成した後にBlob Storageのコンテナを作成

### Blob Storage
ストレージアカウントを作成し，その中で作成できる。
本番用のストレージではAzureの仮想ネットワーク内でのみアクセスできる様に限定するため，アクセスキーでのアクセスを選択。
同名のファイルが存在すると上書きされる設定？のため，アップロードされるファイルにはUUIDやタイムスタンプをつけて対処。

アカウント名，アクセスキー，コンテナ名が分かればアップロードが可能，＋ファイル名が分かればダウンロードも可能。
本番用の設定はテストが不便なため，同じ構造のパブリックアクセスが可能なバージョンのテスト用ストレージを作成した。

### カスタムドメイン
個人で所有しているドメインを使用。
初期ドメイン(azuresites.net)に追加の形で外部ドメインを追加。
自前のドメインサービスのDNS設定にCNAMEレコードとTXTレコードを追加。
APIリクエストをそのドメインを通して行うことでCookieが1st partyになるため，`sameSite=Strict`を指定しより安全になった。
また，3rd partyなCookieはiOSのsafariやchromeではデフォルトで許可されないっぽい。

## やらかしたこと
### Azure App Services へのデプロイ
#### 起こったこと
フロントエンド側のリポジトリはGitHub Actions経由ですぐにデプロイできたが，API側のリポジトリはうまくいかず苦戦した。

#### やったこと，調べたこと
Azure App Servicesのデプロイ/ビルドは"Oryx"が行っており，`NODE_ENV`が`production`になる？(これは自分で設定したかもしれない)ため，`package.json`の`devDependencies`の方にインストールしたライブラリは本番環境でインストールされない(よくよく考えたら当たり前だが普段は手元でしか動かさないから気づくのに時間がかかった)。

#### 解決策
Typescriptのコンパイルは`tsc`コマンドだが，これは`typescript`というライブラリのコマンドであり，`npm install tsc` をしてしまっていたためのエラー(Kuduやログストリームから発見/解決)
(TypeScript🔰が出てしまった)

### GitHub Actions のストレージ
#### 起こったこと
GitHub Educationに登録しているため，容量などを気にせず使用していたが，デプロイ時にzipファイルが生成され，それをAzureにアップロードする形式のため，ワークフロー実行の度にArtifactが溜まっていった。これを知らず，自動削除のタスクや削除機関の設定をしていなかったために，早々に容量がオーバーして利用できなくなってしまった。

#### やったこと，調べたこと
手動でArtifactを削除すれば良いらしいが，それをしても空き容量は更新されなかった(結構待っても空き容量はちょこっと減る程度だった)。
これはissueが立っており，支払い上限を1$に設定すると即時リセットされるらしいが個人アカウントで支払い情報の登録がうまくできなかった(VAT/GST ID がよくわからない)。

#### 解決策
GitHub Actions経由での自動デプロイを諦めた。
Azure App Services の VSCode extension を導入した。これを利用するとVSCodeから直でデプロイできる(し，かなり高速に終わるから最初からこれにすれば良かったかもしれない)。

### ブランチ管理
#### 起こったこと
APIとフロントエンドのリポジトリを分割した。
API側をほとんど自分が制作し，フロントエンド側の一部をメンバーに仕事として振っていた。
仕事を振る時には専用の作業ブランチを用意していた。そのまま自分はAPI側の制作を進めていたが，仕事を振った時のブランチと対応するバージョンを残さずに更新していってしまった。
そのため，メンバーの作業用ブランチに最新のブランチをマージする必要があった。

### マイグレーション管理
#### 起こったこと
なぜか `.gitignore` にPrismaの `prisma/migrations` ディレクトリが含まれてしまっていた。
そのため，メンバーそれぞれがマイグレーションを行う必要があり，その差異によってエラーが出てしまった。

#### 解決策
`.gitignore` から `prisma/migrations` を除外した。
手動でマイグレーションを整理した。

### 本番環境のマイグレーション
#### 起こったこと
Prisma Clientが正しくモデルを認識しておらずエラーが出た。

#### 解決策
`npx prisma generate`したりPrismaのバージョンアップ等を試したが変わらず，`package.json` のビルドスクリプトに `npx prisma generate` を入れこと無きを得た(もっと良いやり方があるかもしれない/そもそも何故今まで動いていた？)。

### Azure 使用停止
#### 起こったこと
学校が契約しているAzureを学生アカウントから利用していた。予めこの程度の利用であれば問題ない旨伝えられていたため，DB(Flexible MySQLサーバ)/API(App Services)/WEBサーバ(App Services)とオブジェクトストレージ(Blob Storage)をすべてAzure上で構築し，利用していたが開発中に突如止められてしまった。
学校全体での契約であるが，どうやら誰かが使いすぎたためにコストが非常に嵩んでしまった(アラートが出た)らしい(完全なとばっちりを喰らった)。

#### 解決策
自分が個人で契約しているVPSに移行した(メンバー全員分で払っている教材費分使えた気はしないけどなあ)。


## 感想・思ったこと
今回はSNS部分を制作する上で，簡易的なREST APIを制作し，フロントエンド側からそれを叩く構成になりました。
しかし，実際にAPIの設計をするとかなり無駄な通信が多くなってしまったように感じます。こういった用途であればREST APIではなくGraphQLのようなものを採用するとやりやすかったのかなあと思いました(勉強時間的に間に合わなかったとは思いますが)。

また，メンバーの技量を考えて仕事を振るのも非常に大変でした。昨年度は個人制作だったので，自分のペースで見積もりができたのですが，メンバーに仕事を配ると長めに見積もる必要があり，思ったように進まないということがありました。
実際，開発の大部分を自分一人で作ってしまったので，あまりマネジメントができなかったなという気持ちがあります(これも期間的にはしょうがなかったのですが，PMであればもっと人を動かす側に回るべきなのかなという思いもありました)。