migrate orion repo into monorepo structure

This commit is contained in:
talksik
2026-02-21 08:48:34 -08:00
parent 144596fcaa
commit b5f90709de
91 changed files with 19775 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
package network
import "time"
type Network struct {
ID string
Name string
AdminEmail string
MemberEmails []string
OpenStreamCapacity int
OpenStreamCount int
CreatedAt time.Time
}
+252
View File
@@ -0,0 +1,252 @@
package network
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"go.jetify.com/typeid"
)
var errNotFound = errors.New("not found")
type networkIDPrefix struct{}
func (networkIDPrefix) Prefix() string { return "net" }
type networkID struct {
typeid.TypeID[networkIDPrefix]
}
func newNetworkID() (networkID, error) {
return typeid.New[networkID]()
}
var errCapacityExceeded = errors.New("capacity exceeded")
type repository interface {
create(ctx context.Context, name, adminEmail string) (*Network, error)
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, email string) error
removeMember(ctx context.Context, networkID, email string) error
getMemberEmails(ctx context.Context, networkID string) ([]string, error)
getNetworksForEmail(ctx context.Context, email string) ([]*Network, error)
isMember(ctx context.Context, networkID, email string) (bool, error)
setOpenStreamCapacity(ctx context.Context, id string, capacity int) error
incrementOpenStreamCount(ctx context.Context, id string) error
decrementOpenStreamCount(ctx context.Context, id string) error
}
type repositoryImpl struct {
pool *pgxpool.Pool
}
func newRepository(pool *pgxpool.Pool) repository {
return &repositoryImpl{pool: pool}
}
func (r *repositoryImpl) create(ctx context.Context, name, adminEmail string) (*Network, error) {
id, err := newNetworkID()
if err != nil {
return nil, err
}
var n Network
err = r.pool.QueryRow(ctx,
`INSERT INTO networks (id, name, admin_email) VALUES ($1, $2, $3)
RETURNING id, name, admin_email, open_stream_capacity, open_stream_count, created_at`,
id.String(), name, adminEmail,
).Scan(&n.ID, &n.Name, &n.AdminEmail, &n.OpenStreamCapacity, &n.OpenStreamCount, &n.CreatedAt)
if err != nil {
return nil, err
}
n.MemberEmails = []string{}
return &n, nil
}
func (r *repositoryImpl) getByID(ctx context.Context, id string) (*Network, error) {
var n Network
err := r.pool.QueryRow(ctx,
`SELECT id, name, admin_email, open_stream_capacity, open_stream_count, created_at FROM networks WHERE id = $1`,
id,
).Scan(&n.ID, &n.Name, &n.AdminEmail, &n.OpenStreamCapacity, &n.OpenStreamCount, &n.CreatedAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, errNotFound
}
return nil, err
}
n.MemberEmails, err = r.getMemberEmails(ctx, id)
if err != nil {
return nil, err
}
return &n, nil
}
func (r *repositoryImpl) updateName(ctx context.Context, id, name string) error {
result, err := r.pool.Exec(ctx,
`UPDATE networks SET name = $1 WHERE id = $2`,
name, id,
)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return errNotFound
}
return nil
}
func (r *repositoryImpl) delete(ctx context.Context, id string) error {
result, err := r.pool.Exec(ctx, `DELETE FROM networks WHERE id = $1`, id)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return errNotFound
}
return nil
}
func (r *repositoryImpl) addMember(ctx context.Context, networkID, email string) error {
_, err := r.pool.Exec(ctx,
`INSERT INTO network_members (network_id, email) VALUES ($1, $2)
ON CONFLICT (network_id, email) DO NOTHING`,
networkID, email,
)
return err
}
func (r *repositoryImpl) removeMember(ctx context.Context, networkID, email string) error {
_, err := r.pool.Exec(ctx,
`DELETE FROM network_members WHERE network_id = $1 AND email = $2`,
networkID, email,
)
return err
}
func (r *repositoryImpl) getMemberEmails(ctx context.Context, networkID string) ([]string, error) {
rows, err := r.pool.Query(ctx,
`SELECT email FROM network_members WHERE network_id = $1`,
networkID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var emails []string
for rows.Next() {
var email string
if err := rows.Scan(&email); err != nil {
return nil, err
}
emails = append(emails, email)
}
return emails, rows.Err()
}
func (r *repositoryImpl) getNetworksForEmail(ctx context.Context, email string) ([]*Network, error) {
rows, err := r.pool.Query(ctx,
`SELECT n.id, n.name, n.admin_email, n.open_stream_capacity, n.open_stream_count, n.created_at
FROM networks n
WHERE n.admin_email = $1
OR EXISTS (SELECT 1 FROM network_members nm WHERE nm.network_id = n.id AND nm.email = $1)`,
email,
)
if err != nil {
return nil, err
}
defer rows.Close()
var networks []*Network
for rows.Next() {
var n Network
if err := rows.Scan(&n.ID, &n.Name, &n.AdminEmail, &n.OpenStreamCapacity, &n.OpenStreamCount, &n.CreatedAt); err != nil {
return nil, err
}
networks = append(networks, &n)
}
if err := rows.Err(); err != nil {
return nil, err
}
for _, n := range networks {
n.MemberEmails, err = r.getMemberEmails(ctx, n.ID)
if err != nil {
return nil, err
}
}
return networks, nil
}
func (r *repositoryImpl) isMember(ctx context.Context, networkID, email string) (bool, error) {
var isMember bool
err := r.pool.QueryRow(ctx, `
SELECT EXISTS(
SELECT 1 FROM networks n
LEFT JOIN network_members nm ON nm.network_id = n.id AND nm.email = $2
WHERE n.id = $1 AND (n.admin_email = $2 OR nm.email IS NOT NULL)
)
`, networkID, email).Scan(&isMember)
return isMember, err
}
func (r *repositoryImpl) setOpenStreamCapacity(ctx context.Context, id string, capacity int) error {
result, err := r.pool.Exec(ctx,
`UPDATE networks SET open_stream_capacity = $1 WHERE id = $2`,
capacity, id,
)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return errNotFound
}
return nil
}
func (r *repositoryImpl) incrementOpenStreamCount(ctx context.Context, id string) error {
result, err := r.pool.Exec(ctx,
`UPDATE networks SET open_stream_count = open_stream_count + 1
WHERE id = $1 AND open_stream_count < open_stream_capacity`,
id,
)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
// Check if network exists vs capacity exceeded
var exists bool
err := r.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM networks WHERE id = $1)`, id).Scan(&exists)
if err != nil {
return err
}
if !exists {
return errNotFound
}
return errCapacityExceeded
}
return nil
}
func (r *repositoryImpl) decrementOpenStreamCount(ctx context.Context, id string) error {
result, err := r.pool.Exec(ctx,
`UPDATE networks SET open_stream_count = GREATEST(0, open_stream_count - 1) WHERE id = $1`,
id,
)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return errNotFound
}
return nil
}
+155
View File
@@ -0,0 +1,155 @@
package network
import (
"context"
"errors"
"fmt"
"strings"
"github.com/flowy-live/llink/internal/utils"
"github.com/jackc/pgx/v5/pgxpool"
)
var ErrNotFound = errors.New("network not found")
var ErrInvalidName = errors.New("name cannot be empty")
var ErrCapacityExceeded = errors.New("active stream capacity exceeded")
type Service interface {
// Create creates a network and adds adminEmail as the first member. Returns ErrInvalidName if name is empty.
Create(ctx context.Context, name, adminEmail string) (*Network, error)
// GetByID returns ErrNotFound if network doesn't exist.
GetByID(ctx context.Context, id string) (*Network, error)
// SetName returns ErrNotFound or ErrInvalidName.
SetName(ctx context.Context, id, name string) error
AddMembers(ctx context.Context, networkID string, emails []string) error
RemoveMember(ctx context.Context, networkID, email string) error
ListForEmail(ctx context.Context, email string) ([]*Network, error)
IsMember(ctx context.Context, networkID, email string) (bool, error)
// SetOpenStreamCapacity sets the max open streams for a network. Returns ErrNotFound.
SetOpenStreamCapacity(ctx context.Context, networkID string, capacity int) error
// IncrementOpenStreamCount returns ErrNotFound or ErrCapacityExceeded.
IncrementOpenStreamCount(ctx context.Context, networkID string) error
// DecrementOpenStreamCount returns ErrNotFound.
DecrementOpenStreamCount(ctx context.Context, networkID string) error
}
type serviceImpl struct {
repo repository
}
func NewService(pool *pgxpool.Pool) Service {
return &serviceImpl{repo: newRepository(pool)}
}
func (s *serviceImpl) Create(ctx context.Context, name, adminEmail string) (*Network, error) {
name = strings.TrimSpace(name)
if name == "" {
return nil, ErrInvalidName
}
adminEmail, err := utils.NormalizeEmail(adminEmail)
if err != nil {
return nil, err
}
network, err := s.repo.create(ctx, name, adminEmail)
if err != nil {
return nil, err
}
err = s.AddMembers(ctx, network.ID, []string{adminEmail})
if err != nil {
return nil, err
}
return network, nil
}
func (s *serviceImpl) GetByID(ctx context.Context, id string) (*Network, error) {
n, err := s.repo.getByID(ctx, id)
if errors.Is(err, errNotFound) {
return nil, ErrNotFound
}
return n, err
}
func (s *serviceImpl) SetName(ctx context.Context, id, name string) error {
name = strings.TrimSpace(name)
if name == "" {
return ErrInvalidName
}
err := s.repo.updateName(ctx, id, name)
if errors.Is(err, errNotFound) {
return ErrNotFound
}
return err
}
func (s *serviceImpl) AddMembers(ctx context.Context, networkID string, emails []string) error {
for _, email := range emails {
normalized, err := utils.NormalizeEmail(email)
if err != nil {
return fmt.Errorf("invalid email: %w", err)
}
if err := s.repo.addMember(ctx, networkID, normalized); err != nil {
return err
}
}
return nil
}
func (s *serviceImpl) RemoveMember(ctx context.Context, networkID, email string) error {
email, err := utils.NormalizeEmail(email)
if err != nil {
return fmt.Errorf("invalid email: %w", err)
}
return s.repo.removeMember(ctx, networkID, email)
}
func (s *serviceImpl) ListForEmail(ctx context.Context, email string) ([]*Network, error) {
email, err := utils.NormalizeEmail(email)
if err != nil {
return nil, fmt.Errorf("invalid email: %w", err)
}
return s.repo.getNetworksForEmail(ctx, email)
}
func (s *serviceImpl) IsMember(ctx context.Context, networkID, email string) (bool, error) {
email, err := utils.NormalizeEmail(email)
if err != nil {
return false, err
}
return s.repo.isMember(ctx, networkID, email)
}
func (s *serviceImpl) SetOpenStreamCapacity(ctx context.Context, networkID string, capacity int) error {
if capacity < 0 {
capacity = 0
}
err := s.repo.setOpenStreamCapacity(ctx, networkID, capacity)
if errors.Is(err, errNotFound) {
return ErrNotFound
}
return err
}
func (s *serviceImpl) IncrementOpenStreamCount(ctx context.Context, networkID string) error {
err := s.repo.incrementOpenStreamCount(ctx, networkID)
if errors.Is(err, errNotFound) {
return ErrNotFound
}
if errors.Is(err, errCapacityExceeded) {
return ErrCapacityExceeded
}
return err
}
func (s *serviceImpl) DecrementOpenStreamCount(ctx context.Context, networkID string) error {
err := s.repo.decrementOpenStreamCount(ctx, networkID)
if errors.Is(err, errNotFound) {
return ErrNotFound
}
return err
}
+109
View File
@@ -0,0 +1,109 @@
package network_test
import (
"context"
"os"
"testing"
"github.com/flowy-live/llink/internal/network"
"github.com/flowy-live/llink/internal/testhelper"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
)
var dbPool *pgxpool.Pool
func TestMain(m *testing.M) {
dbPool = testhelper.SetupTestDB()
defer testhelper.TeardownTestDB()
ret := m.Run()
os.Exit(ret)
}
func TestNetworkService(t *testing.T) {
ctx := context.Background()
svc := network.NewService(dbPool)
// Test Create
createdNetwork, err := svc.Create(ctx, "Test Network", "[email protected]")
assert.NoError(t, err)
assert.NotEmpty(t, createdNetwork.ID)
assert.Equal(t, "Test Network", createdNetwork.Name)
assert.Equal(t, "[email protected]", createdNetwork.AdminEmail)
assert.NotZero(t, createdNetwork.CreatedAt)
// Test GetByID
foundNetwork, err := svc.GetByID(ctx, createdNetwork.ID)
assert.NoError(t, err)
assert.Equal(t, createdNetwork.ID, foundNetwork.ID)
assert.Equal(t, createdNetwork.Name, foundNetwork.Name)
assert.Equal(t, createdNetwork.AdminEmail, foundNetwork.AdminEmail)
// Test GetByID with non-existent id
_, err = svc.GetByID(ctx, "network_nonexistent")
assert.Error(t, err)
assert.ErrorIs(t, err, network.ErrNotFound)
// Test SetName
err = svc.SetName(ctx, createdNetwork.ID, "Updated Network Name")
assert.NoError(t, err)
// Verify name was updated
updatedNetwork, err := svc.GetByID(ctx, createdNetwork.ID)
assert.NoError(t, err)
assert.Equal(t, "Updated Network Name", updatedNetwork.Name)
// Test SetName with non-existent id
err = svc.SetName(ctx, "network_nonexistent", "New Name")
assert.Error(t, err)
assert.ErrorIs(t, err, network.ErrNotFound)
// Test AddMembers
err = svc.AddMembers(ctx, createdNetwork.ID, []string{"[email protected]", "[email protected]"})
assert.NoError(t, err)
// Test ListForEmail - should find network for admin
networks, err := svc.ListForEmail(ctx, "[email protected]")
assert.NoError(t, err)
assert.Len(t, networks, 1)
assert.Equal(t, createdNetwork.ID, networks[0].ID)
// Test ListForEmail - should find network for member
networks, err = svc.ListForEmail(ctx, "[email protected]")
assert.NoError(t, err)
assert.Len(t, networks, 1)
assert.Equal(t, createdNetwork.ID, networks[0].ID)
// Test ListForEmail - should return empty for non-member
networks, err = svc.ListForEmail(ctx, "[email protected]")
assert.NoError(t, err)
assert.Len(t, networks, 0)
// Test RemoveMember
err = svc.RemoveMember(ctx, createdNetwork.ID, "[email protected]")
assert.NoError(t, err)
// Verify member was removed
networks, err = svc.ListForEmail(ctx, "[email protected]")
assert.NoError(t, err)
assert.Len(t, networks, 0)
// member2 should still have access
networks, err = svc.ListForEmail(ctx, "[email protected]")
assert.NoError(t, err)
assert.Len(t, networks, 1)
// Create another network and verify ListForEmail returns multiple
network2, err := svc.Create(ctx, "Second Network", "[email protected]")
assert.NoError(t, err)
networks, err = svc.ListForEmail(ctx, "[email protected]")
assert.NoError(t, err)
assert.Len(t, networks, 2)
// Verify both networks are returned
networkIDs := []string{networks[0].ID, networks[1].ID}
assert.Contains(t, networkIDs, createdNetwork.ID)
assert.Contains(t, networkIDs, network2.ID)
}