aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/api
diff options
context:
space:
mode:
Diffstat (limited to 'backend/api')
-rw-r--r--backend/api/converters.go79
-rw-r--r--backend/api/echo_context.go21
-rw-r--r--backend/api/generated.go1162
-rw-r--r--backend/api/handler.go16
-rw-r--r--backend/api/handler_articles.go245
-rw-r--r--backend/api/handler_auth.go73
-rw-r--r--backend/api/handler_feeds.go185
-rw-r--r--backend/api/oapi-codegen.yaml6
8 files changed, 1787 insertions, 0 deletions
diff --git a/backend/api/converters.go b/backend/api/converters.go
new file mode 100644
index 0000000..0ccbdc0
--- /dev/null
+++ b/backend/api/converters.go
@@ -0,0 +1,79 @@
+package api
+
+import (
+ "strconv"
+
+ "undef.ninja/x/feedaka/db"
+)
+
+func dbFeedToAPI(f db.Feed, unreadCount int64) Feed {
+ return Feed{
+ Id: strconv.FormatInt(f.ID, 10),
+ Url: f.Url,
+ Title: f.Title,
+ FetchedAt: f.FetchedAt,
+ IsSubscribed: f.IsSubscribed == 1,
+ UnreadCount: int32(unreadCount),
+ }
+}
+
+type articleRow struct {
+ ID int64
+ FeedID int64
+ Guid string
+ Title string
+ Url string
+ IsRead int64
+ FeedID2 int64
+ FeedUrl string
+ FeedTitle string
+ FeedIsSubscribed int64
+}
+
+func toArticleRow(r any) articleRow {
+ switch v := r.(type) {
+ case db.GetArticlesPaginatedRow:
+ return articleRow{v.ID, v.FeedID, v.Guid, v.Title, v.Url, v.IsRead, v.FeedID2, v.FeedUrl, v.FeedTitle, v.FeedIsSubscribed}
+ case db.GetArticlesPaginatedAfterRow:
+ return articleRow{v.ID, v.FeedID, v.Guid, v.Title, v.Url, v.IsRead, v.FeedID2, v.FeedUrl, v.FeedTitle, v.FeedIsSubscribed}
+ case db.GetArticlesByFeedPaginatedRow:
+ return articleRow{v.ID, v.FeedID, v.Guid, v.Title, v.Url, v.IsRead, v.FeedID2, v.FeedUrl, v.FeedTitle, v.FeedIsSubscribed}
+ case db.GetArticlesByFeedPaginatedAfterRow:
+ return articleRow{v.ID, v.FeedID, v.Guid, v.Title, v.Url, v.IsRead, v.FeedID2, v.FeedUrl, v.FeedTitle, v.FeedIsSubscribed}
+ default:
+ panic("unexpected row type")
+ }
+}
+
+func rowToArticle(row articleRow) Article {
+ return Article{
+ Id: strconv.FormatInt(row.ID, 10),
+ FeedId: strconv.FormatInt(row.FeedID, 10),
+ Guid: row.Guid,
+ Title: row.Title,
+ Url: row.Url,
+ IsRead: row.IsRead == 1,
+ Feed: ArticleFeed{
+ Id: strconv.FormatInt(row.FeedID2, 10),
+ Url: row.FeedUrl,
+ Title: row.FeedTitle,
+ IsSubscribed: row.FeedIsSubscribed == 1,
+ },
+ }
+}
+
+func dbArticleToAPI(row db.GetArticleRow) Article {
+ return Article{
+ Id: strconv.FormatInt(row.ID, 10),
+ FeedId: strconv.FormatInt(row.FeedID, 10),
+ Guid: row.Guid,
+ Title: row.Title,
+ Url: row.Url,
+ IsRead: row.IsRead == 1,
+ Feed: ArticleFeed{
+ Id: strconv.FormatInt(row.FeedID2, 10),
+ Url: row.FeedUrl,
+ Title: row.FeedTitle,
+ },
+ }
+}
diff --git a/backend/api/echo_context.go b/backend/api/echo_context.go
new file mode 100644
index 0000000..b775850
--- /dev/null
+++ b/backend/api/echo_context.go
@@ -0,0 +1,21 @@
+package api
+
+import (
+ "context"
+ "errors"
+
+ "github.com/labstack/echo/v4"
+)
+
+type echoContextKey struct{}
+
+var errNoEchoContext = errors.New("echo context not found")
+
+func getEchoContext(ctx context.Context) echo.Context {
+ echoCtx, _ := ctx.Value(echoContextKey{}).(echo.Context)
+ return echoCtx
+}
+
+func WithEchoContext(ctx context.Context, echoCtx echo.Context) context.Context {
+ return context.WithValue(ctx, echoContextKey{}, echoCtx)
+}
diff --git a/backend/api/generated.go b/backend/api/generated.go
new file mode 100644
index 0000000..4288f40
--- /dev/null
+++ b/backend/api/generated.go
@@ -0,0 +1,1162 @@
+// Package api provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ "github.com/oapi-codegen/runtime"
+ strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo"
+)
+
+// AddFeedRequest defines model for AddFeedRequest.
+type AddFeedRequest struct {
+ Url string `json:"url"`
+}
+
+// Article defines model for Article.
+type Article struct {
+ Feed ArticleFeed `json:"feed"`
+ FeedId string `json:"feedId"`
+ Guid string `json:"guid"`
+ Id string `json:"id"`
+ IsRead bool `json:"isRead"`
+ Title string `json:"title"`
+ Url string `json:"url"`
+}
+
+// ArticleConnection defines model for ArticleConnection.
+type ArticleConnection struct {
+ Articles []Article `json:"articles"`
+ PageInfo PageInfo `json:"pageInfo"`
+}
+
+// ArticleFeed defines model for ArticleFeed.
+type ArticleFeed struct {
+ Id string `json:"id"`
+ IsSubscribed bool `json:"isSubscribed"`
+ Title string `json:"title"`
+ Url string `json:"url"`
+}
+
+// ErrorResponse defines model for ErrorResponse.
+type ErrorResponse struct {
+ Message string `json:"message"`
+}
+
+// Feed defines model for Feed.
+type Feed struct {
+ FetchedAt string `json:"fetchedAt"`
+ Id string `json:"id"`
+ IsSubscribed bool `json:"isSubscribed"`
+ Title string `json:"title"`
+ UnreadCount int32 `json:"unreadCount"`
+ Url string `json:"url"`
+}
+
+// LoginRequest defines model for LoginRequest.
+type LoginRequest struct {
+ Password string `json:"password"`
+ Username string `json:"username"`
+}
+
+// LoginResponse defines model for LoginResponse.
+type LoginResponse struct {
+ User User `json:"user"`
+}
+
+// PageInfo defines model for PageInfo.
+type PageInfo struct {
+ EndCursor *string `json:"endCursor,omitempty"`
+ HasNextPage bool `json:"hasNextPage"`
+}
+
+// User defines model for User.
+type User struct {
+ Id string `json:"id"`
+ Username string `json:"username"`
+}
+
+// ArticlesListReadArticlesParams defines parameters for ArticlesListReadArticles.
+type ArticlesListReadArticlesParams struct {
+ FeedId *string `form:"feedId,omitempty" json:"feedId,omitempty"`
+ After *string `form:"after,omitempty" json:"after,omitempty"`
+ First *int32 `form:"first,omitempty" json:"first,omitempty"`
+}
+
+// ArticlesListUnreadArticlesParams defines parameters for ArticlesListUnreadArticles.
+type ArticlesListUnreadArticlesParams struct {
+ FeedId *string `form:"feedId,omitempty" json:"feedId,omitempty"`
+ After *string `form:"after,omitempty" json:"after,omitempty"`
+ First *int32 `form:"first,omitempty" json:"first,omitempty"`
+}
+
+// AuthLoginJSONRequestBody defines body for AuthLogin for application/json ContentType.
+type AuthLoginJSONRequestBody = LoginRequest
+
+// FeedsAddFeedJSONRequestBody defines body for FeedsAddFeed for application/json ContentType.
+type FeedsAddFeedJSONRequestBody = AddFeedRequest
+
+// ServerInterface represents all server handlers.
+type ServerInterface interface {
+
+ // (GET /api/articles/read)
+ ArticlesListReadArticles(ctx echo.Context, params ArticlesListReadArticlesParams) error
+
+ // (GET /api/articles/unread)
+ ArticlesListUnreadArticles(ctx echo.Context, params ArticlesListUnreadArticlesParams) error
+
+ // (GET /api/articles/{articleId})
+ ArticlesGetArticle(ctx echo.Context, articleId string) error
+
+ // (POST /api/articles/{articleId}/read)
+ ArticlesMarkArticleRead(ctx echo.Context, articleId string) error
+
+ // (POST /api/articles/{articleId}/unread)
+ ArticlesMarkArticleUnread(ctx echo.Context, articleId string) error
+
+ // (POST /api/auth/login)
+ AuthLogin(ctx echo.Context) error
+
+ // (POST /api/auth/logout)
+ AuthLogout(ctx echo.Context) error
+
+ // (GET /api/auth/me)
+ AuthGetCurrentUser(ctx echo.Context) error
+
+ // (GET /api/feeds)
+ FeedsListFeeds(ctx echo.Context) error
+
+ // (POST /api/feeds)
+ FeedsAddFeed(ctx echo.Context) error
+
+ // (DELETE /api/feeds/{feedId})
+ FeedsUnsubscribeFeed(ctx echo.Context, feedId string) error
+
+ // (GET /api/feeds/{feedId})
+ FeedsGetFeed(ctx echo.Context, feedId string) error
+
+ // (POST /api/feeds/{feedId}/read)
+ FeedsMarkFeedRead(ctx echo.Context, feedId string) error
+
+ // (POST /api/feeds/{feedId}/unread)
+ FeedsMarkFeedUnread(ctx echo.Context, feedId string) error
+}
+
+// ServerInterfaceWrapper converts echo contexts to parameters.
+type ServerInterfaceWrapper struct {
+ Handler ServerInterface
+}
+
+// ArticlesListReadArticles converts echo context to params.
+func (w *ServerInterfaceWrapper) ArticlesListReadArticles(ctx echo.Context) error {
+ var err error
+
+ // Parameter object where we will unmarshal all parameters from the context
+ var params ArticlesListReadArticlesParams
+ // ------------- Optional query parameter "feedId" -------------
+
+ err = runtime.BindQueryParameter("form", false, false, "feedId", ctx.QueryParams(), &params.FeedId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter feedId: %s", err))
+ }
+
+ // ------------- Optional query parameter "after" -------------
+
+ err = runtime.BindQueryParameter("form", false, false, "after", ctx.QueryParams(), &params.After)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter after: %s", err))
+ }
+
+ // ------------- Optional query parameter "first" -------------
+
+ err = runtime.BindQueryParameter("form", false, false, "first", ctx.QueryParams(), &params.First)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter first: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.ArticlesListReadArticles(ctx, params)
+ return err
+}
+
+// ArticlesListUnreadArticles converts echo context to params.
+func (w *ServerInterfaceWrapper) ArticlesListUnreadArticles(ctx echo.Context) error {
+ var err error
+
+ // Parameter object where we will unmarshal all parameters from the context
+ var params ArticlesListUnreadArticlesParams
+ // ------------- Optional query parameter "feedId" -------------
+
+ err = runtime.BindQueryParameter("form", false, false, "feedId", ctx.QueryParams(), &params.FeedId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter feedId: %s", err))
+ }
+
+ // ------------- Optional query parameter "after" -------------
+
+ err = runtime.BindQueryParameter("form", false, false, "after", ctx.QueryParams(), &params.After)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter after: %s", err))
+ }
+
+ // ------------- Optional query parameter "first" -------------
+
+ err = runtime.BindQueryParameter("form", false, false, "first", ctx.QueryParams(), &params.First)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter first: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.ArticlesListUnreadArticles(ctx, params)
+ return err
+}
+
+// ArticlesGetArticle converts echo context to params.
+func (w *ServerInterfaceWrapper) ArticlesGetArticle(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "articleId" -------------
+ var articleId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "articleId", ctx.Param("articleId"), &articleId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter articleId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.ArticlesGetArticle(ctx, articleId)
+ return err
+}
+
+// ArticlesMarkArticleRead converts echo context to params.
+func (w *ServerInterfaceWrapper) ArticlesMarkArticleRead(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "articleId" -------------
+ var articleId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "articleId", ctx.Param("articleId"), &articleId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter articleId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.ArticlesMarkArticleRead(ctx, articleId)
+ return err
+}
+
+// ArticlesMarkArticleUnread converts echo context to params.
+func (w *ServerInterfaceWrapper) ArticlesMarkArticleUnread(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "articleId" -------------
+ var articleId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "articleId", ctx.Param("articleId"), &articleId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter articleId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.ArticlesMarkArticleUnread(ctx, articleId)
+ return err
+}
+
+// AuthLogin converts echo context to params.
+func (w *ServerInterfaceWrapper) AuthLogin(ctx echo.Context) error {
+ var err error
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.AuthLogin(ctx)
+ return err
+}
+
+// AuthLogout converts echo context to params.
+func (w *ServerInterfaceWrapper) AuthLogout(ctx echo.Context) error {
+ var err error
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.AuthLogout(ctx)
+ return err
+}
+
+// AuthGetCurrentUser converts echo context to params.
+func (w *ServerInterfaceWrapper) AuthGetCurrentUser(ctx echo.Context) error {
+ var err error
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.AuthGetCurrentUser(ctx)
+ return err
+}
+
+// FeedsListFeeds converts echo context to params.
+func (w *ServerInterfaceWrapper) FeedsListFeeds(ctx echo.Context) error {
+ var err error
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.FeedsListFeeds(ctx)
+ return err
+}
+
+// FeedsAddFeed converts echo context to params.
+func (w *ServerInterfaceWrapper) FeedsAddFeed(ctx echo.Context) error {
+ var err error
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.FeedsAddFeed(ctx)
+ return err
+}
+
+// FeedsUnsubscribeFeed converts echo context to params.
+func (w *ServerInterfaceWrapper) FeedsUnsubscribeFeed(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "feedId" -------------
+ var feedId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "feedId", ctx.Param("feedId"), &feedId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter feedId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.FeedsUnsubscribeFeed(ctx, feedId)
+ return err
+}
+
+// FeedsGetFeed converts echo context to params.
+func (w *ServerInterfaceWrapper) FeedsGetFeed(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "feedId" -------------
+ var feedId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "feedId", ctx.Param("feedId"), &feedId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter feedId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.FeedsGetFeed(ctx, feedId)
+ return err
+}
+
+// FeedsMarkFeedRead converts echo context to params.
+func (w *ServerInterfaceWrapper) FeedsMarkFeedRead(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "feedId" -------------
+ var feedId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "feedId", ctx.Param("feedId"), &feedId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter feedId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.FeedsMarkFeedRead(ctx, feedId)
+ return err
+}
+
+// FeedsMarkFeedUnread converts echo context to params.
+func (w *ServerInterfaceWrapper) FeedsMarkFeedUnread(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "feedId" -------------
+ var feedId string
+
+ err = runtime.BindStyledParameterWithOptions("simple", "feedId", ctx.Param("feedId"), &feedId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter feedId: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshaled arguments
+ err = w.Handler.FeedsMarkFeedUnread(ctx, feedId)
+ return err
+}
+
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
+ CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+ TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
+ RegisterHandlersWithBaseURL(router, si, "")
+}
+
+// Registers handlers, and prepends BaseURL to the paths, so that the paths
+// can be served under a prefix.
+func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
+
+ wrapper := ServerInterfaceWrapper{
+ Handler: si,
+ }
+
+ router.GET(baseURL+"/api/articles/read", wrapper.ArticlesListReadArticles)
+ router.GET(baseURL+"/api/articles/unread", wrapper.ArticlesListUnreadArticles)
+ router.GET(baseURL+"/api/articles/:articleId", wrapper.ArticlesGetArticle)
+ router.POST(baseURL+"/api/articles/:articleId/read", wrapper.ArticlesMarkArticleRead)
+ router.POST(baseURL+"/api/articles/:articleId/unread", wrapper.ArticlesMarkArticleUnread)
+ router.POST(baseURL+"/api/auth/login", wrapper.AuthLogin)
+ router.POST(baseURL+"/api/auth/logout", wrapper.AuthLogout)
+ router.GET(baseURL+"/api/auth/me", wrapper.AuthGetCurrentUser)
+ router.GET(baseURL+"/api/feeds", wrapper.FeedsListFeeds)
+ router.POST(baseURL+"/api/feeds", wrapper.FeedsAddFeed)
+ router.DELETE(baseURL+"/api/feeds/:feedId", wrapper.FeedsUnsubscribeFeed)
+ router.GET(baseURL+"/api/feeds/:feedId", wrapper.FeedsGetFeed)
+ router.POST(baseURL+"/api/feeds/:feedId/read", wrapper.FeedsMarkFeedRead)
+ router.POST(baseURL+"/api/feeds/:feedId/unread", wrapper.FeedsMarkFeedUnread)
+
+}
+
+type ArticlesListReadArticlesRequestObject struct {
+ Params ArticlesListReadArticlesParams
+}
+
+type ArticlesListReadArticlesResponseObject interface {
+ VisitArticlesListReadArticlesResponse(w http.ResponseWriter) error
+}
+
+type ArticlesListReadArticles200JSONResponse ArticleConnection
+
+func (response ArticlesListReadArticles200JSONResponse) VisitArticlesListReadArticlesResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesListUnreadArticlesRequestObject struct {
+ Params ArticlesListUnreadArticlesParams
+}
+
+type ArticlesListUnreadArticlesResponseObject interface {
+ VisitArticlesListUnreadArticlesResponse(w http.ResponseWriter) error
+}
+
+type ArticlesListUnreadArticles200JSONResponse ArticleConnection
+
+func (response ArticlesListUnreadArticles200JSONResponse) VisitArticlesListUnreadArticlesResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesGetArticleRequestObject struct {
+ ArticleId string `json:"articleId"`
+}
+
+type ArticlesGetArticleResponseObject interface {
+ VisitArticlesGetArticleResponse(w http.ResponseWriter) error
+}
+
+type ArticlesGetArticle200JSONResponse Article
+
+func (response ArticlesGetArticle200JSONResponse) VisitArticlesGetArticleResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesGetArticle404JSONResponse ErrorResponse
+
+func (response ArticlesGetArticle404JSONResponse) VisitArticlesGetArticleResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesMarkArticleReadRequestObject struct {
+ ArticleId string `json:"articleId"`
+}
+
+type ArticlesMarkArticleReadResponseObject interface {
+ VisitArticlesMarkArticleReadResponse(w http.ResponseWriter) error
+}
+
+type ArticlesMarkArticleRead200JSONResponse Article
+
+func (response ArticlesMarkArticleRead200JSONResponse) VisitArticlesMarkArticleReadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesMarkArticleRead404JSONResponse ErrorResponse
+
+func (response ArticlesMarkArticleRead404JSONResponse) VisitArticlesMarkArticleReadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesMarkArticleUnreadRequestObject struct {
+ ArticleId string `json:"articleId"`
+}
+
+type ArticlesMarkArticleUnreadResponseObject interface {
+ VisitArticlesMarkArticleUnreadResponse(w http.ResponseWriter) error
+}
+
+type ArticlesMarkArticleUnread200JSONResponse Article
+
+func (response ArticlesMarkArticleUnread200JSONResponse) VisitArticlesMarkArticleUnreadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type ArticlesMarkArticleUnread404JSONResponse ErrorResponse
+
+func (response ArticlesMarkArticleUnread404JSONResponse) VisitArticlesMarkArticleUnreadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type AuthLoginRequestObject struct {
+ Body *AuthLoginJSONRequestBody
+}
+
+type AuthLoginResponseObject interface {
+ VisitAuthLoginResponse(w http.ResponseWriter) error
+}
+
+type AuthLogin200JSONResponse LoginResponse
+
+func (response AuthLogin200JSONResponse) VisitAuthLoginResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type AuthLogin401JSONResponse ErrorResponse
+
+func (response AuthLogin401JSONResponse) VisitAuthLoginResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(401)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type AuthLogoutRequestObject struct {
+}
+
+type AuthLogoutResponseObject interface {
+ VisitAuthLogoutResponse(w http.ResponseWriter) error
+}
+
+type AuthLogout204Response struct {
+}
+
+func (response AuthLogout204Response) VisitAuthLogoutResponse(w http.ResponseWriter) error {
+ w.WriteHeader(204)
+ return nil
+}
+
+type AuthLogout401JSONResponse ErrorResponse
+
+func (response AuthLogout401JSONResponse) VisitAuthLogoutResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(401)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type AuthGetCurrentUserRequestObject struct {
+}
+
+type AuthGetCurrentUserResponseObject interface {
+ VisitAuthGetCurrentUserResponse(w http.ResponseWriter) error
+}
+
+type AuthGetCurrentUser200JSONResponse User
+
+func (response AuthGetCurrentUser200JSONResponse) VisitAuthGetCurrentUserResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type AuthGetCurrentUser401JSONResponse ErrorResponse
+
+func (response AuthGetCurrentUser401JSONResponse) VisitAuthGetCurrentUserResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(401)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsListFeedsRequestObject struct {
+}
+
+type FeedsListFeedsResponseObject interface {
+ VisitFeedsListFeedsResponse(w http.ResponseWriter) error
+}
+
+type FeedsListFeeds200JSONResponse []Feed
+
+func (response FeedsListFeeds200JSONResponse) VisitFeedsListFeedsResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsAddFeedRequestObject struct {
+ Body *FeedsAddFeedJSONRequestBody
+}
+
+type FeedsAddFeedResponseObject interface {
+ VisitFeedsAddFeedResponse(w http.ResponseWriter) error
+}
+
+type FeedsAddFeed201JSONResponse Feed
+
+func (response FeedsAddFeed201JSONResponse) VisitFeedsAddFeedResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(201)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsAddFeed400JSONResponse ErrorResponse
+
+func (response FeedsAddFeed400JSONResponse) VisitFeedsAddFeedResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(400)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsUnsubscribeFeedRequestObject struct {
+ FeedId string `json:"feedId"`
+}
+
+type FeedsUnsubscribeFeedResponseObject interface {
+ VisitFeedsUnsubscribeFeedResponse(w http.ResponseWriter) error
+}
+
+type FeedsUnsubscribeFeed204Response struct {
+}
+
+func (response FeedsUnsubscribeFeed204Response) VisitFeedsUnsubscribeFeedResponse(w http.ResponseWriter) error {
+ w.WriteHeader(204)
+ return nil
+}
+
+type FeedsUnsubscribeFeed404JSONResponse ErrorResponse
+
+func (response FeedsUnsubscribeFeed404JSONResponse) VisitFeedsUnsubscribeFeedResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsGetFeedRequestObject struct {
+ FeedId string `json:"feedId"`
+}
+
+type FeedsGetFeedResponseObject interface {
+ VisitFeedsGetFeedResponse(w http.ResponseWriter) error
+}
+
+type FeedsGetFeed200JSONResponse Feed
+
+func (response FeedsGetFeed200JSONResponse) VisitFeedsGetFeedResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsGetFeed404JSONResponse ErrorResponse
+
+func (response FeedsGetFeed404JSONResponse) VisitFeedsGetFeedResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsMarkFeedReadRequestObject struct {
+ FeedId string `json:"feedId"`
+}
+
+type FeedsMarkFeedReadResponseObject interface {
+ VisitFeedsMarkFeedReadResponse(w http.ResponseWriter) error
+}
+
+type FeedsMarkFeedRead200JSONResponse Feed
+
+func (response FeedsMarkFeedRead200JSONResponse) VisitFeedsMarkFeedReadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsMarkFeedRead404JSONResponse ErrorResponse
+
+func (response FeedsMarkFeedRead404JSONResponse) VisitFeedsMarkFeedReadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsMarkFeedUnreadRequestObject struct {
+ FeedId string `json:"feedId"`
+}
+
+type FeedsMarkFeedUnreadResponseObject interface {
+ VisitFeedsMarkFeedUnreadResponse(w http.ResponseWriter) error
+}
+
+type FeedsMarkFeedUnread200JSONResponse Feed
+
+func (response FeedsMarkFeedUnread200JSONResponse) VisitFeedsMarkFeedUnreadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+type FeedsMarkFeedUnread404JSONResponse ErrorResponse
+
+func (response FeedsMarkFeedUnread404JSONResponse) VisitFeedsMarkFeedUnreadResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
+// StrictServerInterface represents all server handlers.
+type StrictServerInterface interface {
+
+ // (GET /api/articles/read)
+ ArticlesListReadArticles(ctx context.Context, request ArticlesListReadArticlesRequestObject) (ArticlesListReadArticlesResponseObject, error)
+
+ // (GET /api/articles/unread)
+ ArticlesListUnreadArticles(ctx context.Context, request ArticlesListUnreadArticlesRequestObject) (ArticlesListUnreadArticlesResponseObject, error)
+
+ // (GET /api/articles/{articleId})
+ ArticlesGetArticle(ctx context.Context, request ArticlesGetArticleRequestObject) (ArticlesGetArticleResponseObject, error)
+
+ // (POST /api/articles/{articleId}/read)
+ ArticlesMarkArticleRead(ctx context.Context, request ArticlesMarkArticleReadRequestObject) (ArticlesMarkArticleReadResponseObject, error)
+
+ // (POST /api/articles/{articleId}/unread)
+ ArticlesMarkArticleUnread(ctx context.Context, request ArticlesMarkArticleUnreadRequestObject) (ArticlesMarkArticleUnreadResponseObject, error)
+
+ // (POST /api/auth/login)
+ AuthLogin(ctx context.Context, request AuthLoginRequestObject) (AuthLoginResponseObject, error)
+
+ // (POST /api/auth/logout)
+ AuthLogout(ctx context.Context, request AuthLogoutRequestObject) (AuthLogoutResponseObject, error)
+
+ // (GET /api/auth/me)
+ AuthGetCurrentUser(ctx context.Context, request AuthGetCurrentUserRequestObject) (AuthGetCurrentUserResponseObject, error)
+
+ // (GET /api/feeds)
+ FeedsListFeeds(ctx context.Context, request FeedsListFeedsRequestObject) (FeedsListFeedsResponseObject, error)
+
+ // (POST /api/feeds)
+ FeedsAddFeed(ctx context.Context, request FeedsAddFeedRequestObject) (FeedsAddFeedResponseObject, error)
+
+ // (DELETE /api/feeds/{feedId})
+ FeedsUnsubscribeFeed(ctx context.Context, request FeedsUnsubscribeFeedRequestObject) (FeedsUnsubscribeFeedResponseObject, error)
+
+ // (GET /api/feeds/{feedId})
+ FeedsGetFeed(ctx context.Context, request FeedsGetFeedRequestObject) (FeedsGetFeedResponseObject, error)
+
+ // (POST /api/feeds/{feedId}/read)
+ FeedsMarkFeedRead(ctx context.Context, request FeedsMarkFeedReadRequestObject) (FeedsMarkFeedReadResponseObject, error)
+
+ // (POST /api/feeds/{feedId}/unread)
+ FeedsMarkFeedUnread(ctx context.Context, request FeedsMarkFeedUnreadRequestObject) (FeedsMarkFeedUnreadResponseObject, error)
+}
+
+type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc
+type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc
+
+func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
+ return &strictHandler{ssi: ssi, middlewares: middlewares}
+}
+
+type strictHandler struct {
+ ssi StrictServerInterface
+ middlewares []StrictMiddlewareFunc
+}
+
+// ArticlesListReadArticles operation middleware
+func (sh *strictHandler) ArticlesListReadArticles(ctx echo.Context, params ArticlesListReadArticlesParams) error {
+ var request ArticlesListReadArticlesRequestObject
+
+ request.Params = params
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.ArticlesListReadArticles(ctx.Request().Context(), request.(ArticlesListReadArticlesRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "ArticlesListReadArticles")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(ArticlesListReadArticlesResponseObject); ok {
+ return validResponse.VisitArticlesListReadArticlesResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// ArticlesListUnreadArticles operation middleware
+func (sh *strictHandler) ArticlesListUnreadArticles(ctx echo.Context, params ArticlesListUnreadArticlesParams) error {
+ var request ArticlesListUnreadArticlesRequestObject
+
+ request.Params = params
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.ArticlesListUnreadArticles(ctx.Request().Context(), request.(ArticlesListUnreadArticlesRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "ArticlesListUnreadArticles")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(ArticlesListUnreadArticlesResponseObject); ok {
+ return validResponse.VisitArticlesListUnreadArticlesResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// ArticlesGetArticle operation middleware
+func (sh *strictHandler) ArticlesGetArticle(ctx echo.Context, articleId string) error {
+ var request ArticlesGetArticleRequestObject
+
+ request.ArticleId = articleId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.ArticlesGetArticle(ctx.Request().Context(), request.(ArticlesGetArticleRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "ArticlesGetArticle")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(ArticlesGetArticleResponseObject); ok {
+ return validResponse.VisitArticlesGetArticleResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// ArticlesMarkArticleRead operation middleware
+func (sh *strictHandler) ArticlesMarkArticleRead(ctx echo.Context, articleId string) error {
+ var request ArticlesMarkArticleReadRequestObject
+
+ request.ArticleId = articleId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.ArticlesMarkArticleRead(ctx.Request().Context(), request.(ArticlesMarkArticleReadRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "ArticlesMarkArticleRead")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(ArticlesMarkArticleReadResponseObject); ok {
+ return validResponse.VisitArticlesMarkArticleReadResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// ArticlesMarkArticleUnread operation middleware
+func (sh *strictHandler) ArticlesMarkArticleUnread(ctx echo.Context, articleId string) error {
+ var request ArticlesMarkArticleUnreadRequestObject
+
+ request.ArticleId = articleId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.ArticlesMarkArticleUnread(ctx.Request().Context(), request.(ArticlesMarkArticleUnreadRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "ArticlesMarkArticleUnread")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(ArticlesMarkArticleUnreadResponseObject); ok {
+ return validResponse.VisitArticlesMarkArticleUnreadResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// AuthLogin operation middleware
+func (sh *strictHandler) AuthLogin(ctx echo.Context) error {
+ var request AuthLoginRequestObject
+
+ var body AuthLoginJSONRequestBody
+ if err := ctx.Bind(&body); err != nil {
+ return err
+ }
+ request.Body = &body
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.AuthLogin(ctx.Request().Context(), request.(AuthLoginRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "AuthLogin")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(AuthLoginResponseObject); ok {
+ return validResponse.VisitAuthLoginResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// AuthLogout operation middleware
+func (sh *strictHandler) AuthLogout(ctx echo.Context) error {
+ var request AuthLogoutRequestObject
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.AuthLogout(ctx.Request().Context(), request.(AuthLogoutRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "AuthLogout")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(AuthLogoutResponseObject); ok {
+ return validResponse.VisitAuthLogoutResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// AuthGetCurrentUser operation middleware
+func (sh *strictHandler) AuthGetCurrentUser(ctx echo.Context) error {
+ var request AuthGetCurrentUserRequestObject
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.AuthGetCurrentUser(ctx.Request().Context(), request.(AuthGetCurrentUserRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "AuthGetCurrentUser")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(AuthGetCurrentUserResponseObject); ok {
+ return validResponse.VisitAuthGetCurrentUserResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// FeedsListFeeds operation middleware
+func (sh *strictHandler) FeedsListFeeds(ctx echo.Context) error {
+ var request FeedsListFeedsRequestObject
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.FeedsListFeeds(ctx.Request().Context(), request.(FeedsListFeedsRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "FeedsListFeeds")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(FeedsListFeedsResponseObject); ok {
+ return validResponse.VisitFeedsListFeedsResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// FeedsAddFeed operation middleware
+func (sh *strictHandler) FeedsAddFeed(ctx echo.Context) error {
+ var request FeedsAddFeedRequestObject
+
+ var body FeedsAddFeedJSONRequestBody
+ if err := ctx.Bind(&body); err != nil {
+ return err
+ }
+ request.Body = &body
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.FeedsAddFeed(ctx.Request().Context(), request.(FeedsAddFeedRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "FeedsAddFeed")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(FeedsAddFeedResponseObject); ok {
+ return validResponse.VisitFeedsAddFeedResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// FeedsUnsubscribeFeed operation middleware
+func (sh *strictHandler) FeedsUnsubscribeFeed(ctx echo.Context, feedId string) error {
+ var request FeedsUnsubscribeFeedRequestObject
+
+ request.FeedId = feedId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.FeedsUnsubscribeFeed(ctx.Request().Context(), request.(FeedsUnsubscribeFeedRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "FeedsUnsubscribeFeed")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(FeedsUnsubscribeFeedResponseObject); ok {
+ return validResponse.VisitFeedsUnsubscribeFeedResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// FeedsGetFeed operation middleware
+func (sh *strictHandler) FeedsGetFeed(ctx echo.Context, feedId string) error {
+ var request FeedsGetFeedRequestObject
+
+ request.FeedId = feedId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.FeedsGetFeed(ctx.Request().Context(), request.(FeedsGetFeedRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "FeedsGetFeed")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(FeedsGetFeedResponseObject); ok {
+ return validResponse.VisitFeedsGetFeedResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// FeedsMarkFeedRead operation middleware
+func (sh *strictHandler) FeedsMarkFeedRead(ctx echo.Context, feedId string) error {
+ var request FeedsMarkFeedReadRequestObject
+
+ request.FeedId = feedId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.FeedsMarkFeedRead(ctx.Request().Context(), request.(FeedsMarkFeedReadRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "FeedsMarkFeedRead")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(FeedsMarkFeedReadResponseObject); ok {
+ return validResponse.VisitFeedsMarkFeedReadResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
+
+// FeedsMarkFeedUnread operation middleware
+func (sh *strictHandler) FeedsMarkFeedUnread(ctx echo.Context, feedId string) error {
+ var request FeedsMarkFeedUnreadRequestObject
+
+ request.FeedId = feedId
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.FeedsMarkFeedUnread(ctx.Request().Context(), request.(FeedsMarkFeedUnreadRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "FeedsMarkFeedUnread")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(FeedsMarkFeedUnreadResponseObject); ok {
+ return validResponse.VisitFeedsMarkFeedUnreadResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("unexpected response type: %T", response)
+ }
+ return nil
+}
diff --git a/backend/api/handler.go b/backend/api/handler.go
new file mode 100644
index 0000000..c5df375
--- /dev/null
+++ b/backend/api/handler.go
@@ -0,0 +1,16 @@
+package api
+
+import (
+ "database/sql"
+
+ "undef.ninja/x/feedaka/auth"
+ "undef.ninja/x/feedaka/db"
+)
+
+type Handler struct {
+ DB *sql.DB
+ Queries *db.Queries
+ SessionConfig *auth.SessionConfig
+}
+
+var _ StrictServerInterface = (*Handler)(nil)
diff --git a/backend/api/handler_articles.go b/backend/api/handler_articles.go
new file mode 100644
index 0000000..d480ddc
--- /dev/null
+++ b/backend/api/handler_articles.go
@@ -0,0 +1,245 @@
+package api
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "strconv"
+
+ appcontext "undef.ninja/x/feedaka/context"
+ "undef.ninja/x/feedaka/db"
+)
+
+const defaultPageSize = 30
+const maxPageSize = 100
+
+func (h *Handler) ArticlesListUnreadArticles(ctx context.Context, request ArticlesListUnreadArticlesRequestObject) (ArticlesListUnreadArticlesResponseObject, error) {
+ conn, err := h.paginatedArticles(ctx, 0, request.Params.FeedId, request.Params.After, request.Params.First)
+ if err != nil {
+ return nil, err
+ }
+ return ArticlesListUnreadArticles200JSONResponse(*conn), nil
+}
+
+func (h *Handler) ArticlesListReadArticles(ctx context.Context, request ArticlesListReadArticlesRequestObject) (ArticlesListReadArticlesResponseObject, error) {
+ conn, err := h.paginatedArticles(ctx, 1, request.Params.FeedId, request.Params.After, request.Params.First)
+ if err != nil {
+ return nil, err
+ }
+ return ArticlesListReadArticles200JSONResponse(*conn), nil
+}
+
+func (h *Handler) ArticlesGetArticle(ctx context.Context, request ArticlesGetArticleRequestObject) (ArticlesGetArticleResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ articleID, err := strconv.ParseInt(request.ArticleId, 10, 64)
+ if err != nil {
+ return ArticlesGetArticle404JSONResponse{Message: "invalid article ID"}, nil
+ }
+
+ row, err := h.Queries.GetArticle(ctx, articleID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return ArticlesGetArticle404JSONResponse{Message: "article not found"}, nil
+ }
+ return nil, err
+ }
+
+ f, err := h.Queries.GetFeed(ctx, row.FeedID)
+ if err != nil {
+ return nil, err
+ }
+ if f.UserID != userID {
+ return ArticlesGetArticle404JSONResponse{Message: "article not found"}, nil
+ }
+
+ article := dbArticleToAPI(row)
+ return ArticlesGetArticle200JSONResponse(article), nil
+}
+
+func (h *Handler) ArticlesMarkArticleRead(ctx context.Context, request ArticlesMarkArticleReadRequestObject) (ArticlesMarkArticleReadResponseObject, error) {
+ article, err := h.updateArticleReadStatus(ctx, request.ArticleId, 1)
+ if err != nil {
+ return nil, err
+ }
+ if article == nil {
+ return ArticlesMarkArticleRead404JSONResponse{Message: "article not found"}, nil
+ }
+ return ArticlesMarkArticleRead200JSONResponse(*article), nil
+}
+
+func (h *Handler) ArticlesMarkArticleUnread(ctx context.Context, request ArticlesMarkArticleUnreadRequestObject) (ArticlesMarkArticleUnreadResponseObject, error) {
+ article, err := h.updateArticleReadStatus(ctx, request.ArticleId, 0)
+ if err != nil {
+ return nil, err
+ }
+ if article == nil {
+ return ArticlesMarkArticleUnread404JSONResponse{Message: "article not found"}, nil
+ }
+ return ArticlesMarkArticleUnread200JSONResponse(*article), nil
+}
+
+func (h *Handler) updateArticleReadStatus(ctx context.Context, articleIdStr string, isRead int64) (*Article, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ articleID, err := strconv.ParseInt(articleIdStr, 10, 64)
+ if err != nil {
+ return nil, nil
+ }
+
+ article, err := h.Queries.GetArticle(ctx, articleID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return nil, nil
+ }
+ return nil, err
+ }
+
+ f, err := h.Queries.GetFeed(ctx, article.FeedID)
+ if err != nil {
+ return nil, err
+ }
+ if f.UserID != userID {
+ return nil, nil
+ }
+
+ err = h.Queries.UpdateArticleReadStatus(ctx, db.UpdateArticleReadStatusParams{
+ IsRead: isRead,
+ ID: article.ID,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Re-fetch for updated state
+ updated, err := h.Queries.GetArticle(ctx, articleID)
+ if err != nil {
+ return nil, err
+ }
+
+ result := dbArticleToAPI(updated)
+ return &result, nil
+}
+
+func (h *Handler) paginatedArticles(ctx context.Context, isRead int64, feedID *string, after *string, first *int32) (*ArticleConnection, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ limit := int64(defaultPageSize)
+ if first != nil {
+ limit = int64(*first)
+ if limit <= 0 {
+ limit = int64(defaultPageSize)
+ }
+ if limit > maxPageSize {
+ limit = maxPageSize
+ }
+ }
+
+ fetchLimit := limit + 1
+
+ var rawRows []any
+
+ if feedID != nil {
+ parsedFeedID, err := strconv.ParseInt(*feedID, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid feed ID: %w", err)
+ }
+
+ if after != nil {
+ cursor, err := strconv.ParseInt(*after, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid cursor: %w", err)
+ }
+ rows, err := h.Queries.GetArticlesByFeedPaginatedAfter(ctx, db.GetArticlesByFeedPaginatedAfterParams{
+ IsRead: isRead,
+ UserID: userID,
+ FeedID: parsedFeedID,
+ ID: cursor,
+ Limit: fetchLimit,
+ })
+ if err != nil {
+ return nil, err
+ }
+ for _, row := range rows {
+ rawRows = append(rawRows, row)
+ }
+ } else {
+ rows, err := h.Queries.GetArticlesByFeedPaginated(ctx, db.GetArticlesByFeedPaginatedParams{
+ IsRead: isRead,
+ UserID: userID,
+ FeedID: parsedFeedID,
+ Limit: fetchLimit,
+ })
+ if err != nil {
+ return nil, err
+ }
+ for _, row := range rows {
+ rawRows = append(rawRows, row)
+ }
+ }
+ } else {
+ if after != nil {
+ cursor, err := strconv.ParseInt(*after, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid cursor: %w", err)
+ }
+ rows, err := h.Queries.GetArticlesPaginatedAfter(ctx, db.GetArticlesPaginatedAfterParams{
+ IsRead: isRead,
+ UserID: userID,
+ ID: cursor,
+ Limit: fetchLimit,
+ })
+ if err != nil {
+ return nil, err
+ }
+ for _, row := range rows {
+ rawRows = append(rawRows, row)
+ }
+ } else {
+ rows, err := h.Queries.GetArticlesPaginated(ctx, db.GetArticlesPaginatedParams{
+ IsRead: isRead,
+ UserID: userID,
+ Limit: fetchLimit,
+ })
+ if err != nil {
+ return nil, err
+ }
+ for _, row := range rows {
+ rawRows = append(rawRows, row)
+ }
+ }
+ }
+
+ hasNextPage := int64(len(rawRows)) > limit
+ if hasNextPage {
+ rawRows = rawRows[:limit]
+ }
+
+ articles := make([]Article, 0, len(rawRows))
+ for _, raw := range rawRows {
+ articles = append(articles, rowToArticle(toArticleRow(raw)))
+ }
+
+ var endCursor *string
+ if len(articles) > 0 {
+ lastID := articles[len(articles)-1].Id
+ endCursor = &lastID
+ }
+
+ return &ArticleConnection{
+ Articles: articles,
+ PageInfo: PageInfo{
+ HasNextPage: hasNextPage,
+ EndCursor: endCursor,
+ },
+ }, nil
+}
diff --git a/backend/api/handler_auth.go b/backend/api/handler_auth.go
new file mode 100644
index 0000000..6e10538
--- /dev/null
+++ b/backend/api/handler_auth.go
@@ -0,0 +1,73 @@
+package api
+
+import (
+ "context"
+ "database/sql"
+ "strconv"
+
+ "undef.ninja/x/feedaka/auth"
+ appcontext "undef.ninja/x/feedaka/context"
+)
+
+func (h *Handler) AuthLogin(ctx context.Context, request AuthLoginRequestObject) (AuthLoginResponseObject, error) {
+ user, err := h.Queries.GetUserByUsername(ctx, request.Body.Username)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return AuthLogin401JSONResponse{Message: "invalid credentials"}, nil
+ }
+ return AuthLogin401JSONResponse{Message: "invalid credentials"}, nil
+ }
+
+ if !auth.VerifyPassword(user.PasswordHash, request.Body.Password) {
+ return AuthLogin401JSONResponse{Message: "invalid credentials"}, nil
+ }
+
+ echoCtx := getEchoContext(ctx)
+ if echoCtx == nil {
+ return nil, errNoEchoContext
+ }
+
+ if err := h.SessionConfig.SetUserID(echoCtx, user.ID); err != nil {
+ return nil, err
+ }
+
+ return AuthLogin200JSONResponse{
+ User: User{
+ Id: strconv.FormatInt(user.ID, 10),
+ Username: user.Username,
+ },
+ }, nil
+}
+
+func (h *Handler) AuthLogout(ctx context.Context, _ AuthLogoutRequestObject) (AuthLogoutResponseObject, error) {
+ echoCtx := getEchoContext(ctx)
+ if echoCtx == nil {
+ return nil, errNoEchoContext
+ }
+
+ if err := h.SessionConfig.DestroySession(echoCtx); err != nil {
+ return nil, err
+ }
+
+ return AuthLogout204Response{}, nil
+}
+
+func (h *Handler) AuthGetCurrentUser(ctx context.Context, _ AuthGetCurrentUserRequestObject) (AuthGetCurrentUserResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return AuthGetCurrentUser401JSONResponse{Message: "authentication required"}, nil
+ }
+
+ user, err := h.Queries.GetUserByID(ctx, userID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return AuthGetCurrentUser401JSONResponse{Message: "authentication required"}, nil
+ }
+ return nil, err
+ }
+
+ return AuthGetCurrentUser200JSONResponse{
+ Id: strconv.FormatInt(user.ID, 10),
+ Username: user.Username,
+ }, nil
+}
diff --git a/backend/api/handler_feeds.go b/backend/api/handler_feeds.go
new file mode 100644
index 0000000..f0a8785
--- /dev/null
+++ b/backend/api/handler_feeds.go
@@ -0,0 +1,185 @@
+package api
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "strconv"
+ "time"
+
+ appcontext "undef.ninja/x/feedaka/context"
+ "undef.ninja/x/feedaka/db"
+ "undef.ninja/x/feedaka/feed"
+)
+
+func (h *Handler) FeedsListFeeds(ctx context.Context, _ FeedsListFeedsRequestObject) (FeedsListFeedsResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ dbFeeds, err := h.Queries.GetFeeds(ctx, userID)
+ if err != nil {
+ return nil, err
+ }
+
+ unreadCounts, err := h.Queries.GetFeedUnreadCounts(ctx, userID)
+ if err != nil {
+ return nil, err
+ }
+ countMap := make(map[int64]int64, len(unreadCounts))
+ for _, uc := range unreadCounts {
+ countMap[uc.FeedID] = uc.UnreadCount
+ }
+
+ feeds := make(FeedsListFeeds200JSONResponse, 0, len(dbFeeds))
+ for _, f := range dbFeeds {
+ feeds = append(feeds, dbFeedToAPI(f, countMap[f.ID]))
+ }
+
+ return feeds, nil
+}
+
+func (h *Handler) FeedsAddFeed(ctx context.Context, request FeedsAddFeedRequestObject) (FeedsAddFeedResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ f, err := feed.Fetch(ctx, request.Body.Url)
+ if err != nil {
+ return FeedsAddFeed400JSONResponse{Message: fmt.Sprintf("failed to parse feed: %v", err)}, nil
+ }
+
+ dbFeed, err := h.Queries.CreateFeed(ctx, db.CreateFeedParams{
+ Url: request.Body.Url,
+ Title: f.Title,
+ FetchedAt: time.Now().UTC().Format(time.RFC3339),
+ UserID: userID,
+ })
+ if err != nil {
+ return FeedsAddFeed400JSONResponse{Message: fmt.Sprintf("failed to insert feed: %v", err)}, nil
+ }
+
+ if err := feed.Sync(ctx, h.Queries, dbFeed.ID, f); err != nil {
+ return FeedsAddFeed400JSONResponse{Message: fmt.Sprintf("failed to sync articles: %v", err)}, nil
+ }
+
+ return FeedsAddFeed201JSONResponse(dbFeedToAPI(dbFeed, 0)), nil
+}
+
+func (h *Handler) FeedsGetFeed(ctx context.Context, request FeedsGetFeedRequestObject) (FeedsGetFeedResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ feedID, err := strconv.ParseInt(request.FeedId, 10, 64)
+ if err != nil {
+ return FeedsGetFeed404JSONResponse{Message: "invalid feed ID"}, nil
+ }
+
+ dbFeed, err := h.Queries.GetFeed(ctx, feedID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return FeedsGetFeed404JSONResponse{Message: "feed not found"}, nil
+ }
+ return nil, err
+ }
+
+ if dbFeed.UserID != userID {
+ return FeedsGetFeed404JSONResponse{Message: "feed not found"}, nil
+ }
+
+ return FeedsGetFeed200JSONResponse(dbFeedToAPI(dbFeed, 0)), nil
+}
+
+func (h *Handler) FeedsUnsubscribeFeed(ctx context.Context, request FeedsUnsubscribeFeedRequestObject) (FeedsUnsubscribeFeedResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ feedID, err := strconv.ParseInt(request.FeedId, 10, 64)
+ if err != nil {
+ return FeedsUnsubscribeFeed404JSONResponse{Message: "invalid feed ID"}, nil
+ }
+
+ dbFeed, err := h.Queries.GetFeed(ctx, feedID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return FeedsUnsubscribeFeed404JSONResponse{Message: "feed not found"}, nil
+ }
+ return nil, err
+ }
+
+ if dbFeed.UserID != userID {
+ return FeedsUnsubscribeFeed404JSONResponse{Message: "feed not found"}, nil
+ }
+
+ if err := h.Queries.UnsubscribeFeed(ctx, feedID); err != nil {
+ return nil, err
+ }
+
+ return FeedsUnsubscribeFeed204Response{}, nil
+}
+
+func (h *Handler) FeedsMarkFeedRead(ctx context.Context, request FeedsMarkFeedReadRequestObject) (FeedsMarkFeedReadResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ feedID, err := strconv.ParseInt(request.FeedId, 10, 64)
+ if err != nil {
+ return FeedsMarkFeedRead404JSONResponse{Message: "invalid feed ID"}, nil
+ }
+
+ dbFeed, err := h.Queries.GetFeed(ctx, feedID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return FeedsMarkFeedRead404JSONResponse{Message: "feed not found"}, nil
+ }
+ return nil, err
+ }
+
+ if dbFeed.UserID != userID {
+ return FeedsMarkFeedRead404JSONResponse{Message: "feed not found"}, nil
+ }
+
+ if err := h.Queries.MarkFeedArticlesRead(ctx, feedID); err != nil {
+ return nil, err
+ }
+
+ return FeedsMarkFeedRead200JSONResponse(dbFeedToAPI(dbFeed, 0)), nil
+}
+
+func (h *Handler) FeedsMarkFeedUnread(ctx context.Context, request FeedsMarkFeedUnreadRequestObject) (FeedsMarkFeedUnreadResponseObject, error) {
+ userID, ok := appcontext.GetUserID(ctx)
+ if !ok {
+ return nil, fmt.Errorf("authentication required")
+ }
+
+ feedID, err := strconv.ParseInt(request.FeedId, 10, 64)
+ if err != nil {
+ return FeedsMarkFeedUnread404JSONResponse{Message: "invalid feed ID"}, nil
+ }
+
+ dbFeed, err := h.Queries.GetFeed(ctx, feedID)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return FeedsMarkFeedUnread404JSONResponse{Message: "feed not found"}, nil
+ }
+ return nil, err
+ }
+
+ if dbFeed.UserID != userID {
+ return FeedsMarkFeedUnread404JSONResponse{Message: "feed not found"}, nil
+ }
+
+ if err := h.Queries.MarkFeedArticlesUnread(ctx, feedID); err != nil {
+ return nil, err
+ }
+
+ return FeedsMarkFeedUnread200JSONResponse(dbFeedToAPI(dbFeed, 0)), nil
+}
diff --git a/backend/api/oapi-codegen.yaml b/backend/api/oapi-codegen.yaml
new file mode 100644
index 0000000..bb775d9
--- /dev/null
+++ b/backend/api/oapi-codegen.yaml
@@ -0,0 +1,6 @@
+package: api
+output: generated.go
+generate:
+ echo-server: true
+ models: true
+ strict-server: true