admin/api: use gorilla/mux instead of Go router

This commit is contained in:
nemunaire 2016-12-08 09:12:18 +01:00
parent 3e74f5f9ef
commit 173dafa69e
18 changed files with 643 additions and 720 deletions

31
admin/api/certificate.go Normal file
View file

@ -0,0 +1,31 @@
package api
import (
"io/ioutil"
"os"
"srs.epita.fr/fic-server/libfic"
)
func init() {
router.Path("/ca").Methods("GET").HandlerFunc(apiHandler(genCA))
rt := router.PathPrefix("/teams/{tid}/certificate").Subrouter()
rt.Path("/").Methods("GET").HandlerFunc(apiHandler(teamHandler(GetTeamCertificate)))
rt.Path("/generate").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { return team.GenerateCert(), nil })))
rt.Path("/revoke").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) { return team.RevokeCert(), nil })))
}
func genCA(args map[string]string, body []byte) (interface{}, error) {
return fic.GenerateCA(), nil
}
func GetTeamCertificate(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
if fd, err := os.Open("../PKI/pkcs/" + team.Name + ".p12"); err == nil {
return ioutil.ReadAll(fd)
} else {
return nil, err
}
}

17
admin/api/events.go Normal file
View file

@ -0,0 +1,17 @@
package api
import (
"srs.epita.fr/fic-server/libfic"
)
func init() {
router.Path("/events/").Methods("GET").HandlerFunc(apiHandler(getEvents))
}
func getEvents(args map[string]string, body []byte) (interface{}, error) {
if evts, err := fic.GetEvents(); err != nil {
return nil, err
} else {
return evts, nil
}
}

117
admin/api/exercice.go Normal file
View file

@ -0,0 +1,117 @@
package api
import (
"encoding/json"
"errors"
"strconv"
"srs.epita.fr/fic-server/libfic"
)
func init() {
router.Path("/exercices/").Methods("GET").HandlerFunc(apiHandler(listExercices))
re := router.Path("/exercices/{eid:[0-9]+}").Subrouter()
re.Methods("GET").HandlerFunc(apiHandler(exerciceHandler(showExercice)))
re.Methods("PUT").HandlerFunc(apiHandler(updateExercice))
re.Methods("DELETE").HandlerFunc(apiHandler(deleteExercice))
}
func listExercices(args map[string]string, body []byte) (interface{}, error) {
// List all exercices
return fic.GetExercices()
}
func showExercice(exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
return exercice, nil
}
func deleteExercice(args map[string]string, body []byte) (interface{}, error) {
if eid, err := strconv.Atoi(args["eid"]); err != nil {
return nil, err
} else if exercice, err := fic.GetExercice(int64(eid)); err != nil {
return nil, err
} else {
return exercice.Delete()
}
}
type uploadedExercice struct {
Title string
Statement string
Depend *int64
Gain int
VideoURI string
}
func updateExercice(args map[string]string, body []byte) (interface{}, error) {
if eid, err := strconv.Atoi(args["eid"]); err != nil {
return nil, err
} else if exercice, err := fic.GetExercice(int64(eid)); err != nil {
return nil, err
} else {
// Update an exercice
var ue uploadedExercice
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
if len(ue.Title) == 0 {
return nil, errors.New("Exercice's title not filled")
}
if ue.Depend != nil {
if _, err := fic.GetExercice(*ue.Depend); err != nil {
return nil, err
}
}
exercice.Title = ue.Title
exercice.Statement = ue.Statement
exercice.Depend = ue.Depend
exercice.Gain = int64(ue.Gain)
exercice.VideoURI = ue.VideoURI
return exercice.Update()
}
}
func createExercice(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) {
// Create a new exercice
var ue uploadedExercice
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
if len(ue.Title) == 0 {
return nil, errors.New("Title not filled")
}
var depend *fic.Exercice = nil
if ue.Depend != nil {
if d, err := fic.GetExercice(*ue.Depend); err != nil {
return nil, err
} else {
depend = &d
}
}
return theme.AddExercice(ue.Title, ue.Statement, depend, ue.Gain, ue.VideoURI)
}
type uploadedKey struct {
Name string
Key string
}
func createExerciceKey(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
var uk uploadedKey
if err := json.Unmarshal(body, &uk); err != nil {
return nil, err
}
if len(uk.Key) == 0 {
return nil, errors.New("Key not filled")
}
return exercice.AddRawKey(uk.Name, uk.Key)
}

