[add] better error handling, [add] font selector, [add] tailwind generation
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:
10
api/api.go
10
api/api.go
@@ -78,6 +78,7 @@ func (api *API) registerWebAppRoutes() {
|
||||
"NiceSeconds": niceSeconds,
|
||||
}
|
||||
|
||||
render.AddFromFiles("error", "templates/error.html")
|
||||
render.AddFromFilesFuncs("login", helperFuncs, "templates/login.html")
|
||||
render.AddFromFilesFuncs("reader", helperFuncs, "templates/reader-base.html", "templates/reader.html")
|
||||
render.AddFromFilesFuncs("home", helperFuncs, "templates/base.html", "templates/home.html")
|
||||
@@ -101,9 +102,10 @@ func (api *API) registerWebAppRoutes() {
|
||||
api.Router.POST("/settings", api.authWebAppMiddleware, api.editSettings)
|
||||
api.Router.GET("/activity", api.authWebAppMiddleware, api.createAppResourcesRoute("activity"))
|
||||
api.Router.GET("/documents", api.authWebAppMiddleware, api.createAppResourcesRoute("documents"))
|
||||
api.Router.POST("/documents", api.authWebAppMiddleware, api.uploadNewDocument)
|
||||
api.Router.GET("/documents/:document", api.authWebAppMiddleware, api.createAppResourcesRoute("document"))
|
||||
api.Router.GET("/documents/:document/reader", api.authWebAppMiddleware, api.documentReader)
|
||||
api.Router.GET("/documents/:document/file", api.authWebAppMiddleware, api.downloadDocumentFile)
|
||||
api.Router.GET("/documents/:document/file", api.authWebAppMiddleware, api.downloadDocument)
|
||||
api.Router.GET("/documents/:document/cover", api.authWebAppMiddleware, api.getDocumentCover)
|
||||
api.Router.POST("/documents/:document/edit", api.authWebAppMiddleware, api.editDocument)
|
||||
api.Router.POST("/documents/:document/identify", api.authWebAppMiddleware, api.identifyDocument)
|
||||
@@ -127,8 +129,8 @@ func (api *API) registerKOAPIRoutes(apiGroup *gin.RouterGroup) {
|
||||
|
||||
koGroup.POST("/documents", api.authKOMiddleware, api.addDocuments)
|
||||
koGroup.POST("/syncs/documents", api.authKOMiddleware, api.checkDocumentsSync)
|
||||
koGroup.PUT("/documents/:document/file", api.authKOMiddleware, api.uploadDocumentFile)
|
||||
koGroup.GET("/documents/:document/file", api.authKOMiddleware, api.downloadDocumentFile)
|
||||
koGroup.PUT("/documents/:document/file", api.authKOMiddleware, api.uploadExistingDocument)
|
||||
koGroup.GET("/documents/:document/file", api.authKOMiddleware, api.downloadDocument)
|
||||
|
||||
koGroup.POST("/activity", api.authKOMiddleware, api.addActivities)
|
||||
koGroup.POST("/syncs/activity", api.authKOMiddleware, api.checkActivitySync)
|
||||
@@ -139,7 +141,7 @@ func (api *API) registerOPDSRoutes(apiGroup *gin.RouterGroup) {
|
||||
|
||||
opdsGroup.GET("/", api.authOPDSMiddleware, api.opdsDocuments)
|
||||
opdsGroup.GET("/search.xml", api.authOPDSMiddleware, api.opdsSearchDescription)
|
||||
opdsGroup.GET("/documents/:document/file", api.authOPDSMiddleware, api.downloadDocumentFile)
|
||||
opdsGroup.GET("/documents/:document/file", api.authOPDSMiddleware, api.downloadDocument)
|
||||
opdsGroup.GET("/documents/:document/cover", api.authOPDSMiddleware, api.getDocumentCover)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"reichard.io/bbank/database"
|
||||
"reichard.io/bbank/metadata"
|
||||
"reichard.io/bbank/search"
|
||||
"reichard.io/bbank/utils"
|
||||
)
|
||||
|
||||
type queryParams struct {
|
||||
@@ -32,6 +33,10 @@ type searchParams struct {
|
||||
BookType *string `form:"book_type"`
|
||||
}
|
||||
|
||||
type requestDocumentUpload struct {
|
||||
DocumentFile *multipart.FileHeader `form:"document_file"`
|
||||
}
|
||||
|
||||
type requestDocumentEdit struct {
|
||||
Title *string `form:"title"`
|
||||
Author *string `form:"author"`
|
||||
@@ -100,7 +105,7 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] GetDocumentsWithStats DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsWithStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -113,7 +118,7 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("[createAppResourcesRoute] Invalid URI Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -123,11 +128,10 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] GetDocumentWithStats DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsWithStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
templateVars["RelBase"] = "../"
|
||||
templateVars["Data"] = document
|
||||
templateVars["TotalTimeLeftSeconds"] = (document.Pages - document.Page) * document.SecondsPerPage
|
||||
} else if routeName == "activity" {
|
||||
@@ -145,7 +149,7 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
activity, err := api.DB.Queries.GetActivity(api.DB.Ctx, activityFilter)
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] GetActivity DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -172,14 +176,14 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, userID)
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] GetUser DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, userID)
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] GetDevices DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -221,7 +225,7 @@ func (api *API) getDocumentCover(c *gin.Context) {
|
||||
var rDoc requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||
log.Error("[getDocumentCover] Invalid URI Bind")
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusNotFound, "Invalid cover.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -229,7 +233,7 @@ func (api *API) getDocumentCover(c *gin.Context) {
|
||||
document, err := api.DB.Queries.GetDocument(api.DB.Ctx, rDoc.DocumentID)
|
||||
if err != nil {
|
||||
log.Error("[getDocumentCover] GetDocument DB Error:", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -314,7 +318,7 @@ func (api *API) documentReader(c *gin.Context) {
|
||||
var rDoc requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||
log.Error("[documentReader] Invalid URI Bind")
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -325,7 +329,7 @@ func (api *API) documentReader(c *gin.Context) {
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.Error("[documentReader] UpsertDocument DB Error:", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -335,7 +339,7 @@ func (api *API) documentReader(c *gin.Context) {
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("[documentReader] GetDocumentWithStats DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentWithStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -343,22 +347,152 @@ func (api *API) documentReader(c *gin.Context) {
|
||||
"SearchEnabled": api.Config.SearchEnabled,
|
||||
"Progress": progress.Progress,
|
||||
"Data": document,
|
||||
"RelBase": "../../",
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) uploadNewDocument(c *gin.Context) {
|
||||
var rDocUpload requestDocumentUpload
|
||||
if err := c.ShouldBind(&rDocUpload); err != nil {
|
||||
log.Error("[uploadNewDocument] Invalid Form Bind")
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
if rDocUpload.DocumentFile == nil {
|
||||
c.Redirect(http.StatusFound, "./documents")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate Type & Derive Extension on MIME
|
||||
uploadedFile, err := rDocUpload.DocumentFile.Open()
|
||||
if err != nil {
|
||||
log.Error("[uploadNewDocument] File Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to open file.")
|
||||
return
|
||||
}
|
||||
|
||||
fileMime, err := mimetype.DetectReader(uploadedFile)
|
||||
if err != nil {
|
||||
log.Error("[uploadNewDocument] MIME Error")
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to detect filetype.")
|
||||
return
|
||||
}
|
||||
fileExtension := fileMime.Extension()
|
||||
|
||||
// Validate Extension
|
||||
if !slices.Contains([]string{".epub"}, fileExtension) {
|
||||
log.Error("[uploadNewDocument] Invalid FileType: ", fileExtension)
|
||||
errorPage(c, http.StatusBadRequest, "Invalid filetype.")
|
||||
return
|
||||
}
|
||||
|
||||
// Create Temp File
|
||||
tempFile, err := os.CreateTemp("", "book")
|
||||
if err != nil {
|
||||
log.Warn("[uploadNewDocument] Temp File Create Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to create temp file.")
|
||||
return
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Save Temp
|
||||
err = c.SaveUploadedFile(rDocUpload.DocumentFile, tempFile.Name())
|
||||
if err != nil {
|
||||
log.Error("[uploadNewDocument] File Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
return
|
||||
}
|
||||
|
||||
// Get Metadata
|
||||
metadataInfo, err := metadata.GetMetadata(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Warn("[uploadNewDocument] GetMetadata Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to acquire file metadata.")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate Partial MD5 ID
|
||||
partialMD5, err := utils.CalculatePartialMD5(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Warn("[uploadNewDocument] Partial MD5 Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to calculate partial MD5.")
|
||||
return
|
||||
}
|
||||
|
||||
// Check Exists
|
||||
_, err = api.DB.Queries.GetDocument(api.DB.Ctx, partialMD5)
|
||||
if err == nil {
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", partialMD5))
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate Actual MD5
|
||||
fileHash, err := getFileMD5(tempFile.Name())
|
||||
if err != nil {
|
||||
log.Error("[uploadNewDocument] MD5 Hash Failure:", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to calculate MD5.")
|
||||
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
|
||||
safePath := filepath.Join(api.Config.DataPath, "documents", fileName)
|
||||
|
||||
// Move File
|
||||
if err := os.Rename(tempFile.Name(), safePath); err != nil {
|
||||
log.Error("[uploadNewDocument] Move Temp File Error:", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert Document
|
||||
if _, err = api.DB.Queries.UpsertDocument(api.DB.Ctx, database.UpsertDocumentParams{
|
||||
ID: partialMD5,
|
||||
Title: metadataInfo.Title,
|
||||
Author: metadataInfo.Author,
|
||||
Description: metadataInfo.Description,
|
||||
Md5: fileHash,
|
||||
Filepath: &fileName,
|
||||
}); err != nil {
|
||||
log.Error("[uploadNewDocument] UpsertDocument DB Error:", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", partialMD5))
|
||||
}
|
||||
|
||||
func (api *API) editDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("[createAppResourcesRoute] Invalid URI Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
return
|
||||
}
|
||||
|
||||
var rDocEdit requestDocumentEdit
|
||||
if err := c.ShouldBind(&rDocEdit); err != nil {
|
||||
log.Error("[createAppResourcesRoute] Invalid Form Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -372,7 +506,7 @@ func (api *API) editDocument(c *gin.Context) {
|
||||
rDocEdit.CoverGBID == nil &&
|
||||
rDocEdit.CoverFile == nil {
|
||||
log.Error("[createAppResourcesRoute] Missing Form Values")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -386,14 +520,14 @@ func (api *API) editDocument(c *gin.Context) {
|
||||
uploadedFile, err := rDocEdit.CoverFile.Open()
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] File Error")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to open file.")
|
||||
return
|
||||
}
|
||||
|
||||
fileMime, err := mimetype.DetectReader(uploadedFile)
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] MIME Error")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to detect filetype.")
|
||||
return
|
||||
}
|
||||
fileExtension := fileMime.Extension()
|
||||
@@ -401,7 +535,7 @@ func (api *API) editDocument(c *gin.Context) {
|
||||
// Validate Extension
|
||||
if !slices.Contains([]string{".jpg", ".png"}, fileExtension) {
|
||||
log.Error("[uploadDocumentFile] Invalid FileType: ", fileExtension)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Filetype"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid filetype.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -412,8 +546,8 @@ func (api *API) editDocument(c *gin.Context) {
|
||||
// Save
|
||||
err = c.SaveUploadedFile(rDocEdit.CoverFile, safePath)
|
||||
if err != nil {
|
||||
log.Error("[createAppResourcesRoute] File Error")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
log.Error("[createAppResourcesRoute] File Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -437,7 +571,7 @@ func (api *API) editDocument(c *gin.Context) {
|
||||
Coverfile: coverFileName,
|
||||
}); err != nil {
|
||||
log.Error("[createAppResourcesRoute] UpsertDocument DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -449,18 +583,18 @@ func (api *API) deleteDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("[deleteDocument] Invalid URI Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
return
|
||||
}
|
||||
changed, err := api.DB.Queries.DeleteDocument(api.DB.Ctx, rDocID.DocumentID)
|
||||
if err != nil {
|
||||
log.Error("[deleteDocument] DeleteDocument DB Error")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("DeleteDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
if changed == 0 {
|
||||
log.Error("[deleteDocument] DeleteDocument DB Error")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Unknown Document"})
|
||||
errorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -473,14 +607,14 @@ func (api *API) identifyDocument(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("[identifyDocument] Invalid URI Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusNotFound, "Invalid document.")
|
||||
return
|
||||
}
|
||||
|
||||
var rDocIdentify requestDocumentIdentify
|
||||
if err := c.ShouldBind(&rDocIdentify); err != nil {
|
||||
log.Error("[identifyDocument] Invalid Form Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -498,13 +632,12 @@ func (api *API) identifyDocument(c *gin.Context) {
|
||||
// Validate Values
|
||||
if rDocIdentify.ISBN == nil && rDocIdentify.Title == nil && rDocIdentify.Author == nil {
|
||||
log.Error("[identifyDocument] Invalid Form")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
// Template Variables
|
||||
templateVars := gin.H{
|
||||
"RelBase": "../../",
|
||||
"SearchEnabled": api.Config.SearchEnabled,
|
||||
}
|
||||
|
||||
@@ -544,7 +677,7 @@ func (api *API) identifyDocument(c *gin.Context) {
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("[identifyDocument] GetDocumentWithStats DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentWithStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -558,7 +691,7 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
||||
var rDocAdd requestDocumentAdd
|
||||
if err := c.ShouldBind(&rDocAdd); err != nil {
|
||||
log.Error("[saveNewDocument] Invalid Form Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -568,7 +701,7 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
||||
rDocAdd.Title == nil ||
|
||||
rDocAdd.Author == nil {
|
||||
log.Error("[saveNewDocument] Missing Form Values")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -581,15 +714,15 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
||||
tempFilePath, err := search.SaveBook(*rDocAdd.ID, bType)
|
||||
if err != nil {
|
||||
log.Warn("[saveNewDocument] Temp File Error: ", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate Partial MD5 ID
|
||||
partialMD5, err := calculatePartialMD5(tempFilePath)
|
||||
partialMD5, err := utils.CalculatePartialMD5(tempFilePath)
|
||||
if err != nil {
|
||||
log.Warn("[saveNewDocument] Partial MD5 Error: ", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to calculate partial MD5.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -623,7 +756,7 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
||||
// Move File
|
||||
if err := os.Rename(tempFilePath, safePath); err != nil {
|
||||
log.Warn("[saveNewDocument] Move Temp File Error: ", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to save file.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -631,7 +764,7 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
||||
fileHash, err := getFileMD5(safePath)
|
||||
if err != nil {
|
||||
log.Error("[saveNewDocument] Hash Failure:", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, "Unable to calculate MD5.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -644,7 +777,7 @@ func (api *API) saveNewDocument(c *gin.Context) {
|
||||
Filepath: &fileName,
|
||||
}); err != nil {
|
||||
log.Error("[saveNewDocument] UpsertDocument DB Error:", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -657,14 +790,14 @@ func (api *API) editSettings(c *gin.Context) {
|
||||
var rUserSettings requestSettingsEdit
|
||||
if err := c.ShouldBind(&rUserSettings); err != nil {
|
||||
log.Error("[editSettings] Invalid Form Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate Something Exists
|
||||
if rUserSettings.Password == nil && rUserSettings.NewPassword == nil && rUserSettings.TimeOffset == nil {
|
||||
log.Error("[editSettings] Missing Form Values")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -703,7 +836,7 @@ func (api *API) editSettings(c *gin.Context) {
|
||||
_, err := api.DB.Queries.UpdateUser(api.DB.Ctx, newUserSettings)
|
||||
if err != nil {
|
||||
log.Error("[editSettings] UpdateUser DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpdateUser DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -711,7 +844,7 @@ func (api *API) editSettings(c *gin.Context) {
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, rUser.(string))
|
||||
if err != nil {
|
||||
log.Error("[editSettings] GetUser DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -719,7 +852,7 @@ func (api *API) editSettings(c *gin.Context) {
|
||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, rUser.(string))
|
||||
if err != nil {
|
||||
log.Error("[editSettings] GetDevices DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -792,3 +925,24 @@ func bindQueryParams(c *gin.Context) queryParams {
|
||||
|
||||
return qParams
|
||||
}
|
||||
|
||||
func errorPage(c *gin.Context, errorCode int, errorMessage string) {
|
||||
var errorHuman string = "We're not even sure what happened."
|
||||
|
||||
switch errorCode {
|
||||
case http.StatusInternalServerError:
|
||||
errorHuman = "Server hiccup."
|
||||
case http.StatusNotFound:
|
||||
errorHuman = "Something's missing."
|
||||
case http.StatusBadRequest:
|
||||
errorHuman = "We didn't expect that."
|
||||
case http.StatusUnauthorized:
|
||||
errorHuman = "You're not allowed to do that."
|
||||
}
|
||||
|
||||
c.HTML(errorCode, "error", gin.H{
|
||||
"Status": errorCode,
|
||||
"Error": errorHuman,
|
||||
"Message": errorMessage,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ func (api *API) authWebAppMiddleware(c *gin.Context) {
|
||||
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
func (api *API) authFormLogin(c *gin.Context) {
|
||||
@@ -152,7 +153,8 @@ func (api *API) authFormLogin(c *gin.Context) {
|
||||
|
||||
func (api *API) authFormRegister(c *gin.Context) {
|
||||
if !api.Config.RegistrationEnabled {
|
||||
c.AbortWithStatus(http.StatusConflict)
|
||||
errorPage(c, http.StatusUnauthorized, "Nice try. Registration is disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
username := strings.TrimSpace(c.PostForm("username"))
|
||||
@@ -202,7 +204,7 @@ func (api *API) authFormRegister(c *gin.Context) {
|
||||
// Set Session
|
||||
session := sessions.Default(c)
|
||||
if err := setSession(session, username); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
errorPage(c, http.StatusUnauthorized, "Unauthorized.")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -500,17 +500,17 @@ func (api *API) checkDocumentsSync(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, rCheckDocSync)
|
||||
}
|
||||
|
||||
func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
func (api *API) uploadExistingDocument(c *gin.Context) {
|
||||
var rDoc requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||
log.Error("[uploadDocumentFile] Invalid URI Bind")
|
||||
log.Error("[uploadExistingDocument] Invalid URI Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
return
|
||||
}
|
||||
|
||||
fileData, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
log.Error("[uploadDocumentFile] File Error:", err)
|
||||
log.Error("[uploadExistingDocument] File Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "File Error"})
|
||||
return
|
||||
}
|
||||
@@ -521,7 +521,7 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
fileExtension := fileMime.Extension()
|
||||
|
||||
if !slices.Contains([]string{".epub", ".html"}, fileExtension) {
|
||||
log.Error("[uploadDocumentFile] Invalid FileType:", fileExtension)
|
||||
log.Error("[uploadExistingDocument] Invalid FileType:", fileExtension)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Filetype"})
|
||||
return
|
||||
}
|
||||
@@ -529,7 +529,7 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
// Validate Document Exists in DB
|
||||
document, err := api.DB.Queries.GetDocument(api.DB.Ctx, rDoc.DocumentID)
|
||||
if err != nil {
|
||||
log.Error("[uploadDocumentFile] GetDocument DB Error:", err)
|
||||
log.Error("[uploadExistingDocument] GetDocument DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Unknown Document"})
|
||||
return
|
||||
}
|
||||
@@ -562,7 +562,7 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
if os.IsNotExist(err) {
|
||||
err = c.SaveUploadedFile(fileData, safePath)
|
||||
if err != nil {
|
||||
log.Error("[uploadDocumentFile] Save Failure:", err)
|
||||
log.Error("[uploadExistingDocument] Save Failure:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "File Error"})
|
||||
return
|
||||
}
|
||||
@@ -571,7 +571,7 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
// Get MD5 Hash
|
||||
fileHash, err := getFileMD5(safePath)
|
||||
if err != nil {
|
||||
log.Error("[uploadDocumentFile] Hash Failure:", err)
|
||||
log.Error("[uploadExistingDocument] Hash Failure:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "File Error"})
|
||||
return
|
||||
}
|
||||
@@ -582,7 +582,7 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
Md5: fileHash,
|
||||
Filepath: &fileName,
|
||||
}); err != nil {
|
||||
log.Error("[uploadDocumentFile] UpsertDocument DB Error:", err)
|
||||
log.Error("[uploadExistingDocument] UpsertDocument DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Document Error"})
|
||||
return
|
||||
}
|
||||
@@ -592,7 +592,7 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
ID: document.ID,
|
||||
Synced: true,
|
||||
}); err != nil {
|
||||
log.Error("[uploadDocumentFile] UpdateDocumentSync DB Error:", err)
|
||||
log.Error("[uploadExistingDocument] UpdateDocumentSync DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Document"})
|
||||
return
|
||||
}
|
||||
@@ -602,10 +602,10 @@ func (api *API) uploadDocumentFile(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) downloadDocumentFile(c *gin.Context) {
|
||||
func (api *API) downloadDocument(c *gin.Context) {
|
||||
var rDoc requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||
log.Error("[downloadDocumentFile] Invalid URI Bind")
|
||||
log.Error("[downloadDocument] Invalid URI Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
return
|
||||
}
|
||||
@@ -613,13 +613,13 @@ func (api *API) downloadDocumentFile(c *gin.Context) {
|
||||
// Get Document
|
||||
document, err := api.DB.Queries.GetDocument(api.DB.Ctx, rDoc.DocumentID)
|
||||
if err != nil {
|
||||
log.Error("[uploadDocumentFile] GetDocument DB Error:", err)
|
||||
log.Error("[downloadDocument] GetDocument DB Error:", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Unknown Document"})
|
||||
return
|
||||
}
|
||||
|
||||
if document.Filepath == nil {
|
||||
log.Error("[uploadDocumentFile] Document Doesn't Have File:", rDoc.DocumentID)
|
||||
log.Error("[downloadDocument] Document Doesn't Have File:", rDoc.DocumentID)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Document Doesn't Exist"})
|
||||
return
|
||||
}
|
||||
@@ -630,7 +630,7 @@ func (api *API) downloadDocumentFile(c *gin.Context) {
|
||||
// Validate File Exists
|
||||
_, err = os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
log.Error("[uploadDocumentFile] File Doesn't Exist:", rDoc.DocumentID)
|
||||
log.Error("[downloadDocument] File Doesn't Exist:", rDoc.DocumentID)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Document Doesn't Exists"})
|
||||
return
|
||||
}
|
||||
|
||||
39
api/utils.go
39
api/utils.go
@@ -1,12 +1,8 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"reichard.io/bbank/database"
|
||||
"reichard.io/bbank/graph"
|
||||
@@ -90,41 +86,6 @@ func niceSeconds(input int64) (result string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reimplemented KOReader Partial MD5 Calculation
|
||||
func calculatePartialMD5(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var step int64 = 1024
|
||||
var size int64 = 1024
|
||||
var buf bytes.Buffer
|
||||
|
||||
for i := -1; i <= 10; i++ {
|
||||
byteStep := make([]byte, size)
|
||||
|
||||
var newShift int64 = int64(i * 2)
|
||||
var newOffset int64
|
||||
if i == -1 {
|
||||
newOffset = 0
|
||||
} else {
|
||||
newOffset = step << newShift
|
||||
}
|
||||
|
||||
_, err := file.ReadAt(byteStep, newOffset)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
buf.Write(byteStep)
|
||||
}
|
||||
|
||||
allBytes := buf.Bytes()
|
||||
return fmt.Sprintf("%x", md5.Sum(allBytes)), nil
|
||||
}
|
||||
|
||||
// Convert Database Array -> Int64 Array
|
||||
func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) graph.SVGGraphData {
|
||||
var intData []int64
|
||||
|
||||
12
api/utils_test.go
Normal file
12
api/utils_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package api
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNiceSeconds(t *testing.T) {
|
||||
want := "22d 7h 39m 31s"
|
||||
nice := niceSeconds(1928371)
|
||||
|
||||
if nice != want {
|
||||
t.Fatalf(`Expected: %v, Got: %v`, want, nice)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user