diff --git a/docs/docs.go b/docs/docs.go index dbdf6e3b729aece2439ba44bef9d640685222521..df6adff7d07778286c12076b94acdc458490df3e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -75,10 +75,28 @@ var doc = `{ ], "responses": { "200": { - "description": "OK", + "description": "Updated successfully", "schema": { "$ref": "#/definitions/monthlyNews.MonthlyNews" } + }, + "201": { + "description": "Created successfully", + "schema": { + "$ref": "#/definitions/monthlyNews.MonthlyNews" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } } } }, @@ -147,6 +165,12 @@ var doc = `{ "schema": { "$ref": "#/definitions/monthlyNews.MonthlyNews" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } } } }, @@ -181,6 +205,12 @@ var doc = `{ "schema": { "type": "string" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } } } } @@ -232,10 +262,28 @@ var doc = `{ ], "responses": { "200": { - "description": "OK", + "description": "Updated successfully", "schema": { "$ref": "#/definitions/poll.Poll" } + }, + "201": { + "description": "Created successfully", + "schema": { + "$ref": "#/definitions/poll.Poll" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } } } }, @@ -304,6 +352,12 @@ var doc = `{ "schema": { "$ref": "#/definitions/poll.Poll" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } } } }, @@ -338,6 +392,38 @@ var doc = `{ "schema": { "type": "string" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/common/monthlyReport": { + "get": { + "description": "Find the most recent MonthlyNews and try to find the corresponding poll if it exists", + "produces": [ + "application/json" + ], + "tags": [ + "monthlyReport" + ], + "summary": "Get details of the most recent monthlyReport", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/monthlyReport.MonthlyReport" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } } } } @@ -374,6 +460,12 @@ var doc = `{ "schema": { "$ref": "#/definitions/monthlyReport.MonthlyReport" } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } } } } diff --git a/docs/swagger.json b/docs/swagger.json index e6071094a76ff2cd33570d1d9665da3a7d7528b4..d179c021d1db8099d54e5be40b13653663e207d6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -60,10 +60,28 @@ ], "responses": { "200": { - "description": "OK", + "description": "Updated successfully", "schema": { "$ref": "#/definitions/monthlyNews.MonthlyNews" } + }, + "201": { + "description": "Created successfully", + "schema": { + "$ref": "#/definitions/monthlyNews.MonthlyNews" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } } } }, @@ -132,6 +150,12 @@ "schema": { "$ref": "#/definitions/monthlyNews.MonthlyNews" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } } } }, @@ -166,6 +190,12 @@ "schema": { "type": "string" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } } } } @@ -217,10 +247,28 @@ ], "responses": { "200": { - "description": "OK", + "description": "Updated successfully", "schema": { "$ref": "#/definitions/poll.Poll" } + }, + "201": { + "description": "Created successfully", + "schema": { + "$ref": "#/definitions/poll.Poll" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "type": "string" + } } } }, @@ -289,6 +337,12 @@ "schema": { "$ref": "#/definitions/poll.Poll" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } } } }, @@ -323,6 +377,38 @@ "schema": { "type": "string" } + }, + "404": { + "description": "Not found", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/common/monthlyReport": { + "get": { + "description": "Find the most recent MonthlyNews and try to find the corresponding poll if it exists", + "produces": [ + "application/json" + ], + "tags": [ + "monthlyReport" + ], + "summary": "Get details of the most recent monthlyReport", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/monthlyReport.MonthlyReport" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } } } } @@ -359,6 +445,12 @@ "schema": { "$ref": "#/definitions/monthlyReport.MonthlyReport" } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "string" + } } } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 920f139bfaa105089bdfe085752971db791c6ba1..9f1922d1089eb92f39d7faedba3e695696df2efc 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -98,9 +98,21 @@ paths: - application/json responses: "200": - description: OK + description: Updated successfully schema: $ref: '#/definitions/monthlyNews.MonthlyNews' + "201": + description: Created successfully + schema: + $ref: '#/definitions/monthlyNews.MonthlyNews' + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal server error + schema: + type: string summary: Create/update a specific monthlyNews' content tags: - monthlyNews @@ -125,6 +137,10 @@ paths: description: successful delete schema: type: string + "404": + description: Not found + schema: + type: string summary: Delete a specific monthlyNews tags: - monthlyNews @@ -148,6 +164,10 @@ paths: description: OK schema: $ref: '#/definitions/monthlyNews.MonthlyNews' + "404": + description: Not found + schema: + type: string summary: Get details of a specific monthlyNews tags: - monthlyNews @@ -202,9 +222,21 @@ paths: - application/json responses: "200": - description: OK + description: Updated successfully schema: $ref: '#/definitions/poll.Poll' + "201": + description: Created successfully + schema: + $ref: '#/definitions/poll.Poll' + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal server error + schema: + type: string summary: Update a specific poll' content tags: - poll @@ -229,6 +261,10 @@ paths: description: successful delete schema: type: string + "404": + description: Not found + schema: + type: string summary: Delete a specific poll tags: - poll @@ -252,9 +288,31 @@ paths: description: OK schema: $ref: '#/definitions/poll.Poll' + "404": + description: Not found + schema: + type: string summary: Get details of a specific poll tags: - poll + /api/common/monthlyReport: + get: + description: Find the most recent MonthlyNews and try to find the corresponding + poll if it exists + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/monthlyReport.MonthlyReport' + "404": + description: Not Found + schema: + type: string + summary: Get details of the most recent monthlyReport + tags: + - monthlyReport /api/common/monthlyReport/{year}/{month}: get: description: Get details of a specific monthlyReport @@ -276,6 +334,10 @@ paths: description: OK schema: $ref: '#/definitions/monthlyReport.MonthlyReport' + "404": + description: Not Found + schema: + type: string summary: Get details of a specific monthlyReport tags: - monthlyReport diff --git a/internal/database/database.go b/internal/database/database.go index a08daa4ebcc15bded1b873c4ba96f49e86f831e2..c435e9a8a55735c256fd51dd8c0f4207e2c09b27 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -37,7 +37,7 @@ func init() { } } else { dsn := fmt.Sprintf("%v:%v@tcp(%v:3306)/%v?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbHost, dbName) - log.Println(dsn) + log.Printf("Data Source Name : %s", dsn) db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database") @@ -58,16 +58,24 @@ func Exists(year int, month int, contentType string) bool { func Get(year int, month int, contentType string) (MonthlyContent, error) { var monthlyContent MonthlyContent if err := db.Where("year = ? AND month = ? AND content_type = ?", year, month, contentType).First(&monthlyContent).Error; err != nil { - log.Println(monthlyContent) + return MonthlyContent{}, err + } + return monthlyContent, nil +} + +func GetMostRecent(contentType string) (MonthlyContent, error) { + var monthlyContent MonthlyContent + + if err := db.Where("content_type = ?", contentType).Order("year desc, month desc").First(&monthlyContent).Error; err != nil { return MonthlyContent{}, err } return monthlyContent, nil } func GetAll(contentType string) []MonthlyContent { - var monthlyContent []MonthlyContent - db.Where("content_type = ?", contentType).Find(&monthlyContent) - return monthlyContent + var monthlyContents []MonthlyContent + db.Where("content_type = ?", contentType).Find(&monthlyContents) + return monthlyContents } func Create(year int, month int, contentType string, content string) error { diff --git a/internal/monthlyNews/monthlyNews.go b/internal/monthlyNews/monthlyNews.go index e789cf96514755b65973baae2616c52e7a6a00d9..9d5d9864d5f161da1e2c5a95604e6ac379c21c66 100644 --- a/internal/monthlyNews/monthlyNews.go +++ b/internal/monthlyNews/monthlyNews.go @@ -46,7 +46,6 @@ func GetAllMonthlyNews(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - log.Println(monthlyNews) json.NewEncoder(w).Encode(monthlyNews) log.Printf("| get all monthly news | %v", r.RemoteAddr) } @@ -57,6 +56,7 @@ func GetAllMonthlyNews(w http.ResponseWriter, r *http.Request) { // @Tags monthlyNews // @Produce json // @Success 200 {object} MonthlyNews +// @Failure 404 {string} string "Not found" // @Param year path int true "Year of the monthlyNews" // @Param month path int true "Month of the monthlyNews" // @Router /api/admin/monthlyNews/{year}/{month} [get] @@ -85,8 +85,8 @@ func GetSingleMonthlyNews(w http.ResponseWriter, r *http.Request) { header, errH := database.Get(year, month, "header") quote, errQ := database.Get(year, month, "quote") if errH != nil || errQ != nil { - // If not found, answer empty json - w.Write([]byte("{}")) + // If not found, answer "not found" + w.WriteHeader(http.StatusNotFound) return } monthlyNews := MonthlyNews{Year: year, Month: month, Header: header.Content, Quote: quote.Content} @@ -140,7 +140,10 @@ func AddMonthlyNews(w http.ResponseWriter, r *http.Request) { // @Tags monthlyNews // @Accept json // @Produce json -// @Success 200 {object} MonthlyNews +// @Success 200 {object} MonthlyNews "Updated successfully" +// @Success 201 {object} MonthlyNews "Created successfully" +// @Failure 400 {string} string "Bad Request" +// @Failure 500 {string} string "Internal server error" // @Param monthlyNews body MonthlyNews true "MonthlyNews to create/update with new content" // @Router /api/admin/monthlyNews [put] func UpdateMonthlyNews(w http.ResponseWriter, r *http.Request) { @@ -204,6 +207,7 @@ func UpdateMonthlyNews(w http.ResponseWriter, r *http.Request) { // @Tags monthlyNews // @Produce json // @Success 200 {string} string "successful delete" +// @Failure 404 {string} string "Not found" // @Param year path int true "Year of the monthlyNews" // @Param month path int true "Month of the monthlyNews" // @Router /api/admin/monthlyNews/{year}/{month} [delete] diff --git a/internal/monthlyReport/monthlyReport.go b/internal/monthlyReport/monthlyReport.go index 7c91006be7ad927b658b4cb2369ee129fed03468..821ebd0da08722ad4639037cadcad56cb9bf51bd 100644 --- a/internal/monthlyReport/monthlyReport.go +++ b/internal/monthlyReport/monthlyReport.go @@ -2,7 +2,6 @@ package monthlyReport import ( "encoding/json" - "log" "net/http" "strconv" @@ -25,6 +24,7 @@ type MonthlyReport struct { // @Tags monthlyReport // @Produce json // @Success 200 {object} MonthlyReport +// @Failure 404 {string} string "Not found" // @Param year path int true "Year of the monthlyReport" // @Param month path int true "Month of the monthlyReport" // @Router /api/common/monthlyReport/{year}/{month} [get] @@ -55,7 +55,8 @@ func GetSingleMonthlyReport(w http.ResponseWriter, r *http.Request) { link, errL := database.Get(year, month, "link") if errH != nil || errQuo != nil || errQue != nil || errL != nil { - http.Error(w, "couldn't find report", http.StatusNotFound) + // If not found, answer empty json + w.WriteHeader(http.StatusNotFound) return } @@ -68,6 +69,38 @@ func GetSingleMonthlyReport(w http.ResponseWriter, r *http.Request) { Question: question.Content, Link: link.Content, } - log.Println(monthlyReport) + json.NewEncoder(w).Encode(monthlyReport) +} + +// GetMostRecentMonthlyReport godoc +// @Summary Get details of the most recent monthlyReport +// @Description Find the most recent MonthlyNews and try to find the corresponding poll if it exists +// @Tags monthlyReport +// @Produce json +// @Success 200 {object} MonthlyReport +// @Failure 404 {string} string "Not found" +// @Router /api/common/monthlyReport [get] +func GetMostRecentMonthlyReport(w http.ResponseWriter, r *http.Request) { + + header, errH := database.GetMostRecent("header") + quote, _ := database.Get(header.Year, header.Month, "quote") + question, _ := database.Get(header.Year, header.Month, "question") + link, _ := database.Get(header.Year, header.Month, "link") + + if errH != nil { + // If not found, answer "not found" + w.WriteHeader(http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + monthlyReport := MonthlyReport{ + Year: header.Year, + Month: header.Month, + Header: header.Content, + Quote: quote.Content, + Question: question.Content, + Link: link.Content, + } json.NewEncoder(w).Encode(monthlyReport) } diff --git a/internal/poll/poll.go b/internal/poll/poll.go index 9f70306ce5a27518593d054868d72242d3deee7c..28907e53b24554cd5ab31c5ec4d343b8332e151c 100644 --- a/internal/poll/poll.go +++ b/internal/poll/poll.go @@ -46,7 +46,6 @@ func GetAllPolls(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - log.Println(poll) json.NewEncoder(w).Encode(poll) log.Printf("| get all polls | %v", r.RemoteAddr) } @@ -57,6 +56,7 @@ func GetAllPolls(w http.ResponseWriter, r *http.Request) { // @Tags poll // @Produce json // @Success 200 {object} Poll +// @Failure 404 {string} string "Not found" // @Param year path int true "Year of the poll" // @Param month path int true "Month of the poll" // @Router /api/admin/poll/{year}/{month} [get] @@ -85,8 +85,8 @@ func GetSinglePoll(w http.ResponseWriter, r *http.Request) { question, errQ := database.Get(year, month, "question") link, errL := database.Get(year, month, "link") if errQ != nil || errL != nil { - // If not found, answer empty json - w.Write([]byte("{}")) + // If not found, answer "not found" + w.WriteHeader(http.StatusNotFound) return } poll := Poll{Year: year, Month: month, Question: question.Content, Link: link.Content} @@ -140,7 +140,10 @@ func AddPoll(w http.ResponseWriter, r *http.Request) { // @Tags poll // @Accept json // @Produce json -// @Success 200 {object} Poll +// @Success 200 {object} Poll "Updated successfully" +// @Success 201 {object} Poll "Created successfully" +// @Failure 400 {string} string "Bad Request" +// @Failure 500 {string} string "Internal server error" // @Param poll body Poll true "Poll to update with new content" // @Router /api/admin/poll [put] func UpdatePoll(w http.ResponseWriter, r *http.Request) { @@ -204,6 +207,7 @@ func UpdatePoll(w http.ResponseWriter, r *http.Request) { // @Tags poll // @Produce json // @Success 200 {string} string "successful delete" +// @Failure 404 {string} string "Not found" // @Param year path int true "Year of the poll" // @Param month path int true "Month of the poll" // @Router /api/admin/poll/{year}/{month} [delete] diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go index 87e760445f4008a99b203cf6a5f14c9fc53bb1d2..7a79a6841e242b4664edbc2f35886c839a4e66d0 100644 --- a/internal/rootmux/rootmux.go +++ b/internal/rootmux/rootmux.go @@ -36,6 +36,7 @@ func CreateRootMux(staticDir string) RootMux { r.HandleFunc("/Logout", m.HandleLogout) r.Handle("/api/common/WhoAmI", auth.ValidateAuthMiddleware(auth.WhoAmI(), []string{"*"}, false)) + r.HandleFunc("/api/common/monthlyReport", monthlyReport.GetMostRecentMonthlyReport).Methods(http.MethodGet) r.HandleFunc("/api/common/monthlyReport/{year}/{month}", monthlyReport.GetSingleMonthlyReport).Methods(http.MethodGet) apiAdmin := r.PathPrefix("/api/admin").Subrouter() diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 27528aeeac998e020f6cbd9b87107919f1e232c6..1a4664b92ea7c7c98db48ae7937cf302c59db9c5 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -18,12 +18,14 @@ import ( ) var ( - oAuth2Server *httptest.Server - newMonthlyNews = monthlyNews.MonthlyNews{Year: 2020, Month: 0, Header: "newsHeader", Quote: "newsQuote"} - newMonthlyNewsStr string - newPoll = poll.Poll{Year: 2020, Month: 0, Question: "pollQuestion", Link: "pollLink"} - newPollStr string - noH map[string]string + oAuth2Server *httptest.Server + newMonthlyNews = monthlyNews.MonthlyNews{Year: 2020, Month: 0, Header: "newsHeader", Quote: "newsQuote"} + newMonthlyNewsStr string + recentMonthlyNews = monthlyNews.MonthlyNews{Year: 2021, Month: 0, Header: "recentNewsHeader", Quote: "recentNewsQuote"} + recentMonthlyNewsStr string + newPoll = poll.Poll{Year: 2020, Month: 0, Question: "pollQuestion", Link: "pollLink"} + newPollStr string + noH map[string]string ) func TestMain(m *testing.M) { @@ -51,6 +53,8 @@ func TestMain(m *testing.M) { // Convert example objects to string newMonthlyNewsBytes, _ := json.Marshal(newMonthlyNews) newMonthlyNewsStr = string(newMonthlyNewsBytes) + recentMonthlyNewsBytes, _ := json.Marshal(recentMonthlyNews) + recentMonthlyNewsStr = string(recentMonthlyNewsBytes) newPollBytes, _ := json.Marshal(newPoll) newPollStr = string(newPollBytes) @@ -94,10 +98,12 @@ func unloggedTests(t *testing.T) { // Try to create a monthlyNews (must fail) do("POST", "/api/admin/monthlyNews", noH, newMonthlyNewsStr, http.StatusUnauthorized, "error extracting token") + // Try to get the most recent monthlyReport (must fail because not found) + do("GET", "/api/common/monthlyReport", noH, "", http.StatusNotFound, "") } /** -ADMIN TESTS (this tests are to check that an administrator can alter the apps) +ADMIN TESTS (this tests are to check that an administrator can edit a newsletter's content) **/ func adminTests(t *testing.T) { // Create the tester @@ -113,35 +119,45 @@ func adminTests(t *testing.T) { do("POST", "/api/admin/monthlyNews", noH, newMonthlyNewsStr, http.StatusUnauthorized, "XSRF protection triggered") // Try to create a monthly news without body (must fail) do("POST", "/api/admin/monthlyNews", xsrfHeader, "", http.StatusBadRequest, "request body is empty") - // Try to get a monthly news before it is created (must pass empty) - do("GET", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusOK, "{}") + // Try to get a monthly news before it is created (must fail because not found) + do("GET", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusNotFound, "") // Try to create a monthly news (must pass) do("PUT", "/api/admin/monthlyNews", xsrfHeader, newMonthlyNewsStr, http.StatusCreated, newMonthlyNewsStr) // Try to update a monthly news (must pass) do("PUT", "/api/admin/monthlyNews", xsrfHeader, newMonthlyNewsStr, http.StatusOK, newMonthlyNewsStr) // Try to get the monthly news created (must pass) do("GET", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusOK, newMonthlyNewsStr) - // Try to delete the monthly news created (must pass) - do("DELETE", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusOK, "successful delete") - // Try to get a monthly news after it is deleted (must pass empty) - do("GET", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusOK, "{}") + // Try to get the most recent monthlyReport (must pass) + do("GET", "/api/common/monthlyReport", noH, "", http.StatusOK, `{"year":2020,"month":0,"header":"newsHeader","quote":"newsQuote","question":"","link":""`) // Try to create a poll without the XSRF-TOKEN (must fail) do("POST", "/api/admin/poll", noH, newPollStr, http.StatusUnauthorized, "XSRF protection triggered") // Try to create a poll without body (must fail) do("POST", "/api/admin/poll", xsrfHeader, "", http.StatusBadRequest, "request body is empty") - // Try to get a poll before it is created (must pass empty) - do("GET", "/api/admin/poll/2020/0", xsrfHeader, "", http.StatusOK, "{}") + // Try to get a poll before it is created (must fail because not found') + do("GET", "/api/admin/poll/2020/0", xsrfHeader, "", http.StatusNotFound, "") // Try to create a poll (must pass) do("PUT", "/api/admin/poll", xsrfHeader, newPollStr, http.StatusCreated, newPollStr) // Try to update a poll (must pass) do("PUT", "/api/admin/poll", xsrfHeader, newPollStr, http.StatusOK, newPollStr) // Try to get the poll created (must pass) do("GET", "/api/admin/poll/2020/0", xsrfHeader, "", http.StatusOK, newPollStr) + // Try to get the most recent monthlyReport (must pass) + do("GET", "/api/common/monthlyReport", noH, "", http.StatusOK, `{"year":2020,"month":0,"header":"newsHeader","quote":"newsQuote","question":"pollQuestion","link":"pollLink"`) + + // Try to create a monthly news (must pass) + do("POST", "/api/admin/monthlyNews", xsrfHeader, recentMonthlyNewsStr, http.StatusCreated, recentMonthlyNewsStr) + // Try to get the most recent monthlyReport (must pass) + do("GET", "/api/common/monthlyReport", noH, "", http.StatusOK, `{"year":2021,"month":0,"header":"recentNewsHeader","quote":"recentNewsQuote","question":"","link":""`) + + // Try to delete the monthly news created (must pass) + do("DELETE", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusOK, "successful delete") + // Try to get a monthly news after it is deleted (must fail because not found) + do("GET", "/api/admin/monthlyNews/2020/0", xsrfHeader, "", http.StatusNotFound, "") // Try to delete the poll created (must pass) do("DELETE", "/api/admin/poll/2020/0", xsrfHeader, "", http.StatusOK, "successful delete") - // Try to get a poll after it is deleted (must pass empty) - do("GET", "/api/admin/poll/2020/0", xsrfHeader, "", http.StatusOK, "{}") + // Try to get a poll after it is deleted (must fail because not found) + do("GET", "/api/admin/poll/2020/0", xsrfHeader, "", http.StatusNotFound, "") } // Try to login (must pass) do("GET", "/OAuth2Login", noH, "", http.StatusOK, "successful login")