Address follow-up review: cheaper avatars, list limits, less chatter.
Switch avatar resize to ApproxBiLinear, cap hunt/profile/admin list queries, drop redundant admin/profile lookups, dedupe CI on app PRs, and refresh stale todo.md notes.
This commit is contained in:
@@ -92,6 +92,9 @@ func (m *Memory) ListUsers(_ context.Context) ([]User, error) {
|
||||
out = append(out, *u)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt < out[j].CreatedAt })
|
||||
if len(out) > AdminUsersLimit {
|
||||
out = out[:AdminUsersLimit]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -223,6 +226,9 @@ func (m *Memory) ListHunt(_ context.Context, huntDate, viewerID string) ([]Ranke
|
||||
}
|
||||
return out[i].CreatedAt < out[j].CreatedAt
|
||||
})
|
||||
if len(out) > HuntListLimit {
|
||||
out = out[:HuntListLimit]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -237,6 +243,9 @@ func (m *Memory) ListQuestionsByAuthor(_ context.Context, authorID string) ([]Ra
|
||||
out = append(out, m.annotate(q, ""))
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt > out[j].CreatedAt })
|
||||
if len(out) > ProfileListLimit {
|
||||
out = out[:ProfileListLimit]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -257,6 +266,9 @@ func (m *Memory) ListQuestionsAnsweredBy(_ context.Context, adminID string) ([]R
|
||||
out = append(out, rq)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt > out[j].CreatedAt })
|
||||
if len(out) > ProfileListLimit {
|
||||
out = out[:ProfileListLimit]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package store
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeUsername(t *testing.T) {
|
||||
if got := NormalizeUsername(" Alice_1 "); got != "alice_1" {
|
||||
@@ -14,20 +17,7 @@ func TestPostgresDSNDefaultsSSLMode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !containsAny(out, "sslmode=verify-full") {
|
||||
if !strings.Contains(out, "sslmode=verify-full") {
|
||||
t.Fatalf("missing default sslmode: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func containsAny(s string, parts ...string) bool {
|
||||
for _, p := range parts {
|
||||
if len(p) > 0 && (len(s) >= len(p)) {
|
||||
for i := 0; i+len(p) <= len(s); i++ {
|
||||
if s[i:i+len(p)] == p {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ func ListHunt(ctx context.Context, db *sql.DB, huntDate, viewerID string) ([]Ran
|
||||
rows, err := sqlc.New(db).ListHunt(ctx, sqlc.ListHuntParams{
|
||||
ViewerID: viewerID,
|
||||
HuntDate: huntDate,
|
||||
RowLimit: HuntListLimit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -125,7 +126,10 @@ func GetQuestion(ctx context.Context, db *sql.DB, id, viewerID string) (*RankedQ
|
||||
}
|
||||
|
||||
func ListQuestionsByAuthor(ctx context.Context, db *sql.DB, authorID string) ([]RankedQuestion, error) {
|
||||
rows, err := sqlc.New(db).ListQuestionsByAuthor(ctx, authorID)
|
||||
rows, err := sqlc.New(db).ListQuestionsByAuthor(ctx, sqlc.ListQuestionsByAuthorParams{
|
||||
AuthorID: authorID,
|
||||
RowLimit: ProfileListLimit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,7 +141,10 @@ func ListQuestionsByAuthor(ctx context.Context, db *sql.DB, authorID string) ([]
|
||||
}
|
||||
|
||||
func ListQuestionsAnsweredBy(ctx context.Context, db *sql.DB, adminID string) ([]RankedQuestion, error) {
|
||||
rows, err := sqlc.New(db).ListQuestionsAnsweredBy(ctx, adminID)
|
||||
rows, err := sqlc.New(db).ListQuestionsAnsweredBy(ctx, sqlc.ListQuestionsAnsweredByParams{
|
||||
AdminID: adminID,
|
||||
RowLimit: ProfileListLimit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -117,11 +117,13 @@ LEFT JOIN answers a ON a.question_id = q.id
|
||||
WHERE q.hunt_date = $2 AND q.hidden = 0
|
||||
GROUP BY q.id, q.author_id, u.name, q.title, q.body, q.city, q.hunt_date, q.hidden, q.created_at, a.question_id
|
||||
ORDER BY score DESC, q.created_at ASC
|
||||
LIMIT $3
|
||||
`
|
||||
|
||||
type ListHuntParams struct {
|
||||
ViewerID string
|
||||
HuntDate string
|
||||
RowLimit int32
|
||||
}
|
||||
|
||||
type ListHuntRow struct {
|
||||
@@ -140,7 +142,7 @@ type ListHuntRow struct {
|
||||
}
|
||||
|
||||
func (q *Queries) ListHunt(ctx context.Context, arg ListHuntParams) ([]ListHuntRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listHunt, arg.ViewerID, arg.HuntDate)
|
||||
rows, err := q.db.QueryContext(ctx, listHunt, arg.ViewerID, arg.HuntDate, arg.RowLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -185,8 +187,14 @@ JOIN questions q ON q.id = ans.question_id
|
||||
JOIN users u ON u.id = q.author_id
|
||||
WHERE ans.author_id = $1 AND q.hidden = 0
|
||||
ORDER BY ans.updated_at DESC
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListQuestionsAnsweredByParams struct {
|
||||
AdminID string
|
||||
RowLimit int32
|
||||
}
|
||||
|
||||
type ListQuestionsAnsweredByRow struct {
|
||||
ID string
|
||||
AuthorID string
|
||||
@@ -202,8 +210,8 @@ type ListQuestionsAnsweredByRow struct {
|
||||
UserVote int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListQuestionsAnsweredBy(ctx context.Context, adminID string) ([]ListQuestionsAnsweredByRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listQuestionsAnsweredBy, adminID)
|
||||
func (q *Queries) ListQuestionsAnsweredBy(ctx context.Context, arg ListQuestionsAnsweredByParams) ([]ListQuestionsAnsweredByRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listQuestionsAnsweredBy, arg.AdminID, arg.RowLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -248,8 +256,14 @@ JOIN users u ON u.id = q.author_id
|
||||
LEFT JOIN answers a ON a.question_id = q.id
|
||||
WHERE q.author_id = $1 AND q.hidden = 0
|
||||
ORDER BY q.created_at DESC
|
||||
LIMIT $2
|
||||
`
|
||||
|
||||
type ListQuestionsByAuthorParams struct {
|
||||
AuthorID string
|
||||
RowLimit int32
|
||||
}
|
||||
|
||||
type ListQuestionsByAuthorRow struct {
|
||||
ID string
|
||||
AuthorID string
|
||||
@@ -265,8 +279,8 @@ type ListQuestionsByAuthorRow struct {
|
||||
UserVote int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListQuestionsByAuthor(ctx context.Context, authorID string) ([]ListQuestionsByAuthorRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listQuestionsByAuthor, authorID)
|
||||
func (q *Queries) ListQuestionsByAuthor(ctx context.Context, arg ListQuestionsByAuthorParams) ([]ListQuestionsByAuthorRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listQuestionsByAuthor, arg.AuthorID, arg.RowLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ const listUsers = `-- name: ListUsers :many
|
||||
SELECT id, username, name, role, avatar_url, state, created_at
|
||||
FROM users
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
type ListUsersRow struct {
|
||||
@@ -142,8 +143,8 @@ type ListUsersRow struct {
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listUsers)
|
||||
func (q *Queries) ListUsers(ctx context.Context, rowLimit int32) ([]ListUsersRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listUsers, rowLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ package store
|
||||
|
||||
import "context"
|
||||
|
||||
// List row caps keep hunt/profile/admin pages bounded.
|
||||
const (
|
||||
HuntListLimit = 100
|
||||
ProfileListLimit = 50
|
||||
AdminUsersLimit = 200
|
||||
)
|
||||
|
||||
// Store is the application persistence API used by the web layer.
|
||||
type Store interface {
|
||||
CreateUser(ctx context.Context, u *User) error
|
||||
|
||||
@@ -172,7 +172,7 @@ func CountAdmins(ctx context.Context, db *sql.DB) (int, error) {
|
||||
}
|
||||
|
||||
func ListUsers(ctx context.Context, db *sql.DB) ([]User, error) {
|
||||
rows, err := sqlc.New(db).ListUsers(ctx)
|
||||
rows, err := sqlc.New(db).ListUsers(ctx, AdminUsersLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user