必要技術 - tmuramat081/42_ft_transcendence GitHub Wiki

必要技術

全体

https://zenn.dev/mfunyu/books/ft_transcendence/viewer/05-nest4 https://note.com/syamashi/n/n6b1f60d1c741 https://github.com/syamaguc/ft_transcendence

フロントエンドのデータの永続化

Redux:
ReduxはReactアプリケーションで最も広く使用される状態管理ライブラリの一つです。大規模なアプリケーションや複雑な状態管理が必要な場合に特に適しています。Reduxは中央集権的なストアを持ち、アプリケーションの状態を一元的に管理します。また、タイムトラベルデバッギングや状態変更に対する厳格な制御などの利点があります。

useContext:
Reactの useContext フックは、コンポーネントツリーを通じてデータを伝達するための手段を提供します。これにより、状態をグローバルに共有することができ、プロップドリリング(親コンポーネントから子コンポーネントへのプロップスの伝達)を回避できます。useContext はReduxほど複雑ではなく、小〜中規模のアプリケーションに適しています。

useSWR:
useSWRは主にデータフェッチングのためのReactフックです。SWR(stale-while-revalidate)は、効率的なキャッシング、再検証、データの同期を提供します。API呼び出しなどの非同期データ操作に適しており、データをフェッチし、ストアにキャッシュし、UIを最新の状態に保つことができます。Reduxや useContext とは異なり、主にデータ取得とキャッシングに特化しています。

認証トークンをヘッダーで送信

https://blog.flatt.tech/entry/auth0_access_token https://zenn.dev/nameless_sn/articles/the_best_practice_of_jwt https://zenn.dev/simsim/articles/3f3e043dd750e8 https://logmi.jp/tech/articles/324349 https://qiita.com/some-nyan/items/d51c50de5f0663cf3bea

データベース

https://qiita.com/H-A-L/items/fe8cb0e0ee0041ff3ceb

フロントエンド

Next.js

- URL
- https://nextjs.org/
- https://ja.wikipedia.org/wiki/Next.js

- ReactとNextJSの違い
- React フレームワーク。Next.js は React と度々比較されますが、両者の 1 番の違いは、サーバ機能の有無です。 Next.js にはサーバ機能を有しており、単体で Web アプリを動作させられますが、React にはないため、別でサーバを用意する必要があります。
https://kinsta.com/jp/blog/nextjs-vs-react/
https://qiita.com/hiroki-yama-1118/items/b3388c5dcb155e2e367d
https://coding-alive.jp/blog/p1482/

- 学習方法
- https://zenn.dev/ttani/articles/nextjs_basic_learning
- https://reffect.co.jp/react/next-js

- サーバー機能使わないからReactでも良いのか…?


Tailwind.js


バックエンド

Nest.js

- URL
- https://nestjs.com/

- 概要
バックエンドでもフロントエンドと同じTypeScriptが使える。
クリーンアーキテクチャっぽい?
https://baapuro.com/NestJS/one/


- 学習方法
- https://zenn.dev/azunasu/articles/28f797ce2405e7
- https://zenn.dev/mikakane/books/nestjs-tutorial/viewer/setup
- https://zenn.dev/higuchimakoto/articles/21c8420c4a612a
- https://qiita.com/NaokiHaba/items/957f43bda4c169d0b52b
- https://www.youtube.com/watch?v=7H6moINfeIA
- https://www.udemy.com/course/nestjs-t/

Go + フレームワーク

シンプルに記述できる。学習コスト低い。

- フレームワーク
https://flxy.jp/media/article/24477
1. Gin(ジン)
2. echo(エコー)


- main.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run()
}

Python + Django


DB ORM

TypeORMとPrismaは、Node.jsで使用される2つの人気のあるオブジェクトリレーショナルマッピング(ORM)ツールですが、それぞれにユニークな特徴があります。

アーキテクチャ:

TypeORM: 伝統的なORMフレームワークで、データベースとオブジェクト間のマッピングに重点を置いています。Active RecordとData Mapperの両方のパターンをサポートしています。
Prisma: PrismaはORMよりも少し異なるアプローチを採用しており、Prisma Schemaという独自のスキーマ定義言語を使用しています。Prismaはデータベースのスキーマとの連携を中心に構築されており、より現代的なツールチェーンを提供します。
データベースのサポート:

TypeORMとPrismaは、多くの一般的なSQLデータベースをサポートしていますが、具体的なサポート範囲には違いがあります。Prismaは新しいデータベースへの対応を迅速に行っている一方で、TypeORMはより成熟したデータベースに対して広範なサポートを提供しています。
クエリ構築:

TypeORM: より伝統的なSQLライクなクエリビルダを提供し、SQLに近い経験を提供します。
Prisma: Prismaのクエリ言語はJavaScriptのオブジェクト構文を使用し、よりモダンで宣言的なアプローチを採用しています。
パフォーマンス:

