package main import ( "bufio" "encoding/csv" "encoding/json" "flag" "fmt" "io/ioutil" "log" "net/http" "os" "path" "path/filepath" "strings" "time" ) var gdprExceptions arrayFlags var students [][]string var rendusDir string var title string func Serve(w http.ResponseWriter, r *http.Request) { log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) w.Header().Set("Content-Type", "text/html") w.Write([]byte(` ` + title + `

` + title + `

`)) } func genStudents() map[string]map[string]*Submission { ret := map[string]map[string]*Submission{} for _, student := range students { login := student[2] if gdprExceptions.Contains(login) { continue } ret[login] = genStudent(login) } return ret } func ServeJSON(w http.ResponseWriter, r *http.Request) { if addr := r.Header.Get("X-Forwarded-For"); addr != "" { r.RemoteAddr = addr } log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) w.Header().Set("Content-Type", "application/json") if j, err := json.Marshal(genStudents()); err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError) } else { w.WriteHeader(http.StatusOK) w.Write(j) } } type Submission struct { Date time.Time `json:"date"` Hash string `json:"hash"` } func genStudent(login string) map[string]*Submission { ret := map[string]*Submission{} if dirs, err := ioutil.ReadDir(rendusDir); err == nil { for _, dir := range dirs { if dir.IsDir() { p := path.Join(rendusDir, dir.Name(), login) if fi, err := os.Stat(p); err == nil { ret[dir.Name()] = new(Submission) ret[dir.Name()].Date = fi.ModTime() if lnk, err := os.Readlink(p); err == nil { ret[dir.Name()].Hash = strings.TrimPrefix(lnk, login+".") } } else { ret[dir.Name()] = nil } } } } return ret } func ServeJSONStudent(w http.ResponseWriter, r *http.Request) { if addr := r.Header.Get("X-Forwarded-For"); addr != "" { r.RemoteAddr = addr } log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") login := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), ".json") var ret []byte status := http.StatusOK if len(r.URL.Query().Get("rendu")) > 0 { limit := r.URL.Query().Get("rendu") for k, v := range genStudent(login) { if limit == k { r, err := json.Marshal(v) if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError) return } if v == nil { status = http.StatusNotFound } ret = append(ret, r...) } } } else { var err error ret, err = json.Marshal(genStudent(login)) if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError) return } } w.WriteHeader(status) w.Write(ret) } type arrayFlags []string func (i *arrayFlags) String() string { return fmt.Sprintf("%v", *i) } func (i *arrayFlags) Set(value string) error { *i = append(*i, value) return nil } func (i *arrayFlags) Contains(value string) bool { for _, v := range *i { if v == value { return true } } return false } func main() { var studentsFiles arrayFlags var bind = flag.String("bind", "0.0.0.0:8081", "Bind port/socket") flag.Var(&studentsFiles, "students", "Path to a CSV file containing in the third column the name of the directory to look for (can be repeated multiple times)") flag.Var(&gdprExceptions, "gdpr-exception", "Login that should not be listed in global rendus.json (can be repeated multiple times)") flag.StringVar(&rendusDir, "path", "./rendu/", "Path to the submissions directory (each subdirectory is a project)") flag.StringVar(&title, "title", "Rendus VIRLI", "Title of the page") flag.Parse() var err error if rendusDir, err = filepath.Abs(rendusDir); err != nil { log.Fatal(err) } else if dirs, err := ioutil.ReadDir(rendusDir); err != nil { log.Fatal(err) } else { var projects []string nbProj := 0 for _, dir := range dirs { if dir.IsDir() { projects = append(projects, dir.Name()) nbProj++ } } log.Println(nbProj, "projects found:", projects) } // Read and parse students list log.Println("Reading students files...") for _, studentsFile := range studentsFiles { log.Println("Reading", studentsFile, "...") if studentsFile, err = filepath.Abs(studentsFile); err != nil { log.Fatal(err) } else if fi, err := os.Open(studentsFile); err != nil { log.Fatal(err) } else { var nstudents [][]string r := csv.NewReader(bufio.NewReader(fi)) if nstudents, err = r.ReadAll(); err != nil { log.Fatal(err) } students = append(students, nstudents...) } } log.Println(len(students), "students loaded.") log.Println("Registering handlers...") http.HandleFunc("/", Serve) http.HandleFunc("/rendus.json", ServeJSON) for _, student := range students { http.HandleFunc("/"+student[2]+".json", ServeJSONStudent) } log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) if err := http.ListenAndServe(*bind, nil); err != nil { log.Fatal("Unable to listen and serve: ", err) } }