package api import ( "fmt" "log" "net/http" "strconv" "time" "srs.epita.fr/fic-server/libfic" "github.com/gin-gonic/gin" ) func declareQARoutes(router *gin.RouterGroup) { exercicesRoutes := router.Group("/qa") exercicesRoutes.GET("", getExerciceQA) exercicesRoutes.POST("", createExerciceQA) exercicesRoutes.GET("/export", exportQA) exercicesRoutes.GET("/export.json", exportQAJSON) qaRoutes := exercicesRoutes.Group("/:qid") qaRoutes.Use(qaHandler) qaRoutes.PUT("", updateExerciceQA) qaRoutes.DELETE("", deleteExerciceQA) qaRoutes.GET("comments", getQAComments) qaRoutes.POST("comments", createQAComment) declareGitlabExportRoutes(qaRoutes) commentsRoutes := qaRoutes.Group("comments/:cid") commentsRoutes.Use(qaCommentHandler) commentsRoutes.DELETE("", deleteQAComment) } func qaHandler(c *gin.Context) { var qa *fic.QAQuery qid, err := strconv.ParseInt(string(c.Param("qid")), 10, 64) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad QA identifier."}) return } if exercice, ok := c.Get("exercice"); ok { qa, err = exercice.(*fic.Exercice).GetQAQuery(qid) } else { qa, err = fic.GetQAQuery(qid) } if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "QA entry not found."}) return } c.Set("qa", qa) c.Next() } func qaCommentHandler(c *gin.Context) { qa := c.MustGet("qa").(*fic.QAQuery) var comment *fic.QAComment if cid, err := strconv.ParseInt(string(c.Param("cid")), 10, 64); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad comment identifier."}) return } else if comment, err = qa.GetComment(cid); err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Comment entry not found."}) return } c.Set("comment", comment) c.Next() } type QAQuery struct { *fic.QAQuery ForgeLink string `json:"forge_link,omitempty"` } func getExerciceQA(c *gin.Context) { exercice := c.MustGet("exercice").(*fic.Exercice) qas, err := exercice.GetQAQueries() if err != nil { log.Println("Unable to GetQAQueries: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())}) return } forgelink, err := GitLab_getExerciceId(exercice) if err != nil { log.Println("Unable to make request to admin:", err.Error()) c.JSON(http.StatusOK, qas) return } var res []*QAQuery for _, qa := range qas { if qa.Exported != nil { res = append(res, &QAQuery{ qa, gitlabBaseURL + "/" + forgelink + fmt.Sprintf("/-/issues/%d", *qa.Exported), }) } else { res = append(res, &QAQuery{ qa, "", }) } } c.JSON(http.StatusOK, res) } func exportQA(c *gin.Context) { var report string themes, err := fic.GetThemes() if err != nil { log.Println("Unable to GetThemes: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) return } for _, th := range themes { report += fmt.Sprintf("# %s {#%s}\n\n", th.Name, th.URLId) exercices, err := th.GetExercices() if err != nil { log.Println("Unable to GetExercices: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices for theme #%d: %s", th.Id, err.Error())}) return } for _, exercice := range exercices { report += fmt.Sprintf("## %s {#%s}\n\n", exercice.Title, exercice.URLId) qa, err := exercice.GetQAQueries() if err != nil { log.Println("Unable to GetQAQueries: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())}) return } for _, q := range qa { emoji := "❓" if q.Closed != nil { emoji = "👌" } else if q.Solved != nil { emoji = "✅" } report += fmt.Sprintf("### %s [%s] %s\n\nOuvert par %s le %s\n\n", emoji, q.State, q.Subject, q.User, q.Creation) comments, err := q.GetComments() if err != nil { log.Println("Unable to GetQAComments: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA comments: %s", err.Error())}) return } for _, c := range comments { report += fmt.Sprintf("Le %s, %s :\n%s\n\n", c.Date, c.User, c.Content) } } } } c.JSON(http.StatusOK, report) } type ExportTheme struct { Theme *fic.Theme `json:"theme"` Exercices []ExportExercice `json:"exercices"` } type ExportExercice struct { Exercice *fic.Exercice `json:"exercice"` Reports []ExportReport `json:"reports"` } type ExportReport struct { Report *fic.QAQuery `json:"report"` Comments []*fic.QAComment `json:"comments"` } func exportQAJSON(c *gin.Context) { report := map[string]ExportTheme{} themes, err := fic.GetThemes() if err != nil { log.Println("Unable to GetThemes: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) return } for _, th := range themes { export_theme := ExportTheme{ Theme: th, } exercices, err := th.GetExercices() if err != nil { log.Println("Unable to GetExercices: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices for theme #%d: %s", th.Id, err.Error())}) return } for _, exercice := range exercices { export_exercice := ExportExercice{ Exercice: exercice, } qa, err := exercice.GetQAQueries() if err != nil { log.Println("Unable to GetQAQueries: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())}) return } for _, q := range qa { export_report := ExportReport{ Report: q, } comments, err := q.GetComments() if err != nil { log.Println("Unable to GetQAComments: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA comments: %s", err.Error())}) return } for _, c := range comments { export_report.Comments = append(export_report.Comments, c) } export_exercice.Reports = append(export_exercice.Reports, export_report) } export_theme.Exercices = append(export_theme.Exercices, export_exercice) } report[th.Name] = export_theme } c.JSON(http.StatusOK, report) } type QAQueryAndComment struct { *fic.QAQuery Content string `json:"content"` } func createExerciceQA(c *gin.Context) { teamid := c.MustGet("LoggedTeam").(int64) ficteam := c.MustGet("LoggedUser").(string) // Create a new query var uq QAQueryAndComment if err := c.ShouldBindJSON(&uq); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } if len(uq.State) == 0 { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "State not filled"}) return } if len(uq.Subject) == 0 { if uq.State == "ok" { tmp := time.Now() uq.Subject = "RAS" uq.Solved = &tmp } else if uq.State == "timer" { tmp := time.Now() uq.Subject = fmt.Sprintf("Temps passé équipe #%d (%s)", teamid, ficteam) uq.Solved = &tmp } else { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Subject not filled"}) return } } exercice := c.MustGet("exercice").(*fic.Exercice) qa, err := exercice.NewQAQuery(uq.Subject, &teamid, ficteam, uq.State, uq.Solved) if err != nil { log.Println("Unable to NewQAQuery: ", err.Error()) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to create the new QA query. Please retry."}) return } if len(uq.Content) > 0 { _, err = qa.AddComment(uq.Content, &teamid, ficteam) if err != nil { log.Println("Unable to AddComment: ", err.Error()) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "QA entry added successfully, but unable to create the associated comment. Please retry."}) return } } c.JSON(http.StatusOK, qa) } func updateExerciceQA(c *gin.Context) { query := c.MustGet("qa").(*fic.QAQuery) var uq *fic.QAQuery if err := c.ShouldBindJSON(&uq); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } uq.Id = query.Id if uq.User != query.User && (uq.IdExercice != query.IdExercice || uq.IdTeam != query.IdTeam || uq.User != query.User || uq.Creation != query.Creation || uq.State != query.State || uq.Subject != query.Subject || uq.Closed != query.Closed) { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only update your own entry."}) return } _, err := uq.Update() if err != nil { log.Println("Unable to Update QAQuery:", err.Error()) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to update the query. Please try again."}) return } c.JSON(http.StatusOK, uq) } func deleteExerciceQA(c *gin.Context) { query := c.MustGet("qa").(*fic.QAQuery) user := c.MustGet("LoggedUser").(string) if user != query.User { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only delete your own entry."}) return } _, err := query.Delete() if err != nil { log.Println("Unable to Delete QAQuery:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the query. Please try again."}) return } c.JSON(http.StatusNoContent, nil) } func getQAComments(c *gin.Context) { query := c.MustGet("qa").(*fic.QAQuery) comments, err := query.GetComments() if err != nil { log.Println("Unable to GetComments: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list comments: %s", err.Error())}) return } c.JSON(http.StatusOK, comments) } func createQAComment(c *gin.Context) { // Create a new query var uc *fic.QAComment if err := c.ShouldBindJSON(&uc); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } if len(uc.Content) == 0 { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty comment."}) return } teamid := c.MustGet("LoggedTeam").(int64) ficteam := c.MustGet("LoggedUser").(string) query := c.MustGet("qa").(*fic.QAQuery) comment, err := query.AddComment(uc.Content, &teamid, ficteam) if err != nil { log.Println("Unable to AddComment: ", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add your comment. Please try again later."}) return } c.JSON(http.StatusOK, comment) } func deleteQAComment(c *gin.Context) { ficteam := c.MustGet("LoggedUser").(string) comment := c.MustGet("comment").(*fic.QAComment) if ficteam != comment.User { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only delete your own comment."}) return } _, err := comment.Delete() if err != nil { log.Println("Unable to Delete QAComment:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the comment. Please try again."}) return } c.JSON(http.StatusNoContent, nil) }