feat: add groups management with CRUD operations and policy target assignment
Add Group type with id, name, description, members array and optional user_ids field. Add name field to policy targets for display. Add groups API client methods for list, create, update and delete operations. Add GroupsPage component with create form, edit modal, member selection and table view. Add groups route and navigation item to Layout. Add reusable Modal component with title, subtitle and close handler. Update
This commit is contained in:
@@ -52,6 +52,11 @@ func (r *PGRepository) List(ctx context.Context) ([]Policy, error) {
|
||||
if err := rows.Scan(&item.ID, &item.Name, &item.Description, &item.Priority, &item.Effect, &item.FullTunnel, &item.IsActive, &item.Destinations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets, err := r.listTargets(ctx, item.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Targets = targets
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, rows.Err()
|
||||
@@ -95,18 +100,7 @@ func (r *PGRepository) Create(ctx context.Context, input CreateRequest, createdB
|
||||
return Policy{}, err
|
||||
}
|
||||
|
||||
inputPolicy := Policy{
|
||||
ID: policyID,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
Priority: input.Priority,
|
||||
Effect: input.Effect,
|
||||
FullTunnel: input.FullTunnel,
|
||||
IsActive: true,
|
||||
Destinations: input.Destinations,
|
||||
Targets: input.Targets,
|
||||
}
|
||||
return inputPolicy, nil
|
||||
return r.getByID(ctx, policyID)
|
||||
}
|
||||
|
||||
func (r *PGRepository) Update(ctx context.Context, policyID uuid.UUID, input UpdateRequest) (Policy, error) {
|
||||
@@ -164,19 +158,7 @@ func (r *PGRepository) Update(ctx context.Context, policyID uuid.UUID, input Upd
|
||||
return Policy{}, err
|
||||
}
|
||||
|
||||
items, err := r.List(ctx)
|
||||
if err != nil {
|
||||
return Policy{}, err
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.ID == policyID {
|
||||
if input.Targets != nil {
|
||||
item.Targets = input.Targets
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
return Policy{}, errors.New("policy not found after update")
|
||||
return r.getByID(ctx, policyID)
|
||||
}
|
||||
|
||||
func (r *PGRepository) Delete(ctx context.Context, policyID uuid.UUID) error {
|
||||
@@ -195,6 +177,10 @@ func (r *PGRepository) ResolveDestinations(ctx context.Context, userID uuid.UUID
|
||||
and p.effect = 'allow'
|
||||
and (
|
||||
(pt.target_type = 'user' and pt.target_id = $1)
|
||||
or (pt.target_type = 'group' and exists (
|
||||
select 1 from group_memberships gm
|
||||
where gm.group_id = pt.target_id and gm.user_id = $1
|
||||
))
|
||||
`
|
||||
args := []any{userID}
|
||||
if deviceID != nil {
|
||||
@@ -219,3 +205,46 @@ func (r *PGRepository) ResolveDestinations(ctx context.Context, userID uuid.UUID
|
||||
}
|
||||
return destinations, rows.Err()
|
||||
}
|
||||
|
||||
func (r *PGRepository) getByID(ctx context.Context, policyID uuid.UUID) (Policy, error) {
|
||||
items, err := r.List(ctx)
|
||||
if err != nil {
|
||||
return Policy{}, err
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.ID == policyID {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
return Policy{}, errors.New("policy not found")
|
||||
}
|
||||
|
||||
func (r *PGRepository) listTargets(ctx context.Context, policyID uuid.UUID) ([]Target, error) {
|
||||
rows, err := r.db.Query(ctx, `
|
||||
select
|
||||
pt.target_type,
|
||||
pt.target_id,
|
||||
coalesce(u.username, g.name, d.name, '')
|
||||
from policy_targets pt
|
||||
left join users u on pt.target_type = 'user' and u.id = pt.target_id and u.deleted_at is null
|
||||
left join groups g on pt.target_type = 'group' and g.id = pt.target_id and g.deleted_at is null
|
||||
left join devices d on pt.target_type = 'device' and d.id = pt.target_id and d.deleted_at is null
|
||||
where pt.policy_id = $1
|
||||
order by pt.created_at asc
|
||||
`, policyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []Target
|
||||
for rows.Next() {
|
||||
var item Target
|
||||
if err := rows.Scan(&item.Type, &item.ID, &item.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, rows.Err()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import "github.com/google/uuid"
|
||||
type Target struct {
|
||||
Type string `json:"type"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
|
||||
Reference in New Issue
Block a user