AnthoLume/api/app-routes.go

352 lines
10 KiB
Go
Raw Normal View History

2023-09-18 23:57:18 +00:00
package api
import (
"fmt"
2023-09-23 18:14:57 +00:00
"mime/multipart"
2023-09-18 23:57:18 +00:00
"net/http"
"os"
"path/filepath"
2023-09-23 18:14:57 +00:00
"strings"
"time"
2023-09-18 23:57:18 +00:00
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"reichard.io/bbank/database"
"reichard.io/bbank/metadata"
)
2023-09-23 18:14:57 +00:00
type requestDocumentEdit struct {
Title *string `form:"title"`
Author *string `form:"author"`
Description *string `form:"description"`
Cover *multipart.FileHeader `form:"cover"`
}
2023-09-18 23:57:18 +00:00
func baseResourceRoute(template string, args ...map[string]any) func(c *gin.Context) {
variables := gin.H{"RouteName": template}
if len(args) > 0 {
variables = args[0]
}
return func(c *gin.Context) {
rUser, _ := c.Get("AuthorizedUser")
variables["User"] = rUser
c.HTML(http.StatusOK, template, variables)
}
}
func (api *API) webManifest(c *gin.Context) {
c.Header("Content-Type", "application/manifest+json")
c.File("./assets/manifest.json")
}
2023-09-18 23:57:18 +00:00
func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any) func(*gin.Context) {
// Merge Optional Template Data
var templateVarsBase = gin.H{}
2023-09-18 23:57:18 +00:00
if len(args) > 0 {
templateVarsBase = args[0]
2023-09-18 23:57:18 +00:00
}
templateVarsBase["RouteName"] = routeName
2023-09-18 23:57:18 +00:00
return func(c *gin.Context) {
rUser, _ := c.Get("AuthorizedUser")
// Copy Base & Update
templateVars := gin.H{}
for k, v := range templateVarsBase {
templateVars[k] = v
}
2023-09-18 23:57:18 +00:00
templateVars["User"] = rUser
// Potential URL Parameters
qParams := bindQueryParams(c)
2023-09-18 23:57:18 +00:00
if routeName == "documents" {
documents, err := api.DB.Queries.GetDocumentsWithStats(api.DB.Ctx, database.GetDocumentsWithStatsParams{
UserID: rUser.(string),
Offset: (*qParams.Page - 1) * *qParams.Limit,
Limit: *qParams.Limit,
})
if err != nil {
log.Error("[createAppResourcesRoute] GetDocumentsWithStats DB Error:", err)
2023-09-18 23:57:18 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
templateVars["Data"] = documents
} else if routeName == "document" {
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"})
return
}
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
UserID: rUser.(string),
DocumentID: rDocID.DocumentID,
})
if err != nil {
log.Error("[createAppResourcesRoute] GetDocumentWithStats DB Error:", err)
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
templateVars["Data"] = document
2023-09-23 18:14:57 +00:00
} else if routeName == "document-edit" {
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"})
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"})
return
}
if rDocEdit.Author == nil && rDocEdit.Title == nil && rDocEdit.Description == nil && rDocEdit.Cover == nil {
log.Error("[createAppResourcesRoute] Missing Form Values")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
// TODO - Handle Cover
if rDocEdit.Cover != nil {
}
// Update Document
if _, err := api.DB.Queries.UpsertDocument(api.DB.Ctx, database.UpsertDocumentParams{
ID: rDocID.DocumentID,
Title: api.sanitizeInput(rDocEdit.Title),
Author: api.sanitizeInput(rDocEdit.Author),
Description: api.sanitizeInput(rDocEdit.Description),
}); err != nil {
log.Error("[createAppResourcesRoute] UpsertDocument DB Error:", err)
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
c.Redirect(http.StatusFound, "./")
return
} else if routeName == "activity" {
activityFilter := database.GetActivityParams{
UserID: rUser.(string),
Offset: (*qParams.Page - 1) * *qParams.Limit,
Limit: *qParams.Limit,
}
if qParams.Document != nil {
activityFilter.DocFilter = true
activityFilter.DocumentID = *qParams.Document
}
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"})
return
}
templateVars["Data"] = activity
2023-09-18 23:57:18 +00:00
} else if routeName == "home" {
start_time := time.Now()
weekly_streak, err := api.DB.Queries.GetUserWindowStreaks(api.DB.Ctx, database.GetUserWindowStreaksParams{
2023-09-18 23:57:18 +00:00
UserID: rUser.(string),
Window: "WEEK",
})
if err != nil {
log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
}
log.Info("GetUserWindowStreaks - WEEK - ", time.Since(start_time))
start_time = time.Now()
2023-09-18 23:57:18 +00:00
daily_streak, err := api.DB.Queries.GetUserWindowStreaks(api.DB.Ctx, database.GetUserWindowStreaksParams{
2023-09-18 23:57:18 +00:00
UserID: rUser.(string),
Window: "DAY",
})
if err != nil {
log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
2023-09-18 23:57:18 +00:00
}
log.Info("GetUserWindowStreaks - DAY - ", time.Since(start_time))
2023-09-18 23:57:18 +00:00
start_time = time.Now()
database_info, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, rUser.(string))
log.Info("GetDatabaseInfo - ", time.Since(start_time))
start_time = time.Now()
read_graph_data, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, rUser.(string))
log.Info("GetDailyReadStats - ", time.Since(start_time))
2023-09-18 23:57:18 +00:00
templateVars["Data"] = gin.H{
"DailyStreak": daily_streak,
"WeeklyStreak": weekly_streak,
"DatabaseInfo": database_info,
"GraphData": read_graph_data,
}
} else if routeName == "login" {
templateVars["RegistrationEnabled"] = api.Config.RegistrationEnabled
2023-09-18 23:57:18 +00:00
}
c.HTML(http.StatusOK, routeName, templateVars)
}
}
func (api *API) getDocumentCover(c *gin.Context) {
var rDoc requestDocumentID
if err := c.ShouldBindUri(&rDoc); err != nil {
log.Error("[getDocumentCover] Invalid URI Bind")
2023-09-18 23:57:18 +00:00
c.AbortWithStatus(http.StatusBadRequest)
return
}
// Validate Document Exists in DB
document, err := api.DB.Queries.GetDocument(api.DB.Ctx, rDoc.DocumentID)
if err != nil {
log.Error("[getDocumentCover] GetDocument DB Error:", err)
2023-09-18 23:57:18 +00:00
c.AbortWithStatus(http.StatusBadRequest)
return
}
// Handle Identified Document
if document.Olid != nil {
if *document.Olid == "UNKNOWN" {
c.File("./assets/no-cover.jpg")
2023-09-18 23:57:18 +00:00
return
}
// Derive Path
fileName := "." + filepath.Clean(fmt.Sprintf("/%s.jpg", *document.Olid))
safePath := filepath.Join(api.Config.DataPath, "covers", fileName)
// Validate File Exists
_, err = os.Stat(safePath)
if err != nil {
c.File("./assets/no-cover.jpg")
2023-09-18 23:57:18 +00:00
return
}
c.File(safePath)
return
}
/*
This is a bit convoluted because we want to ensure we set the OLID to
UNKNOWN if there are any errors. This will ideally prevent us from
hitting the OpenLibrary API multiple times in the future.
*/
var coverID string = "UNKNOWN"
var coverFilePath string
2023-09-18 23:57:18 +00:00
// Identify Documents & Save Covers
2023-09-23 18:14:57 +00:00
metadataResults, err := metadata.GetMetadata(metadata.MetadataInfo{
Title: document.Title,
Author: document.Author,
2023-09-23 18:14:57 +00:00
})
if err == nil && len(metadataResults) > 0 && metadataResults[0].GBID != nil {
firstResult := metadataResults[0]
// Derive & Sanitize File Name
2023-09-23 18:14:57 +00:00
fileName := "." + filepath.Clean(fmt.Sprintf("/%s.jpg", *firstResult.GBID))
// Generate Storage Path
coverFilePath = filepath.Join(api.Config.DataPath, "covers", fileName)
2023-09-23 18:14:57 +00:00
err := metadata.SaveCover(*firstResult.GBID, coverFilePath)
2023-09-18 23:57:18 +00:00
if err == nil {
2023-09-23 18:14:57 +00:00
coverID = *firstResult.GBID
log.Info("Title:", *firstResult.Title)
log.Info("Author:", *firstResult.Author)
log.Info("Description:", *firstResult.Description)
log.Info("IDs:", firstResult.ISBN)
2023-09-18 23:57:18 +00:00
}
}
// coverIDs, err := metadata.GetCoverOLIDs(document.Title, document.Author)
// if err == nil && len(coverIDs) > 0 {
// coverFilePath, err = metadata.DownloadAndSaveCover(coverIDs[0], api.Config.DataPath)
// if err == nil {
// coverID = coverIDs[0]
// }
// }
2023-09-18 23:57:18 +00:00
// Upsert Document
if _, err = api.DB.Queries.UpsertDocument(api.DB.Ctx, database.UpsertDocumentParams{
ID: document.ID,
Olid: &coverID,
}); err != nil {
log.Warn("[getDocumentCover] UpsertDocument DB Error:", err)
2023-09-18 23:57:18 +00:00
}
// Return Unknown Cover
if coverID == "UNKNOWN" {
c.File("./assets/no-cover.jpg")
2023-09-18 23:57:18 +00:00
return
}
c.File(coverFilePath)
2023-09-18 23:57:18 +00:00
}
2023-09-23 18:14:57 +00:00
// DELETE /api/documents/:document
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"})
return
}
// TODO
}
// POST /api/documents/:document/identify
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"})
return
}
isbn := strings.TrimSpace(c.PostForm("ISBN"))
if isbn == "" {
log.Error("[identifyDocument] Invalid Form")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
metadataResults, err := metadata.GetMetadata(metadata.MetadataInfo{
ISBN: []*string{&isbn},
})
if err != nil || len(metadataResults) == 0 {
log.Error("[identifyDocument] Metadata Error")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Metadata Error"})
return
}
// TODO
firstResult := metadataResults[0]
if firstResult.Title != nil {
log.Info("Title:", *firstResult.Title)
}
if firstResult.Author != nil {
log.Info("Author:", *firstResult.Author)
}
if firstResult.Description != nil {
log.Info("Description:", *firstResult.Description)
}
for _, val := range firstResult.ISBN {
log.Info("ISBN:", *val)
}
c.Redirect(http.StatusFound, "/")
}