89
admin/api/file.go Normal file
View file

@ -0,0 +1,89 @@
package api
import (
"bufio"
"crypto/sha512"
"encoding/base32"
"encoding/json"
"errors"
"log"
"net/http"
"os"
"path"
"strings"
"srs.epita.fr/fic-server/libfic"
)
var CloudDAVBase string
var CloudUsername string
var CloudPassword string
func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
var uf map[string]string
if err := json.Unmarshal(body, &uf); err != nil {
return nil, err
}
var hash [sha512.Size]byte
var logStr string
var fromURI string
var getFile func(string) (error)
if URI, ok := uf["URI"]; ok {
hash = sha512.Sum512([]byte(URI))
logStr = "Import file from Cloud: " + URI + " =>"
fromURI = URI
getFile = func(dest string) error { return getCloudFile(URI, dest); }
} else if path, ok := uf["path"]; ok {
hash = sha512.Sum512([]byte(path))
logStr = "Import file from local FS: " + path + " =>"
fromURI = path
getFile = func(dest string) error { return os.Symlink(path, dest); }
} else {
return nil, errors.New("URI or path not filled")
}
pathname := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.EncodeToString(hash[:])), path.Base(fromURI))
if _, err := os.Stat(pathname); os.IsNotExist(err) {
log.Println(logStr, pathname)
if err := os.MkdirAll(path.Dir(pathname), 0777); err != nil {
return nil, err
} else if err := getFile(pathname); err != nil {
return nil, err
}
}
return exercice.ImportFile(pathname, fromURI)
}
func getCloudFile(pathname string, dest string) error {
client := http.Client{}
if req, err := http.NewRequest("GET", CloudDAVBase+pathname, nil); err != nil {
return err
} else {
req.SetBasicAuth(CloudUsername, CloudPassword)
if resp, err := client.Do(req); err != nil {
return err
} else {
defer resp.Body.Close()
if fd, err := os.Create(dest); err != nil {
return err
} else {
defer fd.Close()
if resp.StatusCode != http.StatusOK {
return errors.New(resp.Status)
} else {
writer := bufio.NewWriter(fd)
reader := bufio.NewReader(resp.Body)
reader.WriteTo(writer)
writer.Flush()
}
}
}
}
return nil
}

131
admin/api/handlers.go Normal file
View file

