294 lines
7.9 KiB
Go
294 lines
7.9 KiB
Go
package v1
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"reichard.io/antholume/database"
|
|
)
|
|
|
|
// DocumentRequest represents a request for a single document
|
|
type DocumentRequest struct {
|
|
ID string
|
|
}
|
|
|
|
// DocumentListRequest represents a request for listing documents
|
|
type DocumentListRequest struct {
|
|
Page int64
|
|
Limit int64
|
|
Search *string
|
|
}
|
|
|
|
// ProgressRequest represents a request for document progress
|
|
type ProgressRequest struct {
|
|
ID string
|
|
}
|
|
|
|
// ActivityRequest represents a request for activity data
|
|
type ActivityRequest struct {
|
|
DocFilter bool
|
|
DocumentID string
|
|
Offset int64
|
|
Limit int64
|
|
}
|
|
|
|
// SettingsRequest represents a request for settings data
|
|
type SettingsRequest struct{}
|
|
|
|
// GetDocument handles GET /api/v1/documents/:id
|
|
func (s *Server) GetDocument(ctx context.Context, req DocumentRequest) (DocumentResponse, error) {
|
|
auth := getAuthFromContext(ctx)
|
|
if auth == nil {
|
|
return DocumentResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
}
|
|
|
|
doc, err := s.db.Queries.GetDocument(ctx, req.ID)
|
|
if err != nil {
|
|
return DocumentResponse{}, &apiError{status: http.StatusNotFound, message: "Document not found"}
|
|
}
|
|
|
|
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
|
UserID: auth.UserName,
|
|
DocumentID: req.ID,
|
|
})
|
|
var progress *Progress
|
|
if err == nil {
|
|
progress = &Progress{
|
|
UserID: progressRow.UserID,
|
|
DocumentID: progressRow.DocumentID,
|
|
DeviceID: progressRow.DeviceID,
|
|
Percentage: progressRow.Percentage,
|
|
Progress: progressRow.Progress,
|
|
CreatedAt: progressRow.CreatedAt,
|
|
}
|
|
}
|
|
|
|
return DocumentResponse{
|
|
Document: doc,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
Progress: progress,
|
|
}, nil
|
|
}
|
|
|
|
// GetDocuments handles GET /api/v1/documents
|
|
func (s *Server) GetDocuments(ctx context.Context, req DocumentListRequest) (DocumentsResponse, error) {
|
|
auth := getAuthFromContext(ctx)
|
|
if auth == nil {
|
|
return DocumentsResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
}
|
|
|
|
rows, err := s.db.Queries.GetDocumentsWithStats(
|
|
ctx,
|
|
database.GetDocumentsWithStatsParams{
|
|
UserID: auth.UserName,
|
|
Query: req.Search,
|
|
Deleted: ptrOf(false),
|
|
Offset: (req.Page - 1) * req.Limit,
|
|
Limit: req.Limit,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return DocumentsResponse{}, &apiError{status: http.StatusInternalServerError, message: err.Error()}
|
|
}
|
|
|
|
total := int64(len(rows))
|
|
var nextPage *int64
|
|
var previousPage *int64
|
|
if req.Page*req.Limit < total {
|
|
nextPage = ptrOf(req.Page + 1)
|
|
}
|
|
if req.Page > 1 {
|
|
previousPage = ptrOf(req.Page - 1)
|
|
}
|
|
|
|
wordCounts := make([]WordCount, 0, len(rows))
|
|
for _, row := range rows {
|
|
if row.Words != nil {
|
|
wordCounts = append(wordCounts, WordCount{
|
|
DocumentID: row.ID,
|
|
Count: *row.Words,
|
|
})
|
|
}
|
|
}
|
|
|
|
return DocumentsResponse{
|
|
Documents: rows,
|
|
Total: total,
|
|
Page: req.Page,
|
|
Limit: req.Limit,
|
|
NextPage: nextPage,
|
|
PreviousPage: previousPage,
|
|
Search: req.Search,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
WordCounts: wordCounts,
|
|
}, nil
|
|
}
|
|
|
|
// GetProgress handles GET /api/v1/progress/:id
|
|
func (s *Server) GetProgress(ctx context.Context, req ProgressRequest) (Progress, error) {
|
|
auth := getAuthFromContext(ctx)
|
|
if auth == nil {
|
|
return Progress{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
}
|
|
|
|
if req.ID == "" {
|
|
return Progress{}, &apiError{status: http.StatusBadRequest, message: "Document ID required"}
|
|
}
|
|
|
|
progressRow, err := s.db.Queries.GetDocumentProgress(ctx, database.GetDocumentProgressParams{
|
|
UserID: auth.UserName,
|
|
DocumentID: req.ID,
|
|
})
|
|
if err != nil {
|
|
return Progress{}, &apiError{status: http.StatusNotFound, message: "Progress not found"}
|
|
}
|
|
|
|
return Progress{
|
|
UserID: progressRow.UserID,
|
|
DocumentID: progressRow.DocumentID,
|
|
DeviceID: progressRow.DeviceID,
|
|
Percentage: progressRow.Percentage,
|
|
Progress: progressRow.Progress,
|
|
CreatedAt: progressRow.CreatedAt,
|
|
}, nil
|
|
}
|
|
|
|
// GetActivity handles GET /api/v1/activity
|
|
func (s *Server) GetActivity(ctx context.Context, req ActivityRequest) (ActivityResponse, error) {
|
|
auth := getAuthFromContext(ctx)
|
|
if auth == nil {
|
|
return ActivityResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
}
|
|
|
|
activities, err := s.db.Queries.GetActivity(ctx, database.GetActivityParams{
|
|
UserID: auth.UserName,
|
|
DocFilter: req.DocFilter,
|
|
DocumentID: req.DocumentID,
|
|
Offset: req.Offset,
|
|
Limit: req.Limit,
|
|
})
|
|
if err != nil {
|
|
return ActivityResponse{}, &apiError{status: http.StatusInternalServerError, message: err.Error()}
|
|
}
|
|
|
|
return ActivityResponse{
|
|
Activities: activities,
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
}, nil
|
|
}
|
|
|
|
// GetSettings handles GET /api/v1/settings
|
|
func (s *Server) GetSettings(ctx context.Context, req SettingsRequest) (SettingsResponse, error) {
|
|
auth := getAuthFromContext(ctx)
|
|
if auth == nil {
|
|
return SettingsResponse{}, &apiError{status: http.StatusUnauthorized, message: "Unauthorized"}
|
|
}
|
|
|
|
user, err := s.db.Queries.GetUser(ctx, auth.UserName)
|
|
if err != nil {
|
|
return SettingsResponse{}, &apiError{status: http.StatusInternalServerError, message: err.Error()}
|
|
}
|
|
|
|
return SettingsResponse{
|
|
Settings: []database.Setting{},
|
|
User: UserData{Username: auth.UserName, IsAdmin: auth.IsAdmin},
|
|
Timezone: user.Timezone,
|
|
}, nil
|
|
}
|
|
|
|
// getAuthFromContext extracts authData from context
|
|
func getAuthFromContext(ctx context.Context) *authData {
|
|
auth, ok := ctx.Value("auth").(authData)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return &auth
|
|
}
|
|
|
|
// apiError represents an API error with status code
|
|
type apiError struct {
|
|
status int
|
|
message string
|
|
}
|
|
|
|
// Error implements error interface
|
|
func (e *apiError) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
// handlerFunc is a generic API handler function
|
|
type handlerFunc[T, R any] func(context.Context, T) (R, error)
|
|
|
|
// requestParser parses an HTTP request into a request struct
|
|
type requestParser[T any] func(*http.Request) T
|
|
|
|
// handle wraps an API handler function with HTTP response writing
|
|
func handle[T, R any](fn handlerFunc[T, R], parser requestParser[T]) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
req := parser(r)
|
|
resp, err := fn(r.Context(), req)
|
|
if err != nil {
|
|
if apiErr, ok := err.(*apiError); ok {
|
|
writeJSONError(w, apiErr.status, apiErr.message)
|
|
} else {
|
|
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
}
|
|
|
|
// parseDocumentRequest extracts document request from HTTP request
|
|
func parseDocumentRequest(r *http.Request) DocumentRequest {
|
|
path := strings.TrimPrefix(r.URL.Path, "/api/v1/documents/")
|
|
id := strings.TrimPrefix(path, "/")
|
|
return DocumentRequest{ID: id}
|
|
}
|
|
|
|
// parseDocumentListRequest extracts document list request from URL query
|
|
func parseDocumentListRequest(r *http.Request) DocumentListRequest {
|
|
query := r.URL.Query()
|
|
page, _ := strconv.ParseInt(query.Get("page"), 10, 64)
|
|
if page == 0 {
|
|
page = 1
|
|
}
|
|
limit, _ := strconv.ParseInt(query.Get("limit"), 10, 64)
|
|
if limit == 0 {
|
|
limit = 9
|
|
}
|
|
search := query.Get("search")
|
|
var searchPtr *string
|
|
if search != "" {
|
|
searchPtr = ptrOf("%" + search + "%")
|
|
}
|
|
return DocumentListRequest{
|
|
Page: page,
|
|
Limit: limit,
|
|
Search: searchPtr,
|
|
}
|
|
}
|
|
|
|
// parseProgressRequest extracts progress request from HTTP request
|
|
func parseProgressRequest(r *http.Request) ProgressRequest {
|
|
path := strings.TrimPrefix(r.URL.Path, "/api/v1/progress/")
|
|
id := strings.TrimPrefix(path, "/")
|
|
return ProgressRequest{ID: id}
|
|
}
|
|
|
|
// parseActivityRequest extracts activity request from HTTP request
|
|
func parseActivityRequest(r *http.Request) ActivityRequest {
|
|
return ActivityRequest{
|
|
DocFilter: false,
|
|
DocumentID: "",
|
|
Offset: 0,
|
|
Limit: 100,
|
|
}
|
|
}
|
|
|
|
// parseSettingsRequest extracts settings request from HTTP request
|
|
func parseSettingsRequest(r *http.Request) SettingsRequest {
|
|
return SettingsRequest{}
|
|
} |