DRFに関する備忘録

#はじめに
本記事では以下のツールを使用しています。

- **anaconda navigator**: Python開発のためのディストリビューション(コンパイル済みのソフトウェアの集まり)
- **Pycharm**: python用のテキストエディター
- **Postman**: apiのエンドポイントにリクエストを送信できるツール
- **Visual Studio Cord**: テキストエディター(以下 VSCord)
- **ModHeader**: chromeの拡張機能でトークンを付与したアクセスなどを可能にするツール

#開発環境
python 3.7
Django 3.1
React

#バックエンド(DRF)の作成
##仮想環境を構築する(Anaconda_navigator)
1. Environments項目のCreateを選択
2. Name(筆者はJIRA)を入力し、Packages(Python 3.7)を選択し仮想環境の作成(しばらく待つ)
3. 再生ボタンをクリックし、ターミナルを開く
4. anaconda navigatorにて仮想環境（python3.7）の作成し、必要なものをインストール(下記コマンド)

```
$ pip install django==3.1
$ pip install djangorestframework==3.11.1
$ pip install djangorestframework-simplejwt==4.6.0
$ pip install django-cors-headers==3.4.0
$ pip install djoser==2.0.3
$ pip install Pillow
```
上記のコマンドにより各ライブラリ？のインストールが完了したら、anaconda navigatorとターミナルを閉じて、anaconda navigatorで作成した仮想環境とPycharmのプロジェクトに紐付ける



##仮想環境とPycharmを紐付ける
1. 空のディレクトリ(筆者はjira_api)を作成し、Openを選択し、Pycharmで開く
2. Preferences...を選択し、Projectの中のPython Interpreterを選択
3. 右上の設定ボタン(歯車マーク)を選択し、Addを選択
4. Existing envirionmentにチェックを入れ、右にある...を選択
5. /Users/ホームディレクトリ/opt/anaconda3/envs/JIRA/bin/pythonを選択しOK

Anaconda　navigatarで作成した仮想環境がenvs配下に作成される

##プロジェクトとアプリを作成する
```
$ django-admin startproject jira_api .
$ django-admin startapp api
```
最後の.(ドット)は現在のディレクトリを意味する


##現時点でのローカル環境を起動する
1. manage.pyを右クリックし、Run 'manage'を選択
2. 右上のmanageをクリックし、Edit Configulations...を選択
3. Parametersという項目にrunserverを追加してOK
4. `python manage.py migrate`を実行 
5. 右上の再生ボタンをクリックで、ローカル環境を起動

以降再生ボタンをクリックするだけで`python manage.py runserver`を実行してくれる
ローカルホストでロケットが表示されているのを確認。

##settings.pyを編集
```settings.py
#記の2行を追記
from datetime import timedelta
import os

INSTALLED_APPS = [
    #省略 下記の4行を追記
    'rest_framework',
    'djoser',
    'api.apps.ApiConfig',
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    #省略 上記の1行を追記
]

#以下の3行を追記
CORS_ORIGIN_WHITELIST = [
    "http://localhost:3000"
]

#以下の8行を追記
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

#以下の4行を追記
SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
}

TIME_ZONE = 'Asia/Tokyo'                     #UTCから変更
#以下の2行を追記
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
```
- jwtトークンの有効期限を設定するためにtimedeltaを導入
- INSTALLED_APPに導入したアプリを追加
- djangoは.(ドット)で繋ぐことができるため、api.apps.ApiConfigとはapiディレクトリのapps.pyのApiConfigのことを指す
- MIDDLEWAREにcorsに関するコードを追記
- React(localhost:3000)からのアクセスを許可するためにCORS_ORIGIN_WHITELISTを追加する
- djangoのデフォルトの認証関連を設定する
- views.pyを特定のユーザーだけに見せるようにするためにDAFAULT_PERMISSION_CLASSEを設定する
- jwtを使用した認証をしたいため、DEFAULT_AUTHENTICATION_CLASSES
- SIMPLE_JWTによってトークンの有効期限を60分に指定している
- djangoにデフォルトのUserモデルではなく、カスタマイズしたUserモデルを使用するように明示的にする必要がある
- プロジェクト直下にmediaディレクトリを作成し、画像の保存先を明示的にする（MEDIA_ROOTとMEDIA_URL）
- migrateコマンドはデフォルトでsqlite3に変換してくれる

