2023-09-18 23:57:18 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/md5"
|
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-09-23 18:14:57 +00:00
|
|
|
"html"
|
2023-09-18 23:57:18 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
log "github.com/sirupsen/logrus"
|
2024-01-11 01:23:36 +00:00
|
|
|
"reichard.io/antholume/database"
|
|
|
|
"reichard.io/antholume/metadata"
|
2023-09-18 23:57:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type activityItem struct {
|
2023-10-03 11:37:14 +00:00
|
|
|
DocumentID string `json:"document"`
|
|
|
|
StartTime int64 `json:"start_time"`
|
|
|
|
Duration int64 `json:"duration"`
|
|
|
|
Page int64 `json:"page"`
|
|
|
|
Pages int64 `json:"pages"`
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type requestActivity struct {
|
|
|
|
DeviceID string `json:"device_id"`
|
|
|
|
Device string `json:"device"`
|
|
|
|
Activity []activityItem `json:"activity"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type requestCheckActivitySync struct {
|
|
|
|
DeviceID string `json:"device_id"`
|
2023-10-03 20:47:38 +00:00
|
|
|
Device string `json:"device"`
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type requestDocument struct {
|
|
|
|
Documents []database.Document `json:"documents"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type requestPosition struct {
|
|
|
|
DocumentID string `json:"document"`
|
|
|
|
Percentage float64 `json:"percentage"`
|
|
|
|
Progress string `json:"progress"`
|
|
|
|
Device string `json:"device"`
|
|
|
|
DeviceID string `json:"device_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type requestUser struct {
|
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type requestCheckDocumentSync struct {
|
|
|
|
DeviceID string `json:"device_id"`
|
|
|
|
Device string `json:"device"`
|
|
|
|
Have []string `json:"have"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type responseCheckDocumentSync struct {
|
2023-09-19 23:29:55 +00:00
|
|
|
WantFiles []string `json:"want_files"`
|
|
|
|
WantMetadata []string `json:"want_metadata"`
|
|
|
|
Give []database.Document `json:"give"`
|
|
|
|
Delete []string `json:"deleted"`
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type requestDocumentID struct {
|
|
|
|
DocumentID string `uri:"document" binding:"required"`
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koAuthorizeUser(c *gin.Context) {
|
2023-09-18 23:57:18 +00:00
|
|
|
c.JSON(200, gin.H{
|
|
|
|
"authorized": "OK",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koSetProgress(c *gin.Context) {
|
2024-01-10 02:08:40 +00:00
|
|
|
var auth authData
|
|
|
|
if data, _ := c.Get("Authorization"); data != nil {
|
|
|
|
auth = data.(authData)
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
var rPosition requestPosition
|
|
|
|
if err := c.ShouldBindJSON(&rPosition); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid JSON Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Progress Data")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upsert Device
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err := api.db.Queries.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: rPosition.DeviceID,
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-09-18 23:57:18 +00:00
|
|
|
DeviceName: rPosition.Device,
|
2023-10-10 23:06:12 +00:00
|
|
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
2023-09-21 00:35:01 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDevice DB Error:", err)
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Upsert Document
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err := api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: rPosition.DocumentID,
|
2023-09-21 00:35:01 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDocument DB Error:", err)
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create or Replace Progress
|
2024-01-27 19:56:01 +00:00
|
|
|
progress, err := api.db.Queries.UpdateProgress(api.db.Ctx, database.UpdateProgressParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
Percentage: rPosition.Percentage,
|
|
|
|
DocumentID: rPosition.DocumentID,
|
|
|
|
DeviceID: rPosition.DeviceID,
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-09-18 23:57:18 +00:00
|
|
|
Progress: rPosition.Progress,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpdateProgress DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"document": progress.DocumentID,
|
|
|
|
"timestamp": progress.CreatedAt,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koGetProgress(c *gin.Context) {
|
2024-01-10 02:08:40 +00:00
|
|
|
var auth authData
|
|
|
|
if data, _ := c.Get("Authorization"); data != nil {
|
|
|
|
auth = data.(authData)
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
var rDocID requestDocumentID
|
|
|
|
if err := c.ShouldBindUri(&rDocID); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid URI Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-27 19:56:01 +00:00
|
|
|
progress, err := api.db.Queries.GetDocumentProgress(api.db.Ctx, database.GetDocumentProgressParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
DocumentID: rDocID.DocumentID,
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-09-18 23:57:18 +00:00
|
|
|
})
|
|
|
|
|
2023-10-10 23:06:12 +00:00
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
// Not Found
|
|
|
|
c.JSON(http.StatusOK, gin.H{})
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("GetDocumentProgress DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Document")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"document": progress.DocumentID,
|
|
|
|
"percentage": progress.Percentage,
|
|
|
|
"progress": progress.Progress,
|
|
|
|
"device": progress.DeviceName,
|
|
|
|
"device_id": progress.DeviceID,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koAddActivities(c *gin.Context) {
|
2024-01-10 02:08:40 +00:00
|
|
|
var auth authData
|
|
|
|
if data, _ := c.Get("Authorization"); data != nil {
|
|
|
|
auth = data.(authData)
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
var rActivity requestActivity
|
|
|
|
if err := c.ShouldBindJSON(&rActivity); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid JSON Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Activity")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do Transaction
|
2024-01-27 19:56:01 +00:00
|
|
|
tx, err := api.db.DB.Begin()
|
2023-09-18 23:57:18 +00:00
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Transaction Begin DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Derive Unique Documents
|
|
|
|
allDocumentsMap := make(map[string]bool)
|
|
|
|
for _, item := range rActivity.Activity {
|
|
|
|
allDocumentsMap[item.DocumentID] = true
|
|
|
|
}
|
|
|
|
allDocuments := getKeys(allDocumentsMap)
|
|
|
|
|
|
|
|
// Defer & Start Transaction
|
|
|
|
defer tx.Rollback()
|
2024-01-27 19:56:01 +00:00
|
|
|
qtx := api.db.Queries.WithTx(tx)
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
// Upsert Documents
|
|
|
|
for _, doc := range allDocuments {
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err := qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: doc,
|
2023-09-21 00:35:01 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDocument DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Document")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upsert Device
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err = qtx.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: rActivity.DeviceID,
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-09-18 23:57:18 +00:00
|
|
|
DeviceName: rActivity.Device,
|
2023-10-10 23:06:12 +00:00
|
|
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
2023-09-21 00:35:01 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDevice DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Device")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add All Activity
|
|
|
|
for _, item := range rActivity.Activity {
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err := qtx.AddActivity(api.db.Ctx, database.AddActivityParams{
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-11-03 23:38:31 +00:00
|
|
|
DocumentID: item.DocumentID,
|
|
|
|
DeviceID: rActivity.DeviceID,
|
|
|
|
StartTime: time.Unix(int64(item.StartTime), 0).UTC().Format(time.RFC3339),
|
|
|
|
Duration: int64(item.Duration),
|
|
|
|
StartPercentage: float64(item.Page) / float64(item.Pages),
|
|
|
|
EndPercentage: float64(item.Page+1) / float64(item.Pages),
|
2023-09-21 00:35:01 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("AddActivity DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Activity")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit Transaction
|
2023-09-21 00:35:01 +00:00
|
|
|
if err := tx.Commit(); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Transaction Commit DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"added": len(rActivity.Activity),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koCheckActivitySync(c *gin.Context) {
|
2024-01-10 02:08:40 +00:00
|
|
|
var auth authData
|
|
|
|
if data, _ := c.Get("Authorization"); data != nil {
|
|
|
|
auth = data.(authData)
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
var rCheckActivity requestCheckActivitySync
|
|
|
|
if err := c.ShouldBindJSON(&rCheckActivity); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid JSON Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-03 20:47:38 +00:00
|
|
|
// Upsert Device
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err := api.db.Queries.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
2023-10-03 20:47:38 +00:00
|
|
|
ID: rCheckActivity.DeviceID,
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-10-03 20:47:38 +00:00
|
|
|
DeviceName: rCheckActivity.Device,
|
2023-10-06 01:04:57 +00:00
|
|
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
2023-10-03 20:47:38 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDevice DB Error", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Device")
|
2023-10-03 20:47:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-18 23:57:18 +00:00
|
|
|
// Get Last Device Activity
|
2024-01-27 19:56:01 +00:00
|
|
|
lastActivity, err := api.db.Queries.GetLastActivity(api.db.Ctx, database.GetLastActivityParams{
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-09-18 23:57:18 +00:00
|
|
|
DeviceID: rCheckActivity.DeviceID,
|
|
|
|
})
|
|
|
|
if err == sql.ErrNoRows {
|
2023-10-06 01:04:57 +00:00
|
|
|
lastActivity = time.UnixMilli(0).Format(time.RFC3339)
|
2023-09-18 23:57:18 +00:00
|
|
|
} else if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("GetLastActivity DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-06 01:04:57 +00:00
|
|
|
// Parse Time
|
|
|
|
parsedTime, err := time.Parse(time.RFC3339, lastActivity)
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Time Parse Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Error")
|
2023-10-06 01:04:57 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-18 23:57:18 +00:00
|
|
|
c.JSON(http.StatusOK, gin.H{
|
2023-10-06 01:04:57 +00:00
|
|
|
"last_sync": parsedTime.Unix(),
|
2023-09-18 23:57:18 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koAddDocuments(c *gin.Context) {
|
2023-09-18 23:57:18 +00:00
|
|
|
var rNewDocs requestDocument
|
|
|
|
if err := c.ShouldBindJSON(&rNewDocs); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid JSON Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Document(s)")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do Transaction
|
2024-01-27 19:56:01 +00:00
|
|
|
tx, err := api.db.DB.Begin()
|
2023-09-18 23:57:18 +00:00
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Transaction Begin DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Defer & Start Transaction
|
|
|
|
defer tx.Rollback()
|
2024-01-27 19:56:01 +00:00
|
|
|
qtx := api.db.Queries.WithTx(tx)
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
// Upsert Documents
|
|
|
|
for _, doc := range rNewDocs.Documents {
|
2024-01-27 19:56:01 +00:00
|
|
|
_, err := qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: doc.ID,
|
2023-09-23 02:12:36 +00:00
|
|
|
Title: api.sanitizeInput(doc.Title),
|
|
|
|
Author: api.sanitizeInput(doc.Author),
|
|
|
|
Series: api.sanitizeInput(doc.Series),
|
2023-09-18 23:57:18 +00:00
|
|
|
SeriesIndex: doc.SeriesIndex,
|
2023-09-23 02:12:36 +00:00
|
|
|
Lang: api.sanitizeInput(doc.Lang),
|
|
|
|
Description: api.sanitizeInput(doc.Description),
|
2023-09-18 23:57:18 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDocument DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Document")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit Transaction
|
2023-09-21 00:35:01 +00:00
|
|
|
if err := tx.Commit(); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Transaction Commit DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Error")
|
2023-09-21 00:35:01 +00:00
|
|
|
return
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"changed": len(rNewDocs.Documents),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
2024-01-10 02:08:40 +00:00
|
|
|
var auth authData
|
|
|
|
if data, _ := c.Get("Authorization"); data != nil {
|
|
|
|
auth = data.(authData)
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
var rCheckDocs requestCheckDocumentSync
|
|
|
|
if err := c.ShouldBindJSON(&rCheckDocs); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid JSON Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upsert Device
|
2024-01-27 19:56:01 +00:00
|
|
|
_, err := api.db.Queries.UpsertDevice(api.db.Ctx, database.UpsertDeviceParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: rCheckDocs.DeviceID,
|
2024-01-10 02:08:40 +00:00
|
|
|
UserID: auth.UserName,
|
2023-09-18 23:57:18 +00:00
|
|
|
DeviceName: rCheckDocs.Device,
|
2023-10-06 01:04:57 +00:00
|
|
|
LastSynced: time.Now().UTC().Format(time.RFC3339),
|
2023-09-18 23:57:18 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDevice DB Error", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Device")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
missingDocs := []database.Document{}
|
|
|
|
deletedDocIDs := []string{}
|
|
|
|
|
2023-11-03 23:38:31 +00:00
|
|
|
// Get Missing Documents
|
2024-01-27 19:56:01 +00:00
|
|
|
missingDocs, err = api.db.Queries.GetMissingDocuments(api.db.Ctx, rCheckDocs.Have)
|
2023-11-03 23:38:31 +00:00
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("GetMissingDocuments DB Error", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-11-03 23:38:31 +00:00
|
|
|
return
|
|
|
|
}
|
2023-09-18 23:57:18 +00:00
|
|
|
|
2023-11-03 23:38:31 +00:00
|
|
|
// Get Deleted Documents
|
2024-01-27 19:56:01 +00:00
|
|
|
deletedDocIDs, err = api.db.Queries.GetDeletedDocuments(api.db.Ctx, rCheckDocs.Have)
|
2023-11-03 23:38:31 +00:00
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("GetDeletedDocuments DB Error", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-11-03 23:38:31 +00:00
|
|
|
return
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get Wanted Documents
|
|
|
|
jsonHaves, err := json.Marshal(rCheckDocs.Have)
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("JSON Marshal Error", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-27 19:56:01 +00:00
|
|
|
wantedDocs, err := api.db.Queries.GetWantedDocuments(api.db.Ctx, string(jsonHaves))
|
2023-09-18 23:57:18 +00:00
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("GetWantedDocuments DB Error", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-19 23:29:55 +00:00
|
|
|
// Split Metadata & File Wants
|
|
|
|
var wantedMetadataDocIDs []string
|
|
|
|
var wantedFilesDocIDs []string
|
|
|
|
for _, v := range wantedDocs {
|
|
|
|
if v.WantMetadata {
|
|
|
|
wantedMetadataDocIDs = append(wantedMetadataDocIDs, v.ID)
|
|
|
|
}
|
|
|
|
if v.WantFile {
|
|
|
|
wantedFilesDocIDs = append(wantedFilesDocIDs, v.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-18 23:57:18 +00:00
|
|
|
rCheckDocSync := responseCheckDocumentSync{
|
2023-09-19 23:29:55 +00:00
|
|
|
Delete: []string{},
|
|
|
|
WantFiles: []string{},
|
|
|
|
WantMetadata: []string{},
|
|
|
|
Give: []database.Document{},
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure Empty Array
|
2023-09-19 23:29:55 +00:00
|
|
|
if wantedMetadataDocIDs != nil {
|
|
|
|
rCheckDocSync.WantMetadata = wantedMetadataDocIDs
|
|
|
|
}
|
|
|
|
if wantedFilesDocIDs != nil {
|
|
|
|
rCheckDocSync.WantFiles = wantedFilesDocIDs
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
if missingDocs != nil {
|
|
|
|
rCheckDocSync.Give = missingDocs
|
|
|
|
}
|
|
|
|
if deletedDocIDs != nil {
|
|
|
|
rCheckDocSync.Delete = deletedDocIDs
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, rCheckDocSync)
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koUploadExistingDocument(c *gin.Context) {
|
2023-09-18 23:57:18 +00:00
|
|
|
var rDoc requestDocumentID
|
|
|
|
if err := c.ShouldBindUri(&rDoc); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Invalid URI Bind")
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-25 01:45:26 +00:00
|
|
|
// Open Form File
|
2023-09-18 23:57:18 +00:00
|
|
|
fileData, err := c.FormFile("file")
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("File Error:", err)
|
2024-02-25 01:45:26 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "File error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate Document Exists in DB
|
2024-01-27 19:56:01 +00:00
|
|
|
document, err := api.db.Queries.GetDocument(api.db.Ctx, rDoc.DocumentID)
|
2023-09-18 23:57:18 +00:00
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("GetDocument DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unknown Document")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-25 01:45:26 +00:00
|
|
|
// Open File
|
|
|
|
uploadedFile, err := fileData.Open()
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Unable to open file")
|
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unable to open file")
|
|
|
|
return
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
2024-02-25 01:45:26 +00:00
|
|
|
// Check Support
|
|
|
|
docType, err := metadata.GetDocumentTypeReader(uploadedFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Unsupported file")
|
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unsupported file")
|
|
|
|
return
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
2024-02-25 01:45:26 +00:00
|
|
|
// Derive Filename
|
|
|
|
fileName := deriveBaseFileName(&metadata.MetadataInfo{
|
|
|
|
Type: *docType,
|
|
|
|
PartialMD5: &document.ID,
|
|
|
|
Title: document.Title,
|
|
|
|
Author: document.Author,
|
|
|
|
})
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
// Generate Storage Path
|
2024-01-27 19:56:01 +00:00
|
|
|
safePath := filepath.Join(api.cfg.DataPath, "documents", fileName)
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
// Save & Prevent Overwrites
|
|
|
|
_, err = os.Stat(safePath)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err = c.SaveUploadedFile(fileData, safePath)
|
|
|
|
if err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("Save Failure:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "File Error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-25 01:45:26 +00:00
|
|
|
// Acquire Metadata
|
|
|
|
metadataInfo, err := metadata.GetMetadata(safePath)
|
2023-11-03 23:38:31 +00:00
|
|
|
if err != nil {
|
2024-02-25 01:45:26 +00:00
|
|
|
log.Errorf("Unable to acquire metadata: %v", err)
|
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Unable to acquire metadata")
|
2023-11-03 23:38:31 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-18 23:57:18 +00:00
|
|
|
// Upsert Document
|
2024-01-27 19:56:01 +00:00
|
|
|
if _, err = api.db.Queries.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
2023-09-18 23:57:18 +00:00
|
|
|
ID: document.ID,
|
2024-02-25 01:45:26 +00:00
|
|
|
Md5: metadataInfo.MD5,
|
|
|
|
Words: metadataInfo.WordCount,
|
2023-09-18 23:57:18 +00:00
|
|
|
Filepath: &fileName,
|
2023-09-21 00:35:01 +00:00
|
|
|
}); err != nil {
|
2024-01-27 01:45:07 +00:00
|
|
|
log.Error("UpsertDocument DB Error:", err)
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusBadRequest, "Document Error")
|
2023-09-18 23:57:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
|
|
"status": "ok",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-01-01 04:12:46 +00:00
|
|
|
func (api *API) koDemoModeJSONError(c *gin.Context) {
|
2024-01-27 03:07:30 +00:00
|
|
|
apiErrorPage(c, http.StatusUnauthorized, "Not Allowed in Demo Mode")
|
|
|
|
}
|
|
|
|
|
|
|
|
func apiErrorPage(c *gin.Context, errorCode int, errorMessage string) {
|
|
|
|
c.AbortWithStatusJSON(errorCode, gin.H{"error": errorMessage})
|
2023-09-18 23:57:18 +00:00
|
|
|
}
|
|
|
|
|
2023-09-23 02:12:36 +00:00
|
|
|
func (api *API) sanitizeInput(val any) *string {
|
|
|
|
switch v := val.(type) {
|
|
|
|
case *string:
|
|
|
|
if v != nil {
|
2024-01-27 19:56:01 +00:00
|
|
|
newString := html.UnescapeString(htmlPolicy.Sanitize(string(*v)))
|
2023-09-23 02:12:36 +00:00
|
|
|
return &newString
|
|
|
|
}
|
|
|
|
case string:
|
|
|
|
if v != "" {
|
2024-01-27 19:56:01 +00:00
|
|
|
newString := html.UnescapeString(htmlPolicy.Sanitize(string(v)))
|
2023-09-23 02:12:36 +00:00
|
|
|
return &newString
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-18 23:57:18 +00:00
|
|
|
func getKeys[M ~map[K]V, K comparable, V any](m M) []K {
|
|
|
|
r := make([]K, 0, len(m))
|
|
|
|
for k := range m {
|
|
|
|
r = append(r, k)
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFileMD5(filePath string) (*string, error) {
|
|
|
|
file, err := os.Open(filePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
hash := md5.New()
|
|
|
|
_, err = io.Copy(hash, file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fileHash := fmt.Sprintf("%x", hash.Sum(nil))
|
|
|
|
|
|
|
|
return &fileHash, nil
|
|
|
|
}
|