A decentralized music tracking and discovery platform built on AT Protocol 🎵

[web] start using rocksky xrpc api

+93 -58
+4 -4
apps/web/src/api/charts.ts
··· 7 8 export const getSongChart = async (uri: string) => { 9 const response = await axios.get( 10 - `${API_URL}/public/scrobbleschart?songuri=${uri}` 11 ); 12 if (response.status !== 200) { 13 return []; ··· 17 18 export const getArtistChart = async (uri: string) => { 19 const response = await axios.get( 20 - `${API_URL}/public/scrobbleschart?artisturi=${uri}` 21 ); 22 if (response.status !== 200) { 23 return []; ··· 27 28 export const getAlbumChart = async (uri: string) => { 29 const response = await axios.get( 30 - `${API_URL}/public/scrobbleschart?albumuri=${uri}` 31 ); 32 if (response.status !== 200) { 33 return []; ··· 37 38 export const getProfileChart = async (did: string) => { 39 const response = await axios.get( 40 - `${API_URL}/public/scrobbleschart?did=${did}` 41 ); 42 if (response.status !== 200) { 43 return [];
··· 7 8 export const getSongChart = async (uri: string) => { 9 const response = await axios.get( 10 + `${API_URL}/xrpc/app.rocksky.charts.getScrobblesChart?songuri=${uri}` 11 ); 12 if (response.status !== 200) { 13 return []; ··· 17 18 export const getArtistChart = async (uri: string) => { 19 const response = await axios.get( 20 + `${API_URL}/xrpc/app.rocksky.charts.getScrobblesChart?artisturi=${uri}` 21 ); 22 if (response.status !== 200) { 23 return []; ··· 27 28 export const getAlbumChart = async (uri: string) => { 29 const response = await axios.get( 30 + `${API_URL}/xrpc/app.rocksky.charts.getScrobblesChart?albumuri=${uri}` 31 ); 32 if (response.status !== 200) { 33 return []; ··· 37 38 export const getProfileChart = async (did: string) => { 39 const response = await axios.get( 40 + `${API_URL}/xrpc/app.rocksky.charts.getScrobblesChart?did=${did}` 41 ); 42 if (response.status !== 200) { 43 return [];
+6
apps/web/src/api/index.ts
···
··· 1 + import axios from "axios"; 2 + import { API_URL } from "../consts"; 3 + 4 + export const client = axios.create({ 5 + baseURL: API_URL, 6 + });
+6 -2
apps/web/src/api/profile.ts
··· 8 }; 9 10 export const getProfileStatsByDid = async (did: string) => { 11 - const response = await axios.get(`${API_URL}/users/${did}/stats`); 12 return response.data; 13 }; 14 ··· 18 size = 10 19 ): Promise<Scrobble[]> => { 20 const response = await axios.get<Scrobble[]>( 21 - `${API_URL}/users/${did}/scrobbles?size=${size}&offset=${offset}` 22 ); 23 return response.data; 24 };
··· 8 }; 9 10 export const getProfileStatsByDid = async (did: string) => { 11 + const response = await axios.get( 12 + `${API_URL}/xrpc/app.rocksky.stats.getStats`, 13 + { params: { did } } 14 + ); 15 return response.data; 16 }; 17 ··· 21 size = 10 22 ): Promise<Scrobble[]> => { 23 const response = await axios.get<Scrobble[]>( 24 + `${API_URL}/users/${did}/scrobbles`, 25 + { params: { size, offset } } 26 ); 27 return response.data; 28 };
+36 -22
apps/web/src/hooks/useChart.tsx
··· 1 import { useQuery } from "@tanstack/react-query"; 2 - import axios from "axios"; 3 import useSWR from "swr"; 4 - import { getArtistChart, getSongChart } from "../api/charts"; 5 import { API_URL } from "../consts"; 6 7 export const useScrobblesChartQuery = () => 8 useQuery({ 9 queryKey: ["scrobblesChart"], 10 - queryFn: () => 11 - fetch(`${API_URL}/public/scrobbleschart`, { 12 - method: "GET", 13 - }).then((res) => res.json()), 14 }); 15 16 export const useSongChartQuery = (uri: string) => 17 useQuery({ 18 queryKey: ["songChart", uri], 19 queryFn: () => getSongChart(uri), 20 }); 21 22 export const useArtistChartQuery = (uri: string) => 23 useQuery({ 24 queryKey: ["artistChart", uri], 25 queryFn: () => getArtistChart(uri), 26 }); 27 28 export const useAlbumChartQuery = (uri: string) => 29 useQuery({ 30 queryKey: ["albumChart", uri], 31 - queryFn: () => getArtistChart(uri), 32 }); 33 34 export const useProfileChartQuery = (did: string) => 35 useQuery({ 36 queryKey: ["profileChart", did], 37 - queryFn: () => getArtistChart(did), 38 }); 39 40 function useChart() { ··· 43 method: "GET", 44 }).then((res) => res.json()); 45 46 - const { data: scrobblesChart } = useSWR("/public/scrobbleschart", fetcher); 47 48 const getScrobblesChart = () => { 49 - return scrobblesChart || []; 50 }; 51 52 const getSongChart = async (uri: string) => { 53 - const response = await axios.get( 54 - `${API_URL}/public/scrobbleschart?songuri=${uri}` 55 ); 56 if (response.status !== 200) { 57 return []; 58 } 59 - return response.data; 60 }; 61 62 const getArtistChart = async (uri: string) => { 63 - const response = await axios.get( 64 - `${API_URL}/public/scrobbleschart?artisturi=${uri}` 65 ); 66 if (response.status !== 200) { 67 return []; 68 } 69 - return response.data; 70 }; 71 72 const getAlbumChart = async (uri: string) => { 73 - const response = await axios.get( 74 - `${API_URL}/public/scrobbleschart?albumuri=${uri}` 75 ); 76 if (response.status !== 200) { 77 return []; 78 } 79 - return response.data; 80 }; 81 82 const getProfileChart = async (did: string) => { 83 - const response = await axios.get( 84 - `${API_URL}/public/scrobbleschart?did=${did}` 85 ); 86 if (response.status !== 200) { 87 return []; 88 } 89 - return response.data; 90 }; 91 92 return {
··· 1 import { useQuery } from "@tanstack/react-query"; 2 import useSWR from "swr"; 3 + import { client } from "../api"; 4 + import { 5 + getAlbumChart, 6 + getArtistChart, 7 + getProfileChart, 8 + getSongChart, 9 + } from "../api/charts"; 10 import { API_URL } from "../consts"; 11 12 export const useScrobblesChartQuery = () => 13 useQuery({ 14 queryKey: ["scrobblesChart"], 15 + queryFn: () => client.get("/xrpc/app.rocksky.charts.getScrobblesChart"), 16 + select: ({ data }) => data.scrobbles || [], 17 }); 18 19 export const useSongChartQuery = (uri: string) => 20 useQuery({ 21 queryKey: ["songChart", uri], 22 queryFn: () => getSongChart(uri), 23 + select: (data) => data.scrobbles || [], 24 }); 25 26 export const useArtistChartQuery = (uri: string) => 27 useQuery({ 28 queryKey: ["artistChart", uri], 29 queryFn: () => getArtistChart(uri), 30 + select: (data) => data.scrobbles || [], 31 }); 32 33 export const useAlbumChartQuery = (uri: string) => 34 useQuery({ 35 queryKey: ["albumChart", uri], 36 + queryFn: () => getAlbumChart(uri), 37 + select: (data) => data.scrobbles || [], 38 }); 39 40 export const useProfileChartQuery = (did: string) => 41 useQuery({ 42 queryKey: ["profileChart", did], 43 + queryFn: () => getProfileChart(did), 44 + select: (data) => data.scrobbles || [], 45 }); 46 47 function useChart() { ··· 50 method: "GET", 51 }).then((res) => res.json()); 52 53 + const { data: scrobblesChart } = useSWR( 54 + "/xrpc/app.rocksky.charts.getScrobblesChart", 55 + fetcher 56 + ); 57 58 const getScrobblesChart = () => { 59 + return scrobblesChart?.scrobbles || []; 60 }; 61 62 const getSongChart = async (uri: string) => { 63 + const response = await client.get( 64 + "/xrpc/app.rocksky.charts.getScrobblesChart", 65 + { params: { songuri: uri } } 66 ); 67 if (response.status !== 200) { 68 return []; 69 } 70 + return response.data.scrobbles; 71 }; 72 73 const getArtistChart = async (uri: string) => { 74 + const response = await client.get( 75 + "/xrpc/app.rocksky.charts.getScrobblesChart", 76 + { params: { artisturi: uri } } 77 ); 78 if (response.status !== 200) { 79 return []; 80 } 81 + return response.data.scrobbles; 82 }; 83 84 const getAlbumChart = async (uri: string) => { 85 + const response = await client.get( 86 + "/xrpc/app.rocksky.charts.getScrobblesChart", 87 + { params: { albumuri: uri } } 88 ); 89 if (response.status !== 200) { 90 return []; 91 } 92 + return response.data.scrobbles; 93 }; 94 95 const getProfileChart = async (did: string) => { 96 + const response = await client.get( 97 + "/xrpc/app.rocksky.charts.getScrobblesChart", 98 + { params: { did } } 99 ); 100 if (response.status !== 200) { 101 return []; 102 } 103 + return response.data.scrobbles; 104 }; 105 106 return {
+6 -5
apps/web/src/hooks/useFeed.tsx
··· 1 import { useQuery } from "@tanstack/react-query"; 2 import { getFeedByUri } from "../api/feed"; 3 - import { API_URL } from "../consts"; 4 5 - export const useFeedQuery = (size = 114) => 6 useQuery({ 7 queryKey: ["feed"], 8 queryFn: () => 9 - fetch(`${API_URL}/public/scrobbles?size=${size}`, { 10 - method: "GET", 11 - }).then((res) => res.json()), 12 refetchInterval: 5000, 13 }); 14 15 export const useFeedByUriQuery = (uri: string) =>
··· 1 import { useQuery } from "@tanstack/react-query"; 2 + import { client } from "../api"; 3 import { getFeedByUri } from "../api/feed"; 4 5 + export const useFeedQuery = (limit = 114) => 6 useQuery({ 7 queryKey: ["feed"], 8 queryFn: () => 9 + client.get("/xrpc/app.rocksky.scrobble.getScrobbles", { 10 + params: { limit }, 11 + }), 12 refetchInterval: 5000, 13 + select: (res) => res.data.scrobbles || [], 14 }); 15 16 export const useFeedByUriQuery = (uri: string) =>
+12 -10
apps/web/src/hooks/useNowPlaying.tsx
··· 1 import { useQuery } from "@tanstack/react-query"; 2 - import { API_URL } from "../consts"; 3 4 export type NowPlayings = { 5 id: string; 6 title: string; 7 artist: string; 8 - album_art: string; 9 - artist_uri?: string; 10 uri: string; 11 avatar: string; 12 handle: string; 13 did: string; 14 - created_at: string; 15 - track_id: string; 16 - track_uri: string; 17 }[]; 18 19 export const useNowPlayingsQuery = () => 20 - useQuery<NowPlayings>({ 21 queryKey: ["now-playings"], 22 queryFn: () => 23 - fetch(`${API_URL}/now-playings?size=7`, { 24 - method: "GET", 25 - }).then((res) => res.json()), 26 refetchInterval: 5000, 27 });
··· 1 import { useQuery } from "@tanstack/react-query"; 2 + import { client } from "../api"; 3 4 export type NowPlayings = { 5 id: string; 6 title: string; 7 artist: string; 8 + albumArt: string; 9 + artistUri?: string; 10 uri: string; 11 avatar: string; 12 handle: string; 13 did: string; 14 + createdAt: string; 15 + trackId: string; 16 + trackUri: string; 17 }[]; 18 19 export const useNowPlayingsQuery = () => 20 + useQuery({ 21 queryKey: ["now-playings"], 22 queryFn: () => 23 + client.get<{ nowPlayings: NowPlayings }>( 24 + "/xrpc/app.rocksky.feed.getNowPlayings", 25 + { params: { size: 7 } } 26 + ), 27 refetchInterval: 5000, 28 + select: (res) => res.data.nowPlayings || [], 29 });
+15 -15
apps/web/src/pages/home/nowplayings/NowPlayings.tsx
··· 94 id: string; 95 title: string; 96 artist: string; 97 - album_art: string; 98 - artist_uri?: string; 99 uri: string; 100 avatar: string; 101 handle: string; 102 did: string; 103 - created_at: string; 104 - track_id: string; 105 - track_uri: string; 106 } | null>(null); 107 const [currentIndex, setCurrentIndex] = useState(0); 108 const [progress, setProgress] = useState(0); ··· 211 </div> 212 </Link> 213 <span className="ml-[10px] text-[15px] text-[var(--color-text-muted)]"> 214 - {dayjs.utc(currentlyPlaying?.created_at).local().fromNow()} 215 </span> 216 </div> 217 </ModalHeader> ··· 225 )} 226 </div> 227 <div className="flex flex-col items-center flex-1"> 228 - {currentlyPlaying?.track_uri && ( 229 <Link 230 - to={`/${currentlyPlaying?.track_uri.split("at://")[1]}`} 231 > 232 <Cover 233 - src={currentlyPlaying?.album_art} 234 key={currentlyPlaying?.id} 235 /> 236 </Link> 237 )} 238 - {currentlyPlaying?.track_uri && ( 239 <Link 240 - to={`/${currentlyPlaying?.track_uri.split("at://")[1]}`} 241 > 242 <TrackTitle>{currentlyPlaying?.title}</TrackTitle> 243 </Link> 244 )} 245 - {!currentlyPlaying?.track_uri && ( 246 <Cover 247 - src={currentlyPlaying?.album_art} 248 key={currentlyPlaying?.id} 249 /> 250 )} ··· 261 </div> 262 </div> 263 264 - {!currentlyPlaying?.track_uri && ( 265 <TrackTitle>{currentlyPlaying?.title}</TrackTitle> 266 )} 267 - <Link to={`/${currentlyPlaying?.artist_uri?.split("at://")[1]}`}> 268 <TrackArtist>{currentlyPlaying?.artist}</TrackArtist> 269 </Link> 270 </ModalBody>
··· 94 id: string; 95 title: string; 96 artist: string; 97 + albumArt: string; 98 + artistUri?: string; 99 uri: string; 100 avatar: string; 101 handle: string; 102 did: string; 103 + createdAt: string; 104 + trackId: string; 105 + trackUri: string; 106 } | null>(null); 107 const [currentIndex, setCurrentIndex] = useState(0); 108 const [progress, setProgress] = useState(0); ··· 211 </div> 212 </Link> 213 <span className="ml-[10px] text-[15px] text-[var(--color-text-muted)]"> 214 + {dayjs.utc(currentlyPlaying?.createdAt).local().fromNow()} 215 </span> 216 </div> 217 </ModalHeader> ··· 225 )} 226 </div> 227 <div className="flex flex-col items-center flex-1"> 228 + {currentlyPlaying?.trackUri && ( 229 <Link 230 + to={`/${currentlyPlaying?.trackUri.split("at://")[1]}`} 231 > 232 <Cover 233 + src={currentlyPlaying?.albumArt} 234 key={currentlyPlaying?.id} 235 /> 236 </Link> 237 )} 238 + {currentlyPlaying?.trackUri && ( 239 <Link 240 + to={`/${currentlyPlaying?.trackUri.split("at://")[1]}`} 241 > 242 <TrackTitle>{currentlyPlaying?.title}</TrackTitle> 243 </Link> 244 )} 245 + {!currentlyPlaying?.trackUri && ( 246 <Cover 247 + src={currentlyPlaying?.albumArt} 248 key={currentlyPlaying?.id} 249 /> 250 )} ··· 261 </div> 262 </div> 263 264 + {!currentlyPlaying?.trackUri && ( 265 <TrackTitle>{currentlyPlaying?.title}</TrackTitle> 266 )} 267 + <Link to={`/${currentlyPlaying?.artistUri?.split("at://")[1]}`}> 268 <TrackArtist>{currentlyPlaying?.artist}</TrackArtist> 269 </Link> 270 </ModalBody>
+8
turbo.json
··· 14 "./dist/**" 15 ] 16 }, 17 "dev": { 18 "persistent": true, 19 "cache": false
··· 14 "./dist/**" 15 ] 16 }, 17 + "build:prod": { 18 + "dependsOn": [ 19 + "^build:prod" 20 + ], 21 + "outputs": [ 22 + "./dist/**" 23 + ] 24 + }, 25 "dev": { 26 "persistent": true, 27 "cache": false