quicknotes/feeds/handler.go

271 lines
7.2 KiB
Go

package feeds
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
// Handler handles HTTP requests for feeds
type Handler struct {
service *Service
}
// NewHandler creates a new feed handler
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
// RegisterRoutes registers the feed routes with the given router group
func (h *Handler) RegisterRoutes(router *gin.RouterGroup) {
feeds := router.Group("/feeds")
{
feeds.GET("", h.handleListFeeds)
feeds.POST("", h.handleAddFeed)
feeds.GET("/:id", h.handleGetFeed)
feeds.DELETE("/:id", h.handleDeleteFeed)
feeds.POST("/:id/refresh", h.handleRefreshFeed)
feeds.POST("/refresh", h.handleRefreshAllFeeds)
feeds.POST("/import/opml", h.handleImportOPML)
// Entry routes
feeds.GET("/entries", h.handleListEntries)
feeds.GET("/entries/:id", h.handleGetEntry)
feeds.POST("/entries/:id/read", h.handleMarkEntryAsRead)
feeds.POST("/entries/read-all", h.handleMarkAllAsRead)
feeds.POST("/entries/:id/full-content", h.handleFetchFullContent)
}
// Test endpoint
router.POST("/test/feeds/reset", h.handleReset)
}
// handleListFeeds handles GET /api/feeds
func (h *Handler) handleListFeeds(c *gin.Context) {
feeds, err := h.service.GetFeeds()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, feeds)
}
// handleAddFeed handles POST /api/feeds
func (h *Handler) handleAddFeed(c *gin.Context) {
var req struct {
URL string `json:"url" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
feed, err := h.service.AddFeed(req.URL)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, feed)
}
// handleGetFeed handles GET /api/feeds/:id
func (h *Handler) handleGetFeed(c *gin.Context) {
id := c.Param("id")
feed, err := h.service.GetFeed(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Feed not found"})
return
}
c.JSON(http.StatusOK, feed)
}
// handleDeleteFeed handles DELETE /api/feeds/:id
func (h *Handler) handleDeleteFeed(c *gin.Context) {
id := c.Param("id")
if err := h.service.DeleteFeed(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusOK)
}
// handleRefreshFeed handles POST /api/feeds/:id/refresh
func (h *Handler) handleRefreshFeed(c *gin.Context) {
id := c.Param("id")
if err := h.service.RefreshFeed(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusOK)
}
// handleRefreshAllFeeds handles POST /api/feeds/refresh
func (h *Handler) handleRefreshAllFeeds(c *gin.Context) {
if err := h.service.RefreshAllFeeds(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusOK)
}
// handleImportOPML handles POST /api/feeds/import
func (h *Handler) handleImportOPML(c *gin.Context) {
// Set max file size to 10MB
const maxFileSize = 10 << 20
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxFileSize)
// Parse the multipart form
if err := c.Request.ParseMultipartForm(maxFileSize); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("File too large or invalid form: %v", err)})
return
}
file, header, err := c.Request.FormFile("file")
if err != nil {
// Check if URL is provided instead
url := c.PostForm("url")
if url != "" {
resp, err := http.Get(url)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Failed to download OPML: %v", err)})
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
fmt.Printf("Error closing response body: %v\n", err)
}
}()
feeds, err := h.service.ImportOPML(resp.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"imported": len(feeds)})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": "No file or URL provided"})
return
}
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("Error closing file: %v\n", err)
}
}()
// Check file size
if header.Size > maxFileSize {
c.JSON(http.StatusBadRequest, gin.H{"error": "File too large (max 10MB)"})
return
}
// Check file extension
if !strings.HasSuffix(strings.ToLower(header.Filename), ".opml") &&
!strings.HasSuffix(strings.ToLower(header.Filename), ".xml") {
c.JSON(http.StatusBadRequest, gin.H{"error": "File must be an OPML or XML file"})
return
}
feeds, err := h.service.ImportOPML(file)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"imported": len(feeds)})
}
// handleListEntries handles GET /api/feeds/entries
func (h *Handler) handleListEntries(c *gin.Context) {
feedID := c.Query("feedId")
unreadOnly, _ := strconv.ParseBool(c.Query("unreadOnly"))
entries, err := h.service.GetEntries(feedID, unreadOnly)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, entries)
}
// handleGetEntry handles GET /api/feeds/entries/:id
func (h *Handler) handleGetEntry(c *gin.Context) {
id := c.Param("id")
entry, err := h.service.GetEntry(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Entry not found"})
return
}
c.JSON(http.StatusOK, entry)
}
// handleMarkEntryAsRead handles POST /api/feeds/entries/:id/read
func (h *Handler) handleMarkEntryAsRead(c *gin.Context) {
id := c.Param("id")
if err := h.service.MarkEntryAsRead(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusOK)
}
// handleFetchFullContent handles POST /api/feeds/entries/:id/full-content
func (h *Handler) handleFetchFullContent(c *gin.Context) {
id := c.Param("id")
if err := h.service.FetchFullContent(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Get the updated entry
entry, err := h.service.GetEntry(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, entry)
}
// handleReset handles POST /api/test/feeds/reset
func (h *Handler) handleReset(c *gin.Context) {
// Delete all entries
if err := h.service.db.Exec("DELETE FROM entries").Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Delete all feeds
if err := h.service.db.Exec("DELETE FROM feeds").Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusOK)
}
func (h *Handler) handleMarkAllAsRead(c *gin.Context) {
feedID := c.Query("feedId")
var err error
if feedID != "" {
// Mark all entries as read for a specific feed
err = h.service.MarkAllEntriesAsRead(feedID)
} else {
// Mark all entries as read across all feeds
err = h.service.MarkAllEntriesAsRead("")
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"success": true})
}