diff --git a/app/report/models/Report.go b/app/report/models/Report.go index 12a14d5..cb63cc5 100644 --- a/app/report/models/Report.go +++ b/app/report/models/Report.go @@ -3,11 +3,16 @@ package models import "time" type Report struct { - ID uint `gorm:"primaryKey"` - UserID uint - AlertCount int - AnalysisTime int - Type string - Predict string - CreatedAt time.Time `gorm:"autoCreateTime"` + ID uint `gorm:"primaryKey"` + UserID uint + AlertCount int + AnalysisTime int + Type string + Predict string + NormalRatio string + Score string + NeckAngles string + Distances string + StatusFrequencies string + CreatedAt time.Time `gorm:"autoCreateTime"` } diff --git a/app/report/repositories/report_repository_test.go b/app/report/repositories/report_repository_test.go index e876324..79424eb 100644 --- a/app/report/repositories/report_repository_test.go +++ b/app/report/repositories/report_repository_test.go @@ -36,18 +36,23 @@ func TestReportrRepository_Save(t *testing.T) { // Create sample report for the test report := models.Report{ - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), } // Set up expectations for the mock DB to return the sample report mock.ExpectBegin() mock.ExpectExec("INSERT INTO `reports`"). - WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.CreatedAt). + WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.Score, report.NormalRatio, report.NeckAngles, report.Distances, report.StatusFrequencies, report.CreatedAt). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -64,8 +69,14 @@ func TestReportrRepository_Save(t *testing.T) { assert.Equal(t, report.UserID, createdReport.UserID) assert.Equal(t, report.AlertCount, createdReport.AlertCount) assert.Equal(t, report.AnalysisTime, createdReport.AnalysisTime) - assert.Equal(t, report.Predict, createdReport.Predict) assert.Equal(t, report.Type, createdReport.Type) + assert.Equal(t, report.Predict, createdReport.Predict) + assert.Equal(t, report.Score, createdReport.Score) + assert.Equal(t, report.NormalRatio, createdReport.NormalRatio) + assert.Equal(t, report.NeckAngles, createdReport.NeckAngles) + assert.Equal(t, report.Distances, createdReport.Distances) + assert.Equal(t, report.StatusFrequencies, createdReport.StatusFrequencies) + assert.Equal(t, report.CreatedAt, createdReport.CreatedAt) } func TestReportRepository_Save_Error(t *testing.T) { @@ -92,18 +103,23 @@ func TestReportRepository_Save_Error(t *testing.T) { // Create sample report for the test report := models.Report{ - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), } // Set up expectations for the mock DB to return the sample report mock.ExpectBegin() mock.ExpectExec("INSERT INTO `reports`"). - WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.CreatedAt). + WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.Score, report.NormalRatio, report.NeckAngles, report.Distances, report.StatusFrequencies, report.CreatedAt). WillReturnError(err) mock.ExpectRollback() @@ -141,18 +157,23 @@ func TestReportRepository_FindByUserID(t *testing.T) { // Create sample report for the test report := models.Report{ - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), } // Set up expectations for the mock DB to return the sample report mock.ExpectBegin() mock.ExpectExec("INSERT INTO `reports`"). - WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.CreatedAt). + WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.Score, report.NormalRatio, report.NeckAngles, report.Distances, report.StatusFrequencies, report.CreatedAt). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -165,8 +186,8 @@ func TestReportRepository_FindByUserID(t *testing.T) { // Set up expectations for the mock DB to return the sample report mock.ExpectQuery("SELECT \\* FROM `reports` WHERE user_id = \\?"). WithArgs(report.UserID). - WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "created_at"}). - AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.CreatedAt)) + WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "score", "normal_ratio", "neck_angles", "distances", "status_frequencies", "created_at"}). + AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.Score, savedReport.NormalRatio, savedReport.NeckAngles, savedReport.Distances, savedReport.StatusFrequencies, savedReport.CreatedAt)) // Call the method under test reports, err := reportRepository.FindByUserID(report.UserID) @@ -181,8 +202,14 @@ func TestReportRepository_FindByUserID(t *testing.T) { assert.Equal(t, savedReport.UserID, reports[0].UserID) assert.Equal(t, savedReport.AlertCount, reports[0].AlertCount) assert.Equal(t, savedReport.AnalysisTime, reports[0].AnalysisTime) - assert.Equal(t, savedReport.Predict, reports[0].Predict) assert.Equal(t, savedReport.Type, reports[0].Type) + assert.Equal(t, savedReport.Predict, reports[0].Predict) + assert.Equal(t, savedReport.Score, reports[0].Score) + assert.Equal(t, savedReport.NormalRatio, reports[0].NormalRatio) + assert.Equal(t, savedReport.NeckAngles, reports[0].NeckAngles) + assert.Equal(t, savedReport.Distances, reports[0].Distances) + assert.Equal(t, savedReport.StatusFrequencies, reports[0].StatusFrequencies) + assert.Equal(t, savedReport.CreatedAt, reports[0].CreatedAt) } func TestReportRepository_FindByUserID_Error(t *testing.T) { @@ -245,18 +272,23 @@ func TestReportRepository_FindById(t *testing.T) { // Create sample report for the test report := models.Report{ - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), } // Set up expectations for the mock DB to return the sample report mock.ExpectBegin() mock.ExpectExec("INSERT INTO `reports`"). - WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.CreatedAt). + WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.Score, report.NormalRatio, report.NeckAngles, report.Distances, report.StatusFrequencies, report.CreatedAt). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -269,8 +301,8 @@ func TestReportRepository_FindById(t *testing.T) { // Set up expectations for the mock DB to return the sample report mock.ExpectQuery("SELECT \\* FROM `reports` WHERE id = \\?"). WithArgs(savedReport.ID). - WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "created_at"}). - AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.CreatedAt)) + WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "score", "normal_ratio", "neck_angles", "distances", "status_frequencies", "created_at"}). + AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.Score, savedReport.NormalRatio, savedReport.NeckAngles, savedReport.Distances, savedReport.StatusFrequencies, savedReport.CreatedAt)) // Call the method under test report, err = reportRepository.FindById(savedReport.ID) @@ -285,8 +317,14 @@ func TestReportRepository_FindById(t *testing.T) { assert.Equal(t, savedReport.UserID, report.UserID) assert.Equal(t, savedReport.AlertCount, report.AlertCount) assert.Equal(t, savedReport.AnalysisTime, report.AnalysisTime) - assert.Equal(t, savedReport.Predict, report.Predict) assert.Equal(t, savedReport.Type, report.Type) + assert.Equal(t, savedReport.Predict, report.Predict) + assert.Equal(t, savedReport.Score, report.Score) + assert.Equal(t, savedReport.NormalRatio, report.NormalRatio) + assert.Equal(t, savedReport.NeckAngles, report.NeckAngles) + assert.Equal(t, savedReport.Distances, report.Distances) + assert.Equal(t, savedReport.StatusFrequencies, report.StatusFrequencies) + assert.Equal(t, savedReport.CreatedAt, report.CreatedAt) } func TestReportRepository_FindById_Error(t *testing.T) { @@ -348,18 +386,23 @@ func TestReportRepository_FindByYearAndMonth(t *testing.T) { // Create sample report for the test report := models.Report{ - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), } // Set up expectations for the mock DB to return the sample report mock.ExpectBegin() mock.ExpectExec("INSERT INTO `reports`"). - WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.CreatedAt). + WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.Score, report.NormalRatio, report.NeckAngles, report.Distances, report.StatusFrequencies, report.CreatedAt). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -375,8 +418,8 @@ func TestReportRepository_FindByYearAndMonth(t *testing.T) { // Set up expectations for the mock DB to return the sample report mock.ExpectQuery("SELECT \\* FROM `reports` WHERE user_id = \\? AND DATE_FORMAT\\(created_at, '%Y%m'\\) = \\?"). WithArgs(savedReport.UserID, yearAndMonth). - WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "created_at"}). - AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.CreatedAt)) + WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "score", "normal_ratio", "neck_angles", "distances", "status_frequencies", "created_at"}). + AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.Score, savedReport.NormalRatio, savedReport.NeckAngles, savedReport.Distances, savedReport.StatusFrequencies, savedReport.CreatedAt)) // Call the method under test reports, err := reportRepository.FindByYearAndMonth(savedReport.UserID, yearAndMonth) @@ -391,8 +434,14 @@ func TestReportRepository_FindByYearAndMonth(t *testing.T) { assert.Equal(t, savedReport.UserID, reports[0].UserID) assert.Equal(t, savedReport.AlertCount, reports[0].AlertCount) assert.Equal(t, savedReport.AnalysisTime, reports[0].AnalysisTime) - assert.Equal(t, savedReport.Predict, reports[0].Predict) assert.Equal(t, savedReport.Type, reports[0].Type) + assert.Equal(t, savedReport.Predict, reports[0].Predict) + assert.Equal(t, savedReport.Score, reports[0].Score) + assert.Equal(t, savedReport.NormalRatio, reports[0].NormalRatio) + assert.Equal(t, savedReport.NeckAngles, reports[0].NeckAngles) + assert.Equal(t, savedReport.Distances, reports[0].Distances) + assert.Equal(t, savedReport.StatusFrequencies, reports[0].StatusFrequencies) + assert.Equal(t, savedReport.CreatedAt, reports[0].CreatedAt) } func TestReportRepository_FindByYearAndMonth_Error(t *testing.T) { @@ -457,18 +506,23 @@ func TestReportRepository_FindAll(t *testing.T) { // Create sample report for the test report := models.Report{ - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), } // Set up expectations for the mock DB to return the sample report mock.ExpectBegin() mock.ExpectExec("INSERT INTO `reports`"). - WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.CreatedAt). + WithArgs(report.UserID, report.AlertCount, report.AnalysisTime, report.Type, report.Predict, report.Score, report.NormalRatio, report.NeckAngles, report.Distances, report.StatusFrequencies, report.CreatedAt). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -480,8 +534,8 @@ func TestReportRepository_FindAll(t *testing.T) { // Set up expectations for the mock DB to return the sample report mock.ExpectQuery("SELECT \\* FROM `reports`"). - WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "created_at"}). - AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.CreatedAt)) + WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "alert_count", "analysis_time", "type", "predict", "score", "normal_ratio", "neck_angles", "distances", "status_frequencies", "created_at"}). + AddRow(savedReport.ID, savedReport.UserID, savedReport.AlertCount, savedReport.AnalysisTime, savedReport.Type, savedReport.Predict, savedReport.Score, savedReport.NormalRatio, savedReport.NeckAngles, savedReport.Distances, savedReport.StatusFrequencies, savedReport.CreatedAt)) // Call the method under test reports, err := reportRepository.FindAll() @@ -496,8 +550,14 @@ func TestReportRepository_FindAll(t *testing.T) { assert.Equal(t, savedReport.UserID, reports[0].UserID) assert.Equal(t, savedReport.AlertCount, reports[0].AlertCount) assert.Equal(t, savedReport.AnalysisTime, reports[0].AnalysisTime) - assert.Equal(t, savedReport.Predict, reports[0].Predict) assert.Equal(t, savedReport.Type, reports[0].Type) + assert.Equal(t, savedReport.Predict, reports[0].Predict) + assert.Equal(t, savedReport.Score, reports[0].Score) + assert.Equal(t, savedReport.NormalRatio, reports[0].NormalRatio) + assert.Equal(t, savedReport.NeckAngles, reports[0].NeckAngles) + assert.Equal(t, savedReport.Distances, reports[0].Distances) + assert.Equal(t, savedReport.StatusFrequencies, reports[0].StatusFrequencies) + assert.Equal(t, savedReport.CreatedAt, reports[0].CreatedAt) } func TestReportRepository_FindAll_Error(t *testing.T) { diff --git a/app/report/services/report_service.go b/app/report/services/report_service.go index c32f580..b1b0605 100644 --- a/app/report/services/report_service.go +++ b/app/report/services/report_service.go @@ -1,20 +1,22 @@ package services import ( + "encoding/json" + "fmt" "gdsc/baro/app/report/models" "gdsc/baro/app/report/repositories" "gdsc/baro/app/report/types" usermodel "gdsc/baro/app/user/models" + "gdsc/baro/global/fcm" "gdsc/baro/global/utils" - "log" + "io" "os" - "strings" + "time" "net/http" "net/url" "github.com/gin-gonic/gin" - "golang.org/x/net/html" ) type ReportServiceInterface interface { @@ -23,8 +25,6 @@ type ReportServiceInterface interface { FindById(c *gin.Context, id uint) (types.ResponseReport, error) FindReportSummaryByMonth(c *gin.Context, yearAndMonth string) ([]types.ResponseReportSummary, error) FindAll() ([]types.ResponseReport, error) - HandleRequest(url string, user usermodel.User, input types.RequestAnalysis) - ParseHTML(n *html.Node) string } type ReportService struct { @@ -55,60 +55,184 @@ func (service *ReportService) Analysis(c *gin.Context, input types.RequestAnalys message := "Video submitted successfully" - go service.HandleRequest(u.String(), *user, input) + go Predict(*service, u.String(), *user, input) return message, nil } -func (service *ReportService) HandleRequest(url string, user usermodel.User, input types.RequestAnalysis) { - req, err := http.NewRequest("POST", url, nil) +func Predict(service ReportService, url string, user usermodel.User, input types.RequestAnalysis) error { + response, err := HandleRequest(url) if err != nil { - log.Println(err) - return + return err + } + + result, scores, nomalRatio, statusFrequencies, distances, landmarksInfo := ParseAnalysis(&response) + score := CalculateScores(result, scores) + + report := models.Report{ + UserID: user.ID, + AlertCount: input.AlertCount, + AnalysisTime: input.AnalysisTime, + Type: input.Type, + Predict: fmt.Sprintf("%v", result), + Score: score, + NormalRatio: nomalRatio, + StatusFrequencies: statusFrequencies, + Distances: distances, + NeckAngles: landmarksInfo, + } + + savedReport, _ := service.ReportRepository.Save(&report) + + title, body, _ := GenerateMessage(savedReport.CreatedAt.String()) + + err = fcm.SendPushNotification(user.FcmToken, title, body) + if err != nil { + return err + } + + return nil +} + +func HandleRequest(url string) (types.ResponseAnalysis, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return types.ResponseAnalysis{}, err } client := &http.Client{} + response, err := client.Do(req) if err != nil { - log.Println(err) - return + return types.ResponseAnalysis{}, err } defer response.Body.Close() - doc, err := html.Parse(response.Body) + body, err := io.ReadAll(response.Body) if err != nil { - log.Println(err) - return + return types.ResponseAnalysis{}, err } - report := models.Report{ - UserID: user.ID, - AlertCount: input.AlertCount, - AnalysisTime: input.AnalysisTime, - Type: input.Type, + var data types.ResponseAnalysis + err = json.Unmarshal([]byte(body), &data) + if err != nil { + fmt.Println("응답 파싱 에러:", err) + return types.ResponseAnalysis{}, err } - report.Predict = service.ParseHTML(doc) + return data, nil +} - _, err = service.ReportRepository.Save(&report) - if err != nil { - log.Println(err) - return +func ParseAnalysis(response *types.ResponseAnalysis) ([]int, []float64, string, string, string, string) { + // 결과 및 신뢰도 + result := response.Result + scores := response.Scores + + // 정상 비율 + nomalRatio := response.NormalRatio + + // 빈도수 + statusFrequencies := []int{ + response.StatusFrequencies["Fine"], + response.StatusFrequencies["Danger"], + response.StatusFrequencies["Serious"], + response.StatusFrequencies["Very Serious"], + } + + // 없는 필드는 0으로 초기화 + for i := range statusFrequencies { + if statusFrequencies[i] == 0 { + statusFrequencies[i] = 0 + } + } + + // 길이 및 각도 + var distances []float64 + var angles []float64 + for i := range response.LandmarksInfo { + // response.LandmarksInfo[i][2] = 길이 + // response.LandmarksInfo[i][3] = 각도 + distances = append(distances, response.LandmarksInfo[i][2].(float64)) + angles = append(angles, response.LandmarksInfo[i][3].(float64)) } + + return result, scores, fmt.Sprintf("%.3f", nomalRatio), fmt.Sprintf("%v", statusFrequencies), fmt.Sprintf("%.3f", distances), fmt.Sprintf("%.3f", angles) +} + +func CalculateScores(result []int, scores []float64) string { + normalCases := make([]float64, 0, len(result)) + abnormalCases := make([]float64, 0, len(result)) + + for i, r := range result { + if r == 1 { + normalCases = append(normalCases, scores[i]) + } else { + abnormalCases = append(abnormalCases, scores[i]) + } + } + + totalCases := len(normalCases) + len(abnormalCases) + caseScore := 100.0 / float64(totalCases) + + var totalScore float64 + + for _, score := range normalCases { + switch { + case score >= 90: + totalScore += 1.0 * caseScore + case score >= 80: + totalScore += 0.98 * caseScore + case score >= 70: + totalScore += 0.95 * caseScore + default: + totalScore += 0.9 * caseScore + } + } + + for _, score := range abnormalCases { + switch { + case score >= 90: + totalScore += 0.15 * caseScore + case score >= 80: + totalScore += 0.2 * caseScore + case score >= 70: + totalScore += 0.25 * caseScore + case score >= 60: + totalScore += 0.27 * caseScore + default: + totalScore += 0.3 * caseScore + } + } + + return fmt.Sprintf("%.2f", totalScore) } -func (service *ReportService) ParseHTML(n *html.Node) string { - if n.Type == html.ElementNode && n.Data == "p" { - return n.FirstChild.Data +func GenerateMessage(date string) (string, string, error) { + timeFormats := []string{ + "2006-01-02 15:04:05.000 -0700 MST", + "2006-01-02 15:04:05.00 -0700 MST", + "2006-01-02 15:04:05.0 -0700 MST", } - for c := n.FirstChild; c != nil; c = c.NextSibling { - result := service.ParseHTML(c) - if strings.TrimSpace(result) != "" { - return result + + var t time.Time + var err error + + for _, format := range timeFormats { + t, err = time.Parse(format, date) + if err == nil { + break } } - return "" + + if err != nil { + return "", "", err + } + + title := "자세 분석이 완료되었어요!" + body := fmt.Sprintf("%d년 %d월 %d일에 측정한 보고서가 도착했습니다!", t.Year(), t.Month(), t.Day()) + + return title, body, nil } func (service *ReportService) FindReportByCurrentUser(c *gin.Context) ([]types.ResponseReport, error) { @@ -125,13 +249,18 @@ func (service *ReportService) FindReportByCurrentUser(c *gin.Context) ([]types.R var responseReports []types.ResponseReport for _, report := range reports { responseReport := types.ResponseReport{ - ID: report.ID, - UserID: report.UserID, - AlertCount: report.AlertCount, - AnalysisTime: report.AnalysisTime, - Predict: report.Predict, - Type: report.Type, - CreatedAt: report.CreatedAt, + ID: report.ID, + UserID: report.UserID, + AlertCount: report.AlertCount, + AnalysisTime: report.AnalysisTime, + Type: report.Type, + Predict: report.Predict, + Score: report.Score, + NormalRatio: report.NormalRatio, + NeckAngles: report.NeckAngles, + Distances: report.Distances, + StatusFrequencies: report.StatusFrequencies, + CreatedAt: report.CreatedAt, } responseReports = append(responseReports, responseReport) } @@ -146,13 +275,18 @@ func (service *ReportService) FindById(c *gin.Context, id uint) (types.ResponseR } responseReport := types.ResponseReport{ - ID: report.ID, - UserID: report.UserID, - AlertCount: report.AlertCount, - AnalysisTime: report.AnalysisTime, - Predict: report.Predict, - Type: report.Type, - CreatedAt: report.CreatedAt, + ID: report.ID, + UserID: report.UserID, + AlertCount: report.AlertCount, + AnalysisTime: report.AnalysisTime, + Type: report.Type, + Predict: report.Predict, + Score: report.Score, + NormalRatio: report.NormalRatio, + NeckAngles: report.NeckAngles, + Distances: report.Distances, + StatusFrequencies: report.StatusFrequencies, + CreatedAt: report.CreatedAt, } return responseReport, nil @@ -164,10 +298,7 @@ func (service *ReportService) FindReportSummaryByMonth(c *gin.Context, yearAndMo return nil, err } - reports, err := service.ReportRepository.FindByYearAndMonth(user.ID, yearAndMonth) - if err != nil { - return nil, err - } + reports, _ := service.ReportRepository.FindByYearAndMonth(user.ID, yearAndMonth) var responseReports []types.ResponseReportSummary for _, report := range reports { @@ -182,21 +313,23 @@ func (service *ReportService) FindReportSummaryByMonth(c *gin.Context, yearAndMo } func (service *ReportService) FindAll() ([]types.ResponseReport, error) { - reports, err := service.ReportRepository.FindAll() - if err != nil { - return nil, err - } + reports, _ := service.ReportRepository.FindAll() var responseReports []types.ResponseReport for _, report := range reports { responseReport := types.ResponseReport{ - ID: report.ID, - UserID: report.UserID, - AlertCount: report.AlertCount, - AnalysisTime: report.AnalysisTime, - Predict: report.Predict, - Type: report.Type, - CreatedAt: report.CreatedAt, + ID: report.ID, + UserID: report.UserID, + AlertCount: report.AlertCount, + AnalysisTime: report.AnalysisTime, + Type: report.Type, + Predict: report.Predict, + Score: report.Score, + NormalRatio: report.NormalRatio, + NeckAngles: report.NeckAngles, + Distances: report.Distances, + StatusFrequencies: report.StatusFrequencies, + CreatedAt: report.CreatedAt, } responseReports = append(responseReports, responseReport) } diff --git a/app/report/services/report_service_test.go b/app/report/services/report_service_test.go index 0a46fb3..b07968f 100644 --- a/app/report/services/report_service_test.go +++ b/app/report/services/report_service_test.go @@ -2,18 +2,18 @@ package services_test import ( "errors" + "fmt" "gdsc/baro/app/report/models" "gdsc/baro/app/report/services" "gdsc/baro/app/report/types" usermodel "gdsc/baro/app/user/models" - "strings" + "os" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "golang.org/x/net/html" ) type MockReportRepository struct { @@ -65,6 +65,9 @@ func TestAnalysis(t *testing.T) { mockReportRepository := new(MockReportRepository) mockUserUtil := new(MockUserUtil) + // Set up environment variables + os.Setenv("AI_SERVER_API_URL", "http://localhost:5000/predict") + // Create ReportService reportService := services.NewReportService(mockReportRepository, mockUserUtil) @@ -79,11 +82,6 @@ func TestAnalysis(t *testing.T) { } // Set up expectations for the mock repository and util - mockUserUtil.On("FindCurrentUser", mock.Anything).Return(&user, nil) - - // Create a test context - c, _ := gin.CreateTestContext(nil) - request := types.RequestAnalysis{ VideoURL: "test", AlertCount: 10, @@ -91,6 +89,12 @@ func TestAnalysis(t *testing.T) { Type: "Study", } + // Set up expectations for the mock repository and util + mockUserUtil.On("FindCurrentUser", mock.Anything).Return(&user, nil) + + // Create a test context + c, _ := gin.CreateTestContext(nil) + // Call the service responseMessage, err := reportService.Analysis(c, request) assert.NoError(t, err) @@ -133,36 +137,193 @@ func TestAnalysis_NoUser(t *testing.T) { mockUserUtil.AssertExpectations(t) } -func TestParseHTML(t *testing.T) { - // Create service - service := services.NewReportService(nil, nil) +func TestPredict_InvalidURL(t *testing.T) { + // Mock ReportRepository, UserUtil + mockReportRepository := new(MockReportRepository) + mockUserUtil := new(MockUserUtil) + + // Create ReportService + service := services.NewReportService(mockReportRepository, mockUserUtil) + + // Set up invalid URL + url := "" + + // Call the service + err := services.Predict(*service, url, usermodel.User{}, types.RequestAnalysis{}) + assert.Error(t, err) +} + +func TestHandleRequest(t *testing.T) { + assert.True(t, true) +} + +func TestParseAnalysis(t *testing.T) { + // response *types.ResponseAnalysis + response := types.ResponseAnalysis{ + Result: []int{0, 1, 1, 1, 0, 1}, + HunchedRatio: 10.0, + NormalRatio: 90.0, + Scores: []float64{99.9, 92.9, 82.3, 92.4, 69.5}, + LandmarksInfo: [][]interface{}{ + {[]float64{0.1, 0.2}, []float64{0.3, 0.4}, 1.0, 45.0}, + {[]float64{0.5, 0.6}, []float64{0.7, 0.8}, 2.0, 60.0}, + }, + StatusFrequencies: map[string]int{"Very Serious": 6}, + } + + // Execute method under test + result, scores, nomalRatio, statusFrequencies, distances, landmarksInfo := services.ParseAnalysis(&response) + + // Assert result + assert.Equal(t, response.Result, result) + assert.Equal(t, response.Scores, scores) + assert.Equal(t, fmt.Sprintf("%.3f", response.NormalRatio), nomalRatio) + assert.Equal(t, fmt.Sprintf("%.3f", []float64{1.0, 2.0}), distances) + assert.Equal(t, fmt.Sprintf("%.3f", []float64{45.0, 60.0}), landmarksInfo) + assert.Equal(t, fmt.Sprintf("%v", []string{"0", "0", "0", "6"}), statusFrequencies) +} + +func TestParseAnalysis_NoStatusFrequencies(t *testing.T) { + // response *types.ResponseAnalysis + response := types.ResponseAnalysis{ + Result: []int{0, 1, 1, 1, 0, 1}, + HunchedRatio: 10.0, + NormalRatio: 90.0, + Scores: []float64{99.9, 92.9, 82.3, 92.4, 69.5}, + LandmarksInfo: [][]interface{}{ + {[]float64{0.1, 0.2}, []float64{0.3, 0.4}, 1.0, 45.0}, + {[]float64{0.5, 0.6}, []float64{0.7, 0.8}, 2.0, 60.0}, + }, + } + + // Execute method under test + result, scores, nomalRatio, statusFrequencies, distances, landmarksInfo := services.ParseAnalysis(&response) + + // Assert result + assert.Equal(t, response.Result, result) + assert.Equal(t, response.Scores, scores) + assert.Equal(t, fmt.Sprintf("%.3f", response.NormalRatio), nomalRatio) + assert.Equal(t, fmt.Sprintf("%.3f", []float64{1.0, 2.0}), distances) + assert.Equal(t, fmt.Sprintf("%.3f", []float64{45.0, 60.0}), landmarksInfo) + assert.Equal(t, fmt.Sprintf("%v", []string{"0", "0", "0", "0"}), statusFrequencies) +} + +func TestParseAnalysis_FullStatusFrequencies(t *testing.T) { + // response *types.ResponseAnalysis + response := types.ResponseAnalysis{ + Result: []int{0, 1, 1, 1, 0, 1}, + HunchedRatio: 10.0, + NormalRatio: 90.0, + Scores: []float64{99.9, 92.9, 82.3, 92.4, 69.5}, + LandmarksInfo: [][]interface{}{ + {[]float64{0.1, 0.2}, []float64{0.3, 0.4}, 1.0, 45.0}, + {[]float64{0.5, 0.6}, []float64{0.7, 0.8}, 2.0, 60.0}, + }, + StatusFrequencies: map[string]int{"Fine": 1, "Danger": 2, "Serious": 1, "Very Serious": 2}, + } + + // Execute method under test + result, scores, nomalRatio, statusFrequencies, distances, landmarksInfo := services.ParseAnalysis(&response) + + // Assert result + assert.Equal(t, response.Result, result) + assert.Equal(t, response.Scores, scores) + assert.Equal(t, fmt.Sprintf("%.3f", response.NormalRatio), nomalRatio) + assert.Equal(t, fmt.Sprintf("%.3f", []float64{1.0, 2.0}), distances) + assert.Equal(t, fmt.Sprintf("%.3f", []float64{45.0, 60.0}), landmarksInfo) + assert.Equal(t, fmt.Sprintf("%v", []string{"1", "2", "1", "2"}), statusFrequencies) +} + +func TestCalculateScores_AllBad(t *testing.T) { + // Set up test data + testResult := []int{0, 0, 0, 0, 0, 0} + testScores := []float64{99.9, 92.9, 92.3, 92.4, 99.5, 92.0} + + // Execute method under test + result := services.CalculateScores(testResult, testScores) + + // Assert result + assert.Equal(t, "15.00", result) +} + +func TestCalculateScores_HalfGood(t *testing.T) { + // Set up test data + testResult := []int{0, 0, 0, 1, 1, 1} + testScores := []float64{99.9, 92.9, 92.3, 92.4, 99.5, 92.0} + + // Execute method under test + result := services.CalculateScores(testResult, testScores) + + // Assert result + assert.Equal(t, "57.50", result) +} + +func TestCalculateScores_AllGoodDiffScore(t *testing.T) { + // Set up test data + testResult := []int{1, 1, 1, 1, 1, 1} + testScores := []float64{99.9, 89.9, 79.9, 69.9, 59.9, 49.9} + + // Execute method under test + result := services.CalculateScores(testResult, testScores) + + // Assert result + assert.Equal(t, "93.83", result) +} +func TestCalculateScores_AllBadSameScore(t *testing.T) { // Set up test data - testHTML := "

Test

" - doc, err := html.Parse(strings.NewReader(testHTML)) - assert.Nil(t, err) + testResult := []int{0, 0, 0, 0, 0, 0} + testScores := []float64{99.9, 89.9, 79.9, 69.9, 59.9, 49.9} // Execute method under test - result := service.ParseHTML(doc) + result := services.CalculateScores(testResult, testScores) // Assert result - assert.Equal(t, "Test", result) + assert.Equal(t, "24.50", result) } -func TestParseHTML_NoData(t *testing.T) { - // Create service - service := services.NewReportService(nil, nil) +func TestCalculateScores_AllGood(t *testing.T) { + // Set up test data + testResult := []int{1, 1, 1, 1, 1, 1} + testScores := []float64{99.9, 92.9, 92.3, 92.4, 99.5, 92.0} + + // Execute method under test + result := services.CalculateScores(testResult, testScores) + + // Assert result + assert.Equal(t, "100.00", result) +} +func TestGenerateMessage(t *testing.T) { // Set up test data - testHTML := "" - doc, err := html.Parse(strings.NewReader(testHTML)) - assert.Nil(t, err) + testTimes := []string{ + "2024-02-01 12:04:05.123 +0900 KST", + "2024-02-01 12:04:05.12 +0900 KST", + "2024-02-01 12:04:05.1 +0900 KST", + } // Execute method under test - result := service.ParseHTML(doc) + for _, testTime := range testTimes { + title, body, err := services.GenerateMessage(testTime) + + // Assert result + assert.NoError(t, err) + assert.Equal(t, "자세 분석이 완료되었어요!", title) + assert.Equal(t, "2024년 2월 1일에 측정한 보고서가 도착했습니다!", body) + } +} + +func TestGenerateMessage_InvaildData(t *testing.T) { + // Set up invalid test data + testTime := "2024-02-01" + + // Execute method under test + title, body, err := services.GenerateMessage(testTime) // Assert result - assert.Equal(t, "", result) + assert.Error(t, err) + assert.Equal(t, "", title) + assert.Equal(t, "", body) } func TestFindReportByCurrentUser(t *testing.T) { @@ -186,20 +347,30 @@ func TestFindReportByCurrentUser(t *testing.T) { // Set up sample reports for the test reports := []models.Report{ { - ID: 1, - UserID: 1, - AlertCount: 10, - AnalysisTime: 1800, - Predict: "Good", - Type: "Study", + ID: 1, + UserID: 1, + AlertCount: 10, + AnalysisTime: 1800, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", }, { - ID: 2, - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", + ID: 2, + UserID: 1, + AlertCount: 10, + AnalysisTime: 1800, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", }, } @@ -225,8 +396,13 @@ func TestFindReportByCurrentUser(t *testing.T) { assert.Equal(t, report.UserID, responseReports[i].UserID) assert.Equal(t, report.AlertCount, responseReports[i].AlertCount) assert.Equal(t, report.AnalysisTime, responseReports[i].AnalysisTime) - assert.Equal(t, report.Predict, responseReports[i].Predict) assert.Equal(t, report.Type, responseReports[i].Type) + assert.Equal(t, report.Predict, responseReports[i].Predict) + assert.Equal(t, report.Score, responseReports[i].Score) + assert.Equal(t, report.NormalRatio, responseReports[i].NormalRatio) + assert.Equal(t, report.NeckAngles, responseReports[i].NeckAngles) + assert.Equal(t, report.Distances, responseReports[i].Distances) + assert.Equal(t, report.StatusFrequencies, responseReports[i].StatusFrequencies) } } @@ -251,6 +427,9 @@ func TestFindReportByCurrentUser_NoUser(t *testing.T) { // Assert that the expectations were met mockReportRepository.AssertExpectations(t) mockUserUtil.AssertExpectations(t) + + // Check the results + assert.Equal(t, "record not found", err.Error()) } func TestFindReportByCurrentUser_NoReport(t *testing.T) { @@ -298,12 +477,17 @@ func TestFindById(t *testing.T) { // Set up sample report for the test report := models.Report{ - ID: 1, - UserID: 1, - AlertCount: 10, - AnalysisTime: 1800, - Predict: "Good", - Type: "Study", + ID: 1, + UserID: 1, + AlertCount: 10, + AnalysisTime: 1800, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", } // Set up expectations for the mock repository @@ -325,8 +509,13 @@ func TestFindById(t *testing.T) { assert.Equal(t, report.UserID, responseReport.UserID) assert.Equal(t, report.AlertCount, responseReport.AlertCount) assert.Equal(t, report.AnalysisTime, responseReport.AnalysisTime) - assert.Equal(t, report.Predict, responseReport.Predict) assert.Equal(t, report.Type, responseReport.Type) + assert.Equal(t, report.Predict, responseReport.Predict) + assert.Equal(t, report.Score, responseReport.Score) + assert.Equal(t, report.NormalRatio, responseReport.NormalRatio) + assert.Equal(t, report.NeckAngles, responseReport.NeckAngles) + assert.Equal(t, report.Distances, responseReport.Distances) + assert.Equal(t, report.StatusFrequencies, responseReport.StatusFrequencies) } func TestFindById_NoReport(t *testing.T) { @@ -376,22 +565,32 @@ func TestFindReportSummaryByMonth(t *testing.T) { // Set up sample reports for the test reports := []models.Report{ { - ID: 1, - UserID: 1, - AlertCount: 10, - AnalysisTime: 1800, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + ID: 1, + UserID: 1, + AlertCount: 10, + AnalysisTime: 1800, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), }, { - ID: 2, - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + ID: 2, + UserID: 1, + AlertCount: 10, + AnalysisTime: 1800, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), }, } @@ -496,22 +695,32 @@ func TestFindAll(t *testing.T) { // Set up sample reports for the test reports := []models.Report{ { - ID: 1, - UserID: 1, - AlertCount: 10, - AnalysisTime: 1800, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + ID: 1, + UserID: 1, + AlertCount: 10, + AnalysisTime: 1800, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), }, { - ID: 2, - UserID: 1, - AlertCount: 30, - AnalysisTime: 3600, - Predict: "Good", - Type: "Study", - CreatedAt: time.Now(), + ID: 2, + UserID: 1, + AlertCount: 30, + AnalysisTime: 3600, + Type: "Study", + Predict: "Good", + Score: "90.000", + NormalRatio: "90.000", + NeckAngles: "angle", + Distances: "distance", + StatusFrequencies: "status", + CreatedAt: time.Now(), }, } diff --git a/app/report/types/response_types.go b/app/report/types/response_types.go index 552e1cb..110367d 100644 --- a/app/report/types/response_types.go +++ b/app/report/types/response_types.go @@ -7,12 +7,26 @@ type ResponseReportSummary struct { CreatedAt time.Time `json:"created_at"` } +type ResponseAnalysis struct { + Result []int `json:"result"` + HunchedRatio float64 `json:"hunched_ratio"` + NormalRatio float64 `json:"normal_ratio"` + Scores []float64 `json:"scores"` + LandmarksInfo [][]interface{} `json:"landmarks_info"` + StatusFrequencies map[string]int `json:"status_frequencies"` +} + type ResponseReport struct { - ID uint `json:"id"` - UserID uint `json:"user_id"` - AlertCount int `json:"alert_count"` - AnalysisTime int `json:"analysis_time"` - Predict string `json:"predict"` - Type string `json:"type"` - CreatedAt time.Time `json:"created_at"` + ID uint `json:"id"` + UserID uint `json:"user_id"` + AlertCount int `json:"alert_count"` + AnalysisTime int `json:"analysis_time"` + Type string `json:"type"` + Predict string `json:"predict"` + Score string `json:"score"` + NormalRatio string `json:"normal_ratio"` + NeckAngles string `json:"neck_angles"` + Distances string `json:"distances"` + StatusFrequencies string `json:"status_frequencies"` + CreatedAt time.Time `json:"created_at"` } diff --git a/app/user/controllers/user_controller.go b/app/user/controllers/user_controller.go index 890ee60..b00a2d4 100644 --- a/app/user/controllers/user_controller.go +++ b/app/user/controllers/user_controller.go @@ -61,6 +61,51 @@ func (controller *UserController) LoginOrRegisterUser(c *gin.Context) { }) } +// @Tags Users +// @Summary FCM 토큰 업데이트 +// @Description FCM 토큰을 업데이트합니다. +// @Accept json +// @Produce json +// @Param fcm_token body types.RequestUpdateFcmToken true "FCM 토큰" +// @Success 200 {object} global.Response +// @Failure 400 {object} global.Response +// @Security Bearer +// @Router /users/fcm-token [put] +func (controller *UserController) UpdateFcmToken(c *gin.Context) { + var input types.RequestUpdateFcmToken + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(400, global.Response{ + Status: 400, + Message: err.Error(), + }) + return + } + + if err := input.Validate(); err != nil { + c.JSON(400, global.Response{ + Status: 400, + Message: err.Error(), + }) + return + } + + err := controller.UserService.UpdateFcmToken(c, input) + if err != nil { + c.JSON(400, global.Response{ + Status: 400, + Message: err.Error(), + Data: "fail", + }) + return + } + + c.JSON(200, global.Response{ + Status: 200, + Message: "success", + Data: "OK", + }) +} + // @Tags Users // @Summary 내 정보 조회 // @Description 현재 로그인한 사용자의 정보를 조회합니다. @@ -107,14 +152,6 @@ func (controller *UserController) UpdateUserInfo(c *gin.Context) { return } - if err := input.Validate(); err != nil { - c.JSON(400, global.Response{ - Status: 400, - Message: err.Error(), - }) - return - } - user, err := controller.UserService.UpdateUserInfo(c, input) if err != nil { c.JSON(400, global.Response{ diff --git a/app/user/controllers/user_controller_test.go b/app/user/controllers/user_controller_test.go index 88f84b2..708ccbf 100644 --- a/app/user/controllers/user_controller_test.go +++ b/app/user/controllers/user_controller_test.go @@ -27,6 +27,11 @@ func (m *MockUserService) Login(input types.RequestCreateUser) (types.ResponseTo return args.Get(0).(types.ResponseToken), args.Error(1) } +func (m *MockUserService) UpdateFcmToken(c *gin.Context, input types.RequestUpdateFcmToken) error { + args := m.Called(c, input) + return args.Error(0) +} + func (m *MockUserService) GetUserInfo(c *gin.Context) (types.ResponseUser, error) { args := m.Called(c) if args.Get(0) == nil { @@ -68,10 +73,11 @@ func TestUserController_LoginOrRegisterUser(t *testing.T) { // Create a sample request for the test input := types.RequestCreateUser{ - Name: "test", - Email: "test@gmail.com", - Age: 20, - Gender: "male", + Name: "test", + Email: "test@gmail.com", + Age: 20, + Gender: "male", + FcmToken: "test_token", } // Set up expectations for the mock service @@ -104,6 +110,40 @@ func TestUserController_LoginOrRegisterUser(t *testing.T) { assert.Equal(t, types.ResponseToken{}, response.Data) } +func TestUserController_LoginOrRegisterUser_InvalidJson(t *testing.T) { + // Mock UserService + mockService := new(MockUserService) + + // Create UserController with the mock service + userController := controllers.NewUserController(mockService) + + // Create a test context using httptest + w := httptest.NewRecorder() + + // Create a test context using httptest + req, _ := http.NewRequest("POST", "/login", nil) + req.Body = io.NopCloser(strings.NewReader("invalid json")) + + c, _ := gin.CreateTestContext(w) + c.Request = req + + // Call the method under test + userController.LoginOrRegisterUser(c) + + // Validate the response + assert.Equal(t, http.StatusBadRequest, w.Code) + + // Parse the response body + var response ResponseToken + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.Nil(t, err) + + // Check the results + assert.Equal(t, http.StatusBadRequest, response.Status) + assert.Equal(t, "invalid character 'i' looking for beginning of value", response.Message) + assert.Equal(t, types.ResponseToken{}, response.Data) +} + func TestUserController_LoginOrRegisterUser_InvalidInput_Email(t *testing.T) { // Mock UserService mockService := new(MockUserService) @@ -113,10 +153,11 @@ func TestUserController_LoginOrRegisterUser_InvalidInput_Email(t *testing.T) { // Create a sample request for the test input := types.RequestCreateUser{ - Name: "test", - Email: "test", - Age: 20, - Gender: "male", + Name: "test", + Email: "test", + Age: 20, + Gender: "male", + FcmToken: "test_token", } // Create a test context using httptest @@ -143,6 +184,108 @@ func TestUserController_LoginOrRegisterUser_InvalidInput_Email(t *testing.T) { assert.Equal(t, types.ResponseToken{}, response.Data) } +func TestUserController_FcmTokenUpdate(t *testing.T) { + // Mock UserService + mockService := new(MockUserService) + + // Create UserController with the mock service + userController := controllers.NewUserController(mockService) + + // Create a sample request for the test + input := types.RequestUpdateFcmToken{ + FcmToken: "new-fcm-token", + } + + // Set up expectations for the mock service + mockService.On("UpdateFcmToken", mock.Anything, input).Return(nil) + + // Create a test context using httptest + w := httptest.NewRecorder() + req, _ := http.NewRequest("PUT", "/users/me/fcm-token", nil) + req.Body = io.NopCloser(mockRequestBody(input)) + c, _ := gin.CreateTestContext(w) + c.Request = req + + // Call the method under test + userController.UpdateFcmToken(c) + + // Assert that the expectations were met + mockService.AssertExpectations(t) + + // Validate the response + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestUserController_FcmTokenUpdate_InvalidJson(t *testing.T) { + // Mock UserService + mockService := new(MockUserService) + + // Create UserController with the mock service + userController := controllers.NewUserController(mockService) + + // Create a test context using httptest + w := httptest.NewRecorder() + + // Create a test context using httptest + req, _ := http.NewRequest("PUT", "/users/me/fcm-token", nil) + req.Body = io.NopCloser(strings.NewReader("invalid json")) + + c, _ := gin.CreateTestContext(w) + c.Request = req + + // Call the method under test + userController.UpdateFcmToken(c) + + // Validate the response + assert.Equal(t, http.StatusBadRequest, w.Code) + + // Parse the response body + var response ResponseToken + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.Nil(t, err) + + // Check the results + assert.Equal(t, http.StatusBadRequest, response.Status) + assert.Equal(t, "invalid character 'i' looking for beginning of value", response.Message) + assert.Equal(t, types.ResponseToken{}, response.Data) +} + +func TestUserController_FcmTokenUpdate_InvalidInput_FcmToken(t *testing.T) { + // Mock UserService + mockService := new(MockUserService) + + // Create UserController with the mock service + userController := controllers.NewUserController(mockService) + + // Create a sample request for the test + input := types.RequestUpdateFcmToken{ + FcmToken: "", + } + + // Create a test context using httptest + w := httptest.NewRecorder() + req, _ := http.NewRequest("PUT", "/users/me/fcm-token", nil) + req.Body = io.NopCloser(mockRequestBody(input)) + c, _ := gin.CreateTestContext(w) + c.Request = req + + // Call the method under test + userController.UpdateFcmToken(c) + + // Validate the response + assert.Equal(t, http.StatusBadRequest, w.Code) + + // Parse the response body + var response ResponseToken + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.Nil(t, err) + + // Check the results + assert.Equal(t, http.StatusBadRequest, response.Status) + assert.Equal(t, "Key: 'RequestUpdateFcmToken.FcmToken' Error:Field validation for 'FcmToken' failed on the 'required' tag", response.Message) + assert.Equal(t, types.ResponseToken{}, response.Data) +} + func TestUserController_GetUserInfo(t *testing.T) { // Mock UserService mockService := new(MockUserService) @@ -277,6 +420,39 @@ func TestUserController_UpdateUserInfo(t *testing.T) { assert.Equal(t, updatedUser, response.Data) } +func TestUserController_UpdateUserInfo_InvalidJson(t *testing.T) { + // Mock UserService + mockService := new(MockUserService) + + // Create UserController with the mock service + userController := controllers.NewUserController(mockService) + // Create a test context using httptest + w := httptest.NewRecorder() + + // Create a test context using httptest + req, _ := http.NewRequest("PUT", "/users/me", nil) + req.Body = io.NopCloser(strings.NewReader("invalid json")) + + c, _ := gin.CreateTestContext(w) + c.Request = req + + // Call the method under test + userController.UpdateUserInfo(c) + + // Validate the response + assert.Equal(t, http.StatusBadRequest, w.Code) + + // Parse the response body + var response ResponseUser + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.Nil(t, err) + + // Check the results + assert.Equal(t, http.StatusBadRequest, response.Status) + assert.Equal(t, "invalid character 'i' looking for beginning of value", response.Message) + assert.Equal(t, types.ResponseUser{}, response.Data) +} + func TestUserController_DeleteUser(t *testing.T) { // Mock UserService mockService := new(MockUserService) diff --git a/app/user/models/User.go b/app/user/models/User.go index 963efb1..f76a48a 100644 --- a/app/user/models/User.go +++ b/app/user/models/User.go @@ -9,5 +9,6 @@ type User struct { Email string `gorm:"unique"` Age int Gender string + FcmToken string Deleted gorm.DeletedAt } diff --git a/app/user/repositories/user_repository_test.go b/app/user/repositories/user_repository_test.go index 7d59fd5..837be11 100644 --- a/app/user/repositories/user_repository_test.go +++ b/app/user/repositories/user_repository_test.go @@ -42,13 +42,14 @@ func TestUserRepository_Create(t *testing.T) { Email: "test@gmail.com", Age: 20, Gender: "male", + FcmToken: "test_token", Deleted: gorm.DeletedAt{}, } // Set up expectations for the mock DB to return the sample user mock.ExpectBegin() mock.ExpectExec("INSERT INTO `users`"). - WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.Deleted). + WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.FcmToken, user.Deleted). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -68,6 +69,7 @@ func TestUserRepository_Create(t *testing.T) { assert.Equal(t, user.Email, createdUser.Email) assert.Equal(t, user.Age, createdUser.Age) assert.Equal(t, user.Gender, createdUser.Gender) + assert.Equal(t, user.FcmToken, createdUser.FcmToken) } func TestUserRepository_FindByID(t *testing.T) { @@ -98,13 +100,14 @@ func TestUserRepository_FindByID(t *testing.T) { Email: "test@gmail.com", Age: 20, Gender: "male", + FcmToken: "test_token", Deleted: gorm.DeletedAt{}, } // Set up expectations for the mock DB to return the sample user mock.ExpectBegin() mock.ExpectExec("INSERT INTO `users`"). - WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.Deleted). + WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.FcmToken, user.Deleted). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -117,8 +120,8 @@ func TestUserRepository_FindByID(t *testing.T) { // Set up expectations for the mock DB to return the user by ID mock.ExpectQuery("SELECT \\* FROM `users` WHERE id = \\? AND `users`.`deleted` IS NULL ORDER BY `users`.`id` LIMIT 1"). WithArgs(sqlmock.AnyArg()). - WillReturnRows(sqlmock.NewRows([]string{"ID", "Name", "Nickname", "Email", "Age", "Gender", "Deleted"}). - AddRow(createdUser.ID, createdUser.Name, createdUser.Nickname, createdUser.Email, createdUser.Age, createdUser.Gender, createdUser.Deleted.Time)) + WillReturnRows(sqlmock.NewRows([]string{"ID", "Name", "Nickname", "Email", "Age", "Gender", "FcmToken", "Deleted"}). + AddRow(createdUser.ID, createdUser.Name, createdUser.Nickname, createdUser.Email, createdUser.Age, createdUser.Gender, createdUser.FcmToken, createdUser.Deleted.Time)) // Find the user by ID foundUser, err := userRepository.FindByID(strconv.Itoa(int(createdUser.ID))) @@ -137,6 +140,7 @@ func TestUserRepository_FindByID(t *testing.T) { assert.Equal(t, user.Email, foundUser.Email) assert.Equal(t, user.Age, foundUser.Age) assert.Equal(t, user.Gender, foundUser.Gender) + assert.Equal(t, user.FcmToken, foundUser.FcmToken) } func TestUserRepository_FindOrCreateByEmail_First_Login(t *testing.T) { @@ -167,19 +171,20 @@ func TestUserRepository_FindOrCreateByEmail_First_Login(t *testing.T) { Email: "test@gmail.com", Age: 20, Gender: "male", + FcmToken: "test_token", Deleted: gorm.DeletedAt{}, } // Set up expectations for the mock DB to create the user mock.ExpectQuery("SELECT \\* FROM `users` WHERE `users`.`email` = \\? AND `users`.`deleted` IS NULL ORDER BY `users`.`id` LIMIT 1"). WithArgs(user.Email). - WillReturnRows(sqlmock.NewRows([]string{"id", "name", "nickname", "email", "age", "gender", "deleted"})) + WillReturnRows(sqlmock.NewRows([]string{"id", "name", "nickname", "email", "age", "gender", "fcm_token", "deleted"})) // Set up expectations for the mock DB to create the user mock.ExpectBegin() mock.ExpectExec("INSERT INTO `users`"). - WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.Deleted). + WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.FcmToken, user.Deleted). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -199,6 +204,7 @@ func TestUserRepository_FindOrCreateByEmail_First_Login(t *testing.T) { assert.Equal(t, user.Email, createdUser.Email) assert.Equal(t, user.Age, createdUser.Age) assert.Equal(t, user.Gender, createdUser.Gender) + assert.Equal(t, user.FcmToken, createdUser.FcmToken) } func TestUserRepository_FindByEmail(t *testing.T) { @@ -230,13 +236,14 @@ func TestUserRepository_FindByEmail(t *testing.T) { Email: email, Age: 20, Gender: "male", + FcmToken: "test_token", Deleted: gorm.DeletedAt{}, } // Set up expectations for the mock DB to find the user by email mock.ExpectQuery("SELECT \\* FROM `users` WHERE email = \\?").WithArgs(email). - WillReturnRows(sqlmock.NewRows([]string{"id", "name", "nickname", "email", "age", "gender", "deleted"}). - AddRow(expectedUser.ID, expectedUser.Name, expectedUser.Nickname, expectedUser.Email, expectedUser.Age, expectedUser.Gender, expectedUser.Deleted)) + WillReturnRows(sqlmock.NewRows([]string{"id", "name", "nickname", "email", "age", "gender", "fcm_token", "deleted"}). + AddRow(expectedUser.ID, expectedUser.Name, expectedUser.Nickname, expectedUser.Email, expectedUser.Age, expectedUser.Gender, expectedUser.FcmToken, expectedUser.Deleted)) // Call the method under test resultUser, err := userRepository.FindByEmail(email) @@ -254,6 +261,7 @@ func TestUserRepository_FindByEmail(t *testing.T) { assert.Equal(t, expectedUser.Email, resultUser.Email) assert.Equal(t, expectedUser.Age, resultUser.Age) assert.Equal(t, expectedUser.Gender, resultUser.Gender) + assert.Equal(t, expectedUser.FcmToken, resultUser.FcmToken) } func TestUserRepository_Update(t *testing.T) { @@ -285,13 +293,14 @@ func TestUserRepository_Update(t *testing.T) { Email: "test@gmail.com", Age: 20, Gender: "male", + FcmToken: "test_token", Deleted: gorm.DeletedAt{}, } // Set up expectations for the mock DB to update the user within a transaction mock.ExpectBegin() mock.ExpectExec("UPDATE `users`"). - WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.Deleted, user.ID). + WithArgs(user.Name, user.Nickname, user.Email, user.Age, user.Gender, user.FcmToken, user.Deleted, user.ID). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() @@ -341,6 +350,7 @@ func TestUserRepository_Delete(t *testing.T) { Email: "test@gmail.com", Age: 20, Gender: "male", + FcmToken: "test_token", Deleted: gorm.DeletedAt{Time: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)}, } diff --git a/app/user/services/user_service.go b/app/user/services/user_service.go index 0ac06c0..a09af87 100644 --- a/app/user/services/user_service.go +++ b/app/user/services/user_service.go @@ -13,6 +13,7 @@ import ( type UserServiceInterface interface { Login(input types.RequestCreateUser) (types.ResponseToken, error) + UpdateFcmToken(c *gin.Context, input types.RequestUpdateFcmToken) error GetUserInfo(c *gin.Context) (types.ResponseUser, error) UpdateUserInfo(c *gin.Context, input types.RequestUpdateUser) (types.ResponseUser, error) DeleteUser(c *gin.Context) error @@ -42,19 +43,32 @@ func (service *UserService) Login(input types.RequestCreateUser) (types.Response Email: input.Email, Age: input.Age, Gender: input.Gender, + FcmToken: input.FcmToken, } - user, err := service.UserRepository.FindOrCreateByEmail(&requestCreateUser) + user, _ := service.UserRepository.FindOrCreateByEmail(&requestCreateUser) + + user.FcmToken = input.FcmToken + updatedUser, _ := service.UserRepository.Update(user) + + token, _ := service.generateToken(updatedUser.ID) + + return types.ResponseToken{Token: token}, nil +} + +func (service *UserService) UpdateFcmToken(c *gin.Context, input types.RequestUpdateFcmToken) error { + user, err := service.UserUtil.FindCurrentUser(c) if err != nil { - return types.ResponseToken{}, err + return err } - token, err := service.generateToken(user.ID) - if err != nil { - return types.ResponseToken{}, err + user.FcmToken = input.FcmToken + _, updateErr := service.UserRepository.Update(user) + if updateErr != nil { + return updateErr } - return types.ResponseToken{Token: token}, nil + return nil } func (service *UserService) GetUserInfo(c *gin.Context) (types.ResponseUser, error) { diff --git a/app/user/services/user_service_test.go b/app/user/services/user_service_test.go index 3e84414..eea2353 100644 --- a/app/user/services/user_service_test.go +++ b/app/user/services/user_service_test.go @@ -62,10 +62,11 @@ func TestUserService_Login(t *testing.T) { // Set up sample user for the test input := types.RequestCreateUser{ - Name: "test", - Email: "test@gmail.com", - Age: 25, - Gender: "male", + Name: "test", + Email: "test@gmail.com", + Age: 25, + Gender: "male", + FcmToken: "test_token", } expectedUser := &models.User{ @@ -75,10 +76,12 @@ func TestUserService_Login(t *testing.T) { Email: input.Email, Age: input.Age, Gender: input.Gender, + FcmToken: input.FcmToken, } // Set up expectations for the mock repository mockRepo.On("FindOrCreateByEmail", mock.AnythingOfType("*models.User")).Return(expectedUser, nil) + mockRepo.On("Update", mock.AnythingOfType("*models.User")).Return(*expectedUser, nil) // Create UserService with the mock repository userService := services.NewUserService(mockRepo, nil) @@ -94,6 +97,123 @@ func TestUserService_Login(t *testing.T) { assert.NotEmpty(t, responseToken.Token) } +func TestUserService_UpdateFcmToken(t *testing.T) { + // Mock UserRepository and UserUtil + mockRepo := new(MockUserRepository) + mockUtil := new(MockUserUtil) + + // Set up sample user for the test + input := types.RequestUpdateFcmToken{ + FcmToken: "new_token", + } + + // Set up expectations + currentUser := &models.User{ + ID: 1, + Name: "test", + Nickname: "test", + Email: "test@gmail.com", + Age: 25, + Gender: "male", + FcmToken: "old_token", + } + + updatedUser := &models.User{ + ID: 1, + Name: "test", + Nickname: "test", + Email: "test@gmail.com", + Age: 25, + Gender: "male", + FcmToken: "new_token", + } + + // Set up expectations for the mock util + mockUtil.On("FindCurrentUser", mock.AnythingOfType("*gin.Context")).Return(currentUser, nil) + + // Set up expectations for the mock repository + mockRepo.On("Update", mock.AnythingOfType("*models.User")).Return(*updatedUser, nil) + + // Create UserService with the mock repository and util + userService := services.NewUserService(mockRepo, mockUtil) + + // Create a test context + ctx, _ := gin.CreateTestContext(nil) + + // Call the method under test + err := userService.UpdateFcmToken(ctx, input) + + // Assert that the expectations were met + mockUtil.AssertExpectations(t) + mockRepo.AssertExpectations(t) + + // Check the results + assert.Nil(t, err) +} + +func TestUserService_UpdateFcmToken_NoUser(t *testing.T) { + // Mock UserRepository + mockRepo := new(MockUserRepository) + mockUtil := new(MockUserUtil) + + // Set up expectations for the mock util + mockUtil.On("FindCurrentUser", mock.AnythingOfType("*gin.Context")).Return((*models.User)(nil), errors.New("user not found")) + + // Create UserService with the mock repository and util + userService := services.NewUserService(mockRepo, mockUtil) + + // Create a test context + ctx, _ := gin.CreateTestContext(nil) + + // Set up sample user for the test + input := types.RequestUpdateFcmToken{ + FcmToken: "new_token", + } + + // Call the method under test + err := userService.UpdateFcmToken(ctx, input) + + // Assert that the expectations were met + mockUtil.AssertExpectations(t) + + // Check the results + assert.NotNil(t, err) + assert.EqualError(t, err, "user not found") +} + +func TestUserService_UpdateFcmToken_Error(t *testing.T) { + // Mock UserRepository + mockRepo := new(MockUserRepository) + mockUtil := new(MockUserUtil) + + // Set up expectations for the mock util + mockUtil.On("FindCurrentUser", mock.AnythingOfType("*gin.Context")).Return(&models.User{}, nil) + + // Set up sample user for the test + input := types.RequestUpdateFcmToken{ + FcmToken: "new_token", + } + + // Set up expectations for the mock repository + mockRepo.On("Update", mock.AnythingOfType("*models.User")).Return(models.User{}, errors.New("record not found")) + + // Create UserService with the mock repository and util + userService := services.NewUserService(mockRepo, mockUtil) + + // Create a test context + ctx, _ := gin.CreateTestContext(nil) + + // Call the method under test + err := userService.UpdateFcmToken(ctx, input) + + // Assert that the expectations were met + mockUtil.AssertExpectations(t) + mockRepo.AssertExpectations(t) + + // Check the results + assert.NotNil(t, err) +} + func TestUserService_GetUserInfo(t *testing.T) { // Mock UserRepository mockRepo := new(MockUserRepository) diff --git a/app/user/types/request_types.go b/app/user/types/request_types.go index 1157c63..517d6a5 100644 --- a/app/user/types/request_types.go +++ b/app/user/types/request_types.go @@ -9,16 +9,25 @@ func init() { } type RequestCreateUser struct { - Name string `json:"name" validate:"required"` - Email string `json:"email" validate:"required,email"` - Age int `json:"age"` - Gender string `json:"gender"` + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required,email"` + Age int `json:"age"` + Gender string `json:"gender"` + FcmToken string `json:"fcm_token"` } func (r *RequestCreateUser) Validate() error { return validate.Struct(r) } +type RequestUpdateFcmToken struct { + FcmToken string `json:"fcm_token" validate:"required"` +} + +func (r *RequestUpdateFcmToken) Validate() error { + return validate.Struct(r) +} + type RequestUpdateUser struct { Nickname string `json:"nickname"` Age int `json:"age"` diff --git a/docs/docs.go b/docs/docs.go index a17f48d..ed1f44c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -67,7 +67,7 @@ const docTemplate = `{ "summary": "자세 추정 요청", "parameters": [ { - "description": "동영상 URL", + "description": "URL, 알림 횟수 등", "name": "video_url", "in": "body", "required": true, @@ -133,7 +133,7 @@ const docTemplate = `{ "Bearer": [] } ], - "description": "로그인한 사용자의 자세 추정 결과를 월별로 요약하여 조회합니다.", + "description": "로그인한 사용자의 자세 추정 결과를 월별로 요약하여 조회합니다. (캘린더 점 찍는 용도로 사용)", "consumes": [ "application/json" ], @@ -147,7 +147,7 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "조회할 년월 (YYYYMM)", + "description": "조회할 년월 (YYYYMM) 예시: 202401 (2024년 1월)", "name": "ym", "in": "query", "required": true @@ -176,7 +176,7 @@ const docTemplate = `{ "Bearer": [] } ], - "description": "보고서 id로 자세 추정 결과를 조회합니다.", + "description": "보고서 id로 자세 추정 결과를 조회합니다. (요약으로 먼저 보고서 id 조회하고 사용자가 그걸 누르면 이걸 사용하기)", "consumes": [ "application/json" ], @@ -275,6 +275,51 @@ const docTemplate = `{ } } }, + "/users/fcm-token": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "FCM 토큰을 업데이트합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "FCM 토큰 업데이트", + "parameters": [ + { + "description": "FCM 토큰", + "name": "fcm_token", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/types.RequestUpdateFcmToken" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/global.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/global.Response" + } + } + } + } + }, "/users/me": { "get": { "security": [ @@ -456,17 +501,17 @@ const docTemplate = `{ "types.RequestAnalysis": { "type": "object", "required": [ - "alert_count", - "analysis_time", "type", "video_url" ], "properties": { "alert_count": { - "type": "integer" + "type": "integer", + "minimum": 0 }, "analysis_time": { - "type": "integer" + "type": "integer", + "minimum": 0 }, "type": { "type": "string" @@ -489,6 +534,9 @@ const docTemplate = `{ "email": { "type": "string" }, + "fcm_token": { + "type": "string" + }, "gender": { "type": "string" }, @@ -497,6 +545,17 @@ const docTemplate = `{ } } }, + "types.RequestUpdateFcmToken": { + "type": "object", + "required": [ + "fcm_token" + ], + "properties": { + "fcm_token": { + "type": "string" + } + } + }, "types.RequestUpdateUser": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index fe88829..7b22e14 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -56,7 +56,7 @@ "summary": "자세 추정 요청", "parameters": [ { - "description": "동영상 URL", + "description": "URL, 알림 횟수 등", "name": "video_url", "in": "body", "required": true, @@ -122,7 +122,7 @@ "Bearer": [] } ], - "description": "로그인한 사용자의 자세 추정 결과를 월별로 요약하여 조회합니다.", + "description": "로그인한 사용자의 자세 추정 결과를 월별로 요약하여 조회합니다. (캘린더 점 찍는 용도로 사용)", "consumes": [ "application/json" ], @@ -136,7 +136,7 @@ "parameters": [ { "type": "string", - "description": "조회할 년월 (YYYYMM)", + "description": "조회할 년월 (YYYYMM) 예시: 202401 (2024년 1월)", "name": "ym", "in": "query", "required": true @@ -165,7 +165,7 @@ "Bearer": [] } ], - "description": "보고서 id로 자세 추정 결과를 조회합니다.", + "description": "보고서 id로 자세 추정 결과를 조회합니다. (요약으로 먼저 보고서 id 조회하고 사용자가 그걸 누르면 이걸 사용하기)", "consumes": [ "application/json" ], @@ -264,6 +264,51 @@ } } }, + "/users/fcm-token": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "FCM 토큰을 업데이트합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "FCM 토큰 업데이트", + "parameters": [ + { + "description": "FCM 토큰", + "name": "fcm_token", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/types.RequestUpdateFcmToken" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/global.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/global.Response" + } + } + } + } + }, "/users/me": { "get": { "security": [ @@ -445,17 +490,17 @@ "types.RequestAnalysis": { "type": "object", "required": [ - "alert_count", - "analysis_time", "type", "video_url" ], "properties": { "alert_count": { - "type": "integer" + "type": "integer", + "minimum": 0 }, "analysis_time": { - "type": "integer" + "type": "integer", + "minimum": 0 }, "type": { "type": "string" @@ -478,6 +523,9 @@ "email": { "type": "string" }, + "fcm_token": { + "type": "string" + }, "gender": { "type": "string" }, @@ -486,6 +534,17 @@ } } }, + "types.RequestUpdateFcmToken": { + "type": "object", + "required": [ + "fcm_token" + ], + "properties": { + "fcm_token": { + "type": "string" + } + } + }, "types.RequestUpdateUser": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6f21292..0efac2d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -10,16 +10,16 @@ definitions: types.RequestAnalysis: properties: alert_count: + minimum: 0 type: integer analysis_time: + minimum: 0 type: integer type: type: string video_url: type: string required: - - alert_count - - analysis_time - type - video_url type: object @@ -29,6 +29,8 @@ definitions: type: integer email: type: string + fcm_token: + type: string gender: type: string name: @@ -37,6 +39,13 @@ definitions: - email - name type: object + types.RequestUpdateFcmToken: + properties: + fcm_token: + type: string + required: + - fcm_token + type: object types.RequestUpdateUser: properties: age: @@ -75,7 +84,7 @@ paths: - application/json description: 자세를 추정합니다. (동영상 URL을 입력받아 자세를 추정합니다.) parameters: - - description: 동영상 URL + - description: URL, 알림 횟수 등 in: body name: video_url required: true @@ -101,7 +110,7 @@ paths: get: consumes: - application/json - description: 보고서 id로 자세 추정 결과를 조회합니다. + description: 보고서 id로 자세 추정 결과를 조회합니다. (요약으로 먼저 보고서 id 조회하고 사용자가 그걸 누르면 이걸 사용하기) parameters: - description: 자세 추정 결과 id in: path @@ -149,9 +158,9 @@ paths: get: consumes: - application/json - description: 로그인한 사용자의 자세 추정 결과를 월별로 요약하여 조회합니다. + description: 로그인한 사용자의 자세 추정 결과를 월별로 요약하여 조회합니다. (캘린더 점 찍는 용도로 사용) parameters: - - description: 조회할 년월 (YYYYMM) + - description: '조회할 년월 (YYYYMM) 예시: 202401 (2024년 1월)' in: query name: ym required: true @@ -213,6 +222,34 @@ paths: summary: 로그인 (첫 로그인 시 회원가입) tags: - Users + /users/fcm-token: + put: + consumes: + - application/json + description: FCM 토큰을 업데이트합니다. + parameters: + - description: FCM 토큰 + in: body + name: fcm_token + required: true + schema: + $ref: '#/definitions/types.RequestUpdateFcmToken' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/global.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/global.Response' + security: + - Bearer: [] + summary: FCM 토큰 업데이트 + tags: + - Users /users/me: delete: consumes: diff --git a/global/fcm/firebase_messaging.go b/global/fcm/firebase_messaging.go new file mode 100644 index 0000000..aeccfc1 --- /dev/null +++ b/global/fcm/firebase_messaging.go @@ -0,0 +1,58 @@ +package fcm + +import ( + "context" + "fmt" + "log" + "os" + + firebase "firebase.google.com/go" + "firebase.google.com/go/messaging" + "google.golang.org/api/option" +) + +var app *firebase.App + +func init() { + fmt.Println("Initializing Firebase App...") + + var err error + FIREBASE_SERVICE_KEY := os.Getenv("FIREBASE_SERVICE_KEY") + if FIREBASE_SERVICE_KEY == "" { + fmt.Println("FIREBASE_SERVICE_KEY is empty") + } + + opt := option.WithCredentialsJSON([]byte(FIREBASE_SERVICE_KEY)) + app, err = firebase.NewApp(context.Background(), nil, opt) + if err != nil { + fmt.Printf("error initializing app: %v\n", err) + } +} + +func SendPushNotification(fcmToken string, title string, body string) error { + ctx := context.Background() + + client, err := app.Messaging(ctx) + if err != nil { + log.Fatalf("error getting Messaging client: %v\n", err) + } + + message := &messaging.Message{ + Data: map[string]string{ + "title": title, + "body": body, + }, + Notification: &messaging.Notification{ + Title: title, + Body: body, + }, + Token: fcmToken, + } + + _, err = client.Send(ctx, message) + if err != nil { + fmt.Printf("error sending message: %v\n", err) + } + + return nil +} diff --git a/go.mod b/go.mod index bfc03b3..8696040 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,21 @@ module gdsc/baro go 1.21.3 +require github.com/soheilhy/cmux v0.1.5 + require ( + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/firestore v1.14.0 // indirect + cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/longrunning v0.5.4 // indirect + cloud.google.com/go/storage v1.36.0 // indirect + firebase.google.com/go v3.13.0+incompatible // indirect + firebase.google.com/go/v4 v4.13.0 // indirect github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/PuerkitoBio/purell v1.2.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/bytedance/sonic v1.10.2 // indirect @@ -12,9 +24,12 @@ require ( github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/spec v0.20.13 // indirect @@ -24,8 +39,14 @@ require ( github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -41,7 +62,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/soheilhy/cmux v0.1.5 github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/swaggo/files v1.0.1 // indirect @@ -51,14 +71,29 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect github.com/urfave/cli/v2 v2.27.1 // indirect github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect golang.org/x/arch v0.6.0 // indirect golang.org/x/crypto v0.18.0 // indirect golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect - google.golang.org/grpc v1.60.1 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.157.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/appengine/v2 v2.0.5 // indirect + google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/grpc v1.61.0 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 724bb47..ce6979c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,31 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= +cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= +firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= +firebase.google.com/go/v4 v4.13.0 h1:meFz9nvDNh/FDyrEykoAzSfComcQbmnQSjoHrePRqeI= +firebase.google.com/go/v4 v4.13.0/go.mod h1:e1/gaR6EnbQfsmTnAMx1hnz+ninJIrrr/RAh59Tpfn8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= +github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -10,6 +34,7 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -17,17 +42,32 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= @@ -47,14 +87,49 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -86,6 +161,7 @@ github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOS github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -119,6 +195,28 @@ github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6S github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -129,9 +227,18 @@ golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -140,8 +247,16 @@ golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -161,22 +276,71 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= +google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/appengine/v2 v2.0.5 h1:4C+F3Cd3L2nWEfSmFEZDPjQvDwL8T0YCeZBysZifP3k= +google.golang.org/appengine/v2 v2.0.5/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -190,6 +354,8 @@ gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/main.go b/main.go index 355b688..23122ec 100644 --- a/main.go +++ b/main.go @@ -101,6 +101,7 @@ func (app *App) InitRouter() { secureAPI.GET("/users/me", func(c *gin.Context) { app.UserCtrl.GetUserInfo(c) }) secureAPI.PUT("/users/me", func(c *gin.Context) { app.UserCtrl.UpdateUserInfo(c) }) secureAPI.DELETE("/users/me", func(c *gin.Context) { app.UserCtrl.DeleteUser(c) }) + secureAPI.PUT("/users/fcm-token", func(c *gin.Context) { app.UserCtrl.UpdateFcmToken(c) }) secureAPI.POST("/analysis", func(c *gin.Context) { app.ReportCtrl.Analysis(c) }) secureAPI.GET("/analysis", func(c *gin.Context) { app.ReportCtrl.GetAnalysis(c) })