【個人開発】小説投稿SNSサービス Shomotsu

# 【個人開発】小説投稿SNSサービス Shomotsu
小説家になろうにインスピレーションを受け、`Next.js`, `Laravel`, `AWS`を使用した、小説投稿SNSサービスを作りました。

https://shomotsu.net

現在停止中

※OGPの情報や画像が今正しく表示されてないです。すぐに修正します。

Githubリポジトリは環境変数漏れがないかを確認した後、貼ります

## コンセプト
- ユーザーが気軽に小説を投稿できる
- 立体感のある小説UIで読書を楽しめる

## 画面
すべてではないですが、各ページの画面は以下のような感じです。`Tailwind CSS`を使用したおかげでレスポンシブ対応

使用したモックアップの写真を生成するサービスは↓

https://shots.so/

色々なデバイスのモックアップの写真が作れるらしく、とても便利でした。

### プロフィールページ
![profile.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3865740/ea98f767-2c25-8b73-b6f1-2f1b24f40e53.png)

### 小説の詳細情報ページ
![book-detail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3865740/0a97dbc6-e79e-9e56-27b0-0651a071953a.png)

### 小説の読書ページ
![read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3865740/e76efe84-e05d-b410-3c46-1b78cc212119.png)

## プロフィール設定ページ
![profile-setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3865740/967c7af7-caea-3431-a682-4e9ba06901e9.png)

