414 lines
11 KiB
Go
414 lines
11 KiB
Go
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)
|
|
|
|
}
|