[add] better error handling, [add] font selector, [add] tailwind generation
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2023-10-25 19:52:01 -04:00
parent 3577dd89a0
commit cdec621043
25 changed files with 2792 additions and 241 deletions

View File

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

View File

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

View File

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

View File

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

View File

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