package main import ( "context" "encoding/json" "strings" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" ) type SqliteRoleStore struct { db dbi.DBI } var _ iam.RoleStore = (*SqliteRoleStore)(nil) func NewSqliteRoleStore(db dbi.DBI) *SqliteRoleStore { return &SqliteRoleStore{db: db} } func (s *SqliteRoleStore) Save(ctx context.Context, role *iam.Role) (bool, error) { if err := role.ID.Valid(); err != nil { return false, err } actions, err := json.Marshal(role.Actions) if err != nil { return false, err } res, err := s.db.Exec(ctx, ` INSERT INTO roles (id, name, actions) VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING `, role.ID, role.Name, string(actions)) if err != nil { return false, err } n, err := res.RowsAffected() if err != nil { return false, err } created := n > 0 if !created { r, err := s.Get(ctx, role.ID) if err != nil { return false, err } *role = *r } return created, nil } func (s *SqliteRoleStore) Get(ctx context.Context, id iam.RoleID) (*iam.Role, error) { row := s.db.QueryRow(ctx, `SELECT id, name, actions FROM roles WHERE id = $1`, id) var role iam.Role var actionsJSON string if err := row.Scan(&role.ID, &role.Name, &actionsJSON); err != nil { return nil, err } if err := json.Unmarshal([]byte(actionsJSON), &role.Actions); err != nil { return nil, err } return &role, nil } func (s *SqliteRoleStore) Delete(ctx context.Context, id iam.RoleID) error { _, err := s.db.Exec(ctx, `DELETE FROM roles WHERE id = $1`, id) return err } func (s *SqliteRoleStore) List(ctx context.Context, page core.PageReq) (core.Page[iam.Role], error) { limit := page.Limit if limit <= 0 { limit = 100 } rows, err := s.db.Query(ctx, ` SELECT id, name, actions FROM roles WHERE id > $1 ORDER BY id LIMIT $2 `, page.Cursor, limit) if err != nil { return core.Page[iam.Role]{}, err } defer rows.Close() var items []iam.Role for rows.Next() { var r iam.Role var actionsJSON string if err := rows.Scan(&r.ID, &r.Name, &actionsJSON); err != nil { return core.Page[iam.Role]{}, err } if err := json.Unmarshal([]byte(actionsJSON), &r.Actions); err != nil { return core.Page[iam.Role]{}, err } items = append(items, r) } if err := rows.Err(); err != nil { return core.Page[iam.Role]{}, err } var cursor string if len(items) == limit { cursor = string(items[limit-1].ID) } return core.Page[iam.Role]{Items: items, Cursor: cursor}, nil } func (s *SqliteRoleStore) ListByNamespace(ctx context.Context, namespace string, page core.PageReq) (core.Page[iam.Role], error) { limit := page.Limit if limit <= 0 { limit = 100 } pattern := escapeLikeRole(namespace) + ".%" rows, err := s.db.Query(ctx, ` SELECT id, name, actions FROM roles WHERE id LIKE $1 ESCAPE '\' AND id > $2 ORDER BY id LIMIT $3 `, pattern, page.Cursor, limit) if err != nil { return core.Page[iam.Role]{}, err } defer rows.Close() var items []iam.Role for rows.Next() { var r iam.Role var actionsJSON string if err := rows.Scan(&r.ID, &r.Name, &actionsJSON); err != nil { return core.Page[iam.Role]{}, err } if err := json.Unmarshal([]byte(actionsJSON), &r.Actions); err != nil { return core.Page[iam.Role]{}, err } items = append(items, r) } if err := rows.Err(); err != nil { return core.Page[iam.Role]{}, err } var cursor string if len(items) == limit { cursor = string(items[limit-1].ID) } return core.Page[iam.Role]{Items: items, Cursor: cursor}, nil } func escapeLikeRole(s string) string { s = strings.ReplaceAll(s, `\`, `\\`) s = strings.ReplaceAll(s, `%`, `\%`) s = strings.ReplaceAll(s, `_`, `\_`) return s }