Skip to content

Commit

Permalink
Merge pull request #15 from constata-eu/initial_asami_explorer
Browse files Browse the repository at this point in the history
Initial asami explorer
  • Loading branch information
nubis authored Dec 16, 2024
2 parents 995b788 + 32e91d8 commit 4d5da15
Show file tree
Hide file tree
Showing 64 changed files with 2,197 additions and 343 deletions.
25 changes: 25 additions & 0 deletions api/migrations/20241213120000_add_indexed_columns_for_explorer.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ALTER TABLE accounts ADD COLUMN force_hydrate BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE accounts ADD COLUMN total_collabs INT4 NOT NULL DEFAULT 0;
ALTER TABLE accounts ADD COLUMN total_collab_rewards VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN total_campaigns INT4 NOT NULL DEFAULT 0;
ALTER TABLE accounts ADD COLUMN total_collabs_received INT4 NOT NULL DEFAULT 0;
ALTER TABLE accounts ADD COLUMN total_spent VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN unclaimed_asami_balance VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN unclaimed_doc_balance VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN asami_balance VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN doc_balance VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN rbtc_balance VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE accounts ADD COLUMN last_on_chain_sync TIMESTAMPTZ NOT NULL DEFAULT now();

ALTER TABLE handles ADD COLUMN force_hydrate BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE handles ADD COLUMN total_collabs INT4 NOT NULL DEFAULT 0;
ALTER TABLE handles ADD COLUMN total_collab_rewards VARCHAR NOT NULL DEFAULT '0x0';

ALTER TABLE campaigns ADD COLUMN force_hydrate BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE campaigns ADD COLUMN total_collabs INT4 NOT NULL DEFAULT 0;
ALTER TABLE campaigns ADD COLUMN total_spent VARCHAR NOT NULL DEFAULT '0x0';
ALTER TABLE campaigns ADD COLUMN total_budget VARCHAR NOT NULL DEFAULT '0x0';

UPDATE accounts SET force_hydrate = TRUE;
UPDATE handles SET force_hydrate = TRUE;
UPDATE campaigns SET force_hydrate = TRUE;
34 changes: 34 additions & 0 deletions api/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::models::{CampaignStatus, CollabStatus};

use super::{error::Error, models, *};
use ethers::{abi::AbiEncode, types::U256};
use juniper::{
graphql_object, graphql_value, EmptySubscription, FieldError, FieldResult, GraphQLInputObject, GraphQLObject,
IntrospectionFormat,
Expand All @@ -24,6 +27,12 @@ mod claim_account_request;
use claim_account_request::*;
mod campaign_preference;
use campaign_preference::*;
mod on_chain_job;
use on_chain_job::*;
mod audit_log_entry;
use audit_log_entry::*;
mod stats;
use stats::*;

type JsonResult<T> = AsamiResult<Json<T>>;

Expand Down Expand Up @@ -293,6 +302,31 @@ make_graphql_query! {
[Handle, allHandles, allHandlesMeta, "_allHandlesMeta", HandleFilter, i32],
[Collab, allCollabs, allCollabsMeta, "_allCollabsMeta", CollabFilter, i32],
[CampaignPreference, allCampaignPreferences, allCampaignPreferencesMeta, "_allCampaignPreferencesMeta", CampaignPreferenceFilter, i32],
[OnChainJob, allOnChainJobs, allOnChainJobsMeta, "_allOnChainJobsMeta", OnChainJobFilter, i32],
[AuditLogEntry, allAuditLogEntries, allAuditLogEntriesMeta, "_allAuditLogEntriesMeta", AuditLogEntryFilter, i32],
}

#[graphql(name="Stats")]
async fn stats(context: &Context, _id: i32) -> FieldResult<Stats> {
let total_rewards_paid: U256 = context.app
.collab()
.select()
.status_eq(CollabStatus::Cleared)
.all()
.await?
.iter()
.map(|c| c.reward_u256() )
.fold(U256::zero(), |acc, x| acc + x);

Ok(Stats {
id: 0,
total_active_handles:
context.app.db.fetch_one_scalar::<i32>(sqlx::query_scalar("SELECT count(distinct handle_id)::INT4 FROM collabs")).await?,
total_collabs: context.app.collab().select().count().await?.try_into()?,
total_campaigns: context.app.campaign().select().status_eq(CampaignStatus::Published).count().await?.try_into()?,
total_rewards_paid: total_rewards_paid.encode_hex(),
date: chrono::Utc::now()
})
}
}