##urls.pyの作成
```jira_api/urls.py
#includeを追加
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    #以下の2行を追加
    path('api/', include('api.urls')),
    path('authen/', include('djoser.urls.jwt'))
]
#以下の行を追加
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
```
- MEDIA_ROOTなどの値も持ってきたいのでsettingsのimportを追加
- 画像の配置場所を指定するためにstaticを使用する
- localhost:8000/api/にアクセスがあった際にapiディレクトリのurlsを参照する
- `path('authen/', include('djoser.urls.jwt'))`についてはjwtの認証トークンを返すpathの設定
- media/というpathとmediaディレクトリの紐付けをしていて、media/にアクセスがあった際にmediaディレクトリを参照するようにしている。

```api/urls.py
from django.urls import path, include
from rest_framework import routers

router = routers.DefaultRouter()

urlpatterns = [
    path('', include(router.urls)),
]
```
##モデルの作成

```models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
import uuid

def upload_avatar_path(instance, filename):
    ext = filename.split('.')[-1]
    return '/'.join(['avatars', str(instance.user_profile.id)+str(".")+str(ext)])

class Profile(models.Model):
    user_profile = models.OneToOneField(
        User, related_name='user_profile',
        on_delete=models.CASCADE
    )
    img = models.ImageField(blank=True, null=True, upload_to=upload_avatar_path)

    def __str__(self):
        return self.user_profile.username

class Category(models.Model):
    item = models.CharField(max_length=100)

    def __str__(self):
        return self.item

class Task(models.Model):
    STATUS = (
        ('1', 'Not started'),
        ('2', 'On going'),
        ('3', 'Done'),
    )
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=300)
    criteria = models.CharField(max_length=100)
    status = models.CharField(max_length=40, choices=STATUS, default='1')
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    estimate = models.IntegerField(validators=[MinValueValidator(0)])
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner')
    responsible = models.ForeignKey(User, on_delete=models.CASCADE, related_name='responsible')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title
```
- 整数のみを扱いたいのでdjangoのvalidationとして提供されているMinValueValidatorを使用する
- djangoはデフォルトだとデータを作成した時にプライマリーキーが連番で付与されるのが、セキュリティーの観点からよくないので,univarsal unique idを使用し、128bitで一意な値を使用する
- OneToOneFieldはdjangoのUserモデルと1対1で紐付けていて、CASCADEすることによってUserが削除された場合にuser_profileも削除するようにしている。
- Profileモデルのインスタンスがprintなどで呼ばれた際に、文字列のusername（DjangoのデフォルトのUserモデルで設定されている属性）を返すようにしている
- foreignKeyを使用してCategoryモデルを参照し、要素を選択できるようにしている
- `MinValueValidator(0)`は0以上の整数のみ受け付けるようにしているj 


##データベースに反映させる
```
$ python manage.py makemigrations
$ python manage.py makemirate
```

##ダッシュボードで追加したモデル内容を確認する
```admin.py
from django.contrib import admin
from .models import Category, Task, Profile

admin.site.register(Category)
admin.site.register(Task)
admin.site.register(Profile)
```

```
$ python manage.py createsuperuser
```
- 管理者権限をもったユーザーの作成

##シリアライザーの作成
###シリアライザーとは
データベースからクライアント側にオブジェクトを渡す時にjsonに変換してくれたり、クライアント側からemailやpasswordなどをデータベースに渡す時に精査(validation)してくれたりするもの。
又、新規でモデルを作成した際にセットで作るもの。(User, Profile, Post, Commentの4つのシリアライザーを作成する)

1. アプリケーションディレクトリ(startappコマンドで作成したディレクトリ)直下にserializers.pyを作成

