diff --git a/admin/api/qa.go b/admin/api/qa.go new file mode 100644 index 00000000..20166cc4 --- /dev/null +++ b/admin/api/qa.go @@ -0,0 +1,84 @@ +package api + +import ( + "encoding/json" + "errors" + "strconv" + + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.POST("/api/qa/", apiHandler(importExerciceQA)) + router.POST("/api/qa/:qid/comments", apiHandler(qaHandler(importQAComment))) +} + +func qaHandler(f func(fic.QAQuery, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) { + return func(ps httprouter.Params, body []byte) (interface{}, error) { + if qid, err := strconv.ParseInt(string(ps.ByName("qid")), 10, 64); err != nil { + return nil, err + } else if query, err := fic.GetQAQuery(qid); err != nil { + return nil, err + } else { + return f(query, body) + } + } +} + +func importExerciceQA(_ httprouter.Params, body []byte) (interface{}, error) { + // Create a new query + var uq fic.QAQuery + if err := json.Unmarshal(body, &uq); err != nil { + return nil, err + } + + var exercice fic.Exercice + var err error + if uq.IdExercice == 0 { + return nil, errors.New("id_exercice not filled") + } else if exercice, err = fic.GetExercice(uq.IdExercice); err != nil { + return nil, err + } + + if len(uq.State) == 0 { + return nil, errors.New("State not filled") + } + + if len(uq.Subject) == 0 { + return nil, errors.New("Subject not filled") + } + + if qa, err := exercice.NewQAQuery(uq.Subject, uq.IdTeam, uq.User, uq.State); err != nil { + return nil, err + } else { + qa.Creation = uq.Creation + qa.Solved = uq.Solved + qa.Closed = qa.Closed + + _, err = qa.Update() + return qa, err + } +} + +func importQAComment(query fic.QAQuery, body []byte) (interface{}, error) { + // Create a new query + var uc fic.QAComment + if err := json.Unmarshal(body, &uc); err != nil { + return nil, err + } + + if len(uc.Content) == 0 { + return nil, errors.New("Empty comment") + } + + if qac, err := query.AddComment(uc.Content, uc.IdTeam, uc.User); err != nil { + return nil, err + } else { + qac.Date = uc.Date + + _, err = qac.Update() + return qac, err + } +} diff --git a/libfic/db.go b/libfic/db.go index 77005d9d..41ad70af 100644 --- a/libfic/db.go +++ b/libfic/db.go @@ -422,7 +422,7 @@ CREATE TABLE IF NOT EXISTS claim_descriptions( CREATE TABLE IF NOT EXISTS exercices_qa( id_qa INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_exercice INTEGER NOT NULL, - id_team INTEGER NOT NULL, + id_team INTEGER NULL, authuser VARCHAR(255) NOT NULL, subject VARCHAR(255) NOT NULL, creation TIMESTAMP NOT NULL, @@ -439,7 +439,7 @@ CREATE TABLE IF NOT EXISTS exercices_qa( CREATE TABLE IF NOT EXISTS qa_comments( id_comment INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_qa INTEGER NOT NULL, - id_team INTEGER NOT NULL, + id_team INTEGER NULL, authuser VARCHAR(255) NOT NULL, date TIMESTAMP NOT NULL, content TEXT NOT NULL, diff --git a/libfic/qa.go b/libfic/qa.go index 7e60f18e..25980a77 100644 --- a/libfic/qa.go +++ b/libfic/qa.go @@ -9,7 +9,7 @@ import ( type QAQuery struct { Id int64 `json:"id"` IdExercice int64 `json:"id_exercice"` - IdTeam int64 `json:"id_team"` + IdTeam *int64 `json:"id_team"` User string `json:"user"` Creation time.Time `json:"creation"` State string `json:"state"` @@ -91,7 +91,7 @@ func (e Exercice) GetQAQuery(id int64) (q QAQuery, err error) { } // NewQAQuery creates and fills a new struct QAQuery and registers it into the database. -func (e Exercice) NewQAQuery(subject string, teamId int64, user string, state string) (QAQuery, error) { +func (e Exercice) NewQAQuery(subject string, teamId *int64, user string, state string) (QAQuery, error) { if res, err := DBExec("INSERT INTO exercices_qa (id_exercice, id_team, authuser, creation, state, subject) VALUES (?, ?, ?, ?, ?, ?)", e.Id, teamId, user, time.Now(), state, subject); err != nil { return QAQuery{}, err } else if qid, err := res.LastInsertId(); err != nil { @@ -139,7 +139,7 @@ func ClearQAQueries() (int64, error) { // QAComment represents some text describing a QAQuery. type QAComment struct { Id int64 `json:"id"` - IdTeam int64 `json:"id_team"` + IdTeam *int64 `json:"id_team"` User string `json:"user"` Date time.Time `json:"date"` Content string `json:"content"` @@ -172,7 +172,7 @@ func (q QAQuery) GetComment(id int64) (c QAComment, err error) { } // AddComment append in the database a new description; then returns the corresponding structure. -func (q QAQuery) AddComment(content string, teamId int64, user string) (QAComment, error) { +func (q QAQuery) AddComment(content string, teamId *int64, user string) (QAComment, error) { if res, err := DBExec("INSERT INTO qa_comments (id_qa, id_team, authuser, date, content) VALUES (?, ?, ?, ?, ?)", q.Id, teamId, user, time.Now(), content); err != nil { return QAComment{}, err } else if cid, err := res.LastInsertId(); err != nil { diff --git a/qa-fill-todo.sh b/qa-fill-todo.sh new file mode 100755 index 00000000..a4fd2935 --- /dev/null +++ b/qa-fill-todo.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +NB_THEMES_TODO=3 +MIN_LVL_TODO=1 +MAX_LVL_TODO=3 + + +MIN_EXO_TODO=$(($MIN_LVL_TODO - 1)) +MAX_EXO_TODO=$(($MAX_LVL_TODO - 1)) + +curl -s http://127.0.0.1:8081/api/teams/ | jq -r '.[] | [(.id | tostring), " ", .name] | add' | while read TEAMID TEAMSTR +do + if echo $TEAMSTR | grep "FIC Groupe" > /dev/null 2> /dev/null + then + GRPFIC=$(echo $TEAMSTR | sed -r 's/FIC Groupe //') + + THEMES_TO_TESTS=$(curl -s http://127.0.0.1:8081/api/themes | jq -r '.[] | [(.id | tostring), " ", (.path | split("-") | .[0])] | add' | grep -v " $GRPFIC" | shuf | head -n $NB_THEMES_TODO | cut -d " " -f 1 | xargs | sed 's/^/"/;s/ /","/g;s/$/"/') + + curl -s http://127.0.0.1:8081/api/themes.json | jq -r '.['$THEMES_TO_TESTS'] | .exercices | keys | .['$MIN_EXO_TODO:$MAX_EXO_TODO'] | .[]' | while read EXID + do + #curl -X POST -d @- -H "X-FIC-Team: nemunaire" http://127.0.0.1:8083/api/qa_work.json < /dev/null 2> /dev/null; then echo TEAM_$TEAM=$ID | sed -r 's/FIC Groupe //'; fi; done` + +# Add their themes and exercices +curl -s http://127.0.0.1:8081/api/themes | jq '.[].id' | while read tid +do + TEAM="TEAM_$(curl -s http://127.0.0.1:8081/api/themes/$tid | jq -r .path | sed -r 's/-.*$//')" + curl -s http://127.0.0.1:8081/api/themes/$tid/exercices | jq .[].id | while read ex + do + curl -X POST -d @- -H "X-FIC-Team: nemunaire" http://127.0.0.1:8083/api/qa_my_exercices.json <&2 echo "Please give DB to read from as argument"; exit 1; } + +MYSQL_USER="${MYSQL_USER:-fic}" +MYSQL_PASSWORD="${MYSQL_PASSWORD:-fic}" +MYSQL_DATABASE="$1" + +QA_ADMIN="${QA_ADMIN:-nemunaire@nemunai.re}" + +EXERCICES=$(curl -s -H "X-FIC-Team: ${QA_ADMIN}" http://127.0.0.1:8083/api/exercices/) +TEAMS=$(curl -s http://127.0.0.1:8081/api/teams/ | jq -r '.[].id' | while read IDTEAM; do curl -s http://127.0.0.1:8081/api/teams/$IDTEAM/associations | jq -r ".[] | {login: ., id_team: \"$IDTEAM\"}"; done) + +echo "select Q.*, T.name, E.title from exercices_qa Q INNER JOIN exercices E ON E.id_exercice = Q.id_exercice INNER JOIN themes T ON T.id_theme = E.id_theme;" | mysql --skip-column-names -u "${MYSQL_USER}" --password="${MYSQL_PASSWORD}" "${MYSQL_DATABASE}" | tr '\t' '|' | while IFS='|' read IDQ IDEXERCICE IDTEAM REMOTE_USER SUBJECT CREATION STATE SOLVED CLOSED THEME EXERCICE_TITLE +do + # Search exercice by title + IDEXERCICE=$(echo "${EXERCICES}" | jq -r ".[] | select(.title == \"${EXERCICE_TITLE}\") | .id") + [ -z "$IDEXERCICE" ] && { >&2 echo "QA query $IDQ: no exercice match"; continue; } + + # Search user in teams (if not found, set to null, this is allowed) + IDTEAM=$(echo "${TEAMS}" | jq -r "select(.login == \"${REMOTE_USER}\") | .id_team") + [ -z "$IDTEAM" ] && IDTEAM=null + + CREATION=$(echo ${CREATION}Z | sed 's/ /T/') + [ "$SOLVED" == "NULL" ] && SOLVED=null || SOLVED="\"$(echo ${SOLVED}Z | sed 's/ /T/')\"" + [ "$CLOSED" == "NULL" ] && CLOSED=null || CLOSED="\"$(echo ${CLOSED}Z | sed 's/ /T/')\"" + + SUBJECT=$(cat < /dev/null +{ + "id_team": $IDTEAM, + "user": "$REMOTE_USER", + "date": "$DATEC", + "content": $COMMENT +} +EOF + done +done diff --git a/qa/api/handler.go b/qa/api/handler.go index 1d76e57f..f823a853 100644 --- a/qa/api/handler.go +++ b/qa/api/handler.go @@ -42,9 +42,11 @@ func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, htt } else if teamid, err = strconv.ParseInt(ficteam, 10, 64); err != nil { if lnk, err := os.Readlink(path.Join(TeamsDir, ficteam)); err != nil { log.Printf("[ERR] Unable to readlink %q: %s\n", path.Join(TeamsDir, ficteam), err) + http.Error(w, fmt.Sprintf("{errmsg:\"Unable to validate authentication.\"}"), http.StatusInternalServerError) return } else if teamid, err = strconv.ParseInt(lnk, 10, 64); err != nil { log.Printf("[ERR] Error during ParseInt team %q: %s\n", lnk, err) + http.Error(w, fmt.Sprintf("{errmsg:\"Unable to validate authentication.\"}"), http.StatusInternalServerError) return } } diff --git a/qa/api/qa.go b/qa/api/qa.go index 373b5c53..26b96703 100644 --- a/qa/api/qa.go +++ b/qa/api/qa.go @@ -74,7 +74,7 @@ func createExerciceQA(u QAUser, exercice fic.Exercice, body []byte) (interface{} } } - if qa, err := exercice.NewQAQuery(uq.Subject, u.TeamId, u.User, uq.State); err != nil { + if qa, err := exercice.NewQAQuery(uq.Subject, &u.TeamId, u.User, uq.State); err != nil { return nil, err } else { var uc fic.QAComment @@ -83,7 +83,7 @@ func createExerciceQA(u QAUser, exercice fic.Exercice, body []byte) (interface{} } if uc.Content != "" { - _, err = qa.AddComment(uc.Content, u.TeamId, u.User) + _, err = qa.AddComment(uc.Content, &u.TeamId, u.User) } return qa, err @@ -132,7 +132,7 @@ func createQAComment(u QAUser, query fic.QAQuery, exercice fic.Exercice, body [] return nil, errors.New("Empty comment") } - return query.AddComment(uc.Content, u.TeamId, u.User) + return query.AddComment(uc.Content, &u.TeamId, u.User) } func deleteQAComment(u QAUser, comment fic.QAComment, query fic.QAQuery, exercice fic.Exercice, body []byte) (interface{}, error) {