Merge branch '2268-update-upload-spec-and-api-spec' into 'develop'

Add upload groups to upload spec

Closes #2268

See merge request funkwhale/funkwhale!2697
Ciarán Ainsworth 2024-04-16 13:13:34 +00:00
commit 640c2720db
2 zmienionych plików z 172 dodań i 32 usunięć

Wyświetl plik

@ -0,0 +1 @@
Add import group details to content upload spec (#2268)

Wyświetl plik

@ -2,11 +2,11 @@
## The issue
Our current upload process is quite cumbersome and can be confusing for new users. Essentially, users have to know exactly where they want to put content before they've even uploaded it, and once it's uploaded it's hard to move.
Funkwhale's current upload process is cumbersome and can be confusing for new users. Essentially, users have to know exactly where they want to put content before they've even uploaded it, and once it's uploaded it's hard to move.
There are currently 2 upload flows:
### Upload to library
:::{dropdown} Upload to library
Uploading to a content library is the oldest upload mechanism in Funkwhale. The flow goes like this:
@ -15,9 +15,11 @@ Uploading to a content library is the oldest upload mechanism in Funkwhale. The
3. The user chooses which library they want to upload to and clicks "Upload"
4. The user selects content or drags and drops it to complete the upload
### Upload to a channel
:::
Channels are a newer feature, but the upload process is largely the same:
:::{dropdown} Upload to a channel
Channels are a newer feature, but the upload process is similar:
1. The user clicks on the upload button
2. The user selects "Publish your work in a channel"
@ -27,6 +29,8 @@ Channels are a newer feature, but the upload process is largely the same:
6. The user selects content or drags and drops it to complete the upload
7. The user inputs relevant details about each track
:::
## Proposed solution
To simplify this flow, the upload process should have a single entrypoint which then guides the user to the correct upload context. Rather than needing to go through a series of steps to upload each item. The proposed solution is to create a single modal for uploads that doesn't require the user to navigate to upload content to different locations.
@ -41,7 +45,7 @@ The new feature will present an upload menu that gives users all upload options
Once the user selects an option, they can select the target from a dropdown and start uploading straight away.
### Frontend
## Web app
The new workflow goes as follows:
@ -57,7 +61,7 @@ The new workflow goes as follows:
4. The user selects the location to which they want to upload their content
5. The user selects the files they want to upload from a file picker, or by dragging and dropping files onto the modal
6. The user can select a "Upload in background" button which dismisses the modal but _continues the upload_
7. Funkwhale assesses if all files have the correct metadata and highlights any issues for the user to fix _with a meaningful message_. The frontend keep the connection open until the API sends a response
7. Funkwhale assesses if all files have the correct metadata and highlights any issues for the user to fix _with a meaningful message_. The web app keep the connection open until the API sends a response
```{mermaid}
flowchart TD
@ -70,56 +74,191 @@ flowchart TD
choose --> files[The user drags files to upload\nor selects files in a file picker]
files --> wait(The user waits for the upload to complete) & close(The user closes the modal)
wait & close --> process(Funkwhale processes the uploads\nand verifies metadata)
process --> message([Funkwhale returns status messages for all uploads\nand notifies the user the the upload is complete])
process --> message([Funkwhale returns status messages for all uploads\nand notifies the user the upload is complete])
```
The frontend should reflect the **status** of the upload to inform the user how the upload is progressing:
The web app should reflect the **status** of the upload to inform the user how the upload is progressing:
- `Failed`: The file is improperly tagged **or** the API has responded with an error
- `Uploading`: The file is being sent to the server
- `Processing`: The server is processing the file and no success response has been returned yet
- `Uploading`: The web app is uploading the file to the server
- `Processing`: The server is processing the file and hasn't returned a response
- `Success`: The API has responded with a `200: Success` response and passed back information about the upload.
#### UX considerations
### UX considerations
To prevent disrupted uploads, the following UX should be implemented
To prevent disrupted uploads, we should implement the following UX:
- If the user dismisses the modal with the escape key, by clicking outside the modal, or by moving back to the previous page, _the user should be warned and given the option to cancel their upload or continue it in the background_
- If the user dismisses the modal with the escape key, by clicking outside the modal, or by moving back to the previous page, _the web app must warn the user and given the option to cancel their upload or continue it in the background_
- The user should have the option to cancel an upload at any time
- If an upload is sent to the background, it should notify the user in some way when the upload is complete
- If the user sends the upload modal to the background, Funkwhale should notify the user when the upload is complete
:::{seealso}
See the [interactive prototype](https://design.funkwhale.audio/#/view/e3a187f0-0f5e-11ed-adb9-fff9e854a67c?page-id=d9f9f4d0-1a7b-11ed-8551-a35b3c702efa&section=interactions&index=0) for an overview of the behavior.
:::
### Backend behavior
## Backend
The upload process remains the same on the backend. However, the error checking needs to be more descriptive. For example:
To give upload results more structure, each upload created in the web app must belong to an upload group. An upload group is a simple collection of uploads.
- Failed metadata checks should be explicit about what issues were found in the metadata and should return this in a readable way for the user to fix
- If an upload fails partway, this should be made clear so that the user can attempt a reupload
- The backend should return a meaningful status message reflecting the file processing state
```{mermaid}
sequenceDiagram
Client->>+API: POST /api/v2/upload-groups
API-->>Client: 201 GUID
loop For each file
Client->>API: POST /api/v2/upload-groups/{guid}/uploads
API-->>Client: 200 Creation message
end
```
#### Response structure (V2 only)
Clients may also send a file to the `/api/v2/uploads` endpoint. This endpoint accepts **only** a file with no additional metadata nor target.
### Upload group
Authenticated users may create upload groups by sending a POST request to the `/api/v2/upload-groups` endpoint with no request body.
```console
$ curl -X POST "/api/v2/upload-groups" \
-H "accept: application/json"
```
The user may optionally send a group `name` in the body of the request to give the release group a meaningful name.
```console
$ curl -X POST "/api/v2/upload-groups" \
-H "Content-type: application/json" \
-d '{"name": "My cool group"}'
```
:::{note}
If no `name` is present, the server should use the timestamp of the request as the upload group's `name`.
:::
The API should respond with the following information:
- A `201: Created` response
- The `name` of the newly created upload group
- The `guid` of the newly created upload group
- The `uploadUrl` where clients can send new uploads or query the uploads in the group
```json
{
"name": "My cool group",
"guid": "18c697b6-f0b0-4000-84cd-30e3e4b1a201",
"uploadUrl": "/api/v2/upload-groups/18c697b6-f0b0-4000-84cd-30e3e4b1a201/uploads"
}
```
Clients should also be able to send a PATCH request to alter the `name` of an upload group:
```console
$ curl -X PATCH "/api/v2/upload-groups/18c697b6-f0b0-4000-84cd-30e3e4b1a201"" \
-H "Content-type: application/json" \
-d '{"name": "My cool group"}'
```
The server should respond with a `200: OK` response to reflect that the request updated the resource.
```json
{
"name": "My cool group",
"guid": "18c697b6-f0b0-4000-84cd-30e3e4b1a201",
"uploadUrl": "/api/v2/upload-groups/18c697b6-f0b0-4000-84cd-30e3e4b1a201/uploads"
}
```
#### File upload
Once the server creates the upload group, the client can send files to the `/api/v2/upload-groups/{guid}/uploads` endpoint to add new uploads to the group.
This endpoint must support 2 methods controlled by the `Content-Type` header:
1. A single file as an `octet-stream`
- If the client sends an audio file as an `octet-stream`, the server is responsible for parsing the file metadata and managing the import
2. A `multipart/form-data` submission including metadata, the audio file, and an optional cover. This method enables the client to set parse and set metadata information independent of the server
If the client sends a `multipart/form-data` submission, the payload must contain:
- A `metadata` object containing **at least**
- The `title` of the uploaded file
- The `artist.name` of the artist
- An optional `target` object containing _any of the following_:
- An array of collections
- An array of channels
- A library
- The audio file
:::{important}
If the client doesn't specify a `target`, the server must implicitly add the upload to the built-in `Uploads` collection.
:::
```console
$ curl -X 'POST' \
'/api/v2/upload-groups/18c697b6-f0b0-4000-84cd-30e3e4b1a201/uploads' \
-H 'accept: application/json' \
-H 'Content-Type: multipart/form-data' \
-F 'metadata={"title": "Juggernaut", \
"mbid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", \
"tags": ["Rock"], \
"position": 1, \
"entryNumber": 1, \
"license": "string", \
"release": {"title": "Juggernaut", "artist": "Autoheart", "date": "2023-12-14", "mbid": "3fa85f64-5717-4562-b3fc-2c963f66afa6"}, \
"artist": {"name": "Autoheart","mbid": "3fa85f64-5717-4562-b3fc-2c963f66afa6"} \
}' \
-F 'target={"collections": ["18cda279-b570-4000-800d-580fc7ecb401"]}' \
-F 'audioFile=@Autoheart - Juggernaut.opus;type=audio/opus' \
-F 'cover=@cover.png;type=image/png'
```
### File upload
If the client doesn't have the capability to send a multipart request or the user prefers to simply send plain files, the file can be uploaded by sending a POST request to the `/api/v2/uploads` endpoint.
On receipt of a file, the server should respond with a `202: Accepted` response to inform the client that the server has received the file but not yet processed it. The server must then:
- Create a **daily** upload group, if none exists for the current 24 hour period
- Add the upload to the daily upload group. All uploads sent through the `/api/v2/uploads` endpoint must be assigned to the daily upload group so that users can check the upload's status in the web app
- Add the built-in **Uploads** collection as the upload's `target`
```console
$ curl -X 'POST'\
'/api/v2/uploads' \
-H 'Content-Type: application/octet-stream'
-F 'audioFile=@Autoheart - Juggernaut.opus;type=audio/opus'
```
```json
{
"guid": "18c697b6-f0b0-4000-84cd-30e3e4b1a201"
}
```
The client should then be able to query the status of a specific upload by sending a GET request to the `/api/v2/uploads/{guid}` endpoint. This endpoint must contain the information listed below.
### Response structure
If the upload succeeds, the API should respond with a `200: Success` message and return a payload containing the following information:
- The upload `guid`
- The `title` of the uploaded file
- The `createdDate` of the upload
- The `fileType` of the upload
- The `mimeType` of the upload
- The associated `recording`
- The associated `release`
- The `owner` (actor) of the upload
- The `collections` and/or `channels` the upload is contained in
```json
{
"guid": "18c455d8-9840-4000-804d-c53e92d85d01",
"guid": "18cda279-b5a0-4000-89fc-811321642380",
"title": "string",
"createdDate": "1970-01-01T00:00:00.000Z",
"fileType": "flac",
"mimeType": "flac",
"uploadGroup": "18cda279-b5a0-4000-8f5b-fa6702365101",
"status": "Succeeded",
"collections": ["18cda279-b5a0-4000-8d96-e9c3c9045c01"],
"recording": {
"guid": "18c455d8-9840-4000-82af-67024a9e2018",
"guid": "18cda279-b5a0-4000-889e-8f6a6a54f401",
"fid": "http://example.com",
"name": "string",
"playable": false,
@ -127,13 +266,13 @@ If the upload succeeds, the API should respond with a `200: Success` message and
"artistCredit": [
{
"name": "string",
"guid": "18c455d8-9840-4000-8271-2731b97a2c01",
"mbid": "18c455d8-9840-4000-8f04-1f9dd7f16201",
"guid": "18cda279-b5a0-4000-88b1-d3f39c359101",
"mbid": "18cda279-b5a0-4000-89ff-4e4993cadd01",
"joinPhrase": "string"
}
],
"cover": {
"guid": "18c455d8-9840-4000-85af-4178e969db01",
"guid": "18cda279-b5a0-4000-83c2-afe780820380",
"mimetype": "string",
"size": 0,
"creationDate": "1970-01-01T00:00:00.000Z",
@ -144,21 +283,21 @@ If the upload succeeds, the API should respond with a `200: Success` message and
}
},
"release": {
"guid": "18c455d8-9840-4000-81e9-3cc3a7567201",
"guid": "18cda279-b5a0-4000-8e2c-622921d05d01",
"fid": "http://example.com",
"mbid": "18c455d8-9840-4000-8b86-dd1e40a7bb80",
"mbid": "18cda279-b5a0-4000-868d-ee9479980d80",
"name": "string",
"artistCredit": [
{
"name": "string",
"guid": "18c455d8-9840-4000-88dc-fd4cf3957201",
"mbid": "18c455d8-9840-4000-8e40-35019dd11180",
"guid": "18cda279-b5a0-4000-8492-68d322f50701",
"mbid": "18cda279-b5a0-4000-8bc1-f4454eecf980",
"joinPhrase": "string"
}
],
"playable": false,
"cover": {
"guid": "18c455d8-9840-4000-831e-ea8add02c380",
"guid": "18cda279-b5a0-4000-85d3-596516082580",
"mimetype": "string",
"size": 0,
"creationDate": "1970-01-01T00:00:00.000Z",