[add] admin panel, [add] better logging
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
d3d89b36f6
commit
c5b181dda4
26
api/api.go
26
api/api.go
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-contrib/multitemplate"
|
"github.com/gin-contrib/multitemplate"
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
@ -32,12 +33,15 @@ type API struct {
|
|||||||
func NewApi(db *database.DBManager, c *config.Config, assets *embed.FS) *API {
|
func NewApi(db *database.DBManager, c *config.Config, assets *embed.FS) *API {
|
||||||
api := &API{
|
api := &API{
|
||||||
HTMLPolicy: bluemonday.StrictPolicy(),
|
HTMLPolicy: bluemonday.StrictPolicy(),
|
||||||
Router: gin.Default(),
|
Router: gin.New(),
|
||||||
Config: c,
|
Config: c,
|
||||||
DB: db,
|
DB: db,
|
||||||
Assets: assets,
|
Assets: assets,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Logger
|
||||||
|
api.Router.Use(apiLogger())
|
||||||
|
|
||||||
// Assets & Web App Templates
|
// Assets & Web App Templates
|
||||||
assetsDir, _ := fs.Sub(assets, "assets")
|
assetsDir, _ := fs.Sub(assets, "assets")
|
||||||
api.Router.StaticFS("/assets", http.FS(assetsDir))
|
api.Router.StaticFS("/assets", http.FS(assetsDir))
|
||||||
@ -107,6 +111,9 @@ func (api *API) registerWebAppRoutes() {
|
|||||||
api.Router.GET("/logout", api.authWebAppMiddleware, api.appAuthLogout)
|
api.Router.GET("/logout", api.authWebAppMiddleware, api.appAuthLogout)
|
||||||
api.Router.GET("/register", api.appGetRegister)
|
api.Router.GET("/register", api.appGetRegister)
|
||||||
api.Router.GET("/settings", api.authWebAppMiddleware, api.appGetSettings)
|
api.Router.GET("/settings", api.authWebAppMiddleware, api.appGetSettings)
|
||||||
|
api.Router.GET("/admin/logs", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdminLogs)
|
||||||
|
api.Router.GET("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appGetAdmin)
|
||||||
|
api.Router.POST("/admin", api.authWebAppMiddleware, api.authAdminWebAppMiddleware, api.appPerformAdminAction)
|
||||||
api.Router.POST("/login", api.appAuthFormLogin)
|
api.Router.POST("/login", api.appAuthFormLogin)
|
||||||
api.Router.POST("/register", api.appAuthFormRegister)
|
api.Router.POST("/register", api.appAuthFormRegister)
|
||||||
|
|
||||||
@ -228,6 +235,23 @@ func (api *API) generateTemplates() *multitemplate.Renderer {
|
|||||||
return &render
|
return &render
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiLogger() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Start Timer
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
// Process Request
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// End Timer
|
||||||
|
endTime := time.Now()
|
||||||
|
latency := endTime.Sub(startTime).Round(time.Microsecond)
|
||||||
|
|
||||||
|
// Log Result
|
||||||
|
log.Infof("[HTTPRouter] %-15s (%10s) %d %7s %s", c.ClientIP(), latency, c.Writer.Status(), c.Request.Method, c.Request.URL.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func generateToken(n int) ([]byte, error) {
|
func generateToken(n int) ([]byte, error) {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
_, err := rand.Read(b)
|
_, err := rand.Read(b)
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -24,6 +27,29 @@ import (
|
|||||||
"reichard.io/bbank/utils"
|
"reichard.io/bbank/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AdminAction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AA_IMPORT AdminAction = "IMPORT"
|
||||||
|
AA_BACKUP AdminAction = "BACKUP"
|
||||||
|
AA_RESTORE AdminAction = "RESTORE"
|
||||||
|
AA_METADATA_MATCH AdminAction = "METADATA_MATCH"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImportType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
IMPORT_TYPE_DIRECT ImportType = "DIRECT"
|
||||||
|
IMPORT_TYPE_COPY ImportType = "COPY"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackupType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BACKUP_TYPE_COVERS BackupType = "COVERS"
|
||||||
|
BACKUP_TYPE_DOCUMENTS BackupType = "DOCUMENTS"
|
||||||
|
)
|
||||||
|
|
||||||
type queryParams struct {
|
type queryParams struct {
|
||||||
Page *int64 `form:"page"`
|
Page *int64 `form:"page"`
|
||||||
Limit *int64 `form:"limit"`
|
Limit *int64 `form:"limit"`
|
||||||
@ -51,6 +77,20 @@ type requestDocumentEdit struct {
|
|||||||
CoverFile *multipart.FileHeader `form:"cover_file"`
|
CoverFile *multipart.FileHeader `form:"cover_file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type requestAdminAction struct {
|
||||||
|
Action AdminAction `form:"action"`
|
||||||
|
|
||||||
|
// Import Action
|
||||||
|
ImportDirectory *string `form:"import_directory"`
|
||||||
|
ImportType *ImportType `form:"import_type"`
|
||||||
|
|
||||||
|
// Backup Action
|
||||||
|
BackupTypes []BackupType `form:"backup_types"`
|
||||||
|
|
||||||
|
// Restore Action
|
||||||
|
RestoreFile *multipart.FileHeader `form:"restore_file"`
|
||||||
|
}
|
||||||
|
|
||||||
type requestDocumentIdentify struct {
|
type requestDocumentIdentify struct {
|
||||||
Title *string `form:"title"`
|
Title *string `form:"title"`
|
||||||
Author *string `form:"author"`
|
Author *string `form:"author"`
|
||||||
@ -93,7 +133,7 @@ func (api *API) appDocumentReader(c *gin.Context) {
|
|||||||
|
|
||||||
func (api *API) appGetDocuments(c *gin.Context) {
|
func (api *API) appGetDocuments(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("documents", c)
|
templateVars := api.getBaseTemplateVars("documents", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
qParams := bindQueryParams(c, 9)
|
qParams := bindQueryParams(c, 9)
|
||||||
|
|
||||||
var query *string
|
var query *string
|
||||||
@ -103,7 +143,7 @@ func (api *API) appGetDocuments(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
documents, err := api.DB.Queries.GetDocumentsWithStats(api.DB.Ctx, database.GetDocumentsWithStatsParams{
|
documents, err := api.DB.Queries.GetDocumentsWithStats(api.DB.Ctx, database.GetDocumentsWithStatsParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
Query: query,
|
Query: query,
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||||
Limit: *qParams.Limit,
|
Limit: *qParams.Limit,
|
||||||
@ -145,7 +185,7 @@ func (api *API) appGetDocuments(c *gin.Context) {
|
|||||||
|
|
||||||
func (api *API) appGetDocument(c *gin.Context) {
|
func (api *API) appGetDocument(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("document", c)
|
templateVars := api.getBaseTemplateVars("document", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
|
|
||||||
var rDocID requestDocumentID
|
var rDocID requestDocumentID
|
||||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||||
@ -155,7 +195,7 @@ func (api *API) appGetDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
DocumentID: rDocID.DocumentID,
|
DocumentID: rDocID.DocumentID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,11 +212,12 @@ func (api *API) appGetDocument(c *gin.Context) {
|
|||||||
|
|
||||||
func (api *API) appGetProgress(c *gin.Context) {
|
func (api *API) appGetProgress(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("progress", c)
|
templateVars := api.getBaseTemplateVars("progress", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
|
|
||||||
qParams := bindQueryParams(c, 15)
|
qParams := bindQueryParams(c, 15)
|
||||||
|
|
||||||
progressFilter := database.GetProgressParams{
|
progressFilter := database.GetProgressParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||||
Limit: *qParams.Limit,
|
Limit: *qParams.Limit,
|
||||||
}
|
}
|
||||||
@ -200,11 +241,11 @@ func (api *API) appGetProgress(c *gin.Context) {
|
|||||||
|
|
||||||
func (api *API) appGetActivity(c *gin.Context) {
|
func (api *API) appGetActivity(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("activity", c)
|
templateVars := api.getBaseTemplateVars("activity", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
qParams := bindQueryParams(c, 15)
|
qParams := bindQueryParams(c, 15)
|
||||||
|
|
||||||
activityFilter := database.GetActivityParams{
|
activityFilter := database.GetActivityParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||||
Limit: *qParams.Limit,
|
Limit: *qParams.Limit,
|
||||||
}
|
}
|
||||||
@ -228,17 +269,17 @@ func (api *API) appGetActivity(c *gin.Context) {
|
|||||||
|
|
||||||
func (api *API) appGetHome(c *gin.Context) {
|
func (api *API) appGetHome(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("home", c)
|
templateVars := api.getBaseTemplateVars("home", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
graphData, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, userID)
|
graphData, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, auth.UserName)
|
||||||
log.Info("GetDailyReadStats Performance: ", time.Since(start))
|
log.Debug("[appGetHome] GetDailyReadStats Performance: ", time.Since(start))
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
databaseInfo, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, userID)
|
databaseInfo, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, auth.UserName)
|
||||||
log.Info("GetDatabaseInfo Performance: ", time.Since(start))
|
log.Debug("[appGetHome] GetDatabaseInfo Performance: ", time.Since(start))
|
||||||
|
|
||||||
streaks, _ := api.DB.Queries.GetUserStreaks(api.DB.Ctx, userID)
|
streaks, _ := api.DB.Queries.GetUserStreaks(api.DB.Ctx, auth.UserName)
|
||||||
WPMLeaderboard, _ := api.DB.Queries.GetWPMLeaderboard(api.DB.Ctx)
|
WPMLeaderboard, _ := api.DB.Queries.GetWPMLeaderboard(api.DB.Ctx)
|
||||||
|
|
||||||
templateVars["Data"] = gin.H{
|
templateVars["Data"] = gin.H{
|
||||||
@ -253,16 +294,16 @@ func (api *API) appGetHome(c *gin.Context) {
|
|||||||
|
|
||||||
func (api *API) appGetSettings(c *gin.Context) {
|
func (api *API) appGetSettings(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("settings", c)
|
templateVars := api.getBaseTemplateVars("settings", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
|
|
||||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, userID)
|
user, err := api.DB.Queries.GetUser(api.DB.Ctx, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[appGetSettings] GetUser DB Error:", err)
|
log.Error("[appGetSettings] GetUser DB Error:", err)
|
||||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, userID)
|
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[appGetSettings] GetDevices DB Error:", err)
|
log.Error("[appGetSettings] GetDevices DB Error:", err)
|
||||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||||
@ -277,6 +318,126 @@ func (api *API) appGetSettings(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "page/settings", templateVars)
|
c.HTML(http.StatusOK, "page/settings", templateVars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) appGetAdmin(c *gin.Context) {
|
||||||
|
templateVars := api.getBaseTemplateVars("admin", c)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "page/admin", templateVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) appGetAdminLogs(c *gin.Context) {
|
||||||
|
// Open Log File
|
||||||
|
logPath := path.Join(api.Config.ConfigPath, "logs/antholume.log")
|
||||||
|
logFile, err := os.Open(logPath)
|
||||||
|
if err != nil {
|
||||||
|
errorPage(c, http.StatusBadRequest, "Missing AnthoLume log file.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer logFile.Close()
|
||||||
|
|
||||||
|
// Write Log File
|
||||||
|
c.Stream(func(w io.Writer) bool {
|
||||||
|
_, err = io.Copy(w, logFile)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||||
|
templateVars := api.getBaseTemplateVars("admin", c)
|
||||||
|
|
||||||
|
var rAdminAction requestAdminAction
|
||||||
|
if err := c.ShouldBind(&rAdminAction); err != nil {
|
||||||
|
log.Error("[appPerformAdminAction] Invalid Form Bind")
|
||||||
|
errorPage(c, http.StatusBadRequest, "Invalid or missing form values.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rAdminAction.Action {
|
||||||
|
case AA_IMPORT:
|
||||||
|
// TODO
|
||||||
|
case AA_METADATA_MATCH:
|
||||||
|
// TODO
|
||||||
|
// 1. Documents xref most recent metadata table?
|
||||||
|
// 2. Select all / deselect?
|
||||||
|
case AA_RESTORE:
|
||||||
|
// TODO
|
||||||
|
// 1. Consume backup ZIP
|
||||||
|
// 2. Move existing to "backup" folder (db, wal, shm, covers, documents)
|
||||||
|
// 3. Extract backup zip
|
||||||
|
// 4. Restart server?
|
||||||
|
case AA_BACKUP:
|
||||||
|
// Get File Paths
|
||||||
|
fileName := fmt.Sprintf("%s.db", api.Config.DBName)
|
||||||
|
dbLocation := path.Join(api.Config.ConfigPath, fileName)
|
||||||
|
|
||||||
|
c.Header("Content-type", "application/octet-stream")
|
||||||
|
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"AnthoLumeExport_%s.zip\"", time.Now().Format("20060102")))
|
||||||
|
|
||||||
|
// Stream Backup ZIP Archive
|
||||||
|
c.Stream(func(w io.Writer) bool {
|
||||||
|
ar := zip.NewWriter(w)
|
||||||
|
|
||||||
|
exportWalker := func(currentPath string, f fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open File on Disk
|
||||||
|
file, err := os.Open(currentPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Derive Export Structure
|
||||||
|
fileName := filepath.Base(currentPath)
|
||||||
|
folderName := filepath.Base(filepath.Dir(currentPath))
|
||||||
|
|
||||||
|
// Create File in Export
|
||||||
|
newF, err := ar.Create(path.Join(folderName, fileName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy File in Export
|
||||||
|
_, err = io.Copy(newF, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy Database File
|
||||||
|
dbFile, _ := os.Open(dbLocation)
|
||||||
|
newDbFile, _ := ar.Create(fileName)
|
||||||
|
io.Copy(newDbFile, dbFile)
|
||||||
|
|
||||||
|
// Backup Covers & Documents
|
||||||
|
for _, item := range rAdminAction.BackupTypes {
|
||||||
|
if item == BACKUP_TYPE_COVERS {
|
||||||
|
filepath.WalkDir(path.Join(api.Config.ConfigPath, "covers"), exportWalker)
|
||||||
|
|
||||||
|
} else if item == BACKUP_TYPE_DOCUMENTS {
|
||||||
|
filepath.WalkDir(path.Join(api.Config.ConfigPath, "documents"), exportWalker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ar.Close()
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "page/admin", templateVars)
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) appGetSearch(c *gin.Context) {
|
func (api *API) appGetSearch(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("search", c)
|
templateVars := api.getBaseTemplateVars("search", c)
|
||||||
|
|
||||||
@ -320,7 +481,10 @@ func (api *API) appGetRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) appGetDocumentProgress(c *gin.Context) {
|
func (api *API) appGetDocumentProgress(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
var rDoc requestDocumentID
|
var rDoc requestDocumentID
|
||||||
if err := c.ShouldBindUri(&rDoc); err != nil {
|
if err := c.ShouldBindUri(&rDoc); err != nil {
|
||||||
@ -331,7 +495,7 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
|||||||
|
|
||||||
progress, err := api.DB.Queries.GetDocumentProgress(api.DB.Ctx, database.GetDocumentProgressParams{
|
progress, err := api.DB.Queries.GetDocumentProgress(api.DB.Ctx, database.GetDocumentProgressParams{
|
||||||
DocumentID: rDoc.DocumentID,
|
DocumentID: rDoc.DocumentID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
@ -341,7 +505,7 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DocumentID: rDoc.DocumentID,
|
DocumentID: rDoc.DocumentID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -361,9 +525,12 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) appGetDevices(c *gin.Context) {
|
func (api *API) appGetDevices(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, rUser.(string))
|
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, auth.UserName)
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
log.Error("[appGetDevices] GetDevices DB Error:", err)
|
log.Error("[appGetDevices] GetDevices DB Error:", err)
|
||||||
@ -677,7 +844,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
|||||||
|
|
||||||
// Get Template Variables
|
// Get Template Variables
|
||||||
templateVars := api.getBaseTemplateVars("document", c)
|
templateVars := api.getBaseTemplateVars("document", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
|
|
||||||
// Get Metadata
|
// Get Metadata
|
||||||
metadataResults, err := metadata.SearchMetadata(metadata.GBOOK, metadata.MetadataInfo{
|
metadataResults, err := metadata.SearchMetadata(metadata.GBOOK, metadata.MetadataInfo{
|
||||||
@ -710,7 +877,7 @@ func (api *API) appIdentifyDocument(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
DocumentID: rDocID.DocumentID,
|
DocumentID: rDocID.DocumentID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -896,17 +1063,19 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
templateVars := api.getBaseTemplateVars("settings", c)
|
templateVars := api.getBaseTemplateVars("settings", c)
|
||||||
userID := templateVars["User"].(string)
|
auth := templateVars["Authorization"].(authData)
|
||||||
|
|
||||||
newUserSettings := database.UpdateUserParams{
|
newUserSettings := database.UpdateUserParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)))
|
||||||
authorized := api.authorizeCredentials(userID, password)
|
data := api.authorizeCredentials(auth.UserName, password)
|
||||||
if authorized == true {
|
if data == nil {
|
||||||
|
templateVars["PasswordErrorMessage"] = "Invalid Password"
|
||||||
|
} else {
|
||||||
password := fmt.Sprintf("%x", md5.Sum([]byte(*rUserSettings.NewPassword)))
|
password := fmt.Sprintf("%x", md5.Sum([]byte(*rUserSettings.NewPassword)))
|
||||||
hashedPassword, err := argon2.CreateHash(password, argon2.DefaultParams)
|
hashedPassword, err := argon2.CreateHash(password, argon2.DefaultParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -915,8 +1084,6 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
templateVars["PasswordMessage"] = "Password Updated"
|
templateVars["PasswordMessage"] = "Password Updated"
|
||||||
newUserSettings.Password = &hashedPassword
|
newUserSettings.Password = &hashedPassword
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
templateVars["PasswordErrorMessage"] = "Invalid Password"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,7 +1102,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get User
|
// Get User
|
||||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, userID)
|
user, err := api.DB.Queries.GetUser(api.DB.Ctx, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[appEditSettings] GetUser DB Error:", err)
|
log.Error("[appEditSettings] GetUser DB Error:", err)
|
||||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUser DB Error: %v", err))
|
||||||
@ -943,7 +1110,7 @@ func (api *API) appEditSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get Devices
|
// Get Devices
|
||||||
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, userID)
|
devices, err := api.DB.Queries.GetDevices(api.DB.Ctx, auth.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[appEditSettings] GetDevices DB Error:", err)
|
log.Error("[appEditSettings] GetDevices DB Error:", err)
|
||||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDevices DB Error: %v", err))
|
||||||
@ -1002,14 +1169,14 @@ func (api *API) getDocumentsWordCount(documents []database.GetDocumentsWithStats
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) getBaseTemplateVars(routeName string, c *gin.Context) gin.H {
|
func (api *API) getBaseTemplateVars(routeName string, c *gin.Context) gin.H {
|
||||||
var userID string
|
var auth authData
|
||||||
if rUser, _ := c.Get("AuthorizedUser"); rUser != nil {
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
userID = rUser.(string)
|
auth = data.(authData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return gin.H{
|
return gin.H{
|
||||||
"User": userID,
|
"Authorization": auth,
|
||||||
"RouteName": routeName,
|
"RouteName": routeName,
|
||||||
"Config": gin.H{
|
"Config": gin.H{
|
||||||
"Version": api.Config.Version,
|
"Version": api.Config.Version,
|
||||||
"SearchEnabled": api.Config.SearchEnabled,
|
"SearchEnabled": api.Config.SearchEnabled,
|
||||||
|
98
api/auth.go
98
api/auth.go
@ -14,6 +14,12 @@ import (
|
|||||||
"reichard.io/bbank/database"
|
"reichard.io/bbank/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Authorization Data
|
||||||
|
type authData struct {
|
||||||
|
UserName string
|
||||||
|
IsAdmin bool
|
||||||
|
}
|
||||||
|
|
||||||
// KOSync API Auth Headers
|
// KOSync API Auth Headers
|
||||||
type authKOHeader struct {
|
type authKOHeader struct {
|
||||||
AuthUser string `header:"x-auth-user"`
|
AuthUser string `header:"x-auth-user"`
|
||||||
@ -25,25 +31,28 @@ type authOPDSHeader struct {
|
|||||||
Authorization string `header:"authorization"`
|
Authorization string `header:"authorization"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) authorizeCredentials(username string, password string) (authorized bool) {
|
func (api *API) authorizeCredentials(username string, password string) (auth *authData) {
|
||||||
user, err := api.DB.Queries.GetUser(api.DB.Ctx, username)
|
user, err := api.DB.Queries.GetUser(api.DB.Ctx, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || match != true {
|
if match, err := argon2.ComparePasswordAndHash(password, *user.Pass); err != nil || match != true {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return &authData{
|
||||||
|
UserName: user.ID,
|
||||||
|
IsAdmin: user.Admin,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) authKOMiddleware(c *gin.Context) {
|
func (api *API) authKOMiddleware(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
|
||||||
// Check Session First
|
// Check Session First
|
||||||
if user, ok := getSession(session); ok == true {
|
if auth, ok := getSession(session); ok == true {
|
||||||
c.Set("AuthorizedUser", user)
|
c.Set("Authorization", auth)
|
||||||
c.Header("Cache-Control", "private")
|
c.Header("Cache-Control", "private")
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
@ -61,17 +70,18 @@ func (api *API) authKOMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if authorized := api.authorizeCredentials(rHeader.AuthUser, rHeader.AuthKey); authorized != true {
|
authData := api.authorizeCredentials(rHeader.AuthUser, rHeader.AuthKey)
|
||||||
|
if authData == nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setSession(session, rHeader.AuthUser); err != nil {
|
if err := setSession(session, *authData); err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("AuthorizedUser", rHeader.AuthUser)
|
c.Set("Authorization", *authData)
|
||||||
c.Header("Cache-Control", "private")
|
c.Header("Cache-Control", "private")
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
@ -89,12 +99,13 @@ 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)))
|
||||||
if authorized := api.authorizeCredentials(user, password); authorized != true {
|
authData := api.authorizeCredentials(user, password)
|
||||||
|
if authData == nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Set("AuthorizedUser", user)
|
c.Set("Authorization", *authData)
|
||||||
c.Header("Cache-Control", "private")
|
c.Header("Cache-Control", "private")
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
@ -103,8 +114,8 @@ func (api *API) authWebAppMiddleware(c *gin.Context) {
|
|||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
|
|
||||||
// Check Session
|
// Check Session
|
||||||
if user, ok := getSession(session); ok == true {
|
if auth, ok := getSession(session); ok == true {
|
||||||
c.Set("AuthorizedUser", user)
|
c.Set("Authorization", auth)
|
||||||
c.Header("Cache-Control", "private")
|
c.Header("Cache-Control", "private")
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
@ -115,6 +126,20 @@ func (api *API) authWebAppMiddleware(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) authAdminWebAppMiddleware(c *gin.Context) {
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth := data.(authData)
|
||||||
|
if auth.IsAdmin == true {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorPage(c, http.StatusUnauthorized, "Admin Permissions Required")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) appAuthFormLogin(c *gin.Context) {
|
func (api *API) appAuthFormLogin(c *gin.Context) {
|
||||||
templateVars := api.getBaseTemplateVars("login", c)
|
templateVars := api.getBaseTemplateVars("login", c)
|
||||||
|
|
||||||
@ -129,7 +154,8 @@ func (api *API) appAuthFormLogin(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)))
|
||||||
if authorized := api.authorizeCredentials(username, password); authorized != true {
|
authData := api.authorizeCredentials(username, password)
|
||||||
|
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)
|
||||||
return
|
return
|
||||||
@ -137,7 +163,7 @@ func (api *API) appAuthFormLogin(c *gin.Context) {
|
|||||||
|
|
||||||
// Set Session
|
// Set Session
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
if err := setSession(session, username); err != nil {
|
if err := setSession(session, *authData); err != nil {
|
||||||
templateVars["Error"] = "Invalid Credentials"
|
templateVars["Error"] = "Invalid Credentials"
|
||||||
c.HTML(http.StatusUnauthorized, "page/login", templateVars)
|
c.HTML(http.StatusUnauthorized, "page/login", templateVars)
|
||||||
return
|
return
|
||||||
@ -194,9 +220,22 @@ func (api *API) appAuthFormRegister(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get User
|
||||||
|
user, err := api.DB.Queries.GetUser(api.DB.Ctx, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[appAuthFormRegister] GetUser DB Error:", err)
|
||||||
|
templateVars["Error"] = "Registration Disabled or User Already Exists"
|
||||||
|
c.HTML(http.StatusBadRequest, "page/login", templateVars)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Set Session
|
// Set Session
|
||||||
|
auth := authData{
|
||||||
|
UserName: user.ID,
|
||||||
|
IsAdmin: user.Admin,
|
||||||
|
}
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
if err := setSession(session, username); err != nil {
|
if err := setSession(session, auth); err != nil {
|
||||||
errorPage(c, http.StatusUnauthorized, "Unauthorized.")
|
errorPage(c, http.StatusUnauthorized, "Unauthorized.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -212,26 +251,35 @@ func (api *API) appAuthLogout(c *gin.Context) {
|
|||||||
c.Redirect(http.StatusFound, "/login")
|
c.Redirect(http.StatusFound, "/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSession(session sessions.Session) (user string, ok bool) {
|
func getSession(session sessions.Session) (auth authData, ok bool) {
|
||||||
// Check Session
|
// Check Session
|
||||||
authorizedUser := session.Get("authorizedUser")
|
authorizedUser := session.Get("authorizedUser")
|
||||||
if authorizedUser == nil {
|
isAdmin := session.Get("isAdmin")
|
||||||
return "", false
|
expiresAt := session.Get("expiresAt")
|
||||||
|
if authorizedUser == nil || isAdmin == nil || expiresAt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Auth Object
|
||||||
|
auth = authData{
|
||||||
|
UserName: authorizedUser.(string),
|
||||||
|
IsAdmin: isAdmin.(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh
|
// Refresh
|
||||||
expiresAt := session.Get("expiresAt")
|
if expiresAt.(int64)-time.Now().Unix() < 60*60*24 {
|
||||||
if expiresAt != nil && expiresAt.(int64)-time.Now().Unix() < 60*60*24 {
|
|
||||||
log.Info("[getSession] Refreshing Session")
|
log.Info("[getSession] Refreshing Session")
|
||||||
setSession(session, authorizedUser.(string))
|
setSession(session, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
return authorizedUser.(string), true
|
// Authorized
|
||||||
|
return auth, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSession(session sessions.Session, user string) error {
|
func setSession(session sessions.Session, auth authData) error {
|
||||||
// Set Session Cookie
|
// Set Session Cookie
|
||||||
session.Set("authorizedUser", user)
|
session.Set("authorizedUser", auth.UserName)
|
||||||
|
session.Set("isAdmin", auth.IsAdmin)
|
||||||
session.Set("expiresAt", time.Now().Unix()+(60*60*24*7))
|
session.Set("expiresAt", time.Now().Unix()+(60*60*24*7))
|
||||||
return session.Save()
|
return session.Save()
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,10 @@ func (api *API) koCreateUser(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) koSetProgress(c *gin.Context) {
|
func (api *API) koSetProgress(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
var rPosition requestPosition
|
var rPosition requestPosition
|
||||||
if err := c.ShouldBindJSON(&rPosition); err != nil {
|
if err := c.ShouldBindJSON(&rPosition); err != nil {
|
||||||
@ -141,7 +144,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(api.DB.Ctx, database.UpsertDeviceParams{
|
||||||
ID: rPosition.DeviceID,
|
ID: rPosition.DeviceID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DeviceName: rPosition.Device,
|
DeviceName: rPosition.Device,
|
||||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -160,7 +163,7 @@ func (api *API) koSetProgress(c *gin.Context) {
|
|||||||
Percentage: rPosition.Percentage,
|
Percentage: rPosition.Percentage,
|
||||||
DocumentID: rPosition.DocumentID,
|
DocumentID: rPosition.DocumentID,
|
||||||
DeviceID: rPosition.DeviceID,
|
DeviceID: rPosition.DeviceID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
Progress: rPosition.Progress,
|
Progress: rPosition.Progress,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -171,7 +174,7 @@ func (api *API) koSetProgress(c *gin.Context) {
|
|||||||
|
|
||||||
// Update Statistic
|
// Update Statistic
|
||||||
log.Info("[koSetProgress] UpdateDocumentUserStatistic Running...")
|
log.Info("[koSetProgress] UpdateDocumentUserStatistic Running...")
|
||||||
if err := api.DB.UpdateDocumentUserStatistic(rPosition.DocumentID, rUser.(string)); err != nil {
|
if err := api.DB.UpdateDocumentUserStatistic(rPosition.DocumentID, auth.UserName); err != nil {
|
||||||
log.Error("[koSetProgress] UpdateDocumentUserStatistic Error:", err)
|
log.Error("[koSetProgress] UpdateDocumentUserStatistic Error:", err)
|
||||||
}
|
}
|
||||||
log.Info("[koSetProgress] UpdateDocumentUserStatistic Complete")
|
log.Info("[koSetProgress] UpdateDocumentUserStatistic Complete")
|
||||||
@ -183,7 +186,10 @@ func (api *API) koSetProgress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) koGetProgress(c *gin.Context) {
|
func (api *API) koGetProgress(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
var rDocID requestDocumentID
|
var rDocID requestDocumentID
|
||||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
if err := c.ShouldBindUri(&rDocID); err != nil {
|
||||||
@ -194,7 +200,7 @@ func (api *API) koGetProgress(c *gin.Context) {
|
|||||||
|
|
||||||
progress, err := api.DB.Queries.GetDocumentProgress(api.DB.Ctx, database.GetDocumentProgressParams{
|
progress, err := api.DB.Queries.GetDocumentProgress(api.DB.Ctx, database.GetDocumentProgressParams{
|
||||||
DocumentID: rDocID.DocumentID,
|
DocumentID: rDocID.DocumentID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
@ -217,7 +223,10 @@ func (api *API) koGetProgress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) koAddActivities(c *gin.Context) {
|
func (api *API) koAddActivities(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
var rActivity requestActivity
|
var rActivity requestActivity
|
||||||
if err := c.ShouldBindJSON(&rActivity); err != nil {
|
if err := c.ShouldBindJSON(&rActivity); err != nil {
|
||||||
@ -259,7 +268,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(api.DB.Ctx, database.UpsertDeviceParams{
|
||||||
ID: rActivity.DeviceID,
|
ID: rActivity.DeviceID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DeviceName: rActivity.Device,
|
DeviceName: rActivity.Device,
|
||||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -271,7 +280,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(api.DB.Ctx, database.AddActivityParams{
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DocumentID: item.DocumentID,
|
DocumentID: item.DocumentID,
|
||||||
DeviceID: rActivity.DeviceID,
|
DeviceID: rActivity.DeviceID,
|
||||||
StartTime: time.Unix(int64(item.StartTime), 0).UTC().Format(time.RFC3339),
|
StartTime: time.Unix(int64(item.StartTime), 0).UTC().Format(time.RFC3339),
|
||||||
@ -295,7 +304,7 @@ func (api *API) koAddActivities(c *gin.Context) {
|
|||||||
// Update Statistic
|
// Update Statistic
|
||||||
for _, doc := range allDocuments {
|
for _, doc := range allDocuments {
|
||||||
log.Info("[koAddActivities] UpdateDocumentUserStatistic Running...")
|
log.Info("[koAddActivities] UpdateDocumentUserStatistic Running...")
|
||||||
if err := api.DB.UpdateDocumentUserStatistic(doc, rUser.(string)); err != nil {
|
if err := api.DB.UpdateDocumentUserStatistic(doc, auth.UserName); err != nil {
|
||||||
log.Error("[koAddActivities] UpdateDocumentUserStatistic Error:", err)
|
log.Error("[koAddActivities] UpdateDocumentUserStatistic Error:", err)
|
||||||
}
|
}
|
||||||
log.Info("[koAddActivities] UpdateDocumentUserStatistic Complete")
|
log.Info("[koAddActivities] UpdateDocumentUserStatistic Complete")
|
||||||
@ -307,7 +316,10 @@ func (api *API) koAddActivities(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) koCheckActivitySync(c *gin.Context) {
|
func (api *API) koCheckActivitySync(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
var rCheckActivity requestCheckActivitySync
|
var rCheckActivity requestCheckActivitySync
|
||||||
if err := c.ShouldBindJSON(&rCheckActivity); err != nil {
|
if err := c.ShouldBindJSON(&rCheckActivity); err != nil {
|
||||||
@ -319,7 +331,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(api.DB.Ctx, database.UpsertDeviceParams{
|
||||||
ID: rCheckActivity.DeviceID,
|
ID: rCheckActivity.DeviceID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DeviceName: rCheckActivity.Device,
|
DeviceName: rCheckActivity.Device,
|
||||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -330,7 +342,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(api.DB.Ctx, database.GetLastActivityParams{
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DeviceID: rCheckActivity.DeviceID,
|
DeviceID: rCheckActivity.DeviceID,
|
||||||
})
|
})
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
@ -405,7 +417,10 @@ func (api *API) koAddDocuments(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
||||||
rUser, _ := c.Get("AuthorizedUser")
|
var auth authData
|
||||||
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
|
auth = data.(authData)
|
||||||
|
}
|
||||||
|
|
||||||
var rCheckDocs requestCheckDocumentSync
|
var rCheckDocs requestCheckDocumentSync
|
||||||
if err := c.ShouldBindJSON(&rCheckDocs); err != nil {
|
if err := c.ShouldBindJSON(&rCheckDocs); err != nil {
|
||||||
@ -417,7 +432,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(api.DB.Ctx, database.UpsertDeviceParams{
|
||||||
ID: rCheckDocs.DeviceID,
|
ID: rCheckDocs.DeviceID,
|
||||||
UserID: rUser.(string),
|
UserID: auth.UserName,
|
||||||
DeviceName: rCheckDocs.Device,
|
DeviceName: rCheckDocs.Device,
|
||||||
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
||||||
})
|
})
|
||||||
|
@ -61,9 +61,9 @@ func (api *API) opdsEntry(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) opdsDocuments(c *gin.Context) {
|
func (api *API) opdsDocuments(c *gin.Context) {
|
||||||
var userID string
|
var auth authData
|
||||||
if rUser, _ := c.Get("AuthorizedUser"); rUser != nil {
|
if data, _ := c.Get("Authorization"); data != nil {
|
||||||
userID = rUser.(string)
|
auth = data.(authData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Potential URL Parameters (Default Pagination - 100)
|
// Potential URL Parameters (Default Pagination - 100)
|
||||||
@ -78,7 +78,7 @@ 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(api.DB.Ctx, database.GetDocumentsWithStatsParams{
|
||||||
UserID: userID,
|
UserID: auth.UserName,
|
||||||
Query: query,
|
Query: query,
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
||||||
Limit: *qParams.Limit,
|
Limit: *qParams.Limit,
|
||||||
|
File diff suppressed because one or more lines are too long
@ -2,9 +2,11 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/snowzach/rotatefilehook"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -32,6 +34,15 @@ type Config struct {
|
|||||||
CookieHTTPOnly bool
|
CookieHTTPOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UTCFormatter struct {
|
||||||
|
log.Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) {
|
||||||
|
e.Time = e.Time.UTC()
|
||||||
|
return u.Formatter.Format(e)
|
||||||
|
}
|
||||||
|
|
||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
c := &Config{
|
c := &Config{
|
||||||
Version: "0.0.1",
|
Version: "0.0.1",
|
||||||
@ -50,11 +61,31 @@ func Load() *Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Log Level
|
// Log Level
|
||||||
ll, err := log.ParseLevel(c.LogLevel)
|
logLevel, err := log.ParseLevel(c.LogLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ll = log.InfoLevel
|
logLevel = log.InfoLevel
|
||||||
}
|
}
|
||||||
log.SetLevel(ll)
|
|
||||||
|
// Log Formatter
|
||||||
|
ttyLogFormatter := &UTCFormatter{&log.TextFormatter{FullTimestamp: true}}
|
||||||
|
fileLogFormatter := &UTCFormatter{&log.TextFormatter{FullTimestamp: true, DisableColors: true}}
|
||||||
|
|
||||||
|
// Log Rotater
|
||||||
|
rotateFileHook, err := rotatefilehook.NewRotateFileHook(rotatefilehook.RotateFileConfig{
|
||||||
|
Filename: path.Join(c.ConfigPath, "logs/antholume.log"),
|
||||||
|
MaxSize: 50,
|
||||||
|
MaxBackups: 3,
|
||||||
|
MaxAge: 30,
|
||||||
|
Level: logLevel,
|
||||||
|
Formatter: fileLogFormatter,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("[config.Load] Unable to initialize file rotate hook")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetLevel(logLevel)
|
||||||
|
log.SetFormatter(ttyLogFormatter)
|
||||||
|
log.AddHook(rotateFileHook)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -47,6 +47,7 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
@ -57,6 +58,7 @@ require (
|
|||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
golang.org/x/tools v0.13.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -133,6 +133,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d h1:4660u5vJtsyrn3QwJNfESwCws+TM1CMhRn123xjVyQ8=
|
||||||
|
github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d/go.mod h1:ZLVe3VfhAuMYLYWliGEydMBoRnfib8EFSqkBYu1ck9E=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@ -236,6 +238,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
11
main.go
11
main.go
@ -15,18 +15,7 @@ import (
|
|||||||
//go:embed templates/* assets/*
|
//go:embed templates/* assets/*
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
type UTCFormatter struct {
|
|
||||||
log.Formatter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UTCFormatter) Format(e *log.Entry) ([]byte, error) {
|
|
||||||
e.Time = e.Time.UTC()
|
|
||||||
return u.Formatter.Format(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFormatter(UTCFormatter{&log.TextFormatter{FullTimestamp: true}})
|
|
||||||
|
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Name: "AnthoLume",
|
Name: "AnthoLume",
|
||||||
Usage: "A self hosted e-book progress tracker.",
|
Usage: "A self hosted e-book progress tracker.",
|
||||||
|
@ -231,6 +231,19 @@
|
|||||||
aria-orientation="vertical"
|
aria-orientation="vertical"
|
||||||
aria-labelledby="options-menu"
|
aria-labelledby="options-menu"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{{ if .Authorization.IsAdmin }}
|
||||||
|
<a
|
||||||
|
href="/admin"
|
||||||
|
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600"
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
<span class="flex flex-col">
|
||||||
|
<span>Administration</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/settings"
|
href="/settings"
|
||||||
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600"
|
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600"
|
||||||
@ -265,7 +278,7 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center gap-2 text-gray-500 dark:text-white text-md py-4 cursor-pointer"
|
class="flex items-center gap-2 text-gray-500 dark:text-white text-md py-4 cursor-pointer"
|
||||||
>
|
>
|
||||||
<span>{{ .User }}</span>
|
<span>{{ .Authorization.UserName }}</span>
|
||||||
<span class="text-gray-800 dark:text-gray-200">{{ template "svg/dropdown" (dict "Size" 20) }}</span>
|
<span class="text-gray-800 dark:text-gray-200">{{ template "svg/dropdown" (dict "Size" 20) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
183
templates/pages/admin.html
Normal file
183
templates/pages/admin.html
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
{{template "base" .}} {{define "title"}}Administration{{end}} {{define
|
||||||
|
"header"}}
|
||||||
|
<a href="./admin">Administration</a>
|
||||||
|
{{end}} {{define "content"}}
|
||||||
|
<div class="w-full flex flex-col md:flex-row gap-4">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col p-4 items-center rounded shadow-lg md:w-60 lg:w-80 bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
||||||
|
>
|
||||||
|
{{ template "svg/user" (dict "Size" 60) }}
|
||||||
|
<p class="text-lg">{{ .Authorization.UserName }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 grow">
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-2 grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
||||||
|
>
|
||||||
|
<p class="text-lg font-semibold mb-2">Import Documents</p>
|
||||||
|
<form class="flex gap-4 flex-col" action="./admin" method="POST">
|
||||||
|
<input type="text" name="action" value="IMPORT" class="hidden" />
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex grow relative">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"
|
||||||
|
>
|
||||||
|
{{ template "svg/import" (dict "Size" 15) }}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="import_directory"
|
||||||
|
class="flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
|
||||||
|
placeholder="Directory"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mr-4">
|
||||||
|
<div class="inline-flex gap-2">
|
||||||
|
<input
|
||||||
|
checked
|
||||||
|
type="radio"
|
||||||
|
id="copy"
|
||||||
|
name="import_type"
|
||||||
|
value="COPY"
|
||||||
|
/>
|
||||||
|
<label for="copy"> Copy</label>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex gap-2">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="direct"
|
||||||
|
name="import_type"
|
||||||
|
value="DIRECT"
|
||||||
|
/>
|
||||||
|
<label for="direct"> Direct</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
|
||||||
|
>
|
||||||
|
<span class="w-full">Import Directory</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{{ if .PasswordErrorMessage }}
|
||||||
|
<span class="text-red-400 text-xs">{{ .PasswordErrorMessage }}</span>
|
||||||
|
{{ else if .PasswordMessage }}
|
||||||
|
<span class="text-green-400 text-xs">{{ .PasswordMessage }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-2 grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
||||||
|
>
|
||||||
|
<p class="text-lg font-semibold mb-2">Backup & Restore</p>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<form class="flex justify-between" action="./admin" method="POST">
|
||||||
|
<input type="text" name="action" value="BACKUP" class="hidden" />
|
||||||
|
<div class="flex gap-8 items-center">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="backup_covers"
|
||||||
|
name="backup_types"
|
||||||
|
value="COVERS"
|
||||||
|
/>
|
||||||
|
<label for="backup_covers"> Covers</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="backup_documents"
|
||||||
|
name="backup_types"
|
||||||
|
value="DOCUMENTS"
|
||||||
|
/>
|
||||||
|
<label for="backup_documents"> Documents</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
|
||||||
|
>
|
||||||
|
<span class="w-full">Backup</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
action="./admin"
|
||||||
|
class="flex justify-between grow"
|
||||||
|
>
|
||||||
|
<input type="text" name="action" value="RESTORE" class="hidden" />
|
||||||
|
<div class="flex items-center w-1/2">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".zip"
|
||||||
|
name="restore_file"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
|
||||||
|
>
|
||||||
|
<span class="w-full">Restore</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{ if .PasswordErrorMessage }}
|
||||||
|
<span class="text-red-400 text-xs">{{ .PasswordErrorMessage }}</span>
|
||||||
|
{{ else if .PasswordMessage }}
|
||||||
|
<span class="text-green-400 text-xs">{{ .PasswordMessage }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
||||||
|
>
|
||||||
|
<p class="text-lg font-semibold">Tasks</p>
|
||||||
|
<table class="min-w-full bg-white dark:bg-gray-700 text-sm">
|
||||||
|
<tbody class="text-black dark:text-white">
|
||||||
|
<tr>
|
||||||
|
<td class="pl-0">
|
||||||
|
<p>Metadata Matching</p>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 float-right">
|
||||||
|
<form action="./admin" method="POST">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="action"
|
||||||
|
value="METADATA_MATCH"
|
||||||
|
class="hidden"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
|
||||||
|
>
|
||||||
|
<span class="w-full">Run</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p>Logs</p>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 float-right">
|
||||||
|
<a
|
||||||
|
href="./admin/logs"
|
||||||
|
target="_blank"
|
||||||
|
class="inline-block w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
|
||||||
|
>
|
||||||
|
<span class="w-full">View</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
@ -7,7 +7,7 @@
|
|||||||
class="flex flex-col p-4 items-center rounded shadow-lg md:w-60 lg:w-80 bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
class="flex flex-col p-4 items-center rounded shadow-lg md:w-60 lg:w-80 bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
||||||
>
|
>
|
||||||
{{ template "svg/user" (dict "Size" 60) }}
|
{{ template "svg/user" (dict "Size" 60) }}
|
||||||
<p class="text-lg">{{ .User }}</p>
|
<p class="text-lg">{{ .Authorization.UserName }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
13
templates/svgs/import.svg
Normal file
13
templates/svgs/import.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg
|
||||||
|
width="{{ or .Size 24 }}"
|
||||||
|
height="{{ or .Size 24 }}"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M2.06935 5.00839C2 5.37595 2 5.81722 2 6.69975V13.75C2 17.5212 2 19.4069 3.17157 20.5784C4.34315 21.75 6.22876 21.75 10 21.75H14C17.7712 21.75 19.6569 21.75 20.8284 20.5784C22 19.4069 22 17.5212 22 13.75V11.5479C22 8.91554 22 7.59935 21.2305 6.74383C21.1598 6.66514 21.0849 6.59024 21.0062 6.51946C20.1506 5.75 18.8345 5.75 16.2021 5.75H15.8284C14.6747 5.75 14.0979 5.75 13.5604 5.59678C13.2651 5.5126 12.9804 5.39471 12.7121 5.24543C12.2237 4.97367 11.8158 4.56578 11 3.75L10.4497 3.19975C10.1763 2.92633 10.0396 2.78961 9.89594 2.67051C9.27652 2.15704 8.51665 1.84229 7.71557 1.76738C7.52976 1.75 7.33642 1.75 6.94975 1.75C6.06722 1.75 5.62595 1.75 5.25839 1.81935C3.64031 2.12464 2.37464 3.39031 2.06935 5.00839ZM12 11C12.4142 11 12.75 11.3358 12.75 11.75V13H14C14.4142 13 14.75 13.3358 14.75 13.75C14.75 14.1642 14.4142 14.5 14 14.5H12.75V15.75C12.75 16.1642 12.4142 16.5 12 16.5C11.5858 16.5 11.25 16.1642 11.25 15.75V14.5H10C9.58579 14.5 9.25 14.1642 9.25 13.75C9.25 13.3358 9.58579 13 10 13H11.25V11.75C11.25 11.3358 11.5858 11 12 11Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Loading…
Reference in New Issue
Block a user