diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-07-11 00:43:57 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-07-11 00:43:57 +0900 |
| commit | 5f69e3e972831ca471bc971a034f0a3f5f22b5c5 (patch) | |
| tree | 432cff44241566a4efac03846d45b5051d1b6c70 /backend/main.go | |
| parent | 5048060b6f002e2ea8bdec564a708dd21b7d665f (diff) | |
| download | feedaka-5f69e3e972831ca471bc971a034f0a3f5f22b5c5.tar.gz feedaka-5f69e3e972831ca471bc971a034f0a3f5f22b5c5.tar.zst feedaka-5f69e3e972831ca471bc971a034f0a3f5f22b5c5.zip | |
feat(backend): remove REST API endpoints
Diffstat (limited to 'backend/main.go')
| -rw-r--r-- | backend/main.go | 310 |
1 files changed, 1 insertions, 309 deletions
diff --git a/backend/main.go b/backend/main.go index d01a0ed..90f95e3 100644 --- a/backend/main.go +++ b/backend/main.go @@ -5,14 +5,10 @@ import ( "database/sql" "embed" "fmt" - "html/template" - "io" "log" "net/http" "os" "os/signal" - "strconv" - "strings" "syscall" "time" @@ -26,28 +22,16 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/mmcdole/gofeed" "github.com/vektah/gqlparser/v2/ast" - "golang.org/x/exp/slices" "undef.ninja/x/feedaka/graphql" ) var ( - basePath string - db *sql.DB - //go:embed templates/* - tmplFS embed.FS + db *sql.DB //go:embed static/* staticFS embed.FS ) -type Template struct { - templates *template.Template -} - -func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { - return t.templates.ExecuteTemplate(w, name, data) -} - func initDB(db *sql.DB) error { _, err := db.Exec(` CREATE TABLE IF NOT EXISTS feeds ( @@ -69,102 +53,6 @@ CREATE TABLE IF NOT EXISTS articles ( return err } -func getIndex(c echo.Context) error { - // Redirect to /feeds/unread - return c.Redirect(http.StatusFound, basePath+"/feeds/unread") -} - -func getSettings(c echo.Context) error { - feedURLs := []string{} - rows, err := db.Query(`SELECT url FROM feeds`) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - defer rows.Close() - for rows.Next() { - var url string - err := rows.Scan(&url) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - feedURLs = append(feedURLs, url) - } - // Sort feedURLs in ascending order. - slices.Sort(feedURLs) - - return c.Render(http.StatusOK, "settings.html", struct { - BasePath string - URLs string - }{ - BasePath: basePath, - URLs: strings.Join(feedURLs, "\r\n"), - }) -} - -func postSettings(c echo.Context) error { - // Get "urls" from form parameters. - rawUrls := strings.Split(c.FormValue("urls"), "\r\n") - urls := make([]string, 0, len(rawUrls)) - for _, rawUrl := range rawUrls { - url := strings.TrimSpace(rawUrl) - if url != "" { - urls = append(urls, url) - } - } - existingURLs := make(map[string]bool) - rows, err := db.Query(`SELECT url FROM feeds`) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - defer rows.Close() - for rows.Next() { - var url string - err := rows.Scan(&url) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - existingURLs[url] = true - } - for _, url := range urls { - if existingURLs[url] { - continue - } - _, err := db.Exec( - `INSERT INTO feeds (url, title, fetched_at) VALUES (?, ?, ?)`, - url, - "", - time.Now().AddDate(0, 0, -1).UTC().Format(time.RFC3339), - ) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - } - // Remove: - for existingURL := range existingURLs { - // If existingURL is not in urls, it will be removed. - found := false - for _, url := range urls { - if existingURL == url { - found = true - break - } - } - if found { - continue - } - // Remove feed and articles. - _, err = db.Exec(`DELETE FROM articles WHERE feed_id = (SELECT id FROM feeds WHERE url = ?)`, existingURL) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - _, err := db.Exec(`DELETE FROM feeds WHERE url = ?`, existingURL) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - } - return c.Redirect(http.StatusSeeOther, basePath+"/settings") -} - func fetchOneFeed(feedID int, url string, ctx context.Context) error { log.Printf("Fetching %s...\n", url) fp := gofeed.NewParser() @@ -272,184 +160,6 @@ func fetchAllFeeds(ctx context.Context) error { return result.ErrorOrNil() } -func getUnreadFeeds(c echo.Context) error { - rows, err := db.Query(` -SELECT a.id, a.title, f.url, f.title, f.id -FROM articles AS a -INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE is_read = 0 -ORDER BY a.id DESC -LIMIT 100 -`) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - defer rows.Close() - - return renderFeeds(c, "Unread feeds", "unread-feeds.html", rows) -} - -func getReadFeeds(c echo.Context) error { - rows, err := db.Query(` -SELECT a.id, a.title, f.url, f.title, f.id -FROM articles AS a -INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE is_read = 1 -ORDER BY a.id DESC -LIMIT 100 -`) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - defer rows.Close() - - return renderFeeds(c, "Read feeds", "read-feeds.html", rows) -} - -func renderFeeds(c echo.Context, title string, templateName string, rows *sql.Rows) error { - type Article struct { - ID int - Title string - URL string - } - type Feed struct { - ID int - URL string - Title string - Articles []Article - } - feeds := make(map[int]*Feed) - for rows.Next() { - var articleID int - var articleTitle string - var feedURL string - var feedTitle string - var feedID int - err := rows.Scan(&articleID, &articleTitle, &feedURL, &feedTitle, &feedID) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - if _, ok := feeds[feedID]; !ok { - feeds[feedID] = &Feed{ - ID: feedID, - URL: feedURL, - Title: feedTitle, - } - } - feed := feeds[feedID] - feed.Articles = append(feed.Articles, Article{ - ID: articleID, - Title: articleTitle, - URL: fmt.Sprintf("%s/articles/%d", basePath, articleID), - }) - } - - sortedFeeds := make([]*Feed, 0, len(feeds)) - for _, feed := range feeds { - sortedFeeds = append(sortedFeeds, feed) - } - slices.SortFunc(sortedFeeds, func(a, b *Feed) int { - // Ascending order by URL. - if a.URL < b.URL { - return -1 - } else if a.URL > b.URL { - return 1 - } else { - return 0 - } - }) - - return c.Render(http.StatusOK, templateName, struct { - BasePath string - Title string - Feeds []*Feed - }{ - BasePath: basePath, - Title: title, - Feeds: sortedFeeds, - }) -} - -func getArticle(c echo.Context) error { - rawArticleID := c.Param("articleID") - articleID, err := strconv.Atoi(rawArticleID) - if err != nil { - return c.String(http.StatusNotFound, err.Error()) - } - row := db.QueryRow(`SELECT url FROM articles WHERE id = ?`, articleID) - if row == nil { - return c.String(http.StatusNotFound, "Not found") - } - var url string - err = row.Scan(&url) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - // Turn is_read on. - _, err = db.Exec(`UPDATE articles SET is_read = 1 WHERE id = ?`, articleID) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - // Redirect to the article URL. - return c.Redirect(http.StatusFound, url) -} - -func apiPutFeedRead(c echo.Context) error { - return apiPutFeed(c, "read") -} - -func apiPutFeedUnread(c echo.Context) error { - return apiPutFeed(c, "unread") -} - -func apiPutFeed(c echo.Context, op string) error { - rawFeedID := c.Param("feedID") - feedID, err := strconv.Atoi(rawFeedID) - if err != nil { - return c.String(http.StatusNotFound, err.Error()) - } - // Turn is_read on or off. - var isReadValue int - if op == "read" { - isReadValue = 1 - } else if op == "unread" { - isReadValue = 0 - } - _, err = db.Exec(`UPDATE articles SET is_read = ? WHERE feed_id = ?`, isReadValue, feedID) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - return c.NoContent(http.StatusOK) -} - -func apiPutArticleRead(c echo.Context) error { - return apiPutArticle(c, "read") -} - -func apiPutArticleUnread(c echo.Context) error { - return apiPutArticle(c, "unread") -} - -func apiPutArticle(c echo.Context, op string) error { - rawArticleID := c.Param("articleID") - articleID, err := strconv.Atoi(rawArticleID) - if err != nil { - return c.String(http.StatusNotFound, err.Error()) - } - // Turn is_read on or off. - var isReadValue int - if op == "read" { - isReadValue = 1 - } else if op == "unread" { - isReadValue = 0 - } - _, err = db.Exec(`UPDATE articles SET is_read = ? WHERE id = ?`, isReadValue, articleID) - if err != nil { - return c.String(http.StatusInternalServerError, err.Error()) - } - return c.NoContent(http.StatusOK) -} - func scheduled(ctx context.Context, d time.Duration, fn func()) { ticker := time.NewTicker(d) go func() { @@ -465,7 +175,6 @@ func scheduled(ctx context.Context, d time.Duration, fn func()) { } func main() { - basePath = os.Getenv("FEEDAKA_BASE_PATH") port := os.Getenv("FEEDAKA_PORT") var err error @@ -480,29 +189,12 @@ func main() { log.Fatal(err) } - t := &Template{ - templates: template.Must(template.ParseFS(tmplFS, "templates/*.html")), - } - e := echo.New() - e.Renderer = t e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.Use(middleware.CORS()) - e.GET("/", getIndex) - e.GET("/settings", getSettings) - e.POST("/settings", postSettings) - e.GET("/feeds/unread", getUnreadFeeds) - e.GET("/feeds/read", getReadFeeds) - e.GET("/articles/:articleID", getArticle) - - e.PUT("/api/feeds/:feedID/read", apiPutFeedRead) - e.PUT("/api/feeds/:feedID/unread", apiPutFeedUnread) - e.PUT("/api/articles/:articleID/read", apiPutArticleRead) - e.PUT("/api/articles/:articleID/unread", apiPutArticleUnread) - e.GET("/static/*", echo.WrapHandler(http.FileServer(http.FS(staticFS)))) // Setup GraphQL server |
