Add categories to sort/filter works/surveys
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
b88d284859
commit
4c76dd9728
2
api.go
2
api.go
@ -13,6 +13,7 @@ func declareAPIRoutes(router *gin.Engine) {
|
|||||||
apiRoutes.Use(authMiddleware())
|
apiRoutes.Use(authMiddleware())
|
||||||
|
|
||||||
declareAPIAuthRoutes(apiRoutes)
|
declareAPIAuthRoutes(apiRoutes)
|
||||||
|
declareAPICategoriesRoutes(apiRoutes)
|
||||||
declareAPISurveysRoutes(apiRoutes)
|
declareAPISurveysRoutes(apiRoutes)
|
||||||
declareAPIWorksRoutes(apiRoutes)
|
declareAPIWorksRoutes(apiRoutes)
|
||||||
declareAPIKeysRoutes(apiRoutes)
|
declareAPIKeysRoutes(apiRoutes)
|
||||||
@ -50,6 +51,7 @@ func declareAPIRoutes(router *gin.Engine) {
|
|||||||
|
|
||||||
declareAPIAdminAuthRoutes(apiAdminRoutes)
|
declareAPIAdminAuthRoutes(apiAdminRoutes)
|
||||||
declareAPIAdminAsksRoutes(apiAdminRoutes)
|
declareAPIAdminAsksRoutes(apiAdminRoutes)
|
||||||
|
declareAPIAdminCategoriesRoutes(apiRoutes)
|
||||||
declareAPIAuthGradesRoutes(apiAdminRoutes)
|
declareAPIAuthGradesRoutes(apiAdminRoutes)
|
||||||
declareAPIAdminHelpRoutes(apiAdminRoutes)
|
declareAPIAdminHelpRoutes(apiAdminRoutes)
|
||||||
declareAPIAdminQuestionsRoutes(apiAdminRoutes)
|
declareAPIAdminQuestionsRoutes(apiAdminRoutes)
|
||||||
|
168
categories.go
Normal file
168
categories.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func declareAPICategoriesRoutes(router *gin.RouterGroup) {
|
||||||
|
categoriesRoutes := router.Group("/categories/:cid")
|
||||||
|
categoriesRoutes.Use(categoryHandler)
|
||||||
|
|
||||||
|
categoriesRoutes.GET("", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("category").(*Category))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func declareAPIAdminCategoriesRoutes(router *gin.RouterGroup) {
|
||||||
|
router.GET("categories", func(c *gin.Context) {
|
||||||
|
categories, err := getCategories()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to getCategories:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve categories. Please try again later."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, categories)
|
||||||
|
})
|
||||||
|
router.POST("categories", func(c *gin.Context) {
|
||||||
|
var new Category
|
||||||
|
if err := c.ShouldBindJSON(&new); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if new.Promo == 0 {
|
||||||
|
new.Promo = currentPromo
|
||||||
|
}
|
||||||
|
|
||||||
|
if cat, err := NewCategory(new.Label, new.Promo, new.Expand); err != nil {
|
||||||
|
log.Println("Unable to NewCategory:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during category creation: %s", err.Error())})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, cat)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
categoriesRoutes := router.Group("/categories/:cid")
|
||||||
|
categoriesRoutes.Use(categoryHandler)
|
||||||
|
|
||||||
|
categoriesRoutes.PUT("", func(c *gin.Context) {
|
||||||
|
current := c.MustGet("category").(*Category)
|
||||||
|
|
||||||
|
var new Category
|
||||||
|
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 {
|
||||||
|
log.Println("Unable to Update category:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update the given category. Please try again later."})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, new)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
categoriesRoutes.DELETE("", func(c *gin.Context) {
|
||||||
|
current := c.MustGet("category").(*Category)
|
||||||
|
|
||||||
|
if _, err := current.Delete(); err != nil {
|
||||||
|
log.Println("Unable to Delete category:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the given category. Please try again later."})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func categoryHandler(c *gin.Context) {
|
||||||
|
var category *Category
|
||||||
|
|
||||||
|
cid, err := strconv.Atoi(string(c.Param("cid")))
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad category identifier."})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
category, err = getCategory(cid)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Category not found."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("category", category)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Promo uint `json:"promo"`
|
||||||
|
Expand bool `json:"expand,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCategories() (categories []Category, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_category, label, promo, expand FROM categories ORDER BY promo DESC, expand DESC, id_category DESC"); errr != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var c Category
|
||||||
|
if err = rows.Scan(&c.Id, &c.Label, &c.Promo, &c.Expand); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
categories = append(categories, c)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCategory(id int) (c *Category, err error) {
|
||||||
|
c = new(Category)
|
||||||
|
err = DBQueryRow("SELECT id_category, label, promo, expand FROM categories WHERE id_category=?", id).Scan(&c.Id, &c.Label, &c.Promo, &c.Expand)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCategory(label string, promo uint, expand bool) (*Category, error) {
|
||||||
|
if res, err := DBExec("INSERT INTO categories (label, promo, expand) VALUES (?, ?, ?)", label, promo, expand); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if cid, err := res.LastInsertId(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Category{cid, label, promo, expand}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) Update() (int64, error) {
|
||||||
|
if res, err := DBExec("UPDATE categories SET label = ?, promo = ?, expand = ? WHERE id_category = ?", c.Label, c.Promo, c.Expand, c.Id); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Category) Delete() (int64, error) {
|
||||||
|
if res, err := DBExec("DELETE FROM categories WHERE id_category = ?", c.Id); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
20
db.go
20
db.go
@ -93,6 +93,7 @@ CREATE TABLE IF NOT EXISTS user_keys(
|
|||||||
if _, err := db.Exec(`
|
if _, err := db.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS surveys(
|
CREATE TABLE IF NOT EXISTS surveys(
|
||||||
id_survey INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
id_survey INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
id_category INTEGER NOT NULL,
|
||||||
title VARCHAR(255),
|
title VARCHAR(255),
|
||||||
promo MEDIUMINT NOT NULL,
|
promo MEDIUMINT NOT NULL,
|
||||||
grp VARCHAR(255) NOT NULL,
|
grp VARCHAR(255) NOT NULL,
|
||||||
@ -100,7 +101,8 @@ CREATE TABLE IF NOT EXISTS surveys(
|
|||||||
direct INTEGER DEFAULT NULL,
|
direct INTEGER DEFAULT NULL,
|
||||||
corrected BOOLEAN NOT NULL DEFAULT FALSE,
|
corrected BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY(id_category) REFERENCES categories(id_category)
|
||||||
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -200,6 +202,7 @@ CREATE TABLE IF NOT EXISTS user_need_help(
|
|||||||
if _, err := db.Exec(`
|
if _, err := db.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS works(
|
CREATE TABLE IF NOT EXISTS works(
|
||||||
id_work INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
id_work INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
id_category INTEGER NOT NULL,
|
||||||
title VARCHAR(255),
|
title VARCHAR(255),
|
||||||
promo MEDIUMINT NOT NULL,
|
promo MEDIUMINT NOT NULL,
|
||||||
grp VARCHAR(255) NOT NULL,
|
grp VARCHAR(255) NOT NULL,
|
||||||
@ -209,7 +212,8 @@ CREATE TABLE IF NOT EXISTS works(
|
|||||||
submission_URL VARCHAR(255) NULL,
|
submission_URL VARCHAR(255) NULL,
|
||||||
corrected BOOLEAN NOT NULL DEFAULT FALSE,
|
corrected BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY(id_category) REFERENCES categories(id_category)
|
||||||
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -241,6 +245,16 @@ CREATE TABLE IF NOT EXISTS user_work_repositories(
|
|||||||
FOREIGN KEY(id_work) REFERENCES works(id_work),
|
FOREIGN KEY(id_work) REFERENCES works(id_work),
|
||||||
UNIQUE one_repo_per_work (id_user, id_work)
|
UNIQUE one_repo_per_work (id_user, id_work)
|
||||||
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS categories(
|
||||||
|
id_category INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
label VARCHAR(255) NOT NULL,
|
||||||
|
promo MEDIUMINT NOT NULL,
|
||||||
|
expand BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -250,7 +264,7 @@ CREATE VIEW IF NOT EXISTS student_scores AS SELECT T.id_user, T.id_survey, Q.id_
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := db.Exec(`
|
if _, err := db.Exec(`
|
||||||
CREATE VIEW IF NOT EXISTS all_works AS SELECT "work" AS kind, id_work AS id, title, promo, grp, shown, NULL AS direct, submission_url, corrected, start_availability, end_availability FROM works UNION SELECT "survey" AS kind, id_survey AS id, title, promo, grp, shown, direct, NULL AS submission_url, corrected, start_availability, end_availability FROM surveys;
|
CREATE VIEW IF NOT EXISTS all_works AS SELECT "work" AS kind, id_work AS id, id_category, title, promo, grp, shown, NULL AS direct, submission_url, corrected, start_availability, end_availability FROM works UNION SELECT "survey" AS kind, id_survey AS id, id_category, title, promo, grp, shown, direct, NULL AS submission_url, corrected, start_availability, end_availability FROM surveys;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,8 @@ func declareStaticRoutes(router *gin.Engine) {
|
|||||||
router.GET("/_app/*_", serveOrReverse(""))
|
router.GET("/_app/*_", serveOrReverse(""))
|
||||||
router.GET("/auth/", serveOrReverse("/"))
|
router.GET("/auth/", serveOrReverse("/"))
|
||||||
router.GET("/bug-bounty", serveOrReverse("/"))
|
router.GET("/bug-bounty", serveOrReverse("/"))
|
||||||
|
router.GET("/categories", serveOrReverse("/"))
|
||||||
|
router.GET("/categories/*_", serveOrReverse("/"))
|
||||||
router.GET("/donnees-personnelles", serveOrReverse("/"))
|
router.GET("/donnees-personnelles", serveOrReverse("/"))
|
||||||
router.GET("/grades", serveOrReverse("/"))
|
router.GET("/grades", serveOrReverse("/"))
|
||||||
router.GET("/help", serveOrReverse("/"))
|
router.GET("/help", serveOrReverse("/"))
|
||||||
|
17
surveys.go
17
surveys.go
@ -146,7 +146,7 @@ func declareAPIAdminSurveysRoutes(router *gin.RouterGroup) {
|
|||||||
new.Promo = currentPromo
|
new.Promo = currentPromo
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, err := NewSurvey(new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability); err != nil {
|
if s, err := NewSurvey(new.IdCategory, new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability); err != nil {
|
||||||
log.Println("Unable to NewSurvey:", err)
|
log.Println("Unable to NewSurvey:", err)
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey creation: %s", err.Error())})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey creation: %s", err.Error())})
|
||||||
return
|
return
|
||||||
@ -240,6 +240,7 @@ func surveyUserAccessHandler(c *gin.Context) {
|
|||||||
|
|
||||||
type Survey struct {
|
type Survey struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
|
IdCategory int64 `json:"id_category"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Promo uint `json:"promo"`
|
Promo uint `json:"promo"`
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
@ -251,14 +252,14 @@ type Survey struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if rows, errr := DBQuery("SELECT id_survey, id_category, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys "+cnd, param...); errr != nil {
|
||||||
return nil, errr
|
return nil, errr
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var s Survey
|
var s Survey
|
||||||
if err = rows.Scan(&s.Id, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
|
if err = rows.Scan(&s.Id, &s.IdCategory, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
surveys = append(surveys, &s)
|
surveys = append(surveys, &s)
|
||||||
@ -280,7 +281,7 @@ func getSurvey(id int) (s *Survey, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s = new(Survey)
|
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)
|
err = DBQueryRow("SELECT id_survey, id_category, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.IdCategory, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability)
|
||||||
|
|
||||||
_surveys_cache_mutex.Lock()
|
_surveys_cache_mutex.Lock()
|
||||||
_surveys_cache[int64(id)] = s
|
_surveys_cache[int64(id)] = s
|
||||||
@ -288,13 +289,13 @@ func getSurvey(id int) (s *Survey, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSurvey(title string, promo uint, group string, shown bool, direct *int64, startAvailability time.Time, endAvailability time.Time) (*Survey, error) {
|
func NewSurvey(id_category int64, title string, promo uint, group string, shown bool, direct *int64, startAvailability time.Time, endAvailability time.Time) (*Survey, error) {
|
||||||
if res, err := DBExec("INSERT INTO surveys (title, promo, grp, shown, direct, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?)", title, promo, group, shown, direct, startAvailability, endAvailability); err != nil {
|
if res, err := DBExec("INSERT INTO surveys (id_category, title, promo, grp, shown, direct, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", id_category, title, promo, group, shown, direct, startAvailability, endAvailability); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if sid, err := res.LastInsertId(); err != nil {
|
} else if sid, err := res.LastInsertId(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return &Survey{sid, title, promo, group, shown, direct, false, startAvailability, endAvailability}, nil
|
return &Survey{sid, id_category, title, promo, group, shown, direct, false, startAvailability, endAvailability}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +353,7 @@ func (s Survey) GetScores() (scores map[int64]*float64, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Survey) Update() (*Survey, error) {
|
func (s *Survey) Update() (*Survey, error) {
|
||||||
if _, err := DBExec("UPDATE surveys SET title = ?, promo = ?, grp = ?, shown = ?, direct = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Promo, s.Group, s.Shown, s.Direct, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
|
if _, err := DBExec("UPDATE surveys SET id_category = ?, title = ?, promo = ?, grp = ?, shown = ?, direct = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.IdCategory, s.Title, s.Promo, s.Group, s.Shown, s.Direct, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
_surveys_cache_mutex.Lock()
|
_surveys_cache_mutex.Lock()
|
||||||
|
60
ui/src/lib/categories.js
Normal file
60
ui/src/lib/categories.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
export async function getCategories() {
|
||||||
|
let url = '/api/categories';
|
||||||
|
const res = await fetch(url, {headers: {'Accept': 'application/json'}})
|
||||||
|
if (res.status == 200) {
|
||||||
|
return (await res.json()).map((r) => new Category(r));
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Category {
|
||||||
|
constructor(res) {
|
||||||
|
if (res) {
|
||||||
|
this.update(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update({ id, label, promo, expand }) {
|
||||||
|
this.id = id;
|
||||||
|
this.label = label;
|
||||||
|
this.promo = promo;
|
||||||
|
this.expand = expand;
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
const res = await fetch(this.id?`api/categories/${this.id}`:'api/categories', {
|
||||||
|
method: this.id?'PUT':'POST',
|
||||||
|
headers: {'Accept': 'application/json'},
|
||||||
|
body: JSON.stringify(this),
|
||||||
|
});
|
||||||
|
if (res.status == 200) {
|
||||||
|
const data = await res.json()
|
||||||
|
this.update(data);
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
const res = await fetch(`api/categories/${this.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {'Accept': 'application/json'},
|
||||||
|
});
|
||||||
|
if (res.status == 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCategory(cid) {
|
||||||
|
const res = await fetch(`api/categories/${cid}`, {headers: {'Accept': 'application/json'}})
|
||||||
|
if (res.status == 200) {
|
||||||
|
return new Category(await res.json());
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
92
ui/src/lib/components/CategoryAdmin.svelte
Normal file
92
ui/src/lib/components/CategoryAdmin.svelte
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import DateTimeInput from './DateTimeInput.svelte';
|
||||||
|
import { ToastsStore } from '$lib/stores/toasts';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
export let category = null;
|
||||||
|
|
||||||
|
function saveCategory() {
|
||||||
|
category.save().then((response) => {
|
||||||
|
dispatch('saved', response);
|
||||||
|
}, (error) => {
|
||||||
|
ToastsStore.addErrorToast({
|
||||||
|
msg: error,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCategory() {
|
||||||
|
category.delete().then((response) => {
|
||||||
|
goto(`categories`);
|
||||||
|
}, (error) => {
|
||||||
|
ToastsStore.addErrorToast({
|
||||||
|
msg: error,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function duplicateCategory() {
|
||||||
|
category.duplicate().then((response) => {
|
||||||
|
goto(`categories/${response.id}`);
|
||||||
|
}).catch((error) => {
|
||||||
|
ToastsStore.addErrorToast({
|
||||||
|
msg: error,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={saveCategory}>
|
||||||
|
|
||||||
|
{#if category.id}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 text-sm-end">
|
||||||
|
<label for="cid" class="col-form-label col-form-label-sm">Identifiant de la catégorie</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" class="form-control-plaintext form-control-sm" id="cid" value={category.id}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 text-sm-end">
|
||||||
|
<label for="title" class="col-form-label col-form-label-sm">Titre de la catégorie</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input type="text" class="form-control form-control-sm" id="title" bind:value={category.label}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 text-sm-end">
|
||||||
|
<label for="promo" class="col-form-label col-form-label-sm">Promo</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 col-md-4 col-lg-2">
|
||||||
|
<input type="number" step="1" min="0" max="2068" class="form-control form-control-sm" id="promo" bind:value={category.promo}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-cols-3 mx-1 my-2">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="expand" bind:checked={category.expand}>
|
||||||
|
<label class="form-check-label" for="expand">
|
||||||
|
Étendre par défaut
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||||
|
{#if category.id}
|
||||||
|
<button type="button" class="btn btn-danger" on:click={deleteCategory}>Supprimer</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
@ -2,6 +2,7 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { getCategories } from '$lib/categories';
|
||||||
import { getQuestions } from '$lib/questions';
|
import { getQuestions } from '$lib/questions';
|
||||||
import DateTimeInput from './DateTimeInput.svelte';
|
import DateTimeInput from './DateTimeInput.svelte';
|
||||||
import { ToastsStore } from '$lib/stores/toasts';
|
import { ToastsStore } from '$lib/stores/toasts';
|
||||||
@ -67,6 +68,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 text-sm-end">
|
||||||
|
<label for="category" class="col-form-label col-form-label-sm">Catégorie</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 col-md-4 col-lg-2">
|
||||||
|
{#await getCategories() then categories}
|
||||||
|
<select id="category" class="form-select form-select-sm" bind:value={survey.id_category}>
|
||||||
|
{#each categories as category (category.id)}
|
||||||
|
<option value={category.id}>{category.label} {category.promo}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-3 text-sm-end">
|
<div class="col-sm-3 text-sm-end">
|
||||||
<label for="promo" class="col-form-label col-form-label-sm">Promo</label>
|
<label for="promo" class="col-form-label col-form-label-sm">Promo</label>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import DateFormat from '$lib/components/DateFormat.svelte';
|
import DateFormat from '$lib/components/DateFormat.svelte';
|
||||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||||
import SubmissionStatus from '$lib/components/SubmissionStatus.svelte';
|
import SubmissionStatus from '$lib/components/SubmissionStatus.svelte';
|
||||||
|
import { getCategories } from '$lib/categories';
|
||||||
import { getSurveys } from '$lib/surveys';
|
import { getSurveys } from '$lib/surveys';
|
||||||
import { getScore } from '$lib/users';
|
import { getScore } from '$lib/users';
|
||||||
|
|
||||||
@ -21,6 +22,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let categories = {};
|
||||||
|
getCategories().then((cs) => {
|
||||||
|
for (const c of cs) {
|
||||||
|
categories[c.id] = c;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function gotoSurvey(survey) {
|
function gotoSurvey(survey) {
|
||||||
if (survey.kind === "w") {
|
if (survey.kind === "w") {
|
||||||
goto(`works/${survey.id}`);
|
goto(`works/${survey.id}`);
|
||||||
@ -60,12 +68,30 @@
|
|||||||
{#each surveys as survey, sid (survey.kind + survey.id)}
|
{#each surveys as survey, sid (survey.kind + survey.id)}
|
||||||
{#if (survey.shown || survey.direct == null || ($user && $user.is_admin)) && (!$user || (!$user.was_admin || $user.promo == survey.promo) || $user.is_admin)}
|
{#if (survey.shown || survey.direct == null || ($user && $user.is_admin)) && (!$user || (!$user.was_admin || $user.promo == survey.promo) || $user.is_admin)}
|
||||||
{#if $user && $user.is_admin && (sid == 0 || surveys[sid-1].promo != survey.promo)}
|
{#if $user && $user.is_admin && (sid == 0 || surveys[sid-1].promo != survey.promo)}
|
||||||
<tr class="bg-info text-light">
|
<tr class="bg-warning text-light">
|
||||||
<th colspan="5" class="fw-bold">
|
<th colspan="5" class="fw-bold">
|
||||||
{survey.promo}
|
{survey.promo}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $user && (sid == 0 || surveys[sid-1].id_category != survey.id_category) && categories[survey.id_category]}
|
||||||
|
<tr class="bg-primary text-light">
|
||||||
|
<th colspan="5" class="fw-bold" on:click={() => categories[survey.id_category].expand = !categories[survey.id_category].expand}>
|
||||||
|
{#if categories[survey.id_category].expand}
|
||||||
|
<i class="bi bi-chevron-down"></i>
|
||||||
|
{:else}
|
||||||
|
<i class="bi bi-chevron-right"></i>
|
||||||
|
{/if}
|
||||||
|
{categories[survey.id_category].label}
|
||||||
|
{#if $user && $user.is_admin}
|
||||||
|
<a href="categories/{survey.id_category}" class="float-end btn btn-sm btn-light" style="margin: -6px;">
|
||||||
|
<i class="bi bi-pencil" on:click={() => categories[survey.id_category].expand = !categories[survey.id_category].expand}></i>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{#if categories[survey.id_category] && categories[survey.id_category].expand}
|
||||||
<tr on:click={e => gotoSurvey(survey)}>
|
<tr on:click={e => gotoSurvey(survey)}>
|
||||||
<td>
|
<td>
|
||||||
{#if !survey.shown}<i class="bi bi-eye-slash-fill" title="Ce questionnaire n'est pas affiché aux étudiants"></i>{/if}
|
{#if !survey.shown}<i class="bi bi-eye-slash-fill" title="Ce questionnaire n'est pas affiché aux étudiants"></i>{/if}
|
||||||
@ -127,6 +153,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { getCategories } from '$lib/categories';
|
||||||
import DateTimeInput from './DateTimeInput.svelte';
|
import DateTimeInput from './DateTimeInput.svelte';
|
||||||
import { ToastsStore } from '$lib/stores/toasts';
|
import { ToastsStore } from '$lib/stores/toasts';
|
||||||
|
|
||||||
@ -62,6 +63,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 text-sm-end">
|
||||||
|
<label for="category" class="col-form-label col-form-label-sm">Catégorie</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8 col-md-4 col-lg-2">
|
||||||
|
{#await getCategories() then categories}
|
||||||
|
<select id="category" class="form-select form-select-sm" bind:value={work.id_category}>
|
||||||
|
{#each categories as category (category.id)}
|
||||||
|
<option value={category.id}>{category.label} {category.promo}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-3 text-sm-end">
|
<div class="col-sm-3 text-sm-end">
|
||||||
<label for="promo" class="col-form-label col-form-label-sm">Promo</label>
|
<label for="promo" class="col-form-label col-form-label-sm">Promo</label>
|
||||||
|
@ -11,8 +11,9 @@ export class Survey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ id, title, promo, group, shown, direct, corrected, start_availability, end_availability }) {
|
update({ id, id_category, title, promo, group, shown, direct, corrected, start_availability, end_availability }) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.id_category = id_category;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.promo = promo;
|
this.promo = promo;
|
||||||
this.group = group;
|
this.group = group;
|
||||||
|
@ -6,8 +6,9 @@ export class Work {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ id, title, promo, group, shown, tag, description, descr_raw, submission_url, corrected, start_availability, end_availability }) {
|
update({ id, id_category, title, promo, group, shown, tag, description, descr_raw, submission_url, corrected, start_availability, end_availability }) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.id_category = id_category;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.promo = promo;
|
this.promo = promo;
|
||||||
this.group = group;
|
this.group = group;
|
||||||
|
39
ui/src/routes/categories/[cid]/index.svelte
Normal file
39
ui/src/routes/categories/[cid]/index.svelte
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script context="module">
|
||||||
|
import { getWork } from '$lib/works';
|
||||||
|
|
||||||
|
export async function load({ params }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
cid: params.cid,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { user } from '$lib/stores/user';
|
||||||
|
import CategoryAdmin from '$lib/components/CategoryAdmin.svelte';
|
||||||
|
import { Category, getCategory } from '$lib/categories';
|
||||||
|
|
||||||
|
export let cid;
|
||||||
|
|
||||||
|
let categoryP = null;
|
||||||
|
$: {
|
||||||
|
categoryP = getCategory(cid);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await categoryP then category}
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<h2>
|
||||||
|
<a href="categories/" class="text-muted" style="text-decoration: none"><</a>
|
||||||
|
{category.label}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $user && $user.is_admin}
|
||||||
|
<CategoryAdmin {category} on:saved={(e) => { goto(`categories/`)}} />
|
||||||
|
{/if}
|
||||||
|
{/await}
|
70
ui/src/routes/categories/index.svelte
Normal file
70
ui/src/routes/categories/index.svelte
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { user } from '$lib/stores/user';
|
||||||
|
import { getCategories } from '$lib/categories';
|
||||||
|
import { getPromos } from '$lib/users';
|
||||||
|
|
||||||
|
function showCategory(category) {
|
||||||
|
goto(`categories/${category.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let filterPromo = "";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $user && $user.is_admin}
|
||||||
|
<a href="categories/new" class="btn btn-primary ml-1 float-end" title="Ajouter une catégorie">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
</a>
|
||||||
|
{#await getPromos() then promos}
|
||||||
|
<div class="float-end me-2">
|
||||||
|
<select class="form-select" bind:value={filterPromo}>
|
||||||
|
<option value="">-</option>
|
||||||
|
{#each promos as promo, pid (pid)}
|
||||||
|
<option value={promo}>{promo}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
<h2>
|
||||||
|
Catégories // cours
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{#await getCategories()}
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-danger mx-3" role="status"></div>
|
||||||
|
<span>Chargement des catégories …</span>
|
||||||
|
</div>
|
||||||
|
{:then categories}
|
||||||
|
<table class="table table-sm table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Promo</th>
|
||||||
|
<th>Étendre</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each categories.filter((c) => (filterPromo === "" || filterPromo === c.promo)) as c (c.id)}
|
||||||
|
<tr>
|
||||||
|
<td>{c.id}</td>
|
||||||
|
<td>
|
||||||
|
<a href="categories/{c.id}">{c.label}</a>
|
||||||
|
</td>
|
||||||
|
<td>{c.promo}</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="badge"
|
||||||
|
class:bg-success={c.expand}
|
||||||
|
class:bg-danger={!c.expand}
|
||||||
|
>
|
||||||
|
{c.expand?"Oui":"Non"}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/await}
|
20
ui/src/routes/categories/new.svelte
Normal file
20
ui/src/routes/categories/new.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { user } from '$lib/stores/user';
|
||||||
|
import CategoryAdmin from '$lib/components/CategoryAdmin.svelte';
|
||||||
|
import { Category } from '$lib/categories';
|
||||||
|
|
||||||
|
let category = new Category();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<h2>
|
||||||
|
<a href="categories/" class="text-muted" style="text-decoration: none"><</a>
|
||||||
|
Nouvelle catégorie
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $user && $user.is_admin}
|
||||||
|
<CategoryAdmin {category} on:saved={(e) => { goto(`categories/${e.detail.id}`)}} />
|
||||||
|
{/if}
|
@ -5,17 +5,28 @@
|
|||||||
import DateFormat from '$lib/components/DateFormat.svelte';
|
import DateFormat from '$lib/components/DateFormat.svelte';
|
||||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||||
import SubmissionStatus from '$lib/components/SubmissionStatus.svelte';
|
import SubmissionStatus from '$lib/components/SubmissionStatus.svelte';
|
||||||
|
import { getCategories } from '$lib/categories';
|
||||||
import { getWorks } from '$lib/works';
|
import { getWorks } from '$lib/works';
|
||||||
import { getPromos } from '$lib/users';
|
import { getPromos } from '$lib/users';
|
||||||
import { getScore } from '$lib/users';
|
import { getScore } from '$lib/users';
|
||||||
|
|
||||||
let filterPromo = "";
|
let filterPromo = "";
|
||||||
|
|
||||||
|
let categories = {};
|
||||||
|
getCategories().then((cs) => {
|
||||||
|
for (const c of cs) {
|
||||||
|
categories[c.id] = c;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $user && $user.is_admin}
|
{#if $user && $user.is_admin}
|
||||||
<a href="works/new" class="btn btn-primary ml-1 float-end" title="Ajouter un travail">
|
<a href="works/new" class="btn btn-primary ml-1 float-end" title="Ajouter un travail">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="categories/" class="btn btn-info mx-1 float-end" title="Modifier les catégories">
|
||||||
|
<i class="bi bi-filter-circle"></i>
|
||||||
|
</a>
|
||||||
{#await getPromos() then promos}
|
{#await getPromos() then promos}
|
||||||
<div class="float-end me-2">
|
<div class="float-end me-2">
|
||||||
<select class="form-select" bind:value={filterPromo}>
|
<select class="form-select" bind:value={filterPromo}>
|
||||||
@ -51,12 +62,30 @@
|
|||||||
{#each works as work, wid (work.id)}
|
{#each works as work, wid (work.id)}
|
||||||
{#if (work.shown || ($user && $user.is_admin)) && (!$user || (!$user.was_admin || $user.promo == work.promo) || $user.is_admin)}
|
{#if (work.shown || ($user && $user.is_admin)) && (!$user || (!$user.was_admin || $user.promo == work.promo) || $user.is_admin)}
|
||||||
{#if $user && $user.is_admin && (wid == 0 || works[wid-1].promo != work.promo)}
|
{#if $user && $user.is_admin && (wid == 0 || works[wid-1].promo != work.promo)}
|
||||||
<tr class="bg-info text-light">
|
<tr class="bg-warning text-light">
|
||||||
<th colspan="5" class="fw-bold">
|
<th colspan="5" class="fw-bold">
|
||||||
{work.promo}
|
{work.promo}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $user && (wid == 0 || works[wid-1].id_category != work.id_category) && categories[work.id_category]}
|
||||||
|
<tr class="bg-primary text-light">
|
||||||
|
<th colspan="5" class="fw-bold" on:click={() => categories[work.id_category].expand = !categories[work.id_category].expand}>
|
||||||
|
{#if categories[work.id_category].expand}
|
||||||
|
<i class="bi bi-chevron-down"></i>
|
||||||
|
{:else}
|
||||||
|
<i class="bi bi-chevron-right"></i>
|
||||||
|
{/if}
|
||||||
|
{categories[work.id_category].label}
|
||||||
|
{#if $user && $user.is_admin}
|
||||||
|
<a href="categories/{work.id_category}" class="float-end btn btn-sm btn-light" style="margin: -6px;">
|
||||||
|
<i class="bi bi-pencil" on:click={() => categories[work.id_category].expand = !categories[work.id_category].expand}></i>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{#if categories[work.id_category] && categories[work.id_category].expand}
|
||||||
<tr on:click={e => goto(`works/${work.id}`)}>
|
<tr on:click={e => goto(`works/${work.id}`)}>
|
||||||
<td>
|
<td>
|
||||||
{#if !work.shown}<i class="bi bi-eye-slash-fill" title="Ce travail n'est pas affiché aux étudiants"></i>{/if}
|
{#if !work.shown}<i class="bi bi-eye-slash-fill" title="Ce travail n'est pas affiché aux étudiants"></i>{/if}
|
||||||
@ -94,6 +123,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</tr>
|
</tr>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
22
works.go
22
works.go
@ -108,7 +108,7 @@ func declareAPIAdminWorksRoutes(router *gin.RouterGroup) {
|
|||||||
new.Promo = currentPromo
|
new.Promo = currentPromo
|
||||||
}
|
}
|
||||||
|
|
||||||
work, err := NewWork(new.Title, new.Promo, new.Group, new.Shown, new.DescriptionRaw, new.Tag, new.SubmissionURL, new.StartAvailability, new.EndAvailability)
|
work, err := NewWork(new.IdCategory, new.Title, new.Promo, new.Group, new.Shown, new.DescriptionRaw, new.Tag, new.SubmissionURL, new.StartAvailability, new.EndAvailability)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to NewWork:", err)
|
log.Println("Unable to NewWork:", err)
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during work creation"})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during work creation"})
|
||||||
@ -260,6 +260,7 @@ func workUserAccessHandler(c *gin.Context) {
|
|||||||
type OneWork struct {
|
type OneWork struct {
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
|
IdCategory int64 `json:"id_category"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Promo uint `json:"promo"`
|
Promo uint `json:"promo"`
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
@ -272,14 +273,14 @@ type OneWork struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if rows, errr := DBQuery("SELECT kind, id, id_category, title, promo, grp, shown, direct, submission_url, corrected, start_availability, end_availability FROM all_works "+cnd, param...); errr != nil {
|
||||||
return nil, errr
|
return nil, errr
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var w OneWork
|
var w OneWork
|
||||||
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 {
|
if err = rows.Scan(&w.Kind, &w.Id, &w.IdCategory, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.Direct, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items = append(items, &w)
|
items = append(items, &w)
|
||||||
@ -294,6 +295,7 @@ func allWorks(cnd string, param ...interface{}) (items []*OneWork, err error) {
|
|||||||
|
|
||||||
type Work struct {
|
type Work struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
|
IdCategory int64 `json:"id_category"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Promo uint `json:"promo,omitempty"`
|
Promo uint `json:"promo,omitempty"`
|
||||||
Group string `json:"group,omitempty"`
|
Group string `json:"group,omitempty"`
|
||||||
@ -308,14 +310,14 @@ type Work struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, description, tag, submission_url, corrected, start_availability, end_availability FROM works "+cnd, param...); errr != nil {
|
if rows, errr := DBQuery("SELECT id_work, id_category, title, promo, grp, shown, description, tag, submission_url, corrected, start_availability, end_availability FROM works "+cnd, param...); errr != nil {
|
||||||
return nil, errr
|
return nil, errr
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var w Work
|
var w Work
|
||||||
if err = rows.Scan(&w.Id, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.DescriptionRaw, &w.Tag, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability); err != nil {
|
if err = rows.Scan(&w.Id, &w.IdCategory, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.DescriptionRaw, &w.Tag, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items = append(items, &w)
|
items = append(items, &w)
|
||||||
@ -330,23 +332,23 @@ 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)
|
w = new(Work)
|
||||||
err = DBQueryRow("SELECT id_work, title, promo, grp, shown, description, tag, 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.DescriptionRaw, &w.Tag, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability)
|
err = DBQueryRow("SELECT id_work, id_category, title, promo, grp, shown, description, tag, submission_url, corrected, start_availability, end_availability FROM works WHERE id_work=?", id).Scan(&w.Id, &w.IdCategory, &w.Title, &w.Promo, &w.Group, &w.Shown, &w.DescriptionRaw, &w.Tag, &w.SubmissionURL, &w.Corrected, &w.StartAvailability, &w.EndAvailability)
|
||||||
w.Description = string(blackfriday.Run([]byte(w.DescriptionRaw)))
|
w.Description = string(blackfriday.Run([]byte(w.DescriptionRaw)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWork(title string, promo uint, group string, shown bool, description string, tag string, submissionurl *string, startAvailability time.Time, endAvailability time.Time) (*Work, error) {
|
func NewWork(id_category int64, title string, promo uint, group string, shown bool, description string, tag string, submissionurl *string, startAvailability time.Time, endAvailability time.Time) (*Work, error) {
|
||||||
if res, err := DBExec("INSERT INTO works (title, promo, grp, shown, description, tag, submission_url, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", title, promo, group, shown, description, tag, submissionurl, startAvailability, endAvailability); err != nil {
|
if res, err := DBExec("INSERT INTO works (id_category, title, promo, grp, shown, description, tag, submission_url, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", id_category, title, promo, group, shown, description, tag, submissionurl, startAvailability, endAvailability); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if wid, err := res.LastInsertId(); err != nil {
|
} else if wid, err := res.LastInsertId(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return &Work{wid, title, promo, group, shown, description, description, tag, submissionurl, false, startAvailability, endAvailability}, nil
|
return &Work{wid, id_category, title, promo, group, shown, description, description, tag, submissionurl, false, startAvailability, endAvailability}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Work) Update() (*Work, error) {
|
func (w *Work) Update() (*Work, error) {
|
||||||
if _, err := DBExec("UPDATE works SET title = ?, promo = ?, grp = ?, shown = ?, description = ?, tag = ?, submission_url = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_work = ?", w.Title, w.Promo, w.Group, w.Shown, w.DescriptionRaw, w.Tag, w.SubmissionURL, w.Corrected, w.StartAvailability, w.EndAvailability, w.Id); err != nil {
|
if _, err := DBExec("UPDATE works SET id_category = ?, title = ?, promo = ?, grp = ?, shown = ?, description = ?, tag = ?, submission_url = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_work = ?", w.IdCategory, w.Title, w.Promo, w.Group, w.Shown, w.DescriptionRaw, w.Tag, w.SubmissionURL, w.Corrected, w.StartAvailability, w.EndAvailability, w.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
w.Description = string(blackfriday.Run([]byte(w.DescriptionRaw)))
|
w.Description = string(blackfriday.Run([]byte(w.DescriptionRaw)))
|
||||||
|
Reference in New Issue
Block a user