295 lines
8.2 KiB
Go
295 lines
8.2 KiB
Go
package v1
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"reichard.io/antholume/database"
|
|
"reichard.io/antholume/metadata"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// GET /documents
|
|
func (s *Server) GetDocuments(ctx context.Context, request GetDocumentsRequestObject) (GetDocumentsResponseObject, error) {
|
|
auth, ok := s.getSessionFromContext(ctx)
|
|
if !ok {
|
|
return GetDocuments401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
|
}
|
|
|
|
page := int64(1)
|
|
if request.Params.Page != nil {
|
|
page = *request.Params.Page
|
|
}
|
|
|
|
limit := int64(9)
|
|
if request.Params.Limit != nil {
|
|
limit = *request.Params.Limit
|
|
}
|
|
|
|
search := ""
|
|
if request.Params.Search != nil {
|
|
search = "%" + *request.Params.Search + "%"
|
|
}
|
|
|
|
rows, err := s.db.Queries.GetDocumentsWithStats(
|
|
ctx,
|
|
database.GetDocumentsWithStatsParams{
|
|
UserID: auth.UserName,
|
|
Query: &search,
|
|
Deleted: ptrOf(false),
|
|
Offset: (page - 1) * limit,
|
|
Limit: limit,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return GetDocuments500JSONResponse{Code: 500, Message: err.Error()}, nil
|
|
}
|
|
|
|
total := int64(len(rows))
|
|
var nextPage *int64
|
|
var previousPage *int64
|
|
if page*limit < total {
|
|
nextPage = ptrOf(page + 1)
|
|
}
|
|
if page > 1 {
|
|
previousPage = ptrOf(page - 1)
|
|
}
|
|
|
|
apiDocuments := make([]Document, len(rows))
|
|
wordCounts := make([]WordCount, 0, len(rows))
|
|
for i, row := range rows {
|
|
apiDocuments[i] = Document{
|
|
Id: row.ID,
|
|
Title: *row.Title,
|
|
Author: *row.Author,
|
|
Words: row.Words,
|
|
Filepath: row.Filepath,
|
|
Percentage: ptrOf(float32(row.Percentage)),
|
|
TotalTimeSeconds: ptrOf(row.TotalTimeSeconds),
|
|
}
|
|
if row.Words != nil {
|
|
wordCounts = append(wordCounts, WordCount{
|
|
DocumentId: row.ID,
|
|
Count: *row.Words,
|
|
})
|
|
}
|
|
}
|
|
|
|
response := DocumentsResponse{
|
|
Documents: apiDocuments,
|
|
Total: total,
|
|
Page: page,
|
|
Limit: limit,
|
|
NextPage: nextPage,
|
|
PreviousPage: previousPage,
|
|
Search: request.Params.Search,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
WordCounts: wordCounts,
|
|
}
|
|
return GetDocuments200JSONResponse(response), nil
|
|
}
|
|
|
|
// GET /documents/{id}
|
|
func (s *Server) GetDocument(ctx context.Context, request GetDocumentRequestObject) (GetDocumentResponseObject, error) {
|
|
auth, ok := s.getSessionFromContext(ctx)
|
|
if !ok {
|
|
return GetDocument401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
|
}
|
|
|
|
doc, err := s.db.Queries.GetDocument(ctx, request.Id)
|
|
if err != nil {
|
|
return GetDocument404JSONResponse{Code: 404, Message: "Document not found"}, nil
|
|
}
|
|
|
|
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
|
UserID: auth.UserName,
|
|
DocumentID: request.Id,
|
|
})
|
|
var progress *Progress
|
|
if err == nil {
|
|
progress = &Progress{
|
|
UserId: &progressRow.UserID,
|
|
DocumentId: &progressRow.DocumentID,
|
|
DeviceName: &progressRow.DeviceName,
|
|
Percentage: &progressRow.Percentage,
|
|
CreatedAt: ptrOf(parseTime(progressRow.CreatedAt)),
|
|
}
|
|
}
|
|
|
|
apiDoc := Document{
|
|
Id: doc.ID,
|
|
Title: *doc.Title,
|
|
Author: *doc.Author,
|
|
CreatedAt: parseTime(doc.CreatedAt),
|
|
UpdatedAt: parseTime(doc.UpdatedAt),
|
|
Deleted: doc.Deleted,
|
|
Words: doc.Words,
|
|
}
|
|
|
|
response := DocumentResponse{
|
|
Document: apiDoc,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
Progress: progress,
|
|
}
|
|
return GetDocument200JSONResponse(response), nil
|
|
}
|
|
|
|
// deriveBaseFileName builds the base filename for a given MetadataInfo object.
|
|
func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
|
// Derive New FileName
|
|
var newFileName string
|
|
if metadataInfo.Author != nil && *metadataInfo.Author != "" {
|
|
newFileName = newFileName + *metadataInfo.Author
|
|
} else {
|
|
newFileName = newFileName + "Unknown"
|
|
}
|
|
if metadataInfo.Title != nil && *metadataInfo.Title != "" {
|
|
newFileName = newFileName + " - " + *metadataInfo.Title
|
|
} else {
|
|
newFileName = newFileName + " - Unknown"
|
|
}
|
|
|
|
// Remove Slashes
|
|
fileName := strings.ReplaceAll(newFileName, "/", "")
|
|
return "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, *metadataInfo.PartialMD5, metadataInfo.Type))
|
|
}
|
|
|
|
// POST /documents
|
|
func (s *Server) CreateDocument(ctx context.Context, request CreateDocumentRequestObject) (CreateDocumentResponseObject, error) {
|
|
auth, ok := s.getSessionFromContext(ctx)
|
|
if !ok {
|
|
return CreateDocument401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
|
}
|
|
|
|
if request.Body == nil {
|
|
return CreateDocument400JSONResponse{Code: 400, Message: "Missing request body"}, nil
|
|
}
|
|
|
|
// Read multipart form
|
|
form, err := request.Body.ReadForm(32 << 20) // 32MB max memory
|
|
if err != nil {
|
|
log.Error("ReadForm error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Failed to read form"}, nil
|
|
}
|
|
|
|
// Get file from form
|
|
fileField := form.File["document_file"]
|
|
if len(fileField) == 0 {
|
|
return CreateDocument400JSONResponse{Code: 400, Message: "No file provided"}, nil
|
|
}
|
|
|
|
file := fileField[0]
|
|
|
|
// Validate file extension
|
|
if !strings.HasSuffix(strings.ToLower(file.Filename), ".epub") {
|
|
return CreateDocument400JSONResponse{Code: 400, Message: "Only EPUB files are allowed"}, nil
|
|
}
|
|
|
|
// Open file
|
|
f, err := file.Open()
|
|
if err != nil {
|
|
log.Error("Open file error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Failed to open file"}, nil
|
|
}
|
|
defer f.Close()
|
|
|
|
// Read file content
|
|
data, err := io.ReadAll(f)
|
|
if err != nil {
|
|
log.Error("Read file error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Failed to read file"}, nil
|
|
}
|
|
|
|
// Create temp file to get metadata
|
|
tempFile, err := os.CreateTemp("", "book")
|
|
if err != nil {
|
|
log.Error("Temp file create error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Unable to create temp file"}, nil
|
|
}
|
|
defer os.Remove(tempFile.Name())
|
|
defer tempFile.Close()
|
|
|
|
// Write data to temp file
|
|
if _, err := tempFile.Write(data); err != nil {
|
|
log.Error("Write temp file error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Unable to write temp file"}, nil
|
|
}
|
|
|
|
// Get metadata using metadata package
|
|
metadataInfo, err := metadata.GetMetadata(tempFile.Name())
|
|
if err != nil {
|
|
log.Error("GetMetadata error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Unable to acquire metadata"}, nil
|
|
}
|
|
|
|
// Check if already exists
|
|
_, err = s.db.Queries.GetDocument(ctx, *metadataInfo.PartialMD5)
|
|
if err == nil {
|
|
// Document already exists
|
|
existingDoc, _ := s.db.Queries.GetDocument(ctx, *metadataInfo.PartialMD5)
|
|
apiDoc := Document{
|
|
Id: existingDoc.ID,
|
|
Title: *existingDoc.Title,
|
|
Author: *existingDoc.Author,
|
|
CreatedAt: parseTime(existingDoc.CreatedAt),
|
|
UpdatedAt: parseTime(existingDoc.UpdatedAt),
|
|
Deleted: existingDoc.Deleted,
|
|
Words: existingDoc.Words,
|
|
}
|
|
response := DocumentResponse{
|
|
Document: apiDoc,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
}
|
|
return CreateDocument200JSONResponse(response), nil
|
|
}
|
|
|
|
// Derive & sanitize file name
|
|
fileName := deriveBaseFileName(metadataInfo)
|
|
basePath := filepath.Join(s.cfg.DataPath, "documents")
|
|
safePath := filepath.Join(basePath, fileName)
|
|
|
|
// Save file to storage
|
|
err = os.WriteFile(safePath, data, 0644)
|
|
if err != nil {
|
|
log.Error("Save file error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Unable to save file"}, nil
|
|
}
|
|
|
|
// Upsert document
|
|
doc, err := s.db.Queries.UpsertDocument(ctx, database.UpsertDocumentParams{
|
|
ID: *metadataInfo.PartialMD5,
|
|
Title: metadataInfo.Title,
|
|
Author: metadataInfo.Author,
|
|
Description: metadataInfo.Description,
|
|
Md5: metadataInfo.MD5,
|
|
Words: metadataInfo.WordCount,
|
|
Filepath: &fileName,
|
|
Basepath: &basePath,
|
|
})
|
|
if err != nil {
|
|
log.Error("UpsertDocument DB error:", err)
|
|
return CreateDocument500JSONResponse{Code: 500, Message: "Failed to save document"}, nil
|
|
}
|
|
|
|
apiDoc := Document{
|
|
Id: doc.ID,
|
|
Title: *doc.Title,
|
|
Author: *doc.Author,
|
|
CreatedAt: parseTime(doc.CreatedAt),
|
|
UpdatedAt: parseTime(doc.UpdatedAt),
|
|
Deleted: doc.Deleted,
|
|
Words: doc.Words,
|
|
}
|
|
|
|
response := DocumentResponse{
|
|
Document: apiDoc,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
}
|
|
|
|
return CreateDocument200JSONResponse(response), nil
|
|
}
|