spread

https://git.tonybtw.com/spread.git git://git.tonybtw.com/spread.git
2,714 bytes raw
1
package app
2
3
import (
4
	"context"
5
	"fmt"
6
7
	"golang.org/x/crypto/bcrypt"
8
)
9
10
// SubscriberWithUsers is a subscriber and its users, for the admin listing.
11
type SubscriberWithUsers struct {
12
	Subscriber
13
	CreatedAt string
14
	Users     []User
15
}
16
17
func (a *App) createSubscriber(ctx context.Context, name string) (int64, error) {
18
	var id int64
19
	err := a.pool.QueryRow(ctx,
20
		`INSERT INTO subscribers (name) VALUES ($1) RETURNING id`, name,
21
	).Scan(&id)
22
	return id, err
23
}
24
25
// createUser inserts a user and returns its id. An empty password creates a
26
// "pending" account (no usable hash) that must set a password via an invite
27
// link before it can log in.
28
func (a *App) createUser(ctx context.Context, subscriberID int64, email, name, password string) (int64, error) {
29
	hash := ""
30
	if password != "" {
31
		if len(password) < 8 {
32
			return 0, fmt.Errorf("password must be at least 8 characters")
33
		}
34
		h, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
35
		if err != nil {
36
			return 0, err
37
		}
38
		hash = string(h)
39
	}
40
	var id int64
41
	err := a.pool.QueryRow(ctx,
42
		`INSERT INTO users (subscriber_id, email, name, password_hash) VALUES ($1, $2, $3, $4) RETURNING id`,
43
		subscriberID, normEmail(email), name, hash).Scan(&id)
44
	return id, err
45
}
46
47
// listSubscribers returns every subscriber with its users, internal first.
48
func (a *App) listSubscribers(ctx context.Context) ([]SubscriberWithUsers, error) {
49
	rows, err := a.pool.Query(ctx,
50
		`SELECT id, name, is_internal, to_char(created_at, 'YYYY-MM-DD')
51
		   FROM subscribers ORDER BY is_internal DESC, id`)
52
	if err != nil {
53
		return nil, err
54
	}
55
	defer rows.Close()
56
57
	var subs []SubscriberWithUsers
58
	idx := map[int64]int{}
59
	for rows.Next() {
60
		var s SubscriberWithUsers
61
		if err := rows.Scan(&s.ID, &s.Name, &s.IsInternal, &s.CreatedAt); err != nil {
62
			return nil, err
63
		}
64
		idx[s.ID] = len(subs)
65
		subs = append(subs, s)
66
	}
67
	if err := rows.Err(); err != nil {
68
		return nil, err
69
	}
70
71
	urows, err := a.pool.Query(ctx,
72
		`SELECT id, subscriber_id, email, name FROM users ORDER BY email`)
73
	if err != nil {
74
		return nil, err
75
	}
76
	defer urows.Close()
77
	for urows.Next() {
78
		var u User
79
		if err := urows.Scan(&u.ID, &u.SubscriberID, &u.Email, &u.Name); err != nil {
80
			return nil, err
81
		}
82
		if i, ok := idx[u.SubscriberID]; ok {
83
			subs[i].Users = append(subs[i].Users, u)
84
		}
85
	}
86
	return subs, urows.Err()
87
}
88
89
func (a *App) counts(ctx context.Context) (subs, users, cat, boms int) {
90
	_ = a.pool.QueryRow(ctx, `SELECT count(*) FROM subscribers`).Scan(&subs)
91
	_ = a.pool.QueryRow(ctx, `SELECT count(*) FROM users`).Scan(&users)
92
	_ = a.pool.QueryRow(ctx, `SELECT count(*) FROM catalog`).Scan(&cat)
93
	_ = a.pool.QueryRow(ctx, `SELECT count(*) FROM boms`).Scan(&boms)
94
	return
95
}