一般的に、PrismaはTypeORMよりも高速であると言われています。これは、Prismaが生成するクエリが最適化されており、ランタイムオーバーヘッドが少ないためです。
学習曲線:

Prismaは比較的新しく、直感的なAPIとツールセットを提供しているため、新しいユーザーにとって学びやすいかもしれません。一方、TypeORMはより伝統的なORMのアプローチを採用しており、ORMに慣れている開発者にとってはより親しみやすいかもしれません。
最終的に、選択はプロジェクトの要件、チームの経験、および好みに大きく依存します。どちらも強力なツールであり、特定のニーズに応じて適切な選択を行うことが重要です。

TypeORM

- 概要
https://docs.nestjs.com/recipes/sql-typeorm



- 参考
- https://www.udemy.com/course/nestjs-t/
- https://qiita.com/potato4d/items/64a1f518abdfe281ce01
- https://zenn.dev/engineerhikaru/articles/69eb781a7fb5e0
- https://zenn.dev/hasegawasatoshi/articles/8110ce79119dd0
- https://qiita.com/akasatana12345/items/b98fcee2643c91060999


typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { config } from 'dotenv';
import { join } from 'path';

config({ path: '.env' });

export const typeormConfig: TypeOrmModuleOptions = {
  type: 'mysql',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT),
  database: process.env.DB_DATABASE,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  entities: [join(__dirname, '../**/*.entity{.ts,.js}')],
  synchronize: true,
  logging: false,
};


users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDTO, UpdateUserDTO } from '../dto/user.dto';
import { UsersEntity } from '../entities/users.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(UsersEntity)
    private readonly usersRepository: Repository<UsersEntity>,
  ) {}

  async createOne(dto: CreateUserDTO): Promise<UsersEntity> {
    return await this.usersRepository.save(dto);
  }

  async readOne(id: string): Promise<UsersEntity> {
    return await this.usersRepository.findOneBy({ id: id });
  }

  async updateOne(user: UsersEntity, dto: UpdateUserDTO): Promise<UsersEntity> {
    return await this.usersRepository.save({ ...user, ...dto });
  }

  async deleteOne(user: UsersEntity): Promise<UsersEntity> {
    return await user.remove();
  }
}


Prisma

- 概要
https://docs.nestjs.com/recipes/prisma

- 参考
- https://qiita.com/kikikikimorimori/items/5d1098f6a51324ddaab4
- https://zenn.dev/nameless_sn/articles/nestjs_restapi_by_prisma



prisma/schema.prisma
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}
.env
DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"



src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User, Prisma } from '@prisma/client';

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  async user(
    id: number,
  ): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { id }
    });
  }

  async users(): Promise<User[]> {
    return this.prisma.user.findMany();
  }

  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    return this.prisma.user.create({
      data,
    });
  }
}

Login

OAUTH

https://www.passportjs.org/packages/passport-42/ https://github.com/ykoh42/42OAuth-NestJS https://github.com/topics/passport-42

  • 処理の流れ

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
)

var ClientId = "your_id"
var Secret = "your_secret"

type OauthTokenResponse struct {
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`
	ExpiresIn   int    `json:"expires_in"`
	Scope       string `json:"scope"`
	CreatedAt   int    `json:"created_at"`
}

func main() {
	http.HandleFunc("/", handleMain)
	http.HandleFunc("/login", handle42Login)
	http.HandleFunc("/api/auth/redirect", handle42Callback)
	log.Fatal(http.ListenAndServe(":3000", nil))
}

func handleMain(w http.ResponseWriter, r *http.Request) {
	var htmlIndex = `<html><body><a href="/login">42 Log In</a></body></html>`
	fmt.Fprintf(w, htmlIndex)
}

func handle42Login(w http.ResponseWriter, r *http.Request) {
	value := url.Values{}
	value.Set("grant_type", "authorization_code")
	value.Set("client_id", ClientId)
	value.Set("client_secret", Secret)
	value.Set("redirect_uri", "http://localhost:3000/api/auth/redirect")
	value.Set("response_type", "code")
	value.Set("scope", "public")
	value.Set("state", "qwerty")

	url := "https://api.intra.42.fr/oauth/authorize?" + value.Encode()

	fmt.Println(url)

	http.Redirect(w, r, url, http.StatusTemporaryRedirect)

	// req, err := http.NewRequest(http.MethodPost, "https://api.intra.42.fr/oauth/authorize", strings.NewReader(value.Encode()))
	// if err != nil {
	// 	panic(err)
	// }
	// res, err := http.DefaultClient.Do(req)
	// if err != nil {
	// 	panic(err)
	// }

	// req, err := http.NewRequest(http.MethodPost, "https://api.intra.42.fr/v2/oauth/token", strings.NewReader(value.Encode()))
	// if err != nil {
	// 	panic(err)
	// }

	// res, err := http.DefaultClient.Do(req)
	// if err != nil {
	// 	panic(err)
	// }

	// var oauthRes OauthTokenResponse
	// err = json.NewDecoder(res.Body).Decode(&oauthRes)
	// if err != nil {
	// 	panic(err)
	// }

	// fmt.Printf("%#v\n", oauthRes)
	//http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func handle42Callback(w http.ResponseWriter, r *http.Request) {
	// 認証コードを取得
	code := r.FormValue("code")
	fmt.Println(code)
	value := url.Values{}
	value.Set("grant_type", "authorization_code")
	value.Set("client_id", ClientId)
	value.Set("client_secret", Secret)
	value.Set("code", code)
	value.Set("redirect_uri", "http://localhost:3000/api/auth/redirect") //?

	req, err := http.NewRequest(http.MethodPost, "https://api.intra.42.fr/oauth/token", strings.NewReader(value.Encode()))
	if err != nil {
		panic(err)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}

	// body, err := io.ReadAll(res.Body)
	// if err != nil {
	// 	panic(err)
	// }
	// fmt.Printf("%s\n", body)

	var oauthRes OauthTokenResponse

	err = json.NewDecoder(res.Body).Decode(&oauthRes)
	if err != nil {
		panic(err)
	}

	// アクセストークンを取得
	fmt.Printf("%#v\n", oauthRes)

	header := http.Header{}
	header.Add("Authorization", "Bearer "+oauthRes.AccessToken)

	client := http.Client{}

	//headerを設定したclientを使ってリクエストを送る
	req2, err := http.NewRequest(http.MethodGet, "https://api.intra.42.fr/v2/me", nil)
	req2.Header = header
	res2, err := client.Do(req2)
	if err != nil {
		panic(err)
	}
	body2, err := io.ReadAll(res2.Body)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", body2)

	fmt.Fprintf(w, "Content: %s\n", body2)

}

2FA

https://viblo.asia/p/nodejs-express-%E3%81%A7%E3%81%AE%E4%BA%8C%E8%A6%81%E7%B4%A0%E8%AA%8D%E8%A8%BC2fa-qPoL7ybm4vk
https://qiita.com/yuyake0084/items/f12c60f7c38017d1b4e8
https://qiita.com/waaaaRapy/items/8549890bda4f8ad33963
https://qiita.com/yuyake0084/items/f12c60f7c38017d1b4e8
https://www.authenticatorapi.com/


dbに鍵を保存
nodejs版をnestjsでやってみる。 

Websocket

PingPongGame

Chat DM

キー操作後POST→サーバーからブロードキャストして送信

Document

Test

  • jest

Seedデータ

https://zenn.dev/fjsh/articles/nestjs-typeorm-seed

AppRouter

sample

https://zenn.dev/mfunyu/books/ft_transcendence/viewer/01-intro

https://zenn.dev/mikakane/books/nestjs-tutorial/viewer/todoapp

サービス構成

Controller
HTTPリクエストのハンドリング: コントローラは、クライアントからの HTTP リクエストを受け取り、適切なレスポンスを返す責任があります。
リクエストの検証: リクエストデータの初期検証を行い、適切なサービスメソッドにデータを渡します。
レスポンスの整形: サービスからのデータをクライアントに返す形式に整形します。
エラーハンドリング: HTTP リクエストの処理中に発生するエラーを捕捉し、適切な HTTP ステータスコードとエラーメッセージで応答します。
Service
ビジネスロジックの実装: アプリケーションの核となるビジネスロジックを実装します。
データの加工・変換: ビジネスロジックに基づいてデータを加工・変換し、必要な形式にします。
リポジトリとの連携: データベースや他のストレージへのアクセスをリポジトリを通じて行います。
サードパーティサービスとの統合: 外部APIやサービスとの連携を管理します。
Repository
データ永続化: データベースや他のデータストレージへのデータの保存、更新、削除を担当します。
データ検索: クエリを用いてデータベースからデータを検索し、取得します。
データアクセスの抽象化: データベースやデータストレージの詳細を抽象化し、ビジネスロジックから分離します。
データアクセスロジックのカプセル化: 具体的なデータベース操作をカプセル化し、ビジネスロジックから独立させます。
分離のポイント
コントローラはビジネスロジックを持たない: コントローラはリクエストとレスポンスの処理に集中し、ビジネスロジックはサービスに任せます。
サービスはデータアクセスロジックを持たない: サービスはビジネスルールに集中し、データの永続化や検索はリポジトリに任せます。
リポジトリはビジネスロジックを持たない: リポジトリはデータアクセスの詳細に集中し、ビジネスルールやアプリケーションロジックから独立しています。
これらの分離により、コードの再利用性、テストの容易さ、保守性、拡張性が向上し、アプリケーション全体の構造がよりクリーンで理解しやすくなります。

gurad と strategy


User
nest.js の guard と strategy とは何を指していますか?
ChatGPT
Nest.js における「Guard」と「Strategy」は、アプリケーションのセキュリティと認証の重要な部分です。それぞれについて簡単に説明します。

Guard(ガード)

Guard は Nest.js におけるルート保護のメカニズムです。
Guard は、特定のルートが処理される前に特定の条件が満たされているかどうかをチェックします。これにより、認証されていないユーザーが特定のルートにアクセスするのを防ぐことができます。
Guard は canActivate メソッドを実装したクラスです。このメソッドは、リクエストが処理される前に呼び出され、true または false を返します(または Promise を返し、非同期処理をサポートします)。
例えば、JWT(JSON Web Tokens)を使用してユーザーを認証する Guard を実装することができます。
Strategy(ストラテジー)

Strategy は、Passport.js という認証ミドルウェアを Nest.js で利用する際の方法や戦略を指します。
Passport は多様な認証メカニズムをサポートしており、これらは「ストラテジー」として知られています。例えば、passport-local、passport-jwt、passport-google-oauth などがあります。
各ストラテジーは特定の認証方式を実装しており、Nest.js ではこれらを利用して柔軟に認証処理を構築することができます。
ストラテジーは通常、Guard 内で使用され、リクエストの認証を担当します。
これらの機能を使用することで、Nest.js アプリケーションに効果的なセキュリティ層を追加し、エンドポイントのアクセス制御を精密に管理することが可能になります。

User
strategy はミドルウェアの設定と考えれば良いでしょうか?
ChatGPT
Strategy をミドルウェアの設定と考えることは一部正しいですが、より正確には、特定の認証方法を実装するための設定やロジックを提供するものと理解する方が適切です。Nest.js と Passport.js を組み合わせて使う場合の詳細を以下に説明します。

Strategy とは

Strategy は Passport.js の概念で、特定の認証方式(例:ユーザー名とパスワード、JWT、OAuth など)に基づいてユーザーを認証する方法を定義します。
各 Strategy は独自の認証ロジックを持ち、特定の認証方式に最適化されています。
ミドルウェアとの関係

Strategy はミドルウェアのように機能しますが、Passport の枠組み内で動作します。
リクエストが来た時、Passport は設定された Strategy を使ってユーザーの認証を試みます。
このプロセスは自動的に行われ、開発者は特定の認証戦略を選択して設定するだけです。
Nest.js における統合

Nest.js では、Strategy をカスタム Guard と組み合わせて使用することが一般的です。
Guard はリクエストがルートハンドラーに到達する前に認証を行う責任を持ち、その過程で指定された Strategy を利用します。
要するに、Strategy は認証の「方法」を定義し、その実装を Passport が担い、Nest.js ではこれを Guard を通じて適用します。これにより、アプリケーションの特定の部分へのアクセスを保護するために、柔軟かつ効果的な認証システムを構築することが可能です。

User
DataTransferObject とはなんですか?
ChatGPT
Data Transfer Object(DTO)は、データを一つのオブジェクトとしてグループ化するために使用されるデザインパターンの一つです。DTO は主に、ネットワークを介してデータを転送する際に使用され、複数のデータ項目を一つのオブジェクトにまとめて、転送や処理を簡単にします。以下は DTO の特徴と用途についての詳細です:

データのカプセル化

DTO は、関連するデータを一つのオブジェクトにカプセル化します。これにより、複数のデータ項目を一緒に扱いやすくなります。
ネットワーク効率の向上

ネットワークを介してデータを転送する際、複数の小さなデータ項目を個別に送信するよりも、一つの大きなオブジェクトとして送信する方が効率的です。DTO はこの目的のためによく使用されます。
データの整合性保持

DTO を使用することで、複数のデータソースからのデータを一元化し、整合性を保ちやすくなります。
アプリケーション層の分離

DTO は、アプリケーションの異なる層(例えば、ビジネスロジック層とデータアクセス層)間のデータのやり取りを容易にします。これにより、層間の結合度を低く保ち、アプリケーションの保守性を向上させます。
Nest.js における使用

Nest.js のようなモダンなフレームワークでは、DTO はクライアントからサーバーへのリクエストデータの構造を定義するために使用されます。例えば、クライアントがフォームデータを送信する際に、そのデータを DTO で受け取り、処理することができます。
DTO は、データの転送と処理をより効率的かつ整理された方法で行うための、シンプルで強力な方法です。


⚠️ **GitHub.com Fallback** ⚠️