fix: shortcut view activity

This commit is contained in:
Steven 2024-08-24 00:14:55 +08:00
parent 2ddb47f4df
commit 93bb880e8e
4 changed files with 59 additions and 102 deletions

6
server/common/common.go Normal file
View File

@ -0,0 +1,6 @@
package common
const (
// BotID is the id of bot.
BotID = 0
)

View File

@ -10,8 +10,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
@ -110,11 +108,6 @@ func (s *APIV1Service) GetShortcutByName(ctx context.Context, request *v1pb.GetS
} }
} }
// Create shortcut view activity.
if err := s.createShortcutViewActivity(ctx, shortcut); err != nil {
fmt.Printf("failed to create activity, err: %v", err)
}
composedShortcut, err := s.convertShortcutFromStorepb(ctx, shortcut) composedShortcut, err := s.convertShortcutFromStorepb(ctx, shortcut)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert shortcut, err: %v", err) return nil, status.Errorf(codes.Internal, "failed to convert shortcut, err: %v", err)
@ -352,35 +345,6 @@ func mapToAnalyticsSlice(m map[string]int32) []*v1pb.GetShortcutAnalyticsRespons
return analyticsSlice return analyticsSlice
} }
func (s *APIV1Service) createShortcutViewActivity(ctx context.Context, shortcut *storepb.Shortcut) error {
p, _ := peer.FromContext(ctx)
headers, ok := metadata.FromIncomingContext(ctx)
if !ok {
return errors.New("Failed to get metadata from context")
}
payload := &storepb.ActivityShorcutViewPayload{
ShortcutId: shortcut.Id,
Ip: p.Addr.String(),
Referer: headers.Get("referer")[0],
UserAgent: headers.Get("user-agent")[0],
}
payloadStr, err := protojson.Marshal(payload)
if err != nil {
return errors.Wrap(err, "Failed to marshal activity payload")
}
activity := &store.Activity{
CreatorID: BotID,
Type: store.ActivityShortcutView,
Level: store.ActivityInfo,
Payload: string(payloadStr),
}
_, err = s.Store.CreateActivity(ctx, activity)
if err != nil {
return errors.Wrap(err, "Failed to create activity")
}
return nil
}
func (s *APIV1Service) createShortcutCreateActivity(ctx context.Context, shortcut *storepb.Shortcut) error { func (s *APIV1Service) createShortcutCreateActivity(ctx context.Context, shortcut *storepb.Shortcut) error {
payload := &storepb.ActivityShorcutCreatePayload{ payload := &storepb.ActivityShorcutCreatePayload{
ShortcutId: shortcut.Id, ShortcutId: shortcut.Id,

View File

@ -18,11 +18,6 @@ import (
"github.com/yourselfhosted/slash/store" "github.com/yourselfhosted/slash/store"
) )
const (
// BotID is the id of bot.
BotID = 0
)
func (s *APIV1Service) ListUsers(ctx context.Context, _ *v1pb.ListUsersRequest) (*v1pb.ListUsersResponse, error) { func (s *APIV1Service) ListUsers(ctx context.Context, _ *v1pb.ListUsersRequest) (*v1pb.ListUsersResponse, error) {
users, err := s.Store.ListUsers(ctx, &store.FindUser{}) users, err := s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil { if err != nil {

View File

@ -5,14 +5,18 @@ import (
"embed" "embed"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"net/http" "net/http"
"strings" "strings"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
"github.com/yourselfhosted/slash/internal/util" "github.com/yourselfhosted/slash/internal/util"
storepb "github.com/yourselfhosted/slash/proto/gen/store" storepb "github.com/yourselfhosted/slash/proto/gen/store"
"github.com/yourselfhosted/slash/server/common"
"github.com/yourselfhosted/slash/server/metric" "github.com/yourselfhosted/slash/server/metric"
"github.com/yourselfhosted/slash/server/profile" "github.com/yourselfhosted/slash/server/profile"
"github.com/yourselfhosted/slash/store" "github.com/yourselfhosted/slash/store"
@ -44,35 +48,34 @@ func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) {
HTML5: true, HTML5: true,
Filesystem: getFileSystem("dist"), Filesystem: getFileSystem("dist"),
Skipper: func(c echo.Context) bool { Skipper: func(c echo.Context) bool {
return util.HasPrefixes(c.Path(), "/api", "/slash.api.v1", "/robots.txt", "/sitemap.xml", "/s/:shortcutName", "/c/:collectionName") return util.HasPrefixes(c.Path(), "/api", "/slash.api.v1", "/s/:shortcutName", "/c/:collectionName")
}, },
})) }))
g := e.Group("assets") assetsGroup := e.Group("assets")
// Use echo gzip middleware to compress the response. // Use echo gzip middleware to compress the response.
// Reference: https://echo.labstack.com/docs/middleware/gzip // Reference: https://echo.labstack.com/docs/middleware/gzip
g.Use(middleware.GzipWithConfig(middleware.GzipConfig{ assetsGroup.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Skipper: func(c echo.Context) bool { Skipper: func(c echo.Context) bool {
return util.HasPrefixes(c.Path(), "/api", "/slash.api.v1", "/robots.txt", "/sitemap.xml", "/s/:shortcutName", "/c/:collectionName") return util.HasPrefixes(c.Path(), "/api", "/slash.api.v1", "/s/:shortcutName", "/c/:collectionName")
}, },
Level: 5, Level: 5,
})) }))
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc { assetsGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable") c.Response().Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable")
return next(c) return next(c)
} }
}) })
g.Use(middleware.StaticWithConfig(middleware.StaticConfig{ assetsGroup.Use(middleware.StaticWithConfig(middleware.StaticConfig{
HTML5: true, HTML5: true,
Filesystem: getFileSystem("dist/assets"), Filesystem: getFileSystem("dist/assets"),
Skipper: func(c echo.Context) bool { Skipper: func(c echo.Context) bool {
return util.HasPrefixes(c.Path(), "/api", "/slash.api.v1", "/robots.txt", "/sitemap.xml", "/s/:shortcutName", "/c/:collectionName") return util.HasPrefixes(c.Path(), "/api", "/slash.api.v1", "/s/:shortcutName", "/c/:collectionName")
}, },
})) }))
s.registerRoutes(e) s.registerRoutes(e)
s.registerFileRoutes(ctx, e)
} }
func (s *FrontendService) registerRoutes(e *echo.Echo) { func (s *FrontendService) registerRoutes(e *echo.Echo) {
@ -84,21 +87,20 @@ func (s *FrontendService) registerRoutes(e *echo.Echo) {
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{ shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
Name: &shortcutName, Name: &shortcutName,
}) })
if err != nil { // If any error occurs or the shortcut is not found, return the raw `index.html`.
return c.HTML(http.StatusOK, rawIndexHTML) if err != nil || shortcut == nil {
}
if shortcut == nil {
return c.HTML(http.StatusOK, rawIndexHTML) return c.HTML(http.StatusOK, rawIndexHTML)
} }
metric.Enqueue("shortcut view") // Create shortcut view activity.
// Only set the `Location` header if the link is a valid URI. if err := s.createShortcutViewActivity(ctx, c.Request(), shortcut); err != nil {
if util.ValidateURI(shortcut.Link) { slog.Warn("failed to create shortcut view activity", slog.String("error", err.Error()))
c.Response().Header().Set("Location", shortcut.Link)
} }
metric.Enqueue("shortcut view")
// Inject shortcut metadata into `index.html`. // Inject shortcut metadata into `index.html`.
indexHTML := strings.ReplaceAll(rawIndexHTML, headerMetadataPlaceholder, generateShortcutMetadata(shortcut).String()) indexHTML := strings.ReplaceAll(rawIndexHTML, headerMetadataPlaceholder, generateShortcutMetadata(shortcut).String())
return c.HTML(http.StatusPermanentRedirect, indexHTML) return c.HTML(http.StatusOK, indexHTML)
}) })
e.GET("/c/:collectionName", func(c echo.Context) error { e.GET("/c/:collectionName", func(c echo.Context) error {
@ -107,10 +109,8 @@ func (s *FrontendService) registerRoutes(e *echo.Echo) {
collection, err := s.Store.GetCollection(ctx, &store.FindCollection{ collection, err := s.Store.GetCollection(ctx, &store.FindCollection{
Name: &collectionName, Name: &collectionName,
}) })
if err != nil { // If any error occurs or the collection is not found, return the raw `index.html`.
return c.HTML(http.StatusOK, rawIndexHTML) if err != nil || collection == nil {
}
if collection == nil {
return c.HTML(http.StatusOK, rawIndexHTML) return c.HTML(http.StatusOK, rawIndexHTML)
} }
@ -121,50 +121,42 @@ func (s *FrontendService) registerRoutes(e *echo.Echo) {
}) })
} }
func (s *FrontendService) registerFileRoutes(ctx context.Context, e *echo.Echo) { func (s *FrontendService) createShortcutViewActivity(ctx context.Context, request *http.Request, shortcut *storepb.Shortcut) error {
workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx) ip := getReadUserIP(request)
referer := request.Header.Get("Referer")
userAgent := request.Header.Get("User-Agent")
payload := &storepb.ActivityShorcutViewPayload{
ShortcutId: shortcut.Id,
Ip: ip,
Referer: referer,
UserAgent: userAgent,
}
payloadStr, err := protojson.Marshal(payload)
if err != nil { if err != nil {
return return errors.Wrap(err, "Failed to marshal activity payload")
} }
instanceURL := workspaceGeneralSetting.InstanceUrl activity := &store.Activity{
if instanceURL == "" { CreatorID: common.BotID,
return Type: store.ActivityShortcutView,
Level: store.ActivityInfo,
Payload: string(payloadStr),
} }
_, err = s.Store.CreateActivity(ctx, activity)
if err != nil {
return errors.Wrap(err, "Failed to create activity")
}
return nil
}
e.GET("/robots.txt", func(c echo.Context) error { func getReadUserIP(r *http.Request) string {
robotsTxt := fmt.Sprintf(`User-agent: * IPAddress := r.Header.Get("X-Real-Ip")
Allow: / if IPAddress == "" {
Host: %s IPAddress = r.Header.Get("X-Forwarded-For")
Sitemap: %s/sitemap.xml`, instanceURL, instanceURL) }
return c.String(http.StatusOK, robotsTxt) if IPAddress == "" {
}) IPAddress = r.RemoteAddr
}
e.GET("/sitemap.xml", func(c echo.Context) error { return IPAddress
urlsets := []string{}
// Append shortcut list.
shortcuts, err := s.Store.ListShortcuts(ctx, &store.FindShortcut{
VisibilityList: []store.Visibility{store.VisibilityPublic},
})
if err != nil {
return err
}
for _, shortcut := range shortcuts {
urlsets = append(urlsets, fmt.Sprintf(`<url><loc>%s/s/%s</loc></url>`, instanceURL, shortcut.Name))
}
// Append collection list.
collections, err := s.Store.ListCollections(ctx, &store.FindCollection{
VisibilityList: []store.Visibility{store.VisibilityPublic},
})
if err != nil {
return err
}
for _, collection := range collections {
urlsets = append(urlsets, fmt.Sprintf(`<url><loc>%s/c/%s</loc></url>`, instanceURL, collection.Name))
}
sitemap := fmt.Sprintf(`<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">%s</urlset>`, strings.Join(urlsets, "\n"))
return c.XMLBlob(http.StatusOK, []byte(sitemap))
})
} }
func getFileSystem(path string) http.FileSystem { func getFileSystem(path string) http.FileSystem {