package api import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "path" "strconv" "time" "srs.epita.fr/fic-server/admin/generation" "srs.epita.fr/fic-server/libfic" "github.com/gin-gonic/gin" ) func declareClaimsRoutes(router *gin.RouterGroup) { // Tasks router.GET("/claims", getClaims) router.POST("/claims", newClaim) router.DELETE("/claims", clearClaims) apiClaimsRoutes := router.Group("/claims/:cid") apiClaimsRoutes.Use(ClaimHandler) apiClaimsRoutes.GET("", showClaim) apiClaimsRoutes.PUT("", updateClaim) apiClaimsRoutes.POST("", addClaimDescription) apiClaimsRoutes.DELETE("", deleteClaim) apiClaimsRoutes.GET("/last_update", getClaimLastUpdate) apiClaimsRoutes.PUT("/descriptions", updateClaimDescription) // Assignees router.GET("/claims-assignees", getAssignees) router.POST("/claims-assignees", newAssignee) apiClaimAssigneesRoutes := router.Group("/claims-assignees/:aid") apiClaimAssigneesRoutes.Use(ClaimAssigneeHandler) router.GET("/claims-assignees/:aid", showClaimAssignee) router.PUT("/claims-assignees/:aid", updateClaimAssignee) router.DELETE("/claims-assignees/:aid", deleteClaimAssignee) } func declareExerciceClaimsRoutes(router *gin.RouterGroup) { router.GET("/claims", getExerciceClaims) } func declareTeamClaimsRoutes(router *gin.RouterGroup) { router.GET("/api/teams/:tid/issue.json", func(c *gin.Context) { team := c.MustGet("team").(*fic.Team) issues, err := team.MyIssueFile() if err != nil { log.Printf("Unable to MyIssueFile(tid=%d): %s", team.Id, err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate issues.json."}) return } c.JSON(http.StatusOK, issues) }) router.GET("/claims", getTeamClaims) } func ClaimHandler(c *gin.Context) { cid, err := strconv.ParseInt(string(c.Params.ByName("cid")), 10, 64) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid claim identifier"}) return } claim, err := fic.GetClaim(cid) if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim not found"}) return } c.Set("claim", claim) c.Next() } func ClaimAssigneeHandler(c *gin.Context) { aid, err := strconv.ParseInt(string(c.Params.ByName("aid")), 10, 64) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid claim assignee identifier"}) return } assignee, err := fic.GetAssignee(aid) if err != nil { c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim-assignee not found"}) return } c.Set("claim-assignee", assignee) c.Next() } func getClaims(c *gin.Context) { claims, err := fic.GetClaims() if err != nil { log.Println("Unable to getClaims:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims retrieval."}) return } c.JSON(http.StatusOK, claims) } func getTeamClaims(c *gin.Context) { team := c.MustGet("team").(*fic.Team) claims, err := team.GetClaims() if err != nil { log.Printf("Unable to GetClaims(tid=%d): %s", team.Id, err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."}) return } c.JSON(http.StatusOK, claims) } func getExerciceClaims(c *gin.Context) { exercice := c.MustGet("exercice").(*fic.Exercice) claims, err := exercice.GetClaims() if err != nil { log.Printf("Unable to GetClaims(eid=%d): %s", exercice.Id, err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."}) return } c.JSON(http.StatusOK, claims) } func getClaimLastUpdate(c *gin.Context) { claim := c.MustGet("claim").(*fic.Claim) v, err := claim.GetLastUpdate() if err != nil { log.Printf("Unable to GetLastUpdate: %s", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim last update retrieval."}) return } c.JSON(http.StatusOK, v) } type ClaimExported struct { Id int64 `json:"id"` Subject string `json:"subject"` IdTeam *int64 `json:"id_team"` Team *fic.Team `json:"team"` IdExercice *int64 `json:"id_exercice"` Exercice *fic.Exercice `json:"exercice"` IdAssignee *int64 `json:"id_assignee"` Assignee *fic.ClaimAssignee `json:"assignee"` Creation time.Time `json:"creation"` LastUpdate time.Time `json:"last_update"` State string `json:"state"` Priority string `json:"priority"` Descriptions []*fic.ClaimDescription `json:"descriptions"` } func showClaim(c *gin.Context) { claim := c.MustGet("claim").(*fic.Claim) var e ClaimExported var err error if e.Team, err = claim.GetTeam(); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated team: %s", err.Error())}) return } if e.Exercice, err = claim.GetExercice(); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated exercice: %s", err.Error())}) return } if e.Assignee, err = claim.GetAssignee(); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated assignee: %s", err.Error())}) return } if e.Descriptions, err = claim.GetDescriptions(); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find claim's descriptions: %s", err.Error())}) return } e.LastUpdate = e.Creation for _, d := range e.Descriptions { if d.Date.After(e.LastUpdate) { e.LastUpdate = d.Date } } e.Id = claim.Id e.IdAssignee = claim.IdAssignee e.IdTeam = claim.IdTeam e.IdExercice = claim.IdExercice e.Subject = claim.Subject e.Creation = claim.Creation e.State = claim.State e.Priority = claim.Priority c.JSON(http.StatusOK, e) } type ClaimUploaded struct { fic.Claim Whoami *int64 `json:"whoami"` } func newClaim(c *gin.Context) { var uc ClaimUploaded err := c.ShouldBindJSON(&uc) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } if uc.Subject == "" { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Claim's subject cannot be empty."}) return } var t *fic.Team if uc.IdTeam != nil { if team, err := fic.GetTeam(*uc.IdTeam); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated team: %s", err.Error())}) return } else { t = team } } else { t = nil } var e *fic.Exercice if uc.IdExercice != nil { if exercice, err := fic.GetExercice(*uc.IdExercice); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated exercice: %s", err.Error())}) return } else { e = exercice } } else { e = nil } var a *fic.ClaimAssignee if uc.IdAssignee != nil { if assignee, err := fic.GetAssignee(*uc.IdAssignee); err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())}) return } else { a = assignee } } else { a = nil } if uc.Priority == "" { uc.Priority = "medium" } claim, err := fic.NewClaim(uc.Subject, t, e, a, uc.Priority) if err != nil { log.Println("Unable to newClaim:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register new claim"}) return } c.JSON(http.StatusOK, claim) } func clearClaims(c *gin.Context) { nb, err := fic.ClearClaims() if err != nil { log.Printf("Unable to clearClaims: %s", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims clearing."}) return } c.JSON(http.StatusOK, nb) } func generateTeamIssuesFile(team fic.Team) error { if generation.GeneratorSocket == "" { if my, err := team.MyIssueFile(); err != nil { return fmt.Errorf("Unable to generate issue FILE (tid=%d): %w", team.Id, err) } else if j, err := json.Marshal(my); err != nil { return fmt.Errorf("Unable to encode issues' file JSON: %w", err) } else if err = ioutil.WriteFile(path.Join(TeamsDir, fmt.Sprintf("%d", team.Id), "issues.json"), j, 0644); err != nil { return fmt.Errorf("Unable to write issues' file: %w", err) } } else { resp, err := generation.PerformGeneration(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id}) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { v, _ := ioutil.ReadAll(resp.Body) return fmt.Errorf("%s", string(v)) } } return nil } func addClaimDescription(c *gin.Context) { claim := c.MustGet("claim").(*fic.Claim) var ud fic.ClaimDescription err := c.ShouldBindJSON(&ud) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } assignee, err := fic.GetAssignee(ud.IdAssignee) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())}) return } description, err := claim.AddDescription(ud.Content, assignee, ud.Publish) if err != nil { log.Println("Unable to addClaimDescription:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add description"}) return } if team, _ := claim.GetTeam(); team != nil { err = generateTeamIssuesFile(*team) if err != nil { log.Println("Unable to generateTeamIssuesFile after addClaimDescription:", err.Error()) } } c.JSON(http.StatusOK, description) } func updateClaimDescription(c *gin.Context) { claim := c.MustGet("claim").(*fic.Claim) var ud fic.ClaimDescription err := c.ShouldBindJSON(&ud) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } if _, err := ud.Update(); err != nil { log.Println("Unable to updateClaimDescription:", err.Error()) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during claim description updating."}) return } if team, _ := claim.GetTeam(); team != nil { err = generateTeamIssuesFile(*team) if err != nil { log.Println("Unable to generateTeamIssuesFile:", err.Error()) } } c.JSON(http.StatusOK, ud) } func updateClaim(c *gin.Context) { claim := c.MustGet("claim").(*fic.Claim) var uc ClaimUploaded err := c.ShouldBindJSON(&uc) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } uc.Id = claim.Id _, err = uc.Update() if err != nil { log.Printf("Unable to updateClaim: %s", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim update."}) return } if claim.State != uc.State { if uc.Whoami != nil { if assignee, err := fic.GetAssignee(*uc.Whoami); err == nil { claim.AddDescription(fmt.Sprintf("%s a changé l'état de la tâche vers %q (était %q).", assignee.Name, uc.State, claim.State), assignee, true) } } } if claim.IdAssignee != uc.IdAssignee { if uc.Whoami != nil { if whoami, err := fic.GetAssignee(*uc.Whoami); err == nil { if uc.IdAssignee != nil { if assignee, err := fic.GetAssignee(*uc.IdAssignee); err == nil { if assignee.Id != whoami.Id { claim.AddDescription(fmt.Sprintf("%s a assigné la tâche à %s.", whoami.Name, assignee.Name), whoami, false) } else { claim.AddDescription(fmt.Sprintf("%s s'est assigné la tâche.", assignee.Name), whoami, false) } } } else { claim.AddDescription(fmt.Sprintf("%s a retiré l'attribution de la tâche.", whoami.Name), whoami, false) } } } } if team, _ := claim.GetTeam(); team != nil { err = generateTeamIssuesFile(*team) } c.JSON(http.StatusOK, uc) } func deleteClaim(c *gin.Context) { claim := c.MustGet("claim").(*fic.Claim) if nb, err := claim.Delete(); err != nil { log.Println("Unable to deleteClaim:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim deletion."}) return } else { c.JSON(http.StatusOK, nb) } } func getAssignees(c *gin.Context) { assignees, err := fic.GetAssignees() if err != nil { log.Println("Unable to getAssignees:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignees retrieval."}) return } c.JSON(http.StatusOK, assignees) } func showClaimAssignee(c *gin.Context) { c.JSON(http.StatusOK, c.MustGet("claim-assignee").(*fic.ClaimAssignee)) } func newAssignee(c *gin.Context) { var ua fic.ClaimAssignee err := c.ShouldBindJSON(&ua) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } assignee, err := fic.NewClaimAssignee(ua.Name) if err != nil { log.Println("Unable to newAssignee:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignee creation."}) return } c.JSON(http.StatusOK, assignee) } func updateClaimAssignee(c *gin.Context) { assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee) var ua fic.ClaimAssignee err := c.ShouldBindJSON(&ua) if err != nil { c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) return } ua.Id = assignee.Id if _, err := ua.Update(); err != nil { log.Println("Unable to updateClaimAssignee:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim assignee update."}) return } c.JSON(http.StatusOK, ua) } func deleteClaimAssignee(c *gin.Context) { assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee) if _, err := assignee.Delete(); err != nil { log.Println("Unable to deleteClaimAssignee:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during claim assignee deletion: %s", err.Error())}) return } c.JSON(http.StatusOK, true) }