@ -0,0 +1,131 @@
package api
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strconv"
"srs.epita.fr/fic-server/libfic"
"github.com/gorilla/mux"
)
type DispatchFunction func([]string, []byte) (interface{}, error)
func apiHandler(f func (map[string]string,[]byte) (interface{}, error)) func(http.ResponseWriter, *http.Request) {
return func(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", "application/json")
var ret interface{}
var err error = nil
// Read the body
if r.ContentLength < 0 || r.ContentLength > 6553600 {
http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}", err), http.StatusRequestEntityTooLarge)
return
}
var body []byte
if r.ContentLength > 0 {
tmp := make([]byte, 1024)
for {
n, err := r.Body.Read(tmp)
for j := 0; j < n; j++ {
body = append(body, tmp[j])
}
if err != nil || n <= 0 {
break
}
}
}
ret, err = f(mux.Vars(r), body)
// Format response
resStatus := http.StatusOK
if err != nil {
ret = map[string]string{"errmsg": err.Error()}
resStatus = http.StatusBadRequest
log.Println(r.RemoteAddr, resStatus, err.Error())
}
if ret == nil {
ret = map[string]string{"errmsg": "Page not found"}
resStatus = http.StatusNotFound
}
if str, found := ret.(string); found {
w.WriteHeader(resStatus)
io.WriteString(w, str)
} else if bts, found := ret.([]byte); found {
w.WriteHeader(resStatus)
w.Write(bts)
} else if j, err := json.Marshal(ret); err != nil {
http.Error(w, fmt.Sprintf("{\"errmsg\":\"%q\"}", err), http.StatusInternalServerError)
} else {
w.WriteHeader(resStatus)
w.Write(j)
}
}
}
func teamHandler(f func(fic.Team,map[string]string,[]byte) (interface{}, error)) func (map[string]string,[]byte) (interface{}, error) {
return func (args map[string]string, body []byte) (interface{}, error) {
if tid, err := strconv.Atoi(string(args["tid"])); err != nil {
if team, err := fic.GetTeamByInitialName(args["tid"]); err != nil {
return nil, err
} else {
return f(team, args, body)
}
} else if team, err := fic.GetTeam(tid); err != nil {
return nil, err
} else {
return f(team, args, body)
}
}
}
func themeHandler(f func(fic.Theme,map[string]string,[]byte) (interface{}, error)) func (map[string]string,[]byte) (interface{}, error) {
return func (args map[string]string, body []byte) (interface{}, error) {
if tid, err := strconv.Atoi(string(args["tid"])); err != nil {
return nil, err
} else if theme, err := fic.GetTheme(tid); err != nil {
return nil, err
} else {
return f(theme, args, body)
}
}
}
func exerciceHandler(f func(fic.Exercice,map[string]string,[]byte) (interface{}, error)) func (map[string]string,[]byte) (interface{}, error) {
return func (args map[string]string, body []byte) (interface{}, error) {
if eid, err := strconv.Atoi(string(args["eid"])); err != nil {
return nil, err
} else if exercice, err := fic.GetExercice(int64(eid)); err != nil {
return nil, err
} else {
return f(exercice, args, body)
}
}
}
func themedExerciceHandler(f func(fic.Theme,fic.Exercice,map[string]string,[]byte) (interface{}, error)) func (fic.Theme,map[string]string,[]byte) (interface{}, error) {
return func (theme fic.Theme, args map[string]string, body []byte) (interface{}, error) {
if eid, err := strconv.Atoi(string(args["eid"])); err != nil {
return nil, err
} else if exercice, err := fic.GetExercice(int64(eid)); err != nil {
return nil, err
} else {
return f(theme, exercice, args, body)
}
}
}
func notFound(args map[string]string, body []byte) (interface{}, error) {
return nil, nil
}

14
admin/api/router.go Normal file
View file

@ -0,0 +1,14 @@
package api
import (
"github.com/gorilla/mux"
)
var api_router = mux.NewRouter().StrictSlash(true)
var router = api_router.PathPrefix("/api/").Subrouter()
func Router() *mux.Router {
return api_router
}

43
admin/api/stats.go Normal file
View file

@ -0,0 +1,43 @@
package api
import (
"fmt"
"srs.epita.fr/fic-server/libfic"
)
type statsTheme struct {
SolvedByLevel []int `json:"solvedByLevel"`
}
type stats struct {
Themes map[string]statsTheme `json:"themes"`
TryRank []int64 `json:"tryRank"`
}
func genStats() (interface{}, error) {
ret := map[string]statsTheme{}
if themes, err := fic.GetThemes(); err != nil {
return nil, err
} else {
for _, theme := range themes {
if exercices, err := theme.GetExercices(); err != nil {
return nil, err
} else {
exos := map[string]fic.ExportedExercice{}
for _, exercice := range exercices {
exos[fmt.Sprintf("%d", exercice.Id)] = fic.ExportedExercice{
exercice.Title,
exercice.Gain,
exercice.SolvedCount(),
exercice.TriedTeamCount(),
}
}
ret[fmt.Sprintf("%d", theme.Id)] = statsTheme{}
}
}
return ret, nil
}
}

156
admin/api/team.go Normal file
View file

