package main import ( "crypto/hmac" "crypto/sha512" "encoding/json" "fmt" "strings" "time" "github.com/julienschmidt/httprouter" ) func init() { router.GET("/api/progress/", apiHandler( func(httprouter.Params, []byte) (interface{}, error) { if stds, err := getStudents(); err != nil { return nil, err } else { ret := map[string]map[string]UnlockedChallenge{} for _, std := range stds { if sts, err := std.getStates(); err == nil { ret[std.Login] = map[string]UnlockedChallenge{} for _, s := range sts { ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s } } } return ret, nil } })) router.GET("/api/students/", apiHandler( func(httprouter.Params, []byte) (interface{}, error) { return getStudents() })) router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent))) router.GET("/api/students/:sid/", apiHandler(studentHandler( func(std Student, _ []byte) (interface{}, error) { return std, nil }))) router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent)))) router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler( func(std Student, _ []byte) (interface{}, error) { return std.Delete() })))) router.GET("/api/students/:sid/progress", apiHandler(studentHandler( func(std Student, _ []byte) (interface{}, error) { ret := map[string]UnlockedChallenge{} if sts, err := std.getStates(); err == nil { for _, s := range sts { ret[fmt.Sprintf("%d", s.Challenge)] = s } } return ret, nil }))) } type Student struct { Id int64 `json:"id"` Login string `json:"login"` Time *time.Time `json:"time"` IP *string `json:"ip"` MAC *string `json:"mac"` } type uploadedStudent struct { Login string `json:"login"` IP string `json:"ip"` MAC string `json:"mac"` } func getStudents() (students []Student, err error) { if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var s Student if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC); err != nil { return } students = append(students, s) } if err = rows.Err(); err != nil { return } return } } func getStudent(id int) (s Student, err error) { err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC) return } func getStudentByLogin(login string) (s Student, err error) { err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC) return } func studentExists(login string) bool { var z int err := DBQueryRow("SELECT 1 FROM students WHERE login=?", login).Scan(&z) return err == nil && z == 1 } func NewStudent(login string) (Student, error) { t := time.Now() if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil { return Student{}, err } else if sid, err := res.LastInsertId(); err != nil { return Student{}, err } else { return Student{sid, login, &t, nil, nil}, nil } } func (s Student) GetPKey() []byte { return hmac.New(sha512.New512_224, []byte(sharedSecret)).Sum([]byte(s.Login)) } func (s Student) Update() (int64, error) { if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } func (s Student) Delete() (int64, error) { if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } func ClearStudents() (int64, error) { if res, err := DBExec("DELETE FROM students"); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } func createStudent(_ httprouter.Params, body []byte) (interface{}, error) { var err error var std uploadedStudent if err = json.Unmarshal(body, &std); err != nil { return nil, err } var exist Student if exist, err = getStudentByLogin(strings.TrimSpace(std.Login)); err != nil { if exist, err = NewStudent(strings.TrimSpace(std.Login)); err != nil { return nil, err } } exist.registerAccess(std.IP, std.MAC) ip := fmt.Sprintf("172.23.0.%d", exist.IPSuffix()) exist.IP = &ip return exist, nil } func updateStudent(current Student, body []byte) (interface{}, error) { var new Student if err := json.Unmarshal(body, &new); err != nil { return nil, err } current.Login = new.Login current.Time = new.Time return current.Update() } type UnlockedChallenge struct { Id int64 `json:"id"` IdStudent int64 `json:"id_student"` Challenge int `json:"challenge"` Time time.Time `json:"time"` Value string `json:"value,omitempty"` } func (s Student) getStates() (ucs []UnlockedChallenge, err error) { if rows, errr := DBQuery("SELECT id_st, challenge, time FROM student_challenges WHERE id_student = ?", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var u UnlockedChallenge u.IdStudent = s.Id if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil { return } ucs = append(ucs, u) } if err = rows.Err(); err != nil { return } return } } func (s Student) getStatesByChallenge() (ucs []UnlockedChallenge, err error) { if rows, errr := DBQuery("SELECT id_st, challenge, MIN(time), value FROM student_challenges WHERE id_student = ? GROUP BY challenge, id_student", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { var u UnlockedChallenge u.IdStudent = s.Id if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil { return } ucs = append(ucs, u) } if err = rows.Err(); err != nil { return } return } } func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) { if res, err := DBExec("INSERT INTO student_challenges (id_student, challenge, time, value) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), value); err != nil { return UnlockedChallenge{}, err } else if utid, err := res.LastInsertId(); err != nil { return UnlockedChallenge{}, err } else { return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err } } func (s Student) UpdateUnlockedChallenge(challenge int, value string) (UnlockedChallenge, error) { if _, err := DBExec("UPDATE student_challenges SET time = ?, value = ? WHERE id_student = ? AND challenge = ?", time.Now(), value, s.Id, challenge); err != nil { return UnlockedChallenge{}, err } else { return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err } } func (s Student) registerAccess(ip, mac string) error { if res, err := DBExec("INSERT INTO student_login (id_student, ip, mac, time) VALUES (?, ?, ?, ?)", s.Id, ip, mac, time.Now()); err != nil { return err } else if _, err := res.LastInsertId(); err != nil { return err } else { return err } }