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

[web] start using rocksky xrpc api for googledrive and dropbox

+88 -214
+17 -10
apps/web/src/api/dropbox.ts
··· 1 1 import axios from "axios"; 2 + import { client } from "."; 2 3 import { API_URL } from "../consts"; 3 4 4 5 export const getFiles = async (id?: string) => { 5 - const response = await axios.get<{ 6 - cursor: string; 7 - entries: { 8 - ".tag": string; 6 + const response = await client.get<{ 7 + directories: { 8 + id: string; 9 + name: string; 10 + fileId: string; 11 + path: string; 12 + parentId?: string; 13 + }[]; 14 + files: { 9 15 id: string; 10 16 name: string; 11 - path_display: string; 17 + fileId: string; 18 + directoryId: string; 19 + trackId: string; 12 20 }[]; 13 - has_more: boolean; 14 - }>(`${API_URL}/dropbox/files`, { 21 + }>("/xrpc/app.rocksky.dropbox.getFiles", { 15 22 headers: { 16 23 Authorization: `Bearer ${localStorage.getItem("token")}`, 17 24 }, 18 25 params: { 19 - path: id, 26 + at: id, 20 27 }, 21 28 }); 22 29 return response.data; 23 30 }; 24 31 25 32 export const getFile = async (id: string) => { 26 - const response = await axios.get<{ 33 + const response = await client.get<{ 27 34 ".tag": string; 28 35 id: string; 29 36 name: string; 30 37 path_display: string; 31 - }>(`${API_URL}/dropbox/file`, { 38 + }>("/xrpc/app.rocksky.dropbox.getFiles", { 32 39 headers: { 33 40 Authorization: `Bearer ${localStorage.getItem("token")}`, 34 41 },
+19 -11
apps/web/src/api/googledrive.ts
··· 1 - import axios from "axios"; 2 - import { API_URL } from "../consts"; 1 + import { client } from "."; 3 2 4 3 export const getFiles = async (parent_id?: string) => { 5 - const response = await axios.get<{ 4 + const response = await client.get<{ 5 + directories: { 6 + id: string; 7 + name: string; 8 + fileId: string; 9 + path: string; 10 + parentId?: string; 11 + }[]; 6 12 files: { 7 13 id: string; 8 - mimeType: string; 9 14 name: string; 10 - parents: string[]; 15 + fileId: string; 16 + directoryId: string; 17 + trackId: string; 11 18 }[]; 12 - authUrl?: string; 13 - error?: string; 14 - }>(`${API_URL}/googledrive/files`, { 19 + }>("/xrpc/app.rocksky.googledrive.getFiles", { 15 20 headers: { 16 21 Authorization: `Bearer ${localStorage.getItem("token")}`, 17 22 }, 18 23 params: { 19 - parent_id, 24 + at: parent_id, 20 25 }, 21 26 }); 22 27 return response.data; 23 28 }; 24 29 25 30 export const getFile = async (id: string) => { 26 - const response = await axios.get<{ 31 + const response = await client.get<{ 27 32 id: string; 28 33 mimeType: string; 29 34 name: string; 30 35 parents: string[]; 31 - }>(`${API_URL}/googledrive/files/${id}`, { 36 + }>("/xrpc/app.rocksky.googledrive.getFile", { 32 37 headers: { 33 38 Authorization: `Bearer ${localStorage.getItem("token")}`, 39 + }, 40 + params: { 41 + id, 34 42 }, 35 43 }); 36 44 return response.data;
-6
apps/web/src/atoms/dropbox.ts
··· 10 10 current_dir?: string; 11 11 parent_dir?: string; 12 12 parent_id?: string; 13 - files: { 14 - ".tag": string; 15 - id: string; 16 - name: string; 17 - path_display: string; 18 - }[]; 19 13 } 20 14 >; 21 15 } | null>(null);
-6
apps/web/src/atoms/googledrive.ts
··· 10 10 parent_id?: string; 11 11 parent_dir?: string; 12 12 current_dir?: string; 13 - files: { 14 - id: string; 15 - name: string; 16 - mime_type: string; 17 - parents: string[]; 18 - }[]; 19 13 } 20 14 >; 21 15 } | null>(null);
-40
apps/web/src/hooks/useDropbox.tsx
··· 21 21 }); 22 22 23 23 function useDropbox() { 24 - const getFiles = async (id?: string) => { 25 - const response = await client.get<{ 26 - cursor: string; 27 - entries: { 28 - ".tag": string; 29 - id: string; 30 - name: string; 31 - path_display: string; 32 - }[]; 33 - has_more: boolean; 34 - }>(`/dropbox/files`, { 35 - headers: { 36 - Authorization: `Bearer ${localStorage.getItem("token")}`, 37 - }, 38 - params: { 39 - path: id, 40 - }, 41 - }); 42 - return response.data; 43 - }; 44 - 45 - const getFile = async (id: string) => { 46 - const response = await client.get<{ 47 - ".tag": string; 48 - id: string; 49 - name: string; 50 - path_display: string; 51 - }>(`/dropbox/file`, { 52 - headers: { 53 - Authorization: `Bearer ${localStorage.getItem("token")}`, 54 - }, 55 - params: { 56 - path: id, 57 - }, 58 - }); 59 - return response.data; 60 - }; 61 - 62 24 const getTemporaryLink = async (id: string) => { 63 25 const response = await client.get<{ 64 26 link: string; ··· 74 36 }; 75 37 76 38 return { 77 - getFiles, 78 - getFile, 79 39 getTemporaryLink, 80 40 }; 81 41 }
-46
apps/web/src/hooks/useGoogleDrive.tsx
··· 1 1 import { useQuery } from "@tanstack/react-query"; 2 - import axios from "axios"; 3 2 import { getFile, getFiles } from "../api/googledrive"; 4 - import { API_URL } from "../consts"; 5 3 6 4 export const useFilesQuery = (id?: string) => 7 5 useQuery({ ··· 14 12 queryKey: ["googledrive", "file", id], 15 13 queryFn: () => getFile(id), 16 14 }); 17 - 18 - function useGoogleDrive() { 19 - const getFiles = async (parent_id?: string) => { 20 - const response = await axios.get<{ 21 - files: { 22 - id: string; 23 - mimeType: string; 24 - name: string; 25 - parents: string[]; 26 - }[]; 27 - authUrl?: string; 28 - error?: string; 29 - }>(`${API_URL}/googledrive/files`, { 30 - headers: { 31 - Authorization: `Bearer ${localStorage.getItem("token")}`, 32 - }, 33 - params: { 34 - parent_id, 35 - }, 36 - }); 37 - return response.data; 38 - }; 39 - 40 - const getFile = async (id: string) => { 41 - const response = await axios.get<{ 42 - id: string; 43 - mimeType: string; 44 - name: string; 45 - parents: string[]; 46 - }>(`${API_URL}/googledrive/files/${id}`, { 47 - headers: { 48 - Authorization: `Bearer ${localStorage.getItem("token")}`, 49 - }, 50 - }); 51 - return response.data; 52 - }; 53 - 54 - return { 55 - getFiles, 56 - getFile, 57 - }; 58 - } 59 - 60 - export default useGoogleDrive;
+1 -1
apps/web/src/pages/album/Album.tsx
··· 182 182 </div> 183 183 <div className="flex items-center justify-end flex-1 mr-[10px]"> 184 184 <a 185 - href={`https://pdsls.dev/at/${uri}`} 185 + href={`https://pdsls.dev/at/${uri.replace("at://", "")}`} 186 186 target="_blank" 187 187 className="text-[var(--color-text)] no-underline bg-[var(--color-default-button)] rounded-[10px] p-[16px] pl-[25px] pr-[25px]" 188 188 >
+1 -1
apps/web/src/pages/artist/Artist.tsx
··· 188 188 </div> 189 189 <div className="flex items-center justify-end flex-1 mr-[10px]"> 190 190 <a 191 - href={`https://pdsls.dev/at/${uri}`} 191 + href={`https://pdsls.dev/at/${uri.replace("at://", "")}`} 192 192 target="_blank" 193 193 className="text-[var(--color-text)] no-underline bg-[var(--color-default-button)] rounded-[10px] p-[16px] pl-[25px] pr-[25px]" 194 194 >
+26 -45
apps/web/src/pages/dropbox/Dropbox.tsx
··· 1 1 /* eslint-disable @typescript-eslint/no-explicit-any */ 2 2 import { Folder2, MusicNoteBeamed } from "@styled-icons/bootstrap"; 3 - import { Link, useRouter } from "@tanstack/react-router"; 4 3 import { createColumnHelper } from "@tanstack/react-table"; 5 - import { Breadcrumbs } from "baseui/breadcrumbs"; 6 - import { HeadingMedium } from "baseui/typography"; 7 - import { useAtom } from "jotai"; 8 - import _ from "lodash"; 9 - import { useEffect, useState } from "react"; 10 4 import ContentLoader from "react-content-loader"; 11 - import { dropboxAtom } from "../../atoms/dropbox"; 12 5 import Table from "../../components/Table"; 13 - import { AUDIO_EXTENSIONS } from "../../consts"; 14 - import useDropbox, { 15 - useFileQuery, 16 - useFilesQuery, 17 - useTemporaryLinkQuery, 18 - } from "../../hooks/useDropbox"; 6 + import { useFilesQuery } from "../../hooks/useDropbox"; 19 7 import Main from "../../layouts/Main"; 20 - import Metadata from "../../lib/metadata"; 21 8 import { File } from "../../types/file"; 22 9 import { AudioFile, Directory } from "./styles"; 23 10 ··· 28 15 }; 29 16 30 17 const Dropbox = (props: DropboxProps) => { 31 - const [dropbox, setDropbox] = useAtom(dropboxAtom); 32 - useFilesQuery(); 33 - useFileQuery(props.fileId!); 34 - useTemporaryLinkQuery(props.fileId!); 35 - 36 - const { getFiles, getFile, getTemporaryLink } = useDropbox(); 37 - const [loading, setLoading] = useState(true); 38 - const { 39 - state: { 40 - location: { pathname }, 41 - }, 42 - } = useRouter(); 18 + const { data, isLoading } = useFilesQuery(props.fileId); 43 19 44 20 const playFile = async (id: string) => { 21 + console.log(">> Playing file:", id); 22 + /* 45 23 const { link } = await getTemporaryLink(id); 46 24 console.log(">> Playing file:", link); 47 25 const m = new Metadata(); 48 26 await m.load(link); 49 27 console.log(">> Metadata:", m.get_metadata()); 28 + */ 50 29 }; 51 30 52 31 const columns = [ ··· 87 66 }), 88 67 ]; 89 68 69 + /* 90 70 useEffect(() => { 91 71 const fetchFiles = async () => { 92 72 setLoading(true); ··· 136 116 fetchFiles(); 137 117 // eslint-disable-next-line react-hooks/exhaustive-deps 138 118 }, [props.fileId]); 119 + */ 139 120 140 - const parent_dir = 141 - dropbox?.cache[props.fileId || "/Music"]?.parent_dir || dropbox?.parent_dir; 142 - const current_dir = 143 - dropbox?.cache[props.fileId || "/Music"]?.current_dir || 144 - dropbox?.current_dir; 145 - const parent_id = 146 - dropbox?.cache[props.fileId || "/Music"]?.parent_id || dropbox?.parent_id; 147 121 return ( 148 122 <Main> 149 - {((props.fileId && dropbox?.cache[props.fileId]) || 150 - !loading || 123 + {/* 124 + ((props.fileId && dropbox?.cache[props.fileId]) || 125 + !isLoading || 151 126 pathname === "/dropbox") && ( 152 127 <div className="pt-[80px] fixed bg-[var(--color-background)] top-[19px] w-[770px]"> 153 128 <Breadcrumbs> ··· 169 144 {current_dir === "Music" ? "Dropbox" : current_dir} 170 145 </HeadingMedium> 171 146 </div> 172 - )} 147 + ) 148 + */} 173 149 174 150 <div className="mt-[100px] overflow-x-hidden mb-[140px] "> 175 - {loading && !dropbox?.cache[props.fileId || "/Music"] && ( 151 + {isLoading && ( 176 152 <ContentLoader 177 153 width={700} 178 154 height={350} ··· 193 169 <circle cx="20" cy="271" r="15" /> 194 170 </ContentLoader> 195 171 )} 196 - {(!loading || dropbox?.cache[props.fileId || "/Music"]) && ( 172 + {!isLoading && ( 197 173 <Table 198 174 columns={columns as any} 199 - files={ 200 - dropbox?.cache[props.fileId || "/Music"]?.files.map((entry) => ({ 201 - id: entry.id, 202 - name: entry.name, 203 - tag: entry[".tag"], 204 - })) || [] 205 - } 175 + files={[ 176 + ...data!.directories.map((dir) => ({ 177 + id: dir.fileId, 178 + name: dir.name, 179 + tag: "folder", 180 + })), 181 + ...data!.files.map((file) => ({ 182 + id: file.fileId, 183 + name: file.name, 184 + tag: "file", 185 + })), 186 + ]} 206 187 /> 207 188 )} 208 189 </div>
+22 -46
apps/web/src/pages/googledrive/GoogleDrive.tsx
··· 1 1 /* eslint-disable @typescript-eslint/no-explicit-any */ 2 2 import { Folder2, MusicNoteBeamed } from "@styled-icons/bootstrap"; 3 - import { Link, useRouter } from "@tanstack/react-router"; 4 3 import { createColumnHelper } from "@tanstack/react-table"; 5 - import { Breadcrumbs } from "baseui/breadcrumbs"; 6 - import { HeadingMedium } from "baseui/typography"; 7 - import { useAtom } from "jotai"; 8 - import _ from "lodash"; 9 - import { useEffect, useState } from "react"; 10 4 import ContentLoader from "react-content-loader"; 11 - import googleDriveAtom from "../../atoms/googledrive"; 12 5 import Table from "../../components/Table"; 13 - import { AUDIO_EXTENSIONS } from "../../consts"; 14 - import useGoogleDrive, { 15 - useFileQuery, 16 - useFilesQuery, 17 - } from "../../hooks/useGoogleDrive"; 6 + import { useFilesQuery } from "../../hooks/useGoogleDrive"; 18 7 import Main from "../../layouts/Main"; 19 8 import { File } from "../../types/file"; 20 9 import { AudioFile, Directory } from "./styles"; ··· 26 15 }; 27 16 28 17 const GoogleDrive = (props: GoogleDriveProps) => { 29 - const [googleDrive, setGoogleDrive] = useAtom(googleDriveAtom); 30 - useFilesQuery(); 31 - useFileQuery(props.fileId!); 32 - 33 - const { getFiles, getFile } = useGoogleDrive(); 34 - const [loading, setLoading] = useState(true); 35 - const { 36 - state: { 37 - location: { pathname }, 38 - }, 39 - } = useRouter(); 18 + const { data, isLoading } = useFilesQuery(props.fileId); 40 19 41 20 const columns = [ 42 21 columnHelper.accessor("name", { ··· 74 53 }), 75 54 ]; 76 55 56 + /* 77 57 useEffect(() => { 78 58 const fetchGoogleDrive = async () => { 79 59 setLoading(true); ··· 130 110 fetchGoogleDrive(); 131 111 // eslint-disable-next-line react-hooks/exhaustive-deps 132 112 }, [props.fileId]); 113 + */ 133 114 134 - const parent_dir = 135 - googleDrive?.cache[props.fileId || "/Music"]?.parent_dir || 136 - googleDrive?.parent_dir; 137 - const current_dir = 138 - googleDrive?.cache[props.fileId || "/Music"]?.current_dir || 139 - googleDrive?.current_dir; 140 - const parent_id = 141 - googleDrive?.cache[props.fileId || "/Music"]?.parent_id || 142 - googleDrive?.parent_id; 143 115 return ( 144 116 <Main> 145 - {((props.fileId && googleDrive?.cache[props.fileId]) || 146 - !loading || 117 + {/* 118 + ((props.fileId && googleDrive?.cache[props.fileId]) || 119 + !isLoading || 147 120 pathname === "/googledrive") && ( 148 121 <div className="pt-[80px] fixed bg-[var(--color-background)] top-[19px] w-[770px]"> 149 122 <Breadcrumbs> ··· 167 140 {current_dir === "Music" ? "Google Drive" : current_dir} 168 141 </HeadingMedium> 169 142 </div> 170 - )} 143 + )*/} 171 144 172 145 <div className="mt-[100px] overflow-x-hidden mb-[140px]"> 173 - {loading && !googleDrive?.cache[props.fileId || "/Music"]?.files && ( 146 + {isLoading && ( 174 147 <ContentLoader 175 148 width={700} 176 149 height={350} ··· 191 164 <circle cx="20" cy="271" r="15" /> 192 165 </ContentLoader> 193 166 )} 194 - {(!loading || googleDrive?.cache[props.fileId || "/Music"]?.files) && ( 167 + {!isLoading && ( 195 168 <Table 196 169 columns={columns as any} 197 - files={ 198 - googleDrive?.cache[props.fileId || "/Music"]?.files.map( 199 - (entry) => ({ 200 - id: entry.id, 201 - name: entry.name, 202 - tag: entry.mime_type.includes("folder") ? "folder" : "file", 203 - }) 204 - ) || [] 205 - } 170 + files={[ 171 + ...data!.directories.map((dir) => ({ 172 + id: dir.fileId, 173 + name: dir.name, 174 + tag: "folder", 175 + })), 176 + ...data!.files.map((file) => ({ 177 + id: file.fileId, 178 + name: file.name, 179 + tag: "file", 180 + })), 181 + ]} 206 182 /> 207 183 )} 208 184 </div>
+1 -1
apps/web/src/pages/playlist/Playlist.tsx
··· 140 140 </div> 141 141 <div className="flex items-center justify-end flex-1 mr-[10px]"> 142 142 <a 143 - href={`https://pdsls.dev/at/${uri}`} 143 + href={`https://pdsls.dev/at/${uri.replace("at://", "")}`} 144 144 target="_blank" 145 145 className="text-[var(--color-text)] no-underline p-[16px] bg-[var(--color-default-button)] rounded-[10px] pl-[25px] pr-[25px]" 146 146 >
+1 -1
apps/web/src/pages/song/Song.tsx
··· 303 303 </div> 304 304 <div className="flex items-center justify-end flex-1 mr-[10px]"> 305 305 <a 306 - href={`https://pdsls.dev/at/${uri}`} 306 + href={`https://pdsls.dev/at/${uri.replace("at://", "")}`} 307 307 target="_blank" 308 308 className="text-[var(--color-text)] no-underline bg-[var(--color-default-button)] rounded-[10px] p-[16px] pl-[25px] pr-[25px]" 309 309 >