mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-22 21:14:18 +00:00
230 lines
No EOL
6.9 KiB
JavaScript
230 lines
No EOL
6.9 KiB
JavaScript
import { useCallback, useContext, useEffect, useState } from "react"
|
|
import ContextServer from "../contexts/ContextServer"
|
|
import ContextUser from "../contexts/ContextUser"
|
|
import makeURLSearchParams from "../utils/makeURLSearchParams"
|
|
|
|
|
|
/**
|
|
* An hook which allows access to a full REST viewset (list, create, retrieve, edit, delete).
|
|
*
|
|
* @param resourcesPath - The path of the resource directory.
|
|
* @param pkName - The name of the primary key attribute of the elements.
|
|
* @param refreshOnStart - Whether the data should be loaded at the first startup (defaults to true).
|
|
*/
|
|
export default function useBackendViewset(resourcesPath, pkName, refreshOnStart = true) {
|
|
const { server } = useContext(ContextServer)
|
|
const configured = server !== null
|
|
|
|
const { user } = useContext(ContextUser)
|
|
const loggedIn = user !== null
|
|
|
|
const [abort, setAbort] = useState(null)
|
|
const running = abort !== null
|
|
|
|
const [resources, setResources] = useState(null)
|
|
const loaded = resources !== null
|
|
|
|
const apiRequest = useCallback(
|
|
async (method, path, body, init = {}) => {
|
|
// Check if server is configured
|
|
if(!configured) {
|
|
throw new Error(`Backend server not configured.`)
|
|
}
|
|
|
|
// Check if something is not already being loaded
|
|
if(running) {
|
|
throw new Error(`A request is already running.`)
|
|
}
|
|
|
|
// Ensure init has certain sub-objects
|
|
if(!init["headers"]) {
|
|
init["headers"] = {}
|
|
}
|
|
|
|
// If the user is logged in, add the Authorization headers
|
|
if(loggedIn) {
|
|
init["headers"]["Authorization"] = `Bearer ${user.token}`
|
|
}
|
|
|
|
// Set the Content-Type header
|
|
init["headers"]["Content-Type"] = "application/json"
|
|
|
|
// Use the body param as either search parameter or request body
|
|
if(body) {
|
|
if(["GET", "HEAD"].includes(method.toUpperCase())) {
|
|
path += makeURLSearchParams(body).toString()
|
|
}
|
|
else {
|
|
init["body"] = JSON.stringify(body)
|
|
}
|
|
}
|
|
|
|
// Set the method
|
|
init["method"] = method
|
|
|
|
// Create a new abort handler in case the request needs to be aborted
|
|
const thisAbort = new AbortController()
|
|
init["signal"] = thisAbort.signal
|
|
setAbort(thisAbort)
|
|
|
|
// Fetch the resource
|
|
const response = await fetch(`${server}${path}`, init)
|
|
|
|
// Clear the abort handler
|
|
setAbort(null)
|
|
|
|
// Check if the request was successful
|
|
if(!response.ok) {
|
|
throw new Error(`${method} ${path} failed with status code ${response.status} ${response.statusText}`)
|
|
}
|
|
|
|
// If the response is 204 NO CONTENT, return null
|
|
if(response.status === 204) {
|
|
return null
|
|
}
|
|
|
|
// Otherwise, try parsing the response as JSON
|
|
const json = await response.json()
|
|
|
|
// Check if the JSON contains a success
|
|
if(json["result"] !== "success") {
|
|
throw new Error(`${method} ${path} failed with message ${json["msg"]}`)
|
|
}
|
|
|
|
return json["data"]
|
|
},
|
|
[server, configured, running, loggedIn, user, setAbort],
|
|
)
|
|
|
|
const apiList = useCallback(
|
|
async (init) => await apiRequest("GET", `${resourcesPath}`, undefined, init),
|
|
[apiRequest, resourcesPath],
|
|
)
|
|
|
|
const apiRetrieve = useCallback(
|
|
async (id, init) => await apiRequest("GET", `${resourcesPath}${id}`, undefined, init),
|
|
[apiRequest, resourcesPath],
|
|
)
|
|
|
|
const apiCreate = useCallback(
|
|
async (data, init) => await apiRequest("POST", `${resourcesPath}`, data, init),
|
|
[apiRequest, resourcesPath],
|
|
)
|
|
|
|
const apiEdit = useCallback(
|
|
async (id, data, init) => await apiRequest("PUT", `${resourcesPath}${id}`, data, init),
|
|
[apiRequest, resourcesPath],
|
|
)
|
|
|
|
const apiDestroy = useCallback(
|
|
async (id, init) => await apiRequest("DELETE", `${resourcesPath}${id}`, undefined, init),
|
|
[apiRequest, resourcesPath],
|
|
)
|
|
|
|
const refreshResources = useCallback(
|
|
async () => {
|
|
try {
|
|
setResources(await apiList())
|
|
}
|
|
catch(e) {
|
|
return { error: e }
|
|
}
|
|
return {}
|
|
},
|
|
[apiList],
|
|
)
|
|
|
|
const refreshResource = useCallback(
|
|
async (pk) => {
|
|
try {
|
|
const refreshedResource = await apiRetrieve(pk)
|
|
setResources(resources => resources.map(resource => {
|
|
if(resource[pkName] === pk) {
|
|
return refreshedResource
|
|
}
|
|
return resource
|
|
}))
|
|
}
|
|
catch(e) {
|
|
return { error: e }
|
|
}
|
|
return {}
|
|
},
|
|
[apiRetrieve, pkName],
|
|
)
|
|
|
|
const createResource = useCallback(
|
|
async (data) => {
|
|
try {
|
|
const newResource = await apiCreate(data)
|
|
setResources(resources => [...resources, newResource])
|
|
}
|
|
catch(e) {
|
|
return { error: e }
|
|
}
|
|
return {}
|
|
},
|
|
[apiCreate],
|
|
)
|
|
|
|
const editResource = useCallback(
|
|
async (pk, data) => {
|
|
try {
|
|
const editedResource = await apiEdit(pk, data)
|
|
setResources(resources => resources.map(resource => {
|
|
if(resource[pkName] === pk) {
|
|
return editedResource
|
|
}
|
|
return resource
|
|
}))
|
|
}
|
|
catch(e) {
|
|
return { error: e }
|
|
}
|
|
return {}
|
|
},
|
|
[apiEdit, pkName],
|
|
)
|
|
|
|
const destroyResource = useCallback(
|
|
async (pk) => {
|
|
try {
|
|
await apiDestroy(pk)
|
|
setResources(resources => resources.filter(resource => resource[pkName] !== pk))
|
|
}
|
|
catch(e) {
|
|
return { error: e }
|
|
}
|
|
return {}
|
|
},
|
|
[apiDestroy, pkName],
|
|
)
|
|
|
|
useEffect(
|
|
() => {
|
|
if(refreshOnStart && !(loaded || running)) {
|
|
// noinspection JSIgnoredPromiseFromCall
|
|
refreshResources()
|
|
}
|
|
},
|
|
[loaded, refreshResources, running, refreshOnStart],
|
|
)
|
|
|
|
return {
|
|
abort,
|
|
resources,
|
|
running,
|
|
loaded,
|
|
apiRequest,
|
|
apiList,
|
|
apiRetrieve,
|
|
apiCreate,
|
|
apiEdit,
|
|
apiDestroy,
|
|
refreshResources,
|
|
refreshResource,
|
|
createResource,
|
|
editResource,
|
|
destroyResource,
|
|
}
|
|
} |