Compare commits
3 Commits
456b6e457c
...
7937890acd
Author | SHA1 | Date | |
---|---|---|---|
7937890acd | |||
938dd69e5e | |||
7c92c346fa |
@ -3,7 +3,7 @@ FROM alpine AS alpine
|
|||||||
RUN apk update && apk add --no-cache ca-certificates tzdata
|
RUN apk update && apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
# Build Image
|
# Build Image
|
||||||
FROM golang:1.21 AS build
|
FROM golang:1.24 AS build
|
||||||
|
|
||||||
# Create Package Directory
|
# Create Package Directory
|
||||||
RUN mkdir -p /opt/antholume
|
RUN mkdir -p /opt/antholume
|
||||||
|
@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -112,7 +113,7 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
|||||||
// 2. Select all / deselect?
|
// 2. Select all / deselect?
|
||||||
case adminCacheTables:
|
case adminCacheTables:
|
||||||
go func() {
|
go func() {
|
||||||
err := api.db.CacheTempTables()
|
err := api.db.CacheTempTables(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to cache temp tables: ", err)
|
log.Error("Unable to cache temp tables: ", err)
|
||||||
}
|
}
|
||||||
@ -122,7 +123,7 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
case adminBackup:
|
case adminBackup:
|
||||||
// Vacuum
|
// Vacuum
|
||||||
_, err := api.db.DB.ExecContext(api.db.Ctx, "VACUUM;")
|
_, err := api.db.DB.ExecContext(c, "VACUUM;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to vacuum DB: ", err)
|
log.Error("Unable to vacuum DB: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database")
|
appErrorPage(c, http.StatusInternalServerError, "Unable to vacuum database")
|
||||||
@ -144,7 +145,7 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := api.createBackup(w, directories)
|
err := api.createBackup(c, w, directories)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Backup Error: ", err)
|
log.Error("Backup Error: ", err)
|
||||||
}
|
}
|
||||||
@ -261,7 +262,7 @@ func (api *API) appGetAdminLogs(c *gin.Context) {
|
|||||||
func (api *API) appGetAdminUsers(c *gin.Context) {
|
func (api *API) appGetAdminUsers(c *gin.Context) {
|
||||||
templateVars, _ := api.getBaseTemplateVars("admin-users", c)
|
templateVars, _ := api.getBaseTemplateVars("admin-users", c)
|
||||||
|
|
||||||
users, err := api.db.Queries.GetUsers(api.db.Ctx)
|
users, err := api.db.Queries.GetUsers(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUsers DB Error: ", err)
|
log.Error("GetUsers DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUsers DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUsers DB Error: %v", err))
|
||||||
@ -292,11 +293,11 @@ func (api *API) appUpdateAdminUsers(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
switch rUpdate.Operation {
|
switch rUpdate.Operation {
|
||||||
case opCreate:
|
case opCreate:
|
||||||
err = api.createUser(rUpdate.User, rUpdate.Password, rUpdate.IsAdmin)
|
err = api.createUser(c, rUpdate.User, rUpdate.Password, rUpdate.IsAdmin)
|
||||||
case opUpdate:
|
case opUpdate:
|
||||||
err = api.updateUser(rUpdate.User, rUpdate.Password, rUpdate.IsAdmin)
|
err = api.updateUser(c, rUpdate.User, rUpdate.Password, rUpdate.IsAdmin)
|
||||||
case opDelete:
|
case opDelete:
|
||||||
err = api.deleteUser(rUpdate.User)
|
err = api.deleteUser(c, rUpdate.User)
|
||||||
default:
|
default:
|
||||||
appErrorPage(c, http.StatusNotFound, "Unknown user operation")
|
appErrorPage(c, http.StatusNotFound, "Unknown user operation")
|
||||||
return
|
return
|
||||||
@ -307,7 +308,7 @@ func (api *API) appUpdateAdminUsers(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err := api.db.Queries.GetUsers(api.db.Ctx)
|
users, err := api.db.Queries.GetUsers(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUsers DB Error: ", err)
|
log.Error("GetUsers DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUsers DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUsers DB Error: %v", err))
|
||||||
@ -448,7 +449,7 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
iResult.Name = fmt.Sprintf("%s - %s", *fileMeta.Author, *fileMeta.Title)
|
iResult.Name = fmt.Sprintf("%s - %s", *fileMeta.Author, *fileMeta.Title)
|
||||||
|
|
||||||
// Check already exists
|
// Check already exists
|
||||||
_, err = qtx.GetDocument(api.db.Ctx, *fileMeta.PartialMD5)
|
_, err = qtx.GetDocument(c, *fileMeta.PartialMD5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Warnf("document already exists: %s", *fileMeta.PartialMD5)
|
log.Warnf("document already exists: %s", *fileMeta.PartialMD5)
|
||||||
iResult.Status = importExists
|
iResult.Status = importExists
|
||||||
@ -492,7 +493,7 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert document
|
// Upsert document
|
||||||
if _, err = qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err = qtx.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: *fileMeta.PartialMD5,
|
ID: *fileMeta.PartialMD5,
|
||||||
Title: fileMeta.Title,
|
Title: fileMeta.Title,
|
||||||
Author: fileMeta.Author,
|
Author: fileMeta.Author,
|
||||||
@ -627,7 +628,7 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
|||||||
|
|
||||||
// Save Backup File
|
// Save Backup File
|
||||||
w := bufio.NewWriter(backupFile)
|
w := bufio.NewWriter(backupFile)
|
||||||
err = api.createBackup(w, []string{"covers", "documents"})
|
err = api.createBackup(c, w, []string{"covers", "documents"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to save backup file: ", err)
|
log.Error("Unable to save backup file: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, "Unable to save backup file")
|
appErrorPage(c, http.StatusInternalServerError, "Unable to save backup file")
|
||||||
@ -650,13 +651,13 @@ func (api *API) processRestoreFile(rAdminAction requestAdminAction, c *gin.Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reinit DB
|
// Reinit DB
|
||||||
if err := api.db.Reload(); err != nil {
|
if err := api.db.Reload(c); err != nil {
|
||||||
appErrorPage(c, http.StatusInternalServerError, "Unable to reload DB")
|
appErrorPage(c, http.StatusInternalServerError, "Unable to reload DB")
|
||||||
log.Panicf("Unable to reload DB: %v", err)
|
log.Panicf("Unable to reload DB: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate Auth Hashes
|
// Rotate Auth Hashes
|
||||||
if err := api.rotateAllAuthHashes(); err != nil {
|
if err := api.rotateAllAuthHashes(c); err != nil {
|
||||||
appErrorPage(c, http.StatusInternalServerError, "Unable to rotate hashes")
|
appErrorPage(c, http.StatusInternalServerError, "Unable to rotate hashes")
|
||||||
log.Panicf("Unable to rotate auth hashes: %v", err)
|
log.Panicf("Unable to rotate auth hashes: %v", err)
|
||||||
}
|
}
|
||||||
@ -717,9 +718,9 @@ func (api *API) removeData() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) createBackup(w io.Writer, directories []string) error {
|
func (api *API) createBackup(ctx context.Context, w io.Writer, directories []string) error {
|
||||||
// Vacuum DB
|
// Vacuum DB
|
||||||
_, err := api.db.DB.ExecContext(api.db.Ctx, "VACUUM;")
|
_, err := api.db.DB.ExecContext(ctx, "VACUUM;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Unable to vacuum database")
|
return errors.Wrap(err, "Unable to vacuum database")
|
||||||
}
|
}
|
||||||
@ -792,8 +793,8 @@ func (api *API) createBackup(w io.Writer, directories []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) isLastAdmin(userID string) (bool, error) {
|
func (api *API) isLastAdmin(ctx context.Context, userID string) (bool, error) {
|
||||||
allUsers, err := api.db.Queries.GetUsers(api.db.Ctx)
|
allUsers, err := api.db.Queries.GetUsers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, fmt.Sprintf("GetUsers DB Error: %v", err))
|
return false, errors.Wrap(err, fmt.Sprintf("GetUsers DB Error: %v", err))
|
||||||
}
|
}
|
||||||
@ -809,7 +810,7 @@ func (api *API) isLastAdmin(userID string) (bool, error) {
|
|||||||
return !hasAdmin, nil
|
return !hasAdmin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) createUser(user string, rawPassword *string, isAdmin *bool) error {
|
func (api *API) createUser(ctx context.Context, user string, rawPassword *string, isAdmin *bool) error {
|
||||||
// Validate Necessary Parameters
|
// Validate Necessary Parameters
|
||||||
if rawPassword == nil || *rawPassword == "" {
|
if rawPassword == nil || *rawPassword == "" {
|
||||||
return fmt.Errorf("password can't be empty")
|
return fmt.Errorf("password can't be empty")
|
||||||
@ -844,7 +845,7 @@ func (api *API) createUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
createParams.AuthHash = &authHash
|
createParams.AuthHash = &authHash
|
||||||
|
|
||||||
// Create user in DB
|
// Create user in DB
|
||||||
if rows, err := api.db.Queries.CreateUser(api.db.Ctx, createParams); err != nil {
|
if rows, err := api.db.Queries.CreateUser(ctx, createParams); err != nil {
|
||||||
log.Error("CreateUser DB Error:", err)
|
log.Error("CreateUser DB Error:", err)
|
||||||
return fmt.Errorf("unable to create user")
|
return fmt.Errorf("unable to create user")
|
||||||
} else if rows == 0 {
|
} else if rows == 0 {
|
||||||
@ -855,7 +856,7 @@ func (api *API) createUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) updateUser(user string, rawPassword *string, isAdmin *bool) error {
|
func (api *API) updateUser(ctx context.Context, user string, rawPassword *string, isAdmin *bool) error {
|
||||||
// Validate Necessary Parameters
|
// Validate Necessary Parameters
|
||||||
if rawPassword == nil && isAdmin == nil {
|
if rawPassword == nil && isAdmin == nil {
|
||||||
return fmt.Errorf("nothing to update")
|
return fmt.Errorf("nothing to update")
|
||||||
@ -870,7 +871,7 @@ func (api *API) updateUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
if isAdmin != nil {
|
if isAdmin != nil {
|
||||||
updateParams.Admin = *isAdmin
|
updateParams.Admin = *isAdmin
|
||||||
} else {
|
} else {
|
||||||
user, err := api.db.Queries.GetUser(api.db.Ctx, user)
|
user, err := api.db.Queries.GetUser(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, fmt.Sprintf("GetUser DB Error: %v", err))
|
return errors.Wrap(err, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||||
}
|
}
|
||||||
@ -878,7 +879,7 @@ func (api *API) updateUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check Admins - Disallow Demotion
|
// Check Admins - Disallow Demotion
|
||||||
if isLast, err := api.isLastAdmin(user); err != nil {
|
if isLast, err := api.isLastAdmin(ctx, user); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if isLast && !updateParams.Admin {
|
} else if isLast && !updateParams.Admin {
|
||||||
return fmt.Errorf("unable to demote %s - last admin", user)
|
return fmt.Errorf("unable to demote %s - last admin", user)
|
||||||
@ -908,7 +909,7 @@ func (api *API) updateUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update User
|
// Update User
|
||||||
_, err := api.db.Queries.UpdateUser(api.db.Ctx, updateParams)
|
_, err := api.db.Queries.UpdateUser(ctx, updateParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, fmt.Sprintf("UpdateUser DB Error: %v", err))
|
return errors.Wrap(err, fmt.Sprintf("UpdateUser DB Error: %v", err))
|
||||||
}
|
}
|
||||||
@ -916,9 +917,9 @@ func (api *API) updateUser(user string, rawPassword *string, isAdmin *bool) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) deleteUser(user string) error {
|
func (api *API) deleteUser(ctx context.Context, user string) error {
|
||||||
// Check Admins
|
// Check Admins
|
||||||
if isLast, err := api.isLastAdmin(user); err != nil {
|
if isLast, err := api.isLastAdmin(ctx, user); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if isLast {
|
} else if isLast {
|
||||||
return fmt.Errorf("unable to delete %s - last admin", user)
|
return fmt.Errorf("unable to delete %s - last admin", user)
|
||||||
@ -934,13 +935,13 @@ func (api *API) deleteUser(user string) error {
|
|||||||
|
|
||||||
// Save Backup File (DB Only)
|
// Save Backup File (DB Only)
|
||||||
w := bufio.NewWriter(backupFile)
|
w := bufio.NewWriter(backupFile)
|
||||||
err = api.createBackup(w, []string{})
|
err = api.createBackup(ctx, w, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete User
|
// Delete User
|
||||||
_, err = api.db.Queries.DeleteUser(api.db.Ctx, user)
|
_, err = api.db.Queries.DeleteUser(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, fmt.Sprintf("DeleteUser DB Error: %v", err))
|
return errors.Wrap(err, fmt.Sprintf("DeleteUser DB Error: %v", err))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -22,6 +23,7 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
"reichard.io/antholume/metadata"
|
"reichard.io/antholume/metadata"
|
||||||
|
"reichard.io/antholume/pkg/ptr"
|
||||||
"reichard.io/antholume/search"
|
"reichard.io/antholume/search"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,11 +111,12 @@ func (api *API) appGetDocuments(c *gin.Context) {
|
|||||||
query = &search
|
query = &search
|
||||||
}
|
}
|
||||||
|
|
||||||
documents, err := api.db.Queries.GetDocumentsWithStats(api.db.Ctx, database.GetDocumentsWithStatsParams{
|
documents, err := api.db.Queries.GetDocumentsWithStats(c, database.GetDocumentsWithStatsParams{
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
Query: query,
|
Query: query,
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
Deleted: ptr.Of(false),
|
||||||
Limit: *qParams.Limit,
|
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||||
|
Limit: *qParams.Limit,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocumentsWithStats DB Error: ", err)
|
log.Error("GetDocumentsWithStats DB Error: ", err)
|
||||||
@ -121,14 +124,14 @@ func (api *API) appGetDocuments(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
length, err := api.db.Queries.GetDocumentsSize(api.db.Ctx, query)
|
length, err := api.db.Queries.GetDocumentsSize(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocumentsSize DB Error: ", err)
|
log.Error("GetDocumentsSize DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsSize DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsSize DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = api.getDocumentsWordCount(documents); err != nil {
|
if err = api.getDocumentsWordCount(c, documents); err != nil {
|
||||||
log.Error("Unable to Get Word Counts: ", err)
|
log.Error("Unable to Get Word Counts: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,13 +163,10 @@ func (api *API) appGetDocument(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
document, err := api.db.Queries.GetDocumentWithStats(api.db.Ctx, database.GetDocumentWithStatsParams{
|
document, err := api.db.GetDocument(c, rDocID.DocumentID, auth.UserName)
|
||||||
UserID: auth.UserName,
|
|
||||||
DocumentID: rDocID.DocumentID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocumentWithStats DB Error: ", err)
|
log.Error("GetDocument DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsWithStats DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ func (api *API) appGetProgress(c *gin.Context) {
|
|||||||
progressFilter.DocumentID = *qParams.Document
|
progressFilter.DocumentID = *qParams.Document
|
||||||
}
|
}
|
||||||
|
|
||||||
progress, err := api.db.Queries.GetProgress(api.db.Ctx, progressFilter)
|
progress, err := api.db.Queries.GetProgress(c, progressFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetProgress DB Error: ", err)
|
log.Error("GetProgress DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
||||||
@ -219,7 +219,7 @@ func (api *API) appGetActivity(c *gin.Context) {
|
|||||||
activityFilter.DocumentID = *qParams.Document
|
activityFilter.DocumentID = *qParams.Document
|
||||||
}
|
}
|
||||||
|
|
||||||
activity, err := api.db.Queries.GetActivity(api.db.Ctx, activityFilter)
|
activity, err := api.db.Queries.GetActivity(c, activityFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetActivity DB Error: ", err)
|
log.Error("GetActivity DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
||||||
@ -235,7 +235,7 @@ func (api *API) appGetHome(c *gin.Context) {
|
|||||||
templateVars, auth := api.getBaseTemplateVars("home", c)
|
templateVars, auth := api.getBaseTemplateVars("home", c)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
graphData, err := api.db.Queries.GetDailyReadStats(api.db.Ctx, auth.UserName)
|
graphData, err := api.db.Queries.GetDailyReadStats(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDailyReadStats DB Error: ", err)
|
log.Error("GetDailyReadStats DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDailyReadStats DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDailyReadStats DB Error: %v", err))
|
||||||
@ -244,7 +244,7 @@ func (api *API) appGetHome(c *gin.Context) {
|
|||||||
log.Debug("GetDailyReadStats DB Performance: ", time.Since(start))
|
log.Debug("GetDailyReadStats DB Performance: ", time.Since(start))
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
databaseInfo, err := api.db.Queries.GetDatabaseInfo(api.db.Ctx, auth.UserName)
|
databaseInfo, err := api.db.Queries.GetDatabaseInfo(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDatabaseInfo DB Error: ", err)
|
log.Error("GetDatabaseInfo DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDatabaseInfo DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDatabaseInfo DB Error: %v", err))
|
||||||
@ -253,7 +253,7 @@ func (api *API) appGetHome(c *gin.Context) {
|
|||||||
log.Debug("GetDatabaseInfo DB Performance: ", time.Since(start))
|
log.Debug("GetDatabaseInfo DB Performance: ", time.Since(start))
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
streaks, err := api.db.Queries.GetUserStreaks(api.db.Ctx, auth.UserName)
|
streaks, err := api.db.Queries.GetUserStreaks(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserStreaks DB Error: ", err)
|
log.Error("GetUserStreaks DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStreaks DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStreaks DB Error: %v", err))
|
||||||
@ -262,7 +262,7 @@ func (api *API) appGetHome(c *gin.Context) {
|
|||||||
log.Debug("GetUserStreaks DB Performance: ", time.Since(start))
|
log.Debug("GetUserStreaks DB Performance: ", time.Since(start))
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
userStatistics, err := api.db.Queries.GetUserStatistics(api.db.Ctx)
|
userStatistics, err := api.db.Queries.GetUserStatistics(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserStatistics DB Error: ", err)
|
log.Error("GetUserStatistics DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStatistics DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStatistics DB Error: %v", err))
|
||||||
@ -283,14 +283,14 @@ func (api *API) appGetHome(c *gin.Context) {
|
|||||||
func (api *API) appGetSettings(c *gin.Context) {
|
func (api *API) appGetSettings(c *gin.Context) {
|
||||||
templateVars, auth := api.getBaseTemplateVars("settings", c)
|
templateVars, auth := api.getBaseTemplateVars("settings", c)
|
||||||
|
|
||||||
user, err := api.db.Queries.GetUser(api.db.Ctx, auth.UserName)
|
user, err := api.db.Queries.GetUser(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUser DB Error: ", err)
|
log.Error("GetUser DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
devices, err := api.db.Queries.GetDevices(api.db.Ctx, auth.UserName)
|
devices, err := api.db.Queries.GetDevices(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDevices DB Error: ", err)
|
log.Error("GetDevices DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||||
@ -368,7 +368,7 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
progress, err := api.db.Queries.GetDocumentProgress(api.db.Ctx, database.GetDocumentProgressParams{
|
progress, err := api.db.Queries.GetDocumentProgress(c, database.GetDocumentProgressParams{
|
||||||
DocumentID: rDoc.DocumentID,
|
DocumentID: rDoc.DocumentID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
})
|
})
|
||||||
@ -378,13 +378,10 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
document, err := api.db.Queries.GetDocumentWithStats(api.db.Ctx, database.GetDocumentWithStatsParams{
|
document, err := api.db.GetDocument(c, rDoc.DocumentID, auth.UserName)
|
||||||
UserID: auth.UserName,
|
|
||||||
DocumentID: rDoc.DocumentID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocumentWithStats DB Error: ", err)
|
log.Error("GetDocument DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentWithStats DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +401,7 @@ func (api *API) appGetDevices(c *gin.Context) {
|
|||||||
auth = data.(authData)
|
auth = data.(authData)
|
||||||
}
|
}
|
||||||
|
|
||||||
devices, err := api.db.Queries.GetDevices(api.db.Ctx, auth.UserName)
|
devices, err := api.db.Queries.GetDevices(c, auth.UserName)
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
log.Error("GetDevices DB Error: ", err)
|
log.Error("GetDevices DB Error: ", err)
|
||||||
@ -455,7 +452,7 @@ func (api *API) appUploadNewDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check Already Exists
|
// Check Already Exists
|
||||||
_, err = api.db.Queries.GetDocument(api.db.Ctx, *metadataInfo.PartialMD5)
|
_, err = api.db.Queries.GetDocument(c, *metadataInfo.PartialMD5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Warnf("document already exists: %s", *metadataInfo.PartialMD5)
|
log.Warnf("document already exists: %s", *metadataInfo.PartialMD5)
|
||||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", *metadataInfo.PartialMD5))
|
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", *metadataInfo.PartialMD5))
|
||||||
@ -483,7 +480,7 @@ func (api *API) appUploadNewDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Document
|
// Upsert Document
|
||||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err = api.db.Queries.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: *metadataInfo.PartialMD5,
|
ID: *metadataInfo.PartialMD5,
|
||||||
Title: metadataInfo.Title,
|
Title: metadataInfo.Title,
|
||||||
Author: metadataInfo.Author,
|
Author: metadataInfo.Author,
|
||||||
@ -573,7 +570,7 @@ func (api *API) appEditDocument(c *gin.Context) {
|
|||||||
|
|
||||||
coverFileName = &fileName
|
coverFileName = &fileName
|
||||||
} else if rDocEdit.CoverGBID != nil {
|
} else if rDocEdit.CoverGBID != nil {
|
||||||
var coverDir string = filepath.Join(api.cfg.DataPath, "covers")
|
coverDir := filepath.Join(api.cfg.DataPath, "covers")
|
||||||
fileName, err := metadata.CacheCover(*rDocEdit.CoverGBID, coverDir, rDocID.DocumentID, true)
|
fileName, err := metadata.CacheCover(*rDocEdit.CoverGBID, coverDir, rDocID.DocumentID, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
coverFileName = fileName
|
coverFileName = fileName
|
||||||
@ -581,7 +578,7 @@ func (api *API) appEditDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update Document
|
// Update Document
|
||||||
if _, err := api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err := api.db.Queries.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: rDocID.DocumentID,
|
ID: rDocID.DocumentID,
|
||||||
Title: api.sanitizeInput(rDocEdit.Title),
|
Title: api.sanitizeInput(rDocEdit.Title),
|
||||||
Author: api.sanitizeInput(rDocEdit.Author),
|
Author: api.sanitizeInput(rDocEdit.Author),
|
||||||
@ -605,7 +602,7 @@ func (api *API) appDeleteDocument(c *gin.Context) {
|
|||||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
changed, err := api.db.Queries.DeleteDocument(api.db.Ctx, rDocID.DocumentID)
|
changed, err := api.db.Queries.DeleteDocument(c, rDocID.DocumentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("DeleteDocument DB Error")
|
log.Error("DeleteDocument DB Error")
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("DeleteDocument DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("DeleteDocument DB Error: %v", err))
|
||||||
@ -667,7 +664,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
|||||||
firstResult := metadataResults[0]
|
firstResult := metadataResults[0]
|
||||||
|
|
||||||
// Store First Metadata Result
|
// Store First Metadata Result
|
||||||
if _, err = api.db.Queries.AddMetadata(api.db.Ctx, database.AddMetadataParams{
|
if _, err = api.db.Queries.AddMetadata(c, database.AddMetadataParams{
|
||||||
DocumentID: rDocID.DocumentID,
|
DocumentID: rDocID.DocumentID,
|
||||||
Title: firstResult.Title,
|
Title: firstResult.Title,
|
||||||
Author: firstResult.Author,
|
Author: firstResult.Author,
|
||||||
@ -686,13 +683,10 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
|||||||
templateVars["MetadataError"] = "No Metadata Found"
|
templateVars["MetadataError"] = "No Metadata Found"
|
||||||
}
|
}
|
||||||
|
|
||||||
document, err := api.db.Queries.GetDocumentWithStats(api.db.Ctx, database.GetDocumentWithStatsParams{
|
document, err := api.db.GetDocument(c, rDocID.DocumentID, auth.UserName)
|
||||||
UserID: auth.UserName,
|
|
||||||
DocumentID: rDocID.DocumentID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocumentWithStats DB Error: ", err)
|
log.Error("GetDocument DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentWithStats DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -817,7 +811,7 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
|||||||
sendDownloadMessage("Saving to database...", gin.H{"Progress": 99})
|
sendDownloadMessage("Saving to database...", gin.H{"Progress": 99})
|
||||||
|
|
||||||
// Upsert Document
|
// Upsert Document
|
||||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err = api.db.Queries.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: *metadata.PartialMD5,
|
ID: *metadata.PartialMD5,
|
||||||
Title: &docTitle,
|
Title: &docTitle,
|
||||||
Author: &docAuthor,
|
Author: &docAuthor,
|
||||||
@ -864,7 +858,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
// Set New Password
|
// Set New Password
|
||||||
if rUserSettings.Password != nil && rUserSettings.NewPassword != nil {
|
if rUserSettings.Password != nil && rUserSettings.NewPassword != nil {
|
||||||
password := fmt.Sprintf("%x", md5.Sum([]byte(*rUserSettings.Password)))
|
password := fmt.Sprintf("%x", md5.Sum([]byte(*rUserSettings.Password)))
|
||||||
data := api.authorizeCredentials(auth.UserName, password)
|
data := api.authorizeCredentials(c, auth.UserName, password)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
templateVars["PasswordErrorMessage"] = "Invalid Password"
|
templateVars["PasswordErrorMessage"] = "Invalid Password"
|
||||||
} else {
|
} else {
|
||||||
@ -886,7 +880,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update User
|
// Update User
|
||||||
_, err := api.db.Queries.UpdateUser(api.db.Ctx, newUserSettings)
|
_, err := api.db.Queries.UpdateUser(c, newUserSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("UpdateUser DB Error: ", err)
|
log.Error("UpdateUser DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpdateUser DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpdateUser DB Error: %v", err))
|
||||||
@ -894,7 +888,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get User
|
// Get User
|
||||||
user, err := api.db.Queries.GetUser(api.db.Ctx, auth.UserName)
|
user, err := api.db.Queries.GetUser(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUser DB Error: ", err)
|
log.Error("GetUser DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||||
@ -902,7 +896,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Devices
|
// Get Devices
|
||||||
devices, err := api.db.Queries.GetDevices(api.db.Ctx, auth.UserName)
|
devices, err := api.db.Queries.GetDevices(c, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDevices DB Error: ", err)
|
log.Error("GetDevices DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||||
@ -921,7 +915,7 @@ func (api *API) appDemoModeError(c *gin.Context) {
|
|||||||
appErrorPage(c, http.StatusUnauthorized, "Not Allowed in Demo Mode")
|
appErrorPage(c, http.StatusUnauthorized, "Not Allowed in Demo Mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) getDocumentsWordCount(documents []database.GetDocumentsWithStatsRow) error {
|
func (api *API) getDocumentsWordCount(ctx context.Context, documents []database.GetDocumentsWithStatsRow) error {
|
||||||
// Do Transaction
|
// Do Transaction
|
||||||
tx, err := api.db.DB.Begin()
|
tx, err := api.db.DB.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -944,7 +938,7 @@ func (api *API) getDocumentsWordCount(documents []database.GetDocumentsWithStats
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Word Count Error: ", err)
|
log.Warn("Word Count Error: ", err)
|
||||||
} else {
|
} else {
|
||||||
if _, err := qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err := qtx.UpsertDocument(ctx, database.UpsertDocumentParams{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
Words: wordCount,
|
Words: wordCount,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -1005,7 +999,7 @@ func bindQueryParams(c *gin.Context, defaultLimit int64) queryParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appErrorPage(c *gin.Context, errorCode int, errorMessage string) {
|
func appErrorPage(c *gin.Context, errorCode int, errorMessage string) {
|
||||||
var errorHuman string = "We're not even sure what happened."
|
errorHuman := "We're not even sure what happened."
|
||||||
|
|
||||||
switch errorCode {
|
switch errorCode {
|
||||||
case http.StatusInternalServerError:
|
case http.StatusInternalServerError:
|
||||||
|
39
api/auth.go
39
api/auth.go
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -28,8 +29,8 @@ type authKOHeader struct {
|
|||||||
AuthKey string `header:"x-auth-key"`
|
AuthKey string `header:"x-auth-key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) authorizeCredentials(username string, password string) (auth *authData) {
|
func (api *API) authorizeCredentials(ctx context.Context, username string, password string) (auth *authData) {
|
||||||
user, err := api.db.Queries.GetUser(api.db.Ctx, username)
|
user, err := api.db.Queries.GetUser(ctx, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -52,7 +53,7 @@ func (api *API) authKOMiddleware(c *gin.Context) {
|
|||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
|
||||||
// Check Session First
|
// Check Session First
|
||||||
if auth, ok := api.getSession(session); ok {
|
if auth, ok := api.getSession(c, session); ok {
|
||||||
c.Set("Authorization", auth)
|
c.Set("Authorization", auth)
|
||||||
c.Header("Cache-Control", "private")
|
c.Header("Cache-Control", "private")
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -71,7 +72,7 @@ func (api *API) authKOMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authData := api.authorizeCredentials(rHeader.AuthUser, rHeader.AuthKey)
|
authData := api.authorizeCredentials(c, rHeader.AuthUser, rHeader.AuthKey)
|
||||||
if authData == nil {
|
if authData == nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
return
|
return
|
||||||
@ -100,7 +101,7 @@ func (api *API) authOPDSMiddleware(c *gin.Context) {
|
|||||||
|
|
||||||
// Validate Auth
|
// Validate Auth
|
||||||
password := fmt.Sprintf("%x", md5.Sum([]byte(rawPassword)))
|
password := fmt.Sprintf("%x", md5.Sum([]byte(rawPassword)))
|
||||||
authData := api.authorizeCredentials(user, password)
|
authData := api.authorizeCredentials(c, user, password)
|
||||||
if authData == nil {
|
if authData == nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
return
|
return
|
||||||
@ -115,7 +116,7 @@ func (api *API) authWebAppMiddleware(c *gin.Context) {
|
|||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
|
||||||
// Check Session
|
// Check Session
|
||||||
if auth, ok := api.getSession(session); ok {
|
if auth, ok := api.getSession(c, session); ok {
|
||||||
c.Set("Authorization", auth)
|
c.Set("Authorization", auth)
|
||||||
c.Header("Cache-Control", "private")
|
c.Header("Cache-Control", "private")
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -153,7 +154,7 @@ func (api *API) appAuthLogin(c *gin.Context) {
|
|||||||
|
|
||||||
// MD5 - KOSync Compatiblity
|
// MD5 - KOSync Compatiblity
|
||||||
password := fmt.Sprintf("%x", md5.Sum([]byte(rawPassword)))
|
password := fmt.Sprintf("%x", md5.Sum([]byte(rawPassword)))
|
||||||
authData := api.authorizeCredentials(username, password)
|
authData := api.authorizeCredentials(c, username, password)
|
||||||
if authData == nil {
|
if authData == nil {
|
||||||
templateVars["Error"] = "Invalid Credentials"
|
templateVars["Error"] = "Invalid Credentials"
|
||||||
c.HTML(http.StatusUnauthorized, "page/login", templateVars)
|
c.HTML(http.StatusUnauthorized, "page/login", templateVars)
|
||||||
@ -208,7 +209,7 @@ func (api *API) appAuthRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get current users
|
// Get current users
|
||||||
currentUsers, err := api.db.Queries.GetUsers(api.db.Ctx)
|
currentUsers, err := api.db.Queries.GetUsers(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to check all users: ", err)
|
log.Error("Failed to check all users: ", err)
|
||||||
templateVars["Error"] = "Failed to Create User"
|
templateVars["Error"] = "Failed to Create User"
|
||||||
@ -224,7 +225,7 @@ func (api *API) appAuthRegister(c *gin.Context) {
|
|||||||
|
|
||||||
// Create user in DB
|
// Create user in DB
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
if rows, err := api.db.Queries.CreateUser(api.db.Ctx, database.CreateUserParams{
|
if rows, err := api.db.Queries.CreateUser(c, database.CreateUserParams{
|
||||||
ID: username,
|
ID: username,
|
||||||
Pass: &hashedPassword,
|
Pass: &hashedPassword,
|
||||||
AuthHash: &authHash,
|
AuthHash: &authHash,
|
||||||
@ -242,7 +243,7 @@ func (api *API) appAuthRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get user
|
// Get user
|
||||||
user, err := api.db.Queries.GetUser(api.db.Ctx, username)
|
user, err := api.db.Queries.GetUser(c, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUser DB Error:", err)
|
log.Error("GetUser DB Error:", err)
|
||||||
templateVars["Error"] = "Registration Disabled or User Already Exists"
|
templateVars["Error"] = "Registration Disabled or User Already Exists"
|
||||||
@ -312,7 +313,7 @@ func (api *API) koAuthRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get current users
|
// Get current users
|
||||||
currentUsers, err := api.db.Queries.GetUsers(api.db.Ctx)
|
currentUsers, err := api.db.Queries.GetUsers(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to check all users: ", err)
|
log.Error("Failed to check all users: ", err)
|
||||||
apiErrorPage(c, http.StatusBadRequest, "Failed to Create User")
|
apiErrorPage(c, http.StatusBadRequest, "Failed to Create User")
|
||||||
@ -327,7 +328,7 @@ func (api *API) koAuthRegister(c *gin.Context) {
|
|||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
if rows, err := api.db.Queries.CreateUser(api.db.Ctx, database.CreateUserParams{
|
if rows, err := api.db.Queries.CreateUser(c, database.CreateUserParams{
|
||||||
ID: rUser.Username,
|
ID: rUser.Username,
|
||||||
Pass: &hashedPassword,
|
Pass: &hashedPassword,
|
||||||
AuthHash: &authHash,
|
AuthHash: &authHash,
|
||||||
@ -347,7 +348,7 @@ func (api *API) koAuthRegister(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) getSession(session sessions.Session) (auth authData, ok bool) {
|
func (api *API) getSession(ctx context.Context, session sessions.Session) (auth authData, ok bool) {
|
||||||
// Get Session
|
// Get Session
|
||||||
authorizedUser := session.Get("authorizedUser")
|
authorizedUser := session.Get("authorizedUser")
|
||||||
isAdmin := session.Get("isAdmin")
|
isAdmin := session.Get("isAdmin")
|
||||||
@ -365,7 +366,7 @@ func (api *API) getSession(session sessions.Session) (auth authData, ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate Auth Hash
|
// Validate Auth Hash
|
||||||
correctAuthHash, err := api.getUserAuthHash(auth.UserName)
|
correctAuthHash, err := api.getUserAuthHash(ctx, auth.UserName)
|
||||||
if err != nil || correctAuthHash != auth.AuthHash {
|
if err != nil || correctAuthHash != auth.AuthHash {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -393,14 +394,14 @@ func (api *API) setSession(session sessions.Session, auth authData) error {
|
|||||||
return session.Save()
|
return session.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) getUserAuthHash(username string) (string, error) {
|
func (api *API) getUserAuthHash(ctx context.Context, username string) (string, error) {
|
||||||
// Return Cache
|
// Return Cache
|
||||||
if api.userAuthCache[username] != "" {
|
if api.userAuthCache[username] != "" {
|
||||||
return api.userAuthCache[username], nil
|
return api.userAuthCache[username], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get DB
|
// Get DB
|
||||||
user, err := api.db.Queries.GetUser(api.db.Ctx, username)
|
user, err := api.db.Queries.GetUser(ctx, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUser DB Error:", err)
|
log.Error("GetUser DB Error:", err)
|
||||||
return "", err
|
return "", err
|
||||||
@ -412,7 +413,7 @@ func (api *API) getUserAuthHash(username string) (string, error) {
|
|||||||
return api.userAuthCache[username], nil
|
return api.userAuthCache[username], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) rotateAllAuthHashes() error {
|
func (api *API) rotateAllAuthHashes(ctx context.Context) error {
|
||||||
// Do Transaction
|
// Do Transaction
|
||||||
tx, err := api.db.DB.Begin()
|
tx, err := api.db.DB.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -428,7 +429,7 @@ func (api *API) rotateAllAuthHashes() error {
|
|||||||
}()
|
}()
|
||||||
qtx := api.db.Queries.WithTx(tx)
|
qtx := api.db.Queries.WithTx(tx)
|
||||||
|
|
||||||
users, err := qtx.GetUsers(api.db.Ctx)
|
users, err := qtx.GetUsers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -444,7 +445,7 @@ func (api *API) rotateAllAuthHashes() error {
|
|||||||
|
|
||||||
// Update User
|
// Update User
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
if _, err = qtx.UpdateUser(api.db.Ctx, database.UpdateUserParams{
|
if _, err = qtx.UpdateUser(ctx, database.UpdateUserParams{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
AuthHash: &authHash,
|
AuthHash: &authHash,
|
||||||
Admin: user.Admin,
|
Admin: user.Admin,
|
||||||
|
@ -22,7 +22,7 @@ func (api *API) createDownloadDocumentHandler(errorFunc func(*gin.Context, int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Document
|
// Get Document
|
||||||
document, err := api.db.Queries.GetDocument(api.db.Ctx, rDoc.DocumentID)
|
document, err := api.db.Queries.GetDocument(c, rDoc.DocumentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocument DB Error:", err)
|
log.Error("GetDocument DB Error:", err)
|
||||||
errorFunc(c, http.StatusBadRequest, "Unknown Document")
|
errorFunc(c, http.StatusBadRequest, "Unknown Document")
|
||||||
@ -68,7 +68,7 @@ func (api *API) createGetCoverHandler(errorFunc func(*gin.Context, int, string))
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate Document Exists in DB
|
// Validate Document Exists in DB
|
||||||
document, err := api.db.Queries.GetDocument(api.db.Ctx, rDoc.DocumentID)
|
document, err := api.db.Queries.GetDocument(c, rDoc.DocumentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocument DB Error:", err)
|
log.Error("GetDocument DB Error:", err)
|
||||||
errorFunc(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
errorFunc(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
||||||
@ -117,7 +117,7 @@ func (api *API) createGetCoverHandler(errorFunc func(*gin.Context, int, string))
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store First Metadata Result
|
// Store First Metadata Result
|
||||||
if _, err = api.db.Queries.AddMetadata(api.db.Ctx, database.AddMetadataParams{
|
if _, err = api.db.Queries.AddMetadata(c, database.AddMetadataParams{
|
||||||
DocumentID: document.ID,
|
DocumentID: document.ID,
|
||||||
Title: firstResult.Title,
|
Title: firstResult.Title,
|
||||||
Author: firstResult.Author,
|
Author: firstResult.Author,
|
||||||
@ -132,7 +132,7 @@ func (api *API) createGetCoverHandler(errorFunc func(*gin.Context, int, string))
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Document
|
// Upsert Document
|
||||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err = api.db.Queries.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: document.ID,
|
ID: document.ID,
|
||||||
Coverfile: &coverFile,
|
Coverfile: &coverFile,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -91,7 +91,7 @@ func (api *API) koSetProgress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Device
|
// Upsert Device
|
||||||
if _, err := api.db.Queries.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
if _, err := api.db.Queries.UpsertDevice(c, database.UpsertDeviceParams{
|
||||||
ID: rPosition.DeviceID,
|
ID: rPosition.DeviceID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DeviceName: rPosition.Device,
|
DeviceName: rPosition.Device,
|
||||||
@ -101,14 +101,14 @@ func (api *API) koSetProgress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Document
|
// Upsert Document
|
||||||
if _, err := api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err := api.db.Queries.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: rPosition.DocumentID,
|
ID: rPosition.DocumentID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("UpsertDocument DB Error:", err)
|
log.Error("UpsertDocument DB Error:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or Replace Progress
|
// Create or Replace Progress
|
||||||
progress, err := api.db.Queries.UpdateProgress(api.db.Ctx, database.UpdateProgressParams{
|
progress, err := api.db.Queries.UpdateProgress(c, database.UpdateProgressParams{
|
||||||
Percentage: rPosition.Percentage,
|
Percentage: rPosition.Percentage,
|
||||||
DocumentID: rPosition.DocumentID,
|
DocumentID: rPosition.DocumentID,
|
||||||
DeviceID: rPosition.DeviceID,
|
DeviceID: rPosition.DeviceID,
|
||||||
@ -140,7 +140,7 @@ func (api *API) koGetProgress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
progress, err := api.db.Queries.GetDocumentProgress(api.db.Ctx, database.GetDocumentProgressParams{
|
progress, err := api.db.Queries.GetDocumentProgress(c, database.GetDocumentProgressParams{
|
||||||
DocumentID: rDocID.DocumentID,
|
DocumentID: rDocID.DocumentID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
})
|
})
|
||||||
@ -202,7 +202,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
|||||||
|
|
||||||
// Upsert Documents
|
// Upsert Documents
|
||||||
for _, doc := range allDocuments {
|
for _, doc := range allDocuments {
|
||||||
if _, err := qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err := qtx.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: doc,
|
ID: doc,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("UpsertDocument DB Error:", err)
|
log.Error("UpsertDocument DB Error:", err)
|
||||||
@ -212,7 +212,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Device
|
// Upsert Device
|
||||||
if _, err = qtx.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
if _, err = qtx.UpsertDevice(c, database.UpsertDeviceParams{
|
||||||
ID: rActivity.DeviceID,
|
ID: rActivity.DeviceID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DeviceName: rActivity.Device,
|
DeviceName: rActivity.Device,
|
||||||
@ -225,7 +225,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
|||||||
|
|
||||||
// Add All Activity
|
// Add All Activity
|
||||||
for _, item := range rActivity.Activity {
|
for _, item := range rActivity.Activity {
|
||||||
if _, err := qtx.AddActivity(api.db.Ctx, database.AddActivityParams{
|
if _, err := qtx.AddActivity(c, database.AddActivityParams{
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DocumentID: item.DocumentID,
|
DocumentID: item.DocumentID,
|
||||||
DeviceID: rActivity.DeviceID,
|
DeviceID: rActivity.DeviceID,
|
||||||
@ -266,7 +266,7 @@ func (api *API) koCheckActivitySync(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Device
|
// Upsert Device
|
||||||
if _, err := api.db.Queries.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
if _, err := api.db.Queries.UpsertDevice(c, database.UpsertDeviceParams{
|
||||||
ID: rCheckActivity.DeviceID,
|
ID: rCheckActivity.DeviceID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DeviceName: rCheckActivity.Device,
|
DeviceName: rCheckActivity.Device,
|
||||||
@ -278,7 +278,7 @@ func (api *API) koCheckActivitySync(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Last Device Activity
|
// Get Last Device Activity
|
||||||
lastActivity, err := api.db.Queries.GetLastActivity(api.db.Ctx, database.GetLastActivityParams{
|
lastActivity, err := api.db.Queries.GetLastActivity(c, database.GetLastActivityParams{
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DeviceID: rCheckActivity.DeviceID,
|
DeviceID: rCheckActivity.DeviceID,
|
||||||
})
|
})
|
||||||
@ -329,7 +329,7 @@ func (api *API) koAddDocuments(c *gin.Context) {
|
|||||||
|
|
||||||
// Upsert Documents
|
// Upsert Documents
|
||||||
for _, doc := range rNewDocs.Documents {
|
for _, doc := range rNewDocs.Documents {
|
||||||
_, err := qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
_, err := qtx.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: doc.ID,
|
ID: doc.ID,
|
||||||
Title: api.sanitizeInput(doc.Title),
|
Title: api.sanitizeInput(doc.Title),
|
||||||
Author: api.sanitizeInput(doc.Author),
|
Author: api.sanitizeInput(doc.Author),
|
||||||
@ -371,7 +371,7 @@ func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Device
|
// Upsert Device
|
||||||
_, err := api.db.Queries.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
_, err := api.db.Queries.UpsertDevice(c, database.UpsertDeviceParams{
|
||||||
ID: rCheckDocs.DeviceID,
|
ID: rCheckDocs.DeviceID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
DeviceName: rCheckDocs.Device,
|
DeviceName: rCheckDocs.Device,
|
||||||
@ -384,7 +384,7 @@ func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Missing Documents
|
// Get Missing Documents
|
||||||
missingDocs, err := api.db.Queries.GetMissingDocuments(api.db.Ctx, rCheckDocs.Have)
|
missingDocs, err := api.db.Queries.GetMissingDocuments(c, rCheckDocs.Have)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetMissingDocuments DB Error", err)
|
log.Error("GetMissingDocuments DB Error", err)
|
||||||
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
||||||
@ -392,7 +392,7 @@ func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Deleted Documents
|
// Get Deleted Documents
|
||||||
deletedDocIDs, err := api.db.Queries.GetDeletedDocuments(api.db.Ctx, rCheckDocs.Have)
|
deletedDocIDs, err := api.db.Queries.GetDeletedDocuments(c, rCheckDocs.Have)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDeletedDocuments DB Error", err)
|
log.Error("GetDeletedDocuments DB Error", err)
|
||||||
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
||||||
@ -407,7 +407,7 @@ func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wantedDocs, err := api.db.Queries.GetWantedDocuments(api.db.Ctx, string(jsonHaves))
|
wantedDocs, err := api.db.Queries.GetWantedDocuments(c, string(jsonHaves))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetWantedDocuments DB Error", err)
|
log.Error("GetWantedDocuments DB Error", err)
|
||||||
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
||||||
@ -467,7 +467,7 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate Document Exists in DB
|
// Validate Document Exists in DB
|
||||||
document, err := api.db.Queries.GetDocument(api.db.Ctx, rDoc.DocumentID)
|
document, err := api.db.Queries.GetDocument(c, rDoc.DocumentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocument DB Error:", err)
|
log.Error("GetDocument DB Error:", err)
|
||||||
apiErrorPage(c, http.StatusBadRequest, "Unknown Document")
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Document")
|
||||||
@ -522,7 +522,7 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upsert Document
|
// Upsert Document
|
||||||
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err = api.db.Queries.UpsertDocument(c, database.UpsertDocumentParams{
|
||||||
ID: document.ID,
|
ID: document.ID,
|
||||||
Md5: metadataInfo.MD5,
|
Md5: metadataInfo.MD5,
|
||||||
Words: metadataInfo.WordCount,
|
Words: metadataInfo.WordCount,
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
"reichard.io/antholume/opds"
|
"reichard.io/antholume/opds"
|
||||||
|
"reichard.io/antholume/pkg/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mimeMapping map[string]string = map[string]string{
|
var mimeMapping map[string]string = map[string]string{
|
||||||
@ -77,11 +78,12 @@ func (api *API) opdsDocuments(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Documents
|
// Get Documents
|
||||||
documents, err := api.db.Queries.GetDocumentsWithStats(api.db.Ctx, database.GetDocumentsWithStatsParams{
|
documents, err := api.db.Queries.GetDocumentsWithStats(c, database.GetDocumentsWithStatsParams{
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
Query: query,
|
Query: query,
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
Deleted: ptr.Of(false),
|
||||||
Limit: *qParams.Limit,
|
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||||
|
Limit: *qParams.Limit,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetDocumentsWithStats DB Error:", err)
|
log.Error("GetDocumentsWithStats DB Error:", err)
|
||||||
|
@ -55,6 +55,7 @@ func getTimeZones() []string {
|
|||||||
|
|
||||||
// niceSeconds takes in an int (in seconds) and returns a string readable
|
// niceSeconds takes in an int (in seconds) and returns a string readable
|
||||||
// representation. For example 1928371 -> "22d 7h 39m 31s".
|
// representation. For example 1928371 -> "22d 7h 39m 31s".
|
||||||
|
// Deprecated: Use formatters.FormatDuration
|
||||||
func niceSeconds(input int64) (result string) {
|
func niceSeconds(input int64) (result string) {
|
||||||
if input == 0 {
|
if input == 0 {
|
||||||
return "N/A"
|
return "N/A"
|
||||||
@ -85,6 +86,7 @@ func niceSeconds(input int64) (result string) {
|
|||||||
|
|
||||||
// niceNumbers takes in an int and returns a string representation. For example
|
// niceNumbers takes in an int and returns a string representation. For example
|
||||||
// 19823 -> "19.8k".
|
// 19823 -> "19.8k".
|
||||||
|
// Deprecated: Use formatters.FormatNumber
|
||||||
func niceNumbers(input int64) string {
|
func niceNumbers(input int64) string {
|
||||||
if input == 0 {
|
if input == 0 {
|
||||||
return "0"
|
return "0"
|
||||||
|
27
database/documents.go
Normal file
27
database/documents.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"reichard.io/antholume/pkg/ptr"
|
||||||
|
"reichard.io/antholume/pkg/sliceutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *DBManager) GetDocument(ctx context.Context, docID, userID string) (*GetDocumentsWithStatsRow, error) {
|
||||||
|
documents, err := d.Queries.GetDocumentsWithStats(ctx, GetDocumentsWithStatsParams{
|
||||||
|
ID: ptr.Of(docID),
|
||||||
|
UserID: userID,
|
||||||
|
Limit: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
document, found := sliceutils.First(documents)
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("document not found: %s", docID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &document, nil
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ func (suite *DocumentsTestSuite) SetupTest() {
|
|||||||
suite.dbm = NewMgr(&cfg)
|
suite.dbm = NewMgr(&cfg)
|
||||||
|
|
||||||
// Create Document
|
// Create Document
|
||||||
_, err := suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
_, err := suite.dbm.Queries.UpsertDocument(context.Background(), UpsertDocumentParams{
|
||||||
ID: documentID,
|
ID: documentID,
|
||||||
Title: &documentTitle,
|
Title: &documentTitle,
|
||||||
Author: &documentAuthor,
|
Author: &documentAuthor,
|
||||||
@ -42,7 +43,7 @@ func (suite *DocumentsTestSuite) SetupTest() {
|
|||||||
// - (q *Queries) GetDocumentsWithStats
|
// - (q *Queries) GetDocumentsWithStats
|
||||||
// - (q *Queries) GetMissingDocuments
|
// - (q *Queries) GetMissingDocuments
|
||||||
func (suite *DocumentsTestSuite) TestGetDocument() {
|
func (suite *DocumentsTestSuite) TestGetDocument() {
|
||||||
doc, err := suite.dbm.Queries.GetDocument(suite.dbm.Ctx, documentID)
|
doc, err := suite.dbm.Queries.GetDocument(context.Background(), documentID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(documentID, doc.ID, "should have changed the document")
|
suite.Equal(documentID, doc.ID, "should have changed the document")
|
||||||
}
|
}
|
||||||
@ -50,7 +51,7 @@ func (suite *DocumentsTestSuite) TestGetDocument() {
|
|||||||
func (suite *DocumentsTestSuite) TestUpsertDocument() {
|
func (suite *DocumentsTestSuite) TestUpsertDocument() {
|
||||||
testDocID := "docid1"
|
testDocID := "docid1"
|
||||||
|
|
||||||
doc, err := suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
doc, err := suite.dbm.Queries.UpsertDocument(context.Background(), UpsertDocumentParams{
|
||||||
ID: testDocID,
|
ID: testDocID,
|
||||||
Title: &documentTitle,
|
Title: &documentTitle,
|
||||||
Author: &documentAuthor,
|
Author: &documentAuthor,
|
||||||
@ -63,51 +64,51 @@ func (suite *DocumentsTestSuite) TestUpsertDocument() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DocumentsTestSuite) TestDeleteDocument() {
|
func (suite *DocumentsTestSuite) TestDeleteDocument() {
|
||||||
changed, err := suite.dbm.Queries.DeleteDocument(suite.dbm.Ctx, documentID)
|
changed, err := suite.dbm.Queries.DeleteDocument(context.Background(), documentID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(int64(1), changed, "should have changed the document")
|
suite.Equal(int64(1), changed, "should have changed the document")
|
||||||
|
|
||||||
doc, err := suite.dbm.Queries.GetDocument(suite.dbm.Ctx, documentID)
|
doc, err := suite.dbm.Queries.GetDocument(context.Background(), documentID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.True(doc.Deleted, "should have deleted the document")
|
suite.True(doc.Deleted, "should have deleted the document")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DocumentsTestSuite) TestGetDeletedDocuments() {
|
func (suite *DocumentsTestSuite) TestGetDeletedDocuments() {
|
||||||
changed, err := suite.dbm.Queries.DeleteDocument(suite.dbm.Ctx, documentID)
|
changed, err := suite.dbm.Queries.DeleteDocument(context.Background(), documentID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(int64(1), changed, "should have changed the document")
|
suite.Equal(int64(1), changed, "should have changed the document")
|
||||||
|
|
||||||
deletedDocs, err := suite.dbm.Queries.GetDeletedDocuments(suite.dbm.Ctx, []string{documentID})
|
deletedDocs, err := suite.dbm.Queries.GetDeletedDocuments(context.Background(), []string{documentID})
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(deletedDocs, 1, "should have one deleted document")
|
suite.Len(deletedDocs, 1, "should have one deleted document")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Convert GetWantedDocuments -> (sqlc.slice('document_ids'));
|
// TODO - Convert GetWantedDocuments -> (sqlc.slice('document_ids'));
|
||||||
func (suite *DocumentsTestSuite) TestGetWantedDocuments() {
|
func (suite *DocumentsTestSuite) TestGetWantedDocuments() {
|
||||||
wantedDocs, err := suite.dbm.Queries.GetWantedDocuments(suite.dbm.Ctx, fmt.Sprintf("[\"%s\"]", documentID))
|
wantedDocs, err := suite.dbm.Queries.GetWantedDocuments(context.Background(), fmt.Sprintf("[\"%s\"]", documentID))
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(wantedDocs, 1, "should have one wanted document")
|
suite.Len(wantedDocs, 1, "should have one wanted document")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DocumentsTestSuite) TestGetMissingDocuments() {
|
func (suite *DocumentsTestSuite) TestGetMissingDocuments() {
|
||||||
// Create Document
|
// Create Document
|
||||||
_, err := suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
_, err := suite.dbm.Queries.UpsertDocument(context.Background(), UpsertDocumentParams{
|
||||||
ID: documentID,
|
ID: documentID,
|
||||||
Filepath: &documentFilepath,
|
Filepath: &documentFilepath,
|
||||||
})
|
})
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
missingDocs, err := suite.dbm.Queries.GetMissingDocuments(suite.dbm.Ctx, []string{documentID})
|
missingDocs, err := suite.dbm.Queries.GetMissingDocuments(context.Background(), []string{documentID})
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(missingDocs, 0, "should have no wanted document")
|
suite.Len(missingDocs, 0, "should have no wanted document")
|
||||||
|
|
||||||
missingDocs, err = suite.dbm.Queries.GetMissingDocuments(suite.dbm.Ctx, []string{"other"})
|
missingDocs, err = suite.dbm.Queries.GetMissingDocuments(context.Background(), []string{"other"})
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(missingDocs, 1, "should have one missing document")
|
suite.Len(missingDocs, 1, "should have one missing document")
|
||||||
suite.Equal(documentID, missingDocs[0].ID, "should have missing doc")
|
suite.Equal(documentID, missingDocs[0].ID, "should have missing doc")
|
||||||
|
|
||||||
// TODO - https://github.com/sqlc-dev/sqlc/issues/3451
|
// TODO - https://github.com/sqlc-dev/sqlc/issues/3451
|
||||||
// missingDocs, err = suite.dbm.Queries.GetMissingDocuments(suite.dbm.Ctx, []string{})
|
// missingDocs, err = suite.dbm.Queries.GetMissingDocuments(context.Background(), []string{})
|
||||||
// suite.Nil(err, "should have nil err")
|
// suite.Nil(err, "should have nil err")
|
||||||
// suite.Len(missingDocs, 1, "should have one missing document")
|
// suite.Len(missingDocs, 1, "should have one missing document")
|
||||||
// suite.Equal(documentID, missingDocs[0].ID, "should have missing doc")
|
// suite.Equal(documentID, missingDocs[0].ID, "should have missing doc")
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"embed"
|
"embed"
|
||||||
_ "embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -20,7 +19,6 @@ import (
|
|||||||
|
|
||||||
type DBManager struct {
|
type DBManager struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
Ctx context.Context
|
|
||||||
Queries *Queries
|
Queries *Queries
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
@ -54,12 +52,9 @@ func init() {
|
|||||||
// NewMgr Returns an initialized manager
|
// NewMgr Returns an initialized manager
|
||||||
func NewMgr(c *config.Config) *DBManager {
|
func NewMgr(c *config.Config) *DBManager {
|
||||||
// Create Manager
|
// Create Manager
|
||||||
dbm := &DBManager{
|
dbm := &DBManager{cfg: c}
|
||||||
Ctx: context.Background(),
|
|
||||||
cfg: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbm.init(); err != nil {
|
if err := dbm.init(context.Background()); err != nil {
|
||||||
log.Panic("Unable to init DB")
|
log.Panic("Unable to init DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +62,7 @@ func NewMgr(c *config.Config) *DBManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init loads the DB manager
|
// init loads the DB manager
|
||||||
func (dbm *DBManager) init() error {
|
func (dbm *DBManager) init(ctx context.Context) error {
|
||||||
// Build DB Location
|
// Build DB Location
|
||||||
var dbLocation string
|
var dbLocation string
|
||||||
switch dbm.cfg.DBType {
|
switch dbm.cfg.DBType {
|
||||||
@ -113,14 +108,14 @@ func (dbm *DBManager) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update settings
|
// Update settings
|
||||||
err = dbm.updateSettings()
|
err = dbm.updateSettings(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Error running DB settings update: %v", err)
|
log.Panicf("Error running DB settings update: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache tables
|
// Cache tables
|
||||||
if err := dbm.CacheTempTables(); err != nil {
|
if err := dbm.CacheTempTables(ctx); err != nil {
|
||||||
log.Warn("Refreshing temp table cache failed: ", err)
|
log.Warn("Refreshing temp table cache failed: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +123,7 @@ func (dbm *DBManager) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reload closes the DB & reinits
|
// Reload closes the DB & reinits
|
||||||
func (dbm *DBManager) Reload() error {
|
func (dbm *DBManager) Reload(ctx context.Context) error {
|
||||||
// Close handle
|
// Close handle
|
||||||
err := dbm.DB.Close()
|
err := dbm.DB.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -136,7 +131,7 @@ func (dbm *DBManager) Reload() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reinit DB
|
// Reinit DB
|
||||||
if err := dbm.init(); err != nil {
|
if err := dbm.init(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,15 +139,15 @@ func (dbm *DBManager) Reload() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CacheTempTables clears existing statistics and recalculates
|
// CacheTempTables clears existing statistics and recalculates
|
||||||
func (dbm *DBManager) CacheTempTables() error {
|
func (dbm *DBManager) CacheTempTables(ctx context.Context) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if _, err := dbm.DB.ExecContext(dbm.Ctx, user_streaks); err != nil {
|
if _, err := dbm.DB.ExecContext(ctx, user_streaks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debug("Cached 'user_streaks' in: ", time.Since(start))
|
log.Debug("Cached 'user_streaks' in: ", time.Since(start))
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
if _, err := dbm.DB.ExecContext(dbm.Ctx, document_user_statistics); err != nil {
|
if _, err := dbm.DB.ExecContext(ctx, document_user_statistics); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debug("Cached 'document_user_statistics' in: ", time.Since(start))
|
log.Debug("Cached 'document_user_statistics' in: ", time.Since(start))
|
||||||
@ -162,7 +157,7 @@ func (dbm *DBManager) CacheTempTables() error {
|
|||||||
|
|
||||||
// updateSettings ensures that we're enforcing foreign keys and enable journal
|
// updateSettings ensures that we're enforcing foreign keys and enable journal
|
||||||
// mode.
|
// mode.
|
||||||
func (dbm *DBManager) updateSettings() error {
|
func (dbm *DBManager) updateSettings(ctx context.Context) error {
|
||||||
// Set SQLite PRAGMA Settings
|
// Set SQLite PRAGMA Settings
|
||||||
pragmaQuery := `
|
pragmaQuery := `
|
||||||
PRAGMA foreign_keys = ON;
|
PRAGMA foreign_keys = ON;
|
||||||
@ -174,7 +169,7 @@ func (dbm *DBManager) updateSettings() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update Antholume Version in DB
|
// Update Antholume Version in DB
|
||||||
if _, err := dbm.Queries.UpdateSettings(dbm.Ctx, UpdateSettingsParams{
|
if _, err := dbm.Queries.UpdateSettings(ctx, UpdateSettingsParams{
|
||||||
Name: "version",
|
Name: "version",
|
||||||
Value: dbm.cfg.Version,
|
Value: dbm.cfg.Version,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -46,7 +47,7 @@ func (suite *DatabaseTestSuite) SetupTest() {
|
|||||||
// Create User
|
// Create User
|
||||||
rawAuthHash, _ := utils.GenerateToken(64)
|
rawAuthHash, _ := utils.GenerateToken(64)
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
_, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
|
_, err := suite.dbm.Queries.CreateUser(context.Background(), CreateUserParams{
|
||||||
ID: userID,
|
ID: userID,
|
||||||
Pass: &userPass,
|
Pass: &userPass,
|
||||||
AuthHash: &authHash,
|
AuthHash: &authHash,
|
||||||
@ -54,7 +55,7 @@ func (suite *DatabaseTestSuite) SetupTest() {
|
|||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Create Document
|
// Create Document
|
||||||
_, err = suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
_, err = suite.dbm.Queries.UpsertDocument(context.Background(), UpsertDocumentParams{
|
||||||
ID: documentID,
|
ID: documentID,
|
||||||
Title: &documentTitle,
|
Title: &documentTitle,
|
||||||
Author: &documentAuthor,
|
Author: &documentAuthor,
|
||||||
@ -64,7 +65,7 @@ func (suite *DatabaseTestSuite) SetupTest() {
|
|||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Create Device
|
// Create Device
|
||||||
_, err = suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
|
_, err = suite.dbm.Queries.UpsertDevice(context.Background(), UpsertDeviceParams{
|
||||||
ID: deviceID,
|
ID: deviceID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
DeviceName: deviceName,
|
DeviceName: deviceName,
|
||||||
@ -80,7 +81,7 @@ func (suite *DatabaseTestSuite) SetupTest() {
|
|||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
// Add Item
|
// Add Item
|
||||||
activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
|
activity, err := suite.dbm.Queries.AddActivity(context.Background(), AddActivityParams{
|
||||||
DocumentID: documentID,
|
DocumentID: documentID,
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@ -95,7 +96,7 @@ func (suite *DatabaseTestSuite) SetupTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initiate Cache
|
// Initiate Cache
|
||||||
err = suite.dbm.CacheTempTables()
|
err = suite.dbm.CacheTempTables(context.Background())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +106,7 @@ func (suite *DatabaseTestSuite) SetupTest() {
|
|||||||
// - (q *Queries) UpsertDevice
|
// - (q *Queries) UpsertDevice
|
||||||
func (suite *DatabaseTestSuite) TestDevice() {
|
func (suite *DatabaseTestSuite) TestDevice() {
|
||||||
testDevice := "dev123"
|
testDevice := "dev123"
|
||||||
device, err := suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
|
device, err := suite.dbm.Queries.UpsertDevice(context.Background(), UpsertDeviceParams{
|
||||||
ID: testDevice,
|
ID: testDevice,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
DeviceName: deviceName,
|
DeviceName: deviceName,
|
||||||
@ -123,7 +124,7 @@ func (suite *DatabaseTestSuite) TestDevice() {
|
|||||||
// - (q *Queries) GetLastActivity
|
// - (q *Queries) GetLastActivity
|
||||||
func (suite *DatabaseTestSuite) TestActivity() {
|
func (suite *DatabaseTestSuite) TestActivity() {
|
||||||
// Validate Exists
|
// Validate Exists
|
||||||
existsRows, err := suite.dbm.Queries.GetActivity(suite.dbm.Ctx, GetActivityParams{
|
existsRows, err := suite.dbm.Queries.GetActivity(context.Background(), GetActivityParams{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
Limit: 50,
|
Limit: 50,
|
||||||
@ -133,7 +134,7 @@ func (suite *DatabaseTestSuite) TestActivity() {
|
|||||||
suite.Len(existsRows, 10, "should have correct number of rows get activity")
|
suite.Len(existsRows, 10, "should have correct number of rows get activity")
|
||||||
|
|
||||||
// Validate Doesn't Exist
|
// Validate Doesn't Exist
|
||||||
doesntExistsRows, err := suite.dbm.Queries.GetActivity(suite.dbm.Ctx, GetActivityParams{
|
doesntExistsRows, err := suite.dbm.Queries.GetActivity(context.Background(), GetActivityParams{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
DocumentID: "unknownDoc",
|
DocumentID: "unknownDoc",
|
||||||
DocFilter: true,
|
DocFilter: true,
|
||||||
@ -151,7 +152,7 @@ func (suite *DatabaseTestSuite) TestActivity() {
|
|||||||
// - (q *Queries) GetDatabaseInfo
|
// - (q *Queries) GetDatabaseInfo
|
||||||
// - (q *Queries) UpdateSettings
|
// - (q *Queries) UpdateSettings
|
||||||
func (suite *DatabaseTestSuite) TestGetDailyReadStats() {
|
func (suite *DatabaseTestSuite) TestGetDailyReadStats() {
|
||||||
readStats, err := suite.dbm.Queries.GetDailyReadStats(suite.dbm.Ctx, userID)
|
readStats, err := suite.dbm.Queries.GetDailyReadStats(context.Background(), userID)
|
||||||
|
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(readStats, 30, "should have length of 30")
|
suite.Len(readStats, 30, "should have length of 30")
|
||||||
|
@ -163,42 +163,6 @@ ORDER BY
|
|||||||
DESC
|
DESC
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- name: GetDocumentWithStats :one
|
|
||||||
SELECT
|
|
||||||
docs.id,
|
|
||||||
docs.title,
|
|
||||||
docs.author,
|
|
||||||
docs.description,
|
|
||||||
docs.isbn10,
|
|
||||||
docs.isbn13,
|
|
||||||
docs.filepath,
|
|
||||||
docs.words,
|
|
||||||
|
|
||||||
CAST(COALESCE(dus.total_wpm, 0.0) AS INTEGER) AS wpm,
|
|
||||||
COALESCE(dus.read_percentage, 0) AS read_percentage,
|
|
||||||
COALESCE(dus.total_time_seconds, 0) AS total_time_seconds,
|
|
||||||
STRFTIME('%Y-%m-%d %H:%M:%S', LOCAL_TIME(COALESCE(dus.last_read, STRFTIME('%Y-%m-%dT%H:%M:%SZ', 0, 'unixepoch')), users.timezone))
|
|
||||||
AS last_read,
|
|
||||||
ROUND(CAST(CASE
|
|
||||||
WHEN dus.percentage IS NULL THEN 0.0
|
|
||||||
WHEN (dus.percentage * 100.0) > 97.0 THEN 100.0
|
|
||||||
ELSE dus.percentage * 100.0
|
|
||||||
END AS REAL), 2) AS percentage,
|
|
||||||
CAST(CASE
|
|
||||||
WHEN dus.total_time_seconds IS NULL THEN 0.0
|
|
||||||
ELSE
|
|
||||||
CAST(dus.total_time_seconds AS REAL)
|
|
||||||
/ (dus.read_percentage * 100.0)
|
|
||||||
END AS INTEGER) AS seconds_per_percent
|
|
||||||
FROM documents AS docs
|
|
||||||
LEFT JOIN users ON users.id = $user_id
|
|
||||||
LEFT JOIN
|
|
||||||
document_user_statistics AS dus
|
|
||||||
ON dus.document_id = docs.id AND dus.user_id = $user_id
|
|
||||||
WHERE users.id = $user_id
|
|
||||||
AND docs.id = $document_id
|
|
||||||
LIMIT 1;
|
|
||||||
|
|
||||||
-- name: GetDocuments :many
|
-- name: GetDocuments :many
|
||||||
SELECT * FROM documents
|
SELECT * FROM documents
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@ -236,26 +200,25 @@ SELECT
|
|||||||
WHEN (dus.percentage * 100.0) > 97.0 THEN 100.0
|
WHEN (dus.percentage * 100.0) > 97.0 THEN 100.0
|
||||||
ELSE dus.percentage * 100.0
|
ELSE dus.percentage * 100.0
|
||||||
END AS REAL), 2) AS percentage,
|
END AS REAL), 2) AS percentage,
|
||||||
|
CAST(CASE
|
||||||
CASE
|
|
||||||
WHEN dus.total_time_seconds IS NULL THEN 0.0
|
WHEN dus.total_time_seconds IS NULL THEN 0.0
|
||||||
ELSE
|
ELSE
|
||||||
ROUND(
|
CAST(dus.total_time_seconds AS REAL)
|
||||||
CAST(dus.total_time_seconds AS REAL)
|
/ (dus.read_percentage * 100.0)
|
||||||
/ (dus.read_percentage * 100.0)
|
END AS INTEGER) AS seconds_per_percent
|
||||||
)
|
|
||||||
END AS seconds_per_percent
|
|
||||||
FROM documents AS docs
|
FROM documents AS docs
|
||||||
LEFT JOIN users ON users.id = $user_id
|
LEFT JOIN users ON users.id = $user_id
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
document_user_statistics AS dus
|
document_user_statistics AS dus
|
||||||
ON dus.document_id = docs.id AND dus.user_id = $user_id
|
ON dus.document_id = docs.id AND dus.user_id = $user_id
|
||||||
WHERE
|
WHERE
|
||||||
docs.deleted = false AND (
|
(docs.id = sqlc.narg('id') OR $id IS NULL)
|
||||||
$query IS NULL OR (
|
AND (docs.deleted = sqlc.narg(deleted) OR $deleted IS NULL)
|
||||||
docs.title LIKE $query OR
|
AND (
|
||||||
|
(
|
||||||
|
docs.title LIKE sqlc.narg('query') OR
|
||||||
docs.author LIKE $query
|
docs.author LIKE $query
|
||||||
)
|
) OR $query IS NULL
|
||||||
)
|
)
|
||||||
ORDER BY dus.last_read DESC, docs.created_at DESC
|
ORDER BY dus.last_read DESC, docs.created_at DESC
|
||||||
LIMIT $limit
|
LIMIT $limit
|
||||||
|
@ -543,87 +543,6 @@ func (q *Queries) GetDocumentProgress(ctx context.Context, arg GetDocumentProgre
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDocumentWithStats = `-- name: GetDocumentWithStats :one
|
|
||||||
SELECT
|
|
||||||
docs.id,
|
|
||||||
docs.title,
|
|
||||||
docs.author,
|
|
||||||
docs.description,
|
|
||||||
docs.isbn10,
|
|
||||||
docs.isbn13,
|
|
||||||
docs.filepath,
|
|
||||||
docs.words,
|
|
||||||
|
|
||||||
CAST(COALESCE(dus.total_wpm, 0.0) AS INTEGER) AS wpm,
|
|
||||||
COALESCE(dus.read_percentage, 0) AS read_percentage,
|
|
||||||
COALESCE(dus.total_time_seconds, 0) AS total_time_seconds,
|
|
||||||
STRFTIME('%Y-%m-%d %H:%M:%S', LOCAL_TIME(COALESCE(dus.last_read, STRFTIME('%Y-%m-%dT%H:%M:%SZ', 0, 'unixepoch')), users.timezone))
|
|
||||||
AS last_read,
|
|
||||||
ROUND(CAST(CASE
|
|
||||||
WHEN dus.percentage IS NULL THEN 0.0
|
|
||||||
WHEN (dus.percentage * 100.0) > 97.0 THEN 100.0
|
|
||||||
ELSE dus.percentage * 100.0
|
|
||||||
END AS REAL), 2) AS percentage,
|
|
||||||
CAST(CASE
|
|
||||||
WHEN dus.total_time_seconds IS NULL THEN 0.0
|
|
||||||
ELSE
|
|
||||||
CAST(dus.total_time_seconds AS REAL)
|
|
||||||
/ (dus.read_percentage * 100.0)
|
|
||||||
END AS INTEGER) AS seconds_per_percent
|
|
||||||
FROM documents AS docs
|
|
||||||
LEFT JOIN users ON users.id = ?1
|
|
||||||
LEFT JOIN
|
|
||||||
document_user_statistics AS dus
|
|
||||||
ON dus.document_id = docs.id AND dus.user_id = ?1
|
|
||||||
WHERE users.id = ?1
|
|
||||||
AND docs.id = ?2
|
|
||||||
LIMIT 1
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetDocumentWithStatsParams struct {
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
DocumentID string `json:"document_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetDocumentWithStatsRow struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Title *string `json:"title"`
|
|
||||||
Author *string `json:"author"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Isbn10 *string `json:"isbn10"`
|
|
||||||
Isbn13 *string `json:"isbn13"`
|
|
||||||
Filepath *string `json:"filepath"`
|
|
||||||
Words *int64 `json:"words"`
|
|
||||||
Wpm int64 `json:"wpm"`
|
|
||||||
ReadPercentage float64 `json:"read_percentage"`
|
|
||||||
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
|
||||||
LastRead interface{} `json:"last_read"`
|
|
||||||
Percentage float64 `json:"percentage"`
|
|
||||||
SecondsPerPercent int64 `json:"seconds_per_percent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetDocumentWithStats(ctx context.Context, arg GetDocumentWithStatsParams) (GetDocumentWithStatsRow, error) {
|
|
||||||
row := q.db.QueryRowContext(ctx, getDocumentWithStats, arg.UserID, arg.DocumentID)
|
|
||||||
var i GetDocumentWithStatsRow
|
|
||||||
err := row.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Title,
|
|
||||||
&i.Author,
|
|
||||||
&i.Description,
|
|
||||||
&i.Isbn10,
|
|
||||||
&i.Isbn13,
|
|
||||||
&i.Filepath,
|
|
||||||
&i.Words,
|
|
||||||
&i.Wpm,
|
|
||||||
&i.ReadPercentage,
|
|
||||||
&i.TotalTimeSeconds,
|
|
||||||
&i.LastRead,
|
|
||||||
&i.Percentage,
|
|
||||||
&i.SecondsPerPercent,
|
|
||||||
)
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDocuments = `-- name: GetDocuments :many
|
const getDocuments = `-- name: GetDocuments :many
|
||||||
SELECT id, md5, basepath, filepath, coverfile, title, author, series, series_index, lang, description, words, gbid, olid, isbn10, isbn13, synced, deleted, updated_at, created_at FROM documents
|
SELECT id, md5, basepath, filepath, coverfile, title, author, series, series_index, lang, description, words, gbid, olid, isbn10, isbn13, synced, deleted, updated_at, created_at FROM documents
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@ -719,37 +638,38 @@ SELECT
|
|||||||
WHEN (dus.percentage * 100.0) > 97.0 THEN 100.0
|
WHEN (dus.percentage * 100.0) > 97.0 THEN 100.0
|
||||||
ELSE dus.percentage * 100.0
|
ELSE dus.percentage * 100.0
|
||||||
END AS REAL), 2) AS percentage,
|
END AS REAL), 2) AS percentage,
|
||||||
|
CAST(CASE
|
||||||
CASE
|
|
||||||
WHEN dus.total_time_seconds IS NULL THEN 0.0
|
WHEN dus.total_time_seconds IS NULL THEN 0.0
|
||||||
ELSE
|
ELSE
|
||||||
ROUND(
|
CAST(dus.total_time_seconds AS REAL)
|
||||||
CAST(dus.total_time_seconds AS REAL)
|
/ (dus.read_percentage * 100.0)
|
||||||
/ (dus.read_percentage * 100.0)
|
END AS INTEGER) AS seconds_per_percent
|
||||||
)
|
|
||||||
END AS seconds_per_percent
|
|
||||||
FROM documents AS docs
|
FROM documents AS docs
|
||||||
LEFT JOIN users ON users.id = ?1
|
LEFT JOIN users ON users.id = ?1
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
document_user_statistics AS dus
|
document_user_statistics AS dus
|
||||||
ON dus.document_id = docs.id AND dus.user_id = ?1
|
ON dus.document_id = docs.id AND dus.user_id = ?1
|
||||||
WHERE
|
WHERE
|
||||||
docs.deleted = false AND (
|
(docs.id = ?2 OR ?2 IS NULL)
|
||||||
?2 IS NULL OR (
|
AND (docs.deleted = ?3 OR ?3 IS NULL)
|
||||||
docs.title LIKE ?2 OR
|
AND (
|
||||||
docs.author LIKE ?2
|
(
|
||||||
)
|
docs.title LIKE ?4 OR
|
||||||
|
docs.author LIKE ?4
|
||||||
|
) OR ?4 IS NULL
|
||||||
)
|
)
|
||||||
ORDER BY dus.last_read DESC, docs.created_at DESC
|
ORDER BY dus.last_read DESC, docs.created_at DESC
|
||||||
LIMIT ?4
|
LIMIT ?6
|
||||||
OFFSET ?3
|
OFFSET ?5
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetDocumentsWithStatsParams struct {
|
type GetDocumentsWithStatsParams struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
Query interface{} `json:"query"`
|
ID *string `json:"id"`
|
||||||
Offset int64 `json:"offset"`
|
Deleted *bool `json:"-"`
|
||||||
Limit int64 `json:"limit"`
|
Query *string `json:"query"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetDocumentsWithStatsRow struct {
|
type GetDocumentsWithStatsRow struct {
|
||||||
@ -766,12 +686,14 @@ type GetDocumentsWithStatsRow struct {
|
|||||||
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
||||||
LastRead interface{} `json:"last_read"`
|
LastRead interface{} `json:"last_read"`
|
||||||
Percentage float64 `json:"percentage"`
|
Percentage float64 `json:"percentage"`
|
||||||
SecondsPerPercent interface{} `json:"seconds_per_percent"`
|
SecondsPerPercent int64 `json:"seconds_per_percent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetDocumentsWithStats(ctx context.Context, arg GetDocumentsWithStatsParams) ([]GetDocumentsWithStatsRow, error) {
|
func (q *Queries) GetDocumentsWithStats(ctx context.Context, arg GetDocumentsWithStatsParams) ([]GetDocumentsWithStatsRow, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, getDocumentsWithStats,
|
rows, err := q.db.QueryContext(ctx, getDocumentsWithStats,
|
||||||
arg.UserID,
|
arg.UserID,
|
||||||
|
arg.ID,
|
||||||
|
arg.Deleted,
|
||||||
arg.Query,
|
arg.Query,
|
||||||
arg.Offset,
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
@ -36,7 +37,7 @@ func (suite *UsersTestSuite) SetupTest() {
|
|||||||
// Create User
|
// Create User
|
||||||
rawAuthHash, _ := utils.GenerateToken(64)
|
rawAuthHash, _ := utils.GenerateToken(64)
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
_, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
|
_, err := suite.dbm.Queries.CreateUser(context.Background(), CreateUserParams{
|
||||||
ID: testUserID,
|
ID: testUserID,
|
||||||
Pass: &testUserPass,
|
Pass: &testUserPass,
|
||||||
AuthHash: &authHash,
|
AuthHash: &authHash,
|
||||||
@ -44,7 +45,7 @@ func (suite *UsersTestSuite) SetupTest() {
|
|||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Create Document
|
// Create Document
|
||||||
_, err = suite.dbm.Queries.UpsertDocument(suite.dbm.Ctx, UpsertDocumentParams{
|
_, err = suite.dbm.Queries.UpsertDocument(context.Background(), UpsertDocumentParams{
|
||||||
ID: documentID,
|
ID: documentID,
|
||||||
Title: &documentTitle,
|
Title: &documentTitle,
|
||||||
Author: &documentAuthor,
|
Author: &documentAuthor,
|
||||||
@ -53,7 +54,7 @@ func (suite *UsersTestSuite) SetupTest() {
|
|||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Create Device
|
// Create Device
|
||||||
_, err = suite.dbm.Queries.UpsertDevice(suite.dbm.Ctx, UpsertDeviceParams{
|
_, err = suite.dbm.Queries.UpsertDevice(context.Background(), UpsertDeviceParams{
|
||||||
ID: deviceID,
|
ID: deviceID,
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
DeviceName: deviceName,
|
DeviceName: deviceName,
|
||||||
@ -62,7 +63,7 @@ func (suite *UsersTestSuite) SetupTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UsersTestSuite) TestGetUser() {
|
func (suite *UsersTestSuite) TestGetUser() {
|
||||||
user, err := suite.dbm.Queries.GetUser(suite.dbm.Ctx, testUserID)
|
user, err := suite.dbm.Queries.GetUser(context.Background(), testUserID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(testUserPass, *user.Pass)
|
suite.Equal(testUserPass, *user.Pass)
|
||||||
}
|
}
|
||||||
@ -76,7 +77,7 @@ func (suite *UsersTestSuite) TestCreateUser() {
|
|||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
|
|
||||||
authHash := fmt.Sprintf("%x", rawAuthHash)
|
authHash := fmt.Sprintf("%x", rawAuthHash)
|
||||||
changed, err := suite.dbm.Queries.CreateUser(suite.dbm.Ctx, CreateUserParams{
|
changed, err := suite.dbm.Queries.CreateUser(context.Background(), CreateUserParams{
|
||||||
ID: testUser,
|
ID: testUser,
|
||||||
Pass: &testPass,
|
Pass: &testPass,
|
||||||
AuthHash: &authHash,
|
AuthHash: &authHash,
|
||||||
@ -85,29 +86,29 @@ func (suite *UsersTestSuite) TestCreateUser() {
|
|||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(int64(1), changed)
|
suite.Equal(int64(1), changed)
|
||||||
|
|
||||||
user, err := suite.dbm.Queries.GetUser(suite.dbm.Ctx, testUser)
|
user, err := suite.dbm.Queries.GetUser(context.Background(), testUser)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(testPass, *user.Pass)
|
suite.Equal(testPass, *user.Pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UsersTestSuite) TestDeleteUser() {
|
func (suite *UsersTestSuite) TestDeleteUser() {
|
||||||
changed, err := suite.dbm.Queries.DeleteUser(suite.dbm.Ctx, testUserID)
|
changed, err := suite.dbm.Queries.DeleteUser(context.Background(), testUserID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Equal(int64(1), changed, "should have one changed row")
|
suite.Equal(int64(1), changed, "should have one changed row")
|
||||||
|
|
||||||
_, err = suite.dbm.Queries.GetUser(suite.dbm.Ctx, testUserID)
|
_, err = suite.dbm.Queries.GetUser(context.Background(), testUserID)
|
||||||
suite.ErrorIs(err, sql.ErrNoRows, "should have no rows error")
|
suite.ErrorIs(err, sql.ErrNoRows, "should have no rows error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UsersTestSuite) TestGetUsers() {
|
func (suite *UsersTestSuite) TestGetUsers() {
|
||||||
users, err := suite.dbm.Queries.GetUsers(suite.dbm.Ctx)
|
users, err := suite.dbm.Queries.GetUsers(context.Background())
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(users, 1, "should have single user")
|
suite.Len(users, 1, "should have single user")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UsersTestSuite) TestUpdateUser() {
|
func (suite *UsersTestSuite) TestUpdateUser() {
|
||||||
newPassword := "newPass123"
|
newPassword := "newPass123"
|
||||||
user, err := suite.dbm.Queries.UpdateUser(suite.dbm.Ctx, UpdateUserParams{
|
user, err := suite.dbm.Queries.UpdateUser(context.Background(), UpdateUserParams{
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
Password: &newPassword,
|
Password: &newPassword,
|
||||||
})
|
})
|
||||||
@ -116,11 +117,11 @@ func (suite *UsersTestSuite) TestUpdateUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UsersTestSuite) TestGetUserStatistics() {
|
func (suite *UsersTestSuite) TestGetUserStatistics() {
|
||||||
err := suite.dbm.CacheTempTables()
|
err := suite.dbm.CacheTempTables(context.Background())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Ensure Zero Items
|
// Ensure Zero Items
|
||||||
userStats, err := suite.dbm.Queries.GetUserStatistics(suite.dbm.Ctx)
|
userStats, err := suite.dbm.Queries.GetUserStatistics(context.Background())
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Empty(userStats, "should be empty")
|
suite.Empty(userStats, "should be empty")
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ func (suite *UsersTestSuite) TestGetUserStatistics() {
|
|||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
// Add Item
|
// Add Item
|
||||||
activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
|
activity, err := suite.dbm.Queries.AddActivity(context.Background(), AddActivityParams{
|
||||||
DocumentID: documentID,
|
DocumentID: documentID,
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
@ -147,21 +148,21 @@ func (suite *UsersTestSuite) TestGetUserStatistics() {
|
|||||||
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = suite.dbm.CacheTempTables()
|
err = suite.dbm.CacheTempTables(context.Background())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Ensure One Item
|
// Ensure One Item
|
||||||
userStats, err = suite.dbm.Queries.GetUserStatistics(suite.dbm.Ctx)
|
userStats, err = suite.dbm.Queries.GetUserStatistics(context.Background())
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(userStats, 1, "should have length of one")
|
suite.Len(userStats, 1, "should have length of one")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *UsersTestSuite) TestGetUsersStreaks() {
|
func (suite *UsersTestSuite) TestGetUsersStreaks() {
|
||||||
err := suite.dbm.CacheTempTables()
|
err := suite.dbm.CacheTempTables(context.Background())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Ensure Zero Items
|
// Ensure Zero Items
|
||||||
userStats, err := suite.dbm.Queries.GetUserStreaks(suite.dbm.Ctx, testUserID)
|
userStats, err := suite.dbm.Queries.GetUserStreaks(context.Background(), testUserID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Empty(userStats, "should be empty")
|
suite.Empty(userStats, "should be empty")
|
||||||
|
|
||||||
@ -174,7 +175,7 @@ func (suite *UsersTestSuite) TestGetUsersStreaks() {
|
|||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
// Add Item
|
// Add Item
|
||||||
activity, err := suite.dbm.Queries.AddActivity(suite.dbm.Ctx, AddActivityParams{
|
activity, err := suite.dbm.Queries.AddActivity(context.Background(), AddActivityParams{
|
||||||
DocumentID: documentID,
|
DocumentID: documentID,
|
||||||
DeviceID: deviceID,
|
DeviceID: deviceID,
|
||||||
UserID: testUserID,
|
UserID: testUserID,
|
||||||
@ -188,11 +189,11 @@ func (suite *UsersTestSuite) TestGetUsersStreaks() {
|
|||||||
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
suite.Equal(counter, activity.ID, fmt.Sprintf("[%d] should have correct id for add activity", counter))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = suite.dbm.CacheTempTables()
|
err = suite.dbm.CacheTempTables(context.Background())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
// Ensure Two Item
|
// Ensure Two Item
|
||||||
userStats, err = suite.dbm.Queries.GetUserStreaks(suite.dbm.Ctx, testUserID)
|
userStats, err = suite.dbm.Queries.GetUserStreaks(context.Background(), testUserID)
|
||||||
suite.Nil(err, "should have nil err")
|
suite.Nil(err, "should have nil err")
|
||||||
suite.Len(userStats, 2, "should have length of two")
|
suite.Len(userStats, 2, "should have length of two")
|
||||||
|
|
||||||
|
37
pkg/formatters/duration.go
Normal file
37
pkg/formatters/duration.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatDuration takes a duration and returns a human-readable duration string.
|
||||||
|
// For example: 1928371 seconds -> "22d 7h 39m 31s"
|
||||||
|
func FormatDuration(d time.Duration) string {
|
||||||
|
if d == 0 {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
days := int(d.Hours()) / 24
|
||||||
|
hours := int(d.Hours()) % 24
|
||||||
|
minutes := int(d.Minutes()) % 60
|
||||||
|
seconds := int(d.Seconds()) % 60
|
||||||
|
|
||||||
|
if days > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dd", days))
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dh", hours))
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%dm", minutes))
|
||||||
|
}
|
||||||
|
if seconds > 0 {
|
||||||
|
parts = append(parts, fmt.Sprintf("%ds", seconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
45
pkg/formatters/numbers.go
Normal file
45
pkg/formatters/numbers.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package formatters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatNumber takes an int64 and returns a human-readable string.
|
||||||
|
// For example: 19823 -> "19.8k", 1500000 -> "1.5M"
|
||||||
|
func FormatNumber(input int64) string {
|
||||||
|
if input == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Negative
|
||||||
|
negative := input < 0
|
||||||
|
if negative {
|
||||||
|
input = -input
|
||||||
|
}
|
||||||
|
|
||||||
|
abbreviations := []string{"", "k", "M", "B", "T"}
|
||||||
|
abbrevIndex := int(math.Log10(float64(input)) / 3)
|
||||||
|
|
||||||
|
// Bounds Check
|
||||||
|
if abbrevIndex >= len(abbreviations) {
|
||||||
|
abbrevIndex = len(abbreviations) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
scaledNumber := float64(input) / math.Pow(10, float64(abbrevIndex*3))
|
||||||
|
|
||||||
|
var result string
|
||||||
|
if scaledNumber >= 100 {
|
||||||
|
result = fmt.Sprintf("%.0f%s", scaledNumber, abbreviations[abbrevIndex])
|
||||||
|
} else if scaledNumber >= 10 {
|
||||||
|
result = fmt.Sprintf("%.1f%s", scaledNumber, abbreviations[abbrevIndex])
|
||||||
|
} else {
|
||||||
|
result = fmt.Sprintf("%.2f%s", scaledNumber, abbreviations[abbrevIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
if negative {
|
||||||
|
result = "-" + result
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
13
pkg/ptr/ptr.go
Normal file
13
pkg/ptr/ptr.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
func Of[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func Deref[T any](v *T) T {
|
||||||
|
var zeroT T
|
||||||
|
if v == nil {
|
||||||
|
return zeroT
|
||||||
|
}
|
||||||
|
return *v
|
||||||
|
}
|
17
pkg/sliceutils/sliceutils.go
Normal file
17
pkg/sliceutils/sliceutils.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package sliceutils
|
||||||
|
|
||||||
|
func First[T any](s []T) (T, bool) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
var zeroT T
|
||||||
|
return zeroT, false
|
||||||
|
}
|
||||||
|
return s[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map[R, I any](s []I, f func(I) R) []R {
|
||||||
|
r := make([]R, 0, len(s))
|
||||||
|
for _, v := range s {
|
||||||
|
r = append(r, f(v))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
18
pkg/utils/utils.go
Normal file
18
pkg/utils/utils.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func Ternary[T any](cond bool, tVal, fVal T) T {
|
||||||
|
if cond {
|
||||||
|
return tVal
|
||||||
|
}
|
||||||
|
return fVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func FirstNonZero[T comparable](v ...T) T {
|
||||||
|
var zero T
|
||||||
|
for _, val := range v {
|
||||||
|
if val != zero {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zero
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@ -52,12 +53,14 @@ func (s *server) Start() {
|
|||||||
ticker := time.NewTicker(15 * time.Minute)
|
ticker := time.NewTicker(15 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Minute))
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
s.runScheduledTasks()
|
s.runScheduledTasks(ctx)
|
||||||
case <-s.done:
|
case <-s.done:
|
||||||
log.Info("Stopping task runner...")
|
log.Info("Stopping task runner...")
|
||||||
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,9 +84,9 @@ func (s *server) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run normal scheduled tasks
|
// Run normal scheduled tasks
|
||||||
func (s *server) runScheduledTasks() {
|
func (s *server) runScheduledTasks(ctx context.Context) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if err := s.db.CacheTempTables(); err != nil {
|
if err := s.db.CacheTempTables(ctx); err != nil {
|
||||||
log.Warn("Refreshing temp table cache failed: ", err)
|
log.Warn("Refreshing temp table cache failed: ", err)
|
||||||
}
|
}
|
||||||
log.Debug("Completed in: ", time.Since(start))
|
log.Debug("Completed in: ", time.Since(start))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user