Expand Down
94 changes: 47 additions & 47 deletions api/src/api/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use super::{
#[serde(rename_all = "camelCase")]
#[graphql(description = "A summary view of everything important regarding a member account.")]
pub struct Account {
#[graphql(description = "Account ID as stored in the ASAMI contract.")]
id: String,
#[graphql(description = "Account ID as integer")]
id: i32,
#[graphql(description = "Status of this account claim request, if any.")]
status: AccountStatus,
#[graphql(description = "The address of a claimed account.")]
Expand All @@ -27,31 +27,55 @@ pub struct Account {
description = "Is the account happy with receiving gasless claims if they are allowed in the smart contract?"
)]
allows_gasless: bool,
#[graphql(description = "Date in which this account was created")]
created_at: UtcDateTime,

#[graphql(description = "Collabs made")]
total_collabs: i32,
#[graphql(description = "Rewards from collabs made")]
total_collab_rewards: String,
#[graphql(description = "Campaigns created")]
total_campaigns: i32,
#[graphql(description = "Collabs received in campaings")]
total_collabs_received: i32,
#[graphql(description = "Total spent on collabs received")]
total_spent: String,
}

#[derive(Debug, Clone, Default, GraphQLInputObject, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountFilter {
ids: Option<Vec<String>>,
id_eq: Option<String>,
addr_eq: Option<String>,
ids: Option<Vec<i32>>,
id_eq: Option<i32>,
addr_like: Option<String>,
}

