１週間で作ったアプリを紹介します【Laravel・MySQL・Nginx・Docker】

# はじめに
今年3月ごろに未経験転職の技術課題でオリジナルアプリの作成というものがあったので、１週間ちょっとで作成して公開しました。
色々事情があり現在も公開しているのでせっかくなら振り返りの記事を書こうと思った次第です！！
# アプリ概要
## アプリ名：WorksHub
<a href="https://workshub.herokuapp.com/" target="_blank">[![Image from Gyazo](https://i.gyazo.com/f474cb4d7ac44aba0c6c7dc85c33c554.png)](https://gyazo.com/f474cb4d7ac44aba0c6c7dc85c33c554)</a>
↓URLはこちらです。
https://workshub.herokuapp.com/
また、ソースコードはこちらです。

https://github.com/ooooose/php_portfolio

## サービス内容
**個人で開発したサービスを投稿・紹介できるサービス**です。ログインユーザーは投稿したサービスに対してお気に入り登録ができ、管理することができます。
私はRUNTEQというプログラミングスクールに通っていたんですが、同じ受講生が作るポートフォリオがとても好きでした。
そんな受講生が作ったポートフォリオと何かしら絡みを持たせたアプリにしたいと思い、WorksHubを作りました！
１週間で作ってスクールのtimesで周知したところ、多くの方が登録してくれてFBもたくさんいただくことができました。嬉しかったです。

## 画面遷移図とER図
簡単にですが、それぞれ作成しました。
機能的には最小限に抑えないと期限までに終わらないので簡単な仕様にしました。
- 画面遷移図
[![Image from Gyazo](https://i.gyazo.com/75b4eb814dfe40149497e6b98eb759bd.png)](https://gyazo.com/75b4eb814dfe40149497e6b98eb759bd)
- ER図
[![Image from Gyazo](https://i.gyazo.com/06d3e25f63bb6d1fa5c3686fac143889.png)](https://gyazo.com/06d3e25f63bb6d1fa5c3686fac143889)
## 実装機能
以下実装しました。アプリとして最低限の実装を意識しました。
- ユーザー登録機能
- ログイン・ログアウト機能（認証機能）
- 作品のCRUD機能
- 作品の検索機能
- お気に入り機能
## 技術概要
ローカルではLEMP環境として構築し開発しました。
デプロイ先はHeroku,画像アップード先はS3にしました。これも手順的に慣れているという理由から選択しました。
- PHP(8), Laravel(8)
- Nginx
- MySQL 8
- Docker, docker-compose
- Heroku
- S3（画像アップロード先として選択）
## 機能紹介
機能について触れていけたらと思います。
### ユーザー登録・ログイン機能
[![Image from Gyazo](https://i.gyazo.com/e5119001073394aab6e0ac3990ed1a6e.png)](https://gyazo.com/e5119001073394aab6e0ac3990ed1a6e)
こちらは期限もあったためLaravel breezeで作成しました。
以下ドキュメントを参考に作成しました。

https://readouble.com/laravel/8.x/ja/starter-kits.html

ログインユーザーでないとアプリのTOP画面意外はアクセスできないようにミドルウェアとルーティングは設定しています。そのほかバリデーションの日本語化などやっています。

### 作品のCRUD機能
作品は認証ユーザーが作成できます。他のユーザーの編集・削除ができないようにPolicyを作成してURLからのアクセスなどを防止しています。
#### 投稿機能
[![Image from Gyazo](https://i.gyazo.com/a46406a566f3e7694e1aa6ca0bc679d3.png)](https://gyazo.com/a46406a566f3e7694e1aa6ca0bc679d3)
新規作成に合わせてvalidationの実装、old関数による値の保持、エラーメッセージの表示などを実装しました。
サムネイルのプレビュー画面は選択と同時に表示されるようにしています。
[![Image from Gyazo](https://i.gyazo.com/e1287495b8073e42c5e7474098c6edbf.gif)](https://gyazo.com/e1287495b8073e42c5e7474098c6edbf)

#### 編集機能
詳細画面は下のようになっており、編集ボタンを押せば編集画面に飛べます。
[![Image from Gyazo](https://i.gyazo.com/5dbeaef033beb57c1d68106d78cad8a0.gif)](https://gyazo.com/5dbeaef033beb57c1d68106d78cad8a0)
#### 削除機能
削除ボタンを押してアラートを確認すれば削除ができます。
[![Image from Gyazo](https://i.gyazo.com/eb37bee88adea3d67baf50b446f09a7d.gif)](https://gyazo.com/eb37bee88adea3d67baf50b446f09a7d)
### 作品の検索機能
入力した文字列に合わせてヒットした作品を返します。
[![Image from Gyazo](https://i.gyazo.com/5f161544172fe13b035c64ea2b2338f1.gif)](https://gyazo.com/5f161544172fe13b035c64ea2b2338f1)
作品検索は独自の作成ではなく、以下Udemyを参考に作成しました。
以下参考URLを貼っておきますm(_ _)m

https://www.udemy.com/course/phpbeginnertolaravel/

### お気に入り機能
作品に対してお気に入りができます。お気に入りした作品は一覧画面に表示されます。
こちらは特に頑張ったところなので実装内容も後述させていただきます！
[![Image from Gyazo](https://i.gyazo.com/7f0d7b7628aa6f76fa318d057a49f612.gif)](https://gyazo.com/7f0d7b7628aa6f76fa318d057a49f612)

## 特にお気に入りの非同期処理頑張った
お気に入り登録の非同期処理を頑張りました。ReactやVueは導入しておらず、jQueryを使用して作成しました。面接まで残り1日のところ急ピッチで作成しました。
当時いい実装記事が見つけにくく苦労したため、今回自分が実装したコードを少し紹介できたらと思います。
ルーティングは以下のようになっています。
以下、`php artisan route:list`の実行結果の一部抜粋です。
```zsh:terminal
Method   | URI                   | Name                | Action
POST     | boards/{id}/bookmark  | boards.bookmark     | App\Http\Controllers\BoardController@bookmark

```
コントローラは以下のように記述しています。
認証ユーザーが記事に対してお気に入りしているかを`$already_bookmarked`で確認し、存在しなければお気に入りとしてbookmarkを作成し、存在すれば削除すると言った処理をしています。
bookmarkコントローラでCreateとDestroy両方の役割を持たせています。
```php:BoardController.php

public function bookmark(Request $request)
    {
        $user_id = Auth::user()->id;
        $board_id = $request->board_id;
        $already_bookmarked = Bookmark::where('user_id', $user_id)->where('board_id', $board_id)->first();

        if (!$already_bookmarked) {
            Bookmark::create([
                'user_id' => $user_id,
                'board_id' => $board_id]);
        } else {
                Bookmark::where('board_id', $board_id)->where('user_id', $user_id)->delete(); 
        }

        return redirect()->back();
    }
```
お気に入り関連の記述は以下の通り作品一覧を表示する際、各作品に以下のdiv要素を設定しています。
`is_bookmarked_by_auth_user()`はモデルで定義しており、`$board`が認証ユーザーによってお気に入りされているかを判定するメソッドとして実装しています。
それに対して、spanタグ内でクラスを設定して非同期処理をjQueryで実装し、ボタンを押すたびに星の色を切り替えるようにしました。
```php:index.blade.php
(中略)
<div class="float-right flex items-center flex-wrap mt-3">
    @if($board->is_bookmarked_by_auth_user())
        <span class='bookmarks'>
            <i class='cursor-pointer fa-solid fa-star bookmark-toggle' data-board-id="{{ $board->id }}"></i>
        </span>
    @else
        <span class='bookmarks'>
            <i class='cursor-pointer fa-solid fa-star bookmark-toggle bookmarked' data-board-id="{{ $board->id }}"></i>
        </span>
    @endif
</div>
（以下略）
```
jQueryの記述は以下の通りです。　

```javascript:bookmark.js
window.addEventListener('DOMContentLoaded', function () {
    $(function () {
        let bookmark = $('.bookmark-toggle');
        let bookmarkBoardId;

        bookmark.on('click', function () {
            let $this = $(this);
            bookmarkBoardId = $this.data('board-id');
            $.ajax({
                headers: {
                    'X-CSRF-TOKEN' : $('meta[name="csrf-token"]').attr('content')
                },
                url: `/boards/${bookmarkBoardId}/bookmark`,
                method: "POST",
                data: {
                    'board_id': bookmarkBoardId,
                },
            })
            .done(function () {
                $this.toggleClass('bookmarked');
            })
            .fail(function() {
                console.log('fail');
            })
        })
    })

});

```
`.bookmark-toggle`としているiタグを取得して`bookmark`という変数に格納しています。
このiタグには以下の通りdata属性を設定しています。
```
data-board-id="{{ $board->id }}"
```
クリックするたび、bookmarkBoardId変数に`board-id`というデータ属性に設定している作品（`$board`）IDを格納します。
その後、urlとして設定しているエンドポイント`/boards/${bookmarkBoardId}/bookmark`に対して、POSTメソッドで非同期処理を実行しています。
挙動が完了すれば、iタグのクラスに`.bookmarked`をtoggleClassでつけ外しすることで星の色を変えています。なお、bookmarkedクラスのCSSは以下のように指定しています。
```css:
.bookmarked {
    opacity: 0.2;
}
```

## 最後に
簡単なアプリですが作ってて楽しかったですし、Laravelに初めて触りましたがいい勉強になりました。
こうやってアウトプットしながら学習する習慣を今後も継続したいと思えた、いい経験だったと思います！ここまで読んでいただいてありがとうございました🙇‍♂️