fix: hide suspended account
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
@@ -204,3 +205,56 @@ func TestGift_DeleteRemovesItFromEveryGallery(t *testing.T) {
|
||||
t.Errorf("the seeded reward was removed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A suspended account must not be discoverable. One query backs the people
|
||||
// search page, the mention autocomplete and the chat member pickers, so a leak
|
||||
// here surfaces in all three.
|
||||
func TestBannedUsers_AreNotDiscoverable(t *testing.T) {
|
||||
_, pg := testRewardRepo(t)
|
||||
ctx := context.Background()
|
||||
users := repository.NewUserRepo(pg)
|
||||
|
||||
// A distinctive name so the search can't match anyone else.
|
||||
marker := "Zzbanprobe" + uuid.NewString()[:6]
|
||||
active := giftUser(t, pg)
|
||||
banned := giftUser(t, pg)
|
||||
for id, name := range map[string]string{active: marker + " Active", banned: marker + " Banned"} {
|
||||
if _, err := pg.Pool.Exec(ctx, `UPDATE users SET name = $2 WHERE id = $1`, id, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := pg.Pool.Exec(ctx,
|
||||
`UPDATE users SET banned_at = now() WHERE id = $1`, banned); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
found, total, err := users.Search(ctx, marker, 20, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := map[string]bool{}
|
||||
for _, u := range found {
|
||||
ids[u.ID] = true
|
||||
}
|
||||
if !ids[active] {
|
||||
t.Error("the active account is missing from search")
|
||||
}
|
||||
if ids[banned] {
|
||||
t.Error("a suspended account is searchable — it can be @mentioned or added to a group")
|
||||
}
|
||||
// The count backs pagination, so it has to agree with the page.
|
||||
if total != len(found) {
|
||||
t.Errorf("total = %d but page returned %d — count and page WHERE disagree", total, len(found))
|
||||
}
|
||||
|
||||
// And the welcome strip, which is the most prominent placement there is.
|
||||
recent, err := users.RecentlyJoined(ctx, active, time.Now().Add(-24*time.Hour), 20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, u := range recent {
|
||||
if u.ID == banned {
|
||||
t.Error("a suspended account appears in the new-users strip")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
@@ -31,6 +32,9 @@ func NewVaniService(repo *repository.VaniRepo, logger *zap.Logger) *VaniService
|
||||
// Today returns the current day's pick, choosing (and persisting) one on the
|
||||
// first call of the day. Returns (nil, nil) only if there's genuinely no
|
||||
// content to pick.
|
||||
// ErrVaniBadKind is a kind outside verse/prose/transcript.
|
||||
var ErrVaniBadKind = errors.New("kind must be verse, prose or transcript")
|
||||
|
||||
func (s *VaniService) Today(ctx context.Context) (*model.VaniPayload, error) {
|
||||
now := time.Now().UTC()
|
||||
day := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
|
||||
@@ -75,6 +79,58 @@ func (s *VaniService) Today(ctx context.Context) (*model.VaniPayload, error) {
|
||||
|
||||
// choose tries the three content kinds in random order and returns the first
|
||||
// successful sample, so each day's pick varies across content types.
|
||||
// SetForDay pins a specific piece of content as a given day's Vāṇī.
|
||||
//
|
||||
// Takes only a kind and a content id — never a payload. The display blob is
|
||||
// rebuilt here with the same builders the random picker uses, so a pinned pick
|
||||
// is indistinguishable from an automatic one and a client cannot dictate what
|
||||
// the home page says.
|
||||
func (s *VaniService) SetForDay(ctx context.Context, day time.Time, kind, hex string) (*model.VaniPick, error) {
|
||||
day = day.UTC().Truncate(24 * time.Hour)
|
||||
|
||||
var pick *model.VaniPick
|
||||
switch kind {
|
||||
case "verse":
|
||||
v, err := s.repo.VerseByHex(ctx, hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pick = versePick(v, day)
|
||||
case "prose":
|
||||
p, err := s.repo.ProseByHex(ctx, hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pick = prosePick(p, s.repo.BookTitle(ctx, p.Book), day)
|
||||
case "transcript":
|
||||
t, err := s.repo.TranscriptByHex(ctx, hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pick = transcriptPick(t, day)
|
||||
default:
|
||||
return nil, ErrVaniBadKind
|
||||
}
|
||||
|
||||
if err := s.repo.Upsert(ctx, pick); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pick, nil
|
||||
}
|
||||
|
||||
// ClearForDay drops a pinned pick, handing the day back to the random picker.
|
||||
func (s *VaniService) ClearForDay(ctx context.Context, day time.Time) error {
|
||||
return s.repo.Clear(ctx, day.UTC().Truncate(24*time.Hour))
|
||||
}
|
||||
|
||||
// Schedule lists decided days from today onward, for the admin view.
|
||||
func (s *VaniService) Schedule(ctx context.Context, limit int) ([]model.VaniPick, error) {
|
||||
if limit <= 0 || limit > 60 {
|
||||
limit = 30
|
||||
}
|
||||
return s.repo.PicksFrom(ctx, time.Now().UTC().Truncate(24*time.Hour), limit)
|
||||
}
|
||||
|
||||
func (s *VaniService) choose(ctx context.Context, exclude map[string][]string, day time.Time) *model.VaniPick {
|
||||
kinds := []string{"verse", "prose", "transcript"}
|
||||
rand.Shuffle(len(kinds), func(i, j int) { kinds[i], kinds[j] = kinds[j], kinds[i] })
|
||||
|
||||
Reference in New Issue
Block a user