### 実装で苦労した・工夫した点
小説の読書画面の実装にとても苦労しました。`Three.js`などのグラフィックに関するライブラリで実装する技術がなかったため、素のCSS・Reactで3D風にページをめくっているような見た目になるように工夫しながら実装しました(こちらのリンク([デモページ](https://www.shomotsu.net/demo))からご覧いただけます。)。アニメーション系のCSSプロパティを多用しているせいか画面がかくつく・動作がもっさりしているのが欠点です。

## 機能一覧
### 管理者
| 機能                              | 内容                                    |
| :-------------------------------- | :-------------------------------------- |
| 最近のデータ一覧                  | 最新の登録者、通報一覧を3件ずつ取得     |
| 登録されているユーザー一覧        | 登録ユーザーを一覧を取得する            |
| 通報一覧                          | 通報一覧を取得する                      |
| ユーザーのアカウントBAN           | ユーザーのアカウントをBANする           |
| 通報されたユーザーのアカウントBAN | 通報されたユーザーのアカウントをBANする |
| 通報された小説の削除              | 通報された小説を削除する                |

### 認証
| 機能           | 内容                                   |
| :------------- | :------------------------------------- |
| OAuthのURL取得 | GoogleのOAuthログイン用のURLを取得する |
| ログイン       | GoogleのOAuthログインを行う            |

### ユーザー
| 機能                       | 内容                                         |
| :------------------------- | :------------------------------------------- |
| ユーザー削除               | アカウントを削除する                         |
| ユーザーネーム入力         | サインアップ後、ユーザーネームを入力する     |
| フォロー                   | 他のユーザーをフォローする                   |
| 認証ユーザー取得           | ログインしたユーザー情報を取得する           |
| フォロワー取得             | フォロワーを取得する                         |
| フォロー中のユーザー取得   | フォロー中のユーザー取得する                 |
| ユーザーのプロフィール情報 | ユーザーのプロフィール情報を取得する         |
| ログアウト                 | ログアウトする                               |
| 通知を既読                 | ログインユーザーの通知をすべて既読にする     |
| 通報                       | 違反しているユーザーを報告する               |
| プロフィールを更新         | ログインユーザーのプロフィール情報を更新する |

### 小説
| 機能             | 内容                                   |
| :--------------- | :------------------------------------- |
| 作成             | 新しい小説を執筆する                   |
| 削除             | 小説を削除する                         |
| ダウンロード     | 小説に関するデータを取得する           |
| 詳細情報取得     | 小説の詳細情報を取得する               |
| 小説一覧         | ユーザーの小説一覧を取得する           |
| 書庫一覧         | ユーザーがいいねした小説一覧を取得する |
| おすすめ小説一覧 | おすすめの小説一覧を取得する           |
| タイムライン     | タイムラインを取得する                 |
| 更新             | 小説のデータを更新する                 |
| いいね           | 小説にいいねする                       |
| 検索             | 小説を検索する                         |


## ER図
Laravelのモデルを使用しています。
### User
| キー | 型     | 属性名      | 説明                       |
| :--- | :----- | :---------- | :------------------------- |
| PK   | string | id          | モデルID                   |
|      | string | name        | ユーザーネーム             |
|      | string | email       | Eメールアドレス            |
|      | string | password    | パスワード                 |
|      | string | social_id   | 認証されたユーザーのID     |
|      | string | social_type | 認証に使用したプロバイダー |

### Book
| キー | 型              | 属性名       | 説明               |
| :--- | :-------------- | :----------- | :----------------- |
| PK   | string          | id           | モデルID           |
| FK   | string          | user_id      | ユーザーID         |
| FK   | string          | category_id  | カテゴリーID       |
|      | string          | title        | 小説のタイトル     |
|      | string          | sub_title    | 小説のサブタイトル |
|      | string          | summary      | 小説のあらすじ     |
|      | string          | description  | 小説の説明         |
|      | string          | content      | 小説のコンテンツ   |
|      | string  \| null | book_photo   | 小説の表紙の写真   |
|      | int             | update_count | 小説の更新回数     |
|      | int             | price        | 小説の値段         |
|      | int             | page_length  | 小説のページ数     |
|      | boolean         | publish      | 小説の公開・非公開 |

### Category
| キー | 型     | 属性名   | 説明                       |
| :--- | :----- | :------- | :------------------------- |
| PK   | string | id       | モデルID                   |
|      | string | category | カテゴリー名               |
|      | string | value    | カテゴリーの英語名(検索用) |

### Reports
| キー | 型      | 属性名                | 説明                       |
| :--- | :------ | :-------------------- | :------------------------- |
| PK   | string  | id                    | モデルID                   |
| FK   | string  | report_target_user_id | 通報対象のユーザーID       |
| FK   | string  | report_target_book_id | 通報対象の小説ID           |
|      | string  | user_id               | 通報したユーザーID         |
|      | string  | report_type           | 通報対象がユーザーか小説家 |
|      | int     | report_code           | 通報コード                 |
|      | boolean | is_complete           | 通報を管理者が処理したか   |

### Profile
| キー | 型             | 属性名        | 説明                       |
| :--- | :------------- | :------------ | :------------------------- |
| PK   | string         | id            | モデルID                   |
| FK   | string         | user_id       | ユーザーID                 |
|      | string         | display_name  | ユーザーの表示名           |
|      | string \| null | profile_photo | ユーザーのプロフィール画像 |
|      | string \| null | bio           | ユーザーのプロフィール欄   |
|      | string \| null | social_link_1 | SNS・Webサイトのリンク     |
|      | string \| null | social_link_2 | SNS・Webサイトのリンク     |
|      | string \| null | social_link_3 | SNS・Webサイトのリンク     |
|      | string \| null | social_link_4 | SNS・Webサイトのリンク     |
|      | string \| null | social_link_5 | SNS・Webサイトのリンク     |

### Follow
| キー | 型     | 属性名      | 説明                     |
| :--- | :----- | :---------- | :----------------------- |
| PK   | string | id          | モデルID                 |
| FK   | string | follower_id | フォローされるユーザーID |
| FK   | string | follow_id   | フォローするユーザーID   |

### Activity
| キー | 型     | 属性名               | 説明                 |
| :--- | :----- | :------------------- | :------------------- |
| PK   | string | id                   | モデルID             |
| FK   | string | user_id              | ユーザーID           |
|      | int    | word_count           | 執筆した文字数       |
|      | int    | consective_login_day | 連続ログイン日       |
|      | Date   | last_login_day       | 最後にログインした日 |

### Like
| キー | 型     | 属性名  | 説明       |
| :--- | :----- | :------ | :--------- |
| PK   | string | id      | モデルID   |
| FK   | string | user_id | ユーザーID |
| FK   | string | book_id | 小説ID     |

### Team
| キー | 型     | 属性名   | 説明                       |
| :--- | :----- | :------- | :------------------------- |
| PK   | string | id       | モデルID                   |
| FK   | string | owner_id | チーム作成者(ユーザー)のID |
|      | string | name     | チーム名                   |

### Version
| キー | 型     | 属性名  | 説明                         |
| :--- | :----- | :------ | :--------------------------- |
| PK   | string | id      | モデルID                     |
| FK   | string | book_id | 小説ID                       |
|      | int    | version | 小説のコンテンツのバージョン |
|      | string | content | 更新前のコンテンツ           |

## ER図
![ER図](/images/2024/12/er.png)

## URL設計
### ページ
| URL                 | ページ                                 |
| :------------------ | :------------------------------------- |
| /[user]             | プロフィールページ                     |
| /activity           | ログインユーザーのアクティビティページ |
| /admin/users        | ユーザー一覧ページ                     |
| /admin/reports      | 通報一覧ページ                         |
| /admin              | 管理者トップページ                     |
| /book/[book]/       | 小説の詳細情報ページ                   |
| /book/[book]/read   | 読書ページ                             |
| /book/[book]/update | 小説更新フォームページ                 |
| /book/create        | 小説執筆フォームページ                 |
| /library            | 書庫ページ                             |
| /setting/account    | アカウント設定ページ                   |
| /setting            | 設定のトップページ                     |
| /setting/profile    | プロフィール設定ページ                 |
| /404                | 404ページ                              |
| /about              | aboutページ                            |
| /changelog          | 変更履歴ページ                         |
| /demo               | デモページ                             |
| /                   | トップページ                           |
| /privacy            | プライバシーポリシーページ             |
| /search             | 小説検索ページ                         |
| /terms              | 利用規約ページ                         |
### API側
| APIのURL                        | HTTPメソッド | 機能                                     |
| :------------------------------ | :----------- | :--------------------------------------- |
| **管理者**                      |              |                                          |
| v1/admin/recent                 | GET          | 最近のユーザー登録者、通報を取得する     |
| v1/admin/users                  | GET          | 登録されているユーザー一覧を取得する     |
| v1/admin/report/users           | GET          | 通報されたユーザー一覧を取得する         |
| v1/admin/report/books           | GET          | 通報された一覧を取得する                 |
| v1/admin/users/{id}             | PATCH        | 通報されたユーザーをBANする              |
| v1/admin/report/users/{id}      | PATCH        | 通報されたユーザーをBANする              |
| v1/admin/report/books/{id}      | DELETE       | 通報された小説を強制削除する             |
| **認証**                        |              |                                          |
| v1/auth/google                  | GET          | OAuth認証用URLを 取得する                |
| v1/auth/google/callback         | GET          | Googleログイン                           |
| **ユーザー**                    |              |                                          |
| v1/auth/user                    | GET          | 認証ユーザーの情報を取得する             |
| v1/users/{id}/follower          | GET          | フォロワー一覧を取得する                 |
| v1/users/{id}/following         | GET          | フォローしているユーザー一覧を取得する   |
| v1/users/{user}                 | GET          | ユーザーのプロフィール情報を取得する     |
| v1/reports/{id}                 | POST         | ユーザーを通報する                       |
| v1/users/{id}/follow            | POST         | ユーザーをフォローする                   |
| v1/auth/enter_name              | POST         | ログイン後、ユーザーネームを決定する     |
| v1/auth/logout                  | POST         | ログアウトする                           |
| v1/auth/user                    | POST         | ログインユーザーのプロフィールを編集する |
| v1/auth/user/notifications/read | POST         | ユーザーの通知を既読にする               |
| v1/auth/user                    | DELETE       | ユーザーアカウントを削除する             |
| **小説**                        |              |                                          |
| v1/books                        | GET          | ユーザーの小説一覧を取得する             |
| v1/books/library                | GET          | ユーザーがいいねした小説一覧を取得する   |
| v1/books/recommend              | GET          | ユーザーにおすすめな小説一覧を取得する   |
| v1/books/timeline               | GET          | タイムラインを取得する                   |
| v1/books/search                 | GET          | 小説を検索する                           |
| v1/books/{book}                 | GET          | idに一致する小説を取得する               |
| v1/books/{book}/download-file   | GET          | idに一致する小説のデータを取得する       |
| v1/books/{book}/update-book     | GET          | idに一致する小説を取得する               |
| v1/books/create                 | POST         | 新しい小説を執筆する                     |
| v1/books/{book}/like            | POST         | 小説にいいねする                         |
| v1/books/{book}                 | PATCH        | 小説を編集する                           |
| v1/books/{book}                 | DELETE       | 小説を削除する                           |

## 使用技術・フレームワーク
1. Laravel
2. React・Next.js
3. Nginx
4. MySQL
5. Redis
6. Github Actions
7. AWS

### 選定理由
1. Laravel
- Githubのスター数も多く、活発的に開発が行われている
- Laravel標準機能だけでもWebアプリを作れるぐらい機能が充実しているので、開発が楽にできる
- 公式ドキュメントやコミュニティなどエコシステムが充実している

2. Next.js
- CSR,SSG,ISG,SSR全ての対応することができ、パフォーマンスチューニングのために変更することになっても、リファクタリングが容易にできる
- ファイルシステムベースのルーティング機能があり、アプリのページやAPIルートを楽に構築できる
- フレームワークの設定を特にしなくても動く
- pageルーターで実装していても途中からappルーターへの変更できる

3. Nginx
- Apacheよりも処理能力が高くて柔軟に設定を行える(初めて使用したので、そのレベルまで至っていません)
- 設定もシンプルなので学びやすいと思った

4. MySQL
- 世界で最も普及しているOSSデータベースで多数企業で採用されている実績
- LaravelのORMも対応しているので簡単に使用できる
- 小規模から大規模プロジェクトに対応しているので拡張性が高い

5. Redis
- データベースへの負荷を軽減するために採用
- インメモリデータストアのため高速
- キャッシュについて少し理解が深まるのではないかという学習面からも採用

6. Github Actions
- 無料で使える
- ymlファイルを特定のディレクトリに置くだけで簡単にCI/CDを構築できる
- 様々な言語・フレームワーク用の設定ファイルが充実している
- ワークフローは分割することができ、再利用性が高くなる

7. AWS
- 多数の企業やサービスでインフラとして使われている実績がある
- Amazonのインフラなどで、突然サービスが使用できないなどの状況に陥る可能性が低い
- 多数のサービスがあり、可用性の高いWebアプリを構築できる

8. その他
- TypeScript
- TailwindCSS
- shadcn/ui
- eslint
- prettier
- Docker
- jest
- msw
- swr

### インフラ構成図
少しでも料金を抑えるかつシンプルにしました。AWSのサービスについての知識がまだまだです。知識を深めていき、より堅牢に拡張性に優れたアプリケーションのインフラを構築できるようになりたいです。
![インフラ構成図](/images/2024/12/aws.png)

## 学べた点
### 1. サービスの企画から公開までの流れについて少し理解ができたこと
アプリやサービスは作って終わりではなく、作ってからが本番であることを理解することができました。また公開するまでの設計段階やテストも重要であると気づかされました。

### 2. マネタイズができないとサービスは維持できないということ
当たり前のことですが、マネタイズできてないと常に赤字状態になります。マネタイズ手法を学び、また別の機会にマネタイズも含めたサービスを公開したいです。そのためにも人に使ってもらえる・お金を払う価値があると思ってもらえるようなサービス・アプリを作れるよう設計や技術力を上げていきたいです。

### 3. 設計が最も重要だということ
設計はある程度決めたうえで作業を開始しましたが、それでも開発中にこれ追加したい、やっぱこうしようみたいになったのでもっと設計段階で細かく決めるべきだったと思います。

## 反省点
### 1. それぞれのフレームワーク・サービスを一から学びながら実装・時間制限を設けずだらだら続けてきたため、半年以上もかかったこと
公開する時間を決めずにちまちま学びながらやってきたせいで、とても時間がかかってしまいました。締め切りを決めてからやらないとだらだらしてしまう。時間制限を設けて緊張感をもってやらないといけないなと改めて気づきました。

### 2. 途中からcommitの文章、pull requestの文章が乱雑になったこと
最初は分かりやすいようなcommitメッセージを書くように心がけていたつもりでしたが、だんだんと適当な文章で書くようになりました。かっこつけて英語のcommitメッセージを打つのも原因の一つなのかなと思う。英語がわからないうちは日本語で書いた方が良さそう。

### 3. 開発する機能のissueの粒度を大きくしすぎた
機能実装を行う上でのタスクごとにissue管理するのがissue駆動開発だが、機能ごとにissueを作成してしまったのでタスクが膨れ上がり、ずっと終わらない状態が続きました。issue駆動開発についての理解が足りなかったことが原因でした。

### 4. 良いコード・美しいコードを書くことが全てだと思い込んでいたこと
最初から完璧・疎結合・美しいコードを書くことが全てだと思い込み開発を始めたのが半年もかかったもう一つの要因です。目的はアプリを公開することだったのに美しいコードを書くことに置き換わってしまった。

## 最後に
まだまだ改善すべき点はたくさんありますが、Shomotsuの良かったこと、改善点、感想などお教え頂ければ幸いです。よろしくお願いいたします。