@ -0,0 +1,156 @@
package api
import (
"encoding/json"
"fmt"
"strings"
"srs.epita.fr/fic-server/libfic"
)
func init() {
rts := router.PathPrefix("/teams").Subrouter()
router.Path("/teams.json").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.ExportTeams() }))
rts.Path("/").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.GetTeams() }))
rts.Path("/binding").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return bindingTeams() }))
rts.Path("/nginx").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return nginxGenTeam() }))
rts.Path("/nginx-members").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return nginxGenMember() }))
rts.Path("/binding").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.GetTries(nil, nil) }))
rts.Path("/0/my.json").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.MyJSONTeam(nil, true) }))
rts.Path("/0/wait.json").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.MyJSONTeam(nil, false) }))
rts.Path("/0/stats.json").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.GetTeamsStats(nil) }))
rts.Path("/0/tries").Methods("GET").HandlerFunc(apiHandler(
func(map[string]string,[]byte) (interface{}, error) {
return fic.GetTries(nil, nil) }))
rt := rts.PathPrefix("/{tid}").Subrouter()
rt.Path("/").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return team, nil })))
rt.Path("/").Methods("DELETE").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return team.Delete() })))
rt.Path("/my.json").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return fic.MyJSONTeam(&team, true) })))
rt.Path("/wait.json").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return fic.MyJSONTeam(&team, false) })))
rt.Path("/stats.json").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return team.GetStats() })))
rt.Path("/tries").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return fic.GetTries(&team, nil) })))
rt.Path("/members").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return team.GetMembers() })))
rt.Path("/name").Methods("GET").HandlerFunc(apiHandler(teamHandler(
func(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
return team.Name, nil })))
}
func nginxGenMember() (string, error) {
if teams, err := fic.GetTeams(); err != nil {
return "", err
} else {
ret := ""
for _, team := range teams {
if members, err := team.GetMembers(); err == nil {
for _, member := range members {
ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%s\"; }\n", member.Nickname, team.InitialName)
}
} else {
return "", err
}
}
return ret, nil
}
}
func nginxGenTeam() (string, error) {
if teams, err := fic.GetTeams(); err != nil {
return "", err
} else {
ret := ""
for _, team := range teams {
ret += fmt.Sprintf(" if ($ssl_client_s_dn ~ \"/C=FR/ST=France/O=Epita/OU=SRS/CN=%s\") { set $team \"%s\"; }\n", team.InitialName, team.InitialName)
}
return ret, nil
}
}
func bindingTeams() (string, error) {
if teams, err := fic.GetTeams(); err != nil {
return "", err
} else {
ret := ""
for _, team := range teams {
if members, err := team.GetMembers(); err != nil {
return "", err
} else {
var mbs []string
for _, member := range members {
mbs = append(mbs, fmt.Sprintf("%s %s", member.Firstname, member.Lastname))
}
ret += fmt.Sprintf("%d;%s;%s\n", team.Id, team.Name, strings.Join(mbs, ";"))
}
}
return ret, nil
}
}
type uploadedTeam struct {
Name string
Color uint32
}
type uploadedMember struct {
Firstname string
Lastname string
Nickname string
Company string
}
func createTeam(args map[string]string, body []byte) (interface{}, error) {
var ut uploadedTeam
if err := json.Unmarshal(body, &ut); err != nil {
return nil, err
}
return fic.CreateTeam(ut.Name, ut.Color)
}
func addTeamMember(team fic.Team, args map[string]string, body []byte) (interface{}, error) {
var members []uploadedMember
if err := json.Unmarshal(body, &members); err != nil {
return nil, err
}
for _, member := range members {
team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company)
}
return team.GetMembers()
}

153
admin/api/theme.go Normal file
View file

