diff --git a/ui/src/lib/works.js b/ui/src/lib/works.js index 1d5976c..c87dd0f 100644 --- a/ui/src/lib/works.js +++ b/ui/src/lib/works.js @@ -109,6 +109,18 @@ export class Work { } } + async addMissingGrades() { + const res = await fetch(`api/works/${this.id}/grades`, { + method: 'PATCH', + headers: {'Accept': 'application/json'}, + }); + if (res.status == 200) { + return (await res.json()).map((g) => new Grade(g)); + } else { + throw new Error((await res.json()).errmsg); + } + } + async getSubmission(uid) { const res = await fetch(uid?`api/users/${uid}/works/${this.id}/submission`:`api/works/${this.id}/submission`, { headers: {'Accept': 'application/json'} diff --git a/ui/src/routes/works/[wid]/+page.svelte b/ui/src/routes/works/[wid]/+page.svelte index 9048e5f..df2a76f 100644 --- a/ui/src/routes/works/[wid]/+page.svelte +++ b/ui/src/routes/works/[wid]/+page.svelte @@ -40,6 +40,11 @@ stats.mean = sum / grades.length; }); } + + async function addMissingStudents(w) { + await w.addMissingGrades(); + refresh_grades(w); + } {#if $user && $user.is_admin} @@ -72,12 +77,22 @@ {#if stats.mean > 0}(moyenne : {Math.round(stats.mean*100)/100}, min : {stats.min}, max : {stats.max}){/if} - +
+ + +
{#await gradesP} diff --git a/users.go b/users.go index 5ed899b..26dcfe3 100644 --- a/users.go +++ b/users.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net/http" + "regexp" "strconv" "strings" "time" @@ -169,6 +170,33 @@ func getUsers() (users []User, err error) { } } +func getFilteredUsers(promo uint, group string) (users []User, err error) { + // Avoid SQL injection: check group name doesn't contains harmful content + var validGroup = regexp.MustCompile(`^[a-z0-9-]*$`) + if !validGroup.MatchString(group) { + return nil, fmt.Errorf("%q is not a valid group name", group) + } + + if rows, errr := DBQuery("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE promo = ? AND groups LIKE '%,"+group+",%' ORDER BY promo DESC, id_user DESC", promo); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var u User + if err = rows.Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin); err != nil { + return + } + users = append(users, u) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + func getPromos() (promos []uint, err error) { if rows, errr := DBQuery("SELECT DISTINCT promo FROM users ORDER BY promo DESC"); errr != nil { return nil, errr diff --git a/works.go b/works.go index e2d857d..6909cec 100644 --- a/works.go +++ b/works.go @@ -179,6 +179,49 @@ func declareAPIAdminWorksRoutes(router *gin.RouterGroup) { c.JSON(http.StatusOK, grades) }) + worksRoutes.PATCH("/grades", func(c *gin.Context) { + w := c.MustGet("work").(*Work) + + // Fetch existing grades + grades, err := w.GetGrades("") + if err != nil { + log.Printf("Unable to GetGrades(wid=%d): %s", w.Id, err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during grades retrieval."}) + return + } + + // Create an index + known_users := map[int64]bool{} + for _, g := range grades { + known_users[g.IdUser] = true + } + + // Fetch students list registered for this course + users, err := getFilteredUsers(w.Promo, w.Group) + if err != nil { + log.Printf("Unable to getFilteredUsers(%d, %s): %s", w.Promo, w.Group, err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during users retrieval."}) + return + } + + var toAdd []WorkGrade + for _, user := range users { + if _, ok := known_users[user.Id]; !ok { + toAdd = append(toAdd, WorkGrade{ + IdUser: user.Id, + Login: user.Login, + Grade: 0, + Comment: "- Non rendu -", + }) + } + } + + if len(toAdd) > 0 { + w.AddGrades(toAdd) + } + + c.JSON(http.StatusOK, toAdd) + }) worksRoutes.PUT("/grades", func(c *gin.Context) { w := c.MustGet("work").(*Work)