-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ChangeRequest): add support for change requests to query reposit…
…ories In order go give users the capacity to either reject or allow a change based on shifts to a specified url, we need the concept of a "request" that can be either accepted or rejected. Accepting a request means adding the change to a requested history. Rejecting it should mark the change rejected in some way.
- Loading branch information
Showing
9 changed files
with
348 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package repo | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/ipfs/go-datastore" | ||
"github.com/qri-io/dataset" | ||
) | ||
|
||
// ChangeRequestStore is the interface for storying & manipulating Change Requests | ||
// Qri repos should embed a ChangeRequestStore | ||
type ChangeRequestStore interface { | ||
// Put a change request into the store | ||
PutChangeRequest(path datastore.Key, cr *ChangeRequest) error | ||
// Get a change request by it's path | ||
GetChangeRequest(path datastore.Key) (*ChangeRequest, error) | ||
// accept an open change request | ||
// AcceptChangeRequest(path datastore.Key) error | ||
// decline an open change request | ||
// DeclineChangeRequest(path datastore.Key) error | ||
// get change requests for a given target | ||
ChangeRequestsForTarget(target datastore.Key, limit, offset int) ([]*ChangeRequest, error) | ||
// list change requests in this store | ||
ListChangeRequests(limit, offset int) ([]*ChangeRequest, error) | ||
} | ||
|
||
const ( | ||
// open change requests haven't been addressed yet | ||
ChangeRequestStatusOpen = "open" | ||
// accepted change requests have been merged into | ||
// the target history | ||
ChangeRequestStatusAccepted = "accepted" | ||
// declined change requests will not be merged into | ||
// the target history | ||
ChangeRequestStatusDeclined = "declined" | ||
) | ||
|
||
// ChangeRequests are proposed additions to the history of a given dataset | ||
type ChangeRequest struct { | ||
// status of this request. one of: open,accepted,declined | ||
Status string `json:"status"` | ||
// created marks the time this change request was created | ||
Created time.Time `json:"created"` | ||
// the dataset this change is aimed at. The | ||
// history of target must match the history | ||
// of change up until new history entries | ||
// TODO - should changes be targeting a mutable history? | ||
Target datastore.Key `json:"target"` | ||
// path to HEAD of the change history | ||
Path datastore.Key `json:"path"` | ||
// The actual change history. All relevant details must be stored | ||
// in the dataset itself. Title & description of the change goes | ||
// into this dataset's commit. | ||
Change *dataset.Dataset `json:"change"` | ||
} | ||
|
||
// accept an open change request, advancing the name of the dataset | ||
// that refer to the target path to the newly-added history | ||
func AcceptChangeRequest(r Repo, path datastore.Key) (err error) { | ||
cr, err := r.GetChangeRequest(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cr.Status = ChangeRequestStatusAccepted | ||
if err := r.PutChangeRequest(path, cr); err != nil { | ||
return err | ||
} | ||
|
||
// TODO - place all datasets related to this history chain in the store | ||
ds := &dataset.Dataset{Previous: path} | ||
for { | ||
if ds.Previous.Equal(cr.Target) { | ||
break | ||
} | ||
// datasets can sometimes resolve over the netowork, so this get / put | ||
// combination is required | ||
ds, err = r.GetDataset(ds.Previous) | ||
if err != nil { | ||
return | ||
} | ||
|
||
if err = r.PutDataset(ds.Previous, ds); err != nil { | ||
return | ||
} | ||
} | ||
|
||
name, err := r.GetName(cr.Target) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := r.PutName(name, cr.Path); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// decline an open change request | ||
func DeclineChangeRequest(r Repo, path datastore.Key) error { | ||
cr, err := r.GetChangeRequest(path) | ||
if err != nil { | ||
return err | ||
} | ||
cr.Status = ChangeRequestStatusDeclined | ||
return r.PutChangeRequest(path, cr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package fs_repo | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
|
||
"github.com/ipfs/go-datastore" | ||
"github.com/qri-io/qri/repo" | ||
) | ||
|
||
type ChangeRequests struct { | ||
basepath | ||
file File | ||
} | ||
|
||
func NewChangeRequests(base string, file File) ChangeRequests { | ||
return ChangeRequests{basepath: basepath(base), file: file} | ||
} | ||
|
||
// Put a change request into the store | ||
func (r ChangeRequests) PutChangeRequest(path datastore.Key, cr *repo.ChangeRequest) error { | ||
crs, err := r.changeRequests() | ||
if err != nil { | ||
return err | ||
} | ||
crs[path.String()] = cr | ||
return r.saveFile(cr, r.file) | ||
} | ||
|
||
func (r ChangeRequests) DeleteChangeRequest(path datastore.Key) error { | ||
cr, err := r.changeRequests() | ||
if err != nil { | ||
return err | ||
} | ||
delete(cr, path.String()) | ||
return r.saveFile(cr, r.file) | ||
} | ||
|
||
// Get a change request by it's path | ||
func (r ChangeRequests) GetChangeRequest(path datastore.Key) (*repo.ChangeRequest, error) { | ||
crs, err := r.changeRequests() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cr := crs[path.String()] | ||
if cr == nil { | ||
return nil, datastore.ErrNotFound | ||
} | ||
return cr, nil | ||
} | ||
|
||
// get change requests for a given target | ||
func (r ChangeRequests) ChangeRequestsForTarget(target datastore.Key, limit, offset int) ([]*repo.ChangeRequest, error) { | ||
crs, err := r.changeRequests() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
results := []*repo.ChangeRequest{} | ||
skipped := 0 | ||
for _, cr := range crs { | ||
if cr.Target == target { | ||
if skipped < offset { | ||
skipped++ | ||
continue | ||
} | ||
results = append(results, cr) | ||
} | ||
if len(results) >= limit { | ||
break | ||
} | ||
} | ||
|
||
return results, nil | ||
} | ||
|
||
// list change requests in this store | ||
func (r ChangeRequests) ListChangeRequests(limit, offset int) ([]*repo.ChangeRequest, error) { | ||
crs, err := r.changeRequests() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if limit == -1 && len(crs) <= 0 { | ||
// default to limit of 100 entries | ||
limit = 100 | ||
} else if limit == -1 { | ||
limit = len(crs) | ||
} | ||
|
||
i := 0 | ||
added := 0 | ||
res := make([]*repo.ChangeRequest, limit) | ||
for _, cr := range crs { | ||
if i < offset { | ||
continue | ||
} | ||
|
||
if limit > 0 && added < limit { | ||
res[i] = cr | ||
added++ | ||
} else if added == limit { | ||
break | ||
} | ||
|
||
i++ | ||
} | ||
return res[:added], nil | ||
} | ||
|
||
func (r ChangeRequests) changeRequests() (map[string]*repo.ChangeRequest, error) { | ||
ds := map[string]*repo.ChangeRequest{} | ||
data, err := ioutil.ReadFile(r.filepath(r.file)) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
return ds, nil | ||
} | ||
return ds, fmt.Errorf("error loading changeRequests: %s", err.Error()) | ||
} | ||
|
||
if err := json.Unmarshal(data, &ds); err != nil { | ||
return ds, fmt.Errorf("error unmarshaling changeRequests: %s", err.Error()) | ||
} | ||
return ds, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.