diff --git a/server/api/api.go b/server/api/api.go index 554119144..e6260c4dc 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -82,12 +82,12 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/login", a.handleLogin).Methods("POST") apiv1.HandleFunc("/register", a.handleRegister).Methods("POST") - apiv1.HandleFunc("/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") + apiv1.HandleFunc("/workspaces/{workspaceID}/{rootID}/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") // Get Files API files := r.PathPrefix("/files").Subrouter() - files.HandleFunc("/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") + files.HandleFunc("/workspaces/{workspaceID}/{rootID}/{filename}", a.attachSession(a.handleServeFile, false)).Methods("GET") } func (a *API) RegisterAdminRoutes(r *mux.Router) { @@ -116,15 +116,47 @@ func (a *API) checkCSRFToken(r *http.Request) bool { return false } +func (a *API) hasValidReadTokenForBlock(r *http.Request, container store.Container, blockID string) bool { + query := r.URL.Query() + readToken := query.Get("read_token") + + if len(readToken) < 1 { + return false + } + + isValid, err := a.app().IsValidReadToken(container, blockID, readToken) + if err != nil { + log.Printf("IsValidReadToken ERROR: %v", err) + return false + } + + return isValid +} + func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID string) (*store.Container, error) { + ctx := r.Context() + session, _ := ctx.Value("session").(*model.Session) + if a.WorkspaceAuthenticator == nil { // Native auth: always use root workspace container := store.Container{ WorkspaceID: "", } - return &container, nil + + // Has session + if session != nil { + return &container, nil + } + + // No session, but has valid read token (read-only mode) + if len(blockID) > 0 && a.hasValidReadTokenForBlock(r, container, blockID) { + return &container, nil + } + + return nil, errors.New("Access denied to workspace") } + // Workspace auth vars := mux.Vars(r) workspaceID := vars["workspaceID"] @@ -137,34 +169,17 @@ func (a *API) getContainerAllowingReadTokenForBlock(r *http.Request, blockID str WorkspaceID: workspaceID, } - ctx := r.Context() - session, _ := ctx.Value("session").(*model.Session) - if session == nil && len(blockID) > 0 { - // No session, check for read_token - query := r.URL.Query() - readToken := query.Get("read_token") - - // Require read token - if len(readToken) < 1 { - return nil, errors.New("Access denied to workspace") - } - - isValid, err := a.app().IsValidReadToken(container, blockID, readToken) - if err != nil { - log.Printf("IsValidReadToken ERROR: %v", err) - return nil, errors.New("Access denied to workspace") - } - - if !isValid { - return nil, errors.New("Access denied to workspace") - } - } else { - if !a.WorkspaceAuthenticator.DoesUserHaveWorkspaceAccess(session, workspaceID) { - return nil, errors.New("Access denied to workspace") - } + // Has session and access to workspace + if session != nil && a.WorkspaceAuthenticator.DoesUserHaveWorkspaceAccess(session, container.WorkspaceID) { + return &container, nil } - return &container, nil + // No session, but has valid read token (read-only mode) + if len(blockID) > 0 && a.hasValidReadTokenForBlock(r, container, blockID) { + return &container, nil + } + + return nil, errors.New("Access denied to workspace") } func (a *API) getContainer(r *http.Request) (*store.Container, error) { @@ -956,7 +971,7 @@ func (a *API) handlePostWorkspaceRegenerateSignupToken(w http.ResponseWriter, r // File upload func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /files/{fileID} getFile + // swagger:operation GET /workspaces/{workspaceID}/{rootID}/{fileID} getFile // // Returns the contents of an uploaded file // @@ -966,6 +981,16 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { // - image/jpg // - image/png // parameters: + // - name: workspaceID + // in: path + // description: Workspace ID + // required: true + // type: string + // - name: rootID + // in: path + // description: ID of the root block + // required: true + // type: string // - name: fileID // in: path // description: ID of the file @@ -982,8 +1007,17 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { // "$ref": "#/definitions/ErrorResponse" vars := mux.Vars(r) + workspaceID := vars["workspaceID"] + rootID := vars["rootID"] filename := vars["filename"] + // Caller must have access to the root block's container + _, err := a.getContainerAllowingReadTokenForBlock(r, rootID) + if err != nil { + noContainerErrorResponse(w, err) + return + } + contentType := "image/jpg" fileExtension := strings.ToLower(filepath.Ext(filename)) @@ -993,7 +1027,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", contentType) - filePath := a.app().GetFilePath(filename) + filePath := a.app().GetFilePath(workspaceID, rootID, filename) http.ServeFile(w, r, filePath) } @@ -1006,9 +1040,9 @@ type FileUploadResponse struct { } func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /api/v1/files uploadFile + // swagger:operation POST /api/v1/workspaces/{workspaceID}/{rootID}/files uploadFile // - // Upload a binary file + // Upload a binary file, attached to a root block // // --- // consumes: @@ -1016,6 +1050,16 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { // produces: // - application/json // parameters: + // - name: workspaceID + // in: path + // description: Workspace ID + // required: true + // type: string + // - name: rootID + // in: path + // description: ID of the root block + // required: true + // type: string // - name: uploaded file // in: formData // type: file @@ -1032,6 +1076,17 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { // schema: // "$ref": "#/definitions/ErrorResponse" + vars := mux.Vars(r) + workspaceID := vars["workspaceID"] + rootID := vars["rootID"] + + // Caller must have access to the root block's container + _, err := a.getContainerAllowingReadTokenForBlock(r, rootID) + if err != nil { + noContainerErrorResponse(w, err) + return + } + file, handle, err := r.FormFile("file") if err != nil { fmt.Fprintf(w, "%v", err) @@ -1040,7 +1095,7 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { } defer file.Close() - fileId, err := a.app().SaveFile(file, handle.Filename) + fileId, err := a.app().SaveFile(file, workspaceID, rootID, handle.Filename) if err != nil { errorResponse(w, http.StatusInternalServerError, "", err) return diff --git a/server/app/files.go b/server/app/files.go index 177927296..6ad756158 100644 --- a/server/app/files.go +++ b/server/app/files.go @@ -10,7 +10,7 @@ import ( "github.com/mattermost/focalboard/server/utils" ) -func (a *App) SaveFile(reader io.Reader, filename string) (string, error) { +func (a *App) SaveFile(reader io.Reader, workspaceID, rootID, filename string) (string, error) { // NOTE: File extension includes the dot fileExtension := strings.ToLower(filepath.Ext(filename)) if fileExtension == ".jpeg" { @@ -18,8 +18,9 @@ func (a *App) SaveFile(reader io.Reader, filename string) (string, error) { } createdFilename := fmt.Sprintf(`%s%s`, utils.CreateGUID(), fileExtension) + filePath := filepath.Join(workspaceID, rootID, createdFilename) - _, appErr := a.filesBackend.WriteFile(reader, createdFilename) + _, appErr := a.filesBackend.WriteFile(reader, filePath) if appErr != nil { return "", errors.New("unable to store the file in the files storage") } @@ -27,8 +28,9 @@ func (a *App) SaveFile(reader io.Reader, filename string) (string, error) { return createdFilename, nil } -func (a *App) GetFilePath(filename string) string { +func (a *App) GetFilePath(workspaceID, rootID, filename string) string { folderPath := a.config.FilesPath + rootPath := filepath.Join(folderPath, workspaceID, rootID) - return filepath.Join(folderPath, filename) + return filepath.Join(rootPath, filename) } diff --git a/server/swagger/docs/html/index.html b/server/swagger/docs/html/index.html index e011f976b..7acf0abd3 100644 --- a/server/swagger/docs/html/index.html +++ b/server/swagger/docs/html/index.html @@ -3160,7 +3160,7 @@ Type of blocks to return, omit to specify all types
Returns the contents of an uploaded file
/files/{fileID}
+ /workspaces/{workspaceID}/{rootID}/{fileID}
curl -X GET\
-H "Authorization: [[apiKey]]"\
-H "Accept: application/json,image/jpg,image/png"\
- "http://localhost/api/v1/files/{fileID}"
+ "http://localhost/api/v1/workspaces/{workspaceID}/{rootID}/{fileID}"
import org.openapitools.client.*;
@@ -3207,10 +3207,12 @@ public class DefaultApiExample {
// Create an instance of the API class
DefaultApi apiInstance = new DefaultApi();
+ String workspaceID = workspaceID_example; // String | Workspace ID
+ String rootID = rootID_example; // String | ID of the root block
String fileID = fileID_example; // String | ID of the file
try {
- apiInstance.getFile(fileID);
+ apiInstance.getFile(workspaceID, rootID, fileID);
} catch (ApiException e) {
System.err.println("Exception when calling DefaultApi#getFile");
e.printStackTrace();
@@ -3226,10 +3228,12 @@ public class DefaultApiExample {
public class DefaultApiExample {
public static void main(String[] args) {
DefaultApi apiInstance = new DefaultApi();
+ String workspaceID = workspaceID_example; // String | Workspace ID
+ String rootID = rootID_example; // String | ID of the root block
String fileID = fileID_example; // String | ID of the file
try {
- apiInstance.getFile(fileID);
+ apiInstance.getFile(workspaceID, rootID, fileID);
} catch (ApiException e) {
System.err.println("Exception when calling DefaultApi#getFile");
e.printStackTrace();
@@ -3252,9 +3256,13 @@ public class DefaultApiExample {
// Create an instance of the API class
DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workspaceID = workspaceID_example; // Workspace ID (default to null)
+String *rootID = rootID_example; // ID of the root block (default to null)
String *fileID = fileID_example; // ID of the file (default to null)
-[apiInstance getFileWith:fileID
+[apiInstance getFileWith:workspaceID
+ rootID:rootID
+ fileID:fileID
completionHandler: ^(NSError* error) {
if (error) {
NSLog(@"Error: %@", error);
@@ -3275,6 +3283,8 @@ BearerAuth.apiKey = "YOUR API KEY";
// Create an instance of the API class
var api = new FocalboardServer.DefaultApi()
+var workspaceID = workspaceID_example; // {String} Workspace ID
+var rootID = rootID_example; // {String} ID of the root block
var fileID = fileID_example; // {String} ID of the file
var callback = function(error, data, response) {
@@ -3284,7 +3294,7 @@ var callback = function(error, data, response) {
console.log('API called successfully.');
}
};
-api.getFile(fileID, callback);
+api.getFile(workspaceID, rootID, fileID, callback);
extern crate DefaultApi;
pub fn main() {
+ let workspaceID = workspaceID_example; // String
+ let rootID = rootID_example; // String
let fileID = fileID_example; // String
let mut context = DefaultApi::Context::default();
- let result = client.getFile(fileID, &context).wait();
+ let result = client.getFile(workspaceID, rootID, fileID, &context).wait();
println!("{:?}", result);
}
@@ -3417,6 +3437,52 @@ pub fn main() {
Name
Description
+ workspaceID*
+
+
+
+
+
+
+
+ String
+
+
+
+Workspace ID
+
+
+
+ Required
+
+
+
+
+
+
+ rootID*
+
+
+
+
+
+
+
+ String
+
+
+
+ID of the root block
+
+
+
+ Required
+
+
+
+
+
+
fileID*
@@ -8490,10 +8556,10 @@ $(document).ready(function() {
- Upload a binary file
+ Upload a binary file, attached to a root block
- /api/v1/files
+ /api/v1/workspaces/{workspaceID}/{rootID}/files
Usage and SDK Samples
@@ -8518,7 +8584,7 @@ $(document).ready(function() {
-H "Authorization: [[apiKey]]"\
-H "Accept: application/json"\
-H "Content-Type: multipart/form-data"\
- "http://localhost/api/v1/api/v1/files"
+ "http://localhost/api/v1/api/v1/workspaces/{workspaceID}/{rootID}/files"
import org.openapitools.client.*;
@@ -8541,10 +8607,12 @@ public class DefaultApiExample {
// Create an instance of the API class
DefaultApi apiInstance = new DefaultApi();
+ String workspaceID = workspaceID_example; // String | Workspace ID
+ String rootID = rootID_example; // String | ID of the root block
File uploaded file = BINARY_DATA_HERE; // File | The file to upload
try {
- FileUploadResponse result = apiInstance.uploadFile(uploaded file);
+ FileUploadResponse result = apiInstance.uploadFile(workspaceID, rootID, uploaded file);
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling DefaultApi#uploadFile");
@@ -8561,10 +8629,12 @@ public class DefaultApiExample {
public class DefaultApiExample {
public static void main(String[] args) {
DefaultApi apiInstance = new DefaultApi();
+ String workspaceID = workspaceID_example; // String | Workspace ID
+ String rootID = rootID_example; // String | ID of the root block
File uploaded file = BINARY_DATA_HERE; // File | The file to upload
try {
- FileUploadResponse result = apiInstance.uploadFile(uploaded file);
+ FileUploadResponse result = apiInstance.uploadFile(workspaceID, rootID, uploaded file);
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling DefaultApi#uploadFile");
@@ -8588,9 +8658,13 @@ public class DefaultApiExample {
// Create an instance of the API class
DefaultApi *apiInstance = [[DefaultApi alloc] init];
+String *workspaceID = workspaceID_example; // Workspace ID (default to null)
+String *rootID = rootID_example; // ID of the root block (default to null)
File *uploaded file = BINARY_DATA_HERE; // The file to upload (optional) (default to null)
-[apiInstance uploadFileWith:uploaded file
+[apiInstance uploadFileWith:workspaceID
+ rootID:rootID
+ uploaded file:uploaded file
completionHandler: ^(FileUploadResponse output, NSError* error) {
if (output) {
NSLog(@"%@", output);
@@ -8614,6 +8688,8 @@ BearerAuth.apiKey = "YOUR API KEY";
// Create an instance of the API class
var api = new FocalboardServer.DefaultApi()
+var workspaceID = workspaceID_example; // {String} Workspace ID
+var rootID = rootID_example; // {String} ID of the root block
var opts = {
'uploaded file': BINARY_DATA_HERE // {File} The file to upload
};
@@ -8625,7 +8701,7 @@ var callback = function(error, data, response) {
console.log('API called successfully. Returned data: ' + data);
}
};
-api.uploadFile(opts, callback);
+api.uploadFile(workspaceID, rootID, opts, callback);
@@ -8652,10 +8728,12 @@ namespace Example
// Create an instance of the API class
var apiInstance = new DefaultApi();
+ var workspaceID = workspaceID_example; // String | Workspace ID (default to null)
+ var rootID = rootID_example; // String | ID of the root block (default to null)
var uploaded file = BINARY_DATA_HERE; // File | The file to upload (optional) (default to null)
try {
- FileUploadResponse result = apiInstance.uploadFile(uploaded file);
+ FileUploadResponse result = apiInstance.uploadFile(workspaceID, rootID, uploaded file);
Debug.WriteLine(result);
} catch (Exception e) {
Debug.Print("Exception when calling DefaultApi.uploadFile: " + e.Message );
@@ -8677,10 +8755,12 @@ OpenAPITools\Client\Configuration::getDefaultConfiguration()->setApiKey('Authori
// Create an instance of the API class
$api_instance = new OpenAPITools\Client\Api\DefaultApi();
+$workspaceID = workspaceID_example; // String | Workspace ID
+$rootID = rootID_example; // String | ID of the root block
$uploaded file = BINARY_DATA_HERE; // File | The file to upload
try {
- $result = $api_instance->uploadFile($uploaded file);
+ $result = $api_instance->uploadFile($workspaceID, $rootID, $uploaded file);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling DefaultApi->uploadFile: ', $e->getMessage(), PHP_EOL;
@@ -8700,10 +8780,12 @@ $WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';
# Create an instance of the API class
my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
+my $workspaceID = workspaceID_example; # String | Workspace ID
+my $rootID = rootID_example; # String | ID of the root block
my $uploaded file = BINARY_DATA_HERE; # File | The file to upload
eval {
- my $result = $api_instance->uploadFile(uploaded file => $uploaded file);
+ my $result = $api_instance->uploadFile(workspaceID => $workspaceID, rootID => $rootID, uploaded file => $uploaded file);
print Dumper($result);
};
if ($@) {
@@ -8725,10 +8807,12 @@ openapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'
# Create an instance of the API class
api_instance = openapi_client.DefaultApi()
+workspaceID = workspaceID_example # String | Workspace ID (default to null)
+rootID = rootID_example # String | ID of the root block (default to null)
uploaded file = BINARY_DATA_HERE # File | The file to upload (optional) (default to null)
try:
- api_response = api_instance.upload_file(uploaded file=uploaded file)
+ api_response = api_instance.upload_file(workspaceID, rootID, uploaded file=uploaded file)
pprint(api_response)
except ApiException as e:
print("Exception when calling DefaultApi->uploadFile: %s\n" % e)
@@ -8738,10 +8822,12 @@ except ApiException as e:
extern crate DefaultApi;
pub fn main() {
+ let workspaceID = workspaceID_example; // String
+ let rootID = rootID_example; // String
let uploaded file = BINARY_DATA_HERE; // File
let mut context = DefaultApi::Context::default();
- let result = client.uploadFile(uploaded file, &context).wait();
+ let result = client.uploadFile(workspaceID, rootID, uploaded file, &context).wait();
println!("{:?}", result);
}
@@ -8756,6 +8842,59 @@ pub fn main() {
Parameters
+ Path parameters
+
+
+ Name
+ Description
+
+ workspaceID*
+
+
+
+
+
+
+
+ String
+
+
+
+Workspace ID
+
+
+
+ Required
+
+
+
+
+
+
+ rootID*
+
+
+
+
+
+
+
+ String
+
+
+
+ID of the root block
+
+
+
+ Required
+
+
+
+
+
+
+
diff --git a/server/swagger/swagger.yml b/server/swagger/swagger.yml
index a2d38e77f..209dee468 100644
--- a/server/swagger/swagger.yml
+++ b/server/swagger/swagger.yml
@@ -291,30 +291,6 @@ info:
title: Focalboard Server
version: 1.0.0
paths:
- /api/v1/files:
- post:
- consumes:
- - multipart/form-data
- description: Upload a binary file
- operationId: uploadFile
- parameters:
- - description: The file to upload
- in: formData
- name: uploaded file
- type: file
- produces:
- - application/json
- responses:
- "200":
- description: success
- schema:
- $ref: '#/definitions/FileUploadResponse'
- default:
- description: internal error
- schema:
- $ref: '#/definitions/ErrorResponse'
- security:
- - BearerAuth: []
/api/v1/login:
post:
description: Login user
@@ -457,6 +433,40 @@ paths:
$ref: '#/definitions/ErrorResponse'
security:
- BearerAuth: []
+ /api/v1/workspaces/{workspaceID}/{rootID}/files:
+ post:
+ consumes:
+ - multipart/form-data
+ description: Upload a binary file, attached to a root block
+ operationId: uploadFile
+ parameters:
+ - description: Workspace ID
+ in: path
+ name: workspaceID
+ required: true
+ type: string
+ - description: ID of the root block
+ in: path
+ name: rootID
+ required: true
+ type: string
+ - description: The file to upload
+ in: formData
+ name: uploaded file
+ type: file
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: success
+ schema:
+ $ref: '#/definitions/FileUploadResponse'
+ default:
+ description: internal error
+ schema:
+ $ref: '#/definitions/ErrorResponse'
+ security:
+ - BearerAuth: []
/api/v1/workspaces/{workspaceID}/blocks:
get:
description: Returns blocks
@@ -714,11 +724,21 @@ paths:
$ref: '#/definitions/ErrorResponse'
security:
- BearerAuth: []
- /files/{fileID}:
+ /workspaces/{workspaceID}/{rootID}/{fileID}:
get:
description: Returns the contents of an uploaded file
operationId: getFile
parameters:
+ - description: Workspace ID
+ in: path
+ name: workspaceID
+ required: true
+ type: string
+ - description: ID of the root block
+ in: path
+ name: rootID
+ required: true
+ type: string
- description: ID of the file
in: path
name: fileID
diff --git a/webapp/src/components/addContentMenuItem.tsx b/webapp/src/components/addContentMenuItem.tsx
index 1434e2f02..1a070994a 100644
--- a/webapp/src/components/addContentMenuItem.tsx
+++ b/webapp/src/components/addContentMenuItem.tsx
@@ -38,7 +38,7 @@ const AddContentMenuItem = React.memo((props:Props): JSX.Element => {
name={handler.getDisplayText(intl)}
icon={handler.getIcon()}
onClick={async () => {
- const newBlock = await handler.createBlock()
+ const newBlock = await handler.createBlock(card.rootId)
newBlock.parentId = card.id
newBlock.rootId = card.rootId
diff --git a/webapp/src/components/cardDetail/cardDetailContentsMenu.tsx b/webapp/src/components/cardDetail/cardDetailContentsMenu.tsx
index 0509cc6eb..9c471c1c0 100644
--- a/webapp/src/components/cardDetail/cardDetailContentsMenu.tsx
+++ b/webapp/src/components/cardDetail/cardDetailContentsMenu.tsx
@@ -34,7 +34,7 @@ function addContentMenu(card: Card, intl: IntlShape, type: BlockTypes): JSX.Elem
}
async function addBlock(card: Card, intl: IntlShape, handler: ContentHandler) {
- const newBlock = await handler.createBlock()
+ const newBlock = await handler.createBlock(card.rootId)
newBlock.parentId = card.id
newBlock.rootId = card.rootId
diff --git a/webapp/src/components/content/contentRegistry.tsx b/webapp/src/components/content/contentRegistry.tsx
index 834b34536..6b54ad7d2 100644
--- a/webapp/src/components/content/contentRegistry.tsx
+++ b/webapp/src/components/content/contentRegistry.tsx
@@ -11,7 +11,7 @@ type ContentHandler = {
type: BlockTypes,
getDisplayText: (intl: IntlShape) => string,
getIcon: () => JSX.Element,
- createBlock: () => Promise,
+ createBlock: (rootId: string) => Promise,
createComponent: (block: IContentBlock, intl: IntlShape, readonly: boolean) => JSX.Element,
}
diff --git a/webapp/src/components/content/imageElement.tsx b/webapp/src/components/content/imageElement.tsx
index f3804d6bd..9251bb6a2 100644
--- a/webapp/src/components/content/imageElement.tsx
+++ b/webapp/src/components/content/imageElement.tsx
@@ -17,18 +17,18 @@ type Props = {
const ImageElement = React.memo((props: Props): JSX.Element|null => {
const [imageDataUrl, setImageDataUrl] = useState(null)
+ const {block} = props
+
useEffect(() => {
if (!imageDataUrl) {
const loadImage = async () => {
- const url = await octoClient.getFileAsDataUrl(props.block.fields.fileId)
+ const url = await octoClient.getFileAsDataUrl(block.rootId, props.block.fields.fileId)
setImageDataUrl(url)
}
loadImage()
}
})
- const {block} = props
-
if (!imageDataUrl) {
return null
}
@@ -45,11 +45,11 @@ contentRegistry.registerContentType({
type: 'image',
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}),
getIcon: () => ,
- createBlock: async () => {
+ createBlock: async (rootId: string) => {
return new Promise(
(resolve) => {
Utils.selectLocalFile(async (file) => {
- const fileId = await octoClient.uploadFile(file)
+ const fileId = await octoClient.uploadFile(rootId, file)
const block = new MutableImageBlock()
block.fileId = fileId || ''
diff --git a/webapp/src/mutator.ts b/webapp/src/mutator.ts
index 34b09feb2..d18ed6f01 100644
--- a/webapp/src/mutator.ts
+++ b/webapp/src/mutator.ts
@@ -6,7 +6,6 @@ import {Board, IPropertyOption, IPropertyTemplate, MutableBoard, PropertyType} f
import {BoardView, ISortOption, MutableBoardView} from './blocks/boardView'
import {Card, MutableCard} from './blocks/card'
import {FilterGroup} from './blocks/filterGroup'
-import {MutableImageBlock} from './blocks/imageBlock'
import octoClient from './octoClient'
import {OctoUtils} from './octoUtils'
import undoManager from './undomanager'
@@ -592,31 +591,6 @@ class Mutator {
return octoClient.importFullArchive(blocks)
}
- async createImageBlock(parent: IBlock, file: File, description = 'add image'): Promise {
- const fileId = await octoClient.uploadFile(file)
- if (!fileId) {
- return undefined
- }
-
- const block = new MutableImageBlock()
- block.parentId = parent.id
- block.rootId = parent.rootId
- block.fileId = fileId
-
- await undoManager.perform(
- async () => {
- await octoClient.insertBlock(block)
- },
- async () => {
- await octoClient.deleteBlock(block.id)
- },
- description,
- this.undoGroupId,
- )
-
- return block
- }
-
get canUndo(): boolean {
return undoManager.canUndo
}
diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts
index 957133d7e..e89c9dbd7 100644
--- a/webapp/src/octoClient.ts
+++ b/webapp/src/octoClient.ts
@@ -17,7 +17,8 @@ class OctoClient {
set token(value: string) {
localStorage.setItem('sessionId', value)
}
- get readToken(): string {
+
+ private readToken(): string {
const queryString = new URLSearchParams(window.location.search)
const readToken = queryString.get('r') || ''
return readToken
@@ -123,8 +124,9 @@ class OctoClient {
async getSubtree(rootId?: string, levels = 2): Promise {
let path = this.workspacePath() + `/blocks/${encodeURIComponent(rootId || '')}/subtree?l=${levels}`
- if (this.readToken) {
- path += `&read_token=${this.readToken}`
+ const readToken = this.readToken()
+ if (readToken) {
+ path += `&read_token=${readToken}`
}
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
@@ -308,7 +310,7 @@ class OctoClient {
// Files
// Returns fileId of uploaded file, or undefined on failure
- async uploadFile(file: File): Promise {
+ async uploadFile(rootID: string, file: File): Promise {
// IMPORTANT: We need to post the image as a form. The browser will convert this to a application/x-www-form-urlencoded POST
const formData = new FormData()
formData.append('file', file)
@@ -319,7 +321,7 @@ class OctoClient {
// TIPTIP: Leave out Content-Type here, it will be automatically set by the browser
delete headers['Content-Type']
- const response = await fetch(this.serverUrl + '/api/v1/files', {
+ const response = await fetch(this.serverUrl + this.workspacePath() + '/' + rootID + '/files', {
method: 'POST',
headers,
body: formData,
@@ -345,8 +347,12 @@ class OctoClient {
return undefined
}
- async getFileAsDataUrl(fileId: string): Promise {
- const path = '/files/' + fileId
+ async getFileAsDataUrl(rootId: string, fileId: string): Promise {
+ let path = '/files/workspaces/' + this.workspaceId + '/' + rootId + '/' + fileId
+ const readToken = this.readToken()
+ if (readToken) {
+ path += `?read_token=${readToken}`
+ }
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
if (response.status !== 200) {
return ''