tests(all): improve tests, refactor(api): saving books
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:
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/itchyny/gojq"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"reichard.io/antholume/metadata"
|
||||
)
|
||||
|
||||
type adminAction string
|
||||
@@ -63,7 +64,7 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
var rAdminAction requestAdminAction
|
||||
if err := c.ShouldBind(&rAdminAction); err != nil {
|
||||
log.Error("Invalid Form Bind: ", err)
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,6 +76,7 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
// 2. Select all / deselect?
|
||||
case adminCacheTables:
|
||||
go api.db.CacheTempTables()
|
||||
// TODO - Message
|
||||
case adminRestore:
|
||||
api.processRestoreFile(rAdminAction, c)
|
||||
return
|
||||
@@ -83,7 +85,7 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
_, err := api.db.DB.ExecContext(api.db.Ctx, "VACUUM;")
|
||||
if err != nil {
|
||||
log.Error("Unable to vacuum DB: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -126,7 +128,7 @@ func (api *API) appGetAdminLogs(c *gin.Context) {
|
||||
var rAdminLogs requestAdminLogs
|
||||
if err := c.ShouldBindQuery(&rAdminLogs); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid URI parameters.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid URI parameters")
|
||||
return
|
||||
}
|
||||
rAdminLogs.Filter = strings.TrimSpace(rAdminLogs.Filter)
|
||||
@@ -136,14 +138,14 @@ func (api *API) appGetAdminLogs(c *gin.Context) {
|
||||
parsed, err := gojq.Parse(rAdminLogs.Filter)
|
||||
if err != nil {
|
||||
log.Error("Unable to parse JQ filter")
|
||||
appErrorPage(c, http.StatusNotFound, "Unable to parse JQ filter.")
|
||||
appErrorPage(c, http.StatusNotFound, "Unable to parse JQ filter")
|
||||
return
|
||||
}
|
||||
|
||||
jqFilter, err = gojq.Compile(parsed)
|
||||
if err != nil {
|
||||
log.Error("Unable to compile JQ filter")
|
||||
appErrorPage(c, http.StatusNotFound, "Unable to compile JQ filter.")
|
||||
appErrorPage(c, http.StatusNotFound, "Unable to compile JQ filter")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -152,7 +154,7 @@ func (api *API) appGetAdminLogs(c *gin.Context) {
|
||||
logPath := filepath.Join(api.cfg.ConfigPath, "logs/antholume.log")
|
||||
logFile, err := os.Open(logPath)
|
||||
if err != nil {
|
||||
appErrorPage(c, http.StatusBadRequest, "Missing AnthoLume log file.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Missing AnthoLume log file")
|
||||
return
|
||||
}
|
||||
defer logFile.Close()
|
||||
@@ -229,7 +231,7 @@ func (api *API) appGetAdminImport(c *gin.Context) {
|
||||
var rImportFolder requestAdminImport
|
||||
if err := c.ShouldBindQuery(&rImportFolder); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid directory.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid directory")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -244,7 +246,7 @@ func (api *API) appGetAdminImport(c *gin.Context) {
|
||||
dPath, err := filepath.Abs(api.cfg.DataPath)
|
||||
if err != nil {
|
||||
log.Error("Absolute filepath error: ", rImportFolder.Directory)
|
||||
appErrorPage(c, http.StatusNotFound, "Unable to get data directory absolute path.")
|
||||
appErrorPage(c, http.StatusNotFound, "Unable to get data directory absolute path")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -254,7 +256,7 @@ func (api *API) appGetAdminImport(c *gin.Context) {
|
||||
entries, err := os.ReadDir(rImportFolder.Directory)
|
||||
if err != nil {
|
||||
log.Error("Invalid directory: ", rImportFolder.Directory)
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid directory.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid directory")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -279,13 +281,46 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
||||
var rAdminImport requestAdminImport
|
||||
if err := c.ShouldBind(&rAdminImport); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid directory.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid directory")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO
|
||||
// TODO - Store results for approval?
|
||||
|
||||
fmt.Println(rAdminImport)
|
||||
// Walk import directory & copy or import files
|
||||
importDirectory := filepath.Clean(rAdminImport.Directory)
|
||||
_ = filepath.WalkDir(importDirectory, func(currentPath string, f fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get metadata
|
||||
fileMeta, err := metadata.GetMetadata(currentPath)
|
||||
if err != nil {
|
||||
fmt.Printf("metadata error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only needed if copying
|
||||
newName := deriveBaseFileName(fileMeta)
|
||||
|
||||
// Open File on Disk
|
||||
// file, err := os.Open(currentPath)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
// TODO - BasePath in DB
|
||||
// TODO - Copy / Import
|
||||
|
||||
fmt.Printf("New File Metadata: %s\n", newName)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
templateVars["CurrentPath"] = filepath.Clean(rAdminImport.Directory)
|
||||
|
||||
@@ -297,14 +332,14 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
uploadedFile, err := rAdminAction.RestoreFile.Open()
|
||||
if err != nil {
|
||||
log.Error("File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to open file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to open file")
|
||||
return
|
||||
}
|
||||
|
||||
fileMime, err := mimetype.DetectReader(uploadedFile)
|
||||
if err != nil {
|
||||
log.Error("MIME Error")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to detect filetype.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to detect filetype")
|
||||
return
|
||||
}
|
||||
fileExtension := fileMime.Extension()
|
||||
@@ -312,7 +347,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
// Validate Extension
|
||||
if !slices.Contains([]string{".zip"}, fileExtension) {
|
||||
log.Error("Invalid FileType: ", fileExtension)
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid filetype.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid filetype")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -320,7 +355,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
tempFile, err := os.CreateTemp("", "restore")
|
||||
if err != nil {
|
||||
log.Warn("Temp File Create Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to create temp file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to create temp file")
|
||||
return
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
@@ -330,7 +365,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
err = c.SaveUploadedFile(rAdminAction.RestoreFile, tempFile.Name())
|
||||
if err != nil {
|
||||
log.Error("File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -338,7 +373,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
fileInfo, err := tempFile.Stat()
|
||||
if err != nil {
|
||||
log.Error("File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to read file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to read file")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -346,7 +381,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
zipReader, err := zip.NewReader(tempFile, fileInfo.Size())
|
||||
if err != nil {
|
||||
log.Error("ZIP Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to read zip.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to read zip")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -380,7 +415,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
backupFile, err := os.Create(backupFilePath)
|
||||
if err != nil {
|
||||
log.Error("Unable to create backup file: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to create backup file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to create backup file")
|
||||
return
|
||||
}
|
||||
defer backupFile.Close()
|
||||
@@ -389,7 +424,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
_, err = api.db.DB.ExecContext(api.db.Ctx, "VACUUM;")
|
||||
if err != nil {
|
||||
log.Error("Unable to vacuum DB: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -398,7 +433,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
err = api.createBackup(w, []string{"covers", "documents"})
|
||||
if err != nil {
|
||||
log.Error("Unable to save backup file: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save backup file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save backup file")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -406,26 +441,26 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
err = api.removeData()
|
||||
if err != nil {
|
||||
log.Error("Unable to delete data: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to delete data.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to delete data")
|
||||
return
|
||||
}
|
||||
|
||||
// Restore Data
|
||||
err = api.restoreData(zipReader)
|
||||
if err != nil {
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to restore data.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to restore data")
|
||||
log.Panic("Unable to restore data: ", err)
|
||||
}
|
||||
|
||||
// Reinit DB
|
||||
if err := api.db.Reload(); err != nil {
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to reload DB.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to reload DB")
|
||||
log.Panicf("Unable to reload DB: %v", err)
|
||||
}
|
||||
|
||||
// Rotate Auth Hashes
|
||||
if err := api.rotateAllAuthHashes(); err != nil {
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to rotate hashes.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to rotate hashes")
|
||||
log.Panicf("Unable to rotate auth hashes: %v", err)
|
||||
}
|
||||
|
||||
@@ -433,6 +468,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
}
|
||||
|
||||
// Restore all data
|
||||
func (api *API) restoreData(zipReader *zip.Reader) error {
|
||||
// Ensure Directories
|
||||
api.cfg.EnsureDirectories()
|
||||
@@ -463,6 +499,7 @@ func (api *API) restoreData(zipReader *zip.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove all data
|
||||
func (api *API) removeData() error {
|
||||
allPaths := []string{
|
||||
"covers",
|
||||
@@ -485,6 +522,7 @@ func (api *API) removeData() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Backup all data
|
||||
func (api *API) createBackup(w io.Writer, directories []string) error {
|
||||
ar := zip.NewWriter(w)
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ func (api *API) appGetDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
||||
var rDoc requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ func (api *API) appUploadNewDocument(c *gin.Context) {
|
||||
var rDocUpload requestDocumentUpload
|
||||
if err := c.ShouldBind(&rDocUpload); err != nil {
|
||||
log.Error("Invalid Form Bind")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -426,153 +426,92 @@ func (api *API) appUploadNewDocument(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate Type & Derive Extension on MIME
|
||||
uploadedFile, err := rDocUpload.DocumentFile.Open()
|
||||
if err != nil {
|
||||
log.Error("File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to open file.")
|
||||
return
|
||||
}
|
||||
|
||||
fileMime, err := mimetype.DetectReader(uploadedFile)
|
||||
if err != nil {
|
||||
log.Error("MIME Error")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to detect filetype.")
|
||||
return
|
||||
}
|
||||
fileExtension := fileMime.Extension()
|
||||
|
||||
// Validate Extension
|
||||
if !slices.Contains([]string{".epub"}, fileExtension) {
|
||||
log.Error("Invalid FileType: ", fileExtension)
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid filetype.")
|
||||
return
|
||||
}
|
||||
|
||||
// Create Temp File
|
||||
tempFile, err := os.CreateTemp("", "book")
|
||||
if err != nil {
|
||||
log.Warn("Temp File Create Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to create temp file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to create temp file")
|
||||
return
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer tempFile.Close()
|
||||
|
||||
// Save Temp
|
||||
// Save Temp File
|
||||
err = c.SaveUploadedFile(rDocUpload.DocumentFile, tempFile.Name())
|
||||
if err != nil {
|
||||
log.Error("File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file")
|
||||
return
|
||||
}
|
||||
|
||||
// Get Metadata
|
||||
metadataInfo, err := metadata.GetMetadata(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Warn("GetMetadata Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to acquire file metadata.")
|
||||
log.Errorf("unable to acquire metadata: %v", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to acquire metadata")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate Partial MD5 ID
|
||||
partialMD5, err := utils.CalculatePartialMD5(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Warn("Partial MD5 Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to calculate partial MD5.")
|
||||
return
|
||||
}
|
||||
|
||||
// Check Exists
|
||||
_, err = api.db.Queries.GetDocument(api.db.Ctx, partialMD5)
|
||||
// Check Already Exists
|
||||
_, err = api.db.Queries.GetDocument(api.db.Ctx, *metadataInfo.PartialMD5)
|
||||
if err == nil {
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", partialMD5))
|
||||
return
|
||||
log.Warnf("document already exists: %s", *metadataInfo.PartialMD5)
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", *metadataInfo.PartialMD5))
|
||||
}
|
||||
|
||||
// Calculate Actual MD5
|
||||
fileHash, err := getFileMD5(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Error("MD5 Hash Failure: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to calculate MD5.")
|
||||
return
|
||||
}
|
||||
|
||||
// Get Word Count
|
||||
wordCount, err := metadata.GetWordCount(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Error("Word Count Failure: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to calculate word count.")
|
||||
return
|
||||
}
|
||||
|
||||
// Derive Filename
|
||||
var fileName string
|
||||
if *metadataInfo.Author != "" {
|
||||
fileName = fileName + *metadataInfo.Author
|
||||
} else {
|
||||
fileName = fileName + "Unknown"
|
||||
}
|
||||
|
||||
if *metadataInfo.Title != "" {
|
||||
fileName = fileName + " - " + *metadataInfo.Title
|
||||
} else {
|
||||
fileName = fileName + " - Unknown"
|
||||
}
|
||||
|
||||
// Remove Slashes
|
||||
fileName = strings.ReplaceAll(fileName, "/", "")
|
||||
|
||||
// Derive & Sanitize File Name
|
||||
fileName = "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, partialMD5, fileExtension))
|
||||
|
||||
// Generate Storage Path & Open File
|
||||
fileName := deriveBaseFileName(metadataInfo)
|
||||
safePath := filepath.Join(api.cfg.DataPath, "documents", fileName)
|
||||
|
||||
// Open Destination File
|
||||
destFile, err := os.Create(safePath)
|
||||
if err != nil {
|
||||
log.Error("Dest File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
log.Errorf("unable to open destination file: %v", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to open destination file")
|
||||
return
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
// Copy File
|
||||
if _, err = io.Copy(destFile, tempFile); err != nil {
|
||||
log.Error("Copy Temp File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
log.Errorf("unable to save file: %v", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file")
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert Document
|
||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
||||
ID: partialMD5,
|
||||
ID: *metadataInfo.PartialMD5,
|
||||
Title: metadataInfo.Title,
|
||||
Author: metadataInfo.Author,
|
||||
Description: metadataInfo.Description,
|
||||
Words: &wordCount,
|
||||
Md5: fileHash,
|
||||
Md5: metadataInfo.MD5,
|
||||
Words: metadataInfo.WordCount,
|
||||
Filepath: &fileName,
|
||||
|
||||
// TODO (BasePath):
|
||||
// - Should be current config directory
|
||||
}); err != nil {
|
||||
log.Error("UpsertDocument DB Error: ", err)
|
||||
log.Errorf("UpsertDocument DB Error: %v", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", partialMD5))
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", *metadataInfo.PartialMD5))
|
||||
}
|
||||
|
||||
func (api *API) appEditDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
|
||||
var rDocEdit requestDocumentEdit
|
||||
if err := c.ShouldBind(&rDocEdit); err != nil {
|
||||
log.Error("Invalid Form Bind")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -586,7 +525,7 @@ func (api *API) appEditDocument(c *gin.Context) {
|
||||
rDocEdit.CoverGBID == nil &&
|
||||
rDocEdit.CoverFile == nil {
|
||||
log.Error("Missing Form Values")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -600,14 +539,14 @@ func (api *API) appEditDocument(c *gin.Context) {
|
||||
uploadedFile, err := rDocEdit.CoverFile.Open()
|
||||
if err != nil {
|
||||
log.Error("File Error")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to open file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to open file")
|
||||
return
|
||||
}
|
||||
|
||||
fileMime, err := mimetype.DetectReader(uploadedFile)
|
||||
if err != nil {
|
||||
log.Error("MIME Error")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to detect filetype.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to detect filetype")
|
||||
return
|
||||
}
|
||||
fileExtension := fileMime.Extension()
|
||||
@@ -615,7 +554,7 @@ func (api *API) appEditDocument(c *gin.Context) {
|
||||
// Validate Extension
|
||||
if !slices.Contains([]string{".jpg", ".png"}, fileExtension) {
|
||||
log.Error("Invalid FileType: ", fileExtension)
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid filetype.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid filetype")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -627,7 +566,7 @@ func (api *API) appEditDocument(c *gin.Context) {
|
||||
err = c.SaveUploadedFile(rDocEdit.CoverFile, safePath)
|
||||
if err != nil {
|
||||
log.Error("File Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save file")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -663,7 +602,7 @@ func (api *API) appDeleteDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
changed, err := api.db.Queries.DeleteDocument(api.db.Ctx, rDocID.DocumentID)
|
||||
@@ -674,7 +613,7 @@ func (api *API) appDeleteDocument(c *gin.Context) {
|
||||
}
|
||||
if changed == 0 {
|
||||
log.Error("DeleteDocument DB Error")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -685,14 +624,14 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
|
||||
var rDocIdentify requestDocumentIdentify
|
||||
if err := c.ShouldBind(&rDocIdentify); err != nil {
|
||||
log.Error("Invalid Form Bind")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -710,7 +649,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
// Validate Values
|
||||
if rDocIdentify.ISBN == nil && rDocIdentify.Title == nil && rDocIdentify.Author == nil {
|
||||
log.Error("Invalid Form")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -718,7 +657,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
templateVars, auth := api.getBaseTemplateVars("document", c)
|
||||
|
||||
// Get Metadata
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.GBOOK, metadata.MetadataInfo{
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SOURCE_GBOOK, metadata.MetadataInfo{
|
||||
Title: rDocIdentify.Title,
|
||||
Author: rDocIdentify.Author,
|
||||
ISBN10: rDocIdentify.ISBN,
|
||||
@@ -767,7 +706,7 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
||||
var rDocAdd requestDocumentAdd
|
||||
if err := c.ShouldBind(&rDocAdd); err != nil {
|
||||
log.Error("Invalid Form Bind")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -845,7 +784,7 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
||||
fileName = strings.ReplaceAll(fileName, "/", "")
|
||||
|
||||
// Derive & Sanitize File Name
|
||||
fileName = "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, partialMD5, fileExtension))
|
||||
fileName = "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, *partialMD5, fileExtension))
|
||||
|
||||
// Open Source File
|
||||
sourceFile, err := os.Open(tempFilePath)
|
||||
@@ -901,12 +840,12 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
||||
|
||||
// Upsert Document
|
||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
||||
ID: partialMD5,
|
||||
ID: *partialMD5,
|
||||
Title: rDocAdd.Title,
|
||||
Author: rDocAdd.Author,
|
||||
Md5: fileHash,
|
||||
Filepath: &fileName,
|
||||
Words: &wordCount,
|
||||
Words: wordCount,
|
||||
}); err != nil {
|
||||
log.Error("UpsertDocument DB Error: ", err)
|
||||
sendDownloadMessage("Unable to save to database", gin.H{"Error": true})
|
||||
@@ -917,7 +856,7 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
||||
sendDownloadMessage("Download Success", gin.H{
|
||||
"Progress": 100,
|
||||
"ButtonText": "Go to Book",
|
||||
"ButtonHref": fmt.Sprintf("./documents/%s", partialMD5),
|
||||
"ButtonHref": fmt.Sprintf("./documents/%s", *partialMD5),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -925,14 +864,14 @@ func (api *API) appEditSettings(c *gin.Context) {
|
||||
var rUserSettings requestSettingsEdit
|
||||
if err := c.ShouldBind(&rUserSettings); err != nil {
|
||||
log.Error("Invalid Form Bind")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate Something Exists
|
||||
if rUserSettings.Password == nil && rUserSettings.NewPassword == nil && rUserSettings.TimeOffset == nil {
|
||||
log.Error("Missing Form Values")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1023,7 +962,7 @@ func (api *API) getDocumentsWordCount(documents []database.GetDocumentsWithStats
|
||||
} else {
|
||||
if _, err := qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
||||
ID: item.ID,
|
||||
Words: &wordCount,
|
||||
Words: wordCount,
|
||||
}); err != nil {
|
||||
log.Error("UpsertDocument DB Error: ", err)
|
||||
return err
|
||||
|
||||
@@ -95,7 +95,7 @@ func (api *API) createGetCoverHandler(errorFunc func(*gin.Context, int, string))
|
||||
var coverFile string = "UNKNOWN"
|
||||
|
||||
// Identify Documents & Save Covers
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.GBOOK, metadata.MetadataInfo{
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SOURCE_GBOOK, metadata.MetadataInfo{
|
||||
Title: document.Title,
|
||||
Author: document.Author,
|
||||
})
|
||||
|
||||
@@ -10,13 +10,10 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/metadata"
|
||||
)
|
||||
@@ -456,21 +453,11 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Open Form File
|
||||
fileData, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error("File Error:", err)
|
||||
apiErrorPage(c, http.StatusBadRequest, "File Error")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate Type & Derive Extension on MIME
|
||||
uploadedFile, err := fileData.Open()
|
||||
fileMime, err := mimetype.DetectReader(uploadedFile)
|
||||
fileExtension := fileMime.Extension()
|
||||
|
||||
if !slices.Contains([]string{".epub", ".html"}, fileExtension) {
|
||||
log.Error("Invalid FileType:", fileExtension)
|
||||
apiErrorPage(c, http.StatusBadRequest, "Invalid Filetype")
|
||||
apiErrorPage(c, http.StatusBadRequest, "File error")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -482,25 +469,29 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Open File
|
||||
uploadedFile, err := fileData.Open()
|
||||
if err != nil {
|
||||
log.Error("Unable to open file")
|
||||
apiErrorPage(c, http.StatusBadRequest, "Unable to open file")
|
||||
return
|
||||
}
|
||||
|
||||
// Check Support
|
||||
docType, err := metadata.GetDocumentTypeReader(uploadedFile)
|
||||
if err != nil {
|
||||
log.Error("Unsupported file")
|
||||
apiErrorPage(c, http.StatusBadRequest, "Unsupported file")
|
||||
return
|
||||
}
|
||||
|
||||
// Derive Filename
|
||||
var fileName string
|
||||
if document.Author != nil {
|
||||
fileName = fileName + *document.Author
|
||||
} else {
|
||||
fileName = fileName + "Unknown"
|
||||
}
|
||||
|
||||
if document.Title != nil {
|
||||
fileName = fileName + " - " + *document.Title
|
||||
} else {
|
||||
fileName = fileName + " - Unknown"
|
||||
}
|
||||
|
||||
// Remove Slashes
|
||||
fileName = strings.ReplaceAll(fileName, "/", "")
|
||||
|
||||
// Derive & Sanitize File Name
|
||||
fileName = "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, document.ID, fileExtension))
|
||||
fileName := deriveBaseFileName(&metadata.MetadataInfo{
|
||||
Type: *docType,
|
||||
PartialMD5: &document.ID,
|
||||
Title: document.Title,
|
||||
Author: document.Author,
|
||||
})
|
||||
|
||||
// Generate Storage Path
|
||||
safePath := filepath.Join(api.cfg.DataPath, "documents", fileName)
|
||||
@@ -516,28 +507,20 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get MD5 Hash
|
||||
fileHash, err := getFileMD5(safePath)
|
||||
// Acquire Metadata
|
||||
metadataInfo, err := metadata.GetMetadata(safePath)
|
||||
if err != nil {
|
||||
log.Error("Hash Failure:", err)
|
||||
apiErrorPage(c, http.StatusBadRequest, "File Error")
|
||||
return
|
||||
}
|
||||
|
||||
// Get Word Count
|
||||
wordCount, err := metadata.GetWordCount(safePath)
|
||||
if err != nil {
|
||||
log.Error("Word Count Failure:", err)
|
||||
apiErrorPage(c, http.StatusBadRequest, "File Error")
|
||||
log.Errorf("Unable to acquire metadata: %v", err)
|
||||
apiErrorPage(c, http.StatusBadRequest, "Unable to acquire metadata")
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert Document
|
||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
||||
ID: document.ID,
|
||||
Md5: fileHash,
|
||||
Md5: metadataInfo.MD5,
|
||||
Words: metadataInfo.WordCount,
|
||||
Filepath: &fileName,
|
||||
Words: &wordCount,
|
||||
}); err != nil {
|
||||
log.Error("UpsertDocument DB Error:", err)
|
||||
apiErrorPage(c, http.StatusBadRequest, "Document Error")
|
||||
|
||||
22
api/utils.go
22
api/utils.go
@@ -4,10 +4,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/graph"
|
||||
"reichard.io/antholume/metadata"
|
||||
)
|
||||
|
||||
type UTCOffset struct {
|
||||
@@ -144,3 +147,22 @@ func fields(value interface{}) (map[string]interface{}, error) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
||||
// Derive New FileName
|
||||
var newFileName string
|
||||
if *metadataInfo.Author != "" {
|
||||
newFileName = newFileName + *metadataInfo.Author
|
||||
} else {
|
||||
newFileName = newFileName + "Unknown"
|
||||
}
|
||||
if *metadataInfo.Title != "" {
|
||||
newFileName = newFileName + " - " + *metadataInfo.Title
|
||||
} else {
|
||||
newFileName = newFileName + " - Unknown"
|
||||
}
|
||||
|
||||
// Remove Slashes
|
||||
fileName := strings.ReplaceAll(newFileName, "/", "")
|
||||
return "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, *metadataInfo.PartialMD5, metadataInfo.Type))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
package api
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNiceSeconds(t *testing.T) {
|
||||
want := "22d 7h 39m 31s"
|
||||
nice := niceSeconds(1928371)
|
||||
wantOne := "22d 7h 39m 31s"
|
||||
wantNA := "N/A"
|
||||
|
||||
if nice != want {
|
||||
t.Fatalf(`Expected: %v, Got: %v`, want, nice)
|
||||
}
|
||||
niceOne := niceSeconds(1928371)
|
||||
niceNA := niceSeconds(0)
|
||||
|
||||
assert.Equal(t, wantOne, niceOne, "should be nice seconds")
|
||||
assert.Equal(t, wantNA, niceNA, "should be nice NA")
|
||||
}
|
||||
|
||||
func TestNiceNumbers(t *testing.T) {
|
||||
wantMillions := "198M"
|
||||
wantThousands := "19.8k"
|
||||
wantThousandsTwo := "1.98k"
|
||||
wantZero := "0"
|
||||
|
||||
niceMillions := niceNumbers(198236461)
|
||||
niceThousands := niceNumbers(19823)
|
||||
niceThousandsTwo := niceNumbers(1984)
|
||||
niceZero := niceNumbers(0)
|
||||
|
||||
assert.Equal(t, wantMillions, niceMillions, "should be nice millions")
|
||||
assert.Equal(t, wantThousands, niceThousands, "should be nice thousands")
|
||||
assert.Equal(t, wantThousandsTwo, niceThousandsTwo, "should be nice thousands")
|
||||
assert.Equal(t, wantZero, niceZero, "should be nice zero")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user