diff --git a/uploader/api/src/Entity/Commit.php b/uploader/api/src/Entity/Commit.php index 7d7bc09fc..8f6da385d 100644 --- a/uploader/api/src/Entity/Commit.php +++ b/uploader/api/src/Entity/Commit.php @@ -75,7 +75,7 @@ class Commit extends AbstractUuidEntity private ?string $totalSize = null; #[ORM\Column(type: Types::JSON)] - #[Groups(['asset:read', 'commit:read'])] + #[Groups(['asset:read', 'commit:read', 'commit:write'])] private array $formData = []; #[Groups(['asset:read', 'commit:read', 'commit:write'])] diff --git a/uploader/client/src/App.js b/uploader/client/src/App.js index aa2f909ae..e1802506d 100644 --- a/uploader/client/src/App.js +++ b/uploader/client/src/App.js @@ -17,7 +17,7 @@ import AuthError from "./components/page/AuthError"; import SelectTarget from "./components/page/SelectTarget"; import {DashboardMenu} from "@alchemy/react-ps"; import FullPageLoader from "./components/FullPageLoader"; -import {authenticatedRequest} from "./lib/api"; +import apiClient from "./lib/api"; class App extends Component { state = { @@ -45,10 +45,12 @@ class App extends Component { authenticate = async () => { this.setState({authenticating: true}); - await authenticatedRequest({ - url: '/me', - }); - this.setState({authenticating: false}); + const user = (await apiClient.get('/me')).data; + + this.setState({ + user, + authenticating: false, + }) } handleStateChange(state) { @@ -65,7 +67,7 @@ class App extends Component { render() { const {user} = this.state; - const perms = user && user.permissions; + const perms = user?.permissions ?? {}; return {config.displayServicesMenu && : ''} this.closeMenu()} to="/" className="menu-item">Home - {perms && perms.form_schema ? + {perms.form_schema ? this.closeMenu()} to="/form-editor">Form editor : ''} - {perms && perms.target_data ? + {perms.target_data ? this.closeMenu()} to="/target-data-editor">Target data editor : ''} {oauthClient.isAuthenticated() ? Logout @@ -97,8 +99,8 @@ class App extends Component { - {perms && perms.form_schema ? : ''} - {perms && perms.target_data ? + {perms.form_schema ? : ''} + {perms.target_data ? : ''} diff --git a/uploader/client/src/actions/download.js b/uploader/client/src/actions/download.js index a2a5e9e52..c70c81ce2 100644 --- a/uploader/client/src/actions/download.js +++ b/uploader/client/src/actions/download.js @@ -1,11 +1,7 @@ -import {authenticatedRequest} from "../lib/api"; +import apiClient from "../lib/api"; export function Download(url, callback, errCallback) { - authenticatedRequest({ - url: '/downloads', - method: 'POST', - data: { - url, - }, - }).then(() => callback()); + apiClient.post('/downloads', { + url, + }).then(() => callback()).catch(errCallback); } diff --git a/uploader/client/src/components/AssetForm.js b/uploader/client/src/components/AssetForm.js index fc7c08daa..08d476168 100644 --- a/uploader/client/src/components/AssetForm.js +++ b/uploader/client/src/components/AssetForm.js @@ -5,7 +5,7 @@ import AssetLiForm from "./AssetLiForm"; import {SubmissionError} from 'redux-form'; import {Translation} from "react-i18next"; import {getFormSchema} from "../requests"; -import {authenticatedRequest} from "../lib/api"; +import apiClient from "../lib/api"; export default class AssetForm extends Component { static propTypes = { @@ -79,10 +79,7 @@ export default class AssetForm extends Component { let r; try { - r = await authenticatedRequest({ - url: submitPath, - data, - }); + r = (await apiClient.post(submitPath, data)).data; } catch (e) { console.log(e); throw new SubmissionError({_error: e.toString()}); diff --git a/uploader/client/src/components/page/FormEditor.js b/uploader/client/src/components/page/FormEditor.js index 41a77b19e..baa793b42 100644 --- a/uploader/client/src/components/page/FormEditor.js +++ b/uploader/client/src/components/page/FormEditor.js @@ -4,7 +4,7 @@ import FormPreview from "../FormPreview"; import Container from "../Container"; import {getFormSchema, getTargets} from "../../requests"; import FullPageLoader from "../FullPageLoader"; -import {authenticatedRequest} from "../../lib/api"; +import apiClient from "../../lib/api"; export default class FormEditor extends Component { state = { @@ -73,7 +73,7 @@ export default class FormEditor extends Component { data.target = `/targets/${selected}`; } - await authenticatedRequest(requestConfig); + await apiClient.request(requestConfig); this.setState({ saved: true, diff --git a/uploader/client/src/components/page/TargetDataEditor.js b/uploader/client/src/components/page/TargetDataEditor.js index 7cbccec08..d85f648cd 100644 --- a/uploader/client/src/components/page/TargetDataEditor.js +++ b/uploader/client/src/components/page/TargetDataEditor.js @@ -3,7 +3,7 @@ import {Button, Form} from "react-bootstrap"; import Container from "../Container"; import {getTargetParams, getTargets} from "../../requests"; import FullPageLoader from "../FullPageLoader"; -import {authenticatedRequest} from "../../lib/api"; +import apiClient from "../../lib/api"; export default class TargetDataEditor extends Component { state = { @@ -77,7 +77,7 @@ export default class TargetDataEditor extends Component { data.target = `/targets/${selected}`; } - await authenticatedRequest(requestConfig); + await apiClient.request(requestConfig); this.setState({ saved: true, diff --git a/uploader/client/src/lib/api.js b/uploader/client/src/lib/api.js index 986aeccab..bad511a6e 100644 --- a/uploader/client/src/lib/api.js +++ b/uploader/client/src/lib/api.js @@ -1,19 +1,11 @@ -import axios from "axios"; import config from '../config'; import {oauthClient} from "../oauth"; +import {configureClientAuthentication, createHttpClient} from "@alchemy/auth"; -export const apiClient = axios.create({ - baseURL: config.baseUrl, -}) +const apiClient = createHttpClient(config.baseUrl); -export function authenticatedRequest(config) { - return oauthClient.wrapPromiseWithValidToken(async ({access_token, token_type}) => { - return (await apiClient.request({ - ...config, - headers: { - Authorization: `${token_type} ${access_token}`, - ...(config.headers ?? {}), - }, - })).data; - }); -} +configureClientAuthentication(apiClient, oauthClient, () => { + alert('Your session has expired'); +}); + +export default apiClient; diff --git a/uploader/client/src/multiPartUpload.js b/uploader/client/src/multiPartUpload.js index 5ebc7d5f6..bd4dd7d65 100644 --- a/uploader/client/src/multiPartUpload.js +++ b/uploader/client/src/multiPartUpload.js @@ -1,49 +1,8 @@ -import {oauthClient} from "./oauth"; import {getUniqueFileId, uploadStateStorage} from "./uploadStateStorage"; -import {apiClient} from "./lib/api"; +import apiClient from "./lib/api"; const fileChunkSize = 5242880 // 5242880 is the minimum allowed by AWS S3; -async function asyncRequest(file, method, uri, auth, postData, onProgress, options = {}) { - const config = { - url: uri, - method, - }; - if (postData) { - config.data = postData; - } - - if (onProgress) { - config.onUploadProgress = onProgress; - /*{ - function (axiosProgressEvent) { - loaded: number; - total?: number; - progress?: number; // in range [0..1] - bytes: number; // how many bytes have been transferred since the last trigger (delta) - estimated?: number; // estimated time in seconds - rate?: number; // upload speed in bytes - upload: true; // upload sign - }*/ - } - - if (file) { - file.abortController = new AbortController(); - config.signal = file.abortController.signal; - } - - if (auth) { - return oauthClient.wrapPromiseWithValidToken(({access_token, token_type}) => { - config.headers ??= {}; - config.headers['Authorization'] = `${token_type} ${access_token}`; - - return apiClient.request(config); - }); - } else { - return apiClient.request(config); - } -} - export async function uploadMultipartFile(targetId, userId, file, onProgress) { const fileUID = getUniqueFileId(file.file, fileChunkSize); @@ -65,12 +24,16 @@ export async function uploadMultipartFile(targetId, userId, file, onProgress) { }); } } else { - const res = await asyncRequest(file, 'POST', `/uploads`, true, { + file.abortController = new AbortController(); + + const res = await apiClient.post(`/uploads`, { filename: file.file.name, type: file.file.type, size: file.file.size, + }, { + signal: file.abortController.signal, }); - console.log('res', res); + console.debug('res', res); uploadId = res.data.id; path = res.data.path; uploadStateStorage.initUpload(userId, fileUID, uploadId, path); @@ -83,22 +46,32 @@ export async function uploadMultipartFile(targetId, userId, file, onProgress) { const start = (index - 1) * fileChunkSize; const end = (index) * fileChunkSize; - const getUploadUrlResp = await asyncRequest(file, 'POST', `/uploads/${uploadId}/part`, true, { + file.abortController = new AbortController(); + + const getUploadUrlResp = await apiClient.post(`/uploads/${uploadId}/part`, { part: index, + }, { + signal: file.abortController.signal, }); - console.log('getUploadUrlResp', getUploadUrlResp); + console.debug('getUploadUrlResp', getUploadUrlResp); const {url} = getUploadUrlResp.data; const blob = (index < numChunks) ? file.file.slice(start, end) : file.file.slice(start); - const uploadResp = await asyncRequest(file, 'PUT', url, null, blob, (e) => { - const multiPartEvent = { - ...e, - loaded: e.loaded + start, - }; + file.abortController = new AbortController(); + + const uploadResp = await apiClient.put(url, blob, { + signal: file.abortController.signal, + anonymous: true, + onUploadProgress: (e) => { + const multiPartEvent = { + ...e, + loaded: e.loaded + start, + }; - onProgress(multiPartEvent); + onProgress(multiPartEvent); + } }); const eTag = uploadResp.headers.etag; @@ -110,12 +83,16 @@ export async function uploadMultipartFile(targetId, userId, file, onProgress) { uploadStateStorage.updateUpload(userId, fileUID, eTag); } - const finalRes = await asyncRequest(file, 'POST', `/assets`, true, { + file.abortController = new AbortController(); + + const finalRes = await apiClient.post(`/assets`, { targetId, multipart: { uploadId, parts: uploadParts, } + }, { + signal: file.abortController.signal, }); uploadStateStorage.removeUpload(userId, fileUID); diff --git a/uploader/client/src/requests.js b/uploader/client/src/requests.js index 3f03222fb..e08c26eaf 100644 --- a/uploader/client/src/requests.js +++ b/uploader/client/src/requests.js @@ -1,28 +1,21 @@ -import {authenticatedRequest} from "./lib/api"; +import apiClient from "./lib/api"; -export function getFormSchema(targetId) { - return authenticatedRequest({ - url: `/targets/${targetId}/form-schema`, - }); +export async function getFormSchema(targetId) { + return (await apiClient.get(`/targets/${targetId}/form-schema`)).data; } export async function getTargets() { - return (await authenticatedRequest({ - url: `/targets`, - }))['hydra:member']; + return (await apiClient.get(`/targets`)).data['hydra:member']; } -export function getTarget(id) { - return authenticatedRequest({ - url: `/targets/${id}`, - }); +export async function getTarget(id) { + return (await apiClient.get(`/targets/${id}`)).data; } -export function getTargetParams(targetId) { - return authenticatedRequest({ - url: `/target-params`, +export async function getTargetParams(targetId) { + return (await apiClient.get(`/target-params`, { params: { target: targetId, }, - }); + })).data['hydra:member']; } diff --git a/uploader/client/src/uploadBatch.js b/uploader/client/src/uploadBatch.js index d9bac6887..d250f83e7 100644 --- a/uploader/client/src/uploadBatch.js +++ b/uploader/client/src/uploadBatch.js @@ -1,6 +1,6 @@ import {oauthClient} from "./oauth"; import {uploadMultipartFile} from "./multiPartUpload"; -import {authenticatedRequest} from "./lib/api"; +import apiClient from "./lib/api"; export default class UploadBatch { files = []; @@ -127,11 +127,7 @@ export default class UploadBatch { target: `/targets/${this.targetId}`, }; - authenticatedRequest({ - url: '/commit', - method: 'POST', - data: formData, - }); + apiClient.post('/commit', formData); } async uploadFile(index, retry = 0) {