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 db016c4..769c45b 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -458,12 +458,12 @@ impl QueryRoot { Ok(map_objects) } - async fn leaderboard( + async fn leaderboard_players( &self, ctx: &Context<'_>, spacetime_policy_id: String, - fuel_policy_id: String, - ship_address: String, + pellet_policy_id: String, + spacetime_address: String, ) -> Result, Error> { let pool = ctx .data::() @@ -486,8 +486,8 @@ impl QueryRoot { ORDER BY distance ASC ", spacetime_policy_id, - fuel_policy_id, - ship_address, + pellet_policy_id, + spacetime_address, ) .fetch_all(pool) .await @@ -508,6 +508,56 @@ impl QueryRoot { Ok(map_objects) } + + async fn leaderboard_winners( + &self, + ctx: &Context<'_>, + spacetime_policy_id: String, + pellet_policy_id: String, + spacetime_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 + ", + spacetime_policy_id, + pellet_policy_id, + spacetime_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/components/ui/NavBar.tsx b/frontend/src/components/ui/NavBar.tsx index 764eed9..8b9b462 100644 --- a/frontend/src/components/ui/NavBar.tsx +++ b/frontend/src/components/ui/NavBar.tsx @@ -1,3 +1,5 @@ +"use client"; + import Link from 'next/link'; import Image from 'next/image'; import { usePathname } from 'next/navigation'; diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 0d3cce1..72978b1 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -80,7 +80,7 @@ export default function Landing() { Network | { challenge.network }

- Shipyard Policy | { challenge.shipyardPolicyId } + Spacetime Policy | { challenge.spacetimePolicyId }

{ challenge.label } diff --git a/frontend/src/pages/leaderboard/index.tsx b/frontend/src/pages/leaderboard/index.tsx index 9b85400..533d6b2 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($spacetimePolicyId: String, $pelletPolicyId: String, $spacetimeAddress: String) { + leaderboardPlayers(spacetimePolicyId: $spacetimePolicyId, pelletPolicyId: $pelletPolicyId, spacetimeAddress: $spacetimeAddress) { ranking, address, shipName, @@ -17,8 +17,21 @@ const GET_LEADERBOARD_RECORDS = gql` } `; +const GET_LEADERBOARD_WINNERS_RECORDS = gql` + query LeaderboardWinners($spacetimePolicyId: String, $pelletPolicyId: String, $spacetimeAddress: String) { + leaderboardWinners(spacetimePolicyId: $spacetimePolicyId, pelletPolicyId: $pelletPolicyId, spacetimeAddress: $spacetimeAddress) { + 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} + + + )} ); 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, + spacetimePolicyId: current().spacetimePolicyId, + pelletPolicyId: current().pelletPolicyId, + spacetimeAddress: current().spacetimeAddress, }, }); - 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()}