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