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}

Usage and SDK Samples

@@ -3184,7 +3184,7 @@ Type of blocks to return, omit to specify all types
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);
 
@@ -3311,10 +3321,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 fileID = fileID_example; // String | ID of the file (default to null) try { - apiInstance.getFile(fileID); + apiInstance.getFile(workspaceID, rootID, fileID); } catch (Exception e) { Debug.Print("Exception when calling DefaultApi.getFile: " + e.Message ); } @@ -3335,10 +3347,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 $fileID = fileID_example; // String | ID of the file try { - $api_instance->getFile($fileID); + $api_instance->getFile($workspaceID, $rootID, $fileID); } catch (Exception $e) { echo 'Exception when calling DefaultApi->getFile: ', $e->getMessage(), PHP_EOL; } @@ -3357,10 +3371,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 $fileID = fileID_example; # String | ID of the file eval { - $api_instance->getFile(fileID => $fileID); + $api_instance->getFile(workspaceID => $workspaceID, rootID => $rootID, fileID => $fileID); }; if ($@) { warn "Exception when calling DefaultApi->getFile: $@\n"; @@ -3381,10 +3397,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) fileID = fileID_example # String | ID of the file (default to null) try: - api_instance.get_file(fileID) + api_instance.get_file(workspaceID, rootID, fileID) except ApiException as e: print("Exception when calling DefaultApi->getFile: %s\n" % e) @@ -3393,10 +3411,12 @@ except ApiException as e:
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
+ + + + + + + + + + + + + +
NameDescription
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 ''