[add] heavy query caching, [add] wpm leaderboard
This commit is contained in:
@@ -53,7 +53,7 @@ func NewApi(db *database.DBManager, c *config.Config) *API {
|
||||
// Configure Cookie Session Store
|
||||
store := cookie.NewStore(newToken)
|
||||
store.Options(sessions.Options{
|
||||
MaxAge: 60 * 60 * 24,
|
||||
MaxAge: 60 * 60 * 24 * 7,
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
@@ -81,7 +81,6 @@ func (api *API) registerWebAppRoutes() {
|
||||
|
||||
render.AddFromFilesFuncs("login", helperFuncs, "templates/login.html")
|
||||
render.AddFromFilesFuncs("home", helperFuncs, "templates/base.html", "templates/home.html")
|
||||
render.AddFromFilesFuncs("graphs", helperFuncs, "templates/base.html", "templates/graphs.html")
|
||||
render.AddFromFilesFuncs("settings", helperFuncs, "templates/base.html", "templates/settings.html")
|
||||
render.AddFromFilesFuncs("activity", helperFuncs, "templates/base.html", "templates/activity.html")
|
||||
render.AddFromFilesFuncs("documents", helperFuncs, "templates/base.html", "templates/documents.html")
|
||||
@@ -107,9 +106,6 @@ func (api *API) registerWebAppRoutes() {
|
||||
api.Router.POST("/documents/:document/edit", api.authWebAppMiddleware, api.editDocument)
|
||||
api.Router.POST("/documents/:document/identify", api.authWebAppMiddleware, api.identifyDocument)
|
||||
api.Router.POST("/documents/:document/delete", api.authWebAppMiddleware, api.deleteDocument)
|
||||
|
||||
// TODO
|
||||
api.Router.GET("/graphs", api.authWebAppMiddleware, baseResourceRoute("graphs"))
|
||||
}
|
||||
|
||||
func (api *API) registerKOAPIRoutes(apiGroup *gin.RouterGroup) {
|
||||
|
||||
@@ -48,19 +48,6 @@ type requestSettingsEdit struct {
|
||||
TimeOffset *string `form:"time_offset"`
|
||||
}
|
||||
|
||||
func baseResourceRoute(template string, args ...map[string]any) func(c *gin.Context) {
|
||||
variables := gin.H{"RouteName": template}
|
||||
if len(args) > 0 {
|
||||
variables = args[0]
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
variables["User"] = rUser
|
||||
c.HTML(http.StatusOK, template, variables)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) webManifest(c *gin.Context) {
|
||||
c.Header("Content-Type", "application/manifest+json")
|
||||
c.File("./assets/manifest.json")
|
||||
@@ -125,18 +112,9 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
return
|
||||
}
|
||||
|
||||
statistics := gin.H{
|
||||
"TotalTimeLeftSeconds": (document.Pages - document.Page) * document.SecondsPerPage,
|
||||
"WordsPerMinute": "N/A",
|
||||
}
|
||||
|
||||
if document.Words != nil && *document.Words != 0 {
|
||||
statistics["WordsPerMinute"] = (*document.Words / document.Pages * document.ReadPages) / (document.TotalTimeSeconds / 60.0)
|
||||
}
|
||||
|
||||
templateVars["RelBase"] = "../"
|
||||
templateVars["Data"] = document
|
||||
templateVars["Statistics"] = statistics
|
||||
templateVars["TotalTimeLeftSeconds"] = (document.Pages - document.Page) * document.SecondsPerPage
|
||||
} else if routeName == "activity" {
|
||||
activityFilter := database.GetActivityParams{
|
||||
UserID: userID,
|
||||
@@ -158,39 +136,22 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
|
||||
templateVars["Data"] = activity
|
||||
} else if routeName == "home" {
|
||||
start_time := time.Now()
|
||||
weekly_streak, err := api.DB.Queries.GetUserWindowStreaks(api.DB.Ctx, database.GetUserWindowStreaksParams{
|
||||
UserID: userID,
|
||||
Window: "WEEK",
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
|
||||
}
|
||||
log.Debug("GetUserWindowStreaks - WEEK - ", time.Since(start_time))
|
||||
start_time = time.Now()
|
||||
|
||||
daily_streak, err := api.DB.Queries.GetUserWindowStreaks(api.DB.Ctx, database.GetUserWindowStreaksParams{
|
||||
UserID: userID,
|
||||
Window: "DAY",
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
|
||||
}
|
||||
log.Debug("GetUserWindowStreaks - DAY - ", time.Since(start_time))
|
||||
|
||||
start_time = time.Now()
|
||||
database_info, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, userID)
|
||||
log.Debug("GetDatabaseInfo - ", time.Since(start_time))
|
||||
|
||||
start_time = time.Now()
|
||||
start := time.Now()
|
||||
read_graph_data, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, userID)
|
||||
log.Debug("GetDailyReadStats - ", time.Since(start_time))
|
||||
log.Info("GetDailyReadStats Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
database_info, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, userID)
|
||||
log.Info("GetDatabaseInfo Performance: ", time.Since(start))
|
||||
|
||||
streaks, _ := api.DB.Queries.GetUserStreaks(api.DB.Ctx, userID)
|
||||
wpn_leaderboard, _ := api.DB.Queries.GetWPMLeaderboard(api.DB.Ctx)
|
||||
|
||||
templateVars["Data"] = gin.H{
|
||||
"DailyStreak": daily_streak,
|
||||
"WeeklyStreak": weekly_streak,
|
||||
"DatabaseInfo": database_info,
|
||||
"GraphData": read_graph_data,
|
||||
"Streaks": streaks,
|
||||
"GraphData": read_graph_data,
|
||||
"DatabaseInfo": database_info,
|
||||
"WPMLeaderboard": wpn_leaderboard,
|
||||
}
|
||||
} else if routeName == "settings" {
|
||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, userID)
|
||||
@@ -512,17 +473,8 @@ func (api *API) identifyDocument(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
statistics := gin.H{
|
||||
"TotalTimeLeftSeconds": (document.Pages - document.Page) * document.SecondsPerPage,
|
||||
"WordsPerMinute": "N/A",
|
||||
}
|
||||
|
||||
if document.Words != nil && *document.Words != 0 {
|
||||
statistics["WordsPerMinute"] = (*document.Words / document.Pages * document.ReadPages) / (document.TotalTimeSeconds / 60.0)
|
||||
}
|
||||
|
||||
templateVars["Data"] = document
|
||||
templateVars["Statistics"] = statistics
|
||||
templateVars["TotalTimeLeftSeconds"] = (document.Pages - document.Page) * document.SecondsPerPage
|
||||
|
||||
c.HTML(http.StatusOK, "document", templateVars)
|
||||
}
|
||||
|
||||
71
api/auth.go
71
api/auth.go
@@ -5,10 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
argon2 "github.com/alexedwards/argon2id"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"reichard.io/bbank/database"
|
||||
)
|
||||
|
||||
@@ -34,14 +36,16 @@ func (api *API) authorizeCredentials(username string, password string) (authoriz
|
||||
func (api *API) authAPIMiddleware(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
|
||||
// Utilize Session Token
|
||||
if authorizedUser := session.Get("authorizedUser"); authorizedUser != nil {
|
||||
c.Set("AuthorizedUser", authorizedUser)
|
||||
// Check Session First
|
||||
if user, ok := getSession(session); ok == true {
|
||||
c.Set("AuthorizedUser", user)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Session Failed -> Check Headers (Allowed on API for KOSync Compatibility)
|
||||
|
||||
var rHeader authHeader
|
||||
if err := c.ShouldBindHeader(&rHeader); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Incorrect Headers"})
|
||||
@@ -57,20 +61,22 @@ func (api *API) authAPIMiddleware(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set Session Cookie
|
||||
session.Set("authorizedUser", rHeader.AuthUser)
|
||||
session.Save()
|
||||
if err := setSession(session, rHeader.AuthUser); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("AuthorizedUser", rHeader.AuthUser)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (api *API) authWebAppMiddleware(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
|
||||
// Utilize Session Token
|
||||
if authorizedUser := session.Get("authorizedUser"); authorizedUser != nil {
|
||||
c.Set("AuthorizedUser", authorizedUser)
|
||||
// Check Session
|
||||
if user, ok := getSession(session); ok == true {
|
||||
c.Set("AuthorizedUser", user)
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Next()
|
||||
return
|
||||
@@ -102,12 +108,17 @@ func (api *API) authFormLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set Session
|
||||
session := sessions.Default(c)
|
||||
if err := setSession(session, username); err != nil {
|
||||
c.HTML(http.StatusUnauthorized, "login", gin.H{
|
||||
"RegistrationEnabled": api.Config.RegistrationEnabled,
|
||||
"Error": "Unknown Error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set Session Cookie
|
||||
session.Set("authorizedUser", username)
|
||||
session.Save()
|
||||
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
@@ -160,12 +171,14 @@ func (api *API) authFormRegister(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set Session
|
||||
session := sessions.Default(c)
|
||||
if err := setSession(session, username); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Set Session Cookie
|
||||
session.Set("authorizedUser", username)
|
||||
session.Save()
|
||||
|
||||
c.Header("Cache-Control", "private")
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
@@ -175,3 +188,27 @@ func (api *API) authLogout(c *gin.Context) {
|
||||
session.Save()
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
}
|
||||
|
||||
func getSession(session sessions.Session) (user string, ok bool) {
|
||||
// Check Session
|
||||
authorizedUser := session.Get("authorizedUser")
|
||||
if authorizedUser == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Refresh
|
||||
expiresAt := session.Get("expiresAt")
|
||||
if expiresAt != nil && expiresAt.(int64)-time.Now().Unix() < 60*60*24 {
|
||||
log.Info("[getSession] Refreshing Session")
|
||||
setSession(session, authorizedUser.(string))
|
||||
}
|
||||
|
||||
return authorizedUser.(string), true
|
||||
}
|
||||
|
||||
func setSession(session sessions.Session, user string) error {
|
||||
// Set Session Cookie
|
||||
session.Set("authorizedUser", user)
|
||||
session.Set("expiresAt", time.Now().Unix()+(60*60*24*7))
|
||||
return session.Save()
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type requestActivity struct {
|
||||
|
||||
type requestCheckActivitySync struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
Device string `json:"device"`
|
||||
}
|
||||
|
||||
type requestDocument struct {
|
||||
@@ -277,6 +278,14 @@ func (api *API) addActivities(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update Temp Tables
|
||||
go func() {
|
||||
log.Info("[addActivities] Caching Temp Tables")
|
||||
if err := api.DB.CacheTempTables(); err != nil {
|
||||
log.Warn("[addActivities] CacheTempTables Failure: ", err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"added": len(rActivity.Activity),
|
||||
})
|
||||
@@ -292,6 +301,18 @@ func (api *API) checkActivitySync(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert Device
|
||||
if _, err := api.DB.Queries.UpsertDevice(api.DB.Ctx, database.UpsertDeviceParams{
|
||||
ID: rCheckActivity.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
DeviceName: rCheckActivity.Device,
|
||||
LastSynced: time.Now().UTC(),
|
||||
}); err != nil {
|
||||
log.Error("[checkActivitySync] UpsertDevice DB Error", err)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Device"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get Last Device Activity
|
||||
lastActivity, err := api.DB.Queries.GetLastActivity(api.DB.Ctx, database.GetLastActivityParams{
|
||||
UserID: rUser.(string),
|
||||
@@ -385,6 +406,7 @@ func (api *API) checkDocumentsSync(c *gin.Context) {
|
||||
ID: rCheckDocs.DeviceID,
|
||||
UserID: rUser.(string),
|
||||
DeviceName: rCheckDocs.Device,
|
||||
LastSynced: time.Now().UTC(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("[checkDocumentsSync] UpsertDevice DB Error", err)
|
||||
|
||||
Reference in New Issue
Block a user