package app import ( "context" "fmt" "golang.org/x/crypto/bcrypt" ) // SubscriberWithUsers is a subscriber and its users, for the admin listing. type SubscriberWithUsers struct { Subscriber CreatedAt string Users []User } func (a *App) createSubscriber(ctx context.Context, name string) (int64, error) { var id int64 err := a.pool.QueryRow(ctx, `INSERT INTO subscribers (name) VALUES ($1) RETURNING id`, name, ).Scan(&id) return id, err } // createUser inserts a user and returns its id. An empty password creates a // "pending" account (no usable hash) that must set a password via an invite // link before it can log in. func (a *App) createUser(ctx context.Context, subscriberID int64, email, name, password string) (int64, error) { hash := "" if password != "" { if len(password) < 8 { return 0, fmt.Errorf("password must be at least 8 characters") } h, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return 0, err } hash = string(h) } var id int64 err := a.pool.QueryRow(ctx, `INSERT INTO users (subscriber_id, email, name, password_hash) VALUES ($1, $2, $3, $4) RETURNING id`, subscriberID, normEmail(email), name, hash).Scan(&id) return id, err } // listSubscribers returns every subscriber with its users, internal first. func (a *App) listSubscribers(ctx context.Context) ([]SubscriberWithUsers, error) { rows, err := a.pool.Query(ctx, `SELECT id, name, is_internal, to_char(created_at, 'YYYY-MM-DD') FROM subscribers ORDER BY is_internal DESC, id`) if err != nil { return nil, err } defer rows.Close() var subs []SubscriberWithUsers idx := map[int64]int{} for rows.Next() { var s SubscriberWithUsers if err := rows.Scan(&s.ID, &s.Name, &s.IsInternal, &s.CreatedAt); err != nil { return nil, err } idx[s.ID] = len(subs) subs = append(subs, s) } if err := rows.Err(); err != nil { return nil, err } urows, err := a.pool.Query(ctx, `SELECT id, subscriber_id, email, name FROM users ORDER BY email`) if err != nil { return nil, err } defer urows.Close() for urows.Next() { var u User if err := urows.Scan(&u.ID, &u.SubscriberID, &u.Email, &u.Name); err != nil { return nil, err } if i, ok := idx[u.SubscriberID]; ok { subs[i].Users = append(subs[i].Users, u) } } return subs, urows.Err() } func (a *App) counts(ctx context.Context) (subs, users, cat, boms int) { _ = a.pool.QueryRow(ctx, `SELECT count(*) FROM subscribers`).Scan(&subs) _ = a.pool.QueryRow(ctx, `SELECT count(*) FROM users`).Scan(&users) _ = a.pool.QueryRow(ctx, `SELECT count(*) FROM catalog`).Scan(&cat) _ = a.pool.QueryRow(ctx, `SELECT count(*) FROM boms`).Scan(&boms) return }