diff --git a/backend/.sqlx/query-c8d24ca519a169649988c11cd219c72e368b507d97cf30946aa188deff70b9c3.json b/backend/.sqlx/query-c8d24ca519a169649988c11cd219c72e368b507d97cf30946aa188deff70b9c3.json new file mode 100644 index 0000000..66d5b5d --- /dev/null +++ b/backend/.sqlx/query-c8d24ca519a169649988c11cd219c72e368b507d97cf30946aa188deff70b9c3.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n id,\n CAST(utxo_subject_amount(era, cbor, decode($2::varchar, 'hex')) AS INTEGER) AS fuel,\n CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 2 ->> 'bytes' AS TEXT) AS ship_token_name,\n CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 3 ->> 'bytes' AS TEXT) AS pilot_token_name\n FROM \n utxos\n WHERE \n utxo_address(era, cbor) = from_bech32($3::varchar)\n AND utxo_has_policy_id(era, cbor, decode($1::varchar, 'hex'))\n AND ABS(CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 0 ->> 'int' AS INTEGER)) + ABS(CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 1 ->> 'int' AS INTEGER)) = 0\n order by slot ASC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "fuel", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "ship_token_name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "pilot_token_name", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [ + false, + null, + null, + null + ] + }, + "hash": "c8d24ca519a169649988c11cd219c72e368b507d97cf30946aa188deff70b9c3" +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 12ddd89..5825d9a 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -513,7 +513,7 @@ impl QueryRoot { actions } - async fn leaderboard( + async fn leaderboard_players( &self, ctx: &Context<'_>, shipyard_policy_id: String, @@ -563,6 +563,56 @@ impl QueryRoot { Ok(map_objects) } + + async fn leaderboard_winners( + &self, + ctx: &Context<'_>, + shipyard_policy_id: String, + fuel_policy_id: String, + ship_address: String, + ) -> Result, Error> { + let pool = ctx + .data::() + .map_err(|e| Error::new(e.message))?; + + let fetched_objects = sqlx::query!( + " + SELECT + id, + CAST(utxo_subject_amount(era, cbor, decode($2::varchar, 'hex')) AS INTEGER) AS fuel, + CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 2 ->> 'bytes' AS TEXT) AS ship_token_name, + CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 3 ->> 'bytes' AS TEXT) AS pilot_token_name + FROM + utxos + WHERE + utxo_address(era, cbor) = from_bech32($3::varchar) + AND utxo_has_policy_id(era, cbor, decode($1::varchar, 'hex')) + AND ABS(CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 0 ->> 'int' AS INTEGER)) + ABS(CAST(utxo_plutus_data(era, cbor) -> 'fields' -> 1 ->> 'int' AS INTEGER)) = 0 + order by slot ASC + ", + shipyard_policy_id, + fuel_policy_id, + ship_address, + ) + .fetch_all(pool) + .await + .map_err(|e| Error::new(e.to_string()))?; + + let map_objects: Vec = fetched_objects + .into_iter() + .enumerate() + .map(|(i, record)| LeaderboardRecord { + ranking: i as i32 + 1, + address: record.id, + ship_name: record.ship_token_name.unwrap_or_default(), + pilot_name: record.pilot_token_name.unwrap_or_default(), + fuel: record.fuel.unwrap_or(0), + distance: 0 + }) + .collect(); + + Ok(map_objects) + } } type AsteriaSchema = Schema; diff --git a/frontend/src/pages/leaderboard/index.tsx b/frontend/src/pages/leaderboard/index.tsx index ec91624..f6a86c0 100644 --- a/frontend/src/pages/leaderboard/index.tsx +++ b/frontend/src/pages/leaderboard/index.tsx @@ -4,9 +4,9 @@ import { useChallengeStore, Challenge } from '@/stores/challenge'; const PAGE_SIZE = 10; -const GET_LEADERBOARD_RECORDS = gql` - query Leaderboard($shipyardPolicyId: String, $fuelPolicyId: String, $shipAddress: String) { - leaderboard(shipyardPolicyId: $shipyardPolicyId, fuelPolicyId: $fuelPolicyId, shipAddress: $shipAddress) { +const GET_LEADERBOARD_PLAYERS_RECORDS = gql` + query LeaderboardPlayers($shipyardPolicyId: String, $fuelPolicyId: String, $shipAddress: String) { + leaderboardPlayers(shipyardPolicyId: $shipyardPolicyId, fuelPolicyId: $fuelPolicyId, shipAddress: $shipAddress) { ranking, address, shipName, @@ -17,8 +17,21 @@ const GET_LEADERBOARD_RECORDS = gql` } `; +const GET_LEADERBOARD_WINNERS_RECORDS = gql` + query LeaderboardWinners($shipyardPolicyId: String, $fuelPolicyId: String, $shipAddress: String) { + leaderboardWinners(shipyardPolicyId: $shipyardPolicyId, fuelPolicyId: $fuelPolicyId, shipAddress: $shipAddress) { + ranking, + address, + shipName, + pilotName, + fuel + } + } +`; + interface LeaderboardQueryResult { - leaderboard: LeaderboardRecord[]; + leaderboardPlayers: LeaderboardRecord[]; + leaderboardWinners: LeaderboardRecord[]; } interface LeaderboardRecord { @@ -31,6 +44,7 @@ interface LeaderboardRecord { } interface RecordProps { + leaderboard: boolean; challenge: Challenge; record: LeaderboardRecord; } @@ -89,27 +103,35 @@ const LeaderboardRow: React.FunctionComponent = (props: RecordProps {props.record.fuel} - - - {`${props.record.distance}km`} - - + {props.leaderboard && ( + + + {`${props.record.distance}km`} + + + )} ); export default function Leaderboard() { + const [ offset, setOffset ] = useState(0); + const [ leaderboard, setLeaderboard ] = useState(true); + const { current } = useChallengeStore(); - const { data } = useQuery(GET_LEADERBOARD_RECORDS, { + const { data } = useQuery(leaderboard ? GET_LEADERBOARD_PLAYERS_RECORDS : GET_LEADERBOARD_WINNERS_RECORDS, { variables: { shipyardPolicyId: current().shipyardPolicyId, fuelPolicyId: current().fuelPolicyId, shipAddress: current().shipAddress, }, }); - const [ offset, setOffset ] = useState(0); const hasNextPage = () => { - return data && data.leaderboard && offset + PAGE_SIZE < data.leaderboard.slice(3).length; + if (leaderboard) { + return data && data.leaderboardPlayers && offset + PAGE_SIZE < data.leaderboardPlayers.slice(3).length; + } else { + return data && data.leaderboardWinners && offset + PAGE_SIZE < data.leaderboardWinners.length; + } } const nextPage = () => { @@ -128,9 +150,30 @@ export default function Leaderboard() { } } + const getPagination = () => { + if (leaderboard) { + return `Displaying ${offset+1}-${offset+PAGE_SIZE} of ${data && data.leaderboardPlayers ? data.leaderboardPlayers.length-3 : 0}`; + } else { + return `Displaying ${offset+1}-${offset+PAGE_SIZE} of ${data && data.leaderboardWinners ? data.leaderboardWinners.length : 0}`; + } + } + const getPageData = () => { - if (!data || !data.leaderboard) return []; - return data.leaderboard.slice(3).slice(offset, offset+PAGE_SIZE); + if (leaderboard) { + if (!data || !data.leaderboardPlayers) return []; + return data.leaderboardPlayers.slice(3).slice(offset, offset+PAGE_SIZE); + } else { + if (!data || !data.leaderboardWinners) return []; + return data.leaderboardWinners.slice(offset, offset+PAGE_SIZE); + } + } + + const getColumns = () => { + if (leaderboard) { + return ['Ranking', 'Address', 'Ship name', 'Pilot name', 'Fuel', 'Distance']; + } else { + return ['Ranking', 'Address', 'Ship name', 'Pilot name', 'Fuel']; + } } return ( @@ -138,7 +181,9 @@ export default function Leaderboard() {

- DISTANCE TO ASTERIA + setLeaderboard(!leaderboard) }> + {`ASTERIA ${ leaderboard ? 'PLAYERS' : 'WINNERS' } >`} +

-
- {data && data.leaderboard && data.leaderboard.slice(0, 3).map(record => - - )} -
+ {leaderboard && ( +
+ {data && data.leaderboardPlayers && data.leaderboardPlayers.slice(0, 3).map(record => + + )} +
+ )} - {['Ranking', 'Address', 'Ship name', 'Pilot name', 'Fuel', 'Distance'].map(header => + {getColumns().map(header => )} {getPageData().map(record => - + )}
{header}

- {`Displaying ${offset+1}-${offset+PAGE_SIZE} of ${data && data.leaderboard ? data.leaderboard.length-3 : 0}`} + {getPagination()}