admin: complet API and interface with files checking page

This commit is contained in:
nemunaire 2017-12-27 01:53:01 +01:00
parent 184714aeeb
commit 9a1a64c41c
9 changed files with 166 additions and 19 deletions

View File

@ -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 {

View File

@ -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()
}

View File

@ -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
}

View File

@ -33,6 +33,7 @@ const indextpl = `<!DOCTYPE html>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}teams">&Eacute;quipes</a></li>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}themes">Thèmes</a></li>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}exercices">Exercices</a></li>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}files">Fichiers</a></li>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}public/0">Public</a></li>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}events">&Eacute;vénements</a></li>
<li class="nav-item"><a class="nav-link" href="{{.urlbase}}settings">Paramètres</a></li>

View File

@ -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"))
})

View File

@ -31,6 +31,7 @@
<li class="nav-item"><a class="nav-link" href="/teams">&Eacute;quipes</a></li>
<li class="nav-item"><a class="nav-link" href="/themes">Thèmes</a></li>
<li class="nav-item"><a class="nav-link" href="/exercices">Exercices</a></li>
<li class="nav-item"><a class="nav-link" href="/files">Fichiers</a></li>
<li class="nav-item"><a class="nav-link" href="/public/0">Public</a></li>
<li class="nav-item"><a class="nav-link" href="/events">&Eacute;vénements</a></li>
<li class="nav-item"><a class="nav-link" href="/settings">Paramètres</a></li>

View File

@ -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"];

View File

@ -0,0 +1,22 @@
<h2>
Fichiers
<button ng-click="checksumAll()" class="float-right btn btn-sm btn-secondary mr-sm-2"><span class="glyphicon glyphicon-flash" aria-hidden="true"></span> Vérifier les fichiers</button>
</h2>
<p><input type="search" class="form-control" placeholder="Search" ng-model="query"></p>
<table class="table table-hover table-bordered table-striped table-sm">
<thead class="thead-dark">
<tr>
<th ng-repeat="field in fields">
{{ field }}
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="file in files | filter: query" ng-click="show(file)">
<td ng-repeat="field in fields">
{{ file[field] }}
</td>
</tr>
</tbody>
</table>

View File

@ -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
}