admin: Use gin-gonic as router
This commit is contained in:
parent
83468ad723
commit
8b3fbdb64a
32 changed files with 2785 additions and 1635 deletions
|
@ -7,13 +7,13 @@ import (
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -23,91 +23,178 @@ import (
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
"srs.epita.fr/fic-server/admin/pki"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TeamsDir string
|
var TeamsDir string
|
||||||
|
|
||||||
func init() {
|
func declareCertificateRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/htpasswd", apiHandler(
|
router.GET("/htpasswd", func(c *gin.Context) {
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
ret, err := genHtpasswd(true)
|
||||||
return genHtpasswd(true)
|
if err != nil {
|
||||||
}))
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
router.POST("/api/htpasswd", apiHandler(
|
return
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
}
|
||||||
if htpasswd, err := genHtpasswd(true); err != nil {
|
c.String(http.StatusOK, ret)
|
||||||
return nil, err
|
})
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "ficpasswd"), []byte(htpasswd), 0644); err != nil {
|
router.POST("/htpasswd", func(c *gin.Context) {
|
||||||
return nil, err
|
if htpasswd, err := genHtpasswd(true); err != nil {
|
||||||
} else {
|
log.Println("Unable to generate htpasswd:", err)
|
||||||
return true, nil
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
}
|
return
|
||||||
}))
|
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "ficpasswd"), []byte(htpasswd), 0644); err != nil {
|
||||||
router.DELETE("/api/htpasswd", apiHandler(
|
log.Println("Unable to write htpasswd:", err)
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
if err := os.Remove(path.Join(pki.PKIDir, "shared", "ficpasswd")); err != nil {
|
return
|
||||||
return nil, err
|
}
|
||||||
} else {
|
c.AbortWithStatus(http.StatusOK)
|
||||||
return true, nil
|
})
|
||||||
}
|
router.DELETE("/htpasswd", func(c *gin.Context) {
|
||||||
}))
|
if err := os.Remove(path.Join(pki.PKIDir, "shared", "ficpasswd")); err != nil {
|
||||||
router.GET("/api/htpasswd.apr1", apiHandler(
|
log.Println("Unable to remove htpasswd:", err)
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
return genHtpasswd(false)
|
return
|
||||||
}))
|
}
|
||||||
router.GET("/api/ca/", apiHandler(infoCA))
|
c.AbortWithStatus(http.StatusOK)
|
||||||
router.GET("/api/ca.pem", apiHandler(getCAPEM))
|
})
|
||||||
router.POST("/api/ca/new", apiHandler(
|
router.GET("/htpasswd.apr1", func(c *gin.Context) {
|
||||||
func(_ httprouter.Params, body []byte) (interface{}, error) {
|
ret, err := genHtpasswd(false)
|
||||||
var upki PKISettings
|
if err != nil {
|
||||||
if err := json.Unmarshal(body, &upki); err != nil {
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
return true, pki.GenerateCA(upki.NotBefore, upki.NotAfter)
|
c.String(http.StatusOK, ret)
|
||||||
}))
|
})
|
||||||
|
router.GET("/ca", infoCA)
|
||||||
|
router.GET("/ca.pem", getCAPEM)
|
||||||
|
router.POST("/ca/new", func(c *gin.Context) {
|
||||||
|
var upki PKISettings
|
||||||
|
err := c.ShouldBindJSON(&upki)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
router.GET("/api/teams/:tid/certificates", apiHandler(teamHandler(
|
if err := pki.GenerateCA(upki.NotBefore, upki.NotAfter); err != nil {
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
if serials, err := pki.GetTeamSerials(TeamsDir, team.Id); err != nil {
|
return
|
||||||
return nil, err
|
}
|
||||||
} else {
|
|
||||||
var certs []CertExported
|
c.JSON(http.StatusCreated, true)
|
||||||
for _, serial := range serials {
|
})
|
||||||
if cert, err := fic.GetCertificate(serial); err == nil {
|
|
||||||
certs = append(certs, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, &team.Id, cert.Revoked})
|
router.GET("/certs", getCertificates)
|
||||||
} else {
|
router.POST("/certs", generateClientCert)
|
||||||
log.Println("Unable to get back certificate, whereas an association exists on disk: ", err)
|
router.DELETE("/certs", func(c *gin.Context) {
|
||||||
}
|
v, err := fic.ClearCertificates()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to ClearCertificates:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
apiCertificatesRoutes := router.Group("/certs/:certid")
|
||||||
|
apiCertificatesRoutes.Use(CertificateHandler)
|
||||||
|
apiCertificatesRoutes.HEAD("", getTeamP12File)
|
||||||
|
apiCertificatesRoutes.GET("", getTeamP12File)
|
||||||
|
apiCertificatesRoutes.PUT("", updateCertificateAssociation)
|
||||||
|
apiCertificatesRoutes.DELETE("", func(c *gin.Context) {
|
||||||
|
cert := c.MustGet("cert").(*fic.Certificate)
|
||||||
|
|
||||||
|
v, err := cert.Revoke()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to Revoke:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func declareTeamCertificateRoutes(router *gin.RouterGroup) {
|
||||||
|
router.GET("/certificates", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
if serials, err := pki.GetTeamSerials(TeamsDir, team.Id); err != nil {
|
||||||
|
log.Println("Unable to GetTeamSerials:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
var certs []CertExported
|
||||||
|
for _, serial := range serials {
|
||||||
|
if cert, err := fic.GetCertificate(serial); err == nil {
|
||||||
|
certs = append(certs, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, &team.Id, cert.Revoked})
|
||||||
|
} else {
|
||||||
|
log.Println("Unable to get back certificate, whereas an association exists on disk: ", err)
|
||||||
}
|
}
|
||||||
return certs, nil
|
|
||||||
}
|
}
|
||||||
})))
|
c.JSON(http.StatusOK, certs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
router.GET("/api/teams/:tid/associations", apiHandler(teamHandler(
|
router.GET("/associations", func(c *gin.Context) {
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
team := c.MustGet("team").(*fic.Team)
|
||||||
return pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
||||||
})))
|
|
||||||
router.POST("/api/teams/:tid/associations/:assoc", apiHandler(teamAssocHandler(
|
|
||||||
func(team *fic.Team, assoc string, _ []byte) (interface{}, error) {
|
|
||||||
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, assoc)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return "\"" + assoc + "\"", nil
|
|
||||||
})))
|
|
||||||
router.DELETE("/api/teams/:tid/associations/:assoc", apiHandler(teamAssocHandler(
|
|
||||||
func(team *fic.Team, assoc string, _ []byte) (interface{}, error) {
|
|
||||||
return "null", pki.DeleteTeamAssociation(TeamsDir, assoc)
|
|
||||||
})))
|
|
||||||
|
|
||||||
router.GET("/api/certs/", apiHandler(getCertificates))
|
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
||||||
router.POST("/api/certs/", apiHandler(generateClientCert))
|
if err != nil {
|
||||||
router.DELETE("/api/certs/", apiHandler(func(_ httprouter.Params, _ []byte) (interface{}, error) { return fic.ClearCertificates() }))
|
log.Println("Unable to GetTeamAssociations:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
router.HEAD("/api/certs/:certid", apiHandler(certificateHandler(getTeamP12File)))
|
c.JSON(http.StatusOK, assocs)
|
||||||
router.GET("/api/certs/:certid", apiHandler(certificateHandler(getTeamP12File)))
|
})
|
||||||
router.PUT("/api/certs/:certid", apiHandler(certificateHandler(updateCertificateAssociation)))
|
|
||||||
router.DELETE("/api/certs/:certid", apiHandler(certificateHandler(
|
apiTeamAssociationsRoutes := router.Group("/associations/:assoc")
|
||||||
func(cert *fic.Certificate, _ []byte) (interface{}, error) { return cert.Revoke() })))
|
apiTeamAssociationsRoutes.POST("", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, c.Params.ByName("assoc"))); err != nil {
|
||||||
|
log.Println("Unable to create association symlink:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to create association symlink: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, c.Params.ByName("assoc"))
|
||||||
|
})
|
||||||
|
apiTeamAssociationsRoutes.DELETE("", func(c *gin.Context) {
|
||||||
|
err := pki.DeleteTeamAssociation(TeamsDir, c.Params.ByName("assoc"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to DeleteTeamAssociation(%s): %s", c.Params.ByName("assoc"), err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to delete association symlink: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func CertificateHandler(c *gin.Context) {
|
||||||
|
var certid uint64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cid := strings.TrimSuffix(string(c.Params.ByName("certid")), ".p12")
|
||||||
|
if certid, err = strconv.ParseUint(cid, 10, 64); err != nil {
|
||||||
|
if certid, err = strconv.ParseUint(cid, 16, 64); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid certficate identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := fic.GetCertificate(certid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Certificate not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("cert", cert)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
func genHtpasswd(ssha bool) (ret string, err error) {
|
func genHtpasswd(ssha bool) (ret string, err error) {
|
||||||
|
@ -187,13 +274,14 @@ type PKISettings struct {
|
||||||
PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"publicKeyAlgorithm"`
|
PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"publicKeyAlgorithm"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func infoCA(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func infoCA(c *gin.Context) {
|
||||||
_, cacert, err := pki.LoadCA()
|
_, cacert, err := pki.LoadCA()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "CA not found"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return PKISettings{
|
c.JSON(http.StatusOK, PKISettings{
|
||||||
Version: cacert.Version,
|
Version: cacert.Version,
|
||||||
SerialNumber: cacert.SerialNumber,
|
SerialNumber: cacert.SerialNumber,
|
||||||
Issuer: cacert.Issuer,
|
Issuer: cacert.Issuer,
|
||||||
|
@ -202,47 +290,78 @@ func infoCA(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||||
NotAfter: cacert.NotAfter,
|
NotAfter: cacert.NotAfter,
|
||||||
SignatureAlgorithm: cacert.SignatureAlgorithm,
|
SignatureAlgorithm: cacert.SignatureAlgorithm,
|
||||||
PublicKeyAlgorithm: cacert.PublicKeyAlgorithm,
|
PublicKeyAlgorithm: cacert.PublicKeyAlgorithm,
|
||||||
}, nil
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCAPEM(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func getCAPEM(c *gin.Context) {
|
||||||
if _, err := os.Stat(pki.CACertPath()); os.IsNotExist(err) {
|
if _, err := os.Stat(pki.CACertPath()); os.IsNotExist(err) {
|
||||||
return nil, errors.New("Unable to locate the CA root certificate. Have you generated it?")
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to locate the CA root certificate. Have you generated it?"})
|
||||||
|
return
|
||||||
} else if fd, err := os.Open(pki.CACertPath()); err != nil {
|
} else if fd, err := os.Open(pki.CACertPath()); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to open CA root certificate:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
return ioutil.ReadAll(fd)
|
|
||||||
|
cnt, err := ioutil.ReadAll(fd)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to read CA root certificate:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, string(cnt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTeamP12File(cert *fic.Certificate, _ []byte) (interface{}, error) {
|
func getTeamP12File(c *gin.Context) {
|
||||||
|
cert := c.MustGet("cert").(*fic.Certificate)
|
||||||
|
|
||||||
// Create p12 if necessary
|
// Create p12 if necessary
|
||||||
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
|
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
|
||||||
if err := pki.WriteP12(cert.Id, cert.Password); err != nil {
|
if err := pki.WriteP12(cert.Id, cert.Password); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to WriteP12:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
|
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
|
||||||
return nil, errors.New("Unable to locate the p12. Have you generated it?")
|
log.Println("Unable to compute ClientP12Path:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, errors.New("Unable to locate the p12. Have you generated it?"))
|
||||||
|
return
|
||||||
} else if fd, err := os.Open(pki.ClientP12Path(cert.Id)); err != nil {
|
} else if fd, err := os.Open(pki.ClientP12Path(cert.Id)); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to open ClientP12Path:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Unable to open the p12: %w", err.Error()))
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
return ioutil.ReadAll(fd)
|
|
||||||
|
data, err := ioutil.ReadAll(fd)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to open ClientP12Path:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Unable to open the p12: %w", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data(http.StatusOK, "application/x-pkcs12", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateClientCert(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func generateClientCert(c *gin.Context) {
|
||||||
// First, generate a new, unique, serial
|
// First, generate a new, unique, serial
|
||||||
var serial_gen [8]byte
|
var serial_gen [8]byte
|
||||||
if _, err := rand.Read(serial_gen[:]); err != nil {
|
if _, err := rand.Read(serial_gen[:]); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to read enough entropy to generate client certificate:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read enough entropy"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for fic.ExistingCertSerial(serial_gen) {
|
for fic.ExistingCertSerial(serial_gen) {
|
||||||
if _, err := rand.Read(serial_gen[:]); err != nil {
|
if _, err := rand.Read(serial_gen[:]); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to read enough entropy to generate client certificate:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read enough entropy"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,23 +372,35 @@ func generateClientCert(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||||
// Let's pick a random password
|
// Let's pick a random password
|
||||||
password, err := fic.GeneratePassword()
|
password, err := fic.GeneratePassword()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Println("Unable to generate password:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate password: " + err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, now load CA
|
// Ok, now load CA
|
||||||
capriv, cacert, err := pki.LoadCA()
|
capriv, cacert, err := pki.LoadCA()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Println("Unable to load the CA:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to load the CA"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate our privkey
|
// Generate our privkey
|
||||||
if err := pki.GenerateClient(serial, cacert.NotBefore, cacert.NotAfter, &cacert, &capriv); err != nil {
|
if err := pki.GenerateClient(serial, cacert.NotBefore, cacert.NotAfter, &cacert, &capriv); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to generate private key:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate private key: " + err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save in DB
|
// Save in DB
|
||||||
cert, err := fic.RegisterCertificate(serial, password)
|
cert, err := fic.RegisterCertificate(serial, password)
|
||||||
return CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, nil, cert.Revoked}, err
|
if err != nil {
|
||||||
|
log.Println("Unable to register certificate:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register certificate."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, nil, cert.Revoked})
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertExported struct {
|
type CertExported struct {
|
||||||
|
@ -280,35 +411,42 @@ type CertExported struct {
|
||||||
Revoked *time.Time `json:"revoked"`
|
Revoked *time.Time `json:"revoked"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCertificates(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func getCertificates(c *gin.Context) {
|
||||||
if certificates, err := fic.GetCertificates(); err != nil {
|
certificates, err := fic.GetCertificates()
|
||||||
return nil, err
|
if err != nil {
|
||||||
} else {
|
log.Println("Unable to retrieve certificates list:", err)
|
||||||
ret := make([]CertExported, 0)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during certificates retrieval."})
|
||||||
for _, cert := range certificates {
|
return
|
||||||
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
|
|
||||||
|
|
||||||
var idTeam *int64 = nil
|
|
||||||
if lnk, err := os.Readlink(dstLinkPath); err == nil {
|
|
||||||
if tid, err := strconv.ParseInt(lnk, 10, 64); err == nil {
|
|
||||||
idTeam = &tid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, "", idTeam, cert.Revoked})
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
ret := make([]CertExported, 0)
|
||||||
|
for _, cert := range certificates {
|
||||||
|
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
|
||||||
|
|
||||||
|
var idTeam *int64 = nil
|
||||||
|
if lnk, err := os.Readlink(dstLinkPath); err == nil {
|
||||||
|
if tid, err := strconv.ParseInt(lnk, 10, 64); err == nil {
|
||||||
|
idTeam = &tid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, "", idTeam, cert.Revoked})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertUploaded struct {
|
type CertUploaded struct {
|
||||||
Team *int64 `json:"id_team"`
|
Team *int64 `json:"id_team"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCertificateAssociation(cert *fic.Certificate, body []byte) (interface{}, error) {
|
func updateCertificateAssociation(c *gin.Context) {
|
||||||
|
cert := c.MustGet("cert").(*fic.Certificate)
|
||||||
|
|
||||||
var uc CertUploaded
|
var uc CertUploaded
|
||||||
if err := json.Unmarshal(body, &uc); err != nil {
|
err := c.ShouldBindJSON(&uc)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
|
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
|
||||||
|
@ -316,19 +454,26 @@ func updateCertificateAssociation(cert *fic.Certificate, body []byte) (interface
|
||||||
if uc.Team != nil {
|
if uc.Team != nil {
|
||||||
srcLinkPath := fmt.Sprintf("%d", *uc.Team)
|
srcLinkPath := fmt.Sprintf("%d", *uc.Team)
|
||||||
if err := os.Symlink(srcLinkPath, dstLinkPath); err != nil {
|
if err := os.Symlink(srcLinkPath, dstLinkPath); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to create certificate symlink:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to create certificate symlink: %s", err.Error())})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark team as active to ensure it'll be generated
|
// Mark team as active to ensure it'll be generated
|
||||||
if ut, err := fic.GetTeam(*uc.Team); err != nil {
|
if ut, err := fic.GetTeam(*uc.Team); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to GetTeam:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team retrieval."})
|
||||||
|
return
|
||||||
} else if !ut.Active {
|
} else if !ut.Active {
|
||||||
ut.Active = true
|
ut.Active = true
|
||||||
ut.Update()
|
_, err := ut.Update()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to UpdateTeam after updateCertificateAssociation:", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
os.Remove(dstLinkPath)
|
os.Remove(dstLinkPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cert, nil
|
c.JSON(http.StatusOK, cert)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,62 +2,152 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareClaimsRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/teams/:tid/issue.json", apiHandler(teamHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return team.MyIssueFile()
|
|
||||||
})))
|
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
router.GET("/api/claims", apiHandler(getClaims))
|
router.GET("/claims", getClaims)
|
||||||
router.POST("/api/claims", apiHandler(newClaim))
|
router.POST("/claims", newClaim)
|
||||||
router.DELETE("/api/claims", apiHandler(clearClaims))
|
router.DELETE("/claims", clearClaims)
|
||||||
router.GET("/api/teams/:tid/claims", apiHandler(teamHandler(getTeamClaims)))
|
|
||||||
router.GET("/api/exercices/:eid/claims", apiHandler(exerciceHandler(getExerciceClaims)))
|
|
||||||
router.GET("/api/themes/:thid/exercices/:eid/claims", apiHandler(exerciceHandler(getExerciceClaims)))
|
|
||||||
|
|
||||||
router.GET("/api/claims/:cid", apiHandler(claimHandler(showClaim)))
|
apiClaimsRoutes := router.Group("/claims/:cid")
|
||||||
router.PUT("/api/claims/:cid", apiHandler(claimHandler(updateClaim)))
|
apiClaimsRoutes.Use(ClaimHandler)
|
||||||
router.POST("/api/claims/:cid", apiHandler(claimHandler(addClaimDescription)))
|
apiClaimsRoutes.GET("", showClaim)
|
||||||
router.DELETE("/api/claims/:cid", apiHandler(claimHandler(deleteClaim)))
|
apiClaimsRoutes.PUT("", updateClaim)
|
||||||
|
apiClaimsRoutes.POST("", addClaimDescription)
|
||||||
|
apiClaimsRoutes.DELETE("", deleteClaim)
|
||||||
|
|
||||||
router.GET("/api/claims/:cid/last_update", apiHandler(claimHandler(getClaimLastUpdate)))
|
apiClaimsRoutes.GET("/last_update", getClaimLastUpdate)
|
||||||
router.PUT("/api/claims/:cid/descriptions", apiHandler(claimHandler(updateClaimDescription)))
|
apiClaimsRoutes.PUT("/descriptions", updateClaimDescription)
|
||||||
|
|
||||||
// Assignees
|
// Assignees
|
||||||
router.GET("/api/claims-assignees", apiHandler(getAssignees))
|
router.GET("/claims-assignees", getAssignees)
|
||||||
router.POST("/api/claims-assignees", apiHandler(newAssignee))
|
router.POST("/claims-assignees", newAssignee)
|
||||||
|
|
||||||
router.GET("/api/claims-assignees/:aid", apiHandler(claimAssigneeHandler(showClaimAssignee)))
|
apiClaimAssigneesRoutes := router.Group("/claims-assignees/:aid")
|
||||||
router.PUT("/api/claims-assignees/:aid", apiHandler(claimAssigneeHandler(updateClaimAssignee)))
|
apiClaimAssigneesRoutes.Use(ClaimAssigneeHandler)
|
||||||
router.DELETE("/api/claims-assignees/:aid", apiHandler(claimAssigneeHandler(deleteClaimAssignee)))
|
router.GET("/claims-assignees/:aid", showClaimAssignee)
|
||||||
|
router.PUT("/claims-assignees/:aid", updateClaimAssignee)
|
||||||
|
router.DELETE("/claims-assignees/:aid", deleteClaimAssignee)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClaims(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func declareExerciceClaimsRoutes(router *gin.RouterGroup) {
|
||||||
return fic.GetClaims()
|
router.GET("/claims", getExerciceClaims)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTeamClaims(team *fic.Team, _ []byte) (interface{}, error) {
|
func declareTeamClaimsRoutes(router *gin.RouterGroup) {
|
||||||
return team.GetClaims()
|
router.GET("/api/teams/:tid/issue.json", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
issues, err := team.MyIssueFile()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to MyIssueFile(tid=%d): %s", team.Id, err.Error())
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate issues.json."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, issues)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/claims", getTeamClaims)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExerciceClaims(exercice *fic.Exercice, _ []byte) (interface{}, error) {
|
func ClaimHandler(c *gin.Context) {
|
||||||
return exercice.GetClaims()
|
cid, err := strconv.ParseInt(string(c.Params.ByName("cid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid claim identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claim, err := fic.GetClaim(cid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("claim", claim)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClaimLastUpdate(claim *fic.Claim, _ []byte) (interface{}, error) {
|
func ClaimAssigneeHandler(c *gin.Context) {
|
||||||
return claim.GetLastUpdate()
|
aid, err := strconv.ParseInt(string(c.Params.ByName("aid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid claim assignee identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assignee, err := fic.GetAssignee(aid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim-assignee not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("claim-assignee", assignee)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClaims(c *gin.Context) {
|
||||||
|
claims, err := fic.GetClaims()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to getClaims:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims retrieval."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTeamClaims(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
claims, err := team.GetClaims()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to GetClaims(tid=%d): %s", team.Id, err.Error())
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExerciceClaims(c *gin.Context) {
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
|
||||||
|
claims, err := exercice.GetClaims()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to GetClaims(eid=%d): %s", exercice.Id, err.Error())
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClaimLastUpdate(c *gin.Context) {
|
||||||
|
claim := c.MustGet("claim").(*fic.Claim)
|
||||||
|
|
||||||
|
v, err := claim.GetLastUpdate()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to GetLastUpdate: %s", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim last update retrieval."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimExported struct {
|
type ClaimExported struct {
|
||||||
|
@ -76,20 +166,26 @@ type ClaimExported struct {
|
||||||
Descriptions []*fic.ClaimDescription `json:"descriptions"`
|
Descriptions []*fic.ClaimDescription `json:"descriptions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func showClaim(claim *fic.Claim, _ []byte) (interface{}, error) {
|
func showClaim(c *gin.Context) {
|
||||||
|
claim := c.MustGet("claim").(*fic.Claim)
|
||||||
|
|
||||||
var e ClaimExported
|
var e ClaimExported
|
||||||
var err error
|
var err error
|
||||||
if e.Team, err = claim.GetTeam(); err != nil {
|
if e.Team, err = claim.GetTeam(); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to find associated team: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated team: %s", err.Error())})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if e.Exercice, err = claim.GetExercice(); err != nil {
|
if e.Exercice, err = claim.GetExercice(); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to find associated exercice: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated exercice: %s", err.Error())})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if e.Assignee, err = claim.GetAssignee(); err != nil {
|
if e.Assignee, err = claim.GetAssignee(); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to find associated assignee: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated assignee: %s", err.Error())})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if e.Descriptions, err = claim.GetDescriptions(); err != nil {
|
if e.Descriptions, err = claim.GetDescriptions(); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to find claim's descriptions: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find claim's descriptions: %s", err.Error())})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.LastUpdate = e.Creation
|
e.LastUpdate = e.Creation
|
||||||
|
@ -107,7 +203,8 @@ func showClaim(claim *fic.Claim, _ []byte) (interface{}, error) {
|
||||||
e.Creation = claim.Creation
|
e.Creation = claim.Creation
|
||||||
e.State = claim.State
|
e.State = claim.State
|
||||||
e.Priority = claim.Priority
|
e.Priority = claim.Priority
|
||||||
return e, nil
|
|
||||||
|
c.JSON(http.StatusOK, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimUploaded struct {
|
type ClaimUploaded struct {
|
||||||
|
@ -115,20 +212,24 @@ type ClaimUploaded struct {
|
||||||
Whoami *int64 `json:"whoami"`
|
Whoami *int64 `json:"whoami"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClaim(_ httprouter.Params, body []byte) (interface{}, error) {
|
func newClaim(c *gin.Context) {
|
||||||
var uc ClaimUploaded
|
var uc ClaimUploaded
|
||||||
if err := json.Unmarshal(body, &uc); err != nil {
|
err := c.ShouldBindJSON(&uc)
|
||||||
return nil, fmt.Errorf("Unable to decode JSON: %w", err)
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if uc.Subject == "" {
|
if uc.Subject == "" {
|
||||||
return nil, errors.New("Claim's subject cannot be empty.")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Claim's subject cannot be empty."})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var t *fic.Team
|
var t *fic.Team
|
||||||
if uc.IdTeam != nil {
|
if uc.IdTeam != nil {
|
||||||
if team, err := fic.GetTeam(*uc.IdTeam); err != nil {
|
if team, err := fic.GetTeam(*uc.IdTeam); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to get associated team: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated team: %s", err.Error())})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
t = team
|
t = team
|
||||||
}
|
}
|
||||||
|
@ -139,7 +240,8 @@ func newClaim(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
var e *fic.Exercice
|
var e *fic.Exercice
|
||||||
if uc.IdExercice != nil {
|
if uc.IdExercice != nil {
|
||||||
if exercice, err := fic.GetExercice(*uc.IdExercice); err != nil {
|
if exercice, err := fic.GetExercice(*uc.IdExercice); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to get associated exercice: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated exercice: %s", err.Error())})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
e = exercice
|
e = exercice
|
||||||
}
|
}
|
||||||
|
@ -150,7 +252,8 @@ func newClaim(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
var a *fic.ClaimAssignee
|
var a *fic.ClaimAssignee
|
||||||
if uc.IdAssignee != nil {
|
if uc.IdAssignee != nil {
|
||||||
if assignee, err := fic.GetAssignee(*uc.IdAssignee); err != nil {
|
if assignee, err := fic.GetAssignee(*uc.IdAssignee); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to get associated assignee: %w", err)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
a = assignee
|
a = assignee
|
||||||
}
|
}
|
||||||
|
@ -162,11 +265,25 @@ func newClaim(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
uc.Priority = "medium"
|
uc.Priority = "medium"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fic.NewClaim(uc.Subject, t, e, a, uc.Priority)
|
claim, err := fic.NewClaim(uc.Subject, t, e, a, uc.Priority)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to newClaim:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register new claim"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, claim)
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearClaims(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func clearClaims(c *gin.Context) {
|
||||||
return fic.ClearClaims()
|
nb, err := fic.ClearClaims()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to clearClaims: %s", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims clearing."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, nb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTeamIssuesFile(team fic.Team) error {
|
func generateTeamIssuesFile(team fic.Team) error {
|
||||||
|
@ -180,122 +297,189 @@ func generateTeamIssuesFile(team fic.Team) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addClaimDescription(claim *fic.Claim, body []byte) (interface{}, error) {
|
func addClaimDescription(c *gin.Context) {
|
||||||
|
claim := c.MustGet("claim").(*fic.Claim)
|
||||||
|
|
||||||
var ud fic.ClaimDescription
|
var ud fic.ClaimDescription
|
||||||
if err := json.Unmarshal(body, &ud); err != nil {
|
err := c.ShouldBindJSON(&ud)
|
||||||
return nil, fmt.Errorf("Unable to decode JSON: %w", err)
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if assignee, err := fic.GetAssignee(ud.IdAssignee); err != nil {
|
assignee, err := fic.GetAssignee(ud.IdAssignee)
|
||||||
return nil, fmt.Errorf("Unable to get associated assignee: %w", err)
|
if err != nil {
|
||||||
} else if description, err := claim.AddDescription(ud.Content, assignee, ud.Publish); err != nil {
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())})
|
||||||
return nil, fmt.Errorf("Unable to add description: %w", err)
|
return
|
||||||
} else {
|
}
|
||||||
if team, _ := claim.GetTeam(); team != nil {
|
|
||||||
err = generateTeamIssuesFile(*team)
|
description, err := claim.AddDescription(ud.Content, assignee, ud.Publish)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to addClaimDescription:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add description"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if team, _ := claim.GetTeam(); team != nil {
|
||||||
|
err = generateTeamIssuesFile(*team)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to generateTeamIssuesFile after addClaimDescription:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return description, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateClaimDescription(claim *fic.Claim, body []byte) (interface{}, error) {
|
func updateClaimDescription(c *gin.Context) {
|
||||||
|
claim := c.MustGet("claim").(*fic.Claim)
|
||||||
|
|
||||||
var ud fic.ClaimDescription
|
var ud fic.ClaimDescription
|
||||||
if err := json.Unmarshal(body, &ud); err != nil {
|
err := c.ShouldBindJSON(&ud)
|
||||||
return nil, fmt.Errorf("Unable to decode JSON: %w", err)
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := ud.Update(); err != nil {
|
if _, err := ud.Update(); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to update description: %w", err)
|
log.Println("Unable to updateClaimDescription:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during claim description updating."})
|
||||||
if team, _ := claim.GetTeam(); team != nil {
|
return
|
||||||
err = generateTeamIssuesFile(*team)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ud, err
|
|
||||||
}
|
}
|
||||||
|
if team, _ := claim.GetTeam(); team != nil {
|
||||||
|
err = generateTeamIssuesFile(*team)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to generateTeamIssuesFile:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ud)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateClaim(claim *fic.Claim, body []byte) (interface{}, error) {
|
func updateClaim(c *gin.Context) {
|
||||||
|
claim := c.MustGet("claim").(*fic.Claim)
|
||||||
|
|
||||||
var uc ClaimUploaded
|
var uc ClaimUploaded
|
||||||
if err := json.Unmarshal(body, &uc); err != nil {
|
err := c.ShouldBindJSON(&uc)
|
||||||
return nil, fmt.Errorf("Unable to decode JSON: %w", err)
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uc.Id = claim.Id
|
uc.Id = claim.Id
|
||||||
|
|
||||||
if _, err := uc.Update(); err != nil {
|
_, err = uc.Update()
|
||||||
return nil, fmt.Errorf("Unable to update claim: %w", err)
|
if err != nil {
|
||||||
} else {
|
log.Printf("Unable to updateClaim: %s", err.Error())
|
||||||
if claim.State != uc.State {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim update."})
|
||||||
if uc.Whoami != nil {
|
return
|
||||||
if assignee, err := fic.GetAssignee(*uc.Whoami); err == nil {
|
}
|
||||||
claim.AddDescription(fmt.Sprintf("%s a changé l'état de la tâche vers %q (était %q).", assignee.Name, uc.State, claim.State), assignee, true)
|
|
||||||
}
|
if claim.State != uc.State {
|
||||||
|
if uc.Whoami != nil {
|
||||||
|
if assignee, err := fic.GetAssignee(*uc.Whoami); err == nil {
|
||||||
|
claim.AddDescription(fmt.Sprintf("%s a changé l'état de la tâche vers %q (était %q).", assignee.Name, uc.State, claim.State), assignee, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if claim.IdAssignee != uc.IdAssignee {
|
if claim.IdAssignee != uc.IdAssignee {
|
||||||
if uc.Whoami != nil {
|
if uc.Whoami != nil {
|
||||||
if whoami, err := fic.GetAssignee(*uc.Whoami); err == nil {
|
if whoami, err := fic.GetAssignee(*uc.Whoami); err == nil {
|
||||||
if uc.IdAssignee != nil {
|
if uc.IdAssignee != nil {
|
||||||
if assignee, err := fic.GetAssignee(*uc.IdAssignee); err == nil {
|
if assignee, err := fic.GetAssignee(*uc.IdAssignee); err == nil {
|
||||||
if assignee.Id != whoami.Id {
|
if assignee.Id != whoami.Id {
|
||||||
claim.AddDescription(fmt.Sprintf("%s a assigné la tâche à %s.", whoami.Name, assignee.Name), whoami, false)
|
claim.AddDescription(fmt.Sprintf("%s a assigné la tâche à %s.", whoami.Name, assignee.Name), whoami, false)
|
||||||
} else {
|
} else {
|
||||||
claim.AddDescription(fmt.Sprintf("%s s'est assigné la tâche.", assignee.Name), whoami, false)
|
claim.AddDescription(fmt.Sprintf("%s s'est assigné la tâche.", assignee.Name), whoami, false)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
claim.AddDescription(fmt.Sprintf("%s a retiré l'attribution de la tâche.", whoami.Name), whoami, false)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
claim.AddDescription(fmt.Sprintf("%s a retiré l'attribution de la tâche.", whoami.Name), whoami, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if team, _ := claim.GetTeam(); team != nil {
|
if team, _ := claim.GetTeam(); team != nil {
|
||||||
err = generateTeamIssuesFile(*team)
|
err = generateTeamIssuesFile(*team)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uc, err
|
c.JSON(http.StatusOK, uc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteClaim(c *gin.Context) {
|
||||||
|
claim := c.MustGet("claim").(*fic.Claim)
|
||||||
|
|
||||||
|
if nb, err := claim.Delete(); err != nil {
|
||||||
|
log.Println("Unable to deleteClaim:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim deletion."})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, nb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteClaim(claim *fic.Claim, _ []byte) (interface{}, error) {
|
func getAssignees(c *gin.Context) {
|
||||||
return claim.Delete()
|
assignees, err := fic.GetAssignees()
|
||||||
}
|
if err != nil {
|
||||||
|
log.Println("Unable to getAssignees:", err.Error())
|
||||||
func getAssignees(_ httprouter.Params, _ []byte) (interface{}, error) {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignees retrieval."})
|
||||||
return fic.GetAssignees()
|
return
|
||||||
}
|
|
||||||
|
|
||||||
func showClaimAssignee(assignee *fic.ClaimAssignee, _ []byte) (interface{}, error) {
|
|
||||||
return assignee, nil
|
|
||||||
}
|
|
||||||
func newAssignee(_ httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var ua fic.ClaimAssignee
|
|
||||||
if err := json.Unmarshal(body, &ua); err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to decode JSON: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fic.NewClaimAssignee(ua.Name)
|
c.JSON(http.StatusOK, assignees)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateClaimAssignee(assignee *fic.ClaimAssignee, body []byte) (interface{}, error) {
|
func showClaimAssignee(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("claim-assignee").(*fic.ClaimAssignee))
|
||||||
|
}
|
||||||
|
func newAssignee(c *gin.Context) {
|
||||||
var ua fic.ClaimAssignee
|
var ua fic.ClaimAssignee
|
||||||
if err := json.Unmarshal(body, &ua); err != nil {
|
err := c.ShouldBindJSON(&ua)
|
||||||
return nil, fmt.Errorf("Unable to decode JSON: %w", err)
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assignee, err := fic.NewClaimAssignee(ua.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to newAssignee:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignee creation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, assignee)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateClaimAssignee(c *gin.Context) {
|
||||||
|
assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee)
|
||||||
|
|
||||||
|
var ua fic.ClaimAssignee
|
||||||
|
err := c.ShouldBindJSON(&ua)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ua.Id = assignee.Id
|
ua.Id = assignee.Id
|
||||||
|
|
||||||
if _, err := ua.Update(); err != nil {
|
if _, err := ua.Update(); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to update claim assignee: %w", err)
|
log.Println("Unable to updateClaimAssignee:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim assignee update."})
|
||||||
return ua, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ua)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteClaimAssignee(assignee *fic.ClaimAssignee, _ []byte) (interface{}, error) {
|
func deleteClaimAssignee(c *gin.Context) {
|
||||||
return assignee.Delete()
|
assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee)
|
||||||
|
|
||||||
|
if _, err := assignee.Delete(); err != nil {
|
||||||
|
log.Println("Unable to deleteClaimAssignee:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during claim assignee deletion: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,45 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareEventsRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/events/", apiHandler(getEvents))
|
router.GET("/events", getEvents)
|
||||||
router.GET("/api/events.json", apiHandler(getLastEvents))
|
router.GET("/events.json", getLastEvents)
|
||||||
router.POST("/api/events/", apiHandler(newEvent))
|
router.POST("/events", newEvent)
|
||||||
router.DELETE("/api/events/", apiHandler(clearEvents))
|
router.DELETE("/events", clearEvents)
|
||||||
|
|
||||||
router.GET("/api/events/:evid", apiHandler(eventHandler(showEvent)))
|
apiEventsRoutes := router.Group("/events/:evid")
|
||||||
router.PUT("/api/events/:evid", apiHandler(eventHandler(updateEvent)))
|
apiEventsRoutes.Use(EventHandler)
|
||||||
router.DELETE("/api/events/:evid", apiHandler(eventHandler(deleteEvent)))
|
apiEventsRoutes.GET("", showEvent)
|
||||||
|
apiEventsRoutes.PUT("", updateEvent)
|
||||||
|
apiEventsRoutes.DELETE("", deleteEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventHandler(c *gin.Context) {
|
||||||
|
evid, err := strconv.ParseInt(string(c.Params.ByName("evid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid event identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event, err := fic.GetEvent(evid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Event not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("event", event)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
func genEventsFile() error {
|
func genEventsFile() error {
|
||||||
|
@ -33,65 +56,99 @@ func genEventsFile() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEvents(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func getEvents(c *gin.Context) {
|
||||||
if evts, err := fic.GetEvents(); err != nil {
|
evts, err := fic.GetEvents()
|
||||||
return nil, err
|
if err != nil {
|
||||||
} else {
|
log.Println("Unable to GetEvents:", err.Error())
|
||||||
return evts, nil
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve events list"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, evts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastEvents(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func getLastEvents(c *gin.Context) {
|
||||||
if evts, err := fic.GetLastEvents(); err != nil {
|
evts, err := fic.GetLastEvents()
|
||||||
return nil, err
|
|
||||||
} else {
|
if err != nil {
|
||||||
return evts, nil
|
log.Println("Unable to GetLastEvents:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve last events list"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, evts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showEvent(event *fic.Event, _ []byte) (interface{}, error) {
|
func newEvent(c *gin.Context) {
|
||||||
return event, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEvent(_ httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var ue fic.Event
|
var ue fic.Event
|
||||||
if err := json.Unmarshal(body, &ue); err != nil {
|
err := c.ShouldBindJSON(&ue)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if event, err := fic.NewEvent(ue.Text, ue.Kind); err != nil {
|
event, err := fic.NewEvent(ue.Text, ue.Kind)
|
||||||
return nil, err
|
if err != nil {
|
||||||
} else {
|
log.Printf("Unable to newEvent: %s", err.Error())
|
||||||
genEventsFile()
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event creation."})
|
||||||
return event, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genEventsFile()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearEvents(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func clearEvents(c *gin.Context) {
|
||||||
return fic.ClearEvents()
|
nb, err := fic.ClearEvents()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to clearEvent: %s", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event clearing."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, nb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateEvent(event *fic.Event, body []byte) (interface{}, error) {
|
func showEvent(c *gin.Context) {
|
||||||
|
event := c.MustGet("event").(*fic.Event)
|
||||||
|
c.JSON(http.StatusOK, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateEvent(c *gin.Context) {
|
||||||
|
event := c.MustGet("event").(*fic.Event)
|
||||||
|
|
||||||
var ue fic.Event
|
var ue fic.Event
|
||||||
if err := json.Unmarshal(body, &ue); err != nil {
|
err := c.ShouldBindJSON(&ue)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ue.Id = event.Id
|
ue.Id = event.Id
|
||||||
|
|
||||||
if _, err := ue.Update(); err != nil {
|
if _, err := ue.Update(); err != nil {
|
||||||
return nil, err
|
log.Printf("Unable to updateEvent: %s", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event update."})
|
||||||
genEventsFile()
|
return
|
||||||
return ue, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genEventsFile()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteEvent(event *fic.Event, _ []byte) (interface{}, error) {
|
func deleteEvent(c *gin.Context) {
|
||||||
if i, err := event.Delete(); err != nil {
|
event := c.MustGet("event").(*fic.Event)
|
||||||
return i, err
|
|
||||||
} else {
|
_, err := event.Delete()
|
||||||
genEventsFile()
|
if err != nil {
|
||||||
return i, err
|
log.Printf("Unable to deleteEvent: %s", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event deletion."})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genEventsFile()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,43 +2,79 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareFilesGlobalRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/files/", apiHandler(listFiles))
|
router.DELETE("/files/", clearFiles)
|
||||||
router.DELETE("/api/files/", apiHandler(clearFiles))
|
|
||||||
|
|
||||||
router.GET("/api/files/:fileid", apiHandler(fileHandler(showFile)))
|
|
||||||
router.PUT("/api/files/:fileid", apiHandler(fileHandler(updateFile)))
|
|
||||||
router.DELETE("/api/files/:fileid", apiHandler(fileHandler(deleteFile)))
|
|
||||||
|
|
||||||
router.DELETE("/api/files/:fileid/dependancies/:depid", apiHandler(fileDependancyHandler(deleteFileDep)))
|
|
||||||
|
|
||||||
router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
|
||||||
router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
|
||||||
|
|
||||||
router.GET("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(showFile)))
|
|
||||||
router.PUT("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(updateFile)))
|
|
||||||
router.DELETE("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(deleteFile)))
|
|
||||||
|
|
||||||
// Remote
|
// Remote
|
||||||
router.GET("/api/remote/themes/:thid/exercices/:exid/files", apiHandler(sync.ApiGetRemoteExerciceFiles))
|
router.GET("/remote/themes/:thid/exercices/:exid/files", sync.ApiGetRemoteExerciceFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func declareFilesRoutes(router *gin.RouterGroup) {
|
||||||
|
router.GET("/files", listFiles)
|
||||||
|
router.POST("/files", createExerciceFile)
|
||||||
|
|
||||||
|
apiFilesRoutes := router.Group("/files/:fileid")
|
||||||
|
apiFilesRoutes.Use(FileHandler)
|
||||||
|
apiFilesRoutes.GET("", showFile)
|
||||||
|
apiFilesRoutes.PUT("", updateFile)
|
||||||
|
apiFilesRoutes.DELETE("", deleteFile)
|
||||||
|
|
||||||
|
apiFileDepsRoutes := apiFilesRoutes.Group("/dependancies/:depid")
|
||||||
|
apiFileDepsRoutes.Use(FileDepHandler)
|
||||||
|
apiFileDepsRoutes.DELETE("", deleteFileDep)
|
||||||
|
|
||||||
// Check
|
// Check
|
||||||
router.POST("/api/files/:fileid/check", apiHandler(fileHandler(checkFile)))
|
apiFilesRoutes.POST("/check", checkFile)
|
||||||
|
}
|
||||||
|
|
||||||
// Synchronize
|
func FileHandler(c *gin.Context) {
|
||||||
router.POST("/api/sync/exercices/:eid/files", apiHandler(exerciceHandler(
|
fileid, err := strconv.ParseInt(string(c.Params.ByName("fileid")), 10, 64)
|
||||||
func(exercice *fic.Exercice, _ []byte) (interface{}, error) {
|
if err != nil {
|
||||||
return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid file identifier"})
|
||||||
})))
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var file *fic.EFile
|
||||||
|
if exercice, exists := c.Get("exercice"); exists {
|
||||||
|
file, err = exercice.(*fic.Exercice).GetFile(fileid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file, err = fic.GetFile(fileid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("file", file)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FileDepHandler(c *gin.Context) {
|
||||||
|
depid, err := strconv.ParseInt(string(c.Params.ByName("depid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid dependency identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("file-depid", depid)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIFile struct {
|
type APIFile struct {
|
||||||
|
@ -87,20 +123,35 @@ func genFileList(in []*fic.EFile, e error) (out []APIFile, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func listFiles(_ httprouter.Params, body []byte) (interface{}, error) {
|
func listFiles(c *gin.Context) {
|
||||||
return genFileList(fic.GetFiles())
|
var files []APIFile
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if exercice, exists := c.Get("exercice"); exists {
|
||||||
|
files, err = genFileList(exercice.(*fic.Exercice).GetFiles())
|
||||||
|
} else {
|
||||||
|
files, err = genFileList(fic.GetFiles())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, files)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listExerciceFiles(exercice *fic.Exercice, body []byte) (interface{}, error) {
|
func clearFiles(c *gin.Context) {
|
||||||
return genFileList(exercice.GetFiles())
|
_, err := fic.ClearFiles()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearFiles(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func showFile(c *gin.Context) {
|
||||||
return fic.ClearFiles()
|
c.JSON(http.StatusOK, c.MustGet("file").(*fic.EFile))
|
||||||
}
|
|
||||||
|
|
||||||
func showFile(file *fic.EFile, _ []byte) (interface{}, error) {
|
|
||||||
return file, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type uploadedFile struct {
|
type uploadedFile struct {
|
||||||
|
@ -108,45 +159,92 @@ type uploadedFile struct {
|
||||||
Digest string
|
Digest string
|
||||||
}
|
}
|
||||||
|
|
||||||
func createExerciceFile(exercice *fic.Exercice, body []byte) (interface{}, error) {
|
func createExerciceFile(c *gin.Context) {
|
||||||
var uf uploadedFile
|
exercice, exists := c.Get("exercice")
|
||||||
if err := json.Unmarshal(body, &uf); err != nil {
|
if !exists {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "File can only be added inside an exercice."})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return sync.ImportFile(sync.GlobalImporter, uf.URI,
|
var uf uploadedFile
|
||||||
|
err := c.ShouldBindJSON(&uf)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := sync.ImportFile(sync.GlobalImporter, uf.URI,
|
||||||
func(filePath string, origin string) (interface{}, error) {
|
func(filePath string, origin string) (interface{}, error) {
|
||||||
if digest, err := hex.DecodeString(uf.Digest); err != nil {
|
if digest, err := hex.DecodeString(uf.Digest); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return exercice.ImportFile(filePath, origin, digest)
|
return exercice.(*fic.Exercice).ImportFile(filePath, origin, digest)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFile(file *fic.EFile, body []byte) (interface{}, error) {
|
func updateFile(c *gin.Context) {
|
||||||
|
file := c.MustGet("file").(*fic.EFile)
|
||||||
|
|
||||||
var uf fic.EFile
|
var uf fic.EFile
|
||||||
if err := json.Unmarshal(body, &uf); err != nil {
|
err := c.ShouldBindJSON(&uf)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uf.Id = file.Id
|
uf.Id = file.Id
|
||||||
|
|
||||||
if _, err := uf.Update(); err != nil {
|
if _, err := uf.Update(); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to updateFile:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
|
||||||
return uf, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, uf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFile(file *fic.EFile, _ []byte) (interface{}, error) {
|
func deleteFile(c *gin.Context) {
|
||||||
return file.Delete()
|
file := c.MustGet("file").(*fic.EFile)
|
||||||
|
|
||||||
|
_, err := file.Delete()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to updateFile:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFileDep(file *fic.EFile, depid int, _ []byte) (interface{}, error) {
|
func deleteFileDep(c *gin.Context) {
|
||||||
return true, file.DeleteDepend(&fic.FlagKey{Id: depid})
|
file := c.MustGet("file").(*fic.EFile)
|
||||||
|
depid := c.MustGet("file-depid").(int64)
|
||||||
|
|
||||||
|
err := file.DeleteDepend(&fic.FlagKey{Id: int(depid)})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to deleteFileDep:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete file dependency."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFile(file *fic.EFile, _ []byte) (interface{}, error) {
|
func checkFile(c *gin.Context) {
|
||||||
return true, file.CheckFileOnDisk()
|
file := c.MustGet("file").(*fic.EFile)
|
||||||
|
|
||||||
|
err := file.CheckFileOnDisk()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,352 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DispatchFunction func(httprouter.Params, []byte) (interface{}, error)
|
|
||||||
|
|
||||||
func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
|
||||||
r.RemoteAddr = addr
|
|
||||||
}
|
|
||||||
log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())
|
|
||||||
|
|
||||||
// Read the body
|
|
||||||
if r.ContentLength < 0 || r.ContentLength > 6553600 {
|
|
||||||
http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}"), 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret interface{}
|
|
||||||
var err error = nil
|
|
||||||
|
|
||||||
ret, err = f(ps, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("X-FIC-Time", fmt.Sprintf("%f", float64(time.Now().UnixNano()/1000)/1000000))
|
|
||||||
|
|
||||||
if str, found := ret.(string); found {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(resStatus)
|
|
||||||
io.WriteString(w, str)
|
|
||||||
} else if bts, found := ret.([]byte); found {
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
w.Header().Set("Content-Disposition", "attachment")
|
|
||||||
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
||||||
w.WriteHeader(resStatus)
|
|
||||||
w.Write(bts)
|
|
||||||
} else if j, err := json.Marshal(ret); err != nil {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err), http.StatusInternalServerError)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(resStatus)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func teamPublicHandler(f func(*fic.Team, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if tid, err := strconv.ParseInt(string(ps.ByName("tid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if tid == 0 {
|
|
||||||
return f(nil, body)
|
|
||||||
} else if team, err := fic.GetTeam(tid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(team, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func teamHandler(f func(*fic.Team, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if tid, err := strconv.ParseInt(string(ps.ByName("tid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if team, err := fic.GetTeam(tid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(team, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func teamAssocHandler(f func(*fic.Team, string, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var team *fic.Team
|
|
||||||
|
|
||||||
teamHandler(func(tm *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
team = tm
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
return f(team, string(ps.ByName("assoc")), body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func themeHandler(f func(*fic.Theme, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if thid, err := strconv.ParseInt(string(ps.ByName("thid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if theme, err := fic.GetTheme(thid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(theme, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func exerciceHandler(f func(*fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if eid, err := strconv.ParseInt(string(ps.ByName("eid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if exercice, err := fic.GetExercice(eid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(exercice, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func themedExerciceHandler(f func(*fic.Theme, *fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var theme *fic.Theme
|
|
||||||
var exercice *fic.Exercice
|
|
||||||
|
|
||||||
themeHandler(func(th *fic.Theme, _ []byte) (interface{}, error) {
|
|
||||||
theme = th
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
exerciceHandler(func(ex *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
exercice = ex
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
return f(theme, exercice, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hintHandler(f func(*fic.EHint, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if hid, err := strconv.ParseInt(string(ps.ByName("hid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if hint, err := fic.GetHint(hid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(hint, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func flagKeyHandler(f func(*fic.FlagKey, *fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var exercice *fic.Exercice
|
|
||||||
exerciceHandler(func(ex *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
exercice = ex
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
if kid, err := strconv.ParseInt(string(ps.ByName("kid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if flags, err := exercice.GetFlagKeys(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
for _, flag := range flags {
|
|
||||||
if flag.Id == int(kid) {
|
|
||||||
return f(flag, exercice, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Unable to find the requested key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func choiceHandler(f func(*fic.FlagChoice, *fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var exercice *fic.Exercice
|
|
||||||
var flag *fic.FlagKey
|
|
||||||
flagKeyHandler(func(fl *fic.FlagKey, ex *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
exercice = ex
|
|
||||||
flag = fl
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
if cid, err := strconv.ParseInt(string(ps.ByName("cid")), 10, 32); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if choice, err := flag.GetChoice(int(cid)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(choice, exercice, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func quizHandler(f func(*fic.MCQ, *fic.Exercice, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var exercice *fic.Exercice
|
|
||||||
exerciceHandler(func(ex *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
exercice = ex
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
if qid, err := strconv.ParseInt(string(ps.ByName("qid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if mcqs, err := exercice.GetMCQ(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
for _, mcq := range mcqs {
|
|
||||||
if mcq.Id == int(qid) {
|
|
||||||
return f(mcq, exercice, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Unable to find the requested key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func exerciceFileHandler(f func(*fic.EFile, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var exercice *fic.Exercice
|
|
||||||
exerciceHandler(func(ex *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
exercice = ex
|
|
||||||
return nil, nil
|
|
||||||
})(ps, body)
|
|
||||||
|
|
||||||
if fid, err := strconv.ParseInt(string(ps.ByName("fid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if files, err := exercice.GetFiles(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Id == fid {
|
|
||||||
return f(file, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Unable to find the requested file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventHandler(f func(*fic.Event, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if evid, err := strconv.ParseInt(string(ps.ByName("evid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if event, err := fic.GetEvent(evid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(event, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func claimHandler(f func(*fic.Claim, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if cid, err := strconv.ParseInt(string(ps.ByName("cid")), 10, 64); err != nil {
|
|
||||||
return nil, fmt.Errorf("Invalid claim id: %w", err)
|
|
||||||
} else if claim, err := fic.GetClaim(cid); err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to find requested claim (id=%d): %w", cid, err)
|
|
||||||
} else {
|
|
||||||
return f(claim, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func claimAssigneeHandler(f func(*fic.ClaimAssignee, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if aid, err := strconv.ParseInt(string(ps.ByName("aid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if assignee, err := fic.GetAssignee(aid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(assignee, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileHandler(f func(*fic.EFile, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if fileid, err := strconv.ParseInt(string(ps.ByName("fileid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if file, err := fic.GetFile(fileid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(file, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileDependancyHandler(f func(*fic.EFile, int, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
if depid, err := strconv.ParseInt(string(ps.ByName("depid")), 10, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return fileHandler(func(file *fic.EFile, b []byte) (interface{}, error) {
|
|
||||||
return f(file, int(depid), b)
|
|
||||||
})(ps, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func certificateHandler(f func(*fic.Certificate, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var cid uint64
|
|
||||||
var err error
|
|
||||||
certid := strings.TrimSuffix(ps.ByName("certid"), ".p12")
|
|
||||||
if cid, err = strconv.ParseUint(certid, 10, 64); err != nil {
|
|
||||||
if cid, err = strconv.ParseUint(certid, 16, 64); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert, err := fic.GetCertificate(cid); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(cert, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notFound(ps httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -10,26 +11,26 @@ import (
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
"srs.epita.fr/fic-server/admin/pki"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TimestampCheck = "submissions"
|
var TimestampCheck = "submissions"
|
||||||
|
|
||||||
func init() {
|
func declareHealthRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/timestamps.json", apiHandler(
|
router.GET("/timestamps.json", func(c *gin.Context) {
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
stat, err := os.Stat(TimestampCheck)
|
||||||
if stat, err := os.Stat(TimestampCheck); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("timestamp.json: %s", err.Error())})
|
||||||
} else {
|
return
|
||||||
now := time.Now().UTC()
|
}
|
||||||
return map[string]interface{}{
|
now := time.Now().UTC()
|
||||||
"frontend": stat.ModTime().UTC(),
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"backend": now,
|
"frontend": stat.ModTime().UTC(),
|
||||||
"diffFB": now.Sub(stat.ModTime()),
|
"backend": now,
|
||||||
}, nil
|
"diffFB": now.Sub(stat.ModTime()),
|
||||||
}
|
})
|
||||||
}))
|
})
|
||||||
router.GET("/api/health.json", apiHandler(GetHealth))
|
router.GET("/health.json", GetHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
type healthFileReport struct {
|
type healthFileReport struct {
|
||||||
|
@ -41,22 +42,22 @@ type healthFileReport struct {
|
||||||
func getHealth(pathname string) (ret []healthFileReport) {
|
func getHealth(pathname string) (ret []healthFileReport) {
|
||||||
if ds, err := ioutil.ReadDir(pathname); err != nil {
|
if ds, err := ioutil.ReadDir(pathname); err != nil {
|
||||||
ret = append(ret, healthFileReport{
|
ret = append(ret, healthFileReport{
|
||||||
Path: strings.TrimPrefix(pathname, TimestampCheck),
|
Path: strings.TrimPrefix(pathname, TimestampCheck),
|
||||||
Error: fmt.Sprintf("unable to ReadDir: %s", err),
|
Error: fmt.Sprintf("unable to ReadDir: %s", err),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
for _, d := range ds {
|
for _, d := range ds {
|
||||||
p := path.Join(pathname, d.Name())
|
p := path.Join(pathname, d.Name())
|
||||||
if d.IsDir() && d.Name() != ".tmp" && d.Mode()&os.ModeSymlink == 0 {
|
if d.IsDir() && d.Name() != ".tmp" && d.Mode()&os.ModeSymlink == 0 {
|
||||||
ret = append(ret, getHealth(p)...)
|
ret = append(ret, getHealth(p)...)
|
||||||
} else if !d.IsDir() && d.Mode()&os.ModeSymlink == 0 && time.Since(d.ModTime()) > 2 * time.Second {
|
} else if !d.IsDir() && d.Mode()&os.ModeSymlink == 0 && time.Since(d.ModTime()) > 2*time.Second {
|
||||||
teamDir := strings.TrimPrefix(pathname, TimestampCheck)
|
teamDir := strings.TrimPrefix(pathname, TimestampCheck)
|
||||||
idteam, _ := pki.GetAssociation(path.Join(TeamsDir, teamDir))
|
idteam, _ := pki.GetAssociation(path.Join(TeamsDir, teamDir))
|
||||||
ret = append(ret, healthFileReport{
|
ret = append(ret, healthFileReport{
|
||||||
IdTeam: idteam,
|
IdTeam: idteam,
|
||||||
Path: path.Join(teamDir, d.Name()),
|
Path: path.Join(teamDir, d.Name()),
|
||||||
Error: "existing untreated file",
|
Error: "existing untreated file",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,10 +65,11 @@ func getHealth(pathname string) (ret []healthFileReport) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHealth(httprouter.Params, []byte) (interface{}, error) {
|
func GetHealth(c *gin.Context) {
|
||||||
if _, err := os.Stat(TimestampCheck); err != nil {
|
if _, err := os.Stat(TimestampCheck); err != nil {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("health.json: %s", err.Error())})
|
||||||
} else {
|
return
|
||||||
return getHealth(TimestampCheck), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, getHealth(TimestampCheck))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,20 @@ package api
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareMonitorRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/monitor", apiHandler(
|
router.GET("/monitor", func(c *gin.Context) {
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
c.JSON(http.StatusOK, gin.H{
|
||||||
return map[string]interface{}{
|
"localhost": genLocalConstants(),
|
||||||
"localhost": genLocalConstants(),
|
})
|
||||||
}, nil
|
})
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readLoadAvg(fd *os.File) (ret map[string]float64) {
|
func readLoadAvg(fd *os.File) (ret map[string]float64) {
|
||||||
|
@ -58,7 +58,7 @@ func readCPUStats(fd *os.File) (ret map[string]map[string]uint64) {
|
||||||
ret[f[0]] = map[string]uint64{}
|
ret[f[0]] = map[string]uint64{}
|
||||||
var total uint64 = 0
|
var total uint64 = 0
|
||||||
for i, k := range []string{"user", "nice", "system", "idle", "iowait", "irq", "softirq"} {
|
for i, k := range []string{"user", "nice", "system", "idle", "iowait", "irq", "softirq"} {
|
||||||
if v, err := strconv.ParseUint(f[i + 1], 10, 64); err == nil {
|
if v, err := strconv.ParseUint(f[i+1], 10, 64); err == nil {
|
||||||
ret[f[0]][k] = v
|
ret[f[0]][k] = v
|
||||||
total += v
|
total += v
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,67 +4,97 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
"srs.epita.fr/fic-server/admin/pki"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var OidcSecret = ""
|
var OidcSecret = ""
|
||||||
|
|
||||||
func init() {
|
func declarePasswordRoutes(router *gin.RouterGroup) {
|
||||||
router.POST("/api/password", apiHandler(
|
router.POST("/password", func(c *gin.Context) {
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
passwd, err := fic.GeneratePassword()
|
||||||
if passwd, err := fic.GeneratePassword(); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
} else {
|
return
|
||||||
return map[string]string{"password": passwd}, nil
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"password": passwd})
|
||||||
|
})
|
||||||
|
router.GET("/api/dex.yaml", func(c *gin.Context) {
|
||||||
|
cfg, err := genDexConfig()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, string(cfg))
|
||||||
|
})
|
||||||
|
router.POST("/api/dex.yaml", func(c *gin.Context) {
|
||||||
|
if dexcfg, err := genDexConfig(); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-config.yaml"), []byte(dexcfg), 0644); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
|
})
|
||||||
|
router.GET("/api/dex-password.tpl", func(c *gin.Context) {
|
||||||
|
passtpl, err := genDexPasswordTpl()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, string(passtpl))
|
||||||
|
})
|
||||||
|
router.POST("/api/dex-password.tpl", func(c *gin.Context) {
|
||||||
|
if dexcfg, err := genDexPasswordTpl(); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-password.tpl"), []byte(dexcfg), 0644); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func declareTeamsPasswordRoutes(router *gin.RouterGroup) {
|
||||||
|
router.GET("/password", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
c.String(http.StatusOK, *team.Password)
|
||||||
|
})
|
||||||
|
router.POST("/password", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
if passwd, err := fic.GeneratePassword(); err != nil {
|
||||||
|
log.Println("Unable to GeneratePassword:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Something went wrong when generating the new team password"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
team.Password = &passwd
|
||||||
|
|
||||||
|
t, err := team.Update()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to Update Team:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Something went wrong when updating the new team password"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}))
|
|
||||||
router.GET("/api/teams/:tid/password", apiHandler(teamHandler(
|
c.JSON(http.StatusOK, t)
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
}
|
||||||
return team.Password, nil
|
})
|
||||||
})))
|
|
||||||
router.POST("/api/teams/:tid/password", apiHandler(teamHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
if passwd, err := fic.GeneratePassword(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
team.Password = &passwd
|
|
||||||
return team.Update()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
router.GET("/api/dex.yaml", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return genDexConfig()
|
|
||||||
}))
|
|
||||||
router.POST("/api/dex.yaml", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
if dexcfg, err := genDexConfig(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-config.yaml"), []byte(dexcfg), 0644); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
router.GET("/api/dex-password.tpl", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return genDexPasswordTpl()
|
|
||||||
}))
|
|
||||||
router.POST("/api/dex-password.tpl", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
if dexcfg, err := genDexPasswordTpl(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-password.tpl"), []byte(dexcfg), 0644); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dexcfgtpl = `issuer: https://fic.srs.epita.fr
|
const dexcfgtpl = `issuer: https://fic.srs.epita.fr
|
||||||
|
|
|
@ -3,18 +3,20 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DashboardDir string
|
var DashboardDir string
|
||||||
|
|
||||||
func init() {
|
func declarePublicRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/public/:sid", apiHandler(getPublic))
|
router.GET("/public/:sid", getPublic)
|
||||||
router.DELETE("/api/public/:sid", apiHandler(deletePublic))
|
router.DELETE("/public/:sid", deletePublic)
|
||||||
router.PUT("/api/public/:sid", apiHandler(savePublic))
|
router.PUT("/public/:sid", savePublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FICPublicScene struct {
|
type FICPublicScene struct {
|
||||||
|
@ -62,31 +64,44 @@ func savePublicTo(path string, s FICPublicDisplay) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPublic(ps httprouter.Params, body []byte) (interface{}, error) {
|
func getPublic(c *gin.Context) {
|
||||||
if _, err := os.Stat(path.Join(DashboardDir, fmt.Sprintf("public%s.json", ps.ByName("sid")))); !os.IsNotExist(err) {
|
if _, err := os.Stat(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid")))); !os.IsNotExist(err) {
|
||||||
return readPublic(path.Join(DashboardDir, fmt.Sprintf("public%s.json", ps.ByName("sid"))))
|
p, err := readPublic(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid"))))
|
||||||
} else {
|
if err != nil {
|
||||||
return FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}}, nil
|
log.Println("Unable to readPublic in getPublic:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene retrieval."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletePublic(ps httprouter.Params, body []byte) (interface{}, error) {
|
func deletePublic(c *gin.Context) {
|
||||||
if err := savePublicTo(path.Join(DashboardDir, fmt.Sprintf("public%s.json", ps.ByName("sid"))), FICPublicDisplay{}); err != nil {
|
if err := savePublicTo(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid"))), FICPublicDisplay{}); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to deletePublic:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
|
||||||
return FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}}, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func savePublic(ps httprouter.Params, body []byte) (interface{}, error) {
|
func savePublic(c *gin.Context) {
|
||||||
var scenes FICPublicDisplay
|
var scenes FICPublicDisplay
|
||||||
if err := json.Unmarshal(body, &scenes); err != nil {
|
err := c.ShouldBindJSON(&scenes)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := savePublicTo(path.Join(DashboardDir, fmt.Sprintf("public%s.json", ps.ByName("sid"))), scenes); err != nil {
|
if err := savePublicTo(path.Join(DashboardDir, fmt.Sprintf("public%s.json", c.Params.ByName("sid"))), scenes); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to savePublicTo:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene saving."})
|
||||||
return scenes, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, scenes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,84 +1,118 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"log"
|
||||||
"errors"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareQARoutes(router *gin.RouterGroup) {
|
||||||
router.POST("/api/qa/", apiHandler(importExerciceQA))
|
router.POST("/qa/", importExerciceQA)
|
||||||
router.POST("/api/qa/:qid/comments", apiHandler(qaHandler(importQAComment)))
|
|
||||||
|
apiQARoutes := router.Group("/qa/:qid")
|
||||||
|
apiQARoutes.POST("/comments", importQAComment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func qaHandler(f func(*fic.QAQuery, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
|
func QAHandler(c *gin.Context) {
|
||||||
return func(ps httprouter.Params, body []byte) (interface{}, error) {
|
qid, err := strconv.ParseInt(string(c.Params.ByName("qid")), 10, 64)
|
||||||
if qid, err := strconv.ParseInt(string(ps.ByName("qid")), 10, 64); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid QA identifier"})
|
||||||
} else if query, err := fic.GetQAQuery(qid); err != nil {
|
return
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return f(query, body)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qa, err := fic.GetQAQuery(qid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "QA query not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("qa-query", qa)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
func importExerciceQA(_ httprouter.Params, body []byte) (interface{}, error) {
|
func importExerciceQA(c *gin.Context) {
|
||||||
// Create a new query
|
// Create a new query
|
||||||
var uq fic.QAQuery
|
var uq fic.QAQuery
|
||||||
if err := json.Unmarshal(body, &uq); err != nil {
|
err := c.ShouldBindJSON(&uq)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var exercice *fic.Exercice
|
var exercice *fic.Exercice
|
||||||
var err error
|
|
||||||
if uq.IdExercice == 0 {
|
if uq.IdExercice == 0 {
|
||||||
return nil, errors.New("id_exercice not filled")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "id_exercice not filled"})
|
||||||
|
return
|
||||||
} else if exercice, err = fic.GetExercice(uq.IdExercice); err != nil {
|
} else if exercice, err = fic.GetExercice(uq.IdExercice); err != nil {
|
||||||
return nil, err
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to find requested exercice"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uq.State) == 0 {
|
if len(uq.State) == 0 {
|
||||||
return nil, errors.New("State not filled")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "State not filled"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uq.Subject) == 0 {
|
if len(uq.Subject) == 0 {
|
||||||
return nil, errors.New("Subject not filled")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Subject not filled"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if qa, err := exercice.NewQAQuery(uq.Subject, uq.IdTeam, uq.User, uq.State); err != nil {
|
if qa, err := exercice.NewQAQuery(uq.Subject, uq.IdTeam, uq.User, uq.State); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to importExerciceQA:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during query creation."})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
qa.Creation = uq.Creation
|
qa.Creation = uq.Creation
|
||||||
qa.Solved = uq.Solved
|
qa.Solved = uq.Solved
|
||||||
qa.Closed = qa.Closed
|
qa.Closed = qa.Closed
|
||||||
|
|
||||||
_, err = qa.Update()
|
_, err = qa.Update()
|
||||||
return qa, err
|
if err != nil {
|
||||||
|
log.Println("Unable to update in importExerciceQA:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during query updating."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, qa)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func importQAComment(query *fic.QAQuery, body []byte) (interface{}, error) {
|
func importQAComment(c *gin.Context) {
|
||||||
|
query := c.MustGet("qa-query").(*fic.QAQuery)
|
||||||
|
|
||||||
// Create a new query
|
// Create a new query
|
||||||
var uc fic.QAComment
|
var uc fic.QAComment
|
||||||
if err := json.Unmarshal(body, &uc); err != nil {
|
err := c.ShouldBindJSON(&uc)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uc.Content) == 0 {
|
if len(uc.Content) == 0 {
|
||||||
return nil, errors.New("Empty comment")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty comment"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if qac, err := query.AddComment(uc.Content, uc.IdTeam, uc.User); err != nil {
|
if qac, err := query.AddComment(uc.Content, uc.IdTeam, uc.User); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to AddComment in importQAComment:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during comment creation."})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
qac.Date = uc.Date
|
qac.Date = uc.Date
|
||||||
|
|
||||||
_, err = qac.Update()
|
_, err = qac.Update()
|
||||||
return qac, err
|
if err != nil {
|
||||||
|
log.Println("Unable to Update comment in importQAComment")
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during comment creation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, qac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var router = httprouter.New()
|
func DeclareRoutes(router *gin.RouterGroup) {
|
||||||
|
apiRoutes := router.Group("/api")
|
||||||
|
|
||||||
func Router() *httprouter.Router {
|
declareCertificateRoutes(apiRoutes)
|
||||||
return router
|
declareClaimsRoutes(apiRoutes)
|
||||||
|
declareEventsRoutes(apiRoutes)
|
||||||
|
declareExercicesRoutes(apiRoutes)
|
||||||
|
declareFilesRoutes(apiRoutes)
|
||||||
|
declareGlobalExercicesRoutes(apiRoutes)
|
||||||
|
declareHealthRoutes(apiRoutes)
|
||||||
|
declareMonitorRoutes(apiRoutes)
|
||||||
|
declarePasswordRoutes(apiRoutes)
|
||||||
|
declarePublicRoutes(apiRoutes)
|
||||||
|
declareQARoutes(apiRoutes)
|
||||||
|
declareTeamsRoutes(apiRoutes)
|
||||||
|
declareThemesRoutes(apiRoutes)
|
||||||
|
declareSettingsRoutes(apiRoutes)
|
||||||
|
declareSyncRoutes(apiRoutes)
|
||||||
|
DeclareVersionRoutes(apiRoutes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,44 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"errors"
|
"log"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
"srs.epita.fr/fic-server/settings"
|
"srs.epita.fr/fic-server/settings"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var IsProductionEnv = false
|
var IsProductionEnv = false
|
||||||
|
|
||||||
func init() {
|
func declareSettingsRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/challenge.json", apiHandler(getChallengeInfo))
|
router.GET("/challenge.json", getChallengeInfo)
|
||||||
router.PUT("/api/challenge.json", apiHandler(saveChallengeInfo))
|
router.PUT("/challenge.json", saveChallengeInfo)
|
||||||
|
|
||||||
router.GET("/api/settings-ro.json", apiHandler(getROSettings))
|
router.GET("/settings-ro.json", getROSettings)
|
||||||
router.GET("/api/settings.json", apiHandler(getSettings))
|
router.GET("/settings.json", getSettings)
|
||||||
router.PUT("/api/settings.json", apiHandler(saveSettings))
|
router.PUT("/settings.json", saveSettings)
|
||||||
router.DELETE("/api/settings.json", apiHandler(func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
router.DELETE("/settings.json", func(c *gin.Context) {
|
||||||
return true, ResetSettings()
|
err := ResetSettings()
|
||||||
}))
|
if err != nil {
|
||||||
|
log.Println("Unable to ResetSettings:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during setting reset."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
router.POST("/api/reset", apiHandler(reset))
|
c.JSON(http.StatusOK, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/reset", reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getROSettings(_ httprouter.Params, body []byte) (interface{}, error) {
|
func getROSettings(c *gin.Context) {
|
||||||
syncMtd := "Disabled"
|
syncMtd := "Disabled"
|
||||||
if sync.GlobalImporter != nil {
|
if sync.GlobalImporter != nil {
|
||||||
syncMtd = sync.GlobalImporter.Kind()
|
syncMtd = sync.GlobalImporter.Kind()
|
||||||
|
@ -40,51 +49,70 @@ func getROSettings(_ httprouter.Params, body []byte) (interface{}, error) {
|
||||||
syncId = sync.GlobalImporter.Id()
|
syncId = sync.GlobalImporter.Id()
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"sync-type": reflect.TypeOf(sync.GlobalImporter).Name(),
|
"sync-type": reflect.TypeOf(sync.GlobalImporter).Name(),
|
||||||
"sync-id": syncId,
|
"sync-id": syncId,
|
||||||
"sync": syncMtd,
|
"sync": syncMtd,
|
||||||
}, nil
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChallengeInfo(_ httprouter.Params, body []byte) (interface{}, error) {
|
func getChallengeInfo(c *gin.Context) {
|
||||||
return settings.ReadChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile))
|
s, err := settings.ReadChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to ReadChallengeInfo:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to read challenge info: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveChallengeInfo(_ httprouter.Params, body []byte) (interface{}, error) {
|
func saveChallengeInfo(c *gin.Context) {
|
||||||
var info *settings.ChallengeInfo
|
var info *settings.ChallengeInfo
|
||||||
if err := json.Unmarshal(body, &info); err != nil {
|
err := c.ShouldBindJSON(&info)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := settings.SaveChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile), info); err != nil {
|
if err := settings.SaveChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile), info); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to SaveChallengeInfo:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to save challenge info: %s", err.Error())})
|
||||||
return info, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSettings(_ httprouter.Params, body []byte) (interface{}, error) {
|
func getSettings(c *gin.Context) {
|
||||||
if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile))
|
||||||
return nil, err
|
if err != nil {
|
||||||
} else {
|
log.Println("Unable to ReadSettings:", err.Error())
|
||||||
s.WorkInProgress = !IsProductionEnv
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to read settings: %s", err.Error())})
|
||||||
return s, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.WorkInProgress = !IsProductionEnv
|
||||||
|
c.Writer.Header().Add("X-FIC-Time", fmt.Sprintf("%d", time.Now().Unix()))
|
||||||
|
c.JSON(http.StatusOK, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveSettings(_ httprouter.Params, body []byte) (interface{}, error) {
|
func saveSettings(c *gin.Context) {
|
||||||
var config *settings.Settings
|
var config *settings.Settings
|
||||||
if err := json.Unmarshal(body, &config); err != nil {
|
err := c.ShouldBindJSON(&config)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), config); err != nil {
|
if err := settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), config); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to SaveSettings:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to save settings: %s", err.Error())})
|
||||||
ApplySettings(config)
|
return
|
||||||
return config, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplySettings(config)
|
||||||
|
c.JSON(http.StatusOK, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApplySettings(config *settings.Settings) {
|
func ApplySettings(config *settings.Settings) {
|
||||||
|
@ -160,25 +188,40 @@ func ResetChallengeInfo() error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset(_ httprouter.Params, body []byte) (interface{}, error) {
|
func reset(c *gin.Context) {
|
||||||
var m map[string]string
|
var m map[string]string
|
||||||
if err := json.Unmarshal(body, &m); err != nil {
|
err := c.ShouldBindJSON(&m)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if t, ok := m["type"]; !ok {
|
t, ok := m["type"]
|
||||||
return nil, errors.New("Field type not found")
|
if !ok {
|
||||||
} else if t == "teams" {
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Field type not found"})
|
||||||
return true, fic.ResetTeams()
|
|
||||||
} else if t == "challenges" {
|
|
||||||
return true, fic.ResetExercices()
|
|
||||||
} else if t == "game" {
|
|
||||||
return true, fic.ResetGame()
|
|
||||||
} else if t == "settings" {
|
|
||||||
return true, ResetSettings()
|
|
||||||
} else if t == "challengeInfo" {
|
|
||||||
return true, ResetChallengeInfo()
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Unknown reset type")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "teams":
|
||||||
|
err = fic.ResetTeams()
|
||||||
|
case "challenges":
|
||||||
|
err = fic.ResetExercices()
|
||||||
|
case "game":
|
||||||
|
err = fic.ResetGame()
|
||||||
|
case "settings":
|
||||||
|
err = ResetSettings()
|
||||||
|
case "challengeInfo":
|
||||||
|
err = ResetChallengeInfo()
|
||||||
|
default:
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unknown reset type"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to reset (type=%q): %s", t, err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to performe the reset: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
191
admin/api/sync.go
Normal file
191
admin/api/sync.go
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
"srs.epita.fr/fic-server/settings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func declareSyncRoutes(router *gin.RouterGroup) {
|
||||||
|
apiSyncRoutes := router.Group("/sync")
|
||||||
|
|
||||||
|
// Base sync checks if the local directory is in sync with remote one.
|
||||||
|
apiSyncRoutes.POST("/base", func(c *gin.Context) {
|
||||||
|
err := sync.GlobalImporter.Sync()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Speedy sync performs a recursive synchronization without importing files.
|
||||||
|
apiSyncRoutes.POST("/speed", func(c *gin.Context) {
|
||||||
|
st := sync.SpeedySyncDeep(sync.GlobalImporter)
|
||||||
|
sync.EditDeepReport(st, false)
|
||||||
|
c.JSON(http.StatusOK, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Deep sync: a fully recursive synchronization (can be limited by theme).
|
||||||
|
apiSyncRoutes.GET("/deep", func(c *gin.Context) {
|
||||||
|
if sync.DeepSyncProgress == 0 {
|
||||||
|
c.AbortWithStatusJSON(http.StatusTooEarly, gin.H{"errmsg": "Pas de synchronisation en cours"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"progress": sync.DeepSyncProgress})
|
||||||
|
})
|
||||||
|
apiSyncRoutes.POST("/deep", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, sync.SyncDeep(sync.GlobalImporter))
|
||||||
|
})
|
||||||
|
|
||||||
|
apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid")
|
||||||
|
apiSyncDeepRoutes.Use(ThemeHandler)
|
||||||
|
apiSyncDeepRoutes.POST("", func(c *gin.Context) {
|
||||||
|
theme := c.MustGet("theme").(*fic.Theme)
|
||||||
|
|
||||||
|
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
|
||||||
|
sync.EditDeepReport(map[string][]string{theme.Name: st}, false)
|
||||||
|
sync.DeepSyncProgress = 255
|
||||||
|
c.JSON(http.StatusOK, st)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auto sync: to use with continuous deployment, in a development env
|
||||||
|
apiSyncRoutes.POST("/auto/*p", autoSync)
|
||||||
|
|
||||||
|
// Themes
|
||||||
|
apiSyncRoutes.POST("/fixurlids", fixAllURLIds)
|
||||||
|
|
||||||
|
apiSyncRoutes.POST("/themes", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, sync.SyncThemes(sync.GlobalImporter))
|
||||||
|
})
|
||||||
|
|
||||||
|
apiSyncThemesRoutes := apiSyncRoutes.Group("/themes/:thid")
|
||||||
|
apiSyncThemesRoutes.Use(ThemeHandler)
|
||||||
|
apiSyncThemesRoutes.POST("/fixurlid", func(c *gin.Context) {
|
||||||
|
theme := c.MustGet("theme").(*fic.Theme)
|
||||||
|
if theme.FixURLId() {
|
||||||
|
v, err := theme.Update()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to UpdateTheme after fixurlid:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the theme."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, v)
|
||||||
|
} else {
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Exercices
|
||||||
|
declareSyncExercicesRoutes(apiSyncRoutes)
|
||||||
|
declareSyncExercicesRoutes(apiSyncThemesRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func declareSyncExercicesRoutes(router *gin.RouterGroup) {
|
||||||
|
router.POST("/exercices", func(c *gin.Context) {
|
||||||
|
theme := c.MustGet("theme").(*fic.Theme)
|
||||||
|
c.JSON(http.StatusOK, sync.SyncExercices(sync.GlobalImporter, theme))
|
||||||
|
})
|
||||||
|
apiSyncExercicesRoutes := router.Group("/exercices/:eid")
|
||||||
|
apiSyncExercicesRoutes.Use(ExerciceHandler)
|
||||||
|
apiSyncExercicesRoutes.POST("", func(c *gin.Context) {
|
||||||
|
theme, exists := c.Get("theme")
|
||||||
|
if !exists {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "You should sync exercice only through a theme."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
|
||||||
|
_, _, errs := sync.SyncExercice(sync.GlobalImporter, theme.(*fic.Theme), exercice.Path, nil)
|
||||||
|
c.JSON(http.StatusOK, errs)
|
||||||
|
})
|
||||||
|
apiSyncExercicesRoutes.POST("/files", func(c *gin.Context) {
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
c.JSON(http.StatusOK, sync.SyncExerciceFiles(sync.GlobalImporter, exercice))
|
||||||
|
})
|
||||||
|
apiSyncExercicesRoutes.POST("/fixurlid", func(c *gin.Context) {
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
if exercice.FixURLId() {
|
||||||
|
v, err := exercice.Update()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to UpdateExercice after fixurlid:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the exercice."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, v)
|
||||||
|
} else {
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
apiSyncExercicesRoutes.POST("/hints", func(c *gin.Context) {
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
||||||
|
c.JSON(http.StatusOK, errs)
|
||||||
|
})
|
||||||
|
apiSyncExercicesRoutes.POST("/flags", func(c *gin.Context) {
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
|
||||||
|
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
||||||
|
c.JSON(http.StatusOK, append(errs, herrs...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// autoSync tries to performs a smart synchronization, when in development environment.
|
||||||
|
// It'll sync most of modified things, and will delete out of sync data.
|
||||||
|
// Avoid using it in a production environment.
|
||||||
|
func autoSync(c *gin.Context) {
|
||||||
|
p := strings.TrimPrefix(c.Params.ByName("p"), "/")
|
||||||
|
|
||||||
|
themes, err := fic.GetThemes()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetThemes:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve theme list."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p == "" {
|
||||||
|
if !IsProductionEnv {
|
||||||
|
for _, theme := range themes {
|
||||||
|
theme.DeleteDeep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st := sync.SyncDeep(sync.GlobalImporter)
|
||||||
|
c.JSON(http.StatusOK, st)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, theme := range themes {
|
||||||
|
if theme.Path == p {
|
||||||
|
if !IsProductionEnv {
|
||||||
|
exercices, err := theme.GetExercices()
|
||||||
|
if err == nil {
|
||||||
|
for _, exercice := range exercices {
|
||||||
|
exercice.DeleteDeep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
|
||||||
|
sync.EditDeepReport(map[string][]string{theme.Name: st}, false)
|
||||||
|
sync.DeepSyncProgress = 255
|
||||||
|
|
||||||
|
settings.ForceRegeneration()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, st)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("Theme not found %q", p)})
|
||||||
|
}
|
|
@ -3,167 +3,312 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
"srs.epita.fr/fic-server/admin/pki"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareTeamsRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/teams.json", apiHandler(
|
router.GET("/teams.json", func(c *gin.Context) {
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
teams, err := fic.ExportTeams(false)
|
||||||
return fic.ExportTeams(false)
|
if err != nil {
|
||||||
}))
|
log.Println("Unable to ExportTeams:", err.Error())
|
||||||
router.GET("/api/teams-members.json", apiHandler(
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams export."})
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
return
|
||||||
return fic.ExportTeams(true)
|
|
||||||
}))
|
|
||||||
router.GET("/api/teams-binding", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return bindingTeams()
|
|
||||||
}))
|
|
||||||
router.GET("/api/teams-nginx", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return nginxGenTeams()
|
|
||||||
}))
|
|
||||||
router.POST("/api/disableinactiveteams", apiHandler(disableInactiveTeams))
|
|
||||||
router.POST("/api/enableallteams", apiHandler(enableAllTeams))
|
|
||||||
router.GET("/api/teams-members-nginx", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return nginxGenMember()
|
|
||||||
}))
|
|
||||||
router.GET("/api/teams-tries.json", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return fic.GetTries(nil, nil)
|
|
||||||
}))
|
|
||||||
|
|
||||||
router.GET("/api/teams/", apiHandler(
|
|
||||||
func(httprouter.Params, []byte) (interface{}, error) {
|
|
||||||
return fic.GetTeams()
|
|
||||||
}))
|
|
||||||
router.POST("/api/teams/", apiHandler(createTeam))
|
|
||||||
|
|
||||||
router.GET("/api/teams/:tid/", apiHandler(teamHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return team, nil
|
|
||||||
})))
|
|
||||||
router.PUT("/api/teams/:tid/", apiHandler(teamHandler(updateTeam)))
|
|
||||||
router.POST("/api/teams/:tid/", apiHandler(teamHandler(addTeamMember)))
|
|
||||||
router.DELETE("/api/teams/:tid/", apiHandler(teamHandler(deleteTeam)))
|
|
||||||
router.GET("/api/teams/:tid/score-grid.json", apiHandler(teamHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return team.ScoreGrid()
|
|
||||||
})))
|
|
||||||
router.GET("/api/teams/:tid/my.json", apiHandler(teamPublicHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return fic.MyJSONTeam(team, true)
|
|
||||||
})))
|
|
||||||
router.GET("/api/teams/:tid/wait.json", apiHandler(teamPublicHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return fic.MyJSONTeam(team, false)
|
|
||||||
})))
|
|
||||||
router.GET("/api/teams/:tid/stats.json", apiHandler(teamPublicHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
if team != nil {
|
|
||||||
return team.GetStats()
|
|
||||||
} else {
|
|
||||||
return fic.GetTeamsStats(nil)
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
router.GET("/api/teams/:tid/history.json", apiHandler(teamPublicHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
if team != nil {
|
|
||||||
return team.GetHistory()
|
|
||||||
} else {
|
|
||||||
return fic.GetTeamsStats(nil)
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
router.DELETE("/api/teams/:tid/history.json", apiHandler(teamPublicHandler(delHistory)))
|
|
||||||
router.GET("/api/teams/:tid/tries", apiHandler(teamPublicHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return fic.GetTries(team, nil)
|
|
||||||
})))
|
|
||||||
router.GET("/api/teams/:tid/members", apiHandler(teamHandler(
|
|
||||||
func(team *fic.Team, _ []byte) (interface{}, error) {
|
|
||||||
return team.GetMembers()
|
|
||||||
})))
|
|
||||||
router.POST("/api/teams/:tid/members", apiHandler(teamHandler(addTeamMember)))
|
|
||||||
router.PUT("/api/teams/:tid/members", apiHandler(teamHandler(setTeamMember)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func nginxGenTeams() (string, error) {
|
|
||||||
if teams, err := fic.GetTeams(); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
ret := ""
|
|
||||||
for _, team := range teams {
|
|
||||||
ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%d\"; }\n", strings.ToLower(team.Name), team.Id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
c.JSON(http.StatusOK, teams)
|
||||||
}
|
})
|
||||||
}
|
router.GET("/teams-members.json", func(c *gin.Context) {
|
||||||
|
teams, err := fic.ExportTeams(true)
|
||||||
func nginxGenMember() (string, error) {
|
if err != nil {
|
||||||
if teams, err := fic.GetTeams(); err != nil {
|
log.Println("Unable to ExportTeams:", err.Error())
|
||||||
return "", err
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams export."})
|
||||||
} else {
|
return
|
||||||
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 \"%d\"; }\n", member.Nickname, team.Id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
c.JSON(http.StatusOK, teams)
|
||||||
}
|
})
|
||||||
}
|
router.GET("/teams-binding", bindingTeams)
|
||||||
|
router.GET("/teams-nginx", nginxGenTeams)
|
||||||
func bindingTeams() (string, error) {
|
router.POST("/disableinactiveteams", disableInactiveTeams)
|
||||||
if teams, err := fic.GetTeams(); err != nil {
|
router.POST("/enableallteams", enableAllTeams)
|
||||||
return "", err
|
router.GET("/teams-members-nginx", nginxGenMember)
|
||||||
} else {
|
router.GET("/teams-tries.json", func(c *gin.Context) {
|
||||||
ret := ""
|
tries, err := fic.GetTries(nil, nil)
|
||||||
for _, team := range teams {
|
if err != nil {
|
||||||
if members, err := team.GetMembers(); err != nil {
|
log.Println("Unable to GetTries:", err.Error())
|
||||||
return "", err
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
|
||||||
} else {
|
return
|
||||||
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
|
|
||||||
}
|
c.JSON(http.StatusOK, tries)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/teams", func(c *gin.Context) {
|
||||||
|
teams, err := fic.GetTeams()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTeams:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams listing."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, teams)
|
||||||
|
})
|
||||||
|
router.POST("/teams", createTeam)
|
||||||
|
|
||||||
|
apiTeamsRoutes := router.Group("/teams/:tid")
|
||||||
|
apiTeamsRoutes.Use(TeamHandler)
|
||||||
|
apiTeamsRoutes.GET("/", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("team").(*fic.Team))
|
||||||
|
})
|
||||||
|
apiTeamsRoutes.PUT("/", updateTeam)
|
||||||
|
apiTeamsRoutes.POST("/", addTeamMember)
|
||||||
|
apiTeamsRoutes.DELETE("/", deleteTeam)
|
||||||
|
apiTeamsRoutes.GET("/score-grid.json", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
sg, err := team.ScoreGrid()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to get ScoreGrid(tid=%d): %s", team.Id, err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during score grid calculation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, sg)
|
||||||
|
})
|
||||||
|
|
||||||
|
apiTeamsPublicRoutes := router.Group("/teams/:tid")
|
||||||
|
apiTeamsPublicRoutes.Use(TeamPublicHandler)
|
||||||
|
apiTeamsPublicRoutes.GET("/my.json", func(c *gin.Context) {
|
||||||
|
tfile, err := fic.MyJSONTeam(c.MustGet("team").(*fic.Team), true)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to get MyJSONTeam:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team JSON generation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, tfile)
|
||||||
|
})
|
||||||
|
apiTeamsPublicRoutes.GET("/wait.json", func(c *gin.Context) {
|
||||||
|
tfile, err := fic.MyJSONTeam(c.MustGet("team").(*fic.Team), false)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to get MyJSONTeam:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team JSON generation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, tfile)
|
||||||
|
})
|
||||||
|
apiTeamsPublicRoutes.GET("/stats.json", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
if team != nil {
|
||||||
|
stats, err := team.GetStats()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to get GetStats:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during stats calculation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, stats)
|
||||||
|
} else {
|
||||||
|
stats, err := fic.GetTeamsStats(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to get GetTeamsStats:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during global stats calculation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, stats)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
apiTeamsRoutes.GET("/history.json", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
history, err := team.GetHistory()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to get GetHistory:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history calculation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, history)
|
||||||
|
})
|
||||||
|
apiTeamsRoutes.DELETE("/history.json", delHistory)
|
||||||
|
apiTeamsPublicRoutes.GET("/tries", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
tries, err := fic.GetTries(team, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTries:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during tries calculation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, tries)
|
||||||
|
})
|
||||||
|
apiTeamsRoutes.GET("/members", func(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
members, err := team.GetMembers()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetMembers:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during members retrieval."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, members)
|
||||||
|
})
|
||||||
|
apiTeamsRoutes.POST("/members", addTeamMember)
|
||||||
|
apiTeamsRoutes.PUT("/members", setTeamMember)
|
||||||
|
|
||||||
|
declareTeamsPasswordRoutes(apiTeamsRoutes)
|
||||||
|
declareTeamClaimsRoutes(apiTeamsRoutes)
|
||||||
|
declareTeamCertificateRoutes(apiTeamsRoutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTeam(_ httprouter.Params, body []byte) (interface{}, error) {
|
func TeamHandler(c *gin.Context) {
|
||||||
|
tid, err := strconv.ParseInt(string(c.Params.ByName("tid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid team identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
team, err := fic.GetTeam(tid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("team", team)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamPublicHandler(c *gin.Context) {
|
||||||
|
tid, err := strconv.ParseInt(string(c.Params.ByName("tid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid team identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tid != 0 {
|
||||||
|
team, err := fic.GetTeam(tid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("team", team)
|
||||||
|
} else {
|
||||||
|
c.Set("team", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func nginxGenTeams(c *gin.Context) {
|
||||||
|
teams, err := fic.GetTeams()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTeams:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := ""
|
||||||
|
for _, team := range teams {
|
||||||
|
ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%d\"; }\n", strings.ToLower(team.Name), team.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nginxGenMember(c *gin.Context) {
|
||||||
|
teams, err := fic.GetTeams()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTeams:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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 \"%d\"; }\n", member.Nickname, team.Id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindingTeams(c *gin.Context) {
|
||||||
|
teams, err := fic.GetTeams()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTeams:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := ""
|
||||||
|
for _, team := range teams {
|
||||||
|
if members, err := team.GetMembers(); err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
} 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, ";"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTeam(c *gin.Context) {
|
||||||
var ut fic.Team
|
var ut fic.Team
|
||||||
if err := json.Unmarshal(body, &ut); err != nil {
|
err := c.ShouldBindJSON(&ut)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ut.Color == 0 {
|
if ut.Color == 0 {
|
||||||
ut.Color = fic.HSL{rand.Float64(), 1, 0.5}.ToRGB()
|
ut.Color = fic.HSL{rand.Float64(), 1, 0.5}.ToRGB()
|
||||||
}
|
}
|
||||||
|
|
||||||
return fic.CreateTeam(strings.TrimSpace(ut.Name), ut.Color, ut.ExternalId)
|
team, err := fic.CreateTeam(strings.TrimSpace(ut.Name), ut.Color, ut.ExternalId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to CreateTeam:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team creation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, team)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTeam(team *fic.Team, body []byte) (interface{}, error) {
|
func updateTeam(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
var ut fic.Team
|
var ut fic.Team
|
||||||
if err := json.Unmarshal(body, &ut); err != nil {
|
err := c.ShouldBindJSON(&ut)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ut.Id = team.Id
|
ut.Id = team.Id
|
||||||
|
@ -172,102 +317,132 @@ func updateTeam(team *fic.Team, body []byte) (interface{}, error) {
|
||||||
ut.Password = nil
|
ut.Password = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := ut.Update(); err != nil {
|
_, err = ut.Update()
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
log.Println("Unable to updateTeam:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team updating."})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return ut, nil
|
c.JSON(http.StatusOK, team)
|
||||||
}
|
}
|
||||||
|
|
||||||
func disableInactiveTeams(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func disableInactiveTeams(c *gin.Context) {
|
||||||
if teams, err := fic.GetTeams(); err != nil {
|
teams, err := fic.GetTeams()
|
||||||
return nil, err
|
if err != nil {
|
||||||
} else {
|
log.Println("Unable to GetTeams:", err.Error())
|
||||||
for _, team := range teams {
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
var serials []uint64
|
return
|
||||||
serials, err = pki.GetTeamSerials(TeamsDir, team.Id)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var assocs []string
|
for _, team := range teams {
|
||||||
assocs, err = pki.GetTeamAssociations(TeamsDir, team.Id)
|
var serials []uint64
|
||||||
if err != nil {
|
serials, err = pki.GetTeamSerials(TeamsDir, team.Id)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
if len(serials) == 0 && len(assocs) == 0 {
|
|
||||||
if team.Active {
|
|
||||||
team.Active = false
|
|
||||||
team.Update()
|
|
||||||
}
|
|
||||||
} else if !team.Active {
|
|
||||||
team.Active = true
|
|
||||||
team.Update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
var assocs []string
|
||||||
}
|
assocs, err = pki.GetTeamAssociations(TeamsDir, team.Id)
|
||||||
}
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
func enableAllTeams(_ httprouter.Params, _ []byte) (interface{}, error) {
|
return
|
||||||
if teams, err := fic.GetTeams(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
for _, team := range teams {
|
|
||||||
if !team.Active {
|
|
||||||
team.Active = true
|
|
||||||
team.Update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
if len(serials) == 0 && len(assocs) == 0 {
|
||||||
|
if team.Active {
|
||||||
|
team.Active = false
|
||||||
|
team.Update()
|
||||||
|
}
|
||||||
|
} else if !team.Active {
|
||||||
|
team.Active = true
|
||||||
|
team.Update()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteTeam(team *fic.Team, _ []byte) (interface{}, error) {
|
func enableAllTeams(c *gin.Context) {
|
||||||
|
teams, err := fic.GetTeams()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTeams:", err.Error())
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, team := range teams {
|
||||||
|
if !team.Active {
|
||||||
|
team.Active = true
|
||||||
|
team.Update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteTeam(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Printf("Unable to GetTeamAssociations(tid=%s): %s", team.Id, err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve team association."})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, assoc := range assocs {
|
for _, assoc := range assocs {
|
||||||
err = pki.DeleteTeamAssociation(TeamsDir, assoc)
|
err = pki.DeleteTeamAssociation(TeamsDir, assoc)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to DeleteTeamAssociation(assoc=%s): %s", assoc, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = team.Delete()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Println("Unable to deleteTeam:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team deletion."})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return team.Delete()
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTeamMember(team *fic.Team, body []byte) (interface{}, error) {
|
func addTeamMember(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
var members []fic.Member
|
var members []fic.Member
|
||||||
if err := json.Unmarshal(body, &members); err != nil {
|
err := c.ShouldBindJSON(&members)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company))
|
_, err := team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to AddMember:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during member creation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return team.GetMembers()
|
mmbrs, err := team.GetMembers()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to retrieve members list:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve members list."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, mmbrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTeamMember(team *fic.Team, body []byte) (interface{}, error) {
|
func setTeamMember(c *gin.Context) {
|
||||||
var members []fic.Member
|
team := c.MustGet("team").(*fic.Team)
|
||||||
if err := json.Unmarshal(body, &members); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
team.ClearMembers()
|
team.ClearMembers()
|
||||||
for _, member := range members {
|
addTeamMember(c)
|
||||||
team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company))
|
|
||||||
}
|
|
||||||
|
|
||||||
return team.GetMembers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type uploadedHistory struct {
|
type uploadedHistory struct {
|
||||||
|
@ -294,11 +469,21 @@ func updateHistory(team *fic.Team, body []byte) (interface{}, error) {
|
||||||
return team.UpdateHistoryCoeff(uh.Kind, uh.Time, givenId, uh.Coefficient)
|
return team.UpdateHistoryCoeff(uh.Kind, uh.Time, givenId, uh.Coefficient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func delHistory(team *fic.Team, body []byte) (interface{}, error) {
|
func delHistory(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
var uh uploadedHistory
|
var uh uploadedHistory
|
||||||
if err := json.Unmarshal(body, &uh); err != nil {
|
err := c.ShouldBindJSON(&uh)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return team.DelHistoryItem(uh.Kind, uh.Time, uh.Primary, uh.Secondary)
|
_, err = team.DelHistoryItem(uh.Kind, uh.Time, uh.Primary, uh.Secondary)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to delete this history line: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,171 +1,75 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
"srs.epita.fr/fic-server/settings"
|
"srs.epita.fr/fic-server/settings"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func declareThemesRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/themes", apiHandler(listThemes))
|
router.GET("/themes", listThemes)
|
||||||
router.POST("/api/themes", apiHandler(createTheme))
|
router.POST("/themes", createTheme)
|
||||||
router.GET("/api/themes.json", apiHandler(exportThemes))
|
router.GET("/themes.json", exportThemes)
|
||||||
router.GET("/api/session-forensic.yaml", apiHandler(func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
router.GET("/session-forensic.yaml", func(c *gin.Context) {
|
||||||
if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
||||||
return nil, err
|
log.Printf("Unable to ReadSettings: %s", err.Error())
|
||||||
} else if c, err := settings.ReadChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil {
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during settings reading."})
|
||||||
return nil, err
|
return
|
||||||
|
} else if ch, err := settings.ReadChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil {
|
||||||
|
log.Printf("Unable to ReadChallengeInfo: %s", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during challenge info reading."})
|
||||||
|
return
|
||||||
|
} else if sf, err := fic.GenZQDSSessionFile(ch, s); err != nil {
|
||||||
|
log.Printf("Unable to GenZQDSSessionFile: %s", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during session file generation."})
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
return fic.GenZQDSSessionFile(c, s)
|
c.JSON(http.StatusOK, sf)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
router.GET("/api/files-bindings", apiHandler(bindingFiles))
|
router.GET("/files-bindings", bindingFiles)
|
||||||
|
|
||||||
router.GET("/api/themes/:thid", apiHandler(themeHandler(showTheme)))
|
apiThemesRoutes := router.Group("/themes/:thid")
|
||||||
router.PUT("/api/themes/:thid", apiHandler(themeHandler(updateTheme)))
|
apiThemesRoutes.Use(ThemeHandler)
|
||||||
router.DELETE("/api/themes/:thid", apiHandler(themeHandler(deleteTheme)))
|
apiThemesRoutes.GET("", showTheme)
|
||||||
|
apiThemesRoutes.PUT("", updateTheme)
|
||||||
|
apiThemesRoutes.DELETE("", deleteTheme)
|
||||||
|
|
||||||
router.GET("/api/themes/:thid/exercices", apiHandler(themeHandler(listThemedExercices)))
|
declareExercicesRoutes(apiThemesRoutes)
|
||||||
router.POST("/api/themes/:thid/exercices", apiHandler(themeHandler(createExercice)))
|
|
||||||
|
|
||||||
router.GET("/api/themes/:thid/exercices_stats.json", apiHandler(themeHandler(getThemedExercicesStats)))
|
|
||||||
|
|
||||||
router.GET("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
|
|
||||||
router.PUT("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
|
||||||
router.DELETE("/api/themes/:thid/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
|
||||||
|
|
||||||
router.GET("/api/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
|
||||||
router.POST("/api/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
|
||||||
|
|
||||||
router.GET("/api/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(listExerciceHints)))
|
|
||||||
router.POST("/api/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(createExerciceHint)))
|
|
||||||
|
|
||||||
router.GET("/api/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(listExerciceFlags)))
|
|
||||||
router.POST("/api/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(createExerciceFlag)))
|
|
||||||
|
|
||||||
// Remote
|
// Remote
|
||||||
router.GET("/api/remote/themes", apiHandler(sync.ApiListRemoteThemes))
|
router.GET("/remote/themes", sync.ApiListRemoteThemes)
|
||||||
router.GET("/api/remote/themes/:thid", apiHandler(sync.ApiGetRemoteTheme))
|
router.GET("/remote/themes/:thid", sync.ApiGetRemoteTheme)
|
||||||
router.GET("/api/remote/themes/:thid/exercices", apiHandler(sync.ApiListRemoteExercices))
|
router.GET("/remote/themes/:thid/exercices", sync.ApiListRemoteExercices)
|
||||||
|
|
||||||
// Synchronize
|
|
||||||
router.GET("/api/sync/deep", apiHandler(
|
|
||||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
if sync.DeepSyncProgress == 0 {
|
|
||||||
return nil, errors.New("Pas de synchronisation en cours")
|
|
||||||
} else {
|
|
||||||
return map[string]interface{}{"progress": sync.DeepSyncProgress}, nil
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
router.POST("/api/sync/base", apiHandler(
|
|
||||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
err := sync.GlobalImporter.Sync()
|
|
||||||
return true, err
|
|
||||||
}))
|
|
||||||
router.POST("/api/sync/speed", apiHandler(
|
|
||||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
st := sync.SpeedySyncDeep(sync.GlobalImporter)
|
|
||||||
sync.EditDeepReport(st, false)
|
|
||||||
return st, nil
|
|
||||||
}))
|
|
||||||
router.POST("/api/sync/deep", apiHandler(
|
|
||||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
return sync.SyncDeep(sync.GlobalImporter), nil
|
|
||||||
}))
|
|
||||||
router.POST("/api/sync/deep/:thid", apiHandler(themeHandler(
|
|
||||||
func(theme *fic.Theme, _ []byte) (interface{}, error) {
|
|
||||||
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
|
|
||||||
sync.EditDeepReport(map[string][]string{theme.Name: st}, false)
|
|
||||||
sync.DeepSyncProgress = 255
|
|
||||||
return st, nil
|
|
||||||
})))
|
|
||||||
router.POST("/api/sync/auto/*p", apiHandler(
|
|
||||||
func(ps httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
p := strings.TrimPrefix(ps.ByName("p"), "/")
|
|
||||||
|
|
||||||
themes, err := fic.GetThemes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p == "" {
|
|
||||||
if !IsProductionEnv {
|
|
||||||
for _, theme := range themes {
|
|
||||||
theme.DeleteDeep()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
st := sync.SyncDeep(sync.GlobalImporter)
|
|
||||||
return st, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, theme := range themes {
|
|
||||||
if theme.Path == p {
|
|
||||||
if !IsProductionEnv {
|
|
||||||
exercices, err := theme.GetExercices()
|
|
||||||
if err == nil {
|
|
||||||
for _, exercice := range exercices {
|
|
||||||
exercice.DeleteDeep()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
|
|
||||||
sync.EditDeepReport(map[string][]string{theme.Name: st}, false)
|
|
||||||
sync.DeepSyncProgress = 255
|
|
||||||
|
|
||||||
settings.ForceRegeneration()
|
|
||||||
|
|
||||||
return st, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("Theme not found %q", p)
|
|
||||||
}))
|
|
||||||
router.POST("/api/sync/themes", apiHandler(
|
|
||||||
func(_ httprouter.Params, _ []byte) (interface{}, error) {
|
|
||||||
return sync.SyncThemes(sync.GlobalImporter), nil
|
|
||||||
}))
|
|
||||||
router.POST("/api/sync/themes/:thid/exercices", apiHandler(themeHandler(
|
|
||||||
func(theme *fic.Theme, _ []byte) (interface{}, error) {
|
|
||||||
return sync.SyncExercices(sync.GlobalImporter, theme), nil
|
|
||||||
})))
|
|
||||||
router.POST("/api/sync/themes/:thid/exercices/:eid/files", apiHandler(exerciceHandler(
|
|
||||||
func(exercice *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil
|
|
||||||
})))
|
|
||||||
router.POST("/api/sync/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(
|
|
||||||
func(exercice *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
|
||||||
return errs, nil
|
|
||||||
})))
|
|
||||||
router.POST("/api/sync/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(
|
|
||||||
func(exercice *fic.Exercice, _ []byte) (interface{}, error) {
|
|
||||||
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
|
|
||||||
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
|
||||||
return append(errs, herrs...), nil
|
|
||||||
})))
|
|
||||||
|
|
||||||
router.POST("/api/sync/themes/:thid/fixurlid", apiHandler(themeHandler(
|
|
||||||
func(theme *fic.Theme, _ []byte) (interface{}, error) {
|
|
||||||
if theme.FixURLId() {
|
|
||||||
return theme.Update()
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
})))
|
|
||||||
router.POST("/api/sync/fixurlids", apiHandler(fixAllURLIds))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixAllURLIds(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func ThemeHandler(c *gin.Context) {
|
||||||
|
thid, err := strconv.ParseInt(string(c.Params.ByName("thid")), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid theme identifier"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
theme, err := fic.GetTheme(thid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("theme", theme)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixAllURLIds(c *gin.Context) {
|
||||||
nbFix := 0
|
nbFix := 0
|
||||||
if themes, err := fic.GetThemes(); err == nil {
|
if themes, err := fic.GetThemes(); err == nil {
|
||||||
for _, theme := range themes {
|
for _, theme := range themes {
|
||||||
|
@ -185,19 +89,22 @@ func fixAllURLIds(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nbFix, nil
|
c.JSON(http.StatusOK, nbFix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindingFiles(_ httprouter.Params, body []byte) (interface{}, error) {
|
func bindingFiles(c *gin.Context) {
|
||||||
if files, err := fic.GetFiles(); err != nil {
|
files, err := fic.GetFiles()
|
||||||
return "", err
|
if err != nil {
|
||||||
} else {
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
ret := ""
|
return
|
||||||
for _, file := range files {
|
|
||||||
ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret := ""
|
||||||
|
for _, file := range files {
|
||||||
|
ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExercice(args []string) (*fic.Exercice, error) {
|
func getExercice(args []string) (*fic.Exercice, error) {
|
||||||
|
@ -212,60 +119,92 @@ func getExercice(args []string) (*fic.Exercice, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listThemes(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func listThemes(c *gin.Context) {
|
||||||
return fic.GetThemes()
|
themes, err := fic.GetThemes()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to listThemes:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to list themes."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, themes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportThemes(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func exportThemes(c *gin.Context) {
|
||||||
return fic.ExportThemes()
|
themes, err := fic.ExportThemes()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to exportthemes:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to export themes."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, themes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showTheme(theme *fic.Theme, _ []byte) (interface{}, error) {
|
func showTheme(c *gin.Context) {
|
||||||
return theme, nil
|
c.JSON(http.StatusOK, c.MustGet("theme").(*fic.Theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
func listThemedExercices(theme *fic.Theme, _ []byte) (interface{}, error) {
|
func createTheme(c *gin.Context) {
|
||||||
return theme.GetExercices()
|
|
||||||
}
|
|
||||||
|
|
||||||
func showThemedExercice(theme *fic.Theme, exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
||||||
return exercice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTheme(_ httprouter.Params, body []byte) (interface{}, error) {
|
|
||||||
var ut fic.Theme
|
var ut fic.Theme
|
||||||
if err := json.Unmarshal(body, &ut); err != nil {
|
err := c.ShouldBindJSON(&ut)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ut.Name) == 0 {
|
if len(ut.Name) == 0 {
|
||||||
return nil, errors.New("Theme's name not filled")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Theme's name not filled"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return fic.CreateTheme(&ut)
|
th, err := fic.CreateTheme(&ut)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to createTheme:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during theme creation."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, th)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme *fic.Theme, body []byte) (interface{}, error) {
|
func updateTheme(c *gin.Context) {
|
||||||
|
theme := c.MustGet("theme").(*fic.Theme)
|
||||||
|
|
||||||
var ut fic.Theme
|
var ut fic.Theme
|
||||||
if err := json.Unmarshal(body, &ut); err != nil {
|
err := c.ShouldBindJSON(&ut)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ut.Id = theme.Id
|
ut.Id = theme.Id
|
||||||
|
|
||||||
if len(ut.Name) == 0 {
|
if len(ut.Name) == 0 {
|
||||||
return nil, errors.New("Theme's name not filled")
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Theme's name not filled"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := ut.Update(); err != nil {
|
if _, err := ut.Update(); err != nil {
|
||||||
return nil, err
|
log.Println("Unable to updateTheme:", err.Error())
|
||||||
} else {
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during theme update."})
|
||||||
return ut, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ut)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteTheme(theme *fic.Theme, _ []byte) (interface{}, error) {
|
func deleteTheme(c *gin.Context) {
|
||||||
return theme.Delete()
|
theme := c.MustGet("theme").(*fic.Theme)
|
||||||
|
|
||||||
|
_, err := theme.Delete()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to deleteTheme:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during theme deletion."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getThemedExercicesStats(theme *fic.Theme, body []byte) (interface{}, error) {
|
func getThemedExercicesStats(theme *fic.Theme, body []byte) (interface{}, error) {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/julienschmidt/httprouter"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func DeclareVersionRoutes(router *gin.RouterGroup) {
|
||||||
router.GET("/api/version", apiHandler(showVersion))
|
router.GET("/version", showVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showVersion(_ httprouter.Params, body []byte) (interface{}, error) {
|
func showVersion(c *gin.Context) {
|
||||||
return map[string]interface{}{"version": 1.0}, nil
|
c.JSON(http.StatusOK, gin.H{"version": 1.0})
|
||||||
}
|
}
|
||||||
|
|
71
admin/app.go
Normal file
71
admin/app.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/admin/api"
|
||||||
|
"srs.epita.fr/fic-server/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
router *gin.Engine
|
||||||
|
srv *http.Server
|
||||||
|
cfg *settings.Settings
|
||||||
|
bind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApp(cfg *settings.Settings, baseURL string, bind string) App {
|
||||||
|
if !cfg.WorkInProgress {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
gin.ForceConsoleColor()
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
api.DeclareRoutes(router.Group(""))
|
||||||
|
|
||||||
|
var baserouter *gin.RouterGroup
|
||||||
|
if len(baseURL) > 0 {
|
||||||
|
router.GET("/", func(c *gin.Context) {
|
||||||
|
c.Redirect(http.StatusFound, baseURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
baserouter = router.Group(baseURL)
|
||||||
|
|
||||||
|
api.DeclareRoutes(baserouter)
|
||||||
|
declareStaticRoutes(baserouter, cfg, baseURL)
|
||||||
|
} else {
|
||||||
|
declareStaticRoutes(router.Group(""), cfg, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
app := App{
|
||||||
|
router: router,
|
||||||
|
bind: bind,
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Start() {
|
||||||
|
app.srv = &http.Server{
|
||||||
|
Addr: app.bind,
|
||||||
|
Handler: app.router,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Ready, listening on %s\n", app.bind)
|
||||||
|
if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Fatalf("listen: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Stop() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := app.srv.Shutdown(ctx); err != nil {
|
||||||
|
log.Fatal("Server Shutdown:", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -198,12 +196,12 @@ func main() {
|
||||||
os.MkdirAll(settings.SettingsDir, 0777)
|
os.MkdirAll(settings.SettingsDir, 0777)
|
||||||
|
|
||||||
// Initialize settings and load them
|
// Initialize settings and load them
|
||||||
|
var config *settings.Settings
|
||||||
if !settings.ExistsSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) {
|
if !settings.ExistsSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) {
|
||||||
if err = api.ResetSettings(); err != nil {
|
if err = api.ResetSettings(); err != nil {
|
||||||
log.Fatal("Unable to initialize settings.json:", err)
|
log.Fatal("Unable to initialize settings.json:", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var config *settings.Settings
|
|
||||||
if config, err = settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
if config, err = settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
||||||
log.Fatal("Unable to read settings.json:", err)
|
log.Fatal("Unable to read settings.json:", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -231,21 +229,13 @@ func main() {
|
||||||
interrupt := make(chan os.Signal, 1)
|
interrupt := make(chan os.Signal, 1)
|
||||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
srv := &http.Server{
|
app := NewApp(config, baseURL, *bind)
|
||||||
Addr: *bind,
|
go app.Start()
|
||||||
Handler: StripPrefix(baseURL, api.Router()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve content
|
|
||||||
go func() {
|
|
||||||
log.Fatal(srv.ListenAndServe())
|
|
||||||
}()
|
|
||||||
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
|
|
||||||
|
|
||||||
// Wait shutdown signal
|
// Wait shutdown signal
|
||||||
<-interrupt
|
<-interrupt
|
||||||
|
|
||||||
log.Print("The service is shutting down...")
|
log.Print("The service is shutting down...")
|
||||||
srv.Shutdown(context.Background())
|
app.Stop()
|
||||||
log.Println("done")
|
log.Println("done")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
@ -12,8 +13,9 @@ import (
|
||||||
"srs.epita.fr/fic-server/admin/api"
|
"srs.epita.fr/fic-server/admin/api"
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
"srs.epita.fr/fic-server/settings"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed static
|
//go:embed static
|
||||||
|
@ -33,83 +35,83 @@ func genIndex(baseURL string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveIndex(w http.ResponseWriter, r *http.Request) {
|
func serveIndex(c *gin.Context) {
|
||||||
w.Write(indexPage)
|
c.Writer.Write(indexPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
var staticFS http.FileSystem
|
var staticFS http.FileSystem
|
||||||
|
|
||||||
func serveFile(w http.ResponseWriter, r *http.Request, url string) {
|
func serveFile(c *gin.Context, url string) {
|
||||||
r.URL.Path = url
|
c.Request.URL.Path = url
|
||||||
http.FileServer(staticFS).ServeHTTP(w, r)
|
http.FileServer(staticFS).ServeHTTP(c.Writer, c.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseURL string) {
|
||||||
api.Router().GET("/", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/claims/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/claims/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/exercices/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/exercices/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/events/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/events/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/files", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/files", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/public/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/public/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/pki/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/pki/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/settings/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/settings/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/teams/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/teams/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
api.Router().GET("/themes/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/themes/*_", func(c *gin.Context) {
|
||||||
serveIndex(w, r)
|
serveIndex(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Router().GET("/css/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/css/*_", func(c *gin.Context) {
|
||||||
serveFile(w, r, r.URL.Path)
|
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
||||||
})
|
})
|
||||||
api.Router().GET("/fonts/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/fonts/*_", func(c *gin.Context) {
|
||||||
serveFile(w, r, r.URL.Path)
|
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
||||||
})
|
})
|
||||||
api.Router().GET("/img/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/img/*_", func(c *gin.Context) {
|
||||||
serveFile(w, r, r.URL.Path)
|
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
||||||
})
|
})
|
||||||
api.Router().GET("/js/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/js/*_", func(c *gin.Context) {
|
||||||
serveFile(w, r, r.URL.Path)
|
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
||||||
})
|
})
|
||||||
api.Router().GET("/views/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/views/*_", func(c *gin.Context) {
|
||||||
serveFile(w, r, r.URL.Path)
|
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Router().GET("/files/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/files/*_", func(c *gin.Context) {
|
||||||
http.ServeFile(w, r, path.Join(fic.FilesDir, strings.TrimPrefix(r.URL.Path, "/files")))
|
http.ServeFile(c.Writer, c.Request, path.Join(fic.FilesDir, strings.TrimPrefix(c.Request.URL.Path, "/files")))
|
||||||
})
|
})
|
||||||
api.Router().GET("/submissions/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/submissions/*_", func(c *gin.Context) {
|
||||||
http.ServeFile(w, r, path.Join(api.TimestampCheck, strings.TrimPrefix(r.URL.Path, "/submissions")))
|
http.ServeFile(c.Writer, c.Request, path.Join(api.TimestampCheck, strings.TrimPrefix(c.Request.URL.Path, "/submissions")))
|
||||||
})
|
})
|
||||||
api.Router().GET("/vids/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/vids/*_", func(c *gin.Context) {
|
||||||
if importer, ok := sync.GlobalImporter.(sync.LocalImporter); ok {
|
if importer, ok := sync.GlobalImporter.(sync.LocalImporter); ok {
|
||||||
http.ServeFile(w, r, path.Join(importer.Base, strings.TrimPrefix(r.URL.Path, "/vids")))
|
http.ServeFile(c.Writer, c.Request, path.Join(importer.Base, strings.TrimPrefix(c.Request.URL.Path, "/vids")))
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, "Only available with local importer.", 400)
|
c.AbortWithError(http.StatusBadRequest, errors.New("Only available with local importer."))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Router().GET("/check_import.html", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/check_import.html", func(c *gin.Context) {
|
||||||
serveFile(w, r, "check_import.html")
|
serveFile(c, "check_import.html")
|
||||||
})
|
})
|
||||||
api.Router().GET("/full_import_report.json", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
router.GET("/full_import_report.json", func(c *gin.Context) {
|
||||||
http.ServeFile(w, r, sync.DeepReportPath)
|
http.ServeFile(c.Writer, c.Request, sync.DeepReportPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,7 +300,7 @@ angular.module("FICApp")
|
||||||
return $resource("api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
|
return $resource("api/exercices/:exerciceId/history.json", { exerciceId: '@id' })
|
||||||
})
|
})
|
||||||
.factory("ExercicesStats", function($resource) {
|
.factory("ExercicesStats", function($resource) {
|
||||||
return $resource("api/themes/:themeId/exercices_stats.json", { themeId: '@id' })
|
return $resource("api/exercices_stats.json", { themeId: '@id' })
|
||||||
})
|
})
|
||||||
.factory("ExerciceStats", function($resource) {
|
.factory("ExerciceStats", function($resource) {
|
||||||
return $resource("api/exercices/:exerciceId/stats.json", { exerciceId: '@id' })
|
return $resource("api/exercices/:exerciceId/stats.json", { exerciceId: '@id' })
|
||||||
|
@ -1653,7 +1653,7 @@ angular.module("FICApp")
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller("ExercicesStatsController", function($scope, ExercicesStats) {
|
.controller("ExercicesStatsController", function($scope, ExercicesStats) {
|
||||||
$scope.exercices = ExercicesStats.query({ themeId: $scope.theme.id });
|
$scope.exercices = ExercicesStats.query();
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller("ExerciceStatsController", function($scope, ExerciceStats, $routeParams) {
|
.controller("ExerciceStatsController", function($scope, ExerciceStats, $routeParams) {
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
)
|
)
|
||||||
|
@ -146,10 +147,10 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiGetRemoteExerciceFiles is an accessor to remote exercice files list.
|
// ApiGetRemoteExerciceFiles is an accessor to remote exercice files list.
|
||||||
func ApiGetRemoteExerciceFiles(ps httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiGetRemoteExerciceFiles(c *gin.Context) {
|
||||||
theme, errs := BuildTheme(GlobalImporter, ps.ByName("thid"))
|
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, ps.ByName("exid")), nil)
|
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
|
||||||
if exercice != nil {
|
if exercice != nil {
|
||||||
files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files")
|
files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files")
|
||||||
if files != nil {
|
if files != nil {
|
||||||
|
@ -164,14 +165,17 @@ func ApiGetRemoteExerciceFiles(ps httprouter.Params, _ []byte) (interface{}, err
|
||||||
Size: fSize,
|
Size: fSize,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return ret, nil
|
c.JSON(http.StatusOK, ret)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
_ "golang.org/x/crypto/blake2b"
|
_ "golang.org/x/crypto/blake2b"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
@ -140,21 +141,24 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
|
// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
|
||||||
func ApiGetRemoteExerciceHints(ps httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiGetRemoteExerciceHints(c *gin.Context) {
|
||||||
theme, errs := BuildTheme(GlobalImporter, ps.ByName("thid"))
|
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, ps.ByName("exid")), nil)
|
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
|
||||||
if exercice != nil {
|
if exercice != nil {
|
||||||
hints, errs := CheckExerciceHints(GlobalImporter, exercice)
|
hints, errs := CheckExerciceHints(GlobalImporter, exercice)
|
||||||
if hints != nil {
|
if hints != nil {
|
||||||
return hints, nil
|
c.JSON(http.StatusOK, hints)
|
||||||
} else {
|
return
|
||||||
return hints, fmt.Errorf("%q", errs)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return exercice, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,14 @@ package sync
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
)
|
)
|
||||||
|
@ -554,21 +555,25 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.F
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
|
// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
|
||||||
func ApiGetRemoteExerciceFlags(ps httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiGetRemoteExerciceFlags(c *gin.Context) {
|
||||||
theme, errs := BuildTheme(GlobalImporter, ps.ByName("thid"))
|
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, ps.ByName("exid")), nil)
|
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
|
||||||
if exercice != nil {
|
if exercice != nil {
|
||||||
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{})
|
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{})
|
||||||
if flags != nil {
|
if flags != nil {
|
||||||
return flags, nil
|
c.JSON(http.StatusOK, flags)
|
||||||
} else {
|
return
|
||||||
return flags, fmt.Errorf("%q", errs)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return exercice, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@ package sync
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/russross/blackfriday/v2"
|
"github.com/russross/blackfriday/v2"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
@ -308,26 +309,36 @@ func SyncExercices(i Importer, theme *fic.Theme) (errs []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
|
// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
|
||||||
func ApiListRemoteExercices(ps httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiListRemoteExercices(c *gin.Context) {
|
||||||
theme, errs := BuildTheme(GlobalImporter, ps.ByName("thid"))
|
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
return GetExercices(GlobalImporter, theme)
|
exercices, err := GetExercices(GlobalImporter, theme)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, exercices)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
|
// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
|
||||||
func ApiGetRemoteExercice(ps httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiGetRemoteExercice(c *gin.Context) {
|
||||||
theme, errs := BuildTheme(GlobalImporter, ps.ByName("thid"))
|
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||||
if theme != nil {
|
if theme != nil {
|
||||||
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, ps.ByName("exid")), nil)
|
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
|
||||||
if exercice != nil {
|
if exercice != nil {
|
||||||
return exercice, nil
|
c.JSON(http.StatusOK, exercice)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
return exercice, fmt.Errorf("%q", errs)
|
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("%q", errs)
|
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,14 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/russross/blackfriday/v2"
|
"github.com/russross/blackfriday/v2"
|
||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
|
|
||||||
|
@ -235,16 +236,23 @@ func SyncThemes(i Importer) (errs []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiListRemoteThemes is an accessor letting foreign packages to access remote themes list.
|
// ApiListRemoteThemes is an accessor letting foreign packages to access remote themes list.
|
||||||
func ApiListRemoteThemes(_ httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiListRemoteThemes(c *gin.Context) {
|
||||||
return GetThemes(GlobalImporter)
|
themes, err := GetThemes(GlobalImporter)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, themes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApiListRemoteTheme is an accessor letting foreign packages to access remote main theme attributes.
|
// ApiListRemoteTheme is an accessor letting foreign packages to access remote main theme attributes.
|
||||||
func ApiGetRemoteTheme(ps httprouter.Params, _ []byte) (interface{}, error) {
|
func ApiGetRemoteTheme(c *gin.Context) {
|
||||||
r, errs := BuildTheme(GlobalImporter, ps.ByName("thid"))
|
r, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return r, fmt.Errorf("%q", errs)
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||||
} else {
|
return
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, r)
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.16
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.1.0
|
github.com/BurntSushi/toml v1.1.0
|
||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.7.7 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.4.2
|
github.com/go-git/go-git/v5 v5.4.2
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
|
30
go.sum
30
go.sum
|
@ -66,6 +66,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||||
|
@ -78,6 +82,13 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
@ -114,6 +125,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
@ -134,6 +146,8 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
@ -147,9 +161,17 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -164,6 +186,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/studio-b12/gowebdav v0.0.0-20210630100626-7ff61aa87be8 h1:ipNUBPHSUmHhhcLhvqC2vGZsJPzVuJap8rJx3uGAEco=
|
github.com/studio-b12/gowebdav v0.0.0-20210630100626-7ff61aa87be8 h1:ipNUBPHSUmHhhcLhvqC2vGZsJPzVuJap8rJx3uGAEco=
|
||||||
|
@ -172,6 +195,10 @@ github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df h1:C+J/LwTqP8g
|
||||||
github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
||||||
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62 h1:b2nJXyPCa9HY7giGM+kYcnQ71m14JnGdQabMPmyt++8=
|
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62 h1:b2nJXyPCa9HY7giGM+kYcnQ71m14JnGdQabMPmyt++8=
|
||||||
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
@ -345,6 +372,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -509,6 +537,8 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
|
@ -74,6 +74,12 @@ func GetFile(id int64) (f *EFile, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Exercice) GetFile(id int64) (f *EFile, err error) {
|
||||||
|
f = &EFile{}
|
||||||
|
err = DBQueryRow("SELECT id_file, origin, path, name, cksum, size FROM exercice_files WHERE id_file = ? AND id_exercice = ?", id, e.Id).Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.Size)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetFileByPath retrieves the file that should be found at the given location.
|
// GetFileByPath retrieves the file that should be found at the given location.
|
||||||
func GetFileByPath(path string) (*EFile, error) {
|
func GetFileByPath(path string) (*EFile, error) {
|
||||||
path = strings.TrimPrefix(path, FilesDir)
|
path = strings.TrimPrefix(path, FilesDir)
|
||||||
|
|
|
@ -78,6 +78,13 @@ func GetFlagKey(id int) (k *FlagKey, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFlagKey returns a flag.
|
||||||
|
func (e *Exercice) GetFlagKey(id int) (k *FlagKey, err error) {
|
||||||
|
k = &FlagKey{}
|
||||||
|
err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost FROM exercice_flags WHERE id_flag = ? AND id_exercice = ?", id, e.Id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.Unit, &k.IgnoreCase, &k.NoTrim, &k.Multiline, &k.ValidatorRegexp, &k.SortReGroups, &k.Checksum, &k.ChoicesCost)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetFlagKeyByLabel returns a flag matching the given label.
|
// GetFlagKeyByLabel returns a flag matching the given label.
|
||||||
func (e *Exercice) GetFlagKeyByLabel(label string) (k *FlagKey, err error) {
|
func (e *Exercice) GetFlagKeyByLabel(label string) (k *FlagKey, err error) {
|
||||||
k = &FlagKey{}
|
k = &FlagKey{}
|
||||||
|
|
|
@ -46,6 +46,17 @@ func GetHint(id int64) (*EHint, error) {
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHint retrieves the hint with the given id.
|
||||||
|
func (e *Exercice) GetHint(id int64) (*EHint, error) {
|
||||||
|
h := &EHint{}
|
||||||
|
if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ? AND id_exercice = ?", id, e.Id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
treatHintContent(h)
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetHintByTitle retrieves the hint with the given id.
|
// GetHintByTitle retrieves the hint with the given id.
|
||||||
func (e *Exercice) GetHintByTitle(id int64) (*EHint, error) {
|
func (e *Exercice) GetHintByTitle(id int64) (*EHint, error) {
|
||||||
h := &EHint{}
|
h := &EHint{}
|
||||||
|
|
|
@ -31,7 +31,7 @@ type MCQ_entry struct {
|
||||||
// GetMCQ returns a list of flags comming with the challenge.
|
// GetMCQ returns a list of flags comming with the challenge.
|
||||||
func GetMCQ(id int) (m *MCQ, err error) {
|
func GetMCQ(id int) (m *MCQ, err error) {
|
||||||
m = &MCQ{}
|
m = &MCQ{}
|
||||||
err = DBQueryRow("SELECT id_mcq, id_exercice, order, title FROM exercice_mcq WHERE id_mcq = ?", id).Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title)
|
err = DBQueryRow("SELECT id_mcq, id_exercice, ordre, title FROM exercice_mcq WHERE id_mcq = ?", id).Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title)
|
||||||
m.fillEntries()
|
m.fillEntries()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,14 @@ func (e *Exercice) GetMCQ() ([]*MCQ, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMCQById returns a MCQs.
|
||||||
|
func (e *Exercice) GetMCQById(id int) (m *MCQ, err error) {
|
||||||
|
m = &MCQ{}
|
||||||
|
err = DBQueryRow("SELECT id_mcq, id_exercice, ordre, title FROM exercice_mcq WHERE id_mcq = ? AND id_exercice = ?", id, e.Id).Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title)
|
||||||
|
m.fillEntries()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetMCQbyChoice returns the MCQ corresponding to a choice ID.
|
// GetMCQbyChoice returns the MCQ corresponding to a choice ID.
|
||||||
func GetMCQbyChoice(cid int) (m *MCQ, c *MCQ_entry, err error) {
|
func GetMCQbyChoice(cid int) (m *MCQ, c *MCQ_entry, err error) {
|
||||||
m = &MCQ{}
|
m = &MCQ{}
|
||||||
|
|
Reference in a new issue