forked from cerc-io/laconicd
Setup and integrate GQL server (#12)
Reviewed-on: deep-stack/laconic2d#12 Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com> Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
This commit is contained in:
+353
@@ -0,0 +1,353 @@
|
||||
# cerc-io laconic gql
|
||||
|
||||
> Browser : http://localhost:9473 for gql
|
||||
|
||||
## Start server
|
||||
|
||||
```shell
|
||||
laconic2d start --gql-playground --gql-server
|
||||
```
|
||||
|
||||
Basic node status:
|
||||
|
||||
```graphql
|
||||
{
|
||||
getStatus {
|
||||
version
|
||||
node {
|
||||
id
|
||||
network
|
||||
moniker
|
||||
}
|
||||
sync {
|
||||
latest_block_height
|
||||
catching_up
|
||||
}
|
||||
num_peers
|
||||
peers {
|
||||
is_outbound
|
||||
remote_ip
|
||||
}
|
||||
disk_usage
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Full node status:
|
||||
|
||||
```graphql
|
||||
{
|
||||
getStatus {
|
||||
version
|
||||
node {
|
||||
id
|
||||
network
|
||||
moniker
|
||||
}
|
||||
sync {
|
||||
latest_block_hash
|
||||
latest_block_time
|
||||
latest_block_height
|
||||
catching_up
|
||||
}
|
||||
validator {
|
||||
address
|
||||
voting_power
|
||||
proposer_priority
|
||||
}
|
||||
validators {
|
||||
address
|
||||
voting_power
|
||||
proposer_priority
|
||||
}
|
||||
num_peers
|
||||
peers {
|
||||
node {
|
||||
id
|
||||
network
|
||||
moniker
|
||||
}
|
||||
is_outbound
|
||||
remote_ip
|
||||
}
|
||||
disk_usage
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Get records by IDs.
|
||||
|
||||
```graphql
|
||||
{
|
||||
getRecordsByIds(ids: ["bafyreigswvbm4dbpnbwkyegrcxd6kynqe4bivflo6esedgxnuofpzonwdy"]) {
|
||||
id
|
||||
names
|
||||
bondId
|
||||
createTime
|
||||
expiryTime
|
||||
owners
|
||||
attributes {
|
||||
key
|
||||
value {
|
||||
string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Query records.
|
||||
|
||||
```graphql
|
||||
{
|
||||
queryRecords(attributes: [{ key: "type", value: { string: "crn:bot" } }]) {
|
||||
id
|
||||
names
|
||||
bondId
|
||||
createTime
|
||||
expiryTime
|
||||
owners
|
||||
attributes {
|
||||
key
|
||||
value {
|
||||
string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Get account details:
|
||||
|
||||
```graphql
|
||||
{
|
||||
getAccounts(addresses: ["laconic17t5ywvqxntu0afc96tz0yxcx92ss0e2alhx2c2"]) {
|
||||
address
|
||||
pubKey
|
||||
number
|
||||
sequence
|
||||
balance {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Query bonds:
|
||||
|
||||
```graphql
|
||||
{
|
||||
queryBonds(
|
||||
attributes: [
|
||||
{
|
||||
key: "owner"
|
||||
value: { string: "laconic17t5ywvqxntu0afc96tz0yxcx92ss0e2alhx2c2" }
|
||||
}
|
||||
]
|
||||
) {
|
||||
id
|
||||
owner
|
||||
balance {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Get bonds by IDs.
|
||||
|
||||
```graphql
|
||||
{
|
||||
getBondsByIds(
|
||||
ids: [
|
||||
"1c2b677cb2a27c88cc6bf8acf675c94b69051125b40c4fd073153b10f046dd87"
|
||||
"c3f7a78c5042d2003880962ba31ff3b01fcf5942960e0bc3ca331f816346a440"
|
||||
]
|
||||
) {
|
||||
id
|
||||
owner
|
||||
balance {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Query Bonds by Owner
|
||||
|
||||
```graphql
|
||||
{
|
||||
queryBondsByOwner(
|
||||
ownerAddresses: ["laconic17t5ywvqxntu0afc96tz0yxcx92ss0e2alhx2c2"]
|
||||
) {
|
||||
owner
|
||||
bonds {
|
||||
id
|
||||
owner
|
||||
balance {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Query auctions by ids
|
||||
|
||||
```graphql
|
||||
{
|
||||
getAuctionsByIds(
|
||||
ids: ["be98f2073c246194276554eefdb4c95b682a35a0f06fbe619a6da57c10c93e90"]
|
||||
) {
|
||||
id
|
||||
ownerAddress
|
||||
createTime
|
||||
minimumBid {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
commitFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
commitsEndTime
|
||||
revealFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
revealsEndTime
|
||||
winnerBid {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
winnerPrice {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
winnerAddress
|
||||
bids {
|
||||
bidderAddress
|
||||
commitHash
|
||||
commitTime
|
||||
commitFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
revealFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
revealTime
|
||||
bidAmount {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
LookUp Authorities
|
||||
|
||||
```graphql
|
||||
{
|
||||
lookupAuthorities(names: []) {
|
||||
ownerAddress
|
||||
ownerAddress
|
||||
height
|
||||
bondId
|
||||
status
|
||||
expiryTime
|
||||
auction {
|
||||
id
|
||||
ownerAddress
|
||||
createTime
|
||||
minimumBid {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
commitFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
commitsEndTime
|
||||
revealFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
revealsEndTime
|
||||
winnerBid {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
winnerPrice {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
winnerAddress
|
||||
bids {
|
||||
bidderAddress
|
||||
commitHash
|
||||
commitTime
|
||||
commitFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
revealFee {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
revealTime
|
||||
bidAmount {
|
||||
type
|
||||
quantity
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
LookUp Names
|
||||
|
||||
```graphql
|
||||
{
|
||||
lookupNames(names: ["crn://hello/test"]) {
|
||||
latest {
|
||||
id
|
||||
height
|
||||
}
|
||||
history {
|
||||
id
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Resolve Names
|
||||
|
||||
```graphql
|
||||
{
|
||||
resolveNames(names: ["asd"]) {
|
||||
id
|
||||
names
|
||||
bondId
|
||||
createTime
|
||||
expiryTime
|
||||
owners
|
||||
attributes {
|
||||
key
|
||||
value {
|
||||
string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,258 @@
|
||||
# Reference to another record.
|
||||
scalar Link
|
||||
|
||||
# Bonds contain funds that are used to pay rent on record registration and renewal.
|
||||
type Bond {
|
||||
id: String! # Primary key, auto-generated by the server.
|
||||
owner: String! # Bond owner cosmos-sdk address.
|
||||
balance: [Coin!] # Current balance for each coin type.
|
||||
}
|
||||
|
||||
# OwnerBonds contains the bonds related the owner
|
||||
type OwnerBonds {
|
||||
owner: String!
|
||||
bonds: [Bond!]
|
||||
}
|
||||
|
||||
# Mutations require payment in coins (e.g. 100wire).
|
||||
# Used by the wallet to get the account balance for display and mutations.
|
||||
type Coin {
|
||||
type: String! # e.g. 'WIRE'
|
||||
quantity: String! # e.g. 1000000
|
||||
}
|
||||
# Represents an account on the blockchain.
|
||||
# Mutations have to be signed by a particular account.
|
||||
type Account {
|
||||
address: String! # Blockchain address.
|
||||
pubKey: String # Public key.
|
||||
number: String! # Account number.
|
||||
sequence: String! # Sequence number used to prevent replays.
|
||||
balance: [Coin!] # Current balance for each coin type.
|
||||
}
|
||||
|
||||
# Value describes a DAG-JSON compatible value.
|
||||
union Value =
|
||||
BooleanValue
|
||||
| IntValue
|
||||
| FloatValue
|
||||
| StringValue
|
||||
| BytesValue
|
||||
| LinkValue
|
||||
| ArrayValue
|
||||
| MapValue
|
||||
|
||||
type BooleanValue {
|
||||
value: Boolean!
|
||||
}
|
||||
|
||||
type IntValue {
|
||||
value: Int!
|
||||
}
|
||||
|
||||
type FloatValue {
|
||||
value: Float!
|
||||
}
|
||||
|
||||
type StringValue {
|
||||
value: String!
|
||||
}
|
||||
|
||||
type BytesValue {
|
||||
value: String!
|
||||
}
|
||||
|
||||
type ArrayValue {
|
||||
value: [Value]!
|
||||
}
|
||||
|
||||
type LinkValue {
|
||||
value: Link!
|
||||
}
|
||||
|
||||
type MapValue {
|
||||
value: [Attribute!]!
|
||||
}
|
||||
|
||||
# Key/value pair.
|
||||
type Attribute {
|
||||
key: String!
|
||||
value: Value
|
||||
}
|
||||
|
||||
# Value of a given type used as input to queries.
|
||||
# Note: GQL doesn't allow union input types.
|
||||
input ValueInput {
|
||||
int: Int
|
||||
float: Float
|
||||
string: String
|
||||
boolean: Boolean
|
||||
link: Link
|
||||
array: [ValueInput]
|
||||
map: [KeyValueInput!]
|
||||
}
|
||||
|
||||
# Key/value pair for inputs.
|
||||
input KeyValueInput {
|
||||
key: String!
|
||||
value: ValueInput
|
||||
}
|
||||
|
||||
# Status information about a node (https://docs.tendermint.com/master/rpc/#/Info/status).
|
||||
type NodeInfo {
|
||||
id: String! # Tendermint Node ID.
|
||||
network: String! # Name of the network/blockchain.
|
||||
moniker: String! # Name of the node.
|
||||
}
|
||||
|
||||
# Node sync status.
|
||||
type SyncInfo {
|
||||
latest_block_hash: String!
|
||||
latest_block_height: String!
|
||||
latest_block_time: String!
|
||||
catching_up: Boolean!
|
||||
}
|
||||
|
||||
# Validator set info (https://docs.tendermint.com/master/rpc/#/Info/validators).
|
||||
type ValidatorInfo {
|
||||
address: String!
|
||||
voting_power: String!
|
||||
proposer_priority: String
|
||||
}
|
||||
|
||||
# Network/peer info (https://docs.tendermint.com/master/rpc/#/Info/net_info).
|
||||
type PeerInfo {
|
||||
node: NodeInfo!
|
||||
is_outbound: Boolean!
|
||||
remote_ip: String!
|
||||
}
|
||||
|
||||
# Vulcanize laconic status.
|
||||
type Status {
|
||||
version: String!
|
||||
node: NodeInfo!
|
||||
sync: SyncInfo!
|
||||
validator: ValidatorInfo
|
||||
validators: [ValidatorInfo]!
|
||||
num_peers: String!
|
||||
peers: [PeerInfo]
|
||||
disk_usage: String!
|
||||
}
|
||||
|
||||
# An auction bid.
|
||||
type AuctionBid {
|
||||
bidderAddress: String!
|
||||
status: String!
|
||||
commitHash: String!
|
||||
commitTime: String!
|
||||
commitFee: Coin!
|
||||
revealTime: String!
|
||||
revealFee: Coin!
|
||||
bidAmount: Coin!
|
||||
}
|
||||
|
||||
# A sealed-bid, 2nd price auction.
|
||||
type Auction {
|
||||
id: String! # Auction ID.
|
||||
status: String! # Auction status (commit, reveal, expired).
|
||||
ownerAddress: String! # Auction owner time.
|
||||
createTime: String! # Create time.
|
||||
commitsEndTime: String! # Commit phase end time.
|
||||
revealsEndTime: String! # Reveal phase end time.
|
||||
commitFee: Coin! # Fee required to bid/participate in the auction.
|
||||
revealFee: Coin! # Reveal fee (paid back to bidders only if they unseal/reveal the bid).
|
||||
minimumBid: Coin! # Minimum bid amount.
|
||||
winnerAddress: String! # Winner address.
|
||||
winnerBid: Coin! # The winning bid amount.
|
||||
winnerPrice: Coin! # The price that the winner actually pays (2nd highest bid).
|
||||
bids: [AuctionBid] # Bids make in the auction.
|
||||
}
|
||||
|
||||
# Record defines the basic properties of an entity in the graph database.
|
||||
type Record {
|
||||
id: String! # Computed attribute: Multibase encoded content hash (https://github.com/multiformats/multibase).
|
||||
names: [String!] # Names pointing to this CID (reverse lookup).
|
||||
bondId: String! # Associated bond ID.
|
||||
createTime: String! # Record create time.
|
||||
expiryTime: String! # Record expiry time.
|
||||
owners: [String!] # Addresses of record owners.
|
||||
attributes: [Attribute!] # Record attributes.
|
||||
references: [Record] # Record references.
|
||||
}
|
||||
|
||||
# Name authority record.
|
||||
type AuthorityRecord {
|
||||
ownerAddress: String! # Owner address.
|
||||
ownerPublicKey: String! # Owner public key.
|
||||
height: String! # Height at which record was created.
|
||||
status: String! # Status (active, auction, expired).
|
||||
bondId: String! # Associated bond ID.
|
||||
expiryTime: String! # Authority expiry time.
|
||||
auction: Auction # Authority auction.
|
||||
}
|
||||
|
||||
# Name record entry, created at a particular height.
|
||||
type NameRecordEntry {
|
||||
id: String! # Target record ID.
|
||||
height: String! # Height at which record was created.
|
||||
}
|
||||
|
||||
# Name record stores the latest and historical name -> record ID mappings.
|
||||
type NameRecord {
|
||||
latest: NameRecordEntry! # Latest mame record entry.
|
||||
history: [NameRecordEntry] # Historical name record entries.
|
||||
}
|
||||
|
||||
type Query {
|
||||
#
|
||||
# Status API.
|
||||
#
|
||||
getStatus: Status!
|
||||
|
||||
# Get blockchain accounts.
|
||||
getAccounts(addresses: [String!]): [Account]
|
||||
|
||||
# Get bonds by IDs.
|
||||
getBondsByIds(ids: [String!]): [Bond]
|
||||
|
||||
# Query bonds.
|
||||
queryBonds(attributes: [KeyValueInput!]): [Bond]
|
||||
|
||||
# Query bonds by owner.
|
||||
queryBondsByOwner(ownerAddresses: [String!]): [OwnerBonds]
|
||||
|
||||
#
|
||||
# GraphDB API.
|
||||
#
|
||||
|
||||
# Get records by IDs.
|
||||
getRecordsByIds(ids: [String!]): [Record]
|
||||
|
||||
# Query records.
|
||||
queryRecords(
|
||||
# Multiple attribute conditions are in a logical AND.
|
||||
attributes: [KeyValueInput!]
|
||||
|
||||
# Whether to query all records, not just named ones (false by default).
|
||||
all: Boolean
|
||||
): [Record]
|
||||
|
||||
#
|
||||
# Naming API.
|
||||
#
|
||||
|
||||
# Lookup authority information.
|
||||
lookupAuthorities(names: [String!]): [AuthorityRecord]!
|
||||
|
||||
# Lookup name to record mapping information.
|
||||
lookupNames(names: [String!]): [NameRecord]!
|
||||
|
||||
# Resolve names to records.
|
||||
resolveNames(names: [String!]): [Record]!
|
||||
|
||||
#
|
||||
# Auctions API.
|
||||
#
|
||||
|
||||
# Get auctions by IDs.
|
||||
getAuctionsByIds(ids: [String!]): [Auction]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package gql
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// AddGQLFlags adds gql flags for
|
||||
func AddGQLFlags(cmd *cobra.Command) *cobra.Command {
|
||||
// Add flags for GQL server.
|
||||
cmd.PersistentFlags().Bool("gql-server", false, "Start GQL server.")
|
||||
cmd.PersistentFlags().Bool("gql-playground", false, "Enable GQL playground.")
|
||||
cmd.PersistentFlags().String("gql-playground-api-base", "", "GQL API base path to use in GQL playground.")
|
||||
cmd.PersistentFlags().String("gql-port", "9473", "Port to use for the GQL server.")
|
||||
cmd.PersistentFlags().String("log-file", "", "File to tail for GQL 'getLogs' API.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
+11067
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
# Refer to https://gqlgen.com/config/
|
||||
# for detailed .gqlgen.yml documentation.
|
||||
|
||||
schema:
|
||||
- cerc-io/laconic2d/*.graphql
|
||||
exec:
|
||||
filename: generated.go
|
||||
model:
|
||||
filename: models_gen.go
|
||||
resolver:
|
||||
filename: resolver.go
|
||||
type: Resolver
|
||||
|
||||
models:
|
||||
Link:
|
||||
model:
|
||||
- git.vdb.to/cerc-io/laconic2d/gql.Link
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GraphiQL is an in-browser IDE for exploring GraphiQL APIs.
|
||||
// This handler returns GraphiQL when requested.
|
||||
//
|
||||
// For more information, see https://github.com/graphql/graphiql.
|
||||
|
||||
func PlaygroundHandler(apiURL string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "only GET requests are supported", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
err := page.Execute(w, map[string]interface{}{
|
||||
"apiURL": apiURL,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/graphql/graphiql/blob/main/examples/graphiql-cdn/index.html
|
||||
var page = template.Must(template.New("graphiql").Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>GraphiQL</title>
|
||||
<style>
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#graphiql {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--
|
||||
This GraphiQL example depends on Promise and fetch, which are available in
|
||||
modern browsers, but can be "polyfilled" for older browsers.
|
||||
GraphiQL itself depends on React DOM.
|
||||
If you do not want to rely on a CDN, you can host these files locally or
|
||||
include them directly in your favored resource bundler.
|
||||
-->
|
||||
<script
|
||||
src="https://unpkg.com/react@17/umd/react.development.js"
|
||||
integrity="sha512-Vf2xGDzpqUOEIKO+X2rgTLWPY+65++WPwCHkX2nFMu9IcstumPsf/uKKRd5prX3wOu8Q0GBylRpsDB26R6ExOg=="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script
|
||||
src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
|
||||
integrity="sha512-Wr9OKCTtq1anK0hq5bY3X/AvDI5EflDSAh0mE9gma+4hl+kXdTJPKZ3TwLMBcrgUeoY0s3dq9JjhCQc7vddtFg=="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<!--
|
||||
These two files can be found in the npm module, however you may wish to
|
||||
copy them directly into your environment, or perhaps include them in your
|
||||
favored resource bundler.
|
||||
-->
|
||||
<link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="graphiql">Loading...</div>
|
||||
<script
|
||||
src="https://unpkg.com/graphiql/graphiql.min.js"
|
||||
type="application/javascript"
|
||||
></script>
|
||||
<script>
|
||||
// https://github.com/graphql/graphiql/blob/main/packages/graphiql/resources/renderExample.js
|
||||
|
||||
// Parse the search string to get url parameters.
|
||||
var search = window.location.search;
|
||||
var parameters = {};
|
||||
search
|
||||
.substr(1)
|
||||
.split('&')
|
||||
.forEach(function (entry) {
|
||||
var eq = entry.indexOf('=');
|
||||
if (eq >= 0) {
|
||||
parameters[decodeURIComponent(entry.slice(0, eq))] = decodeURIComponent(
|
||||
entry.slice(eq + 1),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// When the query and variables string is edited, update the URL bar so
|
||||
// that it can be easily shared.
|
||||
function onEditQuery(newQuery) {
|
||||
parameters.query = newQuery;
|
||||
updateURL();
|
||||
}
|
||||
|
||||
function onEditVariables(newVariables) {
|
||||
parameters.variables = newVariables;
|
||||
updateURL();
|
||||
}
|
||||
|
||||
function onEditHeaders(newHeaders) {
|
||||
parameters.headers = newHeaders;
|
||||
updateURL();
|
||||
}
|
||||
|
||||
function onTabChange(tabsState) {
|
||||
const activeTab = tabsState.tabs[tabsState.activeTabIndex];
|
||||
parameters.query = activeTab.query;
|
||||
parameters.variables = activeTab.variables;
|
||||
parameters.headers = activeTab.headers;
|
||||
updateURL();
|
||||
}
|
||||
|
||||
function updateURL() {
|
||||
var newSearch =
|
||||
'?' +
|
||||
Object.keys(parameters)
|
||||
.filter(function (key) {
|
||||
return Boolean(parameters[key]);
|
||||
})
|
||||
.map(function (key) {
|
||||
return (
|
||||
encodeURIComponent(key) + '=' + encodeURIComponent(parameters[key])
|
||||
);
|
||||
})
|
||||
.join('&');
|
||||
history.replaceState(null, null, newSearch);
|
||||
}
|
||||
|
||||
// Render <GraphiQL /> into the body.
|
||||
// See the README in the top level of this module to learn more about
|
||||
// how you can customize GraphiQL by providing different values or
|
||||
// additional child elements.
|
||||
ReactDOM.render(
|
||||
React.createElement(GraphiQL, {
|
||||
fetcher: GraphiQL.createFetcher({
|
||||
url: {{.apiURL}}
|
||||
}),
|
||||
query: parameters.query,
|
||||
variables: parameters.variables,
|
||||
headers: parameters.headers,
|
||||
defaultHeaders: parameters.defaultHeaders,
|
||||
onEditQuery: onEditQuery,
|
||||
onEditVariables: onEditVariables,
|
||||
onEditHeaders: onEditHeaders,
|
||||
defaultEditorToolsVisibility: true,
|
||||
isHeadersEditorEnabled: true,
|
||||
shouldPersistHeaders: true,
|
||||
onTabChange,
|
||||
}),
|
||||
document.getElementById('graphiql'),
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
@@ -0,0 +1,193 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package gql
|
||||
|
||||
type Value interface {
|
||||
IsValue()
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
Address string `json:"address"`
|
||||
PubKey *string `json:"pubKey"`
|
||||
Number string `json:"number"`
|
||||
Sequence string `json:"sequence"`
|
||||
Balance []*Coin `json:"balance"`
|
||||
}
|
||||
|
||||
type ArrayValue struct {
|
||||
Value []Value `json:"value"`
|
||||
}
|
||||
|
||||
func (ArrayValue) IsValue() {}
|
||||
|
||||
type Attribute struct {
|
||||
Key string `json:"key"`
|
||||
Value Value `json:"value"`
|
||||
}
|
||||
|
||||
type Auction struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
OwnerAddress string `json:"ownerAddress"`
|
||||
CreateTime string `json:"createTime"`
|
||||
CommitsEndTime string `json:"commitsEndTime"`
|
||||
RevealsEndTime string `json:"revealsEndTime"`
|
||||
CommitFee *Coin `json:"commitFee"`
|
||||
RevealFee *Coin `json:"revealFee"`
|
||||
MinimumBid *Coin `json:"minimumBid"`
|
||||
WinnerAddress string `json:"winnerAddress"`
|
||||
WinnerBid *Coin `json:"winnerBid"`
|
||||
WinnerPrice *Coin `json:"winnerPrice"`
|
||||
Bids []*AuctionBid `json:"bids"`
|
||||
}
|
||||
|
||||
type AuctionBid struct {
|
||||
BidderAddress string `json:"bidderAddress"`
|
||||
Status string `json:"status"`
|
||||
CommitHash string `json:"commitHash"`
|
||||
CommitTime string `json:"commitTime"`
|
||||
CommitFee *Coin `json:"commitFee"`
|
||||
RevealTime string `json:"revealTime"`
|
||||
RevealFee *Coin `json:"revealFee"`
|
||||
BidAmount *Coin `json:"bidAmount"`
|
||||
}
|
||||
|
||||
type AuthorityRecord struct {
|
||||
OwnerAddress string `json:"ownerAddress"`
|
||||
OwnerPublicKey string `json:"ownerPublicKey"`
|
||||
Height string `json:"height"`
|
||||
Status string `json:"status"`
|
||||
BondID string `json:"bondId"`
|
||||
ExpiryTime string `json:"expiryTime"`
|
||||
Auction *Auction `json:"auction"`
|
||||
}
|
||||
|
||||
type Bond struct {
|
||||
ID string `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Balance []*Coin `json:"balance"`
|
||||
}
|
||||
|
||||
type BooleanValue struct {
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
|
||||
func (BooleanValue) IsValue() {}
|
||||
|
||||
type BytesValue struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (BytesValue) IsValue() {}
|
||||
|
||||
type Coin struct {
|
||||
Type string `json:"type"`
|
||||
Quantity string `json:"quantity"`
|
||||
}
|
||||
|
||||
type FloatValue struct {
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
func (FloatValue) IsValue() {}
|
||||
|
||||
type IntValue struct {
|
||||
Value int `json:"value"`
|
||||
}
|
||||
|
||||
func (IntValue) IsValue() {}
|
||||
|
||||
type KeyValueInput struct {
|
||||
Key string `json:"key"`
|
||||
Value *ValueInput `json:"value"`
|
||||
}
|
||||
|
||||
type LinkValue struct {
|
||||
Value Link `json:"value"`
|
||||
}
|
||||
|
||||
func (LinkValue) IsValue() {}
|
||||
|
||||
type MapValue struct {
|
||||
Value []*Attribute `json:"value"`
|
||||
}
|
||||
|
||||
func (MapValue) IsValue() {}
|
||||
|
||||
type NameRecord struct {
|
||||
Latest *NameRecordEntry `json:"latest"`
|
||||
History []*NameRecordEntry `json:"history"`
|
||||
}
|
||||
|
||||
type NameRecordEntry struct {
|
||||
ID string `json:"id"`
|
||||
Height string `json:"height"`
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
ID string `json:"id"`
|
||||
Network string `json:"network"`
|
||||
Moniker string `json:"moniker"`
|
||||
}
|
||||
|
||||
type OwnerBonds struct {
|
||||
Owner string `json:"owner"`
|
||||
Bonds []*Bond `json:"bonds"`
|
||||
}
|
||||
|
||||
type PeerInfo struct {
|
||||
Node *NodeInfo `json:"node"`
|
||||
IsOutbound bool `json:"is_outbound"`
|
||||
RemoteIP string `json:"remote_ip"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
ID string `json:"id"`
|
||||
Names []string `json:"names"`
|
||||
BondID string `json:"bondId"`
|
||||
CreateTime string `json:"createTime"`
|
||||
ExpiryTime string `json:"expiryTime"`
|
||||
Owners []string `json:"owners"`
|
||||
Attributes []*Attribute `json:"attributes"`
|
||||
References []*Record `json:"references"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Version string `json:"version"`
|
||||
Node *NodeInfo `json:"node"`
|
||||
Sync *SyncInfo `json:"sync"`
|
||||
Validator *ValidatorInfo `json:"validator"`
|
||||
Validators []*ValidatorInfo `json:"validators"`
|
||||
NumPeers string `json:"num_peers"`
|
||||
Peers []*PeerInfo `json:"peers"`
|
||||
DiskUsage string `json:"disk_usage"`
|
||||
}
|
||||
|
||||
type StringValue struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (StringValue) IsValue() {}
|
||||
|
||||
type SyncInfo struct {
|
||||
LatestBlockHash string `json:"latest_block_hash"`
|
||||
LatestBlockHeight string `json:"latest_block_height"`
|
||||
LatestBlockTime string `json:"latest_block_time"`
|
||||
CatchingUp bool `json:"catching_up"`
|
||||
}
|
||||
|
||||
type ValidatorInfo struct {
|
||||
Address string `json:"address"`
|
||||
VotingPower string `json:"voting_power"`
|
||||
ProposerPriority *string `json:"proposer_priority"`
|
||||
}
|
||||
|
||||
type ValueInput struct {
|
||||
Int *int `json:"int"`
|
||||
Float *float64 `json:"float"`
|
||||
String *string `json:"string"`
|
||||
Boolean *bool `json:"boolean"`
|
||||
Link *Link `json:"link"`
|
||||
Array []*ValueInput `json:"array"`
|
||||
Map []*KeyValueInput `json:"map"`
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
|
||||
auctiontypes "git.vdb.to/cerc-io/laconic2d/x/auction"
|
||||
bondtypes "git.vdb.to/cerc-io/laconic2d/x/bond"
|
||||
registrytypes "git.vdb.to/cerc-io/laconic2d/x/registry"
|
||||
)
|
||||
|
||||
// DefaultLogNumLines is the number of log lines to tail by default.
|
||||
const DefaultLogNumLines = 50
|
||||
|
||||
// MaxLogNumLines is the max number of log lines that can be tailed.
|
||||
const MaxLogNumLines = 1000
|
||||
|
||||
type Resolver struct {
|
||||
ctx client.Context
|
||||
logFile string
|
||||
}
|
||||
|
||||
// Query is the entry point to query execution.
|
||||
func (r *Resolver) Query() QueryResolver {
|
||||
return &queryResolver{r}
|
||||
}
|
||||
|
||||
type queryResolver struct{ *Resolver }
|
||||
|
||||
func (q queryResolver) LookupAuthorities(ctx context.Context, names []string) ([]*AuthorityRecord, error) {
|
||||
nsQueryClient := registrytypes.NewQueryClient(q.ctx)
|
||||
auctionQueryClient := auctiontypes.NewQueryClient(q.ctx)
|
||||
gqlResponse := []*AuthorityRecord{}
|
||||
|
||||
for _, name := range names {
|
||||
res, err := nsQueryClient.Whois(context.Background(), ®istrytypes.QueryWhoisRequest{Name: name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nameAuthority := res.GetNameAuthority()
|
||||
gqlNameAuthorityRecord, err := GetGQLNameAuthorityRecord(&nameAuthority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nameAuthority.AuctionId != "" {
|
||||
auctionResp, err := auctionQueryClient.GetAuction(context.Background(), &auctiontypes.QueryAuctionRequest{Id: nameAuthority.GetAuctionId()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bidsResp, err := auctionQueryClient.GetBids(context.Background(), &auctiontypes.QueryBidsRequest{AuctionId: nameAuthority.GetAuctionId()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlAuctionRecord, err := GetGQLAuction(auctionResp.GetAuction(), bidsResp.GetBids())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlNameAuthorityRecord.Auction = gqlAuctionRecord
|
||||
}
|
||||
|
||||
gqlResponse = append(gqlResponse, gqlNameAuthorityRecord)
|
||||
}
|
||||
|
||||
return gqlResponse, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) ResolveNames(ctx context.Context, names []string) ([]*Record, error) {
|
||||
nsQueryClient := registrytypes.NewQueryClient(q.ctx)
|
||||
var gqlResponse []*Record
|
||||
for _, name := range names {
|
||||
res, err := nsQueryClient.ResolveLrn(context.Background(), ®istrytypes.QueryResolveLrnRequest{Lrn: name})
|
||||
if err != nil {
|
||||
// Return nil for record not found.
|
||||
gqlResponse = append(gqlResponse, nil)
|
||||
} else {
|
||||
gqlRecord, err := getGQLRecord(context.Background(), q, *res.GetRecord())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlResponse = append(gqlResponse, gqlRecord)
|
||||
}
|
||||
}
|
||||
|
||||
return gqlResponse, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) LookupNames(ctx context.Context, names []string) ([]*NameRecord, error) {
|
||||
nsQueryClient := registrytypes.NewQueryClient(q.ctx)
|
||||
var gqlResponse []*NameRecord
|
||||
|
||||
for _, name := range names {
|
||||
res, err := nsQueryClient.LookupLrn(context.Background(), ®istrytypes.QueryLookupLrnRequest{Lrn: name})
|
||||
if err != nil {
|
||||
// Return nil for name not found.
|
||||
gqlResponse = append(gqlResponse, nil)
|
||||
} else {
|
||||
gqlRecord, err := getGQLNameRecord(res.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlResponse = append(gqlResponse, gqlRecord)
|
||||
}
|
||||
}
|
||||
|
||||
return gqlResponse, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) QueryRecords(ctx context.Context, attributes []*KeyValueInput, all *bool) ([]*Record, error) {
|
||||
nsQueryClient := registrytypes.NewQueryClient(q.ctx)
|
||||
|
||||
res, err := nsQueryClient.Records(
|
||||
context.Background(),
|
||||
®istrytypes.QueryRecordsRequest{
|
||||
Attributes: toRPCAttributes(attributes),
|
||||
All: (all != nil && *all),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := res.GetRecords()
|
||||
gqlResponse := make([]*Record, len(records))
|
||||
|
||||
for i, record := range records {
|
||||
gqlRecord, err := getGQLRecord(context.Background(), q, record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gqlResponse[i] = gqlRecord
|
||||
}
|
||||
|
||||
return gqlResponse, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetRecordsByIds(ctx context.Context, ids []string) ([]*Record, error) {
|
||||
nsQueryClient := registrytypes.NewQueryClient(q.ctx)
|
||||
gqlResponse := make([]*Record, len(ids))
|
||||
|
||||
for i, id := range ids {
|
||||
res, err := nsQueryClient.GetRecord(context.Background(), ®istrytypes.QueryRecordByIdRequest{Id: id})
|
||||
if err != nil {
|
||||
// Return nil for record not found.
|
||||
gqlResponse[i] = nil
|
||||
} else {
|
||||
record, err := getGQLRecord(context.Background(), q, res.GetRecord())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gqlResponse[i] = record
|
||||
}
|
||||
}
|
||||
|
||||
return gqlResponse, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetStatus(ctx context.Context) (*Status, error) {
|
||||
nodeInfo, syncInfo, validatorInfo, err := getStatusInfo(q.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numPeers, peers, err := getNetInfo(q.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validatorSet, err := getValidatorSet(q.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
diskUsage, err := GetDiskUsage(NodeDataPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Status{
|
||||
Version: RegistryVersion,
|
||||
Node: nodeInfo,
|
||||
Sync: syncInfo,
|
||||
Validator: validatorInfo,
|
||||
Validators: validatorSet,
|
||||
NumPeers: numPeers,
|
||||
Peers: peers,
|
||||
DiskUsage: diskUsage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetAccounts(ctx context.Context, addresses []string) ([]*Account, error) {
|
||||
accounts := make([]*Account, len(addresses))
|
||||
for index, address := range addresses {
|
||||
account, err := q.GetAccount(ctx, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts[index] = account
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetAccount(ctx context.Context, address string) (*Account, error) {
|
||||
authQueryClient := authtypes.NewQueryClient(q.ctx)
|
||||
accountResponse, err := authQueryClient.Account(ctx, &authtypes.QueryAccountRequest{Address: address})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var account authtypes.AccountI
|
||||
err = q.ctx.Codec.UnpackAny(accountResponse.GetAccount(), &account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pubKey *string
|
||||
if account.GetPubKey() != nil {
|
||||
pubKeyStr := base64.StdEncoding.EncodeToString(account.GetPubKey().Bytes())
|
||||
pubKey = &pubKeyStr
|
||||
}
|
||||
|
||||
// Get the account balance
|
||||
bankQueryClient := banktypes.NewQueryClient(q.ctx)
|
||||
balance, err := bankQueryClient.AllBalances(ctx, &banktypes.QueryAllBalancesRequest{Address: address})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accNum := strconv.FormatUint(account.GetAccountNumber(), 10)
|
||||
seq := strconv.FormatUint(account.GetSequence(), 10)
|
||||
|
||||
return &Account{
|
||||
Address: address,
|
||||
Number: accNum,
|
||||
Sequence: seq,
|
||||
PubKey: pubKey,
|
||||
Balance: getGQLCoins(balance.GetBalances()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetBondsByIds(ctx context.Context, ids []string) ([]*Bond, error) {
|
||||
bonds := make([]*Bond, len(ids))
|
||||
for index, id := range ids {
|
||||
bondObj, err := q.GetBond(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bonds[index] = bondObj
|
||||
}
|
||||
|
||||
return bonds, nil
|
||||
}
|
||||
|
||||
func (q *queryResolver) GetBond(ctx context.Context, id string) (*Bond, error) {
|
||||
bondQueryClient := bondtypes.NewQueryClient(q.ctx)
|
||||
bondResp, err := bondQueryClient.GetBondById(context.Background(), &bondtypes.QueryGetBondByIdRequest{Id: id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bond := bondResp.GetBond()
|
||||
if bond == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return getGQLBond(bondResp.GetBond())
|
||||
}
|
||||
|
||||
func (q queryResolver) QueryBonds(ctx context.Context, attributes []*KeyValueInput) ([]*Bond, error) {
|
||||
bondQueryClient := bondtypes.NewQueryClient(q.ctx)
|
||||
bonds, err := bondQueryClient.Bonds(context.Background(), &bondtypes.QueryGetBondsRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlResponse := make([]*Bond, len(bonds.GetBonds()))
|
||||
for i, bondObj := range bonds.GetBonds() {
|
||||
gqlBond, err := getGQLBond(bondObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gqlResponse[i] = gqlBond
|
||||
}
|
||||
|
||||
return gqlResponse, nil
|
||||
}
|
||||
|
||||
// QueryBondsByOwner will return bonds by owner
|
||||
func (q queryResolver) QueryBondsByOwner(ctx context.Context, ownerAddresses []string) ([]*OwnerBonds, error) {
|
||||
ownerBonds := make([]*OwnerBonds, len(ownerAddresses))
|
||||
for index, ownerAddress := range ownerAddresses {
|
||||
bondsObj, err := q.GetBondsByOwner(ctx, ownerAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ownerBonds[index] = bondsObj
|
||||
}
|
||||
|
||||
return ownerBonds, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetBondsByOwner(ctx context.Context, address string) (*OwnerBonds, error) {
|
||||
bondQueryClient := bondtypes.NewQueryClient(q.ctx)
|
||||
bondResp, err := bondQueryClient.GetBondsByOwner(context.Background(), &bondtypes.QueryGetBondsByOwnerRequest{Owner: address})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ownerBonds := make([]*Bond, len(bondResp.GetBonds()))
|
||||
for i, bond := range bondResp.GetBonds() {
|
||||
// #nosec G601
|
||||
bondObj, err := getGQLBond(&bond) //nolint: all
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ownerBonds[i] = bondObj
|
||||
}
|
||||
|
||||
return &OwnerBonds{Bonds: ownerBonds, Owner: address}, nil
|
||||
}
|
||||
|
||||
func (q queryResolver) GetAuctionsByIds(ctx context.Context, ids []string) ([]*Auction, error) {
|
||||
auctionQueryClient := auctiontypes.NewQueryClient(q.ctx)
|
||||
gqlAuctionResponse := make([]*Auction, len(ids))
|
||||
for i, id := range ids {
|
||||
auctionObj, err := auctionQueryClient.GetAuction(context.Background(), &auctiontypes.QueryAuctionRequest{Id: id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bidsObj, err := auctionQueryClient.GetBids(context.Background(), &auctiontypes.QueryBidsRequest{AuctionId: id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlAuction, err := GetGQLAuction(auctionObj.GetAuction(), bidsObj.GetBids())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gqlAuctionResponse[i] = gqlAuction
|
||||
}
|
||||
|
||||
return gqlAuctionResponse, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Represents an IPLD link. Links are generally but not necessarily implemented as CIDs
|
||||
type Link string
|
||||
|
||||
func (l Link) String() string {
|
||||
return string(l)
|
||||
}
|
||||
|
||||
// UnmarshalGQLContext implements the graphql.ContextUnmarshaler interface
|
||||
func (l *Link) UnmarshalGQLContext(_ context.Context, v interface{}) error {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("Link must be a string")
|
||||
}
|
||||
*l = Link(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalGQLContext implements the graphql.ContextMarshaler interface
|
||||
func (l Link) MarshalGQLContext(_ context.Context, w io.Writer) error {
|
||||
encodable := map[string]string{
|
||||
"/": l.String(),
|
||||
}
|
||||
return json.NewEncoder(w).Encode(encodable)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/cors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
// Server configures and starts the GQL server.
|
||||
func Server(ctx context.Context, clientCtx client.Context, logger log.Logger) error {
|
||||
if !viper.GetBool("gql-server") {
|
||||
return nil
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
// Add CORS middleware around every request
|
||||
// See https://github.com/rs/cors for full option listing
|
||||
router.Use(cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
Debug: false,
|
||||
}).Handler)
|
||||
|
||||
logFile := viper.GetString("log-file")
|
||||
|
||||
port := viper.GetString("gql-port")
|
||||
|
||||
srv := handler.NewDefaultServer(NewExecutableSchema(Config{Resolvers: &Resolver{
|
||||
ctx: clientCtx,
|
||||
logFile: logFile,
|
||||
}}))
|
||||
|
||||
router.Handle("/", PlaygroundHandler("/api"))
|
||||
|
||||
if viper.GetBool("gql-playground") {
|
||||
apiBase := viper.GetString("gql-playground-api-base")
|
||||
|
||||
router.Handle("/webui", PlaygroundHandler(apiBase+"/api"))
|
||||
router.Handle("/console", PlaygroundHandler(apiBase+"/graphql"))
|
||||
}
|
||||
|
||||
router.Handle("/api", srv)
|
||||
router.Handle("/graphql", srv)
|
||||
|
||||
errCh := make(chan error)
|
||||
|
||||
go func() {
|
||||
logger.Info(fmt.Sprintf("Connect to GraphQL playground url: http://localhost:%s", port))
|
||||
errCh <- http.ListenAndServe(":"+port, router)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Gracefully stop the GQL server.
|
||||
logger.Info("Stopping GQL server...")
|
||||
return nil
|
||||
case err := <-errCh:
|
||||
logger.Error(fmt.Sprintf("Failed to start GQL server: %s", err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
// NodeDataPath is the path to the laconicd data folder.
|
||||
var NodeDataPath = os.ExpandEnv("$HOME/.laconicd/data")
|
||||
|
||||
func getStatusInfo(client client.Context) (*NodeInfo, *SyncInfo, *ValidatorInfo, error) {
|
||||
nodeClient, err := client.GetNode()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
nodeStatus, err := nodeClient.Status(context.Background())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return &NodeInfo{
|
||||
ID: string(nodeStatus.NodeInfo.ID()),
|
||||
Network: nodeStatus.NodeInfo.Network,
|
||||
Moniker: nodeStatus.NodeInfo.Moniker,
|
||||
}, &SyncInfo{
|
||||
LatestBlockHash: nodeStatus.SyncInfo.LatestBlockHash.String(),
|
||||
LatestBlockHeight: strconv.FormatInt(nodeStatus.SyncInfo.LatestBlockHeight, 10),
|
||||
LatestBlockTime: nodeStatus.SyncInfo.LatestBlockTime.String(),
|
||||
CatchingUp: nodeStatus.SyncInfo.CatchingUp,
|
||||
}, &ValidatorInfo{
|
||||
Address: nodeStatus.ValidatorInfo.Address.String(),
|
||||
VotingPower: strconv.FormatInt(nodeStatus.ValidatorInfo.VotingPower, 10),
|
||||
ProposerPriority: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getNetInfo(client client.Context) (string, []*PeerInfo, error) {
|
||||
// TODO: Implement
|
||||
|
||||
// nodeClient, err := client.GetNode()
|
||||
// if err != nil {
|
||||
// return "", nil, err
|
||||
// }
|
||||
// netInfo, err := nodeClient.NetInfo(context.Background())
|
||||
// if err != nil {
|
||||
// return "", nil, err
|
||||
// }
|
||||
|
||||
// peersInfo := make([]*PeerInfo, netInfo.NPeers)
|
||||
// // TODO: find a way to get the peer information from nodeClient
|
||||
// for index, peer := range netInfo.Peers {
|
||||
// peersInfo[index] = &PeerInfo{
|
||||
// Node: &NodeInfo{
|
||||
// ID: string(peer.NodeInfo.ID()),
|
||||
// // Moniker: peer.Node.Moniker,
|
||||
// // Network: peer.Node.Network,
|
||||
// },
|
||||
// // IsOutbound: peer.IsOutbound,
|
||||
// // RemoteIP: peer.RemoteIP,
|
||||
// }
|
||||
// }
|
||||
|
||||
// return strconv.FormatInt(int64(netInfo.NPeers), 10), peersInfo, nil
|
||||
|
||||
return strconv.FormatInt(int64(0), 10), []*PeerInfo{}, nil
|
||||
}
|
||||
|
||||
func getValidatorSet(client client.Context) ([]*ValidatorInfo, error) {
|
||||
nodeClient, err := client.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := nodeClient.Validators(context.Background(), nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validatorSet := make([]*ValidatorInfo, len(res.Validators))
|
||||
for index, validator := range res.Validators {
|
||||
proposerPriority := strconv.FormatInt(validator.ProposerPriority, 10)
|
||||
validatorSet[index] = &ValidatorInfo{
|
||||
Address: validator.Address.String(),
|
||||
VotingPower: strconv.FormatInt(validator.VotingPower, 10),
|
||||
ProposerPriority: &proposerPriority,
|
||||
}
|
||||
}
|
||||
|
||||
return validatorSet, nil
|
||||
}
|
||||
|
||||
// GetDiskUsage returns disk usage for the given path.
|
||||
func GetDiskUsage(dirPath string) (string, error) {
|
||||
out, err := exec.Command("du", "-sh", dirPath).Output() // #nosec G204
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.Fields(string(out))[0], nil
|
||||
}
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt" // #nosec G702
|
||||
"strconv"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
|
||||
auctiontypes "git.vdb.to/cerc-io/laconic2d/x/auction"
|
||||
bondtypes "git.vdb.to/cerc-io/laconic2d/x/bond"
|
||||
registrytypes "git.vdb.to/cerc-io/laconic2d/x/registry"
|
||||
)
|
||||
|
||||
// OwnerAttributeName denotes the owner attribute name for a bond.
|
||||
const OwnerAttributeName = "owner"
|
||||
|
||||
// BondIDAttributeName denotes the record bond ID.
|
||||
const BondIDAttributeName = "bondId"
|
||||
|
||||
// ExpiryTimeAttributeName denotes the record expiry time.
|
||||
const ExpiryTimeAttributeName = "expiryTime"
|
||||
|
||||
func getGQLCoin(coin sdk.Coin) *Coin {
|
||||
gqlCoin := Coin{
|
||||
Type: coin.Denom,
|
||||
Quantity: coin.Amount.BigInt().String(),
|
||||
}
|
||||
|
||||
return &gqlCoin
|
||||
}
|
||||
|
||||
func getGQLCoins(coins sdk.Coins) []*Coin {
|
||||
gqlCoins := make([]*Coin, len(coins))
|
||||
for index, coin := range coins {
|
||||
gqlCoins[index] = getGQLCoin(coin)
|
||||
}
|
||||
|
||||
return gqlCoins
|
||||
}
|
||||
|
||||
func GetGQLNameAuthorityRecord(record *registrytypes.NameAuthority) (*AuthorityRecord, error) {
|
||||
if record == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &AuthorityRecord{
|
||||
OwnerAddress: record.OwnerAddress,
|
||||
OwnerPublicKey: record.OwnerPublicKey,
|
||||
Height: strconv.FormatUint(record.Height, 10),
|
||||
Status: record.Status,
|
||||
BondID: record.GetBondId(),
|
||||
ExpiryTime: record.GetExpiryTime().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getGQLRecord(ctx context.Context, resolver QueryResolver, record registrytypes.Record) (*Record, error) {
|
||||
// Nil record.
|
||||
if record.Deleted {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
node, err := ipld.Decode(record.Attributes, dagjson.Decode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node.Kind() != ipld.Kind_Map {
|
||||
return nil, fmt.Errorf("invalid record attributes")
|
||||
}
|
||||
|
||||
var links []string
|
||||
attributes, err := resolveIPLDNode(node, &links)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
references, err := resolver.GetRecordsByIds(ctx, links)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Record{
|
||||
ID: record.Id,
|
||||
BondID: record.GetBondId(),
|
||||
CreateTime: record.GetCreateTime(),
|
||||
ExpiryTime: record.GetExpiryTime(),
|
||||
Owners: record.GetOwners(),
|
||||
Names: record.GetNames(),
|
||||
Attributes: attributes.(MapValue).Value,
|
||||
References: references,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resolveIPLDNode(node ipld.Node, links *[]string) (Value, error) {
|
||||
switch node.Kind() {
|
||||
case ipld.Kind_Map:
|
||||
var entries []*Attribute
|
||||
for itr := node.MapIterator(); !itr.Done(); {
|
||||
k, v, err := itr.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k.Kind() != ipld.Kind_String {
|
||||
return nil, fmt.Errorf("invalid record attribute key type: %s", k.Kind())
|
||||
}
|
||||
s, err := k.AsString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val, err := resolveIPLDNode(v, links)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, &Attribute{
|
||||
Key: s,
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
return MapValue{entries}, nil
|
||||
case ipld.Kind_List:
|
||||
var values []Value
|
||||
for itr := node.ListIterator(); !itr.Done(); {
|
||||
_, v, err := itr.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val, err := resolveIPLDNode(v, links)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = append(values, val)
|
||||
}
|
||||
return ArrayValue{values}, nil
|
||||
case ipld.Kind_Null:
|
||||
return nil, nil
|
||||
case ipld.Kind_Bool:
|
||||
val, err := node.AsBool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BooleanValue{val}, nil
|
||||
case ipld.Kind_Int:
|
||||
val, err := node.AsInt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: handle bigger ints
|
||||
return IntValue{int(val)}, nil
|
||||
case ipld.Kind_Float:
|
||||
val, err := node.AsFloat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FloatValue{val}, nil
|
||||
case ipld.Kind_String:
|
||||
val, err := node.AsString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return StringValue{val}, nil
|
||||
case ipld.Kind_Bytes:
|
||||
val, err := node.AsBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BytesValue{string(val)}, nil
|
||||
case ipld.Kind_Link:
|
||||
val, err := node.AsLink()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*links = append(*links, val.String())
|
||||
return LinkValue{Link(val.String())}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid node kind")
|
||||
}
|
||||
}
|
||||
|
||||
func getGQLNameRecord(record *registrytypes.NameRecord) (*NameRecord, error) {
|
||||
if record == nil {
|
||||
return nil, fmt.Errorf("got nil record")
|
||||
}
|
||||
|
||||
records := make([]*NameRecordEntry, len(record.History))
|
||||
for index, entry := range record.History {
|
||||
records[index] = getNameRecordEntry(entry)
|
||||
}
|
||||
|
||||
return &NameRecord{
|
||||
Latest: getNameRecordEntry(record.Latest),
|
||||
History: records,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getNameRecordEntry(record *registrytypes.NameRecordEntry) *NameRecordEntry {
|
||||
return &NameRecordEntry{
|
||||
ID: record.Id,
|
||||
Height: strconv.FormatUint(record.Height, 10),
|
||||
}
|
||||
}
|
||||
|
||||
func getGQLBond(bondObj *bondtypes.Bond) (*Bond, error) {
|
||||
// Nil record.
|
||||
if bondObj == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &Bond{
|
||||
ID: bondObj.Id,
|
||||
Owner: bondObj.Owner,
|
||||
Balance: getGQLCoins(bondObj.Balance),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getAuctionBid(bid *auctiontypes.Bid) *AuctionBid {
|
||||
return &AuctionBid{
|
||||
BidderAddress: bid.BidderAddress,
|
||||
Status: bid.Status,
|
||||
CommitHash: bid.CommitHash,
|
||||
CommitTime: bid.GetCommitTime(),
|
||||
RevealTime: bid.GetRevealTime(),
|
||||
CommitFee: getGQLCoin(bid.CommitFee),
|
||||
RevealFee: getGQLCoin(bid.RevealFee),
|
||||
BidAmount: getGQLCoin(bid.BidAmount),
|
||||
}
|
||||
}
|
||||
|
||||
func GetGQLAuction(auction *auctiontypes.Auction, bids []*auctiontypes.Bid) (*Auction, error) {
|
||||
if auction == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
gqlAuction := Auction{
|
||||
ID: auction.Id,
|
||||
Status: auction.Status,
|
||||
OwnerAddress: auction.OwnerAddress,
|
||||
CreateTime: auction.GetCreateTime(),
|
||||
CommitsEndTime: auction.GetCommitsEndTime(),
|
||||
RevealsEndTime: auction.GetRevealsEndTime(),
|
||||
CommitFee: getGQLCoin(auction.CommitFee),
|
||||
RevealFee: getGQLCoin(auction.RevealFee),
|
||||
MinimumBid: getGQLCoin(auction.MinimumBid),
|
||||
WinnerAddress: auction.WinnerAddress,
|
||||
WinnerBid: getGQLCoin(auction.WinningBid),
|
||||
WinnerPrice: getGQLCoin(auction.WinningPrice),
|
||||
}
|
||||
|
||||
auctionBids := make([]*AuctionBid, len(bids))
|
||||
for index, entry := range bids {
|
||||
auctionBids[index] = getAuctionBid(entry)
|
||||
}
|
||||
|
||||
gqlAuction.Bids = auctionBids
|
||||
|
||||
return &gqlAuction, nil
|
||||
}
|
||||
|
||||
func toRPCValue(value *ValueInput) *registrytypes.QueryRecordsRequest_ValueInput {
|
||||
var rpcval registrytypes.QueryRecordsRequest_ValueInput
|
||||
|
||||
switch {
|
||||
case value == nil:
|
||||
return nil
|
||||
case value.Int != nil:
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_Int{Int: int64(*value.Int)}
|
||||
case value.Float != nil:
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_Float{Float: *value.Float}
|
||||
case value.String != nil:
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_String_{String_: *value.String}
|
||||
case value.Boolean != nil:
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_Boolean{Boolean: *value.Boolean}
|
||||
case value.Link != nil:
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_Link{Link: value.Link.String()}
|
||||
case value.Array != nil:
|
||||
var contents registrytypes.QueryRecordsRequest_ArrayInput
|
||||
for _, val := range value.Array {
|
||||
contents.Values = append(contents.Values, toRPCValue(val))
|
||||
}
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_Array{Array: &contents}
|
||||
case value.Map != nil:
|
||||
var contents registrytypes.QueryRecordsRequest_MapInput
|
||||
for _, kv := range value.Map {
|
||||
contents.Values[kv.Key] = toRPCValue(kv.Value)
|
||||
}
|
||||
rpcval.Value = ®istrytypes.QueryRecordsRequest_ValueInput_Map{Map: &contents}
|
||||
}
|
||||
return &rpcval
|
||||
}
|
||||
|
||||
func toRPCAttributes(attrs []*KeyValueInput) []*registrytypes.QueryRecordsRequest_KeyValueInput {
|
||||
kvPairs := []*registrytypes.QueryRecordsRequest_KeyValueInput{}
|
||||
|
||||
for _, value := range attrs {
|
||||
parsedValue := toRPCValue(value.Value)
|
||||
kvPair := ®istrytypes.QueryRecordsRequest_KeyValueInput{
|
||||
Key: value.Key,
|
||||
Value: parsedValue,
|
||||
}
|
||||
kvPairs = append(kvPairs, kvPair)
|
||||
}
|
||||
|
||||
return kvPairs
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package gql
|
||||
|
||||
// RegistryVersion is the registry API version.
|
||||
const RegistryVersion = "0.3.0"
|
||||
Reference in New Issue
Block a user