@ -0,0 +1,153 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"srs.epita.fr/fic-server/libfic"
)
func init() {
router.Path("/themes/").Methods("GET").HandlerFunc(apiHandler(listThemes))
router.Path("/themes/").Methods("POST").HandlerFunc(apiHandler(createTheme))
router.Path("/themes.json").Methods("GET").HandlerFunc(apiHandler(exportThemes))
router.Path("/themes/files-bindings").Methods("GET").HandlerFunc(apiHandler(bindingFiles))
rt := router.PathPrefix("/themes/{tid:[0-9]+}").Subrouter()
rt.Path("/").Methods("GET").HandlerFunc(apiHandler(themeHandler(showTheme)))
rt.Path("/").Methods("PUT").HandlerFunc(apiHandler(themeHandler(updateTheme)))
rt.Path("/").Methods("DELETE").HandlerFunc(apiHandler(themeHandler(deleteTheme)))
rtes := rt.PathPrefix("/exercices").Subrouter()
rtes.Path("/").Methods("GET").HandlerFunc(apiHandler(themeHandler(listThemedExercices)))
rtes.Path("/").Methods("POST").HandlerFunc(apiHandler(themeHandler(createExercice)))
rte := rtes.PathPrefix("/{eid:[0-9]+}").Subrouter()
rte.Path("/").Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(showThemedExercice))))
rte.Path("/").Methods("PUT").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(updateThemedExercice))))
rte.Path("/").Methods("DELETE").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(deleteThemedExercice))))
rtef := rte.Path("/files").Subrouter()
rtef.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceFiles))))
rtef.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceFile))))
rtek := rte.Path("/keys").Subrouter()
rtek.Methods("GET").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(listThemedExerciceKeys))))
rtek.Methods("POST").HandlerFunc(apiHandler(themeHandler(themedExerciceHandler(createExerciceKey))))
}
func bindingFiles(args map[string]string, body []byte) (interface{}, error) {
if files, err := fic.GetFiles(); err != nil {
return "", err
} else {
ret := ""
for _, file := range files {
ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path)
}
return ret, nil
}
}
func getExercice(args []string) (fic.Exercice, error) {
if tid, err := strconv.Atoi(string(args[0])); err != nil {
return fic.Exercice{}, err
} else if theme, err := fic.GetTheme(tid); err != nil {
return fic.Exercice{}, err
} else if eid, err := strconv.Atoi(string(args[1])); err != nil {
return fic.Exercice{}, err
} else {
return theme.GetExercice(eid)
}
}
func listThemes(args map[string]string, body []byte) (interface{}, error) {
return fic.GetThemes()
}
func exportThemes(args map[string]string, body []byte) (interface{}, error) {
return fic.ExportThemes()
}
func showTheme(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) {
return theme, nil
}
func listThemedExercices(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) {
return theme.GetExercices()
}
func showThemedExercice(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
return exercice, nil
}
func listThemedExerciceFiles(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
return exercice.GetFiles()
}
func listThemedExerciceKeys(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
return exercice.GetKeys()
}
type uploadedTheme struct {
Name string
Authors string
}
func createTheme(args map[string]string, body []byte) (interface{}, error) {
var ut uploadedTheme
if err := json.Unmarshal(body, &ut); err != nil {
return nil, err
}
if len(ut.Name) == 0 {
return nil, errors.New("Theme's name not filled")
}
return fic.CreateTheme(ut.Name, ut.Authors)
}
func updateTheme(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) {
var ut fic.Theme
if err := json.Unmarshal(body, &ut); err != nil {
return nil, err
}
ut.Id = theme.Id
if len(ut.Name) == 0 {
return nil, errors.New("Theme's name not filled")
}
return ut.Update()
}
func updateThemedExercice(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
// Update an exercice
var ue fic.Exercice
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
ue.Id = exercice.Id
if len(ue.Title) == 0 {
return nil, errors.New("Exercice's title not filled")
}
if _, err := ue.Update(); err != nil {
return nil, err
}
return ue, nil
}
func deleteTheme(theme fic.Theme, args map[string]string, body []byte) (interface{}, error) {
return theme.Delete()
}
func deleteThemedExercice(theme fic.Theme, exercice fic.Exercice, args map[string]string, body []byte) (interface{}, error) {
return exercice.Delete()
}

11
admin/api/version.go Normal file
View file

@ -0,0 +1,11 @@
package api
import ()
func init() {
router.Path("/version").Methods("GET").HandlerFunc(apiHandler(showVersion))
}
func showVersion(args map[string]string, body []byte) (interface{}, error) {
return map[string]interface{}{"version": 0.1}, nil
}