diff --git a/api/resource/resource.go b/api/resource/resource.go index 5515ec79..622a0344 100644 --- a/api/resource/resource.go +++ b/api/resource/resource.go @@ -30,24 +30,24 @@ const ( thumbnailImagePath = ".thumbnail_cache" ) -type Service struct { +type ResourceService struct { Profile *profile.Profile Store *store.Store } -func NewService(profile *profile.Profile, store *store.Store) *Service { - return &Service{ +func NewResourceService(profile *profile.Profile, store *store.Store) *ResourceService { + return &ResourceService{ Profile: profile, Store: store, } } -func (s *Service) RegisterResourcePublicRoutes(g *echo.Group) { +func (s *ResourceService) RegisterRoutes(g *echo.Group) { g.GET("/r/:resourceName", s.streamResource) g.GET("/r/:resourceName/*", s.streamResource) } -func (s *Service) streamResource(c echo.Context) error { +func (s *ResourceService) streamResource(c echo.Context) error { ctx := c.Request().Context() resourceName := c.Param("resourceName") resource, err := s.Store.GetResource(ctx, &store.FindResource{ diff --git a/api/v1/rss.go b/api/rss/rss.go similarity index 50% rename from api/v1/rss.go rename to api/rss/rss.go index a067f776..1124fd3d 100644 --- a/api/v1/rss.go +++ b/api/rss/rss.go @@ -1,8 +1,7 @@ -package v1 +package rss import ( "context" - "encoding/json" "net/http" "strconv" "strings" @@ -10,12 +9,12 @@ import ( "github.com/gorilla/feeds" "github.com/labstack/echo/v4" + + "github.com/usememos/gomark" "github.com/usememos/gomark/ast" - "github.com/usememos/gomark/parser" - "github.com/usememos/gomark/parser/tokenizer" "github.com/usememos/gomark/renderer" - "github.com/usememos/memos/internal/util" + "github.com/usememos/memos/server/profile" "github.com/usememos/memos/store" ) @@ -24,26 +23,25 @@ const ( maxRSSItemTitleLength = 128 ) -func (s *APIV1Service) registerRSSRoutes(g *echo.Group) { - g.GET("/explore/rss.xml", s.GetExploreRSS) - g.GET("/u/:id/rss.xml", s.GetUserRSS) +type RSSService struct { + Profile *profile.Profile + Store *store.Store } -// GetExploreRSS godoc -// -// @Summary Get RSS -// @Tags rss -// @Produce xml -// @Success 200 {object} nil "RSS" -// @Failure 500 {object} nil "Failed to get system customized profile | Failed to find memo list | Failed to generate rss" -// @Router /explore/rss.xml [GET] -func (s *APIV1Service) GetExploreRSS(c echo.Context) error { - ctx := c.Request().Context() - systemCustomizedProfile, err := s.getSystemCustomizedProfile(ctx) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get system customized profile").SetInternal(err) +func NewRSSService(profile *profile.Profile, store *store.Store) *RSSService { + return &RSSService{ + Profile: profile, + Store: store, } +} + +func (s *RSSService) RegisterRoutes(g *echo.Group) { + g.GET("/explore/rss.xml", s.GetExploreRSS) + g.GET("/u/:username/rss.xml", s.GetUserRSS) +} +func (s *RSSService) GetExploreRSS(c echo.Context) error { + ctx := c.Request().Context() normalStatus := store.Normal memoFind := store.FindMemo{ RowStatus: &normalStatus, @@ -55,7 +53,7 @@ func (s *APIV1Service) GetExploreRSS(c echo.Context) error { } baseURL := c.Scheme() + "://" + c.Request().Host - rss, err := s.generateRSSFromMemoList(ctx, memoList, baseURL, systemCustomizedProfile) + rss, err := s.generateRSSFromMemoList(ctx, memoList, baseURL) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").SetInternal(err) } @@ -63,31 +61,22 @@ func (s *APIV1Service) GetExploreRSS(c echo.Context) error { return c.String(http.StatusOK, rss) } -// GetUserRSS godoc -// -// @Summary Get RSS for a user -// @Tags rss -// @Produce xml -// @Param id path int true "User ID" -// @Success 200 {object} nil "RSS" -// @Failure 400 {object} nil "User id is not a number" -// @Failure 500 {object} nil "Failed to get system customized profile | Failed to find memo list | Failed to generate rss" -// @Router /u/{id}/rss.xml [GET] -func (s *APIV1Service) GetUserRSS(c echo.Context) error { +func (s *RSSService) GetUserRSS(c echo.Context) error { ctx := c.Request().Context() - id, err := util.ConvertStringToInt32(c.Param("id")) + username := c.Param("username") + user, err := s.Store.GetUser(ctx, &store.FindUser{ + Username: &username, + }) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "User id is not a number").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) } - - systemCustomizedProfile, err := s.getSystemCustomizedProfile(ctx) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get system customized profile").SetInternal(err) + if user == nil { + return echo.NewHTTPError(http.StatusNotFound, "User not found") } normalStatus := store.Normal memoFind := store.FindMemo{ - CreatorID: &id, + CreatorID: &user.ID, RowStatus: &normalStatus, VisibilityList: []store.Visibility{store.Public}, } @@ -97,7 +86,7 @@ func (s *APIV1Service) GetUserRSS(c echo.Context) error { } baseURL := c.Scheme() + "://" + c.Request().Host - rss, err := s.generateRSSFromMemoList(ctx, memoList, baseURL, systemCustomizedProfile) + rss, err := s.generateRSSFromMemoList(ctx, memoList, baseURL) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").SetInternal(err) } @@ -105,38 +94,41 @@ func (s *APIV1Service) GetUserRSS(c echo.Context) error { return c.String(http.StatusOK, rss) } -func (s *APIV1Service) generateRSSFromMemoList(ctx context.Context, memoList []*store.Memo, baseURL string, profile *CustomizedProfile) (string, error) { +func (s *RSSService) generateRSSFromMemoList(ctx context.Context, memoList []*store.Memo, baseURL string) (string, error) { feed := &feeds.Feed{ - Title: profile.Name, + Title: "Memos", Link: &feeds.Link{Href: baseURL}, - Description: profile.Description, + Description: "An open source, lightweight note-taking service. Easily capture and share your great thoughts.", Created: time.Now(), } var itemCountLimit = util.Min(len(memoList), maxRSSItemCount) feed.Items = make([]*feeds.Item, itemCountLimit) for i := 0; i < itemCountLimit; i++ { - memoMessage, err := s.convertMemoFromStore(ctx, memoList[i]) - if err != nil { - return "", err - } - description, err := getRSSItemDescription(memoMessage.Content) + memo := memoList[i] + description, err := getRSSItemDescription(memo.Content) if err != nil { return "", err } feed.Items[i] = &feeds.Item{ - Title: getRSSItemTitle(memoMessage.Content), - Link: &feeds.Link{Href: baseURL + "/m/" + memoMessage.Name}, + Title: getRSSItemTitle(memo.Content), + Link: &feeds.Link{Href: baseURL + "/m/" + memo.ResourceName}, Description: description, - Created: time.Unix(memoMessage.CreatedTs, 0), + Created: time.Unix(memo.CreatedTs, 0), } - if len(memoMessage.ResourceList) > 0 { - resource := memoMessage.ResourceList[0] + resources, err := s.Store.ListResources(ctx, &store.FindResource{ + MemoID: &memo.ID, + }) + if err != nil { + return "", err + } + if len(resources) > 0 { + resource := resources[0] enclosure := feeds.Enclosure{} if resource.ExternalLink != "" { enclosure.Url = resource.ExternalLink } else { - enclosure.Url = baseURL + "/o/r/" + resource.Name + enclosure.Url = baseURL + "/o/r/" + resource.ResourceName } enclosure.Length = strconv.Itoa(int(resource.Size)) enclosure.Type = resource.Type @@ -151,29 +143,8 @@ func (s *APIV1Service) generateRSSFromMemoList(ctx context.Context, memoList []* return rss, nil } -func (s *APIV1Service) getSystemCustomizedProfile(ctx context.Context) (*CustomizedProfile, error) { - systemSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ - Name: SystemSettingCustomizedProfileName.String(), - }) - if err != nil { - return nil, err - } - customizedProfile := &CustomizedProfile{ - Name: "Memos", - Locale: "en", - Appearance: "system", - } - if systemSetting != nil { - if err := json.Unmarshal([]byte(systemSetting.Value), customizedProfile); err != nil { - return nil, err - } - } - return customizedProfile, nil -} - func getRSSItemTitle(content string) string { - tokens := tokenizer.Tokenize(content) - nodes, _ := parser.Parse(tokens) + nodes, _ := gomark.Parse(content) if len(nodes) > 0 { firstNode := nodes[0] title := renderer.NewStringRenderer().Render([]ast.Node{firstNode}) @@ -189,8 +160,7 @@ func getRSSItemTitle(content string) string { } func getRSSItemDescription(content string) (string, error) { - tokens := tokenizer.Tokenize(content) - nodes, err := parser.Parse(tokens) + nodes, err := gomark.Parse(content) if err != nil { return "", err } diff --git a/api/v1/v1.go b/api/v1/v1.go index 50d0054c..2143e89b 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -8,6 +8,7 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/usememos/memos/api/resource" + "github.com/usememos/memos/api/rss" "github.com/usememos/memos/plugin/telegram" "github.com/usememos/memos/server/profile" "github.com/usememos/memos/store" @@ -44,9 +45,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store } func (s *APIV1Service) Register(rootGroup *echo.Group) { - // Register RSS routes. - s.registerRSSRoutes(rootGroup) - // Register API v1 routes. apiV1Group := rootGroup.Group("/api/v1") apiV1Group.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ @@ -85,9 +83,12 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) { return JWTMiddleware(s, next, s.Secret) }) s.registerGetterPublicRoutes(publicGroup) + // Create and register resource public routes. - resourceService := resource.NewService(s.Profile, s.Store) - resourceService.RegisterResourcePublicRoutes(publicGroup) + resource.NewResourceService(s.Profile, s.Store).RegisterRoutes(publicGroup) + + // Create and register rss public routes. + rss.NewRSSService(s.Profile, s.Store).RegisterRoutes(rootGroup) // programmatically set API version same as the server version SwaggerInfo.Version = s.Profile.Version diff --git a/go.mod b/go.mod index a50c43c1..196ddad9 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 github.com/swaggo/swag v1.16.2 - github.com/usememos/gomark v0.1.0 + github.com/usememos/gomark v0.1.1 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20240119083558-1b970713d09a diff --git a/go.sum b/go.sum index 90e30468..06efa002 100644 --- a/go.sum +++ b/go.sum @@ -459,8 +459,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/usememos/gomark v0.1.0 h1:3/hxfCm02iHptnHj1fYR38XXKGH8qVIDfYVa7/69tnc= -github.com/usememos/gomark v0.1.0/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= +github.com/usememos/gomark v0.1.1 h1:e5AYuZCdPhcqI0gEG89zqw3r3sc/+O6nH2GvJpRI0/w= +github.com/usememos/gomark v0.1.1/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index ee5477e0..bc09e84e 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -93,7 +93,7 @@ const UserProfile = () => { (user ? ( <>