mirror of
https://github.com/20kaushik02/spotify-manager-web.git
synced 2025-12-06 09:54:07 +00:00
link backfill and prune
This commit is contained in:
parent
c9938aca35
commit
4baedc3e60
@ -1,9 +1,11 @@
|
|||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { apiRespBaseType, axiosInstance } from "./axiosInstance";
|
import { apiRespBaseType, axiosInstance } from "./axiosInstance";
|
||||||
import {
|
import {
|
||||||
|
opBackfillLinkURL,
|
||||||
opCreateLinkURL,
|
opCreateLinkURL,
|
||||||
opDeleteLinkURL,
|
opDeleteLinkURL,
|
||||||
opFetchGraphURL,
|
opFetchGraphURL,
|
||||||
|
opPruneLinkURL,
|
||||||
opUpdateUserDataURL,
|
opUpdateUserDataURL,
|
||||||
} from "./paths";
|
} from "./paths";
|
||||||
|
|
||||||
@ -27,8 +29,26 @@ type createLinkBodyType = {
|
|||||||
to: string; // playlistID
|
to: string; // playlistID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface createLinkDataType extends apiRespBaseType {}
|
||||||
|
|
||||||
type deleteLinkBodyType = createLinkBodyType;
|
type deleteLinkBodyType = createLinkBodyType;
|
||||||
|
|
||||||
|
interface deleteLinkDataType extends apiRespBaseType {}
|
||||||
|
|
||||||
|
type backfillLinkBodyType = createLinkBodyType;
|
||||||
|
|
||||||
|
interface backfillLinkDataType extends apiRespBaseType {
|
||||||
|
added?: number;
|
||||||
|
local?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type pruneLinkBodyType = createLinkBodyType;
|
||||||
|
|
||||||
|
interface pruneLinkDataType extends apiRespBaseType {
|
||||||
|
added?: number;
|
||||||
|
local?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const apiFetchGraph = async (): Promise<
|
export const apiFetchGraph = async (): Promise<
|
||||||
AxiosResponse<fetchGraphDataType, any>
|
AxiosResponse<fetchGraphDataType, any>
|
||||||
> => {
|
> => {
|
||||||
@ -53,7 +73,7 @@ export const apiUpdateUserData = async (): Promise<
|
|||||||
|
|
||||||
export const apiCreateLink = async (
|
export const apiCreateLink = async (
|
||||||
data: createLinkBodyType
|
data: createLinkBodyType
|
||||||
): Promise<AxiosResponse<apiRespBaseType, any>> => {
|
): Promise<AxiosResponse<createLinkDataType, any>> => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.post(opCreateLinkURL, data);
|
const response = await axiosInstance.post(opCreateLinkURL, data);
|
||||||
return response;
|
return response;
|
||||||
@ -64,9 +84,31 @@ export const apiCreateLink = async (
|
|||||||
|
|
||||||
export const apiDeleteLink = async (
|
export const apiDeleteLink = async (
|
||||||
data: deleteLinkBodyType
|
data: deleteLinkBodyType
|
||||||
): Promise<AxiosResponse<apiRespBaseType, any>> => {
|
): Promise<AxiosResponse<deleteLinkDataType, any>> => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.delete(opDeleteLinkURL, { data });
|
const response = await axiosInstance.delete(opDeleteLinkURL, { data }); // axios delete method doesn't take body
|
||||||
|
return response;
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const apiBackfillLink = async (
|
||||||
|
data: backfillLinkBodyType
|
||||||
|
): Promise<AxiosResponse<backfillLinkDataType, any>> => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.put(opBackfillLinkURL, data);
|
||||||
|
return response;
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const apiPruneLink = async (
|
||||||
|
data: pruneLinkBodyType
|
||||||
|
): Promise<AxiosResponse<pruneLinkDataType, any>> => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.put(opPruneLinkURL, data);
|
||||||
return response;
|
return response;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return error.response;
|
return error.response;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
export const backendDomain = process.env.REACT_APP_API_BASE_URL + "/";
|
export const backendDomain = process.env.REACT_APP_API_BASE_URL + "/";
|
||||||
|
export const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
|
||||||
|
|
||||||
export const authLoginURL = backendDomain + "api/auth/login"
|
export const authLoginURL = backendDomain + "api/auth/login";
|
||||||
export const authLogoutURL = backendDomain + "api/auth/logout"
|
export const authLogoutURL = backendDomain + "api/auth/logout";
|
||||||
|
|
||||||
export const authHealthCheckURL = "auth-health";
|
export const authHealthCheckURL = "auth-health";
|
||||||
export const authRefreshURL = "api/auth/refresh";
|
export const authRefreshURL = "api/auth/refresh";
|
||||||
@ -10,3 +11,5 @@ export const opFetchGraphURL = "api/operations/fetch";
|
|||||||
export const opUpdateUserDataURL = "api/operations/update";
|
export const opUpdateUserDataURL = "api/operations/update";
|
||||||
export const opCreateLinkURL = "api/operations/link";
|
export const opCreateLinkURL = "api/operations/link";
|
||||||
export const opDeleteLinkURL = opCreateLinkURL;
|
export const opDeleteLinkURL = opCreateLinkURL;
|
||||||
|
export const opBackfillLinkURL = "api/operations/populate/link";
|
||||||
|
export const opPruneLinkURL = "api/operations/prune/link";
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import styles from "./Button.module.css";
|
|||||||
|
|
||||||
type ButtonProps = {
|
type ButtonProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onClickMethod: () => void;
|
onClickMethod?: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
const Button = ({ children, onClickMethod }: ButtonProps) => {
|
const Button = ({ children, onClickMethod = () => {} }: ButtonProps) => {
|
||||||
const clickHandler = (e: React.MouseEvent) => {
|
const clickHandler = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClickMethod();
|
onClickMethod();
|
||||||
|
|||||||
@ -13,9 +13,9 @@ import {
|
|||||||
BackgroundVariant,
|
BackgroundVariant,
|
||||||
type DefaultEdgeOptions,
|
type DefaultEdgeOptions,
|
||||||
type ProOptions,
|
type ProOptions,
|
||||||
type ReactFlowInstance,
|
|
||||||
type Node,
|
type Node,
|
||||||
type Edge,
|
type Edge,
|
||||||
|
type OnInit,
|
||||||
type OnNodesChange,
|
type OnNodesChange,
|
||||||
type OnEdgesChange,
|
type OnEdgesChange,
|
||||||
type OnSelectionChangeFunc,
|
type OnSelectionChangeFunc,
|
||||||
@ -31,6 +31,7 @@ import { IoIosGitNetwork } from "react-icons/io";
|
|||||||
import { WiCloudRefresh } from "react-icons/wi";
|
import { WiCloudRefresh } from "react-icons/wi";
|
||||||
import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md";
|
import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md";
|
||||||
import { AiFillSpotify } from "react-icons/ai";
|
import { AiFillSpotify } from "react-icons/ai";
|
||||||
|
import { PiSupersetOf, PiSubsetOf } from "react-icons/pi";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
showErrorToastNotification,
|
showErrorToastNotification,
|
||||||
@ -39,10 +40,13 @@ import {
|
|||||||
showWarnToastNotification,
|
showWarnToastNotification,
|
||||||
} from "../../components/ToastNotification";
|
} from "../../components/ToastNotification";
|
||||||
|
|
||||||
|
import { spotifyPlaylistLinkPrefix } from "../../api/paths";
|
||||||
import {
|
import {
|
||||||
|
apiBackfillLink,
|
||||||
apiCreateLink,
|
apiCreateLink,
|
||||||
apiDeleteLink,
|
apiDeleteLink,
|
||||||
apiFetchGraph,
|
apiFetchGraph,
|
||||||
|
apiPruneLink,
|
||||||
apiUpdateUserData,
|
apiUpdateUserData,
|
||||||
} from "../../api/operations";
|
} from "../../api/operations";
|
||||||
|
|
||||||
@ -127,10 +131,11 @@ const Graph = () => {
|
|||||||
const flowInstance = useReactFlow();
|
const flowInstance = useReactFlow();
|
||||||
const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes);
|
const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes);
|
||||||
const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges);
|
const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges);
|
||||||
|
const [selectedEdgeID, setSelectedEdgeID] = useState<Edge["id"]>("");
|
||||||
const [interactive, setInteractive] =
|
const [interactive, setInteractive] =
|
||||||
useState<Interactive>(initialInteractive);
|
useState<Interactive>(initialInteractive);
|
||||||
|
|
||||||
const onFlowInit = (_instance: ReactFlowInstance) => {
|
const onFlowInit: OnInit = (_instance) => {
|
||||||
console.debug("flow loaded");
|
console.debug("flow loaded");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,10 +151,11 @@ const Graph = () => {
|
|||||||
|
|
||||||
const onFlowSelectionChange: OnSelectionChangeFunc = useCallback(
|
const onFlowSelectionChange: OnSelectionChangeFunc = useCallback(
|
||||||
({ nodes, edges }) => {
|
({ nodes, edges }) => {
|
||||||
const newSelectedID = edges[0]?.id ?? "";
|
const selectedID = edges[0]?.id ?? "";
|
||||||
|
setSelectedEdgeID(selectedID);
|
||||||
setLinkEdges((eds) =>
|
setLinkEdges((eds) =>
|
||||||
eds.map((ed) =>
|
eds.map((ed) =>
|
||||||
ed.id === newSelectedID
|
ed.id === selectedID
|
||||||
? { ...ed, ...selectedEdgeOptions }
|
? { ...ed, ...selectedEdgeOptions }
|
||||||
: { ...ed, ...edgeOptions }
|
: { ...ed, ...edgeOptions }
|
||||||
)
|
)
|
||||||
@ -197,7 +203,6 @@ const Graph = () => {
|
|||||||
`deleted connection: ${edges[0].source} -> ${edges[0].target}`
|
`deleted connection: ${edges[0].source} -> ${edges[0].target}`
|
||||||
);
|
);
|
||||||
// call API to delete link
|
// call API to delete link
|
||||||
const spotifyPlaylistLinkPrefix = "https://open.spotify.com/playlist/";
|
|
||||||
const resp = await APIWrapper({
|
const resp = await APIWrapper({
|
||||||
apiFn: apiDeleteLink,
|
apiFn: apiDeleteLink,
|
||||||
data: {
|
data: {
|
||||||
@ -215,6 +220,54 @@ const Graph = () => {
|
|||||||
[refreshAuth]
|
[refreshAuth]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// backfill link
|
||||||
|
const backfillLink = async () => {
|
||||||
|
if (selectedEdgeID === "") {
|
||||||
|
showWarnToastNotification("Select an edge!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedEdge = linkEdges.filter((ed) => ed.id === selectedEdgeID)[0];
|
||||||
|
|
||||||
|
const resp = await APIWrapper({
|
||||||
|
apiFn: apiBackfillLink,
|
||||||
|
data: {
|
||||||
|
from: spotifyPlaylistLinkPrefix + selectedEdge.source,
|
||||||
|
to: spotifyPlaylistLinkPrefix + selectedEdge.target,
|
||||||
|
},
|
||||||
|
refreshAuth,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp?.status === 200) {
|
||||||
|
showSuccessToastNotification(resp?.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// prune link
|
||||||
|
const pruneLink = async () => {
|
||||||
|
if (selectedEdgeID === "") {
|
||||||
|
showWarnToastNotification("Select an edge!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedEdge = linkEdges.filter((ed) => ed.id === selectedEdgeID)[0];
|
||||||
|
|
||||||
|
const resp = await APIWrapper({
|
||||||
|
apiFn: apiPruneLink,
|
||||||
|
data: {
|
||||||
|
from: spotifyPlaylistLinkPrefix + selectedEdge.source,
|
||||||
|
to: spotifyPlaylistLinkPrefix + selectedEdge.target,
|
||||||
|
},
|
||||||
|
refreshAuth,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp?.status === 200) {
|
||||||
|
showSuccessToastNotification(resp?.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
type getLayoutedElementsOpts = {
|
type getLayoutedElementsOpts = {
|
||||||
direction: rankdirType;
|
direction: rankdirType;
|
||||||
};
|
};
|
||||||
@ -405,7 +458,7 @@ const Graph = () => {
|
|||||||
nodesDraggable={interactive.ndDrag}
|
nodesDraggable={interactive.ndDrag}
|
||||||
nodesConnectable={interactive.ndConn}
|
nodesConnectable={interactive.ndConn}
|
||||||
elementsSelectable={interactive.elsSel}
|
elementsSelectable={interactive.elsSel}
|
||||||
deleteKeyCode={["Delete", "Backspace"]}
|
deleteKeyCode={["Delete"]}
|
||||||
multiSelectionKeyCode={null}
|
multiSelectionKeyCode={null}
|
||||||
onInit={onFlowInit}
|
onInit={onFlowInit}
|
||||||
onBeforeDelete={onFlowBeforeDelete}
|
onBeforeDelete={onFlowBeforeDelete}
|
||||||
@ -418,9 +471,13 @@ const Graph = () => {
|
|||||||
<Background variant={BackgroundVariant.Dots} gap={36} size={3} />
|
<Background variant={BackgroundVariant.Dots} gap={36} size={3} />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
<div className={`${styles.operations_wrapper} custom_scrollbar`}>
|
<div className={`${styles.operations_wrapper} custom_scrollbar`}>
|
||||||
<Button onClickMethod={refreshGraph}>
|
<Button onClickMethod={backfillLink}>
|
||||||
<WiCloudRefresh size={36} />
|
<PiSupersetOf size={36} />
|
||||||
Refresh Graph
|
Backfill Link
|
||||||
|
</Button>
|
||||||
|
<Button onClickMethod={pruneLink}>
|
||||||
|
<PiSubsetOf size={36} />
|
||||||
|
Prune Link
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClickMethod={() => arrangeLayout("TB")}>
|
<Button onClickMethod={() => arrangeLayout("TB")}>
|
||||||
<IoIosGitNetwork size={36} />
|
<IoIosGitNetwork size={36} />
|
||||||
@ -442,6 +499,10 @@ const Graph = () => {
|
|||||||
</span>
|
</span>
|
||||||
Sync Spotify
|
Sync Spotify
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClickMethod={refreshGraph}>
|
||||||
|
<WiCloudRefresh size={36} />
|
||||||
|
Refresh Graph
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user