admin: Check all theme/exercice attribute are in sync with repo
This commit is contained in:
parent
5e262b75a3
commit
74f388a2b9
18 changed files with 813 additions and 137 deletions
|
|
@ -23,7 +23,7 @@ func NewThemeError(theme *fic.Theme, err error) *ThemeError {
|
|||
if theme == nil {
|
||||
return &ThemeError{
|
||||
error: err,
|
||||
ThemePath: StandaloneExercicesDirectory,
|
||||
ThemePath: fic.StandaloneExercicesDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -420,36 +420,67 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
return
|
||||
}
|
||||
|
||||
func GetRemoteExerciceFiles(thid, exid string) ([]*fic.EFile, error) {
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
|
||||
if theme == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions)
|
||||
if exercice == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files")
|
||||
if files == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
var ret []*fic.EFile
|
||||
for _, fname := range files {
|
||||
fPath := path.Join(exercice.Path, "files", fname)
|
||||
fSize, _ := GetFileSize(GlobalImporter, fPath)
|
||||
|
||||
file := fic.EFile{
|
||||
Path: fPath,
|
||||
Name: fname,
|
||||
Checksum: digests[fname],
|
||||
Size: fSize,
|
||||
Published: true,
|
||||
}
|
||||
|
||||
if d, exists := digests[strings.TrimSuffix(file.Name, ".gz")]; exists {
|
||||
file.Name = strings.TrimSuffix(file.Name, ".gz")
|
||||
file.Path = strings.TrimSuffix(file.Path, ".gz")
|
||||
file.ChecksumShown = d
|
||||
}
|
||||
|
||||
ret = append(ret, &file)
|
||||
}
|
||||
|
||||
// Complete with attributes
|
||||
if paramsFiles, err := GetExerciceFilesParams(GlobalImporter, exercice); err == nil {
|
||||
for _, file := range ret {
|
||||
if f, ok := paramsFiles[file.Name]; ok {
|
||||
file.Published = !f.Hidden
|
||||
|
||||
if disclaimer, err := ProcessMarkdown(GlobalImporter, fixnbsp(f.Disclaimer), exercice.Path); err == nil {
|
||||
file.Disclaimer = disclaimer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ApiGetRemoteExerciceFiles is an accessor to remote exercice files list.
|
||||
func ApiGetRemoteExerciceFiles(c *gin.Context) {
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||
if theme != nil {
|
||||
exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
|
||||
if exercice != nil {
|
||||
files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files")
|
||||
if files != nil {
|
||||
var ret []*fic.EFile
|
||||
for _, fname := range files {
|
||||
fPath := path.Join(exercice.Path, "files", fname)
|
||||
fSize, _ := GetFileSize(GlobalImporter, fPath)
|
||||
ret = append(ret, &fic.EFile{
|
||||
Path: fPath,
|
||||
Name: fname,
|
||||
Checksum: digests[fname],
|
||||
Size: fSize,
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, ret)
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
files, err := GetRemoteExerciceFiles(c.Params.ByName("thid"), c.Params.ByName("exid"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, files)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,25 +169,32 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
|
|||
return
|
||||
}
|
||||
|
||||
func GetRemoteExerciceHints(thid, exid string) ([]importHint, error) {
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
|
||||
if theme == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions)
|
||||
if exercice == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
hints, errs := CheckExerciceHints(GlobalImporter, exercice, eexceptions)
|
||||
if hints == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return hints, nil
|
||||
}
|
||||
|
||||
// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
|
||||
func ApiGetRemoteExerciceHints(c *gin.Context) {
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||
if theme != nil {
|
||||
exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
|
||||
if exercice != nil {
|
||||
hints, errs := CheckExerciceHints(GlobalImporter, exercice, eexceptions)
|
||||
if hints != nil {
|
||||
c.JSON(http.StatusOK, hints)
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
return
|
||||
}
|
||||
|
||||
hints, errs := GetRemoteExerciceHints(c.Params.ByName("thid"), c.Params.ByName("exid"))
|
||||
if hints != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
c.JSON(http.StatusOK, hints)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -699,26 +699,32 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
return
|
||||
}
|
||||
|
||||
func GetRemoteExerciceFlags(thid, exid string) ([]fic.Flag, error) {
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
|
||||
if theme == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions)
|
||||
if exercice == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, eexceptions)
|
||||
if flags == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
|
||||
func ApiGetRemoteExerciceFlags(c *gin.Context) {
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||
if theme != nil {
|
||||
exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
|
||||
if exercice != nil {
|
||||
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, eexceptions)
|
||||
if flags != nil {
|
||||
c.JSON(http.StatusOK, flags)
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
flags, err := GetRemoteExerciceFlags(c.Params.ByName("thid"), c.Params.ByName("exid"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
return
|
||||
c.JSON(http.StatusOK, flags)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,10 +63,17 @@ func buildDependancyMap(i Importer, theme *fic.Theme) (dmap map[int64]*fic.Exerc
|
|||
continue
|
||||
}
|
||||
|
||||
// ename can be overrride by title.txt
|
||||
if i.Exists(path.Join(theme.Path, edir, "title.txt")) {
|
||||
if myTitle, err := GetFileContent(i, path.Join(theme.Path, edir, "title.txt")); err == nil {
|
||||
ename = strings.TrimSpace(myTitle)
|
||||
}
|
||||
}
|
||||
|
||||
var e *fic.Exercice
|
||||
e, err = theme.GetExerciceByTitle(ename)
|
||||
if err != nil {
|
||||
return
|
||||
return dmap, fmt.Errorf("unable to GetExerciceByTitle(ename=%q, tid=%d): %w", ename, theme.Id, err)
|
||||
}
|
||||
|
||||
dmap[int64(eid)] = e
|
||||
|
|
@ -468,59 +475,57 @@ func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (e
|
|||
return
|
||||
}
|
||||
|
||||
func ListRemoteExercices(thid string) ([]string, error) {
|
||||
if thid == "_" {
|
||||
return GetExercices(GlobalImporter, &fic.StandaloneExercicesTheme)
|
||||
}
|
||||
|
||||
theme, _, errs := BuildTheme(GlobalImporter, thid)
|
||||
if theme == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return GetExercices(GlobalImporter, theme)
|
||||
}
|
||||
|
||||
// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
|
||||
func ApiListRemoteExercices(c *gin.Context) {
|
||||
if c.Params.ByName("thid") == "_" {
|
||||
exercices, err := GetExercices(GlobalImporter, &fic.Theme{Path: StandaloneExercicesDirectory})
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, exercices)
|
||||
exercices, err := ListRemoteExercices(c.Params.ByName("thid"))
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
theme, _, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||
if theme != nil {
|
||||
exercices, err := GetExercices(GlobalImporter, theme)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, exercices)
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, exercices)
|
||||
}
|
||||
|
||||
// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
|
||||
func GetRemoteExercice(thid, exid string, inTheme *fic.Theme) (*fic.Exercice, error) {
|
||||
if thid == fic.StandaloneExercicesDirectory || thid == "_" {
|
||||
exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, nil, path.Join(fic.StandaloneExercicesDirectory, exid), nil, nil)
|
||||
return exercice, errs
|
||||
}
|
||||
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, thid)
|
||||
if theme == nil {
|
||||
return nil, fmt.Errorf("Theme not found")
|
||||
}
|
||||
|
||||
if inTheme == nil {
|
||||
inTheme = theme
|
||||
}
|
||||
|
||||
exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, inTheme, path.Join(theme.Path, exid), nil, exceptions)
|
||||
return exercice, errs
|
||||
}
|
||||
|
||||
// ApiGetRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
|
||||
func ApiGetRemoteExercice(c *gin.Context) {
|
||||
if c.Params.ByName("thid") == "_" {
|
||||
exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, nil, path.Join(StandaloneExercicesDirectory, c.Params.ByName("exid")), nil, nil)
|
||||
if exercice != nil {
|
||||
c.JSON(http.StatusOK, exercice)
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||
if theme != nil {
|
||||
exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
|
||||
if exercice != nil {
|
||||
c.JSON(http.StatusOK, exercice)
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
return
|
||||
}
|
||||
exercice, err := GetRemoteExercice(c.Params.ByName("thid"), c.Params.ByName("exid"), nil)
|
||||
if exercice != nil {
|
||||
c.JSON(http.StatusOK, exercice)
|
||||
return
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
|||
if themes, err := fic.GetThemes(); err == nil {
|
||||
DeepSyncProgress = 2
|
||||
|
||||
if i.Exists(StandaloneExercicesDirectory) {
|
||||
themes = append(themes, &fic.Theme{Path: StandaloneExercicesDirectory})
|
||||
if i.Exists(fic.StandaloneExercicesDirectory) {
|
||||
themes = append(themes, &fic.StandaloneExercicesTheme)
|
||||
}
|
||||
|
||||
var themeStep uint8 = uint8(250) / uint8(len(themes))
|
||||
|
|
@ -147,8 +147,8 @@ func SyncDeep(i Importer) (errs SyncReport) {
|
|||
DeepSyncProgress = 2
|
||||
|
||||
// Also synchronize standalone exercices
|
||||
if i.Exists(StandaloneExercicesDirectory) {
|
||||
themes = append(themes, &fic.Theme{Path: StandaloneExercicesDirectory})
|
||||
if i.Exists(fic.StandaloneExercicesDirectory) {
|
||||
themes = append(themes, &fic.StandaloneExercicesTheme)
|
||||
}
|
||||
|
||||
var themeStep uint8 = uint8(250) / uint8(len(themes))
|
||||
|
|
|
|||
|
|
@ -22,15 +22,13 @@ import (
|
|||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
const StandaloneExercicesDirectory = "exercices"
|
||||
|
||||
// GetThemes returns all theme directories in the base directory.
|
||||
func GetThemes(i Importer) (themes []string, err error) {
|
||||
if dirs, err := i.ListDir("/"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, dir := range dirs {
|
||||
if !strings.HasPrefix(dir, ".") && !strings.HasPrefix(dir, "_") && dir != StandaloneExercicesDirectory {
|
||||
if !strings.HasPrefix(dir, ".") && !strings.HasPrefix(dir, "_") && dir != fic.StandaloneExercicesDirectory {
|
||||
if _, err := i.ListDir(dir); err == nil {
|
||||
themes = append(themes, dir)
|
||||
}
|
||||
|
|
@ -180,8 +178,10 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
th.URLId = fic.ToURLid(th.Name)
|
||||
|
||||
if authors, err := getAuthors(i, tdir); err != nil {
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
return nil, nil, errs
|
||||
if tdir != fic.StandaloneExercicesDirectory {
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
return nil, nil, errs
|
||||
}
|
||||
} else {
|
||||
// Format authors
|
||||
th.Authors = strings.Join(authors, ", ")
|
||||
|
|
@ -355,18 +355,34 @@ func ApiListRemoteThemes(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, themes)
|
||||
}
|
||||
|
||||
func GetRemoteTheme(thid string) (*fic.Theme, error) {
|
||||
if thid == fic.StandaloneExercicesTheme.URLId || thid == fic.StandaloneExercicesDirectory {
|
||||
return &fic.StandaloneExercicesTheme, nil
|
||||
}
|
||||
|
||||
theme, _, errs := BuildTheme(GlobalImporter, thid)
|
||||
if theme == nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return theme, nil
|
||||
}
|
||||
|
||||
// ApiListRemoteTheme is an accessor letting foreign packages to access remote main theme attributes.
|
||||
func ApiGetRemoteTheme(c *gin.Context) {
|
||||
if c.Params.ByName("thid") == "_" {
|
||||
c.Status(http.StatusNoContent)
|
||||
var theme *fic.Theme
|
||||
var err error
|
||||
|
||||
if c.Params.ByName("thid") == fic.StandaloneExercicesTheme.URLId {
|
||||
theme, err = GetRemoteTheme(fic.StandaloneExercicesDirectory)
|
||||
} else {
|
||||
theme, err = GetRemoteTheme(c.Params.ByName("thid"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r, _, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
||||
if r == nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, r)
|
||||
c.JSON(http.StatusOK, theme)
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue