Feat v0.2.8 team admin audit (#12)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m18s
CI/CD / test-sqlite (push) Successful in 2m40s
CI/CD / build-and-deploy (push) Successful in 1m43s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #12.
This commit is contained in:
2026-03-27 16:25:19 +00:00
committed by xcaliber
parent 82430a2cb2
commit babfe66721
12 changed files with 51 additions and 102 deletions

View File

@@ -341,8 +341,7 @@ func TruncateAll(t *testing.T) {
DB.Exec(`
INSERT INTO platform_policies (key, value) VALUES
('allow_registration', 'true'),
('default_user_active', 'false'),
('allow_team_providers', 'true')
('default_user_active', 'false')
ON CONFLICT (key) DO NOTHING
`)
} else {
@@ -356,8 +355,7 @@ func TruncateAll(t *testing.T) {
DB.Exec(`
INSERT INTO platform_policies (key, value) VALUES
('allow_registration', 'true'),
('default_user_active', 'false'),
('allow_team_providers', 'true')
('default_user_active', 'false')
ON CONFLICT (key) DO NOTHING
`)
}

View File

@@ -1,7 +1,7 @@
package handlers
// v0.38.1: Extension connection handlers — team scope.
// Methods on TeamHandler, mirrors team_providers.go.
// Extension connection handlers — team scope.
// Methods on TeamHandler.
import (
"net/http"

View File

@@ -78,15 +78,13 @@ type TeamMember struct {
Email string `json:"email,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Username string `json:"username,omitempty"`
UserRole string `json:"user_role,omitempty"`
}
// PLATFORM POLICIES
var PolicyDefaults = map[string]string{
"allow_registration": "true",
"default_user_active": "false",
"allow_team_providers": "true",
"allow_registration": "true",
"default_user_active": "false",
}
// HandleFromName generates a URL-safe @mention handle from a display name.

View File

@@ -170,10 +170,6 @@ type TeamStore interface {
// AddMemberReturningID inserts a team member and returns the row ID.
AddMemberReturningID(ctx context.Context, teamID, userID, role string) (string, error)
// HasPrivateProviderRequirement checks if a user belongs to any active team
// with the require_private_providers setting enabled.
HasPrivateProviderRequirement(ctx context.Context, userID string) (bool, error)
// MergeSettings merges a JSON string into the team's settings column.
MergeSettings(ctx context.Context, teamID, settingsJSON string) error
}

View File

@@ -19,8 +19,7 @@ func SetDB(db *sql.DB) {
}
// ── Dynamic SQL Builder ─────────────────────
// Replaces the copy-pasted addClause/addField pattern
// found in admin.go, personas.go, team_providers.go, apiconfigs.go.
// Replaces the copy-pasted addClause/addField pattern.
// UpdateBuilder constructs a dynamic UPDATE statement.
type UpdateBuilder struct {

View File

@@ -142,7 +142,7 @@ func (s *TeamStore) UpdateMemberRole(ctx context.Context, teamID, userID, role s
func (s *TeamStore) ListMembers(ctx context.Context, teamID string) ([]models.TeamMember, error) {
rows, err := DB.QueryContext(ctx, `
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at,
u.email, COALESCE(u.display_name, ''), u.username, u.role as user_role
u.email, COALESCE(u.display_name, ''), u.username
FROM team_members tm
JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = $1
@@ -156,7 +156,7 @@ func (s *TeamStore) ListMembers(ctx context.Context, teamID string) ([]models.Te
for rows.Next() {
var m models.TeamMember
err := rows.Scan(&m.ID, &m.TeamID, &m.UserID, &m.Role, &m.JoinedAt,
&m.Email, &m.DisplayName, &m.Username, &m.UserRole)
&m.Email, &m.DisplayName, &m.Username)
if err != nil {
return nil, err
}
@@ -169,12 +169,12 @@ func (s *TeamStore) GetMember(ctx context.Context, teamID, userID string) (*mode
var m models.TeamMember
err := DB.QueryRowContext(ctx, `
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at,
u.email, COALESCE(u.display_name, ''), u.username, u.role as user_role
u.email, COALESCE(u.display_name, ''), u.username
FROM team_members tm
JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = $1 AND tm.user_id = $2`, teamID, userID).Scan(
&m.ID, &m.TeamID, &m.UserID, &m.Role, &m.JoinedAt,
&m.Email, &m.DisplayName, &m.Username, &m.UserRole)
&m.Email, &m.DisplayName, &m.Username)
if err != nil {
return nil, err
}
@@ -302,19 +302,6 @@ func (s *TeamStore) AddMemberReturningID(ctx context.Context, teamID, userID, ro
return id, err
}
func (s *TeamStore) HasPrivateProviderRequirement(ctx context.Context, userID string) (bool, error) {
var has bool
err := DB.QueryRowContext(ctx, `
SELECT EXISTS(
SELECT 1 FROM team_members tm
JOIN teams t ON t.id = tm.team_id
WHERE tm.user_id = $1
AND t.is_active = true
AND t.settings->>'require_private_providers' = 'true'
)`, userID).Scan(&has)
return has, err
}
func (s *TeamStore) MergeSettings(ctx context.Context, teamID, settingsJSON string) error {
_, err := DB.ExecContext(ctx,
`UPDATE teams SET settings = COALESCE(settings, '{}'::jsonb) || $1::jsonb WHERE id = $2`,

View File

@@ -149,7 +149,7 @@ func (s *TeamStore) UpdateMemberRole(ctx context.Context, teamID, userID, role s
func (s *TeamStore) ListMembers(ctx context.Context, teamID string) ([]models.TeamMember, error) {
rows, err := DB.QueryContext(ctx, `
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at,
u.email, COALESCE(u.display_name, ''), u.username, u.role as user_role
u.email, COALESCE(u.display_name, ''), u.username
FROM team_members tm
JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = ?
@@ -163,7 +163,7 @@ func (s *TeamStore) ListMembers(ctx context.Context, teamID string) ([]models.Te
for rows.Next() {
var m models.TeamMember
err := rows.Scan(&m.ID, &m.TeamID, &m.UserID, &m.Role, &m.JoinedAt,
&m.Email, &m.DisplayName, &m.Username, &m.UserRole)
&m.Email, &m.DisplayName, &m.Username)
if err != nil {
return nil, err
}
@@ -176,12 +176,12 @@ func (s *TeamStore) GetMember(ctx context.Context, teamID, userID string) (*mode
var m models.TeamMember
err := DB.QueryRowContext(ctx, `
SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at,
u.email, COALESCE(u.display_name, ''), u.username, u.role as user_role
u.email, COALESCE(u.display_name, ''), u.username
FROM team_members tm
JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = ? AND tm.user_id = ?`, teamID, userID).Scan(
&m.ID, &m.TeamID, &m.UserID, &m.Role, &m.JoinedAt,
&m.Email, &m.DisplayName, &m.Username, &m.UserRole)
&m.Email, &m.DisplayName, &m.Username)
if err != nil {
return nil, err
}
@@ -308,19 +308,6 @@ func (s *TeamStore) AddMemberReturningID(ctx context.Context, teamID, userID, ro
return id, err
}
func (s *TeamStore) HasPrivateProviderRequirement(ctx context.Context, userID string) (bool, error) {
var has bool
err := DB.QueryRowContext(ctx, `
SELECT EXISTS(
SELECT 1 FROM team_members tm
JOIN teams t ON t.id = tm.team_id
WHERE tm.user_id = ?
AND t.is_active = 1
AND json_extract(t.settings, '$.require_private_providers') = 'true'
)`, userID).Scan(&has)
return has, err
}
func (s *TeamStore) MergeSettings(ctx context.Context, teamID, settingsJSON string) error {
_, err := DB.ExecContext(ctx,
`UPDATE teams SET settings = json_patch(COALESCE(settings, '{}'), ?) WHERE id = ?`,