IKHSAN
ENID Baca dalam Bahasa Indonesia

homeio

Public

Forked from doctor-io/homeio

Branch
main
Latest commit
958d74616 October 2026

useGoogleDriveFiles.ts

Open raw
"use client";

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/shared/query-keys";
import type { DriveBrowseResponse, DriveFileEntry, DriveUploadResponse } from "@/lib/shared/contracts/google-drive";

type ApiResponse<T> = { data: T };
type ApiError = { error?: string; code?: string };

function mapError(status: number, body: ApiError) {
  return body.error ?? `Request failed (${status})${body.code ? ` [${body.code}]` : ""}`;
}

async function fetchDriveBrowse(
  connectionId: string,
  folderId: string,
): Promise<DriveBrowseResponse> {
  const params = new URLSearchParams({ folderId });
  const res = await fetch(
    `/api/v1/files/google-drive/${encodeURIComponent(connectionId)}/browse?${params.toString()}`,
    { cache: "no-store" },
  );
  if (!res.ok) {
    const body = (await res.json().catch(() => ({}))) as ApiError;
    throw new Error(mapError(res.status, body));
  }
  const json = (await res.json()) as ApiResponse<DriveBrowseResponse>;
  return json.data;
}

async function deleteDriveFile(connectionId: string, fileId: string): Promise<void> {
  const res = await fetch(
    `/api/v1/files/google-drive/${encodeURIComponent(connectionId)}/files/${encodeURIComponent(fileId)}`,
    { method: "DELETE" },
  );
  if (!res.ok) {
    const body = (await res.json().catch(() => ({}))) as ApiError;
    throw new Error(mapError(res.status, body));
  }
}

async function createDriveFolder(
  connectionId: string,
  parentId: string,
  name: string,
): Promise<DriveFileEntry> {
  const res = await fetch(
    `/api/v1/files/google-drive/${encodeURIComponent(connectionId)}/folders`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ parentId, name }),
    },
  );
  if (!res.ok) {
    const body = (await res.json().catch(() => ({}))) as ApiError;
    throw new Error(mapError(res.status, body));
  }
  const json = (await res.json()) as ApiResponse<DriveFileEntry>;
  return json.data;
}

async function uploadDriveFiles(
  connectionId: string,
  folderId: string,
  files: File[],
  onProgress?: (loaded: number, total: number) => void,
): Promise<DriveUploadResponse> {
  const formData = new FormData();
  formData.append("folderId", folderId);
  let totalSize = 0;
  for (const file of files) {
    formData.append("file", file);
    totalSize += file.size;
  }

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", `/api/v1/files/google-drive/${encodeURIComponent(connectionId)}/upload`);

    if (onProgress) {
      xhr.upload.addEventListener("progress", (e) => {
        if (e.lengthComputable) onProgress(e.loaded, e.total);
        else onProgress(0, totalSize);
      });
    }

    xhr.addEventListener("load", () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          const json = JSON.parse(xhr.responseText) as ApiResponse<DriveUploadResponse>;
          resolve(json.data);
        } catch {
          reject(new Error("Invalid response from upload"));
        }
      } else {
        try {
          const body = JSON.parse(xhr.responseText) as ApiError;
          reject(new Error(mapError(xhr.status, body)));
        } catch {
          reject(new Error(`Upload failed: ${xhr.status}`));
        }
      }
    });

    xhr.addEventListener("error", () => reject(new Error("Network error during upload")));
    xhr.send(formData);
  });
}

export function buildDriveDownloadUrl(
  connectionId: string,
  fileId: string,
  name: string,
  mimeType: string,
): string {
  const params = new URLSearchParams({ fileId, name, mimeType });
  return `/api/v1/files/google-drive/${encodeURIComponent(connectionId)}/download?${params.toString()}`;
}

export function useGoogleDriveFiles(connectionId: string, folderId: string) {
  return useQuery({
    queryKey: queryKeys.googleDriveBrowse(connectionId, folderId),
    queryFn: () => fetchDriveBrowse(connectionId, folderId),
    staleTime: 10_000,
    enabled: Boolean(connectionId),
  });
}

export function useGoogleDriveDeleteFile(connectionId: string, folderId: string) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (fileId: string) => deleteDriveFile(connectionId, fileId),
    onSuccess: () => {
      void queryClient.invalidateQueries({
        queryKey: queryKeys.googleDriveBrowse(connectionId, folderId),
      });
    },
  });
}

export function useGoogleDriveCreateFolder(connectionId: string, folderId: string) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({ parentId, name }: { parentId: string; name: string }) =>
      createDriveFolder(connectionId, parentId, name),
    onSuccess: () => {
      void queryClient.invalidateQueries({
        queryKey: queryKeys.googleDriveBrowse(connectionId, folderId),
      });
    },
  });
}

export function useGoogleDriveUpload(connectionId: string, folderId: string) {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({
      files,
      onProgress,
    }: {
      files: File[];
      onProgress?: (loaded: number, total: number) => void;
    }) => uploadDriveFiles(connectionId, folderId, files, onProgress),
    onSuccess: () => {
      void queryClient.invalidateQueries({
        queryKey: queryKeys.googleDriveBrowse(connectionId, folderId),
      });
    },
  });
}
bytes
5,421
lines
172
Type
ts
Latest commit
16 October 2026