package main import ( "context" "strings" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" ) type SqliteGrantStore struct { db dbi.DBI } var _ iam.GrantStore = (*SqliteGrantStore)(nil) func NewSqliteGrantStore(db dbi.DBI) *SqliteGrantStore { return &SqliteGrantStore{db: db} } func (s *SqliteGrantStore) Add(ctx context.Context, g iam.Grant) error { if err := g.RoleID.Valid(); err != nil { return err } _, err := s.db.Exec(ctx, ` INSERT INTO grants (principal_id, role_id) VALUES ($1, $2) ON CONFLICT (principal_id, role_id) DO NOTHING `, g.PrincipalID, g.RoleID) return err } func (s *SqliteGrantStore) Remove(ctx context.Context, g iam.Grant) error { _, err := s.db.Exec(ctx, ` DELETE FROM grants WHERE principal_id = $1 AND role_id = $2 `, g.PrincipalID, g.RoleID) return err } func (s *SqliteGrantStore) ListAll(ctx context.Context, page core.PageReq) (core.Page[iam.Grant], error) { limit := page.Limit if limit <= 0 { limit = 100 } rows, err := s.db.Query(ctx, ` SELECT principal_id, role_id FROM grants ORDER BY principal_id, role_id LIMIT $1 `, limit) if err != nil { return core.Page[iam.Grant]{}, err } defer rows.Close() var items []iam.Grant for rows.Next() { var g iam.Grant if err := rows.Scan(&g.PrincipalID, &g.RoleID); err != nil { return core.Page[iam.Grant]{}, err } items = append(items, g) } if err := rows.Err(); err != nil { return core.Page[iam.Grant]{}, err } return core.Page[iam.Grant]{Items: items}, nil } func (s *SqliteGrantStore) ListByNamespace(ctx context.Context, namespace string, page core.PageReq) (core.Page[iam.Grant], error) { limit := page.Limit if limit <= 0 { limit = 100 } pattern := escapeLikeGrant(namespace) + ".%" rows, err := s.db.Query(ctx, ` SELECT principal_id, role_id FROM grants WHERE role_id LIKE $1 ESCAPE '\' ORDER BY principal_id, role_id LIMIT $2 `, pattern, limit) if err != nil { return core.Page[iam.Grant]{}, err } defer rows.Close() var items []iam.Grant for rows.Next() { var g iam.Grant if err := rows.Scan(&g.PrincipalID, &g.RoleID); err != nil { return core.Page[iam.Grant]{}, err } items = append(items, g) } if err := rows.Err(); err != nil { return core.Page[iam.Grant]{}, err } return core.Page[iam.Grant]{Items: items}, nil } func (s *SqliteGrantStore) List(ctx context.Context, principalID core.ID, page core.PageReq) (core.Page[iam.Grant], error) { limit := page.Limit if limit <= 0 { limit = 100 } rows, err := s.db.Query(ctx, ` SELECT principal_id, role_id FROM grants WHERE principal_id = $1 ORDER BY role_id LIMIT $2 `, principalID, limit) if err != nil { return core.Page[iam.Grant]{}, err } defer rows.Close() var items []iam.Grant for rows.Next() { var g iam.Grant if err := rows.Scan(&g.PrincipalID, &g.RoleID); err != nil { return core.Page[iam.Grant]{}, err } items = append(items, g) } if err := rows.Err(); err != nil { return core.Page[iam.Grant]{}, err } return core.Page[iam.Grant]{Items: items}, nil } func escapeLikeGrant(s string) string { s = strings.ReplaceAll(s, `\`, `\\`) s = strings.ReplaceAll(s, `%`, `\%`) s = strings.ReplaceAll(s, `_`, `\_`) return s }