| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- package feed
- import (
- "context"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "hash/fnv"
- "math/rand"
- "strconv"
- "time"
- "viewer/internal/albums"
- "viewer/internal/models"
- )
- type Service struct {
- albums *albums.Service
- }
- type cursorState struct {
- Seed int64 `json:"seed"`
- Offset int `json:"offset"`
- }
- type photoRef struct {
- albumID string
- photo models.PhotoMeta
- }
- func NewService(albumsService *albums.Service) *Service {
- return &Service{albums: albumsService}
- }
- func (s *Service) Build(ctx context.Context, limit int, cursor string, seedParam string) (models.FeedResponse, error) {
- _ = ctx
- if limit <= 0 {
- limit = 80
- }
- if limit > 200 {
- limit = 200
- }
- albumsList := s.albums.AllAlbums()
- refs := make([]photoRef, 0)
- for _, a := range albumsList {
- for _, p := range a.Photos {
- refs = append(refs, photoRef{albumID: a.AlbumID, photo: p})
- }
- }
- if len(refs) == 0 {
- return models.FeedResponse{Items: []models.FeedItem{}}, nil
- }
- state, err := parseCursor(cursor)
- if err != nil {
- return models.FeedResponse{}, err
- }
- if state == nil {
- seed := parseSeed(seedParam)
- state = &cursorState{Seed: seed, Offset: 0}
- }
- r := rand.New(rand.NewSource(state.Seed))
- for i := 0; i < state.Offset; i++ {
- _ = r.Intn(len(refs))
- }
- items := make([]models.FeedItem, 0, limit)
- for i := 0; i < limit; i++ {
- idx := r.Intn(len(refs))
- ref := refs[idx]
- items = append(items, models.FeedItem{
- AlbumID: ref.albumID,
- I: ref.photo.I,
- W: ref.photo.W,
- H: ref.photo.H,
- Ratio: ref.photo.Ratio,
- Src: fmt.Sprintf("/api/image/%s/%d?mode=wall&w=480", ref.albumID, ref.photo.I),
- })
- }
- next := &cursorState{Seed: state.Seed, Offset: state.Offset + len(items)}
- nextCursor, err := encodeCursor(next)
- if err != nil {
- return models.FeedResponse{}, err
- }
- return models.FeedResponse{Items: items, NextCursor: nextCursor}, nil
- }
- func parseSeed(seed string) int64 {
- if seed == "" {
- return time.Now().UnixNano()
- }
- if n, err := strconv.ParseInt(seed, 10, 64); err == nil {
- return n
- }
- h := fnv.New64a()
- _, _ = h.Write([]byte(seed))
- return int64(h.Sum64())
- }
- func parseCursor(raw string) (*cursorState, error) {
- if raw == "" {
- return nil, nil
- }
- decoded, err := base64.RawURLEncoding.DecodeString(raw)
- if err != nil {
- return nil, fmt.Errorf("invalid cursor")
- }
- var st cursorState
- if err := json.Unmarshal(decoded, &st); err != nil {
- return nil, fmt.Errorf("invalid cursor")
- }
- if st.Offset < 0 {
- st.Offset = 0
- }
- return &st, nil
- }
- func encodeCursor(st *cursorState) (string, error) {
- b, err := json.Marshal(st)
- if err != nil {
- return "", err
- }
- return base64.RawURLEncoding.EncodeToString(b), nil
- }
|