-
Notifications
You must be signed in to change notification settings - Fork 375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: initial r/gh
realm
#1134
base: master
Are you sure you want to change the base?
feat: initial r/gh
realm
#1134
Changes from all commits
8760b6a
277b4ef
76eaaa0
5b6cdf7
517a002
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# GitHub Realm | ||
|
||
**Disclaimer**: This realm is not designed to automatically score or rank pull | ||
requests. Its primary purpose is to provide factual, reliable data from GitHub | ||
to the on-chain environment. Any interpretation or scoring of this data should | ||
be handled by other systems or contracts. | ||
|
||
Welcome to the GitHub realm. This suite of contracts is designed to bridge the | ||
gap between GitHub's vast repositories of data and our on-chain environment. The | ||
overarching aim is to translate select GitHub metrics and interactions onto the | ||
blockchain, providing a seamless interface between the two ecosystems. | ||
|
||
## Purpose of the Package | ||
|
||
The main goals of this package are as follows: | ||
|
||
1. **Oracle-Filled Data**: The package will primarily be populated by an oracle | ||
that translates and mirrors select GitHub data onto the chain. This ensures a | ||
reliable and consistent flow of data between GitHub and our on-chain | ||
ecosystem. | ||
|
||
2. **User Account Linkage**: Users can establish a connection between their | ||
GitHub accounts and their on-chain identities, strengthening the bond between | ||
off-chain activities and on-chain representations. | ||
|
||
3. **Helper Functions for Gno Contract Integration**: A series of helper | ||
functions will be available to convert GitHub objects into Gno objects. This | ||
will enable other contracts to seamlessly utilize GitHub data within their | ||
logic and operations. | ||
|
||
## Key Features | ||
|
||
### On-Chain Representation of GitHub Metrics | ||
|
||
GitHub is a treasure trove of valuable metrics. This package will mirror | ||
essential GitHub data on-chain, like issues, PR statuses, and more. The goal is | ||
to provide an on-chain management system that reflects GitHub's activities, | ||
ensuring the two platforms remain interlinked. | ||
|
||
### Bidirectional Data Flow | ||
|
||
While our primary focus is to mirror GitHub activities on-chain, the reverse | ||
process is also integral. On-chain activities should be recognizable on GitHub, | ||
allowing for a holistic data flow between the two systems. | ||
|
||
### User Account Linkage | ||
|
||
To bolster user interaction and maintain a reliable data flow, users can link | ||
their GitHub and on-chain accounts. This bi-directional linkage offers users a | ||
cohesive experience, and broadens our spectrum of interactivity. | ||
|
||
### Helper Functions | ||
|
||
Developers can tap into a range of helper functions, which can transform GitHub | ||
data into Gno-compatible objects. This aids in integrating GitHub's vast | ||
datasets into other on-chain contracts and logics. | ||
|
||
## Conclusion | ||
|
||
The GitHub package for Gno is a pioneering step towards integrating off-chain | ||
data sources with on-chain functionalities. As we evolve this package, we remain | ||
committed to maintaining the integrity of data, ensuring fairness, and enhancing | ||
the user experience. Feedback, contributions, and suggestions are always | ||
welcome! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package gh | ||
|
||
import "errors" | ||
|
||
// Account represents a GitHub user account or organization. | ||
type Account struct { | ||
id string | ||
name string | ||
kind string | ||
} | ||
Comment on lines
+5
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm. What about having types User, Organization, and |
||
|
||
func (a Account) ID() string { return a.id } | ||
func (a Account) Name() string { return a.name } | ||
func (a Account) Kind() string { return a.kind } | ||
func (a Account) URL() string { return "https://github.com/" + a.id } | ||
func (a Account) IsUser() bool { return a.kind == UserAccount } | ||
func (a Account) IsOrg() bool { return a.kind == OrgAccount } | ||
|
||
// TODO: func (a Account) RepoByID() Repo ... | ||
|
||
func (a Account) Validate() error { | ||
if a.id == "" { | ||
return errors.New("empty id") | ||
} | ||
if a.kind == "" { | ||
return errors.New("empty kind") | ||
} | ||
if a.name == "" { | ||
return errors.New("empty name") | ||
} | ||
// TODO: validate | ||
return nil | ||
} | ||
|
||
func (a Account) String() string { | ||
// XXX: better idea? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. either |
||
return a.URL() | ||
} | ||
|
||
const ( | ||
UserAccount string = "user" | ||
OrgAccount string = "org" | ||
) | ||
|
||
func AccountByID(id string) *Account { | ||
res, ok := accounts.Get(id) | ||
if !ok { | ||
return nil | ||
} | ||
|
||
return res.(*Account) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package gh | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
// Test for the Account struct functions. | ||
func TestAccountFunctions(t *testing.T) { | ||
account := Account{ | ||
id: "user123", | ||
name: "John Doe", | ||
kind: "user", | ||
} | ||
|
||
t.Run("Test Account ID", func(t *testing.T) { | ||
if account.ID() != "user123" { | ||
t.Fatalf("Expected ID to be user123, got %s", account.ID()) | ||
} | ||
}) | ||
|
||
t.Run("Test Account Name", func(t *testing.T) { | ||
if account.Name() != "John Doe" { | ||
t.Fatalf("Expected Name to be John Doe, got %s", account.Name()) | ||
} | ||
}) | ||
|
||
t.Run("Test Account Kind", func(t *testing.T) { | ||
if account.Kind() != "user" { | ||
t.Fatalf("Expected Kind to be user, got %s", account.Kind()) | ||
} | ||
}) | ||
|
||
t.Run("Test Account URL", func(t *testing.T) { | ||
if account.URL() != "https://github.com/user123" { | ||
t.Fatalf("Expected URL to be https://github.com/user123, got %s", account.URL()) | ||
} | ||
}) | ||
|
||
t.Run("Test Account IsUser", func(t *testing.T) { | ||
if !account.IsUser() { | ||
t.Fatal("Expected IsUser to be true") | ||
} | ||
}) | ||
|
||
t.Run("Test Account IsOrg", func(t *testing.T) { | ||
if account.IsOrg() { | ||
t.Fatal("Expected IsOrg to be false") | ||
} | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1 @@ | ||||||||||||
module gno.land/r/gh | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
anyways There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @harry-hov: We need a solution to eliminate those comments. The |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package gh | ||
|
||
/* | ||
// IssueOrPR represents a GitHub issue or pull request | ||
type IssueOrPR struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this, I think we should have (feel free to ignore, I think the comment makes it clear that you want to tackle this in another PR) |
||
ID int | ||
Title string | ||
Body string | ||
Author *Account | ||
Repo *Repo | ||
Type string | ||
} | ||
|
||
const ( | ||
Issue = "issue" | ||
PR = "pr" | ||
) | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package gh | ||
|
||
import ( | ||
"std" | ||
"strings" | ||
"time" | ||
|
||
"gno.land/p/demo/avl" | ||
) | ||
|
||
var ( | ||
accounts avl.Tree // uri -> Account | ||
repos avl.Tree // uri -> Repo | ||
issueOrPRs avl.Tree // uri -> IssueOrPR | ||
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. they're not uris |
||
lastUpdateTime time.Time // used by the bot to only upload the diff | ||
oracleAddr std.Address = "g1eunnckcl6r8ncwj0lrpxu9g5062xcvwxqlrf29" | ||
adminAddr std.Address = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" // @manfred | ||
) | ||
|
||
func OracleLastUpdated() time.Time { return lastUpdateTime } | ||
|
||
func OracleUpsertAccount(id, name, kind string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Upsert feels sql-y, when under the hood we just have a k-v. Can we not do |
||
assertIsOracle() | ||
lastUpdateTime = time.Now() | ||
|
||
// get or create | ||
account := &Account{} | ||
res, ok := accounts.Get(id) | ||
if ok { | ||
account = res.(*Account) | ||
} else { | ||
account.id = id | ||
} | ||
|
||
// update fields | ||
account.name = name | ||
account.kind = kind | ||
|
||
if err := account.Validate(); err != nil { | ||
panic(err) | ||
} | ||
|
||
// save | ||
accounts.Set(id, account) | ||
} | ||
|
||
func OracleUpsertRepo(id string, isPrivate, isFork bool) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/id/path/ |
||
assertIsOracle() | ||
lastUpdateTime = time.Now() | ||
|
||
// get or create | ||
repo := &Repo{} | ||
res, ok := repos.Get(id) | ||
if ok { | ||
repo = res.(*Repo) | ||
} else { | ||
repo.id = id | ||
} | ||
|
||
parts := strings.Split(id, "/") | ||
if len(parts) != 2 { | ||
panic("invalid id") | ||
} | ||
ownerID := parts[0] | ||
name := parts[1] | ||
Comment on lines
+60
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add util func, de-duplicate from above? |
||
|
||
// update fields | ||
repo.name = name | ||
repo.isPrivate = isPrivate | ||
repo.isFork = isFork | ||
repo.owner = AccountByID(ownerID) | ||
|
||
if err := repo.Validate(); err != nil { | ||
panic(err) | ||
} | ||
|
||
// save | ||
repos.Set(id, repo) | ||
} | ||
|
||
func AdminSetOracleAddr(new std.Address) { | ||
assertIsAdmin() | ||
oracleAddr = new | ||
} | ||
|
||
// XXX: remove once it will be easy to query private variables' state. | ||
func AdminGetOracleAddr() std.Address { return oracleAddr } | ||
|
||
func assertIsAdmin() { | ||
if std.GetOrigCaller() != adminAddr { | ||
panic("restricted area") | ||
} | ||
} | ||
|
||
func assertIsOracle() { | ||
if std.GetOrigCaller() != oracleAddr { | ||
panic("restricted area") | ||
} | ||
} | ||
|
||
// TODO: could be a great fit for a vector-based/state machine approach, mostly for optimizations | ||
// func OracleApplyVectors(vectors ...) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package gh | ||
|
||
func LinkAccount(account string, signature string) { | ||
// TODO: verify signature | ||
// TODO: upsert AccountLink | ||
// TODO: generate challenge to be signed and published on gh | ||
panic("not implemented") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package gh | ||
|
||
func Render(path string) string { | ||
panic("not implemented") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package gh | ||
|
||
import "errors" | ||
|
||
// Repo represents a GitHub repository. | ||
type Repo struct { | ||
id string | ||
owner *Account | ||
name string | ||
isPrivate bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it's private, the oracle probably shouldn't know about it, right? |
||
isFork bool | ||
} | ||
|
||
func (r Repo) ID() string { return r.id } | ||
func (r Repo) Name() string { return r.name } | ||
func (r Repo) Owner() *Account { return r.owner } | ||
func (r Repo) IsPrivate() bool { return r.isPrivate } | ||
func (r Repo) IsFork() bool { return r.isFork } | ||
func (r Repo) URL() string { return r.owner.URL() + "/" + r.name } | ||
|
||
func (r Repo) String() string { | ||
// XXX: better idea? | ||
return r.URL() | ||
} | ||
|
||
func (r Repo) Validate() error { | ||
if r.id == "" { | ||
return errors.New("id is empty") | ||
} | ||
if r.name == "" { | ||
return errors.New("name is empty") | ||
} | ||
if r.owner == nil { | ||
return errors.New("owner is nil") | ||
} | ||
return nil | ||
} | ||
|
||
func RepoByID(id string) *Repo { | ||
res, ok := repos.Get(id) | ||
if !ok { | ||
return nil | ||
} | ||
|
||
return res.(*Repo) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package gh | ||
|
||
import "testing" | ||
|
||
// Test for the Repo struct functions. | ||
func TestRepoFunctions(t *testing.T) { | ||
account := &Account{ | ||
id: "org123", | ||
name: "Sample Org", | ||
kind: "org", | ||
} | ||
repo := Repo{ | ||
id: "org123/sample-repo", | ||
owner: account, | ||
name: "sample-repo", | ||
} | ||
|
||
t.Run("Test Repo ID", func(t *testing.T) { | ||
if repo.ID() != "org123/sample-repo" { | ||
t.Fatalf("Expected ID to be org123/sample-repo, got %s", repo.ID()) | ||
} | ||
}) | ||
|
||
t.Run("Test Repo Name", func(t *testing.T) { | ||
if repo.Name() != "sample-repo" { | ||
t.Fatalf("Expected Name to be sample-repo, got %s", repo.Name()) | ||
} | ||
}) | ||
|
||
t.Run("Test Repo Owner", func(t *testing.T) { | ||
if repo.Owner().ID() != "org123" { | ||
t.Fatalf("Expected Owner ID to be org123, got %s", repo.Owner().ID()) | ||
} | ||
}) | ||
|
||
t.Run("Test Repo URL", func(t *testing.T) { | ||
if repo.URL() != "https://github.com/org123/sample-repo" { | ||
t.Fatalf("Expected URL to be https://github.com/org123/sample-repo, got %s", repo.URL()) | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handle?