Use gin-gonic instead of httprouter
This commit is contained in:
parent
7c719d9fd5
commit
a203cdc36a
94
api.go
Normal file
94
api.go
Normal file
@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func declareAPIRoutes(router *gin.Engine) {
|
||||
apiRoutes := router.Group("/api")
|
||||
apiRoutes.Use(authMiddleware())
|
||||
|
||||
declareAPIAuthRoutes(apiRoutes)
|
||||
declareAPISurveysRoutes(apiRoutes)
|
||||
declareAPIWorksRoutes(apiRoutes)
|
||||
|
||||
apiAuthRoutes := router.Group("/api")
|
||||
apiAuthRoutes.Use(authMiddleware(loggedUser))
|
||||
|
||||
declareAPIAuthAsksRoutes(apiAuthRoutes)
|
||||
declareAPIAuthQuestionsRoutes(apiAuthRoutes)
|
||||
declareAPIAuthHelpRoutes(apiAuthRoutes)
|
||||
declareAPIAuthSurveysRoutes(apiAuthRoutes)
|
||||
declareAPIAuthUsersRoutes(apiAuthRoutes)
|
||||
declareAPIAuthWorksRoutes(apiAuthRoutes)
|
||||
|
||||
apiAdminRoutes := router.Group("/api")
|
||||
apiAdminRoutes.Use(authMiddleware(adminRestricted))
|
||||
|
||||
declareAPIAdminAsksRoutes(apiAdminRoutes)
|
||||
declareAPIAuthGradesRoutes(apiAdminRoutes)
|
||||
declareAPIAdminHelpRoutes(apiAdminRoutes)
|
||||
declareAPIAdminQuestionsRoutes(apiAdminRoutes)
|
||||
declareAPIAdminSurveysRoutes(apiAdminRoutes)
|
||||
declareAPIAdminUsersRoutes(apiAdminRoutes)
|
||||
declareAPIAdminWorksRoutes(apiAdminRoutes)
|
||||
}
|
||||
|
||||
func loggedUser(u *User, c *gin.Context) bool {
|
||||
if u != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission Denied"})
|
||||
return false
|
||||
}
|
||||
|
||||
func adminRestricted(u *User, c *gin.Context) bool {
|
||||
if u != nil && u.IsAdmin {
|
||||
return true
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission Denied"})
|
||||
return false
|
||||
}
|
||||
|
||||
func authMiddleware(access ...func(*User, *gin.Context) bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var user *User = nil
|
||||
if cookie, err := c.Request.Cookie("auth"); err == nil {
|
||||
if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
|
||||
eraseCookie(c)
|
||||
c.AbortWithStatusJSON(http.StatusNotAcceptable, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
} else if session, err := getSession(sessionid); err != nil {
|
||||
eraseCookie(c)
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
} else if session.IdUser == nil {
|
||||
user = nil
|
||||
} else if std, err := getUser(int(*session.IdUser)); err != nil {
|
||||
eraseCookie(c)
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
} else {
|
||||
user = std
|
||||
}
|
||||
}
|
||||
|
||||
// Check access limitation
|
||||
for _, a := range access {
|
||||
if !a(user, c) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve corresponding user
|
||||
c.Set("LoggedUser", user)
|
||||
|
||||
// We are now ready to continue
|
||||
c.Next()
|
||||
}
|
||||
}
|
48
app.go
Normal file
48
app.go
Normal file
@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
router *gin.Engine
|
||||
srv *http.Server
|
||||
}
|
||||
|
||||
func NewApp() App {
|
||||
gin.ForceConsoleColor()
|
||||
router := gin.Default()
|
||||
|
||||
declareStaticRoutes(router)
|
||||
declareAPIRoutes(router)
|
||||
|
||||
app := App{
|
||||
router: router,
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) Start(bind string) {
|
||||
app.srv = &http.Server{
|
||||
Addr: bind,
|
||||
Handler: app.router,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
44
asks.go
44
asks.go
@ -1,32 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.POST("/api/surveys/:sid/ask", apiAuthHandler(surveyAuthHandler(func(s Survey, u *User, body []byte) HTTPResponse {
|
||||
func declareAPIAuthAsksRoutes(router *gin.RouterGroup) {
|
||||
router.POST("/ask", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
var ask Ask
|
||||
if err := json.Unmarshal(body, &ask); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&ask); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
a, err := s.NewAsk(u.Id, ask.Content)
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Println("Unable to NewAsk:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register your question. Please try again in a few moment."})
|
||||
return
|
||||
}
|
||||
|
||||
if s.Direct != nil {
|
||||
s.WSAdminWriteAll(WSMessage{Action: "new_ask", UserId: &u.Id, QuestionId: &a.Id, Response: ask.Content})
|
||||
}
|
||||
|
||||
return formatApiResponse(a, err)
|
||||
}), loggedUser))
|
||||
router.GET("/api/surveys/:sid/ask", apiHandler(surveyHandler(
|
||||
func(s Survey, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(s.GetAsks(true))
|
||||
}), adminRestricted))
|
||||
c.JSON(http.StatusOK, a)
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAdminAsksRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/ask", func(c *gin.Context) {
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
asks, err := s.GetAsks(true)
|
||||
if err != nil {
|
||||
log.Println("Unable to GetAsks:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve asks. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, asks)
|
||||
})
|
||||
}
|
||||
|
||||
type Ask struct {
|
||||
|
64
auth.go
64
auth.go
@ -2,12 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var LocalAuthFunc = checkAuthKrb5
|
||||
@ -18,12 +16,12 @@ type loginForm struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
router.GET("/api/auth", apiAuthHandler(validateAuthToken))
|
||||
router.POST("/api/auth", apiRawHandler(func(w http.ResponseWriter, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return formatApiResponse(LocalAuthFunc(w, ps, body))
|
||||
}))
|
||||
router.POST("/api/auth/logout", apiRawHandler(logout))
|
||||
func declareAPIAuthRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/auth", validateAuthToken)
|
||||
router.POST("/auth", func(c *gin.Context) {
|
||||
LocalAuthFunc(c)
|
||||
})
|
||||
router.POST("/auth/logout", logout)
|
||||
}
|
||||
|
||||
type authToken struct {
|
||||
@ -31,20 +29,21 @@ type authToken struct {
|
||||
CurrentPromo uint `json:"current_promo"`
|
||||
}
|
||||
|
||||
func validateAuthToken(u *User, _ httprouter.Params, _ []byte) HTTPResponse {
|
||||
if u == nil {
|
||||
return APIErrorResponse{status: http.StatusUnauthorized, err: fmt.Errorf("Not connected")}
|
||||
func validateAuthToken(c *gin.Context) {
|
||||
if u, ok := c.Get("LoggedUser"); !ok || u.(*User) == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Not connected"})
|
||||
return
|
||||
} else {
|
||||
return APIResponse{authToken{u, currentPromo}}
|
||||
c.JSON(http.StatusOK, authToken{u.(*User), currentPromo})
|
||||
}
|
||||
}
|
||||
|
||||
func logout(w http.ResponseWriter, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
eraseCookie(w)
|
||||
return APIResponse{true}
|
||||
func logout(c *gin.Context) {
|
||||
eraseCookie(c)
|
||||
c.JSON(http.StatusOK, true)
|
||||
}
|
||||
|
||||
func completeAuth(w http.ResponseWriter, username string, email string, firstname string, lastname string, groups string, session *Session) (usr User, err error) {
|
||||
func completeAuth(c *gin.Context, username string, email string, firstname string, lastname string, groups string, session *Session) (usr *User, err error) {
|
||||
if !userExists(username) {
|
||||
if usr, err = NewUser(username, email, firstname, lastname, groups); err != nil {
|
||||
return
|
||||
@ -64,9 +63,7 @@ func completeAuth(w http.ResponseWriter, username string, email string, firstnam
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
var s Session
|
||||
s, err = usr.NewSession()
|
||||
session = &s
|
||||
session, err = usr.NewSession()
|
||||
} else {
|
||||
_, err = session.SetUser(usr)
|
||||
}
|
||||
@ -75,7 +72,7 @@ func completeAuth(w http.ResponseWriter, username string, email string, firstnam
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
Name: "auth",
|
||||
Value: base64.StdEncoding.EncodeToString(session.Id),
|
||||
Path: baseURL + "/",
|
||||
@ -88,11 +85,28 @@ func completeAuth(w http.ResponseWriter, username string, email string, firstnam
|
||||
return
|
||||
}
|
||||
|
||||
func dummyAuth(w http.ResponseWriter, _ httprouter.Params, body []byte) (interface{}, error) {
|
||||
func eraseCookie(c *gin.Context) {
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
Name: "auth",
|
||||
Value: "",
|
||||
Path: baseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
}
|
||||
|
||||
func dummyAuth(c *gin.Context) {
|
||||
var lf map[string]string
|
||||
if err := json.Unmarshal(body, &lf); err != nil {
|
||||
return nil, err
|
||||
if err := c.ShouldBindJSON(&lf); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
return completeAuth(w, lf["username"], lf["email"], lf["firstname"], lf["lastname"], "", nil)
|
||||
if usr, err := completeAuth(c, lf["username"], lf["email"], lf["firstname"], lf["lastname"], "", nil); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusOK, authToken{usr, currentPromo})
|
||||
}
|
||||
}
|
||||
|
34
auth_krb5.go
34
auth_krb5.go
@ -1,17 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jcmturner/gokrb5/v8/client"
|
||||
"github.com/jcmturner/gokrb5/v8/config"
|
||||
"github.com/jcmturner/gokrb5/v8/iana/etypeID"
|
||||
"github.com/jcmturner/gokrb5/v8/krberror"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func parseETypes(s []string, w bool) []int32 {
|
||||
@ -37,10 +35,11 @@ func parseETypes(s []string, w bool) []int32 {
|
||||
return eti
|
||||
}
|
||||
|
||||
func checkAuthKrb5(w http.ResponseWriter, _ httprouter.Params, body []byte) (interface{}, error) {
|
||||
func checkAuthKrb5(c *gin.Context) {
|
||||
var lf loginForm
|
||||
if err := json.Unmarshal(body, &lf); err != nil {
|
||||
return nil, err
|
||||
if err := c.ShouldBindJSON(&lf); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
@ -52,7 +51,8 @@ func checkAuthKrb5(w http.ResponseWriter, _ httprouter.Params, body []byte) (int
|
||||
}
|
||||
|
||||
if !userExists(lf.Login) && !found {
|
||||
return nil, fmt.Errorf("You are not allowed to log you in this way. Please use OpenID Connect.")
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You are not allowed to log you in this way. Please use OpenID Connect."})
|
||||
return
|
||||
}
|
||||
|
||||
cnf := config.New()
|
||||
@ -62,17 +62,21 @@ func checkAuthKrb5(w http.ResponseWriter, _ httprouter.Params, body []byte) (int
|
||||
cnf.LibDefaults.DefaultTktEnctypeIDs = parseETypes(cnf.LibDefaults.DefaultTktEnctypes, cnf.LibDefaults.AllowWeakCrypto)
|
||||
cnf.LibDefaults.PermittedEnctypeIDs = parseETypes(cnf.LibDefaults.PermittedEnctypes, cnf.LibDefaults.AllowWeakCrypto)
|
||||
|
||||
c := client.NewWithPassword(lf.Login, "CRI.EPITA.FR", lf.Password, cnf)
|
||||
if err := c.Login(); err != nil {
|
||||
cl := client.NewWithPassword(lf.Login, "CRI.EPITA.FR", lf.Password, cnf)
|
||||
if err := cl.Login(); err != nil {
|
||||
if errk, ok := err.(krberror.Krberror); ok {
|
||||
if errk.RootCause == krberror.NetworkingError {
|
||||
return nil, errors.New(`{"status": "Authentication system unavailable, please retry."}`)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Authentication system unavailable, please retry."})
|
||||
return
|
||||
} else if errk.RootCause == krberror.KDCError {
|
||||
return nil, errors.New(`{"status": "Invalid username or password"}`)
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Invalid username or password"})
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
} else {
|
||||
return completeAuth(w, lf.Login, lf.Login+"@epita.fr", "", "", "", nil)
|
||||
log.Println("Unable to login through Kerberos: unknown error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Invalid credentials (unknown error)."})
|
||||
return
|
||||
}
|
||||
|
||||
completeAuth(c, lf.Login, lf.Login+"@epita.fr", "", "", "", nil)
|
||||
}
|
||||
|
49
auth_oidc.go
49
auth_oidc.go
@ -11,7 +11,7 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -27,12 +27,12 @@ func init() {
|
||||
flag.StringVar(&oidcClientID, "oidc-clientid", oidcClientID, "ClientID for OIDC")
|
||||
flag.StringVar(&oidcSecret, "oidc-secret", oidcSecret, "Secret for OIDC")
|
||||
flag.StringVar(&oidcRedirectURL, "oidc-redirect", oidcRedirectURL, "Base URL for the redirect after connection")
|
||||
|
||||
router.GET("/auth/CRI", redirectOIDC_CRI)
|
||||
router.GET("/auth/complete", OIDC_CRI_complete)
|
||||
}
|
||||
|
||||
func initializeOIDC() {
|
||||
func initializeOIDC(router *gin.Engine) {
|
||||
router.GET("/auth/CRI", redirectOIDC_CRI)
|
||||
router.GET("/auth/complete", OIDC_CRI_complete)
|
||||
|
||||
if oidcClientID != "" && oidcSecret != "" {
|
||||
provider, err := oidc.NewProvider(context.Background(), "https://cri.epita.fr")
|
||||
if err != nil {
|
||||
@ -59,47 +59,48 @@ func initializeOIDC() {
|
||||
|
||||
}
|
||||
|
||||
func redirectOIDC_CRI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
func redirectOIDC_CRI(c *gin.Context) {
|
||||
session, err := NewSession()
|
||||
|
||||
// Save next parameter
|
||||
if len(r.URL.Query().Get("next")) > 0 {
|
||||
nextSessionMap[fmt.Sprintf("%x", session.Id)] = r.URL.Query().Get("next")
|
||||
if len(c.Request.URL.Query().Get("next")) > 0 {
|
||||
nextSessionMap[fmt.Sprintf("%x", session.Id)] = c.Request.URL.Query().Get("next")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusInternalServerError)
|
||||
} else {
|
||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(hex.EncodeToString(session.Id)), http.StatusFound)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, oauth2Config.AuthCodeURL(hex.EncodeToString(session.Id)))
|
||||
}
|
||||
|
||||
func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
idsession, err := hex.DecodeString(r.URL.Query().Get("state"))
|
||||
func OIDC_CRI_complete(c *gin.Context) {
|
||||
idsession, err := hex.DecodeString(c.Request.URL.Query().Get("state"))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
session, err := getSession(idsession)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(context.Background(), r.URL.Query().Get("code"))
|
||||
oauth2Token, err := oauth2Config.Exchange(context.Background(), c.Request.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Failed to exchange token: " + err.Error()})
|
||||
return
|
||||
}
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "No id_token field in oauth2 token."})
|
||||
return
|
||||
}
|
||||
idToken, err := oidcVerifier.Verify(context.Background(), rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Failed to verify ID Token: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -112,7 +113,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par
|
||||
Groups []map[string]interface{} `json:"groups"`
|
||||
}
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -123,17 +124,17 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := completeAuth(w, claims.Username, claims.Email, claims.Firstname, claims.Lastname, groups, &session); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
if _, err := completeAuth(c, claims.Username, claims.Email, claims.Firstname, claims.Lastname, groups, session); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve next URL associated with session
|
||||
if next, ok := nextSessionMap[fmt.Sprintf("%x", session.Id)]; ok {
|
||||
http.Redirect(w, r, next, http.StatusFound)
|
||||
c.Redirect(http.StatusFound, next)
|
||||
delete(nextSessionMap, fmt.Sprintf("%x", session.Id))
|
||||
} else {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
}
|
||||
|
304
corrections.go
304
corrections.go
@ -1,160 +1,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/surveys/:sid/questions/:qid/corrections", apiHandler(questionHandler(
|
||||
func(q Question, _ []byte) HTTPResponse {
|
||||
if cts, err := q.GetCorrectionTemplates(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return APIResponse{cts}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.POST("/api/surveys/:sid/questions/:qid/corrections", apiHandler(questionHandler(func(q Question, body []byte) HTTPResponse {
|
||||
var new CorrectionTemplate
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func declareAPIAdminCorrectionsRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/corrections", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
cts, err := q.GetCorrectionTemplates()
|
||||
if err != nil {
|
||||
log.Println("Unable to GetCorrectionTemplates:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrive correction's templates"})
|
||||
return
|
||||
}
|
||||
|
||||
return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination))
|
||||
}), adminRestricted))
|
||||
c.JSON(http.StatusOK, cts)
|
||||
})
|
||||
router.POST("/corrections", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
router.GET("/api/surveys/:sid/questions/:qid/corrections/:cid", apiHandler(correctionHandler(
|
||||
func(ct CorrectionTemplate, _ []byte) HTTPResponse {
|
||||
if users, err := ct.GetUserCorrected(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return APIResponse{users}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/surveys/:sid/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(current CorrectionTemplate, body []byte) HTTPResponse {
|
||||
var new CorrectionTemplate
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ct, err := q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination)
|
||||
if err != nil {
|
||||
log.Println("Unable to NewCorrectionTemplate:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to insert new correction template."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ct)
|
||||
})
|
||||
|
||||
correctionsRoutes := router.Group("/corrections/:cid")
|
||||
correctionsRoutes.Use(correctionHandler)
|
||||
|
||||
correctionsRoutes.GET("", func(c *gin.Context) {
|
||||
ct := c.MustGet("correctiontemplate").(*CorrectionTemplate)
|
||||
|
||||
users, err := ct.GetUserCorrected()
|
||||
if err != nil {
|
||||
log.Println("Unable to GetUserCorrected:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve users' corrections"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, users)
|
||||
})
|
||||
correctionsRoutes.PUT("", func(c *gin.Context) {
|
||||
current := c.MustGet("correctiontemplate").(*CorrectionTemplate)
|
||||
|
||||
var new CorrectionTemplate
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
|
||||
if err := new.Update(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return APIResponse{new}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/surveys/:sid/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(ct CorrectionTemplate, body []byte) HTTPResponse {
|
||||
return formatApiResponse(ct.Delete())
|
||||
}), adminRestricted))
|
||||
|
||||
router.GET("/api/questions/:qid/corrections", apiHandler(questionHandler(
|
||||
func(q Question, _ []byte) HTTPResponse {
|
||||
if cts, err := q.GetCorrectionTemplates(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return APIResponse{cts}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.POST("/api/questions/:qid/corrections", apiHandler(questionHandler(func(q Question, body []byte) HTTPResponse {
|
||||
var new CorrectionTemplate
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Println("Unable to Update correctionTemplate:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update the correction template"})
|
||||
return
|
||||
}
|
||||
|
||||
return formatApiResponse(q.NewCorrectionTemplate(new.Label, new.RegExp, new.Score, new.ScoreExplaination))
|
||||
}), adminRestricted))
|
||||
c.JSON(http.StatusOK, new)
|
||||
})
|
||||
correctionsRoutes.DELETE("", func(c *gin.Context) {
|
||||
ct := c.MustGet("correctiontemplate").(*CorrectionTemplate)
|
||||
|
||||
router.GET("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(
|
||||
func(ct CorrectionTemplate, _ []byte) HTTPResponse {
|
||||
if users, err := ct.GetUserCorrected(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return APIResponse{users}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(current CorrectionTemplate, body []byte) HTTPResponse {
|
||||
var new CorrectionTemplate
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if _, err := ct.Delete(); err != nil {
|
||||
log.Println("Unable to Delete correctionTemplate:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete the correction template."})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
if err := new.Update(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return APIResponse{new}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/questions/:qid/corrections/:cid", apiHandler(correctionHandler(func(ct CorrectionTemplate, body []byte) HTTPResponse {
|
||||
return formatApiResponse(ct.Delete())
|
||||
}), adminRestricted))
|
||||
c.JSON(http.StatusOK, nil)
|
||||
})
|
||||
}
|
||||
|
||||
router.GET("/api/users/:uid/questions/:qid", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||
if qid, err := strconv.Atoi(string(ps.ByName("qid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if question, err := getQuestion(qid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return formatApiResponse(question.ComputeScoreQuestion(&u))
|
||||
}
|
||||
})(ps, body)
|
||||
}, adminRestricted))
|
||||
func declareAPIAdminUserCorrectionsRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/corrections", func(c *gin.Context) {
|
||||
user := c.MustGet("user").(*User)
|
||||
|
||||
corrections, err := user.GetCorrections()
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetCorrections(uid=%d): %s", user.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve corrections."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, corrections)
|
||||
})
|
||||
router.POST("/corrections", func(c *gin.Context) {
|
||||
user := c.MustGet("user").(*User)
|
||||
|
||||
router.GET("/api/users/:uid/corrections", apiHandler(userHandler(
|
||||
func(u User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(u.GetCorrections())
|
||||
}), adminRestricted))
|
||||
router.POST("/api/users/:uid/corrections", apiHandler(userHandler(func(u User, body []byte) HTTPResponse {
|
||||
var new UserCorrection
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
return formatApiResponse(u.NewCorrection(new.IdTemplate))
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/users/:uid/corrections", apiHandler(userHandler(func(u User, body []byte) HTTPResponse {
|
||||
correction, err := user.NewCorrection(new.IdTemplate)
|
||||
if err != nil {
|
||||
log.Printf("Unable to NewCorrection(uid=%d): %s", user.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to insert the new correction."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, correction)
|
||||
})
|
||||
router.PUT("/corrections", func(c *gin.Context) {
|
||||
user := c.MustGet("user").(*User)
|
||||
|
||||
var new map[int64]bool
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
return formatApiResponse(u.EraseCorrections(new))
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/users/:uid/corrections/:cid", apiHandler(userCorrectionHandler(func(u User, uc UserCorrection, body []byte) HTTPResponse {
|
||||
return formatApiResponse(uc.Delete(u))
|
||||
}), adminRestricted))
|
||||
correction, err := user.EraseCorrections(new)
|
||||
if err != nil {
|
||||
log.Printf("Unable to EraseCorrections(uid=%d): %s", user.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to erase the correction."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, correction)
|
||||
})
|
||||
|
||||
correctionsRoutes := router.Group("/corrections/:cid")
|
||||
correctionsRoutes.Use(userCorrectionHandler)
|
||||
|
||||
correctionsRoutes.DELETE("", func(c *gin.Context) {
|
||||
user := c.MustGet("user").(*User)
|
||||
uc := c.MustGet("correction").(*UserCorrection)
|
||||
|
||||
if _, err := uc.Delete(user); err != nil {
|
||||
log.Printf("Unable to Delete(uid=%d, cid=%d) user correction: %s", user.Id, uc.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete this correction."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func correctionHandler(f func(CorrectionTemplate, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return questionHandler(func(q Question, body []byte) HTTPResponse {
|
||||
if cid, err := strconv.Atoi(string(ps.ByName("cid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func correctionHandler(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
if cid, err := strconv.Atoi(string(c.Param("cid"))); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid correction id"})
|
||||
return
|
||||
} else if correction, err := q.GetCorrectionTemplate(cid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Correction not found"})
|
||||
return
|
||||
} else {
|
||||
return f(correction, body)
|
||||
}
|
||||
})(ps, body)
|
||||
}
|
||||
}
|
||||
c.Set("correctiontemplate", correction)
|
||||
|
||||
func userCorrectionHandler(f func(User, UserCorrection, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return userHandler(func(u User, body []byte) HTTPResponse {
|
||||
if cid, err := strconv.Atoi(string(ps.ByName("cid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if correction, err := u.GetCorrection(cid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(u, correction, body)
|
||||
}
|
||||
})(ps, body)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +180,7 @@ type CorrectionTemplate struct {
|
||||
ScoreExplaination string `json:"score_explaination,omitempty"`
|
||||
}
|
||||
|
||||
func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error) {
|
||||
func (q *Question) GetCorrectionTemplates() (ct []*CorrectionTemplate, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_question=?", q.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -178,7 +191,7 @@ func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error)
|
||||
if err = rows.Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination); err != nil {
|
||||
return
|
||||
}
|
||||
ct = append(ct, c)
|
||||
ct = append(ct, &c)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -188,23 +201,25 @@ func (q *Question) GetCorrectionTemplates() (ct []CorrectionTemplate, err error)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Question) GetCorrectionTemplate(id int) (c CorrectionTemplate, err error) {
|
||||
func (q *Question) GetCorrectionTemplate(id int) (c *CorrectionTemplate, err error) {
|
||||
c = new(CorrectionTemplate)
|
||||
err = DBQueryRow("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_question=? AND id_template=?", q.Id, id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination)
|
||||
return
|
||||
}
|
||||
|
||||
func GetCorrectionTemplate(id int64) (c CorrectionTemplate, err error) {
|
||||
func GetCorrectionTemplate(id int64) (c *CorrectionTemplate, err error) {
|
||||
c = new(CorrectionTemplate)
|
||||
err = DBQueryRow("SELECT id_template, id_question, label, re, score, score_explanation FROM correction_templates WHERE id_template=?", id).Scan(&c.Id, &c.IdQuestion, &c.Label, &c.RegExp, &c.Score, &c.ScoreExplaination)
|
||||
return
|
||||
}
|
||||
|
||||
func (q *Question) NewCorrectionTemplate(label string, regexp string, score int, score_explaination string) (CorrectionTemplate, error) {
|
||||
func (q *Question) NewCorrectionTemplate(label string, regexp string, score int, score_explaination string) (*CorrectionTemplate, error) {
|
||||
if res, err := DBExec("INSERT INTO correction_templates (id_question, label, re, score, score_explanation) VALUES (?, ?, ?, ?, ?)", q.Id, label, regexp, score, score_explaination); err != nil {
|
||||
return CorrectionTemplate{}, err
|
||||
return nil, err
|
||||
} else if cid, err := res.LastInsertId(); err != nil {
|
||||
return CorrectionTemplate{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return CorrectionTemplate{cid, q.Id, label, regexp, score, score_explaination}, nil
|
||||
return &CorrectionTemplate{cid, q.Id, label, regexp, score, score_explaination}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +238,7 @@ func (t *CorrectionTemplate) Delete() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CorrectionTemplate) GetUserCorrected() (ucs []UserCorrection, err error) {
|
||||
func (t *CorrectionTemplate) GetUserCorrected() (ucs []*UserCorrection, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_correction, id_user, id_template FROM student_corrected WHERE id_template=?", t.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -234,7 +249,7 @@ func (t *CorrectionTemplate) GetUserCorrected() (ucs []UserCorrection, err error
|
||||
if err = rows.Scan(&c.Id, &c.IdUser, &c.IdTemplate); err != nil {
|
||||
return
|
||||
}
|
||||
ucs = append(ucs, c)
|
||||
ucs = append(ucs, &c)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -260,6 +275,22 @@ type UserCorrection struct {
|
||||
IdTemplate int64 `json:"id_template"`
|
||||
}
|
||||
|
||||
func userCorrectionHandler(c *gin.Context) {
|
||||
u := c.MustGet("user").(*User)
|
||||
|
||||
if cid, err := strconv.Atoi(string(c.Param("cid"))); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid correction id"})
|
||||
return
|
||||
} else if correction, err := u.GetCorrection(cid); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Correction not found"})
|
||||
return
|
||||
} else {
|
||||
c.Set("correction", correction)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) GetCorrections() (uc []UserCorrection, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_correction, id_template FROM student_corrected WHERE id_user=?", u.Id); errr != nil {
|
||||
return nil, errr
|
||||
@ -302,7 +333,8 @@ func (u *User) GetCorrectionsTemplate() (tpls []int64, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) GetCorrection(id int) (c UserCorrection, err error) {
|
||||
func (u *User) GetCorrection(id int) (c *UserCorrection, err error) {
|
||||
c = new(UserCorrection)
|
||||
err = DBQueryRow("SELECT id_correction, id_template FROM student_corrected WHERE id_user=? AND id_correction=?", u.Id, id).Scan(&c.Id, &c.IdTemplate)
|
||||
return
|
||||
}
|
||||
@ -340,7 +372,7 @@ func (u *User) EraseCorrections(ids map[int64]bool) (*UserCorrectionSummary, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserCorrection) Delete(u User) (*UserCorrectionSummary, error) {
|
||||
func (c *UserCorrection) Delete(u *User) (*UserCorrectionSummary, error) {
|
||||
if res, err := DBExec("DELETE FROM student_corrected WHERE id_correction = ?", c.Id); err != nil {
|
||||
return nil, err
|
||||
} else if _, err := res.RowsAffected(); err != nil {
|
||||
@ -374,7 +406,7 @@ func (q *Question) ComputeScoreQuestion(u *User) (*UserCorrectionSummary, error)
|
||||
} else if corrections, err := u.GetCorrectionsTemplate(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tpls := map[int64]CorrectionTemplate{}
|
||||
tpls := map[int64]*CorrectionTemplate{}
|
||||
for _, tpl := range templates {
|
||||
tpls[tpl.Id] = tpl
|
||||
}
|
||||
|
86
direct.go
86
direct.go
@ -4,11 +4,10 @@ import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
"nhooyr.io/websocket"
|
||||
"nhooyr.io/websocket/wsjson"
|
||||
)
|
||||
@ -21,15 +20,18 @@ var (
|
||||
WSAdminMutex = sync.RWMutex{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/surveys/:sid/ws", rawAuthHandler(SurveyWS, loggedUser))
|
||||
router.GET("/api/surveys/:sid/ws-admin", rawAuthHandler(SurveyWSAdmin, adminRestricted))
|
||||
func declareAPIAuthDirectRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/ws", SurveyWS)
|
||||
}
|
||||
|
||||
router.GET("/api/surveys/:sid/ws/stats", apiHandler(surveyHandler(func(s Survey, body []byte) HTTPResponse {
|
||||
return APIResponse{
|
||||
WSSurveyStats(s.Id),
|
||||
}
|
||||
}), adminRestricted))
|
||||
func declareAPIAdminDirectRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/ws-admin", SurveyWSAdmin)
|
||||
|
||||
router.GET("/ws/stats", func(c *gin.Context) {
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
c.JSON(http.StatusOK, WSSurveyStats(s.Id))
|
||||
})
|
||||
}
|
||||
|
||||
func WSSurveyStats(sid int64) map[string]interface{} {
|
||||
@ -104,35 +106,32 @@ func msgCurrentState(survey *Survey) (msg WSMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
func SurveyWS(w http.ResponseWriter, r *http.Request, ps httprouter.Params, u *User, body []byte) {
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Invalid survey identifier\"}", http.StatusBadRequest)
|
||||
func SurveyWS(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
survey := c.MustGet("survey").(*Survey)
|
||||
|
||||
if survey.Direct == nil {
|
||||
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Not a live survey"})
|
||||
return
|
||||
} else if survey, err := getSurvey(sid); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Survey not found\"}", http.StatusNotFound)
|
||||
return
|
||||
} else if survey.Direct == nil {
|
||||
http.Error(w, "{\"errmsg\": \"Not a direct survey\"}", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
ws, err := websocket.Accept(w, r, nil)
|
||||
}
|
||||
|
||||
ws, err := websocket.Accept(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Fatal("error get connection", err)
|
||||
}
|
||||
|
||||
log.Println(u.Login, "is now connected to WS", sid)
|
||||
log.Println(u.Login, "is now connected to WS", survey.Id)
|
||||
|
||||
c := make(chan WSMessage, 1)
|
||||
ch := make(chan WSMessage, 1)
|
||||
|
||||
WSClientsMutex.Lock()
|
||||
defer WSClientsMutex.Unlock()
|
||||
WSClients[survey.Id] = append(WSClients[survey.Id], WSClient{ws, c, u, survey.Id})
|
||||
WSClients[survey.Id] = append(WSClients[survey.Id], WSClient{ws, ch, u, survey.Id})
|
||||
|
||||
// Send current state
|
||||
c <- msgCurrentState(&survey)
|
||||
ch <- msgCurrentState(survey)
|
||||
|
||||
go SurveyWS_run(ws, c, survey.Id, u)
|
||||
}
|
||||
go SurveyWS_run(ws, ch, survey.Id, u)
|
||||
}
|
||||
|
||||
func WSWriteAll(message WSMessage) {
|
||||
@ -230,34 +229,32 @@ loopadmin:
|
||||
log.Println(u.Login, "admin disconnected")
|
||||
}
|
||||
|
||||
func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params, u *User, body []byte) {
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Invalid survey identifier\"}", http.StatusBadRequest)
|
||||
func SurveyWSAdmin(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
survey := c.MustGet("survey").(*Survey)
|
||||
|
||||
if survey.Direct == nil {
|
||||
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Not a live survey"})
|
||||
return
|
||||
} else if survey, err := getSurvey(sid); err != nil {
|
||||
http.Error(w, "{\"errmsg\": \"Survey not found\"}", http.StatusNotFound)
|
||||
return
|
||||
} else if survey.Direct == nil {
|
||||
http.Error(w, "{\"errmsg\": \"Not a direct survey\"}", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
ws, err := websocket.Accept(w, r, nil)
|
||||
}
|
||||
|
||||
ws, err := websocket.Accept(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Fatal("error get connection", err)
|
||||
}
|
||||
|
||||
log.Println(u.Login, "is now connected to WS-admin", sid)
|
||||
log.Println(u.Login, "is now connected to WS-admin", survey.Id)
|
||||
|
||||
c := make(chan WSMessage, 2)
|
||||
ch := make(chan WSMessage, 2)
|
||||
|
||||
WSAdminMutex.Lock()
|
||||
defer WSAdminMutex.Unlock()
|
||||
WSAdmin = append(WSAdmin, WSClient{ws, c, u, survey.Id})
|
||||
WSAdmin = append(WSAdmin, WSClient{ws, ch, u, survey.Id})
|
||||
|
||||
// Send current state
|
||||
c <- msgCurrentState(&survey)
|
||||
ch <- msgCurrentState(survey)
|
||||
|
||||
go SurveyWSAdmin_run(r.Context(), ws, c, survey.Id, u)
|
||||
go SurveyWSAdmin_run(c.Request.Context(), ws, ch, survey.Id, u)
|
||||
go func(c chan WSMessage, sid int) {
|
||||
var v WSMessage
|
||||
var err error
|
||||
@ -381,8 +378,7 @@ func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params,
|
||||
log.Println("Unknown admin action:", v.Action)
|
||||
}
|
||||
}
|
||||
}(c, sid)
|
||||
}
|
||||
}(ch, int(survey.Id))
|
||||
}
|
||||
|
||||
func WSAdminWriteAll(message WSMessage) {
|
||||
|
3
go.mod
3
go.mod
@ -4,13 +4,12 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.3
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
nhooyr.io/websocket v1.8.7
|
||||
)
|
||||
|
42
go.sum
42
go.sum
@ -74,8 +74,6 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
|
||||
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-oidc/v3 v3.2.0 h1:2eR2MGR7thBXSQ2YbODlF0fcmgtliLCfr9iX6RW11fc=
|
||||
github.com/coreos/go-oidc/v3 v3.2.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -94,18 +92,21 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
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/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-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
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.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
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/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
@ -158,10 +159,10 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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=
|
||||
@ -198,7 +199,6 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
@ -210,14 +210,10 @@ github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFK
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
|
||||
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
@ -231,8 +227,10 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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=
|
||||
@ -255,10 +253,10 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@ -282,9 +280,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -359,14 +354,12 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3 h1:2yWTtPWWRcISTw3/o+s/Y4UOMnQL71DWyToOANFusCg=
|
||||
golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
|
||||
@ -388,18 +381,8 @@ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 h1:zwrSfklXn0gxyLRX/aR+q6cgHbV/ItVyzbPlbA+dkAw=
|
||||
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0=
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0 h1:VnGaRqoLmqZH/3TMLJwYCEWkR4j1nuIU1U9TvbqsDUw=
|
||||
golang.org/x/oauth2 v0.0.0-20220630143837-2104d58473e0/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.0.0-20220718184931-c8730f7fcb92 h1:oVlhw3Oe+1reYsE2Nqu19PDJfLzwdU3QUUrG86rLK68=
|
||||
golang.org/x/oauth2 v0.0.0-20220718184931-c8730f7fcb92/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY=
|
||||
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -463,7 +446,6 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -474,6 +456,7 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@ -543,7 +526,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
@ -716,12 +698,12 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
@ -731,8 +713,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
90
grades.go
90
grades.go
@ -1,58 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/users/:uid/surveys/:sid/grades", apiAuthHandler(func(uauth *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return surveyAuthHandler(func(s Survey, uauth *User, _ []byte) HTTPResponse {
|
||||
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||
if uauth != nil && ((s.Shown && u.Id == uauth.Id) || uauth.IsAdmin) {
|
||||
if score, err := s.GetUserGrades(&u); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if score == nil {
|
||||
return APIResponse{"N/A"}
|
||||
func declareAPIAuthGradesRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/grades", func(c *gin.Context) {
|
||||
uauth := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
if survey, ok := c.Get("survey"); !ok {
|
||||
if uauth == nil || !uauth.IsAdmin {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
grades, err := GetAllGrades()
|
||||
if err != nil {
|
||||
log.Println("Unable to GetAllGrades:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve grades."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, grades)
|
||||
} else {
|
||||
return APIResponse{score}
|
||||
s := survey.(*Survey)
|
||||
|
||||
if user, ok := c.Get("user"); ok {
|
||||
u := user.(*User)
|
||||
|
||||
if uauth == nil || !((s.Shown && u.Id == uauth.Id) || uauth.IsAdmin) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible"})
|
||||
return
|
||||
}
|
||||
|
||||
score, err := s.GetUserGrades(u)
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetUserGrades(sid=%d; uid=%d): %s", s.Id, u.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrive user grade. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
if score == nil {
|
||||
c.JSON(http.StatusOK, "N/A")
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not accessible"),
|
||||
c.JSON(http.StatusOK, score)
|
||||
}
|
||||
} else if uauth.IsAdmin {
|
||||
scores, err := s.GetGrades()
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetGrades(sid=%d): %s", s.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrive grades."})
|
||||
return
|
||||
}
|
||||
})(ps, body)
|
||||
})(uauth, ps, body)
|
||||
}, loggedUser))
|
||||
router.GET("/api/surveys/:sid/grades", apiAuthHandler(surveyAuthHandler(func(s Survey, uauth *User, _ []byte) HTTPResponse {
|
||||
if scores, err := s.GetGrades(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if scores == nil {
|
||||
return APIResponse{"N/A"}
|
||||
|
||||
c.JSON(http.StatusOK, scores)
|
||||
} else {
|
||||
return APIResponse{scores}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.GET("/api/grades", apiAuthHandler(func(uauth *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
if uauth != nil && uauth.IsAdmin {
|
||||
if score, err := GetAllGrades(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if score == nil {
|
||||
return APIResponse{"N/A"}
|
||||
} else {
|
||||
return APIResponse{score}
|
||||
}
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not accessible"),
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
|
||||
return
|
||||
}
|
||||
}
|
||||
}, adminRestricted))
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllGrades() (scores map[int64]map[int64]*float64, err error) {
|
||||
|
227
handler.go
227
handler.go
@ -1,227 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
var router = httprouter.New()
|
||||
|
||||
func Router() *httprouter.Router {
|
||||
return router
|
||||
}
|
||||
|
||||
type HTTPResponse interface {
|
||||
WriteResponse(http.ResponseWriter)
|
||||
}
|
||||
|
||||
type APIResponse struct {
|
||||
response interface{}
|
||||
}
|
||||
|
||||
func (r APIResponse) WriteResponse(w http.ResponseWriter) {
|
||||
if str, found := r.response.(string); found {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, str)
|
||||
} else if bts, found := r.response.([]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(http.StatusOK)
|
||||
w.Write(bts)
|
||||
} else if j, err := json.Marshal(r.response); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err.Error()), http.StatusInternalServerError)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(j)
|
||||
}
|
||||
}
|
||||
|
||||
type APIErrorResponse struct {
|
||||
status int
|
||||
err error
|
||||
}
|
||||
|
||||
func (r APIErrorResponse) WriteResponse(w http.ResponseWriter) {
|
||||
if r.status == 0 {
|
||||
r.status = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", r.err.Error()), r.status)
|
||||
}
|
||||
|
||||
type DispatchFunction func(httprouter.Params, []byte) HTTPResponse
|
||||
|
||||
func eraseCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "auth",
|
||||
Value: "",
|
||||
Path: baseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
})
|
||||
}
|
||||
|
||||
func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return rawAuthHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, _ *User, body []byte) {
|
||||
f(w, r, ps, body)
|
||||
}, access...)
|
||||
}
|
||||
|
||||
func rawAuthHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, *User, []byte), access ...func(*User, *http.Request) *APIErrorResponse) 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 Authorization header
|
||||
var user *User = nil
|
||||
if cookie, err := r.Cookie("auth"); err == nil {
|
||||
if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
|
||||
eraseCookie(w)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusNotAcceptable)
|
||||
return
|
||||
} else if session, err := getSession(sessionid); err != nil {
|
||||
eraseCookie(w)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
} else if session.IdUser == nil {
|
||||
user = nil
|
||||
} else if std, err := getUser(int(*session.IdUser)); err != nil {
|
||||
eraseCookie(w)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
} else {
|
||||
user = &std
|
||||
}
|
||||
}
|
||||
|
||||
// Check access limitation
|
||||
for _, a := range access {
|
||||
if err := a(user, r); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg":%q}`, err.err.Error()), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read the body
|
||||
if r.ContentLength < 0 || r.ContentLength > 6553600 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, "{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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f(w, r, ps, user, body)
|
||||
}
|
||||
}
|
||||
|
||||
func formatResponseHandler(f func(*http.Request, httprouter.Params, []byte) HTTPResponse) func(http.ResponseWriter, *http.Request, httprouter.Params, []byte) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, body []byte) {
|
||||
f(r, ps, body).WriteResponse(w)
|
||||
}
|
||||
}
|
||||
|
||||
func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) HTTPResponse, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return rawHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) {
|
||||
formatResponseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) HTTPResponse {
|
||||
return f(w, ps, b)
|
||||
})(w, r, ps, b)
|
||||
}, access...)
|
||||
}
|
||||
|
||||
func apiHandler(f DispatchFunction, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return rawHandler(formatResponseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) HTTPResponse { return f(ps, b) }), access...)
|
||||
}
|
||||
|
||||
func formatApiResponse(i interface{}, err error) HTTPResponse {
|
||||
if err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusBadRequest,
|
||||
err: err,
|
||||
}
|
||||
} else {
|
||||
return APIResponse{i}
|
||||
}
|
||||
}
|
||||
|
||||
func apiAuthHandler(f func(*User, httprouter.Params, []byte) HTTPResponse, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return rawHandler(formatResponseHandler(func(r *http.Request, ps httprouter.Params, b []byte) HTTPResponse {
|
||||
if cookie, err := r.Cookie("auth"); err != nil {
|
||||
return f(nil, ps, b)
|
||||
} else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusBadRequest,
|
||||
err: err,
|
||||
}
|
||||
} else if session, err := getSession(sessionid); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusBadRequest,
|
||||
err: err,
|
||||
}
|
||||
} else if session.IdUser == nil {
|
||||
return f(nil, ps, b)
|
||||
} else if std, err := getUser(int(*session.IdUser)); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusInternalServerError,
|
||||
err: err,
|
||||
}
|
||||
} else {
|
||||
return f(&std, ps, b)
|
||||
}
|
||||
}), access...)
|
||||
}
|
||||
|
||||
func loggedUser(u *User, r *http.Request) *APIErrorResponse {
|
||||
if u != nil {
|
||||
return nil
|
||||
} else {
|
||||
ret := &APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Permission Denied"),
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
func adminRestricted(u *User, r *http.Request) *APIErrorResponse {
|
||||
if u != nil && u.IsAdmin {
|
||||
return nil
|
||||
} else {
|
||||
ret := &APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Permission Denied"),
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
37
help.go
37
help.go
@ -1,18 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/help", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return formatApiResponse(getNeedHelps())
|
||||
}, adminRestricted))
|
||||
router.POST("/api/help", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return formatApiResponse(u.NewNeedHelp())
|
||||
}, loggedUser))
|
||||
func declareAPIAdminHelpRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/help", func(c *gin.Context) {
|
||||
nhs, err := getNeedHelps()
|
||||
if err != nil {
|
||||
log.Println("Unable to getNeedHelps:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during need helps retrieval. Please retry."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nhs)
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAuthHelpRoutes(router *gin.RouterGroup) {
|
||||
router.POST("/help", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
nh, err := u.NewNeedHelp()
|
||||
if err != nil {
|
||||
log.Printf("Unable to NewNeedHelp(uid=%d): %s", u.Id, err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Sorry, something went wrong. Please retry in a few moment."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nh)
|
||||
})
|
||||
}
|
||||
|
||||
type NeedHelp struct {
|
||||
|
30
main.go
30
main.go
@ -1,9 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -83,16 +81,6 @@ func main() {
|
||||
LocalAuthFunc = dummyAuth
|
||||
}
|
||||
|
||||
if DevProxy != "" {
|
||||
Router().GET("/.svelte-kit/*_", serveOrReverse(""))
|
||||
Router().GET("/node_modules/*_", serveOrReverse(""))
|
||||
Router().GET("/@vite/*_", serveOrReverse(""))
|
||||
Router().GET("/__vite_ping", serveOrReverse(""))
|
||||
Router().GET("/src/*_", serveOrReverse(""))
|
||||
}
|
||||
|
||||
initializeOIDC()
|
||||
|
||||
// Initialize contents
|
||||
log.Println("Opening database...")
|
||||
if err := DBInit(*dsn); err != nil {
|
||||
@ -105,25 +93,19 @@ func main() {
|
||||
log.Fatal("Cannot create database: ", err)
|
||||
}
|
||||
|
||||
a := NewApp()
|
||||
go a.Start(*bind)
|
||||
|
||||
initializeOIDC(a.router)
|
||||
|
||||
// Prepare graceful shutdown
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *bind,
|
||||
Handler: StripPrefix(baseURL, Router()),
|
||||
}
|
||||
|
||||
// Serve content
|
||||
go func() {
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}()
|
||||
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
|
||||
|
||||
// Wait shutdown signal
|
||||
<-interrupt
|
||||
|
||||
log.Print("The service is shutting down...")
|
||||
srv.Shutdown(context.Background())
|
||||
a.Stop()
|
||||
log.Println("done")
|
||||
}
|
||||
|
172
proposals.go
172
proposals.go
@ -1,97 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/questions/:qid/proposals", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.GetProposals())
|
||||
}), loggedUser))
|
||||
router.POST("/api/questions/:qid/proposals", apiAuthHandler(questionAuthHandler(func(q Question, u *User, body []byte) HTTPResponse {
|
||||
var new Proposal
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func declareAPIAuthProposalsRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/proposals", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
proposals, err := q.GetProposals()
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetProposals(qid=%d): %s", q.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during proposals retrieving"})
|
||||
return
|
||||
}
|
||||
|
||||
return formatApiResponse(q.NewProposal(new.Label))
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/questions/:qid/proposals/:pid", apiAuthHandler(proposalAuthHandler(func(current Proposal, u *User, body []byte) HTTPResponse {
|
||||
var new Proposal
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/questions/:qid/proposals/:pid", apiAuthHandler(proposalAuthHandler(func(p Proposal, u *User, body []byte) HTTPResponse {
|
||||
return formatApiResponse(p.Delete())
|
||||
}), adminRestricted))
|
||||
|
||||
router.GET("/api/surveys/:sid/questions/:qid/proposals", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.GetProposals())
|
||||
}), loggedUser))
|
||||
router.POST("/api/surveys/:sid/questions/:qid/proposals", apiAuthHandler(questionAuthHandler(func(q Question, u *User, body []byte) HTTPResponse {
|
||||
var new Proposal
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
|
||||
return formatApiResponse(q.NewProposal(new.Label))
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/surveys/:sid/questions/:qid/proposals/:pid", apiAuthHandler(proposalAuthHandler(func(current Proposal, u *User, body []byte) HTTPResponse {
|
||||
var new Proposal
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/surveys/:sid/questions/:qid/proposals/:pid", apiAuthHandler(proposalAuthHandler(func(p Proposal, u *User, body []byte) HTTPResponse {
|
||||
return formatApiResponse(p.Delete())
|
||||
}), adminRestricted))
|
||||
c.JSON(http.StatusOK, proposals)
|
||||
})
|
||||
}
|
||||
|
||||
func proposalHandler(f func(Proposal, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
func declareAPIAdminProposalsRoutes(router *gin.RouterGroup) {
|
||||
router.POST("/proposals", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
var new Proposal
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
proposal, err := q.NewProposal(new.Label)
|
||||
if err != nil {
|
||||
log.Printf("Unable to NewProposal(qid=%d): %s", q.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to insert new proposal."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, proposal)
|
||||
})
|
||||
|
||||
proposalsRoutes := router.Group("/proposals/:pid")
|
||||
proposalsRoutes.Use(proposalHandler)
|
||||
|
||||
proposalsRoutes.PUT("", func(c *gin.Context) {
|
||||
current := c.MustGet("proposal").(*Proposal)
|
||||
|
||||
var new Proposal
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
|
||||
proposal, err := new.Update()
|
||||
if err != nil {
|
||||
log.Println("Unable to Update proposal:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during proposal updating."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, proposal)
|
||||
})
|
||||
proposalsRoutes.DELETE("", func(c *gin.Context) {
|
||||
p := c.MustGet("proposal").(*Proposal)
|
||||
|
||||
if _, err := p.Delete(); err != nil {
|
||||
log.Printf("Unable to Delete(pid=%d) proposal: %s", p.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete proposal."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func proposalHandler(c *gin.Context) {
|
||||
var question *Question = nil
|
||||
|
||||
if qid, err := strconv.Atoi(string(ps.ByName("qid"))); err == nil {
|
||||
if q, err := getQuestion(qid); err == nil {
|
||||
question = &q
|
||||
}
|
||||
if q, ok := c.Get("question"); ok {
|
||||
question = q.(*Question)
|
||||
}
|
||||
|
||||
if pid, err := strconv.Atoi(string(ps.ByName("pid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
var proposal *Proposal
|
||||
if pid, err := strconv.Atoi(string(c.Param("pid"))); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid proposal ID"})
|
||||
return
|
||||
} else if question == nil {
|
||||
if proposal, err := getProposal(pid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(proposal, body)
|
||||
if proposal, err = getProposal(pid); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Proposal not found"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if proposal, err := question.GetProposal(pid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(proposal, body)
|
||||
if proposal, err = question.GetProposal(pid); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Proposal not found"})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func proposalAuthHandler(f func(Proposal, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse {
|
||||
return func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return proposalHandler(func(p Proposal, body []byte) HTTPResponse {
|
||||
return f(p, u, body)
|
||||
})(ps, body)
|
||||
if proposal == nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Proposal not found"})
|
||||
return
|
||||
} else {
|
||||
c.Set("proposal", proposal)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +117,7 @@ type Proposal struct {
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
func (q *Question) GetProposals() (proposals []Proposal, err error) {
|
||||
func (q *Question) GetProposals() (proposals []*Proposal, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_proposal, id_question, label FROM survey_proposals WHERE id_question=?", q.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -112,7 +128,7 @@ func (q *Question) GetProposals() (proposals []Proposal, err error) {
|
||||
if err = rows.Scan(&p.Id, &p.IdQuestion, &p.Label); err != nil {
|
||||
return
|
||||
}
|
||||
proposals = append(proposals, p)
|
||||
proposals = append(proposals, &p)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -122,12 +138,14 @@ func (q *Question) GetProposals() (proposals []Proposal, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func getProposal(id int) (p Proposal, err error) {
|
||||
func getProposal(id int) (p *Proposal, err error) {
|
||||
p = new(Proposal)
|
||||
err = DBQueryRow("SELECT id_proposal, id_question, label FROM survey_proposals WHERE id_proposal=?", id).Scan(&p.Id, &p.IdQuestion, &p.Label)
|
||||
return
|
||||
}
|
||||
|
||||
func (q *Question) GetProposal(id int) (p Proposal, err error) {
|
||||
func (q *Question) GetProposal(id int) (p *Proposal, err error) {
|
||||
p = new(Proposal)
|
||||
err = DBQueryRow("SELECT id_proposal, id_question, label FROM survey_proposals WHERE id_proposal=? AND id_question=?", id, q.Id).Scan(&p.Id, &p.IdQuestion, &p.Label)
|
||||
return
|
||||
}
|
||||
|
288
questions.go
288
questions.go
@ -1,132 +1,200 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/questions", apiHandler(
|
||||
func(httprouter.Params, []byte) HTTPResponse {
|
||||
return formatApiResponse(getQuestions())
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid/questions", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
func declareAPIAuthQuestionsRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/questions", func(c *gin.Context) {
|
||||
var s *Survey
|
||||
if survey, ok := c.Get("survey"); ok {
|
||||
s = survey.(*Survey)
|
||||
}
|
||||
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
if s == nil {
|
||||
if !u.IsAdmin {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission denied"})
|
||||
return
|
||||
}
|
||||
|
||||
if questions, err := getQuestions(); err != nil {
|
||||
log.Println("Unable to getQuestions:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve questions. Please try again later."})
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusOK, questions)
|
||||
}
|
||||
} else {
|
||||
if !s.Shown && !u.IsAdmin {
|
||||
return APIErrorResponse{err: errors.New("Not accessible")}
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible"})
|
||||
return
|
||||
}
|
||||
if s.StartAvailability.After(time.Now()) && !u.IsAdmin {
|
||||
return APIErrorResponse{status: http.StatusPaymentRequired, err: errors.New("Not available yet")}
|
||||
}
|
||||
return formatApiResponse(s.GetQuestions())
|
||||
}), loggedUser))
|
||||
router.POST("/api/surveys/:sid/questions", apiAuthHandler(surveyAuthHandler(func(s Survey, u *User, body []byte) HTTPResponse {
|
||||
if !s.Shown && !u.IsAdmin {
|
||||
return APIErrorResponse{err: errors.New("Not accessible")}
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible yet"})
|
||||
return
|
||||
}
|
||||
|
||||
var new Question
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
|
||||
return formatApiResponse(s.NewQuestion(new.Title, new.DescriptionRaw, new.Placeholder, new.Kind))
|
||||
}), adminRestricted))
|
||||
router.GET("/api/questions/:qid", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
if u.IsAdmin {
|
||||
return APIResponse{q}
|
||||
} else if s, err := getSurvey(int(q.IdSurvey)); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if s.Shown || (s.Direct != nil && *s.Direct == q.Id) {
|
||||
return APIResponse{q}
|
||||
if questions, err := s.GetQuestions(); err != nil {
|
||||
log.Println("Unable to GetQuestions:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve questions. Please try again later."})
|
||||
return
|
||||
} else {
|
||||
return APIErrorResponse{err: fmt.Errorf("Not authorized"), status: http.StatusForbidden}
|
||||
c.JSON(http.StatusOK, questions)
|
||||
}
|
||||
}), loggedUser))
|
||||
router.GET("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(
|
||||
func(s Question, _ []byte) HTTPResponse {
|
||||
return APIResponse{s}
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/questions/:qid", apiHandler(questionHandler(func(current Question, body []byte) HTTPResponse {
|
||||
}
|
||||
})
|
||||
|
||||
questionsRoutes := router.Group("/questions/:qid")
|
||||
questionsRoutes.Use(questionHandler)
|
||||
|
||||
questionsRoutes.GET("", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
if !u.IsAdmin {
|
||||
s, err := getSurvey(int(q.IdSurvey))
|
||||
if err != nil {
|
||||
log.Println("Unable to getSurvey:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during survey retrieval. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
if !(s.Shown || (s.Direct != nil && *s.Direct == q.Id)) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, q)
|
||||
})
|
||||
|
||||
declareAPIAuthProposalsRoutes(questionsRoutes)
|
||||
declareAPIAuthQuestionResponsesRoutes(questionsRoutes)
|
||||
}
|
||||
|
||||
func declareAPIAdminQuestionsRoutes(router *gin.RouterGroup) {
|
||||
router.POST("/questions", func(c *gin.Context) {
|
||||
var s *Survey
|
||||
if survey, ok := c.Get("survey"); ok {
|
||||
s = survey.(*Survey)
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Survey identifier not defined."})
|
||||
return
|
||||
}
|
||||
|
||||
var new Question
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
q, err := s.NewQuestion(new.Title, new.DescriptionRaw, new.Placeholder, new.Kind)
|
||||
if err != nil {
|
||||
log.Println("Unable to NewQuestion:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during question insertion."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, q)
|
||||
})
|
||||
|
||||
questionsRoutes := router.Group("/questions/:qid")
|
||||
questionsRoutes.Use(questionHandler)
|
||||
|
||||
questionsRoutes.PUT("", func(c *gin.Context) {
|
||||
current := c.MustGet("question").(*Question)
|
||||
|
||||
var new Question
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(func(current Question, body []byte) HTTPResponse {
|
||||
var new Question
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
|
||||
if q, err := new.Update(); err != nil {
|
||||
log.Println("Unable to Update question:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during question update."})
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusOK, q)
|
||||
}
|
||||
})
|
||||
questionsRoutes.DELETE("", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
if _, err := q.Delete(); err != nil {
|
||||
log.Println("Unable to Delete question:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete the question."})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/questions/:qid", apiHandler(questionHandler(
|
||||
func(q Question, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.Delete())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(
|
||||
func(q Question, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.Delete())
|
||||
}), adminRestricted))
|
||||
c.JSON(http.StatusOK, nil)
|
||||
})
|
||||
|
||||
declareAPIAdminCorrectionsRoutes(questionsRoutes)
|
||||
declareAPIAdminProposalsRoutes(questionsRoutes)
|
||||
declareAPIAdminResponsesRoutes(questionsRoutes)
|
||||
}
|
||||
|
||||
func questionHandler(f func(Question, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
var survey *Survey = nil
|
||||
func declareAPIAdminUserQuestionsRoutes(router *gin.RouterGroup) {
|
||||
questionsRoutes := router.Group("/questions/:qid")
|
||||
questionsRoutes.Use(questionHandler)
|
||||
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err == nil {
|
||||
if s, err := getSurvey(sid); err == nil {
|
||||
survey = &s
|
||||
}
|
||||
questionsRoutes.GET("", func(c *gin.Context) {
|
||||
question := c.MustGet("question").(*Question)
|
||||
user := c.MustGet("user").(*User)
|
||||
|
||||
score, err := question.ComputeScoreQuestion(user)
|
||||
if err != nil {
|
||||
log.Println("Unable to ComputeScoreQuestion:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to compute score. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
if qid, err := strconv.Atoi(string(ps.ByName("qid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if survey == nil {
|
||||
if question, err := getQuestion(qid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(question, body)
|
||||
}
|
||||
} else {
|
||||
if question, err := survey.GetQuestion(qid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(question, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, score)
|
||||
})
|
||||
}
|
||||
|
||||
func questionAuthHandler(f func(Question, *User, []byte) HTTPResponse, access ...func(*User, *Question) error) func(*User, httprouter.Params, []byte) HTTPResponse {
|
||||
return func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return questionHandler(func(q Question, body []byte) HTTPResponse {
|
||||
// Check access limitation
|
||||
for _, a := range access {
|
||||
if err := a(u, &q); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: err,
|
||||
func questionHandler(c *gin.Context) {
|
||||
var survey *Survey
|
||||
if s, ok := c.Get("survey"); ok {
|
||||
survey = s.(*Survey)
|
||||
}
|
||||
|
||||
qid, err := strconv.Atoi(string(c.Param("qid")))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid question identifier."})
|
||||
return
|
||||
}
|
||||
|
||||
var question *Question
|
||||
|
||||
if survey == nil {
|
||||
question, err = getQuestion(qid)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Question not found"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
question, err = survey.GetQuestion(qid)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Question not found"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return f(q, u, body)
|
||||
})(ps, body)
|
||||
}
|
||||
c.Set("question", question)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
type Question struct {
|
||||
@ -139,7 +207,7 @@ type Question struct {
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
|
||||
func getQuestions() (questions []Question, err error) {
|
||||
func getQuestions() (questions []*Question, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests"); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -151,7 +219,7 @@ func getQuestions() (questions []Question, err error) {
|
||||
return
|
||||
}
|
||||
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
|
||||
questions = append(questions, q)
|
||||
questions = append(questions, &q)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -161,7 +229,7 @@ func getQuestions() (questions []Question, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) GetQuestions() (questions []Question, err error) {
|
||||
func (s *Survey) GetQuestions() (questions []*Question, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests WHERE id_survey=?", s.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -173,7 +241,7 @@ func (s *Survey) GetQuestions() (questions []Question, err error) {
|
||||
return
|
||||
}
|
||||
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
|
||||
questions = append(questions, q)
|
||||
questions = append(questions, &q)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -183,41 +251,43 @@ func (s *Survey) GetQuestions() (questions []Question, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func getQuestion(id int) (q Question, err error) {
|
||||
func getQuestion(id int) (q *Question, err error) {
|
||||
q = new(Question)
|
||||
err = DBQueryRow("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests WHERE id_question=?", id).Scan(&q.Id, &q.IdSurvey, &q.Title, &q.DescriptionRaw, &q.Placeholder, &q.Kind)
|
||||
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Survey) GetQuestion(id int) (q Question, err error) {
|
||||
func (s *Survey) GetQuestion(id int) (q *Question, err error) {
|
||||
q = new(Question)
|
||||
err = DBQueryRow("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests WHERE id_question=? AND id_survey=?", id, s.Id).Scan(&q.Id, &q.IdSurvey, &q.Title, &q.DescriptionRaw, &q.Placeholder, &q.Kind)
|
||||
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Survey) NewQuestion(title string, description string, placeholder string, kind string) (Question, error) {
|
||||
func (s *Survey) NewQuestion(title string, description string, placeholder string, kind string) (*Question, error) {
|
||||
if res, err := DBExec("INSERT INTO survey_quests (id_survey, title, description, placeholder, kind) VALUES (?, ?, ?, ?, ?)", s.Id, title, description, placeholder, kind); err != nil {
|
||||
return Question{}, err
|
||||
return nil, err
|
||||
} else if qid, err := res.LastInsertId(); err != nil {
|
||||
return Question{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return Question{qid, s.Id, title, string(blackfriday.Run([]byte(description))), description, placeholder, kind}, nil
|
||||
return &Question{qid, s.Id, title, string(blackfriday.Run([]byte(description))), description, placeholder, kind}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q Question) GetSurvey() (Survey, error) {
|
||||
func (q *Question) GetSurvey() (*Survey, error) {
|
||||
return getSurvey(int(q.IdSurvey))
|
||||
}
|
||||
|
||||
func (q Question) Update() (Question, error) {
|
||||
func (q *Question) Update() (*Question, error) {
|
||||
if _, err := DBExec("UPDATE survey_quests SET id_survey = ?, title = ?, description = ?, placeholder = ?, kind = ? WHERE id_question = ?", q.IdSurvey, q.Title, q.DescriptionRaw, q.Placeholder, q.Kind, q.Id); err != nil {
|
||||
return Question{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return q, err
|
||||
}
|
||||
}
|
||||
|
||||
func (q Question) Delete() (int64, error) {
|
||||
func (q *Question) Delete() (int64, error) {
|
||||
if res, err := DBExec("DELETE FROM survey_quests WHERE id_question = ?", q.Id); err != nil {
|
||||
return 0, err
|
||||
} else if nb, err := res.RowsAffected(); err != nil {
|
||||
|
307
responses.go
307
responses.go
@ -1,33 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.POST("/api/surveys/:sid", apiAuthHandler(surveyAuthHandler(func(s Survey, u *User, body []byte) HTTPResponse {
|
||||
func declareAPIAuthResponsesRoutes(router *gin.RouterGroup) {
|
||||
router.POST("", func(c *gin.Context) {
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
uauth := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
var u *User
|
||||
if user, ok := c.Get("user"); ok {
|
||||
if !u.IsAdmin {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
u = user.(*User)
|
||||
} else {
|
||||
u = uauth
|
||||
}
|
||||
|
||||
var responses []Response
|
||||
if err := json.Unmarshal(body, &responses); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(responses); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Check the survey is open
|
||||
if !uauth.IsAdmin {
|
||||
now := time.Now()
|
||||
if now.Before(s.StartAvailability) {
|
||||
return APIErrorResponse{err: fmt.Errorf("Le questionnaire n'a pas encore commencé")}
|
||||
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Le questionnaire n'a pas encore commencé"})
|
||||
return
|
||||
} else if now.After(s.EndAvailability.Add(5 * time.Minute)) {
|
||||
return APIErrorResponse{err: fmt.Errorf("Le questionnaire n'est plus ouvert")}
|
||||
c.AbortWithStatusJSON(http.StatusPaymentRequired, gin.H{"errmsg": "Le questionnaire n'est plus ouvert"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, response := range responses {
|
||||
if !s.Shown && (s.Direct == nil || *s.Direct != response.IdQuestion) {
|
||||
return APIErrorResponse{err: fmt.Errorf("Cette question n'est pas disponible")}
|
||||
if !uauth.IsAdmin && !s.Shown && (s.Direct == nil || *s.Direct != response.IdQuestion) {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Cette question n'est pas disponible"})
|
||||
return
|
||||
} else if len(response.Answer) > 0 {
|
||||
// Check if the response has changed
|
||||
if response.Id != 0 {
|
||||
@ -39,7 +59,9 @@ func init() {
|
||||
}
|
||||
|
||||
if _, err := s.NewResponse(response.IdQuestion, u.Id, response.Answer); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Printf("Unable to NewResponse(uid=%d;sid=%d;qid=%d): %s", u.Id, s.Id, response.IdQuestion, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Une erreur s'est produite durant l'enregistrement des réponses. Veuillez réessayer dans quelques instants."})
|
||||
return
|
||||
}
|
||||
|
||||
if s.Direct != nil {
|
||||
@ -48,60 +70,54 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
return APIResponse{true}
|
||||
}), loggedUser))
|
||||
router.POST("/api/users/:uid/surveys/:sid", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return surveyAuthHandler(func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||
var responses []Response
|
||||
if err := json.Unmarshal(body, &responses); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
router.GET("/responses", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
if user, ok := c.Get("user"); ok {
|
||||
if !u.IsAdmin {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
for _, response := range responses {
|
||||
if len(response.Answer) > 0 {
|
||||
// Check if the response has changed
|
||||
if response.Id != 0 {
|
||||
if res, err := s.GetResponse(int(response.Id)); err == nil {
|
||||
if res.IdUser == u.Id && res.Answer == response.Answer {
|
||||
continue
|
||||
}
|
||||
}
|
||||
u = user.(*User)
|
||||
}
|
||||
|
||||
if _, err := s.NewResponse(response.IdQuestion, u.Id, response.Answer); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
}
|
||||
responses, err := s.GetMyResponses(u, s.Corrected)
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetMyResponses(uid=%d;sid=%d): %s", u.Id, s.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Une erreur s'est produite pendant la récupération des réponses."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, responses)
|
||||
})
|
||||
|
||||
responsesRoutes := router.Group("/responses/:rid")
|
||||
responsesRoutes.Use(responseHandler)
|
||||
|
||||
responsesRoutes.GET("", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, c.MustGet("response"))
|
||||
})
|
||||
responsesRoutes.POST("/report", func(c *gin.Context) {
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
r := c.MustGet("response").(*Response)
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
if user, ok := c.Get("user"); ok {
|
||||
if !u.IsAdmin {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
u = user.(*User)
|
||||
}
|
||||
|
||||
return APIResponse{true}
|
||||
})(ps, body)
|
||||
})(u, ps, body)
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid/responses", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(s.GetMyResponses(u, s.Corrected))
|
||||
}), loggedUser))
|
||||
router.GET("/api/questions/:qid/response", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.GetMyResponse(u, false))
|
||||
}), loggedUser))
|
||||
router.GET("/api/users/:uid/surveys/:sid/responses", apiAuthHandler(func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return surveyAuthHandler(func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(s.GetMyResponses(&u, s.Corrected))
|
||||
})(ps, body)
|
||||
})(u, ps, body)
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid/responses/:rid", apiAuthHandler(responseAuthHandler(
|
||||
func(r Response, _ *User, _ []byte) HTTPResponse {
|
||||
return APIResponse{r}
|
||||
}), adminRestricted))
|
||||
router.POST("/api/surveys/:sid/responses/:rid/report", apiAuthHandler(surveyResponseAuthHandler(
|
||||
func(s *Survey, r Response, u *User, _ []byte) HTTPResponse {
|
||||
if s == nil || !s.Corrected || r.IdUser != u.Id {
|
||||
return APIErrorResponse{err: fmt.Errorf("Cette action est impossible pour l'instant"), status: http.StatusForbidden}
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Cette action est impossible pour l'instant"})
|
||||
return
|
||||
}
|
||||
|
||||
if r.TimeScored == nil || r.TimeReported == nil || r.TimeReported.Before(*r.TimeScored) {
|
||||
@ -111,35 +127,56 @@ func init() {
|
||||
r.TimeReported = nil
|
||||
}
|
||||
if _, err := r.Update(); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Printf("Unable to Update(uid=%d;rid=%d) response: %s", u.Id, r.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Une erreur s'est produite lors de la mise à jour du statut de la réponse. Veuillez réessayer dans quelques instants."})
|
||||
return
|
||||
}
|
||||
|
||||
return APIResponse{r}
|
||||
}), loggedUser))
|
||||
router.GET("/api/surveys/:sid/questions/:qid/responses", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.GetResponses())
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/surveys/:sid/questions/:qid/responses/:rid", apiAuthHandler(responseAuthHandler(func(current Response, u *User, body []byte) HTTPResponse {
|
||||
c.JSON(http.StatusOK, r)
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAuthQuestionResponsesRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/response", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
res, err := q.GetMyResponse(u, false)
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetMyResponse(uid=%d;qid=%d;false): %s", u.Id, q.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during response retrieval."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, res)
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAdminResponsesRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/responses", func(c *gin.Context) {
|
||||
q := c.MustGet("question").(*Question)
|
||||
|
||||
res, err := q.GetResponses()
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetResponses(qid=%d): %s", q.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during responses retrieval."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, res)
|
||||
})
|
||||
|
||||
responsesRoutes := router.Group("/responses/:rid")
|
||||
responsesRoutes.Use(responseHandler)
|
||||
|
||||
responsesRoutes.PUT("", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
current := c.MustGet("response").(*Response)
|
||||
|
||||
var new Response
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
|
||||
if new.Score != nil && (current.Score == nil || *new.Score != *current.Score) {
|
||||
now := time.Now()
|
||||
new.IdCorrector = &u.Id
|
||||
new.TimeScored = &now
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
new.IdUser = current.IdUser
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/questions/:qid/responses/:rid", apiAuthHandler(responseAuthHandler(func(current Response, u *User, body []byte) HTTPResponse {
|
||||
var new Response
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if new.Score != nil && (current.Score == nil || *new.Score != *current.Score) {
|
||||
@ -147,6 +184,7 @@ func init() {
|
||||
new.IdCorrector = &u.Id
|
||||
new.TimeScored = &now
|
||||
|
||||
// Remove from cache
|
||||
if _, ok := _score_cache[current.IdUser]; ok {
|
||||
if surveyId, err := current.GetSurveyId(); err == nil {
|
||||
if _, ok = _score_cache[current.IdUser][surveyId]; ok {
|
||||
@ -158,60 +196,42 @@ func init() {
|
||||
|
||||
new.Id = current.Id
|
||||
new.IdUser = current.IdUser
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
|
||||
response, err := new.Update()
|
||||
if err != nil {
|
||||
log.Println("Unable to Update response:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during response updating."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
})
|
||||
}
|
||||
|
||||
func responseHandler(f func(Response, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return surveyResponseHandler(func(s *Survey, r Response, b []byte) HTTPResponse {
|
||||
return f(r, b)
|
||||
})(ps, body)
|
||||
}
|
||||
}
|
||||
func responseHandler(c *gin.Context) {
|
||||
var survey *Survey
|
||||
|
||||
func surveyResponseHandler(f func(*Survey, Response, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
var survey *Survey = nil
|
||||
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err == nil {
|
||||
if s, err := getSurvey(sid); err == nil {
|
||||
survey = &s
|
||||
}
|
||||
if s, ok := c.Get("survey"); ok {
|
||||
survey = s.(*Survey)
|
||||
}
|
||||
|
||||
if rid, err := strconv.Atoi(string(ps.ByName("rid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
var response *Response
|
||||
if rid, err := strconv.Atoi(string(c.Param("rid"))); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad response identifier."})
|
||||
return
|
||||
} else if survey == nil {
|
||||
if response, err := getResponse(rid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(survey, response, body)
|
||||
if response, err = getResponse(rid); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Response not found."})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if response, err := survey.GetResponse(rid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(survey, response, body)
|
||||
} else if response, err = survey.GetResponse(rid); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Response not found."})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func surveyResponseAuthHandler(f func(*Survey, Response, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse {
|
||||
return func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return surveyResponseHandler(func(s *Survey, r Response, body []byte) HTTPResponse {
|
||||
return f(s, r, u, body)
|
||||
})(ps, body)
|
||||
}
|
||||
}
|
||||
c.Set("response", response)
|
||||
|
||||
func responseAuthHandler(f func(Response, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse {
|
||||
return func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return responseHandler(func(r Response, body []byte) HTTPResponse {
|
||||
return f(r, u, body)
|
||||
})(ps, body)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
@ -227,7 +247,7 @@ type Response struct {
|
||||
TimeReported *time.Time `json:"time_reported,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Survey) GetResponses() (responses []Response, err error) {
|
||||
func (s *Survey) GetResponses() (responses []*Response, err error) {
|
||||
if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=?", s.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -238,7 +258,7 @@ func (s *Survey) GetResponses() (responses []Response, err error) {
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported); err != nil {
|
||||
return
|
||||
}
|
||||
responses = append(responses, r)
|
||||
responses = append(responses, &r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -248,7 +268,7 @@ func (s *Survey) GetResponses() (responses []Response, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response, err error) {
|
||||
func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []*Response, err error) {
|
||||
if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=? AND R.id_user=? ORDER BY time_submit DESC", s.Id, u.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -263,7 +283,7 @@ func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response,
|
||||
r.Score = nil
|
||||
r.ScoreExplaination = nil
|
||||
}
|
||||
responses = append(responses, r)
|
||||
responses = append(responses, &r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -273,7 +293,8 @@ func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Question) GetMyResponse(u *User, showScore bool) (r Response, err error) {
|
||||
func (q *Question) GetMyResponse(u *User, showScore bool) (r *Response, err error) {
|
||||
r = new(Response)
|
||||
err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R WHERE R.id_question=? AND R.id_user=? ORDER BY time_submit DESC LIMIT 1", q.Id, u.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported)
|
||||
if !showScore {
|
||||
r.Score = nil
|
||||
@ -282,7 +303,7 @@ func (q *Question) GetMyResponse(u *User, showScore bool) (r Response, err error
|
||||
return
|
||||
}
|
||||
|
||||
func (q *Question) GetResponses() (responses []Response, err error) {
|
||||
func (q *Question) GetResponses() (responses []*Response, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_response, id_question, S.id_user, answer, S.time_submit, score, score_explanation, id_corrector, time_scored, time_reported FROM (SELECT id_user, MAX(time_submit) AS time_submit FROM survey_responses WHERE id_question=? GROUP BY id_user) R INNER JOIN survey_responses S ON S.id_user = R.id_user AND S.time_submit = R.time_submit AND S.id_question=?", q.Id, q.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -293,7 +314,7 @@ func (q *Question) GetResponses() (responses []Response, err error) {
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported); err != nil {
|
||||
return
|
||||
}
|
||||
responses = append(responses, r)
|
||||
responses = append(responses, &r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -303,23 +324,25 @@ func (q *Question) GetResponses() (responses []Response, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func getResponse(id int) (r Response, err error) {
|
||||
func getResponse(id int) (r *Response, err error) {
|
||||
r = new(Response)
|
||||
err = DBQueryRow("SELECT id_response, id_question, id_user, answer, time_submit, score, score_explanation, id_corrector, time_scored, time_reported FROM survey_responses WHERE id_response=?", id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Survey) GetResponse(id int) (r Response, err error) {
|
||||
func (s *Survey) GetResponse(id int) (r *Response, err error) {
|
||||
r = new(Response)
|
||||
err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored, R.time_reported FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE R.id_response=? AND Q.id_survey=?", id, s.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored, &r.TimeReported)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Survey) NewResponse(id_question int64, id_user int64, response string) (Response, error) {
|
||||
func (s *Survey) NewResponse(id_question int64, id_user int64, response string) (*Response, error) {
|
||||
if res, err := DBExec("INSERT INTO survey_responses (id_question, id_user, answer, time_submit) VALUES (?, ?, ?, ?)", id_question, id_user, response, time.Now()); err != nil {
|
||||
return Response{}, err
|
||||
return nil, err
|
||||
} else if rid, err := res.LastInsertId(); err != nil {
|
||||
return Response{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return Response{rid, id_question, id_user, response, time.Now(), nil, nil, nil, nil, nil}, nil
|
||||
return &Response{rid, id_question, id_user, response, time.Now(), nil, nil, nil, nil, nil}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
23
session.go
23
session.go
@ -11,37 +11,38 @@ type Session struct {
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
func getSession(id []byte) (s Session, err error) {
|
||||
func getSession(id []byte) (s *Session, err error) {
|
||||
s = new(Session)
|
||||
err = DBQueryRow("SELECT id_session, id_user, time FROM user_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdUser, &s.Time)
|
||||
return
|
||||
}
|
||||
|
||||
func NewSession() (Session, error) {
|
||||
func NewSession() (*Session, error) {
|
||||
session_id := make([]byte, 255)
|
||||
if _, err := rand.Read(session_id); err != nil {
|
||||
return Session{}, err
|
||||
return nil, err
|
||||
} else if _, err := DBExec("INSERT INTO user_sessions (id_session, time) VALUES (?, ?)", session_id, time.Now()); err != nil {
|
||||
return Session{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return Session{session_id, nil, time.Now()}, nil
|
||||
return &Session{session_id, nil, time.Now()}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (user User) NewSession() (Session, error) {
|
||||
func (user User) NewSession() (*Session, error) {
|
||||
session_id := make([]byte, 255)
|
||||
if _, err := rand.Read(session_id); err != nil {
|
||||
return Session{}, err
|
||||
return nil, err
|
||||
} else if _, err := DBExec("INSERT INTO user_sessions (id_session, id_user, time) VALUES (?, ?, ?)", session_id, user.Id, time.Now()); err != nil {
|
||||
return Session{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return Session{session_id, &user.Id, time.Now()}, nil
|
||||
return &Session{session_id, &user.Id, time.Now()}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s Session) SetUser(user User) (Session, error) {
|
||||
func (s Session) SetUser(user *User) (*Session, error) {
|
||||
s.IdUser = &user.Id
|
||||
_, err := s.Update()
|
||||
return s, err
|
||||
return &s, err
|
||||
}
|
||||
|
||||
func (s Session) Update() (int64, error) {
|
||||
|
68
static.go
68
static.go
@ -6,62 +6,70 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var DevProxy string
|
||||
|
||||
func serveOrReverse(forced_url string) func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
func serveOrReverse(forced_url string) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
if DevProxy != "" {
|
||||
if u, err := url.Parse(DevProxy); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
if forced_url != "" {
|
||||
u.Path = path.Join(u.Path, forced_url)
|
||||
} else {
|
||||
u.Path = path.Join(u.Path, r.URL.Path)
|
||||
u.Path = path.Join(u.Path, c.Request.URL.Path)
|
||||
}
|
||||
|
||||
if r, err := http.NewRequest(r.Method, u.String(), r.Body); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
if r, err := http.NewRequest(c.Request.Method, u.String(), c.Request.Body); err != nil {
|
||||
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
|
||||
} else if resp, err := http.DefaultClient.Do(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadGateway)
|
||||
http.Error(c.Writer, err.Error(), http.StatusBadGateway)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
|
||||
for key := range resp.Header {
|
||||
w.Header().Add(key, resp.Header.Get(key))
|
||||
c.Writer.Header().Add(key, resp.Header.Get(key))
|
||||
}
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
c.Writer.WriteHeader(resp.StatusCode)
|
||||
|
||||
io.Copy(w, resp.Body)
|
||||
io.Copy(c.Writer, resp.Body)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if forced_url != "" {
|
||||
r.URL.Path = forced_url
|
||||
c.Request.URL.Path = forced_url
|
||||
}
|
||||
http.FileServer(Assets).ServeHTTP(w, r)
|
||||
http.FileServer(Assets).ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
Router().GET("/@fs/*_", serveOrReverse(""))
|
||||
Router().GET("/", serveOrReverse(""))
|
||||
Router().GET("/_app/*_", serveOrReverse(""))
|
||||
Router().GET("/auth", serveOrReverse("/"))
|
||||
Router().GET("/bug-bounty", serveOrReverse("/"))
|
||||
Router().GET("/grades", serveOrReverse("/"))
|
||||
Router().GET("/help", serveOrReverse("/"))
|
||||
Router().GET("/surveys", serveOrReverse("/"))
|
||||
Router().GET("/surveys/*_", serveOrReverse("/"))
|
||||
Router().GET("/users", serveOrReverse("/"))
|
||||
Router().GET("/users/*_", serveOrReverse("/"))
|
||||
Router().GET("/works", serveOrReverse("/"))
|
||||
Router().GET("/works/*_", serveOrReverse("/"))
|
||||
Router().GET("/css/*_", serveOrReverse(""))
|
||||
Router().GET("/fonts/*_", serveOrReverse(""))
|
||||
Router().GET("/img/*_", serveOrReverse(""))
|
||||
func declareStaticRoutes(router *gin.Engine) {
|
||||
router.GET("/@fs/*_", serveOrReverse(""))
|
||||
router.GET("/", serveOrReverse(""))
|
||||
router.GET("/_app/*_", serveOrReverse(""))
|
||||
router.GET("/auth/", serveOrReverse("/"))
|
||||
router.GET("/bug-bounty", serveOrReverse("/"))
|
||||
router.GET("/grades", serveOrReverse("/"))
|
||||
router.GET("/help", serveOrReverse("/"))
|
||||
router.GET("/surveys", serveOrReverse("/"))
|
||||
router.GET("/surveys/*_", serveOrReverse("/"))
|
||||
router.GET("/users", serveOrReverse("/"))
|
||||
router.GET("/users/*_", serveOrReverse("/"))
|
||||
router.GET("/works", serveOrReverse("/"))
|
||||
router.GET("/works/*_", serveOrReverse("/"))
|
||||
router.GET("/css/*_", serveOrReverse(""))
|
||||
router.GET("/fonts/*_", serveOrReverse(""))
|
||||
router.GET("/img/*_", serveOrReverse(""))
|
||||
|
||||
if DevProxy != "" {
|
||||
router.GET("/.svelte-kit/*_", serveOrReverse(""))
|
||||
router.GET("/node_modules/*_", serveOrReverse(""))
|
||||
router.GET("/@vite/*_", serveOrReverse(""))
|
||||
router.GET("/__vite_ping", serveOrReverse(""))
|
||||
router.GET("/src/*_", serveOrReverse(""))
|
||||
}
|
||||
}
|
||||
|
239
surveys.go
239
surveys.go
@ -1,77 +1,142 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
_score_cache = map[int64]map[int64]*float64{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/surveys", apiAuthHandler(
|
||||
func(u *User, _ httprouter.Params, _ []byte) HTTPResponse {
|
||||
if u == nil {
|
||||
return formatApiResponse(getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo)))
|
||||
} else if u.IsAdmin {
|
||||
return formatApiResponse(getSurveys("ORDER BY promo DESC, start_availability ASC"))
|
||||
} else {
|
||||
surveys, err := getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND promo = %d ORDER BY start_availability ASC", u.Promo))
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
}
|
||||
func declareAPISurveysRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/surveys", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
var response []Survey
|
||||
var response []*Survey
|
||||
var err error
|
||||
if u == nil {
|
||||
response, err = getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo))
|
||||
} else if u.IsAdmin {
|
||||
response, err = getSurveys("ORDER BY promo DESC, start_availability ASC")
|
||||
} else {
|
||||
var surveys []*Survey
|
||||
surveys, err = getSurveys(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND promo = %d ORDER BY start_availability ASC", u.Promo))
|
||||
if err == nil {
|
||||
for _, s := range surveys {
|
||||
if s.Group == "" || strings.Contains(u.Groups, ","+s.Group+",") {
|
||||
s.Group = ""
|
||||
response = append(response, s)
|
||||
}
|
||||
}
|
||||
|
||||
return formatApiResponse(response, nil)
|
||||
}
|
||||
}))
|
||||
router.POST("/api/surveys", apiHandler(func(_ httprouter.Params, body []byte) HTTPResponse {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Impossible de lister les questionnaires. Veuillez réessayer dans quelques instants"})
|
||||
log.Printf("Unable to list surveys: %s", err.Error())
|
||||
} else {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
})
|
||||
|
||||
surveysRoutes := router.Group("/surveys/:sid")
|
||||
surveysRoutes.Use(surveyHandler)
|
||||
|
||||
surveysRoutes.GET("", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
if u == nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Veuillez vous connecter pour accéder à cette page."})
|
||||
return
|
||||
}
|
||||
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
if (s.Promo == u.Promo && (s.Group == "" || strings.Contains(u.Groups, ","+s.Group+",") && s.Shown)) || u.IsAdmin {
|
||||
c.JSON(http.StatusOK, s)
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible"})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAuthSurveysRoutes(router *gin.RouterGroup) {
|
||||
surveysRoutes := router.Group("/surveys/:sid")
|
||||
surveysRoutes.Use(surveyHandler)
|
||||
|
||||
surveysRoutes.GET("/score", func(c *gin.Context) {
|
||||
var u *User
|
||||
if user, ok := c.Get("user"); ok {
|
||||
u = user.(*User)
|
||||
} else {
|
||||
u = c.MustGet("LoggedUser").(*User)
|
||||
}
|
||||
s := c.MustGet("survey").(*Survey)
|
||||
|
||||
if (s.Promo == u.Promo && s.Shown) || (u != nil && u.IsAdmin) {
|
||||
score, err := s.GetScore(u)
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetScore(uid=%d;sid=%d): %s", u.Id, s.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve score."})
|
||||
return
|
||||
}
|
||||
|
||||
if score == nil {
|
||||
c.JSON(http.StatusOK, map[string]string{"score": "N/A"})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, map[string]float64{"score": *score})
|
||||
}
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible"})
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
declareAPIAuthAsksRoutes(surveysRoutes)
|
||||
declareAPIAuthDirectRoutes(surveysRoutes)
|
||||
declareAPIAuthGradesRoutes(surveysRoutes)
|
||||
declareAPIAuthQuestionsRoutes(surveysRoutes)
|
||||
declareAPIAuthResponsesRoutes(surveysRoutes)
|
||||
}
|
||||
|
||||
func declareAPIAdminSurveysRoutes(router *gin.RouterGroup) {
|
||||
router.POST("/surveys", func(c *gin.Context) {
|
||||
var new Survey
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if new.Promo == 0 {
|
||||
new.Promo = currentPromo
|
||||
}
|
||||
|
||||
return formatApiResponse(NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability))
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
if u == nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusUnauthorized,
|
||||
err: errors.New("Veuillez vous connecter pour accéder à cette page."),
|
||||
}
|
||||
} else if (s.Promo == u.Promo && (s.Group == "" || strings.Contains(u.Groups, ","+s.Group+",") && s.Shown)) || u.IsAdmin {
|
||||
return APIResponse{s}
|
||||
if s, err := NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability); err != nil {
|
||||
log.Println("Unable to NewSurvey:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey creation: %s", err.Error())})
|
||||
return
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not accessible"),
|
||||
c.JSON(http.StatusOK, s)
|
||||
}
|
||||
}
|
||||
})))
|
||||
router.PUT("/api/surveys/:sid", apiHandler(surveyHandler(func(current Survey, body []byte) HTTPResponse {
|
||||
})
|
||||
|
||||
surveysRoutes := router.Group("/surveys/:sid")
|
||||
surveysRoutes.Use(surveyHandler)
|
||||
|
||||
surveysRoutes.PUT("", func(c *gin.Context) {
|
||||
current := c.MustGet("survey").(*Survey)
|
||||
|
||||
var new Survey
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
@ -91,72 +156,41 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/surveys/:sid", apiHandler(surveyHandler(
|
||||
func(s Survey, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(s.Delete())
|
||||
}), adminRestricted))
|
||||
router.GET("/api/surveys/:sid/score", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
if (s.Promo == u.Promo && s.Shown) || (u != nil && u.IsAdmin) {
|
||||
if score, err := s.GetScore(u); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if score == nil {
|
||||
return APIResponse{map[string]string{"score": "N/A"}}
|
||||
if survey, err := new.Update(); err != nil {
|
||||
log.Println("Unable to Update survey:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey updation: %s", err.Error())})
|
||||
return
|
||||
} else {
|
||||
return APIResponse{map[string]float64{"score": *score}}
|
||||
c.JSON(http.StatusOK, survey)
|
||||
}
|
||||
})
|
||||
surveysRoutes.DELETE("", func(c *gin.Context) {
|
||||
survey := c.MustGet("survey").(*Survey)
|
||||
|
||||
if _, err := survey.Delete(); err != nil {
|
||||
log.Println("Unable to Delete survey:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey deletion: %s", err.Error())})
|
||||
return
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not accessible"),
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
||||
}
|
||||
}), loggedUser))
|
||||
router.GET("/api/users/:uid/surveys/:sid/score", apiAuthHandler(func(uauth *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
return surveyAuthHandler(func(s Survey, uauth *User, _ []byte) HTTPResponse {
|
||||
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||
if uauth != nil && ((s.Promo == u.Promo && s.Shown && u.Id == uauth.Id) || uauth.IsAdmin) {
|
||||
if score, err := s.GetScore(&u); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if score == nil {
|
||||
return APIResponse{map[string]string{"score": "N/A"}}
|
||||
} else {
|
||||
return APIResponse{map[string]float64{"score": *score}}
|
||||
}
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not accessible"),
|
||||
}
|
||||
}
|
||||
})(ps, body)
|
||||
})(uauth, ps, body)
|
||||
}, loggedUser))
|
||||
})
|
||||
|
||||
declareAPIAdminAsksRoutes(surveysRoutes)
|
||||
declareAPIAdminDirectRoutes(surveysRoutes)
|
||||
declareAPIAdminQuestionsRoutes(surveysRoutes)
|
||||
}
|
||||
|
||||
func surveyHandler(f func(Survey, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func surveyHandler(c *gin.Context) {
|
||||
if sid, err := strconv.Atoi(string(c.Param("sid"))); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad survey identifier."})
|
||||
return
|
||||
} else if survey, err := getSurvey(sid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Survey not found."})
|
||||
return
|
||||
} else {
|
||||
return f(survey, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func surveyAuthHandler(f func(Survey, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse {
|
||||
return func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if survey, err := getSurvey(sid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(survey, u, body)
|
||||
}
|
||||
c.Set("survey", survey)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,7 +206,7 @@ type Survey struct {
|
||||
EndAvailability time.Time `json:"end_availability"`
|
||||
}
|
||||
|
||||
func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error) {
|
||||
func getSurveys(cnd string, param ...interface{}) (surveys []*Survey, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_survey, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -183,7 +217,7 @@ func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error)
|
||||
if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
|
||||
return
|
||||
}
|
||||
surveys = append(surveys, s)
|
||||
surveys = append(surveys, &s)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -193,7 +227,8 @@ func getSurveys(cnd string, param ...interface{}) (surveys []Survey, err error)
|
||||
}
|
||||
}
|
||||
|
||||
func getSurvey(id int) (s Survey, err error) {
|
||||
func getSurvey(id int) (s *Survey, err error) {
|
||||
s = new(Survey)
|
||||
err = DBQueryRow("SELECT id_survey, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability)
|
||||
return
|
||||
}
|
||||
|
151
users.go
151
users.go
@ -1,49 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var currentPromo uint = 0
|
||||
|
||||
func init() {
|
||||
router.GET("/api/promos", apiHandler(
|
||||
func(httprouter.Params, []byte) HTTPResponse {
|
||||
return formatApiResponse(getPromos())
|
||||
}, adminRestricted))
|
||||
router.GET("/api/users", apiHandler(
|
||||
func(httprouter.Params, []byte) HTTPResponse {
|
||||
return formatApiResponse(getUsers())
|
||||
}, adminRestricted))
|
||||
router.GET("/api/users/:uid", apiHandler(userHandler(
|
||||
func(u User, _ []byte) HTTPResponse {
|
||||
return APIResponse{u}
|
||||
}), loggedUser))
|
||||
router.PUT("/api/users/:uid", apiHandler(userHandler(updateUser), adminRestricted))
|
||||
router.DELETE("/api/users/:uid", apiHandler(userHandler(
|
||||
func(u User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(u.Delete())
|
||||
}), adminRestricted))
|
||||
func declareAPIAuthUsersRoutes(router *gin.RouterGroup) {
|
||||
usersRoutes := router.Group("/users/:uid")
|
||||
usersRoutes.Use(userHandler)
|
||||
usersRoutes.Use(sameUserMiddleware)
|
||||
|
||||
usersRoutes.GET("", func(c *gin.Context) {
|
||||
u := c.MustGet("user").(*User)
|
||||
|
||||
c.JSON(http.StatusOK, u)
|
||||
})
|
||||
|
||||
declareAPIAuthSurveysRoutes(usersRoutes)
|
||||
}
|
||||
|
||||
func userHandler(f func(User, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
if uid, err := strconv.Atoi(string(ps.ByName("uid"))); err != nil {
|
||||
if user, err := getUserByLogin(ps.ByName("uid")); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/promos", func(c *gin.Context) {
|
||||
promos, err := getPromos()
|
||||
if err != nil {
|
||||
log.Println("Unable to getPromos:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve promotions. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, promos)
|
||||
})
|
||||
|
||||
router.GET("/users", func(c *gin.Context) {
|
||||
users, err := getUsers()
|
||||
if err != nil {
|
||||
log.Println("Unable to getUsers:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve users. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, users)
|
||||
})
|
||||
|
||||
usersRoutes := router.Group("/users/:uid")
|
||||
usersRoutes.Use(userHandler)
|
||||
|
||||
usersRoutes.PUT("", updateUser)
|
||||
usersRoutes.DELETE("", func(c *gin.Context) {
|
||||
u := c.MustGet("user").(*User)
|
||||
|
||||
if _, err := u.Delete(); err != nil {
|
||||
log.Println("Unable to Delete user:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the user. Please try again later."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nil)
|
||||
})
|
||||
|
||||
declareAPIAdminUserCorrectionsRoutes(usersRoutes)
|
||||
declareAPIAdminUserQuestionsRoutes(usersRoutes)
|
||||
}
|
||||
|
||||
func userHandler(c *gin.Context) {
|
||||
var user *User
|
||||
|
||||
uid, err := strconv.Atoi(string(c.Param("uid")))
|
||||
if err != nil {
|
||||
user, err = getUserByLogin(c.Param("uid"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad user identifier."})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return f(user, body)
|
||||
}
|
||||
} else if user, err := getUser(uid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(user, body)
|
||||
user, err = getUser(uid)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "User not found."})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Set("user", user)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func sameUserMiddleware(c *gin.Context) {
|
||||
user := c.MustGet("user").(*User)
|
||||
loggeduser := c.MustGet("LoggedUser").(*User)
|
||||
|
||||
if user.Id != loggeduser.Id && !loggeduser.IsAdmin {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission denied."})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
type User struct {
|
||||
@ -100,12 +157,14 @@ func getPromos() (promos []uint, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func getUser(id int) (u User, err error) {
|
||||
func getUser(id int) (u *User, err error) {
|
||||
u = new(User)
|
||||
err = DBQueryRow("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE id_user=?", id).Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin)
|
||||
return
|
||||
}
|
||||
|
||||
func getUserByLogin(login string) (u User, err error) {
|
||||
func getUserByLogin(login string) (u *User, err error) {
|
||||
u = new(User)
|
||||
err = DBQueryRow("SELECT id_user, login, email, firstname, lastname, time, promo, groups, is_admin FROM users WHERE login=?", login).Scan(&u.Id, &u.Login, &u.Email, &u.Firstname, &u.Lastname, &u.Time, &u.Promo, &u.Groups, &u.IsAdmin)
|
||||
return
|
||||
}
|
||||
@ -116,14 +175,14 @@ func userExists(login string) bool {
|
||||
return err == nil && z == 1
|
||||
}
|
||||
|
||||
func NewUser(login string, email string, firstname string, lastname string, groups string) (User, error) {
|
||||
func NewUser(login string, email string, firstname string, lastname string, groups string) (*User, error) {
|
||||
t := time.Now()
|
||||
if res, err := DBExec("INSERT INTO users (login, email, firstname, lastname, time, promo, groups) VALUES (?, ?, ?, ?, ?, ?, ?)", login, email, firstname, lastname, t, currentPromo, groups); err != nil {
|
||||
return User{}, err
|
||||
return nil, err
|
||||
} else if sid, err := res.LastInsertId(); err != nil {
|
||||
return User{}, err
|
||||
return nil, err
|
||||
} else {
|
||||
return User{sid, login, email, firstname, lastname, t, currentPromo, groups, false}, nil
|
||||
return &User{sid, login, email, firstname, lastname, t, currentPromo, groups, false}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,10 +225,13 @@ func ClearUsers() (int64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateUser(current User, body []byte) HTTPResponse {
|
||||
func updateUser(c *gin.Context) {
|
||||
current := c.MustGet("user").(*User)
|
||||
|
||||
var new User
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
current.Login = new.Login
|
||||
@ -179,5 +241,12 @@ func updateUser(current User, body []byte) HTTPResponse {
|
||||
current.Time = new.Time
|
||||
current.Promo = new.Promo
|
||||
current.Groups = new.Groups
|
||||
return formatApiResponse(current.Update())
|
||||
|
||||
if u, err := current.Update(); err != nil {
|
||||
log.Println("Unable to Update user:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update the given user. Please try again later."})
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusOK, u)
|
||||
}
|
||||
}
|
||||
|
270
works.go
270
works.go
@ -2,158 +2,233 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/api/works", apiAuthHandler(
|
||||
func(u *User, _ httprouter.Params, _ []byte) HTTPResponse {
|
||||
if u == nil {
|
||||
return formatApiResponse(getWorks(fmt.Sprintf("WHERE shown = TRUE AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo)))
|
||||
} else if u.IsAdmin {
|
||||
return formatApiResponse(getWorks("ORDER BY promo DESC, start_availability ASC"))
|
||||
} else {
|
||||
works, err := getWorks(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo))
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func declareAPIWorksRoutes(router *gin.RouterGroup) {
|
||||
router.GET("/works", func(c *gin.Context) {
|
||||
var u *User
|
||||
if user, ok := c.Get("LoggedUser"); ok {
|
||||
u = user.(*User)
|
||||
}
|
||||
|
||||
var response []Work
|
||||
var works []*Work
|
||||
var err error
|
||||
if u == nil {
|
||||
works, err = getWorks(fmt.Sprintf("WHERE shown = TRUE AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo))
|
||||
} else if u.IsAdmin {
|
||||
works, err = getWorks("ORDER BY promo DESC, start_availability ASC")
|
||||
} else {
|
||||
works, err = getWorks(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println("Unable to getWorks:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Impossible de récupérer la liste des travaux. Veuillez réessayer dans quelques instants."})
|
||||
return
|
||||
}
|
||||
|
||||
var response []*Work
|
||||
if u == nil || u.IsAdmin {
|
||||
response = works
|
||||
} else {
|
||||
for _, w := range works {
|
||||
if w.Group == "" || strings.Contains(u.Groups, ","+w.Group+",") {
|
||||
w.Group = ""
|
||||
response = append(response, w)
|
||||
}
|
||||
}
|
||||
|
||||
return formatApiResponse(response, nil)
|
||||
}
|
||||
}))
|
||||
router.GET("/api/all_works", apiAuthHandler(
|
||||
func(u *User, _ httprouter.Params, _ []byte) HTTPResponse {
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
})
|
||||
router.GET("/all_works", func(c *gin.Context) {
|
||||
var u *User
|
||||
if user, ok := c.Get("LoggedUser"); ok {
|
||||
u = user.(*User)
|
||||
}
|
||||
|
||||
var works []*OneWork
|
||||
var err error
|
||||
if u == nil {
|
||||
return formatApiResponse(allWorks(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC, end_availability ASC", currentPromo)))
|
||||
works, err = allWorks(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC, end_availability ASC", currentPromo))
|
||||
} else if u.IsAdmin {
|
||||
return formatApiResponse(allWorks("ORDER BY promo DESC, start_availability ASC"))
|
||||
works, err = allWorks("ORDER BY promo DESC, start_availability ASC")
|
||||
} else {
|
||||
works, err := allWorks(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND promo = %d ORDER BY start_availability ASC, end_availability ASC", u.Promo))
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
works, err = allWorks(fmt.Sprintf("WHERE (shown = TRUE OR direct IS NOT NULL) AND promo = %d ORDER BY start_availability ASC, end_availability ASC", u.Promo))
|
||||
}
|
||||
|
||||
var response []OneWork
|
||||
if err != nil {
|
||||
log.Println("Unable to getWorks:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Impossible de récupérer la liste des travaux. Veuillez réessayer dans quelques instants."})
|
||||
return
|
||||
}
|
||||
|
||||
var response []*OneWork
|
||||
if u == nil || u.IsAdmin {
|
||||
response = works
|
||||
} else {
|
||||
for _, w := range works {
|
||||
if w.Group == "" || strings.Contains(u.Groups, ","+w.Group+",") {
|
||||
w.Group = ""
|
||||
response = append(response, w)
|
||||
}
|
||||
}
|
||||
|
||||
return formatApiResponse(response, nil)
|
||||
}
|
||||
}))
|
||||
router.POST("/api/works", apiHandler(func(_ httprouter.Params, body []byte) HTTPResponse {
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAdminWorksRoutes(router *gin.RouterGroup) {
|
||||
router.POST("/works", func(c *gin.Context) {
|
||||
var new Work
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if new.Promo == 0 {
|
||||
new.Promo = currentPromo
|
||||
}
|
||||
|
||||
return formatApiResponse(NewWork(new.Title, new.Promo, new.Group, new.Shown, new.SubmissionURL, new.StartAvailability, new.EndAvailability))
|
||||
}, adminRestricted))
|
||||
router.GET("/api/works/:wid", apiAuthHandler(workAuthHandler(
|
||||
func(w Work, u *User, _ []byte) HTTPResponse {
|
||||
if u.IsAdmin {
|
||||
return APIResponse{w}
|
||||
} else if w.Shown && w.StartAvailability.Before(time.Now()) && (w.Group == "" || strings.Contains(u.Groups, ","+w.Group+",")) {
|
||||
return APIResponse{w}
|
||||
} else {
|
||||
return APIErrorResponse{status: http.StatusForbidden, err: fmt.Errorf("Permission denied")}
|
||||
work, err := NewWork(new.Title, new.Promo, new.Group, new.Shown, new.SubmissionURL, new.StartAvailability, new.EndAvailability)
|
||||
if err != nil {
|
||||
log.Println("Unable to NewWork:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during work creation"})
|
||||
return
|
||||
}
|
||||
}), loggedUser))
|
||||
router.PUT("/api/works/:wid", apiHandler(workHandler(func(current Work, body []byte) HTTPResponse {
|
||||
|
||||
c.JSON(http.StatusOK, work)
|
||||
})
|
||||
|
||||
worksRoutes := router.Group("/works/:wid")
|
||||
worksRoutes.Use(workHandler)
|
||||
|
||||
worksRoutes.PUT("", func(c *gin.Context) {
|
||||
current := c.MustGet("work").(*Work)
|
||||
|
||||
var new Work
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&new); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
|
||||
return formatApiResponse(new.Update())
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/works/:wid", apiHandler(workHandler(
|
||||
func(w Work, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(w.Delete())
|
||||
}), adminRestricted))
|
||||
|
||||
// Grades related to works
|
||||
router.GET("/api/works/:wid/grades", apiHandler(workHandler(
|
||||
func(w Work, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(w.GetGrades(""))
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/works/:wid/grades", apiHandler(workHandler(
|
||||
func(w Work, body []byte) HTTPResponse {
|
||||
_, err := w.DeleteGrades()
|
||||
work, err := new.Update()
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Println("Unable to Update work:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during work update."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, work)
|
||||
})
|
||||
worksRoutes.DELETE("", func(c *gin.Context) {
|
||||
w := c.MustGet("work").(*Work)
|
||||
|
||||
_, err := w.Delete()
|
||||
if err != nil {
|
||||
log.Println("Unable to Delte work:", err)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during work deletion."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nil)
|
||||
})
|
||||
|
||||
// Grades related to works
|
||||
worksRoutes.GET("/grades", func(c *gin.Context) {
|
||||
w := c.MustGet("work").(*Work)
|
||||
|
||||
grades, err := w.GetGrades("")
|
||||
if err != nil {
|
||||
log.Printf("Unable to GetGrades(wid=%d): %s", w.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during grades retrieval."})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, grades)
|
||||
})
|
||||
worksRoutes.PUT("/grades", func(c *gin.Context) {
|
||||
w := c.MustGet("work").(*Work)
|
||||
|
||||
var grades []WorkGrade
|
||||
if err := json.Unmarshal(body, &grades); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
if err := c.ShouldBindJSON(&grades); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
_, err := w.DeleteGrades()
|
||||
if err != nil {
|
||||
log.Printf("Unable to DeleteGrades(wid=%d): %s", w.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during grades deletion."})
|
||||
return
|
||||
}
|
||||
|
||||
err = w.AddGrades(grades)
|
||||
if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Printf("Unable to AddGrades(wid=%d): %s", w.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during grades erasing."})
|
||||
return
|
||||
}
|
||||
|
||||
return APIResponse{true}
|
||||
}), adminRestricted))
|
||||
router.GET("/api/works/:wid/score", apiAuthHandler(workAuthHandler(
|
||||
func(w Work, u *User, _ []byte) HTTPResponse {
|
||||
if g, err := u.GetMyWorkGrade(&w); err != nil && errors.Is(err, sql.ErrNoRows) {
|
||||
return APIErrorResponse{status: http.StatusNotFound, err: fmt.Errorf("Aucune note n'a été attribuée pour ce travail. Avez-vous rendu ce travail ?")}
|
||||
c.JSON(http.StatusOK, true)
|
||||
})
|
||||
}
|
||||
|
||||
func declareAPIAuthWorksRoutes(router *gin.RouterGroup) {
|
||||
worksRoutes := router.Group("/works/:wid")
|
||||
worksRoutes.Use(workHandler)
|
||||
|
||||
worksRoutes.GET("", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
w := c.MustGet("work").(*Work)
|
||||
|
||||
if u.IsAdmin {
|
||||
c.JSON(http.StatusOK, w)
|
||||
} else if w.Shown && w.StartAvailability.Before(time.Now()) && (w.Group == "" || strings.Contains(u.Groups, ","+w.Group+",")) {
|
||||
c.JSON(http.StatusOK, w)
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission denied"})
|
||||
}
|
||||
})
|
||||
|
||||
// Grades related to works
|
||||
worksRoutes.GET("/score", func(c *gin.Context) {
|
||||
u := c.MustGet("LoggedUser").(*User)
|
||||
w := c.MustGet("work").(*Work)
|
||||
|
||||
if g, err := u.GetMyWorkGrade(w); err != nil && errors.Is(err, sql.ErrNoRows) {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Aucune note n'a été attribuée pour ce travail. Avez-vous rendu ce travail ?"})
|
||||
} else if err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
log.Printf("Unable to GetMyWorkGrade(uid=%d;wid=%d): %s", u.Id, w.Id, err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during grade calculation."})
|
||||
} else {
|
||||
return APIResponse{g}
|
||||
c.JSON(http.StatusOK, g)
|
||||
}
|
||||
}), loggedUser))
|
||||
})
|
||||
}
|
||||
|
||||
func workHandler(f func(Work, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||
if wid, err := strconv.Atoi(string(ps.ByName("wid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
func workHandler(c *gin.Context) {
|
||||
if wid, err := strconv.Atoi(string(c.Param("wid"))); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad work identifier."})
|
||||
return
|
||||
} else if work, err := getWork(wid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Work not found."})
|
||||
return
|
||||
} else {
|
||||
return f(work, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func workAuthHandler(f func(Work, *User, []byte) HTTPResponse) func(*User, httprouter.Params, []byte) HTTPResponse {
|
||||
return func(u *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||
if wid, err := strconv.Atoi(string(ps.ByName("wid"))); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else if work, err := getWork(wid); err != nil {
|
||||
return APIErrorResponse{err: err}
|
||||
} else {
|
||||
return f(work, u, body)
|
||||
}
|
||||
c.Set("work", work)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +246,7 @@ type OneWork struct {
|
||||
EndAvailability time.Time `json:"end_availability"`
|
||||
}
|
||||
|
||||
func allWorks(cnd string, param ...interface{}) (items []OneWork, err error) {
|
||||
func allWorks(cnd string, param ...interface{}) (items []*OneWork, err error) {
|
||||
if rows, errr := DBQuery("SELECT kind, id, title, promo, grp, shown, direct, submission_url, corrected, start_availability, end_availability FROM all_works "+cnd, param...); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -182,7 +257,7 @@ func allWorks(cnd string, param ...interface{}) (items []OneWork, err error) {
|
||||
if err = rows.Scan(&w.Kind, &w.Id, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.Direct, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability); err != nil {
|
||||
return
|
||||
}
|
||||
items = append(items, w)
|
||||
items = append(items, &w)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -204,7 +279,7 @@ type Work struct {
|
||||
EndAvailability time.Time `json:"end_availability"`
|
||||
}
|
||||
|
||||
func getWorks(cnd string, param ...interface{}) (items []Work, err error) {
|
||||
func getWorks(cnd string, param ...interface{}) (items []*Work, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_work, title, promo, grp, shown, submission_url, corrected, start_availability, end_availability FROM works "+cnd, param...); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -215,7 +290,7 @@ func getWorks(cnd string, param ...interface{}) (items []Work, err error) {
|
||||
if err = rows.Scan(&w.Id, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability); err != nil {
|
||||
return
|
||||
}
|
||||
items = append(items, w)
|
||||
items = append(items, &w)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
@ -225,7 +300,8 @@ func getWorks(cnd string, param ...interface{}) (items []Work, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func getWork(id int) (w Work, err error) {
|
||||
func getWork(id int) (w *Work, err error) {
|
||||
w = new(Work)
|
||||
err = DBQueryRow("SELECT id_work, title, promo, grp, shown, submission_url, corrected, start_availability, end_availability FROM works WHERE id_work=?", id).Scan(&w.Id, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability)
|
||||
return
|
||||
}
|
||||
|
Reference in New Issue
Block a user