#[rocket::async_trait]
impl Showable<models::Account, AccountFilter> for Account {
fn sort_field_to_order_by(field: &str) -> Option<models::AccountOrderBy> {
match field {
"id" => Some(AccountOrderBy::Id),
"asamiBalance" => Some(AccountOrderBy::AsamiBalance),
"docBalance" => Some(AccountOrderBy::DocBalance),
"rbtcBalance" => Some(AccountOrderBy::RbtcBalance),
"unclaimedAsamiBalance" => Some(AccountOrderBy::UnclaimedAsamiBalance),
"unclaimedDocBalance" => Some(AccountOrderBy::UnclaimedDocBalance),
"totalCollabs" => Some(AccountOrderBy::TotalCollabs),
"totalCollabRewards" => Some(AccountOrderBy::TotalCollabRewards),
"totalCampaigns" => Some(AccountOrderBy::TotalCampaigns),
"totalCollabsReceived" => Some(AccountOrderBy::TotalCollabsReceived),
"totalSpent" => Some(AccountOrderBy::TotalSpent),
"createdAt" => Some(AccountOrderBy::CreatedAt),
_ => None,
}
}

fn filter_to_select(_context: &Context, filter: Option<AccountFilter>) -> FieldResult<models::SelectAccount> {
if let Some(f) = filter {
Ok(models::SelectAccount {
id_in: f.ids,
id_eq: f.id_eq,
addr_eq: f.addr_eq,
id_in: f.ids.map(|ids| ids.into_iter().map(i32_to_hex).collect() ),
id_eq: f.id_eq.map(i32_to_hex),
addr_like: into_like_search(f.addr_like),
..Default::default()
})
} else {
Expand All @@ -61,54 +85,30 @@ impl Showable<models::Account, AccountFilter> for Account {

fn select_by_id(_context: &Context, id: String) -> FieldResult<models::SelectAccount> {
Ok(models::SelectAccount {
id_eq: Some(id),
id_eq: Some(wei(id).encode_hex()),
..Default::default()
})
}

async fn db_to_graphql(_context: &Context, d: models::Account) -> AsamiResult<Self> {
let asami = &d.state.on_chain.asami_contract;
let address = d.decoded_addr()?;

let (doc_balance, asami_balance, rbtc_balance, unclaimed_doc_balance, unclaimed_asami_balance) = match address {
Some(address) => {
let account = asami.accounts(address).call().await?;
(
d.state.on_chain.doc_contract.balance_of(address).call().await?.encode_hex(),
asami.balance_of(address).call().await?.encode_hex(),
asami.client().get_balance(address, None).await?.encode_hex(),
account.4.encode_hex(),
account.3.encode_hex(),
)
}
None => {
let admin = d.state.settings.rsk.admin_address;
let (unclaimed_doc, unclaimed_asami) = asami
.get_sub_account(admin, u256(d.id()))
.call()
.await
.map(|s| {
(
s.unclaimed_doc_balance.encode_hex(),
s.unclaimed_asami_balance.encode_hex(),
)
})
.unwrap_or_else(|_| (weihex("0"), weihex("0")));

(weihex("0"), weihex("0"), weihex("0"), unclaimed_doc, unclaimed_asami)
}
};
let addr = d.decoded_addr()?.map(|x| format!("{x:?}"));

Ok(Account {
id: d.attrs.id,
id: hex_to_i32(&d.attrs.id)?,
status: d.attrs.status,
addr: address.map(|x| format!("{x:?}")),
asami_balance,
doc_balance,
rbtc_balance,
unclaimed_asami_balance,
unclaimed_doc_balance,
addr,
asami_balance: d.attrs.asami_balance,
doc_balance: d.attrs.doc_balance,
rbtc_balance: d.attrs.rbtc_balance,
unclaimed_asami_balance: d.attrs.unclaimed_asami_balance,
unclaimed_doc_balance: d.attrs.unclaimed_doc_balance,
allows_gasless: d.attrs.allows_gasless,
created_at: d.attrs.created_at,
total_collabs: d.attrs.total_collabs,
total_collab_rewards: d.attrs.total_collab_rewards,
total_campaigns: d.attrs.total_campaigns,
total_collabs_received: d.attrs.total_collabs_received,
total_spent: d.attrs.total_spent,
})
}
}
69 changes: 69 additions & 0 deletions api/src/api/audit_log_entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use super::{
models::{self, *},
*,
};

#[derive(Debug, GraphQLObject, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
#[graphql(description = "An message logged for any part of the system")]
pub struct AuditLogEntry {
#[graphql(description = "Unique numeric identifier of this resource")]
id: i32,
severity: AuditLogSeverity,
created_at: UtcDateTime,
kind: String,
subkind: String,
context: String,
loggable_type: Option<String>,
loggable_id: Option<String>,
}

#[derive(Debug, Clone, Default, GraphQLInputObject, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuditLogEntryFilter {
ids: Option<Vec<i32>>,
id_eq: Option<i32>,
}

#[rocket::async_trait]
impl Showable<models::AuditLogEntry, AuditLogEntryFilter> for AuditLogEntry {
fn sort_field_to_order_by(field: &str) -> Option<models::AuditLogEntryOrderBy> {
match field {
"id" => Some(AuditLogEntryOrderBy::Id),
"created_at" => Some(AuditLogEntryOrderBy::CreatedAt),
_ => None,
}
}

fn filter_to_select(_context: &Context, filter: Option<AuditLogEntryFilter>) -> FieldResult<models::SelectAuditLogEntry> {
if let Some(f) = filter {
Ok(models::SelectAuditLogEntry {
id_in: f.ids,
id_eq: f.id_eq,
..Default::default()
})
} else {
Ok(Default::default())
}
}

fn select_by_id(_context: &Context, id: i32) -> FieldResult<models::SelectAuditLogEntry> {
Ok(models::SelectAuditLogEntry {
id_eq: Some(id),
..Default::default()
})
}

async fn db_to_graphql(_context: &Context, d: models::AuditLogEntry) -> AsamiResult<Self> {
Ok(AuditLogEntry {
id: d.attrs.id,
severity: d.attrs.severity,
created_at: d.attrs.created_at,
kind: d.attrs.kind,
subkind: d.attrs.subkind,
context: d.attrs.context,
loggable_type: d.attrs.loggable_type,
loggable_id: d.attrs.loggable_id,
})
}
}
35 changes: 27 additions & 8 deletions api/src/api/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct Campaign {
#[graphql(description = "Unique numeric identifier of this resource")]
id: i32,
#[graphql(description = "The id of the account that created this.")]
account_id: String,
account_id: i32,
#[graphql(description = "The total budget for this campaign to be collected by users.")]
budget: String,
#[graphql(description = "The kind of campaign, what's expected from the member.")]
Expand All @@ -29,28 +29,35 @@ pub struct Campaign {
topic_ids: Vec<i32>,
#[graphql(description = "The reward you would receive. None means it does not apply.")]
you_would_receive: Option<String>,
#[graphql(description = "How many collabs did the campaign get")]
total_collabs: i32,
#[graphql(description = "How much the campaign has spent so far in rewards")]
total_spent: String,
#[graphql(description = "The campaign total budget: remaining + spent")]
total_budget: String,
}

#[derive(Debug, Clone, Default, GraphQLInputObject, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CampaignFilter {
ids: Option<Vec<i32>>,
id_eq: Option<i32>,
account_id_eq: Option<String>,
account_id_eq: Option<i32>,
budget_gt: Option<String>,
budget_lt: Option<String>,
budget_eq: Option<String>,
briefing_hash_eq: Option<String>,
briefing_hash_like: Option<String>,
briefing_json_like: Option<String>,
status_ne: Option<CampaignStatus>,
available_to_account_id: Option<String>,
status_eq: Option<CampaignStatus>,
available_to_account_id: Option<i32>,
}

async fn make_available_to_account_id_filter(context: &Context, account_id: String) -> FieldResult<CampaignFilter> {
async fn make_available_to_account_id_filter(context: &Context, account_id: i32) -> FieldResult<CampaignFilter> {
let offers = context
.app
.account()
.find(account_id)
.find(&i32_to_hex(account_id))
.await?
.campaign_offers()
.await?
Expand All @@ -68,6 +75,11 @@ impl Showable<models::Campaign, CampaignFilter> for Campaign {
fn sort_field_to_order_by(field: &str) -> Option<models::CampaignOrderBy> {
match field {
"id" => Some(CampaignOrderBy::Id),
"budget" => Some(CampaignOrderBy::Budget),
"totalCollabs" => Some(CampaignOrderBy::TotalCollabs),
"totalSpent" => Some(CampaignOrderBy::TotalSpent),
"totalBudget" => Some(CampaignOrderBy::TotalBudget),
"validUntil" => Some(CampaignOrderBy::ValidUntil),
"createdAt" => Some(CampaignOrderBy::CreatedAt),
_ => None,
}
Expand Down Expand Up @@ -102,11 +114,15 @@ impl Showable<models::Campaign, CampaignFilter> for Campaign {
if let Some(f) = filter {
Ok(models::SelectCampaign {
id_in: f.ids,
account_id_eq: f.account_id_eq,
account_id_eq: f.account_id_eq.map(i32_to_hex),
id_eq: f.id_eq,
budget_gt: f.budget_gt,
budget_lt: f.budget_lt,
budget_eq: f.budget_eq,
status_eq: f.status_eq,
status_ne: f.status_ne,
briefing_json_like: into_like_search(f.briefing_json_like),
briefing_hash_like: into_like_search(f.briefing_hash_like),
..Default::default()
})
} else {
Expand All @@ -129,7 +145,7 @@ impl Showable<models::Campaign, CampaignFilter> for Campaign {
let budget = d.available_budget().await.unwrap_or(d.budget_u256()).encode_hex();
Ok(Campaign {
id: d.attrs.id,
account_id: d.attrs.account_id,
account_id: hex_to_i32(&d.attrs.account_id)?,
budget,
valid_until: d.attrs.valid_until,
campaign_kind: d.attrs.campaign_kind,
Expand All @@ -139,6 +155,9 @@ impl Showable<models::Campaign, CampaignFilter> for Campaign {
status: d.attrs.status,
topic_ids,
you_would_receive,
total_collabs: d.attrs.total_collabs,
total_spent: d.attrs.total_spent,
total_budget: d.attrs.total_budget,
})
}
}
Expand Down
Loading

0 comments on commit 4d5da15

Please sign in to comment.