From 9a1a64c41c1990b2ded4df5530e8582ed1b076ba Mon Sep 17 00:00:00 2001 From: nemunaire Date: Wed, 27 Dec 2017 01:53:01 +0100 Subject: [PATCH] admin: complet API and interface with files checking page --- admin/api/exercice.go | 9 ++++-- admin/api/file.go | 50 ++++++++++++++++++++++++++++--- admin/api/handlers.go | 14 ++++++++- admin/index.go | 1 + admin/static.go | 3 ++ admin/static/index.html | 1 + admin/static/js/app.js | 38 +++++++++++++++++++++++ admin/static/views/file-list.html | 22 ++++++++++++++ libfic/file.go | 47 +++++++++++++++++++++-------- 9 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 admin/static/views/file-list.html diff --git a/admin/api/exercice.go b/admin/api/exercice.go index 681e1e3a..1fb3a562 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -21,8 +21,8 @@ func init() { router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles))) router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile))) - router.GET("/api/exercices/:eid/files/:fid", apiHandler(fileHandler(showExerciceFile))) - router.DELETE("/api/exercices/:eid/files/:fid", apiHandler(fileHandler(deleteExerciceFile))) + router.GET("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(showExerciceFile))) + router.DELETE("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(deleteExerciceFile))) router.GET("/api/exercices/:eid/hints", apiHandler(exerciceHandler(listExerciceHints))) router.POST("/api/exercices/:eid/hints", apiHandler(exerciceHandler(createExerciceHint))) @@ -233,6 +233,11 @@ func deleteExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, _ []byte) (interface{}, er return quiz.Delete() } +type uploadedFile struct { + URI string + Digest string +} + func createExerciceFile(exercice fic.Exercice, body []byte) (interface{}, error) { var uf uploadedFile if err := json.Unmarshal(body, &uf); err != nil { diff --git a/admin/api/file.go b/admin/api/file.go index e2179371..b73058f1 100644 --- a/admin/api/file.go +++ b/admin/api/file.go @@ -1,8 +1,50 @@ package api -import () +import ( + "encoding/json" -type uploadedFile struct { - URI string - Digest string + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/api/files/", apiHandler(listFiles)) + router.DELETE("/api/files/", apiHandler(clearFiles)) + + router.GET("/api/files/:fileid", apiHandler(fileHandler(showFile))) + router.PUT("/api/files/:fileid", apiHandler(fileHandler(updateFile))) + router.DELETE("/api/files/:fileid", apiHandler(fileHandler(deleteFile))) +} + +func listFiles(_ httprouter.Params, body []byte) (interface{}, error) { + // List all files + return fic.GetFiles() +} + +func clearFiles(_ httprouter.Params, _ []byte) (interface{}, error) { + return fic.ClearFiles() +} + +func showFile(file fic.EFile, _ []byte) (interface{}, error) { + return file, nil +} + +func updateFile(file fic.EFile, body []byte) (interface{}, error) { + var uf fic.EFile + if err := json.Unmarshal(body, &uf); err != nil { + return nil, err + } + + uf.Id = file.Id + + if _, err := uf.Update(); err != nil { + return nil, err + } else { + return uf, nil + } +} + +func deleteFile(file fic.EFile, _ []byte) (interface{}, error) { + return file.Delete() } diff --git a/admin/api/handlers.go b/admin/api/handlers.go index 228970bd..49a2ccd8 100644 --- a/admin/api/handlers.go +++ b/admin/api/handlers.go @@ -213,7 +213,7 @@ func quizHandler(f func(fic.MCQ,fic.Exercice,[]byte) (interface{}, error)) func } } -func fileHandler(f func(fic.EFile,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { +func exerciceFileHandler(f func(fic.EFile,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { return func (ps httprouter.Params, body []byte) (interface{}, error) { var exercice fic.Exercice exerciceHandler(func (ex fic.Exercice, _[]byte) (interface{}, error) { @@ -248,6 +248,18 @@ func eventHandler(f func(fic.Event,[]byte) (interface{}, error)) func (httproute } } +func fileHandler(f func(fic.EFile,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if fileid, err := strconv.Atoi(string(ps.ByName("fileid"))); err != nil { + return nil, err + } else if file, err := fic.GetFile(fileid); err != nil { + return nil, err + } else { + return f(file, body) + } + } +} + func notFound(ps httprouter.Params, _ []byte) (interface{}, error) { return nil, nil } diff --git a/admin/index.go b/admin/index.go index 7aab5c96..cacb7b98 100644 --- a/admin/index.go +++ b/admin/index.go @@ -33,6 +33,7 @@ const indextpl = ` + diff --git a/admin/static.go b/admin/static.go index ee9d4b17..75127168 100644 --- a/admin/static.go +++ b/admin/static.go @@ -19,6 +19,9 @@ func init() { api.Router().GET("/events/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { http.ServeFile(w, r, path.Join(StaticDir, "index.html")) }) + api.Router().GET("/files/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + http.ServeFile(w, r, path.Join(StaticDir, "index.html")) + }) api.Router().GET("/public/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { http.ServeFile(w, r, path.Join(StaticDir, "index.html")) }) diff --git a/admin/static/index.html b/admin/static/index.html index 9601ce03..4e101efc 100644 --- a/admin/static/index.html +++ b/admin/static/index.html @@ -31,6 +31,7 @@ + diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 43a2a034..b6a11ddf 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -49,6 +49,10 @@ angular.module("FICApp", ["ngRoute", "ngResource", "ngSanitize"]) controller: "PublicController", templateUrl: "views/public.html" }) + .when("/files", { + controller: "FilesListController", + templateUrl: "views/file-list.html" + }) .when("/events", { controller: "EventsListController", templateUrl: "views/event-list.html" @@ -72,6 +76,9 @@ angular.module("FICApp") 'update': {method: 'PUT'}, }) }) + .factory("File", function($resource) { + return $resource("/api/files/:fileId", { fileId: '@id' }) + }) .factory("ROSettings", function($resource) { return $resource("/api/settings-ro.json") }) @@ -525,6 +532,37 @@ angular.module("FICApp") }; }) + .controller("FilesListController", function($scope, File, $location, $http, $rootScope) { + $scope.files = File.query(); + $scope.fields = ["id", "path", "name", "checksum", "size"]; + + $scope.clearFiles = function(id) { + File.delete(function() { + $scope.files = []; + }); + }; + $scope.checksum = function(f) { + $http({ + url: "/api/files/" + f.id + "/checksum", + method: "GET" + }).then(function(response) { + if (response.data) + $rootScope.newBox('danger', response.data); + }, function(response) { + $scope.inSync = false; + $rootScope.newBox('danger', 'An error occurs when checking files:', response.data); + }) + }; + $scope.checksumAll = function() { + angular.forEach($scope.files, function(file) { + $scope.checksum(file); + }); + }; + $scope.show = function(f) { + $location.url("/exercices/" + f.idExercice); + }; + }) + .controller("EventsListController", function($scope, Event, $location) { $scope.events = Event.query(); $scope.fields = ["id", "kind", "txt", "time"]; diff --git a/admin/static/views/file-list.html b/admin/static/views/file-list.html new file mode 100644 index 00000000..937f28f6 --- /dev/null +++ b/admin/static/views/file-list.html @@ -0,0 +1,22 @@ +

+ Fichiers + +

+ +

+ + + + + + + + + + + +
+ {{ field }} +
+ {{ file[field] }} +
diff --git a/libfic/file.go b/libfic/file.go index fdecadaa..8967e684 100644 --- a/libfic/file.go +++ b/libfic/file.go @@ -49,6 +49,11 @@ func GetFiles() ([]EFile, error) { } } +func GetFile(id int) (f EFile, err error) { + err = DBQueryRow("SELECT id_file, origin, path, name, cksum, size FROM exercice_files WHERE id_file = ?", id).Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.Size) + return f, err +} + func GetFileByPath(path string) (EFile, error) { path = strings.TrimPrefix(path, FilesDir) @@ -94,13 +99,13 @@ func (e Exercice) GetFileByPath(path string) (EFile, error) { return f, nil } -func (e Exercice) ImportFile(filePath string, origin string, digest []byte) (interface{}, error) { - if digest == nil && !OptionalDigest { - return EFile{}, errors.New("No digest given.") +func checkFileHash(filePath string, digest []byte) ([]byte, int64, error) { + if digest == nil { + return []byte{}, 0, errors.New("No digest given.") } else if fi, err := os.Stat(filePath); err != nil { - return EFile{}, err + return []byte{}, fi.Size(), err } else if fd, err := os.Open(filePath); err != nil { - return EFile{}, err + return []byte{}, fi.Size(), err } else { defer fd.Close() @@ -110,41 +115,49 @@ func (e Exercice) ImportFile(filePath string, origin string, digest []byte) (int w := io.MultiWriter(hash160, hash512) if _, err := io.Copy(w, reader); err != nil { - return EFile{}, err + return []byte{}, fi.Size(), err } result160 := hash160.Sum(nil) result512 := hash512.Sum(nil) if len(digest) != len(result512) { if len(digest) != len(result160) { - return EFile{}, errors.New("Digests doesn't match: calculated: sha1:" + hex.EncodeToString(result160) + " & blake2b:" + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) + return []byte{}, fi.Size(), errors.New("Digests doesn't match: calculated: sha1:" + hex.EncodeToString(result160) + " & blake2b:" + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) } else if StrongDigest { - return EFile{}, errors.New("Invalid digests: SHA-1 checksums are no more accepted. Calculated sha1:" + hex.EncodeToString(result160) + " & blake2b:" + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) + return []byte{}, fi.Size(), errors.New("Invalid digests: SHA-1 checksums are no more accepted. Calculated sha1:" + hex.EncodeToString(result160) + " & blake2b:" + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) } for k := range result160 { if result160[k] != digest[k] { - return EFile{}, errors.New("Digests doesn't match: calculated: sha1:" + hex.EncodeToString(result160) + " & blake2b:" + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) + return []byte{}, fi.Size(), errors.New("Digests doesn't match: calculated: sha1:" + hex.EncodeToString(result160) + " & blake2b:" + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) } } } else { for k := range result512 { if result512[k] != digest[k] { - return EFile{}, errors.New("Digests doesn't match: calculated: " + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) + return []byte{}, fi.Size(), errors.New("Digests doesn't match: calculated: " + hex.EncodeToString(result512) + " vs. given: " + hex.EncodeToString(digest)) } } } + return result512, fi.Size(), nil + } +} + +func (e Exercice) ImportFile(filePath string, origin string, digest []byte) (interface{}, error) { + if result512, size, err := checkFileHash(filePath, digest); !OptionalDigest && err != nil { + return EFile{}, err + } else { dPath := strings.TrimPrefix(filePath, FilesDir) if f, err := e.GetFileByPath(dPath); err != nil { - return e.AddFile(dPath, origin, path.Base(filePath), result512, fi.Size()) + return e.AddFile(dPath, origin, path.Base(filePath), result512, size) } else { // Don't need to update Path and Name, because they are related to dPath f.IdExercice = e.Id f.origin = origin f.Checksum = result512 - f.Size = fi.Size() + f.Size = size if _, err := f.Update(); err != nil { return nil, err @@ -195,6 +208,16 @@ func (e Exercice) WipeFiles() (int64, error) { } } +func ClearFiles() (int64, error) { + if res, err := DBExec("DELETE FROM exercice_files"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + func (f EFile) GetOrigin() string { return f.origin }