implement core foundation

This commit is contained in:
talksik
2026-04-14 11:00:46 -07:00
parent aff18d82db
commit 279a3e52c2
25 changed files with 1455 additions and 62 deletions
+54 -25
View File
@@ -5,10 +5,20 @@ import (
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
"go.jetify.com/typeid"
)
// dbtx is the subset of pgx's query API shared by *pgxpool.Pool and pgx.Tx.
// Used by repository helpers that the service layer may run either standalone
// (against the pool) or inside a transaction.
type dbtx interface {
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
var errNotFound = errors.New("not found")
type networkIDPrefix struct{}
@@ -30,9 +40,10 @@ type repository interface {
getByID(ctx context.Context, id string) (*Network, error)
updateName(ctx context.Context, id, name string) error
delete(ctx context.Context, id string) error
addMember(ctx context.Context, networkID, humanId string) error
removeMember(ctx context.Context, networkID, humanId string) error
addMember(ctx context.Context, db dbtx, networkID, humanId string) error
removeMember(ctx context.Context, db dbtx, networkID, humanId string) error
getMemberHumanIds(ctx context.Context, networkID string) ([]string, error)
countSeats(ctx context.Context, db dbtx, networkID string) (int, error)
getNetworksForHuman(ctx context.Context, humanId string) ([]*Network, error)
isMember(ctx context.Context, networkID, humanId string) (bool, error)
listAll(ctx context.Context) ([]*Network, error)
@@ -41,7 +52,15 @@ type repository interface {
createInvitation(ctx context.Context, networkID, email string) error
getInvitationsByEmail(ctx context.Context, email string) ([]*Invitation, error)
getInvitationsByNetwork(ctx context.Context, networkID string) ([]*Invitation, error)
deleteInvitation(ctx context.Context, networkID, email string) error
deleteInvitation(ctx context.Context, db dbtx, networkID, email string) error
}
// networkColumns lists every column selected when hydrating a Network.
// Centralized to keep SELECTs and Scan() calls in sync.
const networkColumns = `id, name, admin_human_id, created_at`
func scanNetwork(row pgx.Row, n *Network) error {
return row.Scan(&n.ID, &n.Name, &n.AdminHumanId, &n.CreatedAt)
}
type repositoryImpl struct {
@@ -59,12 +78,12 @@ func (r *repositoryImpl) create(ctx context.Context, name, adminHumanId string)
}
var n Network
err = r.pool.QueryRow(ctx,
row := r.pool.QueryRow(ctx,
`INSERT INTO networks (id, name, admin_human_id) VALUES ($1, $2, $3)
RETURNING id, name, admin_human_id, created_at`,
RETURNING `+networkColumns,
id.String(), name, adminHumanId,
).Scan(&n.ID, &n.Name, &n.AdminHumanId, &n.CreatedAt)
if err != nil {
)
if err := scanNetwork(row, &n); err != nil {
return nil, err
}
@@ -74,21 +93,22 @@ func (r *repositoryImpl) create(ctx context.Context, name, adminHumanId string)
func (r *repositoryImpl) getByID(ctx context.Context, id string) (*Network, error) {
var n Network
err := r.pool.QueryRow(ctx,
`SELECT id, name, admin_human_id, created_at FROM networks WHERE id = $1`,
row := r.pool.QueryRow(ctx,
`SELECT `+networkColumns+` FROM networks WHERE id = $1`,
id,
).Scan(&n.ID, &n.Name, &n.AdminHumanId, &n.CreatedAt)
if err != nil {
)
if err := scanNetwork(row, &n); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, errNotFound
}
return nil, err
}
n.MemberHumanIds, err = r.getMemberHumanIds(ctx, id)
memberIds, err := r.getMemberHumanIds(ctx, id)
if err != nil {
return nil, err
}
n.MemberHumanIds = memberIds
return &n, nil
}
@@ -118,8 +138,8 @@ func (r *repositoryImpl) delete(ctx context.Context, id string) error {
return nil
}
func (r *repositoryImpl) addMember(ctx context.Context, networkID, humanId string) error {
_, err := r.pool.Exec(ctx,
func (r *repositoryImpl) addMember(ctx context.Context, db dbtx, networkID, humanId string) error {
_, err := db.Exec(ctx,
`INSERT INTO network_members (network_id, human_id) VALUES ($1, $2)
ON CONFLICT (network_id, human_id) DO NOTHING`,
networkID, humanId,
@@ -127,14 +147,23 @@ func (r *repositoryImpl) addMember(ctx context.Context, networkID, humanId strin
return err
}
func (r *repositoryImpl) removeMember(ctx context.Context, networkID, humanId string) error {
_, err := r.pool.Exec(ctx,
func (r *repositoryImpl) removeMember(ctx context.Context, db dbtx, networkID, humanId string) error {
_, err := db.Exec(ctx,
`DELETE FROM network_members WHERE network_id = $1 AND human_id = $2`,
networkID, humanId,
)
return err
}
func (r *repositoryImpl) countSeats(ctx context.Context, db dbtx, networkID string) (int, error) {
var count int
err := db.QueryRow(ctx,
`SELECT COUNT(*) FROM network_members WHERE network_id = $1`,
networkID,
).Scan(&count)
return count, err
}
func (r *repositoryImpl) getMemberHumanIds(ctx context.Context, networkID string) ([]string, error) {
rows, err := r.pool.Query(ctx,
`SELECT human_id FROM network_members WHERE network_id = $1`,
@@ -158,10 +187,10 @@ func (r *repositoryImpl) getMemberHumanIds(ctx context.Context, networkID string
func (r *repositoryImpl) getNetworksForHuman(ctx context.Context, humanId string) ([]*Network, error) {
rows, err := r.pool.Query(ctx,
`SELECT n.id, n.name, n.admin_human_id, n.created_at
FROM networks n
WHERE n.admin_human_id = $1
OR EXISTS (SELECT 1 FROM network_members nm WHERE nm.network_id = n.id AND nm.human_id = $1)`,
`SELECT `+networkColumns+`
FROM networks
WHERE admin_human_id = $1
OR EXISTS (SELECT 1 FROM network_members nm WHERE nm.network_id = id AND nm.human_id = $1)`,
humanId,
)
if err != nil {
@@ -172,7 +201,7 @@ func (r *repositoryImpl) getNetworksForHuman(ctx context.Context, humanId string
var networks []*Network
for rows.Next() {
var n Network
if err := rows.Scan(&n.ID, &n.Name, &n.AdminHumanId, &n.CreatedAt); err != nil {
if err := scanNetwork(rows, &n); err != nil {
return nil, err
}
networks = append(networks, &n)
@@ -205,7 +234,7 @@ func (r *repositoryImpl) isMember(ctx context.Context, networkID, humanId string
func (r *repositoryImpl) listAll(ctx context.Context) ([]*Network, error) {
rows, err := r.pool.Query(ctx,
`SELECT id, name, admin_human_id, created_at FROM networks`,
`SELECT `+networkColumns+` FROM networks`,
)
if err != nil {
return nil, err
@@ -215,7 +244,7 @@ func (r *repositoryImpl) listAll(ctx context.Context) ([]*Network, error) {
var networks []*Network
for rows.Next() {
var n Network
if err := rows.Scan(&n.ID, &n.Name, &n.AdminHumanId, &n.CreatedAt); err != nil {
if err := scanNetwork(rows, &n); err != nil {
return nil, err
}
networks = append(networks, &n)
@@ -293,8 +322,8 @@ func (r *repositoryImpl) getInvitationsByNetwork(ctx context.Context, networkID
return invitations, rows.Err()
}
func (r *repositoryImpl) deleteInvitation(ctx context.Context, networkID, email string) error {
_, err := r.pool.Exec(ctx,
func (r *repositoryImpl) deleteInvitation(ctx context.Context, db dbtx, networkID, email string) error {
_, err := db.Exec(ctx,
`DELETE FROM network_invitations WHERE network_id = $1 AND email = $2`,
networkID, email,
)