wip
This commit is contained in:
46
api/api.go
46
api/api.go
@@ -136,24 +136,30 @@ func (api *API) registerWebAppRoutes(router *gin.Engine) {
|
||||
router.GET("/favicon.ico", api.appFaviconIcon)
|
||||
router.GET("/sw.js", api.appServiceWorker)
|
||||
|
||||
// Local / offline static pages (no template, no auth)
|
||||
// Web App - Offline
|
||||
router.GET("/local", api.appLocalDocuments)
|
||||
|
||||
// Reader (reader page, document progress, devices)
|
||||
// Web App - Reader
|
||||
router.GET("/reader", api.appDocumentReader)
|
||||
router.GET("/reader/devices", api.authWebAppMiddleware, api.appGetDevices)
|
||||
router.GET("/reader/progress/:document", api.authWebAppMiddleware, api.appGetDocumentProgress)
|
||||
|
||||
// Web app
|
||||
router.GET("/", api.authWebAppMiddleware, api.appGetHome)
|
||||
router.GET("/activity", api.authWebAppMiddleware, api.appGetActivity)
|
||||
router.GET("/progress", api.authWebAppMiddleware, api.appGetProgress)
|
||||
router.GET("/documents", api.authWebAppMiddleware, api.appGetDocuments)
|
||||
router.GET("/documents/:document", api.authWebAppMiddleware, api.appGetDocument)
|
||||
router.GET("/documents/:document/cover", api.authWebAppMiddleware, api.createGetCoverHandler(appErrorPage))
|
||||
router.GET("/documents/:document/file", api.authWebAppMiddleware, api.createDownloadDocumentHandler(appErrorPage))
|
||||
router.GET("/login", api.appGetLogin)
|
||||
// Web App - Templates
|
||||
router.GET("/", api.authWebAppMiddleware, api.appGetHomeNew) // DONE
|
||||
router.GET("/activity", api.authWebAppMiddleware, api.appGetActivityNew) // DONE
|
||||
router.GET("/progress", api.authWebAppMiddleware, api.appGetProgressNew) // DONE
|
||||
router.GET("/documents", api.authWebAppMiddleware, api.appGetDocumentsNew) // DONE
|
||||
router.GET("/documents/:document", api.authWebAppMiddleware, api.appGetDocumentNew) // DONE
|
||||
|
||||
// Web App - Other Routes
|
||||
router.GET("/documents/:document/cover", api.authWebAppMiddleware, api.createGetCoverHandler(appErrorPage)) // DONE
|
||||
router.GET("/documents/:document/file", api.authWebAppMiddleware, api.createDownloadDocumentHandler(appErrorPage)) // DONE
|
||||
router.GET("/logout", api.authWebAppMiddleware, api.appAuthLogout)
|
||||
router.POST("/login", api.appAuthLogin) // DONE
|
||||
router.POST("/register", api.appAuthRegister) // DONE
|
||||
|
||||
// TODO
|
||||
router.GET("/login", api.appGetLogin)
|
||||
router.GET("/register", api.appGetRegister)
|
||||
router.GET("/settings", api.authWebAppMiddleware, api.appGetSettings)
|
||||
router.GET("/admin/logs", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdminLogs)
|
||||
@@ -163,8 +169,6 @@ func (api *API) registerWebAppRoutes(router *gin.Engine) {
|
||||
router.POST("/admin/users", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appUpdateAdminUsers)
|
||||
router.GET("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdmin)
|
||||
router.POST("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appPerformAdminAction)
|
||||
router.POST("/login", api.appAuthLogin)
|
||||
router.POST("/register", api.appAuthRegister)
|
||||
|
||||
// Demo mode enabled configuration
|
||||
if api.cfg.DemoMode {
|
||||
@@ -174,17 +178,19 @@ func (api *API) registerWebAppRoutes(router *gin.Engine) {
|
||||
router.POST("/documents/:document/identify", api.authWebAppMiddleware, api.appDemoModeError)
|
||||
router.POST("/settings", api.authWebAppMiddleware, api.appDemoModeError)
|
||||
} else {
|
||||
router.POST("/documents", api.authWebAppMiddleware, api.appUploadNewDocument)
|
||||
router.POST("/documents/:document/delete", api.authWebAppMiddleware, api.appDeleteDocument)
|
||||
router.POST("/documents/:document/edit", api.authWebAppMiddleware, api.appEditDocument)
|
||||
router.POST("/documents/:document/identify", api.authWebAppMiddleware, api.appIdentifyDocument)
|
||||
router.POST("/settings", api.authWebAppMiddleware, api.appEditSettings)
|
||||
router.POST("/documents", api.authWebAppMiddleware, api.appUploadNewDocument) // DONE
|
||||
router.POST("/documents/:document/delete", api.authWebAppMiddleware, api.appDeleteDocument) // DONE
|
||||
router.POST("/documents/:document/edit", api.authWebAppMiddleware, api.appEditDocument) // DONE
|
||||
router.POST("/documents/:document/identify", api.authWebAppMiddleware, api.appIdentifyDocumentNew) // DONE
|
||||
router.POST("/settings", api.authWebAppMiddleware, api.appEditSettings) // TODO
|
||||
}
|
||||
|
||||
// Search enabled configuration
|
||||
if api.cfg.SearchEnabled {
|
||||
router.GET("/search", api.authWebAppMiddleware, api.appGetSearch)
|
||||
router.POST("/search", api.authWebAppMiddleware, api.appSaveNewDocument)
|
||||
router.GET("/search", api.authWebAppMiddleware, api.appGetSearchNew) // WIP
|
||||
|
||||
router.GET("/search-old", api.authWebAppMiddleware, api.appGetSearch) // TODO
|
||||
router.POST("/search", api.authWebAppMiddleware, api.appSaveNewDocument) // TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,9 +138,10 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
var directories []string
|
||||
for _, item := range rAdminAction.BackupTypes {
|
||||
if item == backupCovers {
|
||||
switch item {
|
||||
case backupCovers:
|
||||
directories = append(directories, "covers")
|
||||
} else if item == backupDocuments {
|
||||
case backupDocuments:
|
||||
directories = append(directories, "documents")
|
||||
}
|
||||
}
|
||||
|
||||
451
api/app-routes-new.go
Normal file
451
api/app-routes-new.go
Normal file
@@ -0,0 +1,451 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/metadata"
|
||||
"reichard.io/antholume/pkg/formatters"
|
||||
"reichard.io/antholume/pkg/ptr"
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/pkg/utils"
|
||||
"reichard.io/antholume/search"
|
||||
"reichard.io/antholume/web/components/layout"
|
||||
"reichard.io/antholume/web/components/stats"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages"
|
||||
)
|
||||
|
||||
func (api *API) appGetHomeNew(c *gin.Context) {
|
||||
_, auth := api.getBaseTemplateVars("home", c)
|
||||
|
||||
start := time.Now()
|
||||
dailyStats, err := api.db.Queries.GetDailyReadStats(c, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("GetDailyReadStats DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDailyReadStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("GetDailyReadStats DB Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
databaseInfo, err := api.db.Queries.GetDatabaseInfo(c, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("GetDatabaseInfo DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDatabaseInfo DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("GetDatabaseInfo DB Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
streaks, err := api.db.Queries.GetUserStreaks(c, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("GetUserStreaks DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStreaks DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("GetUserStreaks DB Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
userStatistics, err := api.db.Queries.GetUserStatistics(c)
|
||||
if err != nil {
|
||||
log.Error("GetUserStatistics DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStatistics DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("GetUserStatistics DB Performance: ", time.Since(start))
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Home{
|
||||
Leaderboard: arrangeUserStatisticsNew(userStatistics),
|
||||
Streaks: streaks,
|
||||
DailyStats: dailyStats,
|
||||
RecordInfo: &databaseInfo,
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) appGetDocumentsNew(c *gin.Context) {
|
||||
_, auth := api.getBaseTemplateVars("documents", c)
|
||||
qParams := bindQueryParams(c, 9)
|
||||
|
||||
var query *string
|
||||
if qParams.Search != nil && *qParams.Search != "" {
|
||||
search := "%" + *qParams.Search + "%"
|
||||
query = &search
|
||||
}
|
||||
|
||||
documents, err := api.db.Queries.GetDocumentsWithStats(c, database.GetDocumentsWithStatsParams{
|
||||
UserID: auth.UserName,
|
||||
Query: query,
|
||||
Deleted: ptr.Of(false),
|
||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||
Limit: *qParams.Limit,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetDocumentsWithStats DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsWithStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
length, err := api.db.Queries.GetDocumentsSize(c, query)
|
||||
if err != nil {
|
||||
log.Error("GetDocumentsSize DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsSize DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = api.getDocumentsWordCount(c, documents); err != nil {
|
||||
log.Error("Unable to Get Word Counts: ", err)
|
||||
}
|
||||
|
||||
totalPages := int64(math.Ceil(float64(length) / float64(*qParams.Limit)))
|
||||
nextPage := *qParams.Page + 1
|
||||
previousPage := *qParams.Page - 1
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Documents{
|
||||
Data: sliceutils.Map(documents, convertDBDocToUI),
|
||||
Previous: utils.Ternary(previousPage >= 0, int(previousPage), 0),
|
||||
Next: utils.Ternary(nextPage <= totalPages, int(nextPage), 0),
|
||||
Limit: int(ptr.Deref(qParams.Limit)),
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) appGetDocumentNew(c *gin.Context) {
|
||||
_, auth := api.getBaseTemplateVars("document", c)
|
||||
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||
return
|
||||
}
|
||||
|
||||
document, err := api.db.GetDocument(c, rDocID.DocumentID, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("GetDocument DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Document{
|
||||
Data: convertDBDocToUI(*document),
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) appGetActivityNew(c *gin.Context) {
|
||||
_, auth := api.getBaseTemplateVars("activity", c)
|
||||
qParams := bindQueryParams(c, 15)
|
||||
|
||||
activityFilter := database.GetActivityParams{
|
||||
UserID: auth.UserName,
|
||||
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(c, activityFilter)
|
||||
if err != nil {
|
||||
log.Error("GetActivity DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Activity{
|
||||
Data: sliceutils.Map(activity, convertDBActivityToUI),
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) appGetProgressNew(c *gin.Context) {
|
||||
_, auth := api.getBaseTemplateVars("progress", c)
|
||||
|
||||
qParams := bindQueryParams(c, 15)
|
||||
|
||||
progressFilter := database.GetProgressParams{
|
||||
UserID: auth.UserName,
|
||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||
Limit: *qParams.Limit,
|
||||
}
|
||||
|
||||
if qParams.Document != nil {
|
||||
progressFilter.DocFilter = true
|
||||
progressFilter.DocumentID = *qParams.Document
|
||||
}
|
||||
|
||||
progress, err := api.db.Queries.GetProgress(c, progressFilter)
|
||||
if err != nil {
|
||||
log.Error("GetProgress DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Progress{
|
||||
Data: sliceutils.Map(progress, convertDBProgressToUI),
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) appIdentifyDocumentNew(c *gin.Context) {
|
||||
var rDocID requestDocumentID
|
||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||
log.Error("Invalid URI Bind")
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
// Disallow Empty Strings
|
||||
if rDocIdentify.Title != nil && strings.TrimSpace(*rDocIdentify.Title) == "" {
|
||||
rDocIdentify.Title = nil
|
||||
}
|
||||
if rDocIdentify.Author != nil && strings.TrimSpace(*rDocIdentify.Author) == "" {
|
||||
rDocIdentify.Author = nil
|
||||
}
|
||||
if rDocIdentify.ISBN != nil && strings.TrimSpace(*rDocIdentify.ISBN) == "" {
|
||||
rDocIdentify.ISBN = nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
return
|
||||
}
|
||||
|
||||
// Get Template Variables
|
||||
_, auth := api.getBaseTemplateVars("document", c)
|
||||
|
||||
// Get Metadata
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SourceGoogleBooks, metadata.MetadataInfo{
|
||||
Title: rDocIdentify.Title,
|
||||
Author: rDocIdentify.Author,
|
||||
ISBN10: rDocIdentify.ISBN,
|
||||
ISBN13: rDocIdentify.ISBN,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Search Metadata Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Search Metadata Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
var errorMsg *string
|
||||
firstResult, found := sliceutils.First(metadataResults)
|
||||
if found {
|
||||
// Store First Metadata Result
|
||||
if _, err = api.db.Queries.AddMetadata(c, database.AddMetadataParams{
|
||||
DocumentID: rDocID.DocumentID,
|
||||
Title: firstResult.Title,
|
||||
Author: firstResult.Author,
|
||||
Description: firstResult.Description,
|
||||
Gbid: firstResult.SourceID,
|
||||
Olid: nil,
|
||||
Isbn10: firstResult.ISBN10,
|
||||
Isbn13: firstResult.ISBN13,
|
||||
}); err != nil {
|
||||
log.Error("AddMetadata DB Error: ", err)
|
||||
}
|
||||
} else {
|
||||
errorMsg = ptr.Of("No Metadata Found")
|
||||
}
|
||||
|
||||
document, err := api.db.GetDocument(c, rDocID.DocumentID, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("GetDocument DB Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Document{
|
||||
Data: convertDBDocToUI(*document),
|
||||
Search: convertMetaToUI(firstResult, errorMsg),
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Tabs:
|
||||
// - General (Import, Backup & Restore, Version (githash?), Stats?)
|
||||
// - Users
|
||||
// - Metadata
|
||||
func (api *API) appGetSearchNew(c *gin.Context) {
|
||||
_, auth := api.getBaseTemplateVars("search", c)
|
||||
|
||||
var sParams searchParams
|
||||
err := c.BindQuery(&sParams)
|
||||
if err != nil {
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Invalid Form Bind: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Only Handle Query
|
||||
var searchResults []models.SearchResult
|
||||
var searchError string
|
||||
if sParams.Query != nil && sParams.Source != nil {
|
||||
results, err := search.SearchBook(*sParams.Query, *sParams.Source)
|
||||
if err != nil {
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Search Error: %v", err))
|
||||
return
|
||||
}
|
||||
searchResults = sliceutils.Map(results, convertSearchToUI)
|
||||
} else if sParams.Query != nil || sParams.Source != nil {
|
||||
searchError = "Invailid Query"
|
||||
}
|
||||
|
||||
err = layout.Layout(
|
||||
pages.Search{
|
||||
Results: searchResults,
|
||||
Source: ptr.Deref(sParams.Source),
|
||||
Query: ptr.Deref(sParams.Query),
|
||||
Error: searchError,
|
||||
},
|
||||
layout.LayoutOptions{
|
||||
Username: auth.UserName,
|
||||
IsAdmin: auth.IsAdmin,
|
||||
SearchEnabled: api.cfg.SearchEnabled,
|
||||
Version: api.cfg.Version,
|
||||
},
|
||||
).Render(c.Writer)
|
||||
if err != nil {
|
||||
log.Error("Render Error: ", err)
|
||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Unknown Error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func sortItem[T cmp.Ordered](
|
||||
data []database.GetUserStatisticsRow,
|
||||
accessor func(s database.GetUserStatisticsRow) T,
|
||||
formatter func(s T) string,
|
||||
) []stats.LeaderboardItem {
|
||||
sort.SliceStable(data, func(i, j int) bool {
|
||||
return accessor(data[i]) > accessor(data[j])
|
||||
})
|
||||
|
||||
var items []stats.LeaderboardItem
|
||||
for _, s := range data {
|
||||
items = append(items, stats.LeaderboardItem{
|
||||
UserID: s.UserID,
|
||||
Value: formatter(accessor(s)),
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func arrangeUserStatisticsNew(data []database.GetUserStatisticsRow) []stats.LeaderboardData {
|
||||
wpmFormatter := func(v float64) string { return fmt.Sprintf("%.2f WPM", v) }
|
||||
return []stats.LeaderboardData{
|
||||
{
|
||||
Name: "WPM",
|
||||
All: sortItem(data, func(r database.GetUserStatisticsRow) float64 { return r.TotalWpm }, wpmFormatter),
|
||||
Year: sortItem(data, func(r database.GetUserStatisticsRow) float64 { return r.YearlyWpm }, wpmFormatter),
|
||||
Month: sortItem(data, func(r database.GetUserStatisticsRow) float64 { return r.MonthlyWpm }, wpmFormatter),
|
||||
Week: sortItem(data, func(r database.GetUserStatisticsRow) float64 { return r.WeeklyWpm }, wpmFormatter),
|
||||
},
|
||||
{
|
||||
Name: "Words",
|
||||
All: sortItem(data, func(r database.GetUserStatisticsRow) int64 { return r.TotalWordsRead }, formatters.FormatNumber),
|
||||
Year: sortItem(data, func(r database.GetUserStatisticsRow) int64 { return r.YearlyWordsRead }, formatters.FormatNumber),
|
||||
Month: sortItem(data, func(r database.GetUserStatisticsRow) int64 { return r.MonthlyWordsRead }, formatters.FormatNumber),
|
||||
Week: sortItem(data, func(r database.GetUserStatisticsRow) int64 { return r.WeeklyWordsRead }, formatters.FormatNumber),
|
||||
},
|
||||
{
|
||||
Name: "Duration",
|
||||
All: sortItem(data, func(r database.GetUserStatisticsRow) time.Duration {
|
||||
return time.Duration(r.TotalSeconds) * time.Second
|
||||
}, formatters.FormatDuration),
|
||||
Year: sortItem(data, func(r database.GetUserStatisticsRow) time.Duration {
|
||||
return time.Duration(r.YearlySeconds) * time.Second
|
||||
}, formatters.FormatDuration),
|
||||
Month: sortItem(data, func(r database.GetUserStatisticsRow) time.Duration {
|
||||
return time.Duration(r.MonthlySeconds) * time.Second
|
||||
}, formatters.FormatDuration),
|
||||
Week: sortItem(data, func(r database.GetUserStatisticsRow) time.Duration {
|
||||
return time.Duration(r.WeeklySeconds) * time.Second
|
||||
}, formatters.FormatDuration),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -654,7 +654,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
templateVars, auth := api.getBaseTemplateVars("document", c)
|
||||
|
||||
// Get Metadata
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SOURCE_GBOOK, metadata.MetadataInfo{
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SourceGoogleBooks, metadata.MetadataInfo{
|
||||
Title: rDocIdentify.Title,
|
||||
Author: rDocIdentify.Author,
|
||||
ISBN10: rDocIdentify.ISBN,
|
||||
@@ -669,7 +669,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
||||
Title: firstResult.Title,
|
||||
Author: firstResult.Author,
|
||||
Description: firstResult.Description,
|
||||
Gbid: firstResult.ID,
|
||||
Gbid: firstResult.SourceID,
|
||||
Olid: nil,
|
||||
Isbn10: firstResult.ISBN10,
|
||||
Isbn13: firstResult.ISBN13,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -464,9 +465,7 @@ func (api *API) rotateAllAuthHashes(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Transaction Succeeded -> Update Cache
|
||||
for user, hash := range newAuthHashCache {
|
||||
api.userAuthCache[user] = hash
|
||||
}
|
||||
maps.Copy(api.userAuthCache, newAuthHashCache)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -98,20 +98,20 @@ func (api *API) createGetCoverHandler(errorFunc func(*gin.Context, int, string))
|
||||
}
|
||||
|
||||
// Attempt Metadata
|
||||
var coverDir string = filepath.Join(api.cfg.DataPath, "covers")
|
||||
var coverFile string = "UNKNOWN"
|
||||
coverDir := filepath.Join(api.cfg.DataPath, "covers")
|
||||
coverFile := "UNKNOWN"
|
||||
|
||||
// Identify Documents & Save Covers
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SOURCE_GBOOK, metadata.MetadataInfo{
|
||||
metadataResults, err := metadata.SearchMetadata(metadata.SourceGoogleBooks, metadata.MetadataInfo{
|
||||
Title: document.Title,
|
||||
Author: document.Author,
|
||||
})
|
||||
|
||||
if err == nil && len(metadataResults) > 0 && metadataResults[0].ID != nil {
|
||||
if err == nil && len(metadataResults) > 0 && metadataResults[0].SourceID != nil {
|
||||
firstResult := metadataResults[0]
|
||||
|
||||
// Save Cover
|
||||
fileName, err := metadata.CacheCover(*firstResult.ID, coverDir, document.ID, false)
|
||||
fileName, err := metadata.CacheCover(*firstResult.SourceID, coverDir, document.ID, false)
|
||||
if err == nil {
|
||||
coverFile = *fileName
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (api *API) createGetCoverHandler(errorFunc func(*gin.Context, int, string))
|
||||
Title: firstResult.Title,
|
||||
Author: firstResult.Author,
|
||||
Description: firstResult.Description,
|
||||
Gbid: firstResult.ID,
|
||||
Gbid: firstResult.SourceID,
|
||||
Olid: nil,
|
||||
Isbn10: firstResult.ISBN10,
|
||||
Isbn13: firstResult.ISBN13,
|
||||
|
||||
76
api/convert.go
Normal file
76
api/convert.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/metadata"
|
||||
"reichard.io/antholume/pkg/ptr"
|
||||
"reichard.io/antholume/pkg/utils"
|
||||
"reichard.io/antholume/search"
|
||||
"reichard.io/antholume/web/models"
|
||||
)
|
||||
|
||||
func convertDBDocToUI(r database.GetDocumentsWithStatsRow) models.Document {
|
||||
return models.Document{
|
||||
ID: r.ID,
|
||||
Title: ptr.Deref(r.Title),
|
||||
Author: ptr.Deref(r.Author),
|
||||
ISBN10: ptr.Deref(r.Isbn10),
|
||||
ISBN13: ptr.Deref(r.Isbn13),
|
||||
Description: ptr.Deref(r.Description),
|
||||
Percentage: r.Percentage,
|
||||
WPM: r.Wpm,
|
||||
Words: r.Words,
|
||||
TotalTimeRead: time.Duration(r.TotalTimeSeconds) * time.Second,
|
||||
TimePerPercent: time.Duration(r.SecondsPerPercent) * time.Second,
|
||||
HasFile: ptr.Deref(r.Filepath) != "",
|
||||
}
|
||||
}
|
||||
|
||||
func convertMetaToUI(m metadata.MetadataInfo, errorMsg *string) *models.DocumentMetadata {
|
||||
return &models.DocumentMetadata{
|
||||
SourceID: ptr.Deref(m.SourceID),
|
||||
ISBN10: ptr.Deref(m.ISBN10),
|
||||
ISBN13: ptr.Deref(m.ISBN13),
|
||||
Title: ptr.Deref(m.Title),
|
||||
Author: ptr.Deref(m.Author),
|
||||
Description: ptr.Deref(m.Description),
|
||||
Source: m.Source,
|
||||
Error: errorMsg,
|
||||
}
|
||||
}
|
||||
|
||||
func convertDBActivityToUI(r database.GetActivityRow) models.Activity {
|
||||
return models.Activity{
|
||||
ID: r.DocumentID,
|
||||
Author: utils.FirstNonZero(ptr.Deref(r.Author), "N/A"),
|
||||
Title: utils.FirstNonZero(ptr.Deref(r.Title), "N/A"),
|
||||
StartTime: r.StartTime,
|
||||
Duration: time.Duration(r.Duration) * time.Second,
|
||||
Percentage: r.EndPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
func convertDBProgressToUI(r database.GetProgressRow) models.Progress {
|
||||
return models.Progress{
|
||||
ID: r.DocumentID,
|
||||
Author: utils.FirstNonZero(ptr.Deref(r.Author), "N/A"),
|
||||
Title: utils.FirstNonZero(ptr.Deref(r.Title), "N/A"),
|
||||
DeviceName: r.DeviceName,
|
||||
Percentage: r.Percentage,
|
||||
CreatedAt: r.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func convertSearchToUI(r search.SearchItem) models.SearchResult {
|
||||
return models.SearchResult{
|
||||
ID: r.ID,
|
||||
Title: r.Title,
|
||||
Author: r.Author,
|
||||
Series: r.Series,
|
||||
FileType: r.FileType,
|
||||
FileSize: r.FileSize,
|
||||
UploadDate: r.UploadDate,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user