Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/backend/api/v1 #17

Merged
merged 28 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e08a7d4
Merge branch 'feat/backend/api/seed' into feat/backend/api/v1
KinjiKawaguchi Mar 23, 2024
385c3f8
feat: Inverseエッジを追加
KinjiKawaguchi Mar 23, 2024
015909b
WIP: api作った
KinjiKawaguchi Mar 23, 2024
f8236ff
Merge remote-tracking branch 'origin/HEAD' into feat/backend/api/v1
KinjiKawaguchi Mar 23, 2024
34551a0
chore: 依存関係を更新しました: go-chi/chi/v5をv5.0.12に更新しました
KinjiKawaguchi Mar 23, 2024
d02645b
refactor: ヘルスチェック関連のコードの位置を変更
KinjiKawaguchi Mar 23, 2024
62b04c9
feat: ユーザー情報取得APIのパラメータを変更し、スコアランキングAPIを追加
KinjiKawaguchi Mar 24, 2024
cd7a3b3
docs: ディレクトリ戦略に関するドキュメントを作成
KinjiKawaguchi Mar 24, 2024
5c36ba3
docs: 型の変更
KinjiKawaguchi Mar 24, 2024
82f7a32
feat: モデルファイルを追加
KinjiKawaguchi Mar 24, 2024
e88b7ec
feat: openAPIの仕様に合わせてAPIを調整
KinjiKawaguchi Mar 24, 2024
6638bdd
fix: APIハンドラのエラーハンドリングを追加
KinjiKawaguchi Mar 24, 2024
9cf1eed
docs: openAPIを改善
KinjiKawaguchi Mar 25, 2024
2f1cb5a
feat: ランキングの取得件数のクエリパラメータを追加
KinjiKawaguchi Mar 25, 2024
ac3b5ad
feat: GetScoresRanking関数を更新
KinjiKawaguchi Mar 26, 2024
1ba5d50
docs: Add descriptions for user_id, keystrokes, and accuracy fields i…
KinjiKawaguchi Mar 26, 2024
e4ae075
fix: スコアのランキングクエリを修正
KinjiKawaguchi Mar 27, 2024
d02e5c0
fix: ルーティングするの忘れてた笑
KinjiKawaguchi Mar 27, 2024
2ff47ef
feat: corsを追加
KinjiKawaguchi Mar 27, 2024
3ded660
docs: ヘルスチェックを追加
KinjiKawaguchi Mar 27, 2024
75569eb
fix: entCleintが渡せていなかった
KinjiKawaguchi Mar 27, 2024
2a521cc
docs: ランキングの開始位置の説明を修正
KinjiKawaguchi Mar 27, 2024
407c9d3
feat: scoreエンティティにそれぞれの指標がUserにとって最大のものかどうかを判定するFieldを追加
KinjiKawaguchi Mar 27, 2024
9b7fd69
feat: 伴いseedプログラムを変更
KinjiKawaguchi Mar 27, 2024
381c6f8
fix: スコア周りのAPIを修正
KinjiKawaguchi Mar 27, 2024
2ae3972
feat: User取得がそのユーザのすべてのスコアを返すように変更
KinjiKawaguchi Mar 27, 2024
fc1992b
docs: tags と operationId を追加
h-takeyeah Mar 27, 2024
39a9744
openapi.yamlのURLを仮置き
KinjiKawaguchi Mar 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions typing-server/api/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"time"

"github.com/go-sql-driver/mysql"
"github.com/su-its/typing/typing-server/api/handler"
"github.com/su-its/typing/typing-server/api/router"
"github.com/su-its/typing/typing-server/domain/repository/ent"
"github.com/su-its/typing/typing-server/domain/repository/ent/user"
)
Expand Down Expand Up @@ -54,6 +56,7 @@ func main() {
return
}
defer entClient.Close()
handler.SetEntClient(entClient)
logger.Info("ent client is opened")

