This commit is contained in:
2025-05-19 01:49:56 +04:00
commit e6fec752f9
52 changed files with 4337 additions and 0 deletions

49
pkg/api/handler/base.go Normal file
View File

@@ -0,0 +1,49 @@
package handler
import (
"fmt"
"net/http"
"net/url"
"github.com/aykhans/bsky-feedgen/pkg/api/response"
"github.com/whyrusleeping/go-did"
)
type BaseHandler struct {
WellKnownDIDDoc did.Document
}
func NewBaseHandler(serviceEndpoint *url.URL, serviceDID *did.DID) (*BaseHandler, error) {
serviceID, err := did.ParseDID("#bsky_fg")
if err != nil {
return nil, fmt.Errorf("service ID parse error: %v", err)
}
return &BaseHandler{
WellKnownDIDDoc: did.Document{
Context: []string{did.CtxDIDv1},
ID: *serviceDID,
Service: []did.Service{
{
ID: serviceID,
Type: "BskyFeedGenerator",
ServiceEndpoint: serviceEndpoint.String(),
},
},
},
}, nil
}
type WellKnownDidResponse struct {
Context []string `json:"@context"`
ID string `json:"id"`
Service []did.Service `json:"service"`
}
func (handler *BaseHandler) GetWellKnownDIDDoc(w http.ResponseWriter, r *http.Request) {
response.JSON(w, 200, WellKnownDidResponse{
Context: handler.WellKnownDIDDoc.Context,
ID: handler.WellKnownDIDDoc.ID.String(),
Service: handler.WellKnownDIDDoc.Service,
})
}

101
pkg/api/handler/feed.go Normal file
View File

@@ -0,0 +1,101 @@
package handler
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"github.com/aykhans/bsky-feedgen/pkg/api/middleware"
"github.com/aykhans/bsky-feedgen/pkg/api/response"
"github.com/aykhans/bsky-feedgen/pkg/feed"
"github.com/aykhans/bsky-feedgen/pkg/types"
"github.com/aykhans/bsky-feedgen/pkg/utils"
"github.com/bluesky-social/indigo/api/bsky"
"github.com/whyrusleeping/go-did"
)
type FeedHandler struct {
feedsOutput []*bsky.FeedDescribeFeedGenerator_Feed
feedsMap map[string]feed.Feed
publisherDID *did.DID
}
func NewFeedHandler(feeds []feed.Feed, publisherDID *did.DID) *FeedHandler {
ctx := context.Background()
feedsMap := make(map[string]feed.Feed)
for _, feed := range feeds {
feedsMap[feed.GetName(ctx)] = feed
}
feedsOutput := make([]*bsky.FeedDescribeFeedGenerator_Feed, len(feeds))
for i, f := range feeds {
feedsOutput[i] = utils.ToPtr(f.Describe(ctx))
}
return &FeedHandler{
feedsOutput: feedsOutput,
feedsMap: feedsMap,
publisherDID: publisherDID,
}
}
func (handler *FeedHandler) DescribeFeeds(w http.ResponseWriter, r *http.Request) {
response.JSON(w, 200, bsky.FeedDescribeFeedGenerator_Output{
Did: handler.publisherDID.String(),
Feeds: handler.feedsOutput,
})
}
func (handler *FeedHandler) GetFeedSkeleton(w http.ResponseWriter, r *http.Request) {
userDID, _ := r.Context().Value(middleware.UserDIDKey).(string)
feedQuery := r.URL.Query().Get("feed")
if feedQuery == "" {
response.JSON(w, 400, response.M{"error": "feed query parameter is required"})
return
}
feedNameStartingIndex := strings.LastIndex(feedQuery, "/")
if feedNameStartingIndex == -1 {
response.JSON(w, 400, response.M{"error": "feed query parameter is invalid"})
}
feedName := feedQuery[feedNameStartingIndex+1:]
feed := handler.feedsMap[feedName]
if feed == nil {
response.JSON(w, 400, response.M{"error": "feed not found"})
return
}
limitQuery := r.URL.Query().Get("limit")
var limit int64 = 50
if limitQuery != "" {
parsedLimit, err := strconv.ParseInt(limitQuery, 10, 64)
if err == nil && parsedLimit >= 1 && parsedLimit <= 100 {
limit = parsedLimit
}
}
cursor := r.URL.Query().Get("cursor")
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
feedItems, newCursor, err := feed.GetPage(ctx, userDID, limit, cursor)
if err != nil {
if err == types.ErrInternal {
response.JSON500(w)
return
}
response.JSON(w, 400, response.M{"error": err.Error()})
return
}
response.JSON(w, 200, bsky.FeedGetFeedSkeleton_Output{
Feed: feedItems,
Cursor: newCursor,
})
}