```serializers.py
from rest_framework import serializers
from .models import Task, Category, Profile
from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True, 'required': True}}

    def create(self, validated_data):
        user = User.objects.create_user(**validated_data)
        return user

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ['id', 'user_profile', 'img']
        extra_kwargs = {'user_profile': {'read_only': True}}

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'item']

class TaskSerializer(serializers.ModelSerializer):
    category_item = serializers.ReadOnlyField(source='category.item', read_only=True)
    owner_username = serializers.ReadOnlyField(source='owner.username', read_only=True)
    responsible_username = serializers.ReadOnlyField(source='responsible.username', read_only=True)
    status_name = serializers.CharField(source='get_status_display', read_only=True)
    created_at = serializers.DateTimeField(format="%Y-%m-%d %H:%M", read_only=True)
    updated_at = serializers.DateTimeField(format="%Y-%m-%d %H:%M", read_only=True)

    class Meta:
        model = Task
        fields = ['id', 'task', 'description', 'criteria', 'status', 'status_name', 'category', 'category_item',
                  'estimate', 'responsible', 'responsible_name', 'owner', 'owner_user_name', 'created_at', 'updated_at']
        extra_kwargs = {'owner': {'read_only': True}}

```
- class Meta以下に内容を記述するルールになっている
- modelには扱いたいモデルを指定し、fieldsに扱いたい属性を指定し、extra_kwargsでは属性に対してオプションを指定できる
- createメソッドに関してはデフォルトではパスワードを設定した際にハッシュ化されないので、ハッシュ化するようにオーバーライドしている。
- ReadOnlyFieldを使って、他のモデル(Taskモデル以外のCategoryモデルなど)の属性を引っ張ってこれるようにしている
- `get_status_display`はmodels.pyで用意した定数(STATUS）の値(No startedなど)を指している(`get_(models.pyで用意した定数の小文字化)_display`を使用することで値の取得が可能)

##viewの作成
Django(rest_framework)は2種類のviewがある
- generics(特定のメソッドに特化)
- model view set（一般的なCREDを提供している）

```views.py
from django.shortcuts import render
from rest_framework import status, permissions, generics, viewsets
from rest_framework.response import Response
from .serializers import UserSerializer, CategorySerializer, TaskSerializer, ProfileSerializer
from .models import Task, Category, Profile
from django.contrib.auth.models import User

class CreateUserView(generics.CreateAPIView):
    serializer_class = UserSerializer
    permission_classes = (permissions.AllowAny,)

class ListUserView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class LonginUserView(generics.RetrieveUpdateAPIView):
    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user

class ProfileViewSet(viewsets.ModelViewSet):
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

    def perform_create(self, serializer):
        serializer.save(user_profile=self.request.user)

    def destroy(self, request, *args, **kwargs):
        response = {'message': 'DELETE method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

    def partial_update(self, request, *args, **kwargs):
        response = {'message': 'PATCH method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

    def destroy(self, request, *args, **kwargs):
        response = {'message': 'DELETE method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

    def update(self, request, *args, **kwargs):
        response = {'message': 'PUT method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

    def partial_update(self, request, *args, **kwargs):
        response = {'message': 'PATCH method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def partial_update(self, request, *args, **kwargs):
        response = {'message': 'PATCH method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

```
- `CreateUserView`は`generics.CreateAPIView`によってUser作成に特化したメソッドにしている(viewの中にはserializerを指定するようになっている)
- `permission_classes = (permissions.AlloAny,)`は、プロジェクト直下のsettings.pyに認証が通ったUserのみが各viewにアクセスできるように設定してありますが、Userの新規作成では認証が必要ないので誰でもアクセスできるようにpermissionを上書きしている。
- オブジェクトを取得する場合はquery_setに格納する
- `generics.RetrieveUpdateAPIView`は特定のオブジェクトを検索して返してくれるview
- `request.user`はログインしているユーザーを意味しているので、ログイン中のユーザーを返すように、元々用意されている`get_object`をオーバーライドしている
- `viewsets.ModelViewSet`はCRUD丸ごと提供してくれているので、それを使っている
- `perform_create`はCreateに当たるメソッドで、user_profile属性をログイン中のユーザーの情報を格納してからProfileオブジェクトを作成するようにしている(フロントエンドから毎回UserProfileを選択しなくして済む)
- ProfileViewSetではget, post, putのみを使用したいので、使用しないpartial_updateとdeleteを無効化している(メッセージ付きでbad requestで返す様に変更)
- 新規でタスクを作成した際に、自動でログインしているユーザーをオーナーにする様にしている。

##カスタムパーミッションの作成
###パーミッション(permission)とは
DRFでいうパーミッションとは、要はアクセス権限のことです。
DRFではアクセス権限（Permissions）、および認証（Authentication）の両方に標準のクラスが用意されており、それぞれ設定することができます。
ちなみに、Permissionsはリソースにアクセスするための権限、Authenticationはユーザーの認証（ユーザーが本人かどうかのチェック）のことを指しています。

ここでは、利用者がオーナーではないタスクに変更(削除や、更新)を加えられないようにする
アプリケーションディレクトリ(筆者はapi/)配下にcustompermissions.pyを作成

```api/custompermissions.py
from rest_framework import permissions

class OwnerPermission(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.owner.id == request.user.id
```
- permissionsをimportし、カスタムパーミッションクラスを作成する
- 標準で提供されている`has_object_permission`をオーバーライドしている
- `SAFE_METHOD`(いわゆるgetメソッドに当たる)が来た場合は無条件でTrueを返す
- タスクのオーナーとログインしているユーザーのidが一致している場合のみタスクの変更を許可する

```views.py
#省略
from django.contrib.auth.models import User
#以下の1行を追加
from . import custompermissions

#省略

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    #以下の1行を追加
    permission_classes = (permissions.IsAuthenticated, custompermissions.OwnerPermission,)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def partial_update(self, request, *args, **kwargs):
        response = {'message': 'PATCH method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)
```
TaskViewSetでcustompermissionを使用したいので上記の1行を追加

##viewとurlを紐付ける
```api/urls.py
from django.urls import path, include
from rest_framework import routers
#以下の1行を追加
from .views import TaskViewSet, CategoryViewSet, CreateUserView, ListUserView, LoginUserView, ProfileViewSet

router = routers.DefaultRouter()
#以下の3行を追加
router.register('category', CategoryViewSet)
router.register('tasks', TaskViewSet)
router.register('profile', ProfileViewSet)

urlpatterns = [
    #以下の3行を追加(create, users, loginuser)
    path('create/', CreateUserView.as_view(), name='create'),
    path('users/', ListUserView.as_view(), name='users'),
    path('loginuser', LoginUserView.as_view(), name='loginuser'),
    path('', include(router.urls)),
]
```
- **ModelViewSet**を継承したViewはurls.pyの**router**に、**generics**の方は**urlpatterns**に追加する様に決まっている
- genericsを継承したviewは末尾に`as_view()`を追加する必要がある

##Postmanを使用して、テストする
http://127.0.0.1:8000/api/create/
bodyのform-dataでkeyをusername, passwordに指定し、valueを任意の値で、postメソットで上記のURLにリクエストを送信します。
![postman1](https://user-images.githubusercontent.com/46844364/122496496-d709a400-d026-11eb-87ab-f5c0bd71b642.png)
ユーザーの作成が成功すると作成されたユーザーのオブジェクトがjsonとして返って来ます。

同様にjwtのアクセストークンも返ってくるか検証します。
![postman2](https://user-images.githubusercontent.com/46844364/122574928-25508e80-d08b-11eb-9ec1-86d64f49ca89.png)
アクセストークンが返ってきたら無事成功です。
上記の画像の"access"キーに対するvalueをModHeaderにコピぺして、認証が通って入れば正常にアクセスができることを検証します。
![postman3](https://user-images.githubusercontent.com/46844364/122657579-c7fa3180-d19f-11eb-9cf6-88d6354d3122.png)

ModHeaderを有効にした状態で下記のurlなどにアクセスし、CRUDが正常であることを確認してください。
http://127.0.0.1:8000/api/loginuser/
http://127.0.0.1:8000/api/tasks/


#DRFのデプロイについて

##デプロイ用ライブラリの導入
```terminal
$ pip install django-environ
$ pip instlal dj-database-url
```
- 環境変数自体は.env（jira_api直下に新規作成する必要がある）に記述し、django-environは環境変数を簡単に扱える様にする
- dj-dabase-urlはデーターベースを簡単に扱える様にする

```settings.py
import os
#下記の3行を追加
import environ
env = environ.Env()
env.read_env(os.path.join(BASE_DIR, '.env'))
#以下省略

#以下に編集
SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')
DATABASES = {
    'default': env.db(),
}
```
.envファイルの読み込ませるためにsettings.pyに編集


```..env
SECRET_KEY=settings.pyで設定されていたシングルクォートの中身
DEBUG=False
DATABASE_URL=sqlite:///db.sqlite3
```
```terminal
$ pip freeze > requirements-dev.txt
```
- 開発時にimportしたライブラリを`pip freeze > requirements-dev.txt`コマンドで読み取って依存関係を吐き出す

```requirements.txt
-r requirements-dev.txt
gunicorn
psycopg2
```
- 本番環境用にrequirements.txtを作成し、`requirements-dev.txt`を読み込むのと、本番環境でgunicornとPostgreSQLを使用するので上記の2行を追加


- djangoには複数のstaticファイル(adminダッシュボードなど)があり、　djangoアプリのデプロイ時にはcollect staticコマンドを実行して、散らばったstaticファイルを一つのstaticフォルダ(新規作成する必要がある)に統合する必要があるのでstaticディレクトリを作成する

###ubuntuに接続する
```..ssh/config
Host *
  ServerAliveInterval 60
  TCPKeepAlive yes
  IPQoS=throughput
```
上記の設定はubuntuに接続時に強制ログアウトになるのを防ぐ

```terminal
$ sudo apt-get update
$ sudo apt-get install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx
$ sudo -u postgres psql
postgres=# CREATE DATABASE jiradb;
postgres=# CREATE USER jira WITH PASSWORD 'hogehoge';
postgres=# ALTER ROLE jira SET client_encoding TO 'uff8';
postgres=# ALTER ROLE jira SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE jira SET timezone TO 'UTC+9';
postgres=# GRANT ALL PRIVILEGES ON DATABASE jiradb TO jira;
postgres=# \q
$ sudo -H pip3 install --upgrade pip
$ sudo -H pip3 install virtualenv
$ virtualenv jiraec2
$ source jiraec2/bin/activate
(jiraec2) $ git clone https://から始まるレポジトリ
(jiraec2) $ cd jira_api
(jiraec2) ~/jira_api $pip install -r requirements.txt
```
- 必要なライブラリ一式をインストールした後に、postgresqlのデータベースの作成
- ユーザーを作成したのちにutf8に変更
- postgresqlはトランザクション分岐レベルが'read_committed'に決まっている
- データベースの全ての権限をユーザー(上記ではjira)に付与するした後に、postgresqlを終了し、pipをインストール
- pip installを使ってrequirementsに記載されているモジュールを一気にインストールする

```..env
SECRET_KEY=#-=*+7=$2sn@$#+&f%6ywx4t@m=$5($z4$r%vhy0=1_ai)r@(7
DEBUG=False
DATABASE_URL=postgres://jira:hogehoge@localhost:/jiradb
ALLOWED_HOSTS=(ec2インスタンスのパブリックIPアドレス)
```
- dj-dabaseが解釈してくれる、`DATABASE_URL=postgres://(ユーザー名):（パスワード）@localhost:/（データベース名）`にする

- manage.pyと同じ階層に,　ubuntu用の環境変数を管理するenvファイルを作成する

```jira_api/settings.py
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
```
- settings.pyの`ALLOWED_HOSTS`を先ほど定義した環境変数を読み込む様に編集する

```terminal
$ cd
$ sudo -u postgres psql
postgres=# CREATE EXTENSION "uuid-ossp";
postgres=# \q
```
- uuidに対する拡張機能を作成

```terminal
$ cd jira_api
$ python3 manage.py migrate
$ python3 manage.py collectstatic
$ python3 manage.py createsuperuser
```
- 本番環境用にpostgresqlのデーターベースを作成したので、models.pyのデータベース構造を再度展開するためにmigrateする必要がある
- プロジェクト内に散らばったstaticファイルをstaticフォルダに統合する

###gunicornの設定
```terminal
$ sudo vi /etc/systemd/system/gunicorn.service
```

```gunicorn.service
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/jira_api/

ExecStart=/home/ubuntu/jiraec2/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/ubuntu/jira_api/jira_api.sock jira_api.wsgi:application

[Install]
WantedBy=multi-user.target
```
- WorkingDirectoryは`[/ から Django Project Root までパス]/[Django Project Root]`
- ExecStartは`[/ から gunicorn コマンドまでのパス]/gunicorn --access-logfile - --workers 3 --bind unix:[Django Project Name].wsgi:application`
- gunicornの設定ファイルは[公式ドキュメント](https://ai-can-fly.hateblo.jp/entry/gunicorn-install-and-settings)を参考にしております。

###nginxの設定
```terminal
$ sudo vi /etc/nginx/sites-available/jira_api
```

```/etc/nginx/sites-available/jira_api
server {
        listen 80;
        server_name #ec2のパブリックIP;

        location = /favicon.ico {access_log off; log_not_found off;}
        location /static/ {
                root /home/ubuntu/jira_api;
        }
        location / {
                include proxy_params;
                proxy_pass http://unix:/home/ubuntu/jira_api/jira_api.sock;
        }
}
```

```terminal
$ sudo ln -s /etc/nginx/sites-available/jira_api /etc/nginx/sites-enabled/
$ sudo systemctl restart nginx
$ sudo ufw allow 'Nginx Full'
```
- 上記で作成した設定ファイルに対してシンボリックリンクを作成する（こうすることによって実態のファイルを削除しなくてもリンクを切ることで、新しくリンクさせることができる）
- 80ポートを解放するために`sudo ufw allow 'Nginx Full'`を実行
- ec2コンソールに戻り、先ほど作成したセキュリティーグループのインバウンドルールに`httpタイプ`で`0.0.0.0/0ソース`を追加した後にgunicornを起動

```terminal
$ sudo systemctl restart gunicorn
$ sudo systemctl enable gunicorn
```

#バックエンドをec2, フロントエンドをfirebaseにデプロイする
バックエンド側もhttpsでのアクセスを許可しないとMixedContentでエラーになるため
ec2にロードバランサーにてhttpのアクセスをhttpsに変換する設定にする

1. ec2インスタンスを再起動するたびに、パブリックIPが変わってしまうのでElastic IPを使用して固定化する
2. 独自ドメインを取得し、Route53で取得したNSレコードをレジストラ(お名前.com)に登録
3. ACMにてssl証明書を取得し、CNAMEレコードを登録する（ssl証明取得時の画面のボタン）
4. ロードバランサーを作成し、httpsリスナーを追加する
5. httpリスナーを編集し、全てのアクセスをhttpsに変換する設定に変更
6. セキュリティーグループのインバウンドにhttpsを追加する
7. Route53にてルーティング先をロードバランサーにした独自ドメイン（Aタイプ）を登録する
8. nginxの設定ファイルのserver_nameをドメイン名に変更
9. CORS_ORIGIN_WHITELIST（バックエンド）にfirebaseのURL、　React側の.envに独自ドメイン名を追加(両方https)

Route53とACMについての[参考記事](https://qiita.com/shu1124/items/3e4442fe95a91649de01)

#tips
- gitignoreに関して(https://note.com/masato1230/n/na63ac4e7ccdd)
- simplejwtはjson web token
- djoserは認証関係で使用する
- Pillowは画像関係
- TypeScriptの場合、srcはstringもしくはundefinedしか受け付けない仕様になっている
- [ts(17004）時の対応方法](https://qiita.com/Neitworks/items/f8db28770761cbf33262)
- [ElasticIP](https://qiita.com/Jerid/items/d5dd3a29ed9a0e374493)