mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 20:41:18 +00:00
refactor: account model overhaul
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_authenticated"!</div>
|
||||
}
|
||||
@@ -3,11 +3,12 @@ import { atom } from "jotai"
|
||||
|
||||
export const Account = type({
|
||||
id: "string",
|
||||
orgId: "string",
|
||||
userId: "string",
|
||||
role: "'admin'|'member'",
|
||||
status: "'invited'|'active'|'suspended'",
|
||||
createdAt: "string.date.iso.parse",
|
||||
updatedAt: "string.date.iso.parse",
|
||||
storageUsageBytes: "number",
|
||||
storageQuotaBytes: "number",
|
||||
})
|
||||
export type Account = typeof Account.infer
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { accountsQuery } from "@/account/api"
|
||||
import { fetchApi } from "@/lib/api"
|
||||
import { currentUserQuery } from "@/user/api"
|
||||
import { User } from "@/user/user"
|
||||
import { drivesQuery } from "@/drive/api"
|
||||
import { Drive } from "@/drive/drive"
|
||||
|
||||
const LoginResponseSchema = type({
|
||||
user: User,
|
||||
@@ -13,6 +15,7 @@ const LoginResponseSchema = type({
|
||||
const SignUpResponse = type({
|
||||
account: Account,
|
||||
user: User,
|
||||
drive: Drive,
|
||||
})
|
||||
|
||||
export const loginMutation = mutationOptions({
|
||||
@@ -29,6 +32,7 @@ export const loginMutation = mutationOptions({
|
||||
onSuccess: (data, _, __, context) => {
|
||||
context.client.setQueryData(currentUserQuery.queryKey, data.user)
|
||||
context.client.invalidateQueries(accountsQuery)
|
||||
context.client.invalidateQueries(drivesQuery)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -50,5 +54,6 @@ export const signUpMutation = mutationOptions({
|
||||
onSuccess: (data, _, __, context) => {
|
||||
context.client.setQueryData(currentUserQuery.queryKey, data.user)
|
||||
context.client.setQueryData(accountsQuery.queryKey, [data.account])
|
||||
context.client.setQueryData(drivesQuery.queryKey, [data.drive])
|
||||
},
|
||||
})
|
||||
|
||||
11
apps/drive-web/src/drive/api.ts
Normal file
11
apps/drive-web/src/drive/api.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { queryOptions } from "@tanstack/react-query"
|
||||
import { fetchApi } from "@/lib/api"
|
||||
import { Drive } from "./drive"
|
||||
|
||||
export const drivesQuery = queryOptions({
|
||||
queryKey: ["drives"],
|
||||
queryFn: async () =>
|
||||
fetchApi("GET", "/drives", {
|
||||
returns: Drive.array(),
|
||||
}).then(([_, result]) => result),
|
||||
})
|
||||
17
apps/drive-web/src/drive/drive.ts
Normal file
17
apps/drive-web/src/drive/drive.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { type } from "arktype"
|
||||
import { atom } from "jotai"
|
||||
|
||||
export const Drive = type({
|
||||
id: "string",
|
||||
publicId: "string",
|
||||
orgId: "string",
|
||||
"ownerAccountId?": "string",
|
||||
name: "string",
|
||||
createdAt: "string.date.iso.parse",
|
||||
updatedAt: "string.date.iso.parse",
|
||||
storageUsageBytes: "number",
|
||||
storageQuotaBytes: "number",
|
||||
})
|
||||
export type Drive = typeof Drive.infer
|
||||
|
||||
export const currentDriveAtom = atom<Drive | null>(null)
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import { formatError } from "@/lib/error"
|
||||
import { directoryContentQueryKey } from "@/vfs/api"
|
||||
import type { DirectoryInfoWithPath } from "@/vfs/vfs"
|
||||
import { currentAccountAtom } from "../account/account"
|
||||
import { currentDriveAtom } from "@/drive/drive"
|
||||
import {
|
||||
clearAllFileUploadStatusesAtom,
|
||||
clearFileUploadStatusesAtom,
|
||||
@@ -68,12 +68,12 @@ function useUploadFilesAtom({
|
||||
() =>
|
||||
mutationOptions({
|
||||
mutationFn: async (files: PickedFile[]) => {
|
||||
const account = store.get(currentAccountAtom)
|
||||
if (!account) throw new Error("No account selected")
|
||||
const drive = store.get(currentDriveAtom)
|
||||
if (!drive) throw new Error("No drive selected")
|
||||
|
||||
const promises = files.map((pickedFile) =>
|
||||
uploadFile({
|
||||
account,
|
||||
drive,
|
||||
file: pickedFile.file,
|
||||
targetDirectory,
|
||||
onStart: () => {
|
||||
@@ -136,11 +136,11 @@ function useUploadFilesAtom({
|
||||
}
|
||||
|
||||
// Invalidate all queries for the target directory (with any params)
|
||||
const account = store.get(currentAccountAtom)
|
||||
if (account) {
|
||||
const drive = store.get(currentDriveAtom)
|
||||
if (drive) {
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(
|
||||
account.id,
|
||||
drive.id,
|
||||
targetDirectory.id,
|
||||
),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type } from "arktype"
|
||||
import type { Account } from "@/account/account"
|
||||
import { ApiError, fetchApi } from "@/lib/api"
|
||||
import type { Drive } from "@/drive/drive"
|
||||
import type { DirectoryInfoWithPath } from "@/vfs/vfs"
|
||||
|
||||
export const UploadStatus = type.enumerated("pending", "completed", "failed")
|
||||
@@ -14,13 +14,13 @@ export const Upload = type({
|
||||
export type Upload = typeof Upload.infer
|
||||
|
||||
export async function uploadFile({
|
||||
account,
|
||||
drive,
|
||||
file,
|
||||
targetDirectory,
|
||||
onStart,
|
||||
onProgress,
|
||||
}: {
|
||||
account: Account
|
||||
drive: Drive
|
||||
file: File
|
||||
targetDirectory: DirectoryInfoWithPath
|
||||
onStart: (xhr: XMLHttpRequest) => void
|
||||
@@ -28,7 +28,7 @@ export async function uploadFile({
|
||||
}) {
|
||||
const [, upload] = await fetchApi(
|
||||
"POST",
|
||||
`/accounts/${account.id}/uploads`,
|
||||
`/drives/${drive.id}/uploads`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
name: file.name,
|
||||
@@ -45,7 +45,7 @@ export async function uploadFile({
|
||||
onProgress,
|
||||
})
|
||||
|
||||
await fetchApi("PATCH", `/accounts/${account.id}/uploads/${upload.id}`, {
|
||||
await fetchApi("PATCH", `/drives/${drive.id}/uploads/${upload.id}`, {
|
||||
body: JSON.stringify({
|
||||
status: "completed",
|
||||
}),
|
||||
|
||||
@@ -5,15 +5,21 @@ export type ApiRoute =
|
||||
| "/auth/tokens"
|
||||
| "/accounts"
|
||||
| `/accounts/${string}`
|
||||
| `/accounts/${string}/uploads`
|
||||
| `/accounts/${string}/uploads/${string}/content`
|
||||
| `/accounts/${string}/uploads/${string}`
|
||||
| `/accounts/${string}/files${string}`
|
||||
| `/accounts/${string}/files/${string}`
|
||||
| `/accounts/${string}/files/${string}/content`
|
||||
| `/accounts/${string}/directories`
|
||||
| `/accounts/${string}/directories/${string}`
|
||||
| `/accounts/${string}/directories/${string}/content`
|
||||
| "/drives"
|
||||
| `/drives/${string}`
|
||||
| `/drives/${string}/uploads`
|
||||
| `/drives/${string}/uploads/${string}/content`
|
||||
| `/drives/${string}/uploads/${string}`
|
||||
| `/drives/${string}/files${string}`
|
||||
| `/drives/${string}/files/${string}`
|
||||
| `/drives/${string}/files/${string}/content`
|
||||
| `/drives/${string}/files/${string}/shares${string}`
|
||||
| `/drives/${string}/directories`
|
||||
| `/drives/${string}/directories/${string}`
|
||||
| `/drives/${string}/directories/${string}/content`
|
||||
| `/drives/${string}/directories/${string}/shares${string}`
|
||||
| `/drives/${string}/shares`
|
||||
| `/drives/${string}/shares/${string}`
|
||||
| `/shares/${string}`
|
||||
| `/shares/${string}/directories${string}`
|
||||
| `/shares/${string}/files${string}`
|
||||
|
||||
@@ -2,30 +2,30 @@ import { createFileRoute, Navigate, Outlet } from "@tanstack/react-router"
|
||||
import { useAtomValue } from "jotai"
|
||||
import { atomEffect } from "jotai-effect"
|
||||
import { atomWithQuery } from "jotai-tanstack-query"
|
||||
import { accountsQuery } from "@/account/api"
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner"
|
||||
import { currentAccountAtom } from "../account/account"
|
||||
import { drivesQuery } from "@/drive/api"
|
||||
import { currentDriveAtom } from "@/drive/drive"
|
||||
|
||||
export const Route = createFileRoute("/_authenticated")({
|
||||
component: AuthenticatedLayout,
|
||||
})
|
||||
|
||||
const accountsAtom = atomWithQuery(() => accountsQuery)
|
||||
const selectFirstAccountEffect = atomEffect((get, set) => {
|
||||
const { data: accounts } = get(accountsAtom)
|
||||
const firstAccount = accounts?.[0]
|
||||
if (firstAccount && get.peek(currentAccountAtom) === null) {
|
||||
set(currentAccountAtom, firstAccount)
|
||||
const drivesAtom = atomWithQuery(() => drivesQuery)
|
||||
const selectFirstDriveEffect = atomEffect((get, set) => {
|
||||
const { data: drives } = get(drivesAtom)
|
||||
const firstDrive = drives?.[0]
|
||||
if (firstDrive && get.peek(currentDriveAtom) === null) {
|
||||
set(currentDriveAtom, firstDrive)
|
||||
}
|
||||
})
|
||||
|
||||
function AuthenticatedLayout() {
|
||||
const { data: accounts, isLoading: isLoadingAccounts } =
|
||||
useAtomValue(accountsAtom)
|
||||
const { data: drives, isLoading: isLoadingDrives } =
|
||||
useAtomValue(drivesAtom)
|
||||
|
||||
useAtomValue(selectFirstAccountEffect)
|
||||
useAtomValue(selectFirstDriveEffect)
|
||||
|
||||
if (isLoadingAccounts) {
|
||||
if (isLoadingDrives) {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center">
|
||||
<LoadingSpinner className="size-10" />
|
||||
@@ -33,7 +33,7 @@ function AuthenticatedLayout() {
|
||||
)
|
||||
}
|
||||
|
||||
if (!accounts) {
|
||||
if (!drives) {
|
||||
return <Navigate replace to="/login" />
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "lucide-react"
|
||||
import { lazy, Suspense, useCallback, useContext } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { currentAccountAtom } from "@/account/account"
|
||||
import { currentDriveAtom } from "@/drive/drive"
|
||||
import { DirectoryIcon } from "@/components/icons/directory-icon"
|
||||
import { TextFileIcon } from "@/components/icons/text-file-icon"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -378,7 +378,7 @@ function DirectoryContentContextMenu({
|
||||
const [target, setTarget] = useAtom(contextMenuTargetItemsAtom)
|
||||
const setBackgroundTaskProgress = useSetAtom(backgroundTaskProgressAtom)
|
||||
const setCutItems = useSetAtom(cutItemsAtom)
|
||||
const account = useAtomValue(currentAccountAtom)
|
||||
const drive = useAtomValue(currentDriveAtom)
|
||||
const { directory } = useContext(DirectoryPageContext)
|
||||
const search = Route.useSearch()
|
||||
const setActiveDialogData = useSetAtom(activeDialogDataAtom)
|
||||
@@ -390,11 +390,11 @@ function DirectoryContentContextMenu({
|
||||
setBackgroundTaskProgress({
|
||||
label: "Moving items to trash…",
|
||||
})
|
||||
if (!account) {
|
||||
if (!drive) {
|
||||
return null
|
||||
}
|
||||
return optimisticallyRemoveDirectoryItems(client, {
|
||||
queryKey: directoryContentQueryKey(account.id, directory.id, {
|
||||
queryKey: directoryContentQueryKey(drive.id, directory.id, {
|
||||
orderBy: search.orderBy,
|
||||
direction: search.direction,
|
||||
}),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { type } from "arktype"
|
||||
import { atom, useAtomValue } from "jotai"
|
||||
import { atom } from "jotai"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { currentAccountAtom } from "@/account/account"
|
||||
import { DirectoryPageContext } from "@/directories/directory-page/context"
|
||||
import {
|
||||
DEFAULT_DIRECTORY_CONTENT_ORDER_BY,
|
||||
@@ -48,11 +47,9 @@ function RouteComponent() {
|
||||
const { shareId, directoryId } = Route.useParams()
|
||||
const search = Route.useSearch()
|
||||
const navigate = Route.useNavigate()
|
||||
const account = useAtomValue(currentAccountAtom)
|
||||
const accountId = account?.id
|
||||
|
||||
const { data: directoryInfo, isLoading: isLoadingDirectoryInfo } = useQuery(
|
||||
shareDirectoryInfoQuery({ shareId, directoryId, accountId }),
|
||||
shareDirectoryInfoQuery({ shareId, directoryId }),
|
||||
)
|
||||
|
||||
const directoryUrlById = useCallback(
|
||||
@@ -74,9 +71,8 @@ function RouteComponent() {
|
||||
orderBy: search.orderBy,
|
||||
direction: search.direction,
|
||||
limit: 100,
|
||||
accountId,
|
||||
}),
|
||||
[shareId, directoryId, search.orderBy, search.direction, accountId],
|
||||
[shareId, directoryId, search.orderBy, search.direction],
|
||||
)
|
||||
|
||||
const applySorting = useCallback(
|
||||
@@ -99,11 +95,10 @@ function RouteComponent() {
|
||||
const url = shareFileContentUrl({
|
||||
shareId,
|
||||
fileId: file.id,
|
||||
accountId,
|
||||
})
|
||||
window.open(url, "_blank", "noopener,noreferrer")
|
||||
},
|
||||
[shareId, accountId],
|
||||
[shareId],
|
||||
)
|
||||
|
||||
if (isLoadingDirectoryInfo) {
|
||||
|
||||
@@ -33,34 +33,29 @@ function buildQueryString(params: Record<string, string | undefined>): string {
|
||||
type ShareDirectoryInfoQueryParams = {
|
||||
shareId: string
|
||||
directoryId: string
|
||||
accountId?: string
|
||||
}
|
||||
|
||||
export const shareDirectoryInfoQueryKey = (
|
||||
shareId: string,
|
||||
directoryId: string,
|
||||
accountId?: string,
|
||||
): readonly unknown[] => [
|
||||
"shares",
|
||||
shareId,
|
||||
"directories",
|
||||
directoryId,
|
||||
"info",
|
||||
accountId ?? "public",
|
||||
]
|
||||
|
||||
export function shareDirectoryInfoQuery({
|
||||
shareId,
|
||||
directoryId,
|
||||
accountId,
|
||||
}: ShareDirectoryInfoQueryParams) {
|
||||
const queryString = buildQueryString({
|
||||
include: "path",
|
||||
accountId,
|
||||
})
|
||||
|
||||
return queryOptions({
|
||||
queryKey: shareDirectoryInfoQueryKey(shareId, directoryId, accountId),
|
||||
queryKey: shareDirectoryInfoQueryKey(shareId, directoryId),
|
||||
queryFn: () =>
|
||||
fetchApi(
|
||||
"GET",
|
||||
@@ -76,7 +71,6 @@ type ShareDirectoryContentQueryParams = {
|
||||
orderBy: DirectoryContentOrderBy
|
||||
direction: DirectoryContentOrderDirection
|
||||
limit: number
|
||||
accountId?: string
|
||||
}
|
||||
|
||||
export const shareDirectoryContentQueryKey = (
|
||||
@@ -85,7 +79,6 @@ export const shareDirectoryContentQueryKey = (
|
||||
params?: {
|
||||
orderBy?: DirectoryContentOrderBy
|
||||
direction?: DirectoryContentOrderDirection
|
||||
accountId?: string
|
||||
},
|
||||
): readonly unknown[] => [
|
||||
"shares",
|
||||
@@ -98,7 +91,6 @@ export const shareDirectoryContentQueryKey = (
|
||||
{
|
||||
orderBy: params.orderBy,
|
||||
direction: params.direction,
|
||||
accountId: params.accountId,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -117,13 +109,11 @@ export function shareDirectoryContentQuery({
|
||||
orderBy,
|
||||
direction,
|
||||
limit,
|
||||
accountId,
|
||||
}: ShareDirectoryContentQueryParams) {
|
||||
return infiniteQueryOptions({
|
||||
queryKey: shareDirectoryContentQueryKey(shareId, directoryId, {
|
||||
orderBy,
|
||||
direction,
|
||||
accountId,
|
||||
}),
|
||||
initialPageParam: {
|
||||
orderBy,
|
||||
@@ -137,7 +127,6 @@ export function shareDirectoryContentQuery({
|
||||
dir: pageParam.direction,
|
||||
limit: String(pageParam.limit),
|
||||
cursor: pageParam.cursor || undefined,
|
||||
accountId,
|
||||
})
|
||||
return fetchApi(
|
||||
"GET",
|
||||
@@ -158,17 +147,12 @@ export function shareDirectoryContentQuery({
|
||||
type ShareFileContentUrlParams = {
|
||||
shareId: string
|
||||
fileId: string
|
||||
accountId?: string
|
||||
}
|
||||
|
||||
export function shareFileContentUrl({
|
||||
shareId,
|
||||
fileId,
|
||||
accountId,
|
||||
}: ShareFileContentUrlParams): string {
|
||||
const url = buildShareApiUrl(`/shares/${shareId}/files/${fileId}/content`)
|
||||
if (accountId) {
|
||||
url.searchParams.set("accountId", accountId)
|
||||
}
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { mutationOptions, queryOptions, skipToken } from "@tanstack/react-query"
|
||||
import { atom } from "jotai"
|
||||
import { atomFamily } from "jotai/utils"
|
||||
import { currentAccountAtom } from "@/account/account"
|
||||
import { fetchApi, Nothing } from "@/lib/api"
|
||||
import { currentDriveAtom } from "@/drive/drive"
|
||||
import { Share } from "./share"
|
||||
|
||||
export const fileSharesQueryAtom = atomFamily((fileId: string) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
const drive = get(currentDriveAtom)
|
||||
return queryOptions({
|
||||
queryKey: ["accounts", account?.id, "shares", { fileId }],
|
||||
queryFn: account
|
||||
queryKey: ["drives", drive?.id, "shares", { fileId }],
|
||||
queryFn: drive
|
||||
? () =>
|
||||
fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/files/${fileId}/shares?includesExpired=true`,
|
||||
`/drives/${drive.id}/files/${fileId}/shares?includesExpired=true`,
|
||||
{ returns: Share.array() },
|
||||
).then(([_, result]) => result)
|
||||
: skipToken,
|
||||
@@ -24,14 +24,14 @@ export const fileSharesQueryAtom = atomFamily((fileId: string) =>
|
||||
|
||||
export const directorySharesQueryAtom = atomFamily((directoryId: string) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
const drive = get(currentDriveAtom)
|
||||
return queryOptions({
|
||||
queryKey: ["accounts", account?.id, "shares", { directoryId }],
|
||||
queryFn: account
|
||||
queryKey: ["drives", drive?.id, "shares", { directoryId }],
|
||||
queryFn: drive
|
||||
? () =>
|
||||
fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/directories/${directoryId}/shares?includesExpired=true`,
|
||||
`/drives/${drive.id}/directories/${directoryId}/shares?includesExpired=true`,
|
||||
{ returns: Share.array() },
|
||||
).then(([_, result]) => result)
|
||||
: skipToken,
|
||||
@@ -42,12 +42,12 @@ export const directorySharesQueryAtom = atomFamily((directoryId: string) =>
|
||||
export const createShareMutationAtom = atom((get) =>
|
||||
mutationOptions({
|
||||
mutationFn: async ({ items }: { items: string[] }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) throw new Error("No account selected")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) throw new Error("No drive selected")
|
||||
|
||||
const [_, result] = await fetchApi(
|
||||
"POST",
|
||||
`/accounts/${account.id}/shares`,
|
||||
`/drives/${drive.id}/shares`,
|
||||
{
|
||||
body: JSON.stringify({ items }),
|
||||
returns: Share,
|
||||
@@ -62,12 +62,12 @@ export const createShareMutationAtom = atom((get) =>
|
||||
export const deleteShareMutationAtom = atom((get) =>
|
||||
mutationOptions({
|
||||
mutationFn: async ({ shareId }: { shareId: string }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) throw new Error("No account selected")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) throw new Error("No drive selected")
|
||||
|
||||
await fetchApi(
|
||||
"DELETE",
|
||||
`/accounts/${account.id}/shares/${shareId}`,
|
||||
`/drives/${drive.id}/shares/${shareId}`,
|
||||
{ returns: Nothing },
|
||||
)
|
||||
},
|
||||
@@ -83,12 +83,12 @@ export const updateShareMutationAtom = atom((get) =>
|
||||
shareId: string
|
||||
expiresAt?: Date | null
|
||||
}) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) throw new Error("No account selected")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) throw new Error("No drive selected")
|
||||
|
||||
await fetchApi(
|
||||
"PATCH",
|
||||
`/accounts/${account.id}/shares/${shareId}`,
|
||||
`/drives/${drive.id}/shares/${shareId}`,
|
||||
{ body: JSON.stringify({ expiresAt }), returns: Share },
|
||||
)
|
||||
},
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
import { type } from "arktype"
|
||||
import { atom } from "jotai"
|
||||
import { atomFamily } from "jotai/utils"
|
||||
import { currentAccountAtom } from "@/account/account"
|
||||
import { fetchApi } from "@/lib/api"
|
||||
import { currentDriveAtom } from "@/drive/drive"
|
||||
import {
|
||||
DirectoryContent,
|
||||
DirectoryInfo,
|
||||
@@ -30,23 +30,23 @@ export type DirectoryContentResponseType = typeof DirectoryContentResponse.infer
|
||||
*/
|
||||
export const fileUrlAtom = atomFamily((fileId: string) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) {
|
||||
return ""
|
||||
}
|
||||
return `${import.meta.env.VITE_API_URL}/accounts/${account.id}/files/${fileId}/content`
|
||||
return `${import.meta.env.VITE_API_URL}/drives/${drive.id}/files/${fileId}/content`
|
||||
}),
|
||||
)
|
||||
|
||||
export const rootDirectoryQueryAtom = atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
const drive = get(currentDriveAtom)
|
||||
return queryOptions({
|
||||
queryKey: ["accounts", account?.id, "directories", "root"],
|
||||
queryFn: account
|
||||
queryKey: ["drives", drive?.id, "directories", "root"],
|
||||
queryFn: drive
|
||||
? () =>
|
||||
fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/directories/root?include=path`,
|
||||
`/drives/${drive.id}/directories/root?include=path`,
|
||||
{ returns: DirectoryInfoWithPath },
|
||||
).then(([_, result]) => result)
|
||||
: skipToken,
|
||||
@@ -55,14 +55,14 @@ export const rootDirectoryQueryAtom = atom((get) => {
|
||||
|
||||
export const directoryInfoQueryAtom = atomFamily((directoryId: string) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
const drive = get(currentDriveAtom)
|
||||
return queryOptions({
|
||||
queryKey: ["accounts", account?.id, "directories", directoryId],
|
||||
queryFn: account
|
||||
queryKey: ["drives", drive?.id, "directories", directoryId],
|
||||
queryFn: drive
|
||||
? () =>
|
||||
fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/directories/${directoryId}?include=path`,
|
||||
`/drives/${drive.id}/directories/${directoryId}?include=path`,
|
||||
{ returns: DirectoryInfoWithPath },
|
||||
).then(([_, result]) => result)
|
||||
: skipToken,
|
||||
@@ -100,15 +100,15 @@ type DirectoryContentPageParam = {
|
||||
}
|
||||
|
||||
export const directoryContentQueryKey = (
|
||||
accountId: string | undefined,
|
||||
driveId: string | undefined,
|
||||
directoryId: string,
|
||||
params?: {
|
||||
orderBy?: DirectoryContentOrderBy
|
||||
direction?: DirectoryContentOrderDirection
|
||||
},
|
||||
): readonly unknown[] => [
|
||||
"accounts",
|
||||
accountId,
|
||||
"drives",
|
||||
driveId,
|
||||
"directories",
|
||||
directoryId,
|
||||
"content",
|
||||
@@ -126,9 +126,9 @@ export type DirectoryContentQuery = ReturnType<
|
||||
export const directoryContentQueryAtom = atomFamily(
|
||||
({ directoryId, orderBy, direction, limit }: DirectoryContentQueryParams) =>
|
||||
atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
const drive = get(currentDriveAtom)
|
||||
return infiniteQueryOptions({
|
||||
queryKey: directoryContentQueryKey(account?.id, directoryId, {
|
||||
queryKey: directoryContentQueryKey(drive?.id, directoryId, {
|
||||
orderBy,
|
||||
direction,
|
||||
}),
|
||||
@@ -139,13 +139,13 @@ export const directoryContentQueryAtom = atomFamily(
|
||||
cursor: "",
|
||||
},
|
||||
queryFn: ({ pageParam }) =>
|
||||
account
|
||||
drive
|
||||
? fetchApi(
|
||||
"GET",
|
||||
`/accounts/${account.id}/directories/${directoryId}/content?orderBy=${pageParam.orderBy}&dir=${pageParam.direction}&limit=${pageParam.limit}${pageParam.cursor ? `&cursor=${pageParam.cursor}` : ""}`,
|
||||
`/drives/${drive.id}/directories/${directoryId}/content?orderBy=${pageParam.orderBy}&dir=${pageParam.direction}&limit=${pageParam.limit}${pageParam.cursor ? `&cursor=${pageParam.cursor}` : ""}`,
|
||||
{ returns: DirectoryContentResponse },
|
||||
).then(([_, result]) => result)
|
||||
: Promise.reject(new Error("No account selected")),
|
||||
: Promise.reject(new Error("No drive selected")),
|
||||
getNextPageParam: (lastPage, _pages, lastPageParam) =>
|
||||
lastPage.nextCursor
|
||||
? {
|
||||
@@ -163,13 +163,13 @@ export const directoryContentQueryAtom = atomFamily(
|
||||
)
|
||||
|
||||
export const createDirectoryMutationAtom = atom((get) => {
|
||||
const account = get(currentAccountAtom)
|
||||
const drive = get(currentDriveAtom)
|
||||
return mutationOptions({
|
||||
mutationFn: async (data: { name: string; parentId: string }) => {
|
||||
if (!account) throw new Error("No account selected")
|
||||
if (!drive) throw new Error("No drive selected")
|
||||
return fetchApi(
|
||||
"POST",
|
||||
`/accounts/${account.id}/directories?include=path`,
|
||||
`/drives/${drive.id}/directories?include=path`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
name: data.name,
|
||||
@@ -180,9 +180,9 @@ export const createDirectoryMutationAtom = atom((get) => {
|
||||
).then(([_, result]) => result)
|
||||
},
|
||||
onSuccess: (_data, { parentId }, _context, { client }) => {
|
||||
if (account) {
|
||||
if (drive) {
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(account.id, parentId),
|
||||
queryKey: directoryContentQueryKey(drive.id, parentId),
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -209,9 +209,9 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
targetDirectory: DirectoryInfo | string
|
||||
items: DirectoryItem[]
|
||||
}) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
throw new Error("Account not found")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) {
|
||||
throw new Error("Drive not found")
|
||||
}
|
||||
|
||||
const dirId =
|
||||
@@ -221,7 +221,7 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
|
||||
const [, result] = await fetchApi(
|
||||
"POST",
|
||||
`/accounts/${account.id}/directories/${dirId}/content`,
|
||||
`/drives/${drive.id}/directories/${dirId}/content`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
items: items.map((item) => item.id),
|
||||
@@ -232,8 +232,8 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
return result
|
||||
},
|
||||
onSuccess: (_data, { targetDirectory, items }, _result, { client }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) return
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) return
|
||||
|
||||
const dirId =
|
||||
typeof targetDirectory === "string"
|
||||
@@ -241,13 +241,13 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
: targetDirectory.id
|
||||
// Invalidate using base key (without params) to invalidate all queries for these directories
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(account.id, dirId),
|
||||
queryKey: directoryContentQueryKey(drive.id, dirId),
|
||||
})
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(
|
||||
account.id,
|
||||
drive.id,
|
||||
item.parentId,
|
||||
),
|
||||
})
|
||||
@@ -260,9 +260,9 @@ export const moveDirectoryItemsMutationAtom = atom((get) =>
|
||||
export const moveToTrashMutationAtom = atom((get) =>
|
||||
mutationOptions({
|
||||
mutationFn: async (items: DirectoryItem[]) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
throw new Error("Account not found")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) {
|
||||
throw new Error("Drive not found")
|
||||
}
|
||||
|
||||
const fileIds: string[] = []
|
||||
@@ -285,7 +285,7 @@ export const moveToTrashMutationAtom = atom((get) =>
|
||||
fileDeleteParams.set("trash", "true")
|
||||
deleteFilesPromise = fetchApi(
|
||||
"DELETE",
|
||||
`/accounts/${account.id}/files?${fileDeleteParams.toString()}`,
|
||||
`/drives/${drive.id}/files?${fileDeleteParams.toString()}`,
|
||||
{
|
||||
returns: FileInfo.array(),
|
||||
},
|
||||
@@ -301,7 +301,7 @@ export const moveToTrashMutationAtom = atom((get) =>
|
||||
directoryDeleteParams.set("trash", "true")
|
||||
deleteDirectoriesPromise = fetchApi(
|
||||
"DELETE",
|
||||
`/accounts/${account.id}/directories?${directoryDeleteParams.toString()}`,
|
||||
`/drives/${drive.id}/directories?${directoryDeleteParams.toString()}`,
|
||||
{
|
||||
returns: DirectoryInfo.array(),
|
||||
},
|
||||
@@ -318,14 +318,14 @@ export const moveToTrashMutationAtom = atom((get) =>
|
||||
return [...deletedFiles, ...deletedDirectories]
|
||||
},
|
||||
onSuccess: (_data, items, _result, { client }) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (account) {
|
||||
const drive = get(currentDriveAtom)
|
||||
if (drive) {
|
||||
// Invalidate using base key (without params) to invalidate all queries for these directories
|
||||
for (const item of items) {
|
||||
if (item.parentId) {
|
||||
client.invalidateQueries({
|
||||
queryKey: directoryContentQueryKey(
|
||||
account.id,
|
||||
drive.id,
|
||||
item.parentId,
|
||||
),
|
||||
})
|
||||
@@ -339,14 +339,14 @@ export const moveToTrashMutationAtom = atom((get) =>
|
||||
export const renameFileMutationAtom = atom((get) =>
|
||||
mutationOptions({
|
||||
mutationFn: async (file: FileInfo) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
throw new Error("Account not found")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) {
|
||||
throw new Error("Drive not found")
|
||||
}
|
||||
|
||||
const [, result] = await fetchApi(
|
||||
"PATCH",
|
||||
`/accounts/${account.id}/files/${file.id}`,
|
||||
`/drives/${drive.id}/files/${file.id}`,
|
||||
{
|
||||
body: JSON.stringify({ name: file.name }),
|
||||
returns: FileInfo,
|
||||
@@ -361,14 +361,14 @@ export const renameFileMutationAtom = atom((get) =>
|
||||
export const renameDirectoryMutationAtom = atom((get) =>
|
||||
mutationOptions({
|
||||
mutationFn: async (directory: DirectoryInfo) => {
|
||||
const account = get(currentAccountAtom)
|
||||
if (!account) {
|
||||
throw new Error("Account not found")
|
||||
const drive = get(currentDriveAtom)
|
||||
if (!drive) {
|
||||
throw new Error("Drive not found")
|
||||
}
|
||||
|
||||
const [, result] = await fetchApi(
|
||||
"PATCH",
|
||||
`/accounts/${account.id}/directories/${directory.id}`,
|
||||
`/drives/${drive.id}/directories/${directory.id}`,
|
||||
{
|
||||
body: JSON.stringify({ name: directory.name }),
|
||||
returns: DirectoryInfo,
|
||||
|
||||
Reference in New Issue
Block a user