// スキーマの作成
Expand All @@ -65,7 +68,7 @@ func main() {

// シードデータの挿入
if *seedFlag {
if err := seedData(context.Background(), entClient); err != nil {
if err := seedData(context.Background(), entClient, logger); err != nil {
logger.Error("failed to seed data: %v", err)
return
}
Expand All @@ -86,7 +89,14 @@ func main() {
go func() {
defer wg.Done() // 関数終了時にWaitGroupをデクリメント
// サーバーの設定
server := &http.Server{Addr: ":8080"}
// ルーティングの設定
r := router.SetupRouter()

// サーバーの設定
server := &http.Server{
Addr: ":8080",
Handler: r,
}
// 非同期でサーバーを開始
go func() {
logger.Info("server is running at Addr :8080")
Expand Down Expand Up @@ -115,42 +125,77 @@ func main() {
logger.Info("server exited")
}

// TODO: 本番環境では削除する
func seedData(ctx context.Context, client *ent.Client) error {
func seedData(ctx context.Context, client *ent.Client, logger *slog.Logger) error {
// シードデータの作成
for i := 0; i < 10; i++ {
isAlreadySeeded, err := client.User.Query().Where(user.StudentNumber(fmt.Sprintf("user%d", i+1))).Exist(ctx)
studentNumber := fmt.Sprintf("user%d", i+1)
handleName := fmt.Sprintf("handle%d", i+1)

isAlreadySeeded, err := client.User.Query().Where(user.StudentNumber(studentNumber)).Exist(ctx)
if err != nil {
return err
}
if isAlreadySeeded {
logger.Info("User with student number already seeded, skipping", slog.String("studentNumber", studentNumber))
continue
}

u, err := client.User.Create().
SetStudentNumber(fmt.Sprintf("user%d", i+1)).
SetHandleName(fmt.Sprintf("handle%d", i+1)).
SetStudentNumber(studentNumber).
SetHandleName(handleName).
Save(ctx)
if err != nil {
panic(err)
}

logger.Info("Created user", slog.String("studentNumber", studentNumber), slog.String("handleName", handleName))

var maxKeystrokesScore, maxAccuracyScore *ent.Score

for j := 0; j < 5; j++ {
score, err := client.Score.Create().
SetKeystrokes(rand.Intn(200)).
SetAccuracy(rand.Float64()).
keystrokes := rand.Intn(200) + 100
accuracy := rand.Float64()

s, err := client.Score.Create().
SetKeystrokes(keystrokes).
SetAccuracy(accuracy).
SetCreatedAt(time.Now()).
SetUser(u).
Save(ctx)
if err != nil {
panic(err)
}

_, err = client.User.UpdateOne(u).
AddScores(score).Save(ctx)
logger.Info("Created score", slog.Int("keystrokes", keystrokes), slog.Float64("accuracy", accuracy), slog.String("studentNumber", studentNumber))

if s.Keystrokes < 120 || s.Accuracy < 0.95 {
continue
}

if maxKeystrokesScore == nil || s.Keystrokes > maxKeystrokesScore.Keystrokes {
maxKeystrokesScore = s
}
if maxAccuracyScore == nil || s.Accuracy > maxAccuracyScore.Accuracy {
maxAccuracyScore = s
}
}

// 最大のKeystrokesスコアと最大のAccuracyスコアのフラグを設定
if maxKeystrokesScore != nil {
err = maxKeystrokesScore.Update().SetIsMaxKeystrokes(true).Exec(ctx)
if err != nil {
panic(err)
return err
}
logger.Info("Set is_max_keystrokes flag", slog.Int("keystrokes", maxKeystrokesScore.Keystrokes), slog.String("studentNumber", studentNumber))
}
if maxAccuracyScore != nil {
err = maxAccuracyScore.Update().SetIsMaxAccuracy(true).Exec(ctx)
if err != nil {
return err
}
logger.Info("Set is_max_accuracy flag", slog.Float64("accuracy", maxAccuracyScore.Accuracy), slog.String("studentNumber", studentNumber))
}
}

return nil
}
20 changes: 0 additions & 20 deletions typing-server/api/controller/system/health.go

This file was deleted.

14 changes: 14 additions & 0 deletions typing-server/api/handler/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package handler

import (
"log/slog"
"net/http"
)

func HealthCheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("API is running"))
if err != nil {
slog.Error("failed to write response: %v", err)
}
}
9 changes: 9 additions & 0 deletions typing-server/api/handler/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package handler

import "github.com/su-its/typing/typing-server/domain/repository/ent"

var entClient *ent.Client

func SetEntClient(client *ent.Client) {
entClient = client
}
76 changes: 76 additions & 0 deletions typing-server/api/handler/score.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package handler

import (
"encoding/json"
"net/http"
"strconv"

"github.com/google/uuid"
"github.com/su-its/typing/typing-server/api/service"
)

func GetScoresRanking(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

sortBy := r.URL.Query().Get("sort_by")
if sortBy == "" {
sortBy = "keystrokes"
}

startStr := r.URL.Query().Get("start")
start, err := strconv.Atoi(startStr)
if err != nil {
start = 1
}

limitStr := r.URL.Query().Get("limit")
limit, err := strconv.Atoi(limitStr)
if err != nil {
limit = 10
}

rankings, err := service.GetScoresRanking(ctx, entClient, sortBy, start, limit)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(rankings)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

func PostScore(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

userIDStr := r.URL.Query().Get("user_id")
userID, err := uuid.Parse(userIDStr)
if err != nil {
http.Error(w, "Invalid user_id", http.StatusBadRequest)
return
}

keystrokesStr := r.URL.Query().Get("keystrokes")
keystrokes, err := strconv.Atoi(keystrokesStr)
if err != nil {
http.Error(w, "Invalid keystrokes", http.StatusBadRequest)
return
}

accuracyStr := r.URL.Query().Get("accuracy")
accuracy, err := strconv.ParseFloat(accuracyStr, 64)
if err != nil {
http.Error(w, "Invalid accuracy", http.StatusBadRequest)
return
}

if err := service.CreateScore(ctx, entClient, userID, keystrokes, accuracy); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusCreated)
}
31 changes: 31 additions & 0 deletions typing-server/api/handler/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package handler

import (
"encoding/json"
"net/http"

"github.com/su-its/typing/typing-server/api/service"
)

func GetUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

studentNumber := r.URL.Query().Get("student_number")
if studentNumber == "" {
http.Error(w, "student_number is required", http.StatusBadRequest)
return
}

user, err := service.GetUserByStudentNumber(ctx, entClient, studentNumber)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
11 changes: 0 additions & 11 deletions typing-server/api/presenter/server.go

This file was deleted.

Loading
Loading