mirror of https://github.com/halo-dev/halo
				
				
				
			feat: API to save external links as attachments (#6364)
#### What type of PR is this? /kind api-change /kind feature /area core #### What this PR does / why we need it: see #2335 增加将第三方资源转存为附件资源的接口。 `/apis/api.console.halo.run/v1alpha1/attachments/-/upload-from-url` UC: `/apis/uc.api.content.halo.run/v1alpha1/attachments/-/upload-from-url` 其中参数为 ```json { "url": "string", "filename": "string", "groupName": "string", "policyName": "string" } ``` #### How to test it? 测试能否将第三方接口的资源保存至附件中。 测试各类附件,例如图片、视频、文本等。 #### Does this PR introduce a user-facing change? ```release-note 增加通过链接转存第三方资源至附件库的接口 ```pull/6515/head
							parent
							
								
									21db06a507
								
							
						
					
					
						commit
						e5bbbb3b7b
					
				| 
						 | 
				
			
			@ -2250,6 +2250,36 @@
 | 
			
		|||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/api.console.halo.run/v1alpha1/attachments/-/upload-from-url": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "ExternalTransferAttachment",
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UploadFromUrlRequest"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "default": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "*/*": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/Attachment"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": "default response"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "AttachmentV1alpha1Console"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/api.console.halo.run/v1alpha1/attachments/upload": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "UploadAttachment",
 | 
			
		||||
| 
						 | 
				
			
			@ -14136,6 +14166,47 @@
 | 
			
		|||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/uc.api.content.halo.run/v1alpha1/attachments/-/upload-from-url": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "description": "Upload attachment from the given URL.",
 | 
			
		||||
        "operationId": "ExternalTransferAttachment_1",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "description": "Wait for permalink.",
 | 
			
		||||
            "in": "query",
 | 
			
		||||
            "name": "waitForPermalink",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "type": "boolean"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UploadFromUrlRequest"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "default": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "*/*": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/Attachment"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": "default response"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "AttachmentV1alpha1Uc"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/uc.api.content.halo.run/v1alpha1/posts": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "description": "List posts owned by the current user.",
 | 
			
		||||
| 
						 | 
				
			
			@ -22621,6 +22692,21 @@
 | 
			
		|||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "UploadFromUrlRequest": {
 | 
			
		||||
        "required": [
 | 
			
		||||
          "url"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "filename": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "url": {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "format": "url"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "User": {
 | 
			
		||||
        "required": [
 | 
			
		||||
          "apiVersion",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,6 +117,36 @@
 | 
			
		|||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/api.console.halo.run/v1alpha1/attachments/-/upload-from-url": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "ExternalTransferAttachment",
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UploadFromUrlRequest"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "default": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "*/*": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/Attachment"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": "default response"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "AttachmentV1alpha1Console"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/api.console.halo.run/v1alpha1/attachments/upload": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "UploadAttachment",
 | 
			
		||||
| 
						 | 
				
			
			@ -6246,6 +6276,28 @@
 | 
			
		|||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "UploadFromUrlRequest": {
 | 
			
		||||
        "required": [
 | 
			
		||||
          "policyName",
 | 
			
		||||
          "url"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "filename": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "groupName": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "policyName": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "url": {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "format": "url"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "User": {
 | 
			
		||||
        "required": [
 | 
			
		||||
          "apiVersion",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,47 @@
 | 
			
		|||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/uc.api.content.halo.run/v1alpha1/attachments/-/upload-from-url": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "description": "Upload attachment from the given URL.",
 | 
			
		||||
        "operationId": "ExternalTransferAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "description": "Wait for permalink.",
 | 
			
		||||
            "in": "query",
 | 
			
		||||
            "name": "waitForPermalink",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "type": "boolean"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UploadFromUrlRequest"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "default": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "*/*": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/Attachment"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": "default response"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "AttachmentV1alpha1Uc"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/apis/uc.api.content.halo.run/v1alpha1/posts": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "description": "List posts owned by the current user.",
 | 
			
		||||
| 
						 | 
				
			
			@ -1943,6 +1984,21 @@
 | 
			
		|||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "UploadFromUrlRequest": {
 | 
			
		||||
        "required": [
 | 
			
		||||
          "url"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object",
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "filename": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "url": {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "format": "url"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "UserDevice": {
 | 
			
		||||
        "required": [
 | 
			
		||||
          "active",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package run.halo.app.core.extension.service;
 | 
			
		||||
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import org.springframework.core.io.buffer.DataBuffer;
 | 
			
		||||
| 
						 | 
				
			
			@ -91,4 +92,15 @@ public interface AttachmentService {
 | 
			
		|||
     */
 | 
			
		||||
    Mono<URI> getSharedURL(Attachment attachment, Duration ttl);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Transfer external links to attachments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param url external url
 | 
			
		||||
     * @param policyName policy name
 | 
			
		||||
     * @param groupName group name
 | 
			
		||||
     * @param filename filename
 | 
			
		||||
     * @return attachment
 | 
			
		||||
     */
 | 
			
		||||
    Mono<Attachment> uploadFromUrl(@NonNull URL url, @NonNull String policyName,
 | 
			
		||||
        String groupName, String filename);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,8 +22,10 @@ import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldS
 | 
			
		|||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.BooleanUtils;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +104,29 @@ public class AttachmentEndpoint implements CustomEndpoint {
 | 
			
		|||
                        ))
 | 
			
		||||
                    .response(responseBuilder().implementation(Attachment.class))
 | 
			
		||||
                    .build())
 | 
			
		||||
            .POST("/attachments/-/upload-from-url", contentType(MediaType.APPLICATION_JSON),
 | 
			
		||||
                request -> request.bodyToMono(UploadFromUrlRequest.class)
 | 
			
		||||
                    .flatMap(uploadFromUrlRequest -> {
 | 
			
		||||
                        var url = uploadFromUrlRequest.url();
 | 
			
		||||
                        var policyName = uploadFromUrlRequest.policyName();
 | 
			
		||||
                        var groupName = uploadFromUrlRequest.groupName();
 | 
			
		||||
                        var fileName = uploadFromUrlRequest.filename();
 | 
			
		||||
                        return attachmentService.uploadFromUrl(url, policyName,
 | 
			
		||||
                            groupName, fileName);
 | 
			
		||||
                    })
 | 
			
		||||
                    .flatMap(attachment -> ServerResponse.ok().bodyValue(attachment)),
 | 
			
		||||
                builder -> builder
 | 
			
		||||
                    .operationId("ExternalTransferAttachment")
 | 
			
		||||
                    .tag(tag)
 | 
			
		||||
                    .requestBody(requestBodyBuilder()
 | 
			
		||||
                        .required(true)
 | 
			
		||||
                        .content(contentBuilder()
 | 
			
		||||
                            .mediaType(MediaType.APPLICATION_JSON_VALUE)
 | 
			
		||||
                            .schema(schemaBuilder().implementation(UploadFromUrlRequest.class))
 | 
			
		||||
                        ))
 | 
			
		||||
                    .response(responseBuilder().implementation(Attachment.class))
 | 
			
		||||
                    .build()
 | 
			
		||||
            )
 | 
			
		||||
            .GET("/attachments", this::search,
 | 
			
		||||
                builder -> {
 | 
			
		||||
                    builder
 | 
			
		||||
| 
						 | 
				
			
			@ -275,6 +300,21 @@ public class AttachmentEndpoint implements CustomEndpoint {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record UploadFromUrlRequest(@Schema(requiredMode = REQUIRED) URL url,
 | 
			
		||||
                                       @Schema(requiredMode = REQUIRED) String policyName,
 | 
			
		||||
                                       String groupName,
 | 
			
		||||
                                       String filename) {
 | 
			
		||||
        public UploadFromUrlRequest {
 | 
			
		||||
            if (Objects.isNull(url)) {
 | 
			
		||||
                throw new ServerWebInputException("Required url is missing.");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!StringUtils.hasText(policyName)) {
 | 
			
		||||
                throw new ServerWebInputException("Policy name must not be blank");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Schema(types = "object")
 | 
			
		||||
    public interface IUploadRequest {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,14 @@
 | 
			
		|||
package run.halo.app.core.extension.service.impl;
 | 
			
		||||
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicReference;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import org.springframework.core.io.buffer.DataBuffer;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.codec.multipart.FilePart;
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +33,7 @@ import run.halo.app.core.extension.attachment.endpoint.UploadOption;
 | 
			
		|||
import run.halo.app.core.extension.service.AttachmentService;
 | 
			
		||||
import run.halo.app.extension.ConfigMap;
 | 
			
		||||
import run.halo.app.extension.ReactiveExtensionClient;
 | 
			
		||||
import run.halo.app.infra.ReactiveUrlDataBufferFetcher;
 | 
			
		||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
| 
						 | 
				
			
			@ -38,10 +43,14 @@ public class DefaultAttachmentService implements AttachmentService {
 | 
			
		|||
 | 
			
		||||
    private final ExtensionGetter extensionGetter;
 | 
			
		||||
 | 
			
		||||
    private final ReactiveUrlDataBufferFetcher dataBufferFetcher;
 | 
			
		||||
 | 
			
		||||
    public DefaultAttachmentService(ReactiveExtensionClient client,
 | 
			
		||||
        ExtensionGetter extensionGetter) {
 | 
			
		||||
        ExtensionGetter extensionGetter,
 | 
			
		||||
        ReactiveUrlDataBufferFetcher dataBufferFetcher) {
 | 
			
		||||
        this.client = client;
 | 
			
		||||
        this.extensionGetter = extensionGetter;
 | 
			
		||||
        this.dataBufferFetcher = dataBufferFetcher;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +139,45 @@ public class DefaultAttachmentService implements AttachmentService {
 | 
			
		|||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Mono<Attachment> uploadFromUrl(@NonNull URL url, @NonNull String policyName,
 | 
			
		||||
        String groupName, String filename) {
 | 
			
		||||
        var uri = URI.create(url.toString());
 | 
			
		||||
        AtomicReference<MediaType> mediaTypeRef = new AtomicReference<>();
 | 
			
		||||
        AtomicReference<String> fileNameRef = new AtomicReference<>(filename);
 | 
			
		||||
 | 
			
		||||
        Mono<Flux<DataBuffer>> contentMono = dataBufferFetcher.head(uri)
 | 
			
		||||
            .map(response -> {
 | 
			
		||||
                var httpHeaders = response.getHeaders();
 | 
			
		||||
                if (!StringUtils.hasText(fileNameRef.get())) {
 | 
			
		||||
                    fileNameRef.set(getExternalUrlFilename(uri, httpHeaders));
 | 
			
		||||
                }
 | 
			
		||||
                MediaType contentType = httpHeaders.getContentType();
 | 
			
		||||
                mediaTypeRef.set(contentType);
 | 
			
		||||
                return response;
 | 
			
		||||
            })
 | 
			
		||||
            .map(response -> dataBufferFetcher.fetch(uri));
 | 
			
		||||
 | 
			
		||||
        return contentMono.flatMap(
 | 
			
		||||
                (content) -> upload(policyName, groupName, fileNameRef.get(), content,
 | 
			
		||||
                    mediaTypeRef.get())
 | 
			
		||||
            )
 | 
			
		||||
            .onErrorResume(throwable -> Mono.error(
 | 
			
		||||
                new ServerWebInputException(
 | 
			
		||||
                    "Failed to transfer the attachment from the external URL."))
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String getExternalUrlFilename(URI externalUrl, HttpHeaders httpHeaders) {
 | 
			
		||||
        String fileName = httpHeaders.getContentDisposition().getFilename();
 | 
			
		||||
        if (!StringUtils.hasText(fileName)) {
 | 
			
		||||
            var path = externalUrl.getPath();
 | 
			
		||||
            fileName = Paths.get(path).getFileName().toString();
 | 
			
		||||
        }
 | 
			
		||||
        // TODO get file extension from media type
 | 
			
		||||
        return fileName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <T> Mono<T> authenticationConsumer(Function<Authentication, Mono<T>> func) {
 | 
			
		||||
        return ReactiveSecurityContextHolder.getContext()
 | 
			
		||||
            .switchIfEmpty(Mono.error(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,10 +8,13 @@ import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
 | 
			
		|||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
 | 
			
		||||
import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
 | 
			
		||||
import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route;
 | 
			
		||||
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
 | 
			
		||||
 | 
			
		||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
| 
						 | 
				
			
			@ -79,53 +82,106 @@ public class UcPostAttachmentEndpoint implements CustomEndpoint {
 | 
			
		|||
                    )
 | 
			
		||||
                    .response(responseBuilder().implementation(Attachment.class))
 | 
			
		||||
            )
 | 
			
		||||
            .POST("/attachments/-/upload-from-url", contentType(MediaType.APPLICATION_JSON),
 | 
			
		||||
                this::uploadFromUrlForPost,
 | 
			
		||||
                builder -> builder
 | 
			
		||||
                    .operationId("ExternalTransferAttachment")
 | 
			
		||||
                    .description("Upload attachment from the given URL.")
 | 
			
		||||
                    .tag(tag)
 | 
			
		||||
                    .parameter(parameterBuilder()
 | 
			
		||||
                        .name("waitForPermalink")
 | 
			
		||||
                        .description("Wait for permalink.")
 | 
			
		||||
                        .in(ParameterIn.QUERY)
 | 
			
		||||
                        .required(false)
 | 
			
		||||
                        .implementation(boolean.class))
 | 
			
		||||
                    .requestBody(requestBodyBuilder()
 | 
			
		||||
                        .required(true)
 | 
			
		||||
                        .content(contentBuilder()
 | 
			
		||||
                            .mediaType(MediaType.APPLICATION_JSON_VALUE)
 | 
			
		||||
                            .schema(schemaBuilder().implementation(UploadFromUrlRequest.class))
 | 
			
		||||
                        ))
 | 
			
		||||
                    .response(responseBuilder().implementation(Attachment.class))
 | 
			
		||||
                    .build()
 | 
			
		||||
            )
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Mono<ServerResponse> uploadFromUrlForPost(ServerRequest request) {
 | 
			
		||||
        var uploadFromUrlRequestMono = request.bodyToMono(UploadFromUrlRequest.class);
 | 
			
		||||
 | 
			
		||||
        var uploadAttachment = getPostSettingMono()
 | 
			
		||||
            .flatMap(postSetting -> uploadFromUrlRequestMono.flatMap(
 | 
			
		||||
                uploadFromUrlRequest -> {
 | 
			
		||||
                    var url = uploadFromUrlRequest.url();
 | 
			
		||||
                    var fileName = uploadFromUrlRequest.filename();
 | 
			
		||||
                    return attachmentService.uploadFromUrl(url,
 | 
			
		||||
                        postSetting.getAttachmentPolicyName(),
 | 
			
		||||
                        postSetting.getAttachmentGroupName(),
 | 
			
		||||
                        fileName
 | 
			
		||||
                    );
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        var waitForPermalink = request.queryParam("waitForPermalink")
 | 
			
		||||
            .map(Boolean::valueOf)
 | 
			
		||||
            .orElse(false);
 | 
			
		||||
        if (waitForPermalink) {
 | 
			
		||||
            uploadAttachment = waitForPermalink(uploadAttachment);
 | 
			
		||||
        }
 | 
			
		||||
        return ServerResponse.ok().body(uploadAttachment, Attachment.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Mono<ServerResponse> createAttachmentForPost(ServerRequest request) {
 | 
			
		||||
        var postAttachmentRequestMono = request.body(BodyExtractors.toMultipartData())
 | 
			
		||||
            .map(PostAttachmentRequest::from)
 | 
			
		||||
            .cache();
 | 
			
		||||
 | 
			
		||||
        var postSettingMono = systemSettingFetcher.fetchPost()
 | 
			
		||||
            .<SystemSetting.Post>handle((postSetting, sink) -> {
 | 
			
		||||
                var attachmentPolicyName = postSetting.getAttachmentPolicyName();
 | 
			
		||||
                if (StringUtils.isBlank(attachmentPolicyName)) {
 | 
			
		||||
                    sink.error(new ServerWebInputException(
 | 
			
		||||
                        "Please configure storage policy for post attachment first."));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                sink.next(postSetting);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        // get settings
 | 
			
		||||
        var createdAttachment = postSettingMono.flatMap(postSetting -> postAttachmentRequestMono
 | 
			
		||||
            .flatMap(postAttachmentRequest -> getCurrentUser().flatMap(
 | 
			
		||||
                username -> attachmentService.upload(username,
 | 
			
		||||
                    postSetting.getAttachmentPolicyName(),
 | 
			
		||||
                    postSetting.getAttachmentGroupName(),
 | 
			
		||||
                    postAttachmentRequest.file(),
 | 
			
		||||
                    linkWith(postAttachmentRequest)))));
 | 
			
		||||
        var createdAttachment =
 | 
			
		||||
            getPostSettingMono().flatMap(postSetting -> postAttachmentRequestMono
 | 
			
		||||
                .flatMap(postAttachmentRequest -> getCurrentUser().flatMap(
 | 
			
		||||
                    username -> attachmentService.upload(username,
 | 
			
		||||
                        postSetting.getAttachmentPolicyName(),
 | 
			
		||||
                        postSetting.getAttachmentGroupName(),
 | 
			
		||||
                        postAttachmentRequest.file(),
 | 
			
		||||
                        linkWith(postAttachmentRequest)))));
 | 
			
		||||
 | 
			
		||||
        var waitForPermalink = request.queryParam("waitForPermalink")
 | 
			
		||||
            .map(Boolean::valueOf)
 | 
			
		||||
            .orElse(false);
 | 
			
		||||
        if (waitForPermalink) {
 | 
			
		||||
            createdAttachment = createdAttachment.flatMap(attachment ->
 | 
			
		||||
                attachmentService.getPermalink(attachment)
 | 
			
		||||
                    .doOnNext(permalink -> {
 | 
			
		||||
                        var status = attachment.getStatus();
 | 
			
		||||
                        if (status == null) {
 | 
			
		||||
                            status = new Attachment.AttachmentStatus();
 | 
			
		||||
                            attachment.setStatus(status);
 | 
			
		||||
                        }
 | 
			
		||||
                        status.setPermalink(permalink.toString());
 | 
			
		||||
                    })
 | 
			
		||||
                    .thenReturn(attachment));
 | 
			
		||||
            createdAttachment = waitForPermalink(createdAttachment);
 | 
			
		||||
        }
 | 
			
		||||
        return ServerResponse.ok().body(createdAttachment, Attachment.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Mono<Attachment> waitForPermalink(Mono<Attachment> createdAttachment) {
 | 
			
		||||
        createdAttachment = createdAttachment.flatMap(attachment ->
 | 
			
		||||
            attachmentService.getPermalink(attachment)
 | 
			
		||||
                .doOnNext(permalink -> {
 | 
			
		||||
                    var status = attachment.getStatus();
 | 
			
		||||
                    if (status == null) {
 | 
			
		||||
                        status = new Attachment.AttachmentStatus();
 | 
			
		||||
                        attachment.setStatus(status);
 | 
			
		||||
                    }
 | 
			
		||||
                    status.setPermalink(permalink.toString());
 | 
			
		||||
                })
 | 
			
		||||
                .thenReturn(attachment));
 | 
			
		||||
        return createdAttachment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Mono<SystemSetting.Post> getPostSettingMono() {
 | 
			
		||||
        return systemSettingFetcher.fetchPost().handle((postSetting, sink) -> {
 | 
			
		||||
            var attachmentPolicyName = postSetting.getAttachmentPolicyName();
 | 
			
		||||
            if (StringUtils.isBlank(attachmentPolicyName)) {
 | 
			
		||||
                sink.error(new ServerWebInputException(
 | 
			
		||||
                    "Please configure storage policy for post attachment first."));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            sink.next(postSetting);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Consumer<Attachment> linkWith(PostAttachmentRequest request) {
 | 
			
		||||
        return attachment -> {
 | 
			
		||||
            var labels = attachment.getMetadata().getLabels();
 | 
			
		||||
| 
						 | 
				
			
			@ -165,6 +221,15 @@ public class UcPostAttachmentEndpoint implements CustomEndpoint {
 | 
			
		|||
        return GroupVersion.parseAPIVersion("uc.api.content.halo.run/v1alpha1");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record UploadFromUrlRequest(@Schema(requiredMode = REQUIRED) URL url,
 | 
			
		||||
                                       String filename) {
 | 
			
		||||
        public UploadFromUrlRequest {
 | 
			
		||||
            if (Objects.isNull(url)) {
 | 
			
		||||
                throw new ServerWebInputException("Required url is missing.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Schema(types = "object")
 | 
			
		||||
    public record PostAttachmentRequest(
 | 
			
		||||
        @Schema(requiredMode = REQUIRED, description = "Attachment data.")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,10 +3,12 @@ package run.halo.app.infra;
 | 
			
		|||
import java.net.URI;
 | 
			
		||||
import org.springframework.core.io.buffer.DataBuffer;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.web.reactive.function.client.WebClient;
 | 
			
		||||
import reactor.core.publisher.Flux;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
import reactor.netty.http.client.HttpClient;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -31,4 +33,12 @@ public class DefaultReactiveUrlDataBufferFetcher implements ReactiveUrlDataBuffe
 | 
			
		|||
            .retrieve()
 | 
			
		||||
            .bodyToFlux(DataBuffer.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Mono<ResponseEntity<Void>> head(URI uri) {
 | 
			
		||||
        return webClient.head()
 | 
			
		||||
            .uri(uri)
 | 
			
		||||
            .retrieve()
 | 
			
		||||
            .toBodilessEntity();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,9 @@ package run.halo.app.infra;
 | 
			
		|||
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import org.springframework.core.io.buffer.DataBuffer;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import reactor.core.publisher.Flux;
 | 
			
		||||
import reactor.core.publisher.Mono;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * <p>{@link DataBuffer} stream fetcher from uri.</p>
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +12,6 @@ import reactor.core.publisher.Flux;
 | 
			
		|||
 * @author guqing
 | 
			
		||||
 * @since 2.6.0
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface ReactiveUrlDataBufferFetcher {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -20,4 +21,12 @@ public interface ReactiveUrlDataBufferFetcher {
 | 
			
		|||
     * @return data buffer flux
 | 
			
		||||
     */
 | 
			
		||||
    Flux<DataBuffer> fetch(URI uri);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * <p>Get head of the uri.</p>
 | 
			
		||||
     *
 | 
			
		||||
     * @param uri uri to fetch
 | 
			
		||||
     * @return response entity
 | 
			
		||||
     */
 | 
			
		||||
    Mono<ResponseEntity<Void>> head(URI uri);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package run.halo.app.infra.utils;
 | 
			
		||||
 | 
			
		||||
import com.google.common.io.Files;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,20 @@ public final class FileNameUtils {
 | 
			
		|||
    private FileNameUtils() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check whether the file name has an extension.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filename is name of file.
 | 
			
		||||
     * @return True if file name has extension, otherwise false.
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean hasFileExtension(String filename) {
 | 
			
		||||
        if (filename == null || filename.isEmpty()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        var extensionRegex = ".*\\.[a-zA-Z0-9]+$";
 | 
			
		||||
        return Pattern.matches(extensionRegex, filename);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String removeFileExtension(String filename, boolean removeAllExtensions) {
 | 
			
		||||
        if (filename == null || filename.isEmpty()) {
 | 
			
		||||
            return filename;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,10 +17,15 @@ rules:
 | 
			
		|||
  - apiGroups: [ "api.console.halo.run" ]
 | 
			
		||||
    resources: [ "attachments" ]
 | 
			
		||||
    verbs: [ "*" ]
 | 
			
		||||
  - apiGroups: [ "api.console.halo.run" ]
 | 
			
		||||
    resources: [ "attachments/upload-from-url" ]
 | 
			
		||||
    verbs: [ "create" ]
 | 
			
		||||
  - apiGroups: [ "" ]
 | 
			
		||||
    resources: [ "settings" ]
 | 
			
		||||
    verbs: [ "get" ]
 | 
			
		||||
  - nonResourceURLs: [ "/apis/api.console.halo.run/v1alpha1/attachments/upload" ]
 | 
			
		||||
  - nonResourceURLs: [
 | 
			
		||||
      "/apis/api.console.halo.run/v1alpha1/attachments/upload"
 | 
			
		||||
    ]
 | 
			
		||||
    verbs: [ "create" ]
 | 
			
		||||
---
 | 
			
		||||
apiVersion: v1alpha1
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,3 +114,6 @@ rules:
 | 
			
		|||
  - apiGroups: [ "uc.api.content.halo.run" ]
 | 
			
		||||
    resources: [ "attachments" ]
 | 
			
		||||
    verbs: [ "create", "update", "delete" ]
 | 
			
		||||
  - apiGroups: [ "uc.api.content.halo.run" ]
 | 
			
		||||
    resources: [ "attachments/upload-from-url" ]
 | 
			
		||||
    verbs: [ "create" ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,8 +18,11 @@ import org.junit.jupiter.api.Test;
 | 
			
		|||
import org.junit.jupiter.api.extension.ExtendWith;
 | 
			
		||||
import org.mockito.Mock;
 | 
			
		||||
import org.mockito.junit.jupiter.MockitoExtension;
 | 
			
		||||
import org.springframework.core.io.buffer.DataBuffer;
 | 
			
		||||
import org.springframework.data.domain.Sort;
 | 
			
		||||
import org.springframework.http.HttpStatusCode;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.http.client.MultipartBodyBuilder;
 | 
			
		||||
import org.springframework.test.web.reactive.server.WebTestClient;
 | 
			
		||||
import org.springframework.web.reactive.function.BodyInserters;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +38,7 @@ import run.halo.app.extension.ListResult;
 | 
			
		|||
import run.halo.app.extension.Metadata;
 | 
			
		||||
import run.halo.app.extension.PageRequest;
 | 
			
		||||
import run.halo.app.extension.ReactiveExtensionClient;
 | 
			
		||||
import run.halo.app.infra.ReactiveUrlDataBufferFetcher;
 | 
			
		||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
 | 
			
		||||
 | 
			
		||||
@ExtendWith(MockitoExtension.class)
 | 
			
		||||
| 
						 | 
				
			
			@ -46,13 +50,17 @@ class AttachmentEndpointTest {
 | 
			
		|||
    @Mock
 | 
			
		||||
    ExtensionGetter extensionGetter;
 | 
			
		||||
 | 
			
		||||
    @Mock
 | 
			
		||||
    ReactiveUrlDataBufferFetcher dataBufferFetcher;
 | 
			
		||||
 | 
			
		||||
    AttachmentEndpoint endpoint;
 | 
			
		||||
 | 
			
		||||
    WebTestClient webClient;
 | 
			
		||||
 | 
			
		||||
    @BeforeEach
 | 
			
		||||
    void setUp() {
 | 
			
		||||
        var attachmentService = new DefaultAttachmentService(client, extensionGetter);
 | 
			
		||||
        var attachmentService =
 | 
			
		||||
            new DefaultAttachmentService(client, extensionGetter, dataBufferFetcher);
 | 
			
		||||
        endpoint = new AttachmentEndpoint(attachmentService, client);
 | 
			
		||||
        webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint())
 | 
			
		||||
            .apply(springSecurity())
 | 
			
		||||
| 
						 | 
				
			
			@ -263,4 +271,78 @@ class AttachmentEndpointTest {
 | 
			
		|||
            verify(client).listBy(eq(Attachment.class), any(), any(PageRequest.class));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nested
 | 
			
		||||
    class ExternalTransferTest {
 | 
			
		||||
        @Test
 | 
			
		||||
        void shouldResponseErrorIfNoPermalinkProvided() {
 | 
			
		||||
            webClient
 | 
			
		||||
                .mutateWith(mockUser("fake-user").password("fake-password"))
 | 
			
		||||
                .post()
 | 
			
		||||
                .uri("/attachments/-/upload-from-url")
 | 
			
		||||
                .contentType(MediaType.APPLICATION_JSON)
 | 
			
		||||
                .bodyValue(Map.of("policyName", "fake-policy"))
 | 
			
		||||
                .exchange()
 | 
			
		||||
                .expectStatus().isBadRequest();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void shouldTransferSuccessfully() {
 | 
			
		||||
            var policySpec = new PolicySpec();
 | 
			
		||||
            policySpec.setConfigMapName("fake-configmap");
 | 
			
		||||
            var policyMetadata = new Metadata();
 | 
			
		||||
            policyMetadata.setName("fake-policy");
 | 
			
		||||
            var policy = new Policy();
 | 
			
		||||
            policy.setSpec(policySpec);
 | 
			
		||||
            policy.setMetadata(policyMetadata);
 | 
			
		||||
 | 
			
		||||
            var cm = new ConfigMap();
 | 
			
		||||
            var cmMetadata = new Metadata();
 | 
			
		||||
            cmMetadata.setName("fake-configmap");
 | 
			
		||||
            cm.setData(Map.of());
 | 
			
		||||
 | 
			
		||||
            when(client.get(Policy.class, "fake-policy")).thenReturn(Mono.just(policy));
 | 
			
		||||
            when(client.get(ConfigMap.class, "fake-configmap")).thenReturn(Mono.just(cm));
 | 
			
		||||
 | 
			
		||||
            var handler = mock(AttachmentHandler.class);
 | 
			
		||||
            var metadata = new Metadata();
 | 
			
		||||
            metadata.setName("fake-attachment");
 | 
			
		||||
            var attachment = new Attachment();
 | 
			
		||||
            attachment.setMetadata(metadata);
 | 
			
		||||
 | 
			
		||||
            ResponseEntity<Void> response = new ResponseEntity<>(HttpStatusCode.valueOf(200));
 | 
			
		||||
            DataBuffer dataBuffer = mock(DataBuffer.class);
 | 
			
		||||
 | 
			
		||||
            when(handler.upload(any())).thenReturn(Mono.just(attachment));
 | 
			
		||||
            when(dataBufferFetcher.head(any())).thenReturn(Mono.just(response));
 | 
			
		||||
            when(dataBufferFetcher.fetch(any())).thenReturn(Flux.just(dataBuffer));
 | 
			
		||||
            when(extensionGetter.getExtensions(AttachmentHandler.class))
 | 
			
		||||
                .thenReturn(Flux.just(handler));
 | 
			
		||||
            when(client.create(attachment)).thenReturn(Mono.just(attachment));
 | 
			
		||||
 | 
			
		||||
            var fakeValue =
 | 
			
		||||
                Map.of("policyName", "fake-policy", "url",
 | 
			
		||||
                    "http://localhost:8090/fake-url.jpg");
 | 
			
		||||
            webClient
 | 
			
		||||
                .mutateWith(mockUser("fake-user").password("fake-password"))
 | 
			
		||||
                .post()
 | 
			
		||||
                .uri("/attachments/-/upload-from-url")
 | 
			
		||||
                .contentType(MediaType.APPLICATION_JSON)
 | 
			
		||||
                .bodyValue(fakeValue)
 | 
			
		||||
                .exchange()
 | 
			
		||||
                .expectStatus().isOk()
 | 
			
		||||
                .expectBody()
 | 
			
		||||
                .jsonPath("$.metadata.name").isEqualTo("fake-attachment")
 | 
			
		||||
                .jsonPath("$.spec.ownerName").isEqualTo("fake-user")
 | 
			
		||||
                .jsonPath("$.spec.policyName").isEqualTo("fake-policy")
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
            verify(client).get(Policy.class, "fake-policy");
 | 
			
		||||
            verify(client).get(ConfigMap.class, "fake-configmap");
 | 
			
		||||
            verify(client).create(attachment);
 | 
			
		||||
            verify(dataBufferFetcher).head(any());
 | 
			
		||||
            verify(dataBufferFetcher).fetch(any());
 | 
			
		||||
            verify(handler).upload(any());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -320,6 +320,7 @@ models/totp-auth-link-response.ts
 | 
			
		|||
models/totp-request.ts
 | 
			
		||||
models/two-factor-auth-settings.ts
 | 
			
		||||
models/upgrade-from-uri-request.ts
 | 
			
		||||
models/upload-from-url-request.ts
 | 
			
		||||
models/user-connection-list.ts
 | 
			
		||||
models/user-connection-spec.ts
 | 
			
		||||
models/user-connection.ts
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,12 +25,57 @@ import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, ope
 | 
			
		|||
import { Attachment } from '../models';
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import { AttachmentList } from '../models';
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import { UploadFromUrlRequest } from '../models';
 | 
			
		||||
/**
 | 
			
		||||
 * AttachmentV1alpha1ConsoleApi - axios parameter creator
 | 
			
		||||
 * @export
 | 
			
		||||
 */
 | 
			
		||||
export const AttachmentV1alpha1ConsoleApiAxiosParamCreator = function (configuration?: Configuration) {
 | 
			
		||||
    return {
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {UploadFromUrlRequest} uploadFromUrlRequest 
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        externalTransferAttachment: async (uploadFromUrlRequest: UploadFromUrlRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
 | 
			
		||||
            // verify required parameter 'uploadFromUrlRequest' is not null or undefined
 | 
			
		||||
            assertParamExists('externalTransferAttachment', 'uploadFromUrlRequest', uploadFromUrlRequest)
 | 
			
		||||
            const localVarPath = `/apis/api.console.halo.run/v1alpha1/attachments/-/upload-from-url`;
 | 
			
		||||
            // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | 
			
		||||
            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
 | 
			
		||||
            let baseOptions;
 | 
			
		||||
            if (configuration) {
 | 
			
		||||
                baseOptions = configuration.baseOptions;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
 | 
			
		||||
            const localVarHeaderParameter = {} as any;
 | 
			
		||||
            const localVarQueryParameter = {} as any;
 | 
			
		||||
 | 
			
		||||
            // authentication basicAuth required
 | 
			
		||||
            // http basic authentication required
 | 
			
		||||
            setBasicAuthToObject(localVarRequestOptions, configuration)
 | 
			
		||||
 | 
			
		||||
            // authentication bearerAuth required
 | 
			
		||||
            // http bearer authentication required
 | 
			
		||||
            await setBearerAuthToObject(localVarHeaderParameter, configuration)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
            localVarHeaderParameter['Content-Type'] = 'application/json';
 | 
			
		||||
 | 
			
		||||
            setSearchParams(localVarUrlObj, localVarQueryParameter);
 | 
			
		||||
            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
 | 
			
		||||
            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
 | 
			
		||||
            localVarRequestOptions.data = serializeDataIfNeeded(uploadFromUrlRequest, localVarRequestOptions, configuration)
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                url: toPathString(localVarUrlObj),
 | 
			
		||||
                options: localVarRequestOptions,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {number} [page] Page number. Default is 0.
 | 
			
		||||
| 
						 | 
				
			
			@ -178,6 +223,18 @@ export const AttachmentV1alpha1ConsoleApiAxiosParamCreator = function (configura
 | 
			
		|||
export const AttachmentV1alpha1ConsoleApiFp = function(configuration?: Configuration) {
 | 
			
		||||
    const localVarAxiosParamCreator = AttachmentV1alpha1ConsoleApiAxiosParamCreator(configuration)
 | 
			
		||||
    return {
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {UploadFromUrlRequest} uploadFromUrlRequest 
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        async externalTransferAttachment(uploadFromUrlRequest: UploadFromUrlRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Attachment>> {
 | 
			
		||||
            const localVarAxiosArgs = await localVarAxiosParamCreator.externalTransferAttachment(uploadFromUrlRequest, options);
 | 
			
		||||
            const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
 | 
			
		||||
            const localVarOperationServerBasePath = operationServerMap['AttachmentV1alpha1ConsoleApi.externalTransferAttachment']?.[localVarOperationServerIndex]?.url;
 | 
			
		||||
            return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {number} [page] Page number. Default is 0.
 | 
			
		||||
| 
						 | 
				
			
			@ -221,6 +278,15 @@ export const AttachmentV1alpha1ConsoleApiFp = function(configuration?: Configura
 | 
			
		|||
export const AttachmentV1alpha1ConsoleApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
 | 
			
		||||
    const localVarFp = AttachmentV1alpha1ConsoleApiFp(configuration)
 | 
			
		||||
    return {
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {AttachmentV1alpha1ConsoleApiExternalTransferAttachmentRequest} requestParameters Request parameters.
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        externalTransferAttachment(requestParameters: AttachmentV1alpha1ConsoleApiExternalTransferAttachmentRequest, options?: RawAxiosRequestConfig): AxiosPromise<Attachment> {
 | 
			
		||||
            return localVarFp.externalTransferAttachment(requestParameters.uploadFromUrlRequest, options).then((request) => request(axios, basePath));
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * 
 | 
			
		||||
         * @param {AttachmentV1alpha1ConsoleApiSearchAttachmentsRequest} requestParameters Request parameters.
 | 
			
		||||
| 
						 | 
				
			
			@ -242,6 +308,20 @@ export const AttachmentV1alpha1ConsoleApiFactory = function (configuration?: Con
 | 
			
		|||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Request parameters for externalTransferAttachment operation in AttachmentV1alpha1ConsoleApi.
 | 
			
		||||
 * @export
 | 
			
		||||
 * @interface AttachmentV1alpha1ConsoleApiExternalTransferAttachmentRequest
 | 
			
		||||
 */
 | 
			
		||||
export interface AttachmentV1alpha1ConsoleApiExternalTransferAttachmentRequest {
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {UploadFromUrlRequest}
 | 
			
		||||
     * @memberof AttachmentV1alpha1ConsoleApiExternalTransferAttachment
 | 
			
		||||
     */
 | 
			
		||||
    readonly uploadFromUrlRequest: UploadFromUrlRequest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Request parameters for searchAttachments operation in AttachmentV1alpha1ConsoleApi.
 | 
			
		||||
 * @export
 | 
			
		||||
| 
						 | 
				
			
			@ -340,6 +420,17 @@ export interface AttachmentV1alpha1ConsoleApiUploadAttachmentRequest {
 | 
			
		|||
 * @extends {BaseAPI}
 | 
			
		||||
 */
 | 
			
		||||
export class AttachmentV1alpha1ConsoleApi extends BaseAPI {
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {AttachmentV1alpha1ConsoleApiExternalTransferAttachmentRequest} requestParameters Request parameters.
 | 
			
		||||
     * @param {*} [options] Override http request option.
 | 
			
		||||
     * @throws {RequiredError}
 | 
			
		||||
     * @memberof AttachmentV1alpha1ConsoleApi
 | 
			
		||||
     */
 | 
			
		||||
    public externalTransferAttachment(requestParameters: AttachmentV1alpha1ConsoleApiExternalTransferAttachmentRequest, options?: RawAxiosRequestConfig) {
 | 
			
		||||
        return AttachmentV1alpha1ConsoleApiFp(this.configuration).externalTransferAttachment(requestParameters.uploadFromUrlRequest, options).then((request) => request(this.axios, this.basePath));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {AttachmentV1alpha1ConsoleApiSearchAttachmentsRequest} requestParameters Request parameters.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,8 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj
 | 
			
		|||
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import { Attachment } from '../models';
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import { UploadFromUrlRequest } from '../models';
 | 
			
		||||
/**
 | 
			
		||||
 * AttachmentV1alpha1UcApi - axios parameter creator
 | 
			
		||||
 * @export
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +89,54 @@ export const AttachmentV1alpha1UcApiAxiosParamCreator = function (configuration?
 | 
			
		|||
            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
 | 
			
		||||
            localVarRequestOptions.data = localVarFormParams;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                url: toPathString(localVarUrlObj),
 | 
			
		||||
                options: localVarRequestOptions,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Upload attachment from the given URL.
 | 
			
		||||
         * @param {UploadFromUrlRequest} uploadFromUrlRequest 
 | 
			
		||||
         * @param {boolean} [waitForPermalink] Wait for permalink.
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        externalTransferAttachment1: async (uploadFromUrlRequest: UploadFromUrlRequest, waitForPermalink?: boolean, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
 | 
			
		||||
            // verify required parameter 'uploadFromUrlRequest' is not null or undefined
 | 
			
		||||
            assertParamExists('externalTransferAttachment1', 'uploadFromUrlRequest', uploadFromUrlRequest)
 | 
			
		||||
            const localVarPath = `/apis/uc.api.content.halo.run/v1alpha1/attachments/-/upload-from-url`;
 | 
			
		||||
            // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | 
			
		||||
            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
 | 
			
		||||
            let baseOptions;
 | 
			
		||||
            if (configuration) {
 | 
			
		||||
                baseOptions = configuration.baseOptions;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
 | 
			
		||||
            const localVarHeaderParameter = {} as any;
 | 
			
		||||
            const localVarQueryParameter = {} as any;
 | 
			
		||||
 | 
			
		||||
            // authentication basicAuth required
 | 
			
		||||
            // http basic authentication required
 | 
			
		||||
            setBasicAuthToObject(localVarRequestOptions, configuration)
 | 
			
		||||
 | 
			
		||||
            // authentication bearerAuth required
 | 
			
		||||
            // http bearer authentication required
 | 
			
		||||
            await setBearerAuthToObject(localVarHeaderParameter, configuration)
 | 
			
		||||
 | 
			
		||||
            if (waitForPermalink !== undefined) {
 | 
			
		||||
                localVarQueryParameter['waitForPermalink'] = waitForPermalink;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
            localVarHeaderParameter['Content-Type'] = 'application/json';
 | 
			
		||||
 | 
			
		||||
            setSearchParams(localVarUrlObj, localVarQueryParameter);
 | 
			
		||||
            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
 | 
			
		||||
            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
 | 
			
		||||
            localVarRequestOptions.data = serializeDataIfNeeded(uploadFromUrlRequest, localVarRequestOptions, configuration)
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                url: toPathString(localVarUrlObj),
 | 
			
		||||
                options: localVarRequestOptions,
 | 
			
		||||
| 
						 | 
				
			
			@ -117,6 +167,19 @@ export const AttachmentV1alpha1UcApiFp = function(configuration?: Configuration)
 | 
			
		|||
            const localVarOperationServerBasePath = operationServerMap['AttachmentV1alpha1UcApi.createAttachmentForPost']?.[localVarOperationServerIndex]?.url;
 | 
			
		||||
            return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Upload attachment from the given URL.
 | 
			
		||||
         * @param {UploadFromUrlRequest} uploadFromUrlRequest 
 | 
			
		||||
         * @param {boolean} [waitForPermalink] Wait for permalink.
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        async externalTransferAttachment1(uploadFromUrlRequest: UploadFromUrlRequest, waitForPermalink?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Attachment>> {
 | 
			
		||||
            const localVarAxiosArgs = await localVarAxiosParamCreator.externalTransferAttachment1(uploadFromUrlRequest, waitForPermalink, options);
 | 
			
		||||
            const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
 | 
			
		||||
            const localVarOperationServerBasePath = operationServerMap['AttachmentV1alpha1UcApi.externalTransferAttachment1']?.[localVarOperationServerIndex]?.url;
 | 
			
		||||
            return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +199,15 @@ export const AttachmentV1alpha1UcApiFactory = function (configuration?: Configur
 | 
			
		|||
        createAttachmentForPost(requestParameters: AttachmentV1alpha1UcApiCreateAttachmentForPostRequest, options?: RawAxiosRequestConfig): AxiosPromise<Attachment> {
 | 
			
		||||
            return localVarFp.createAttachmentForPost(requestParameters.file, requestParameters.waitForPermalink, requestParameters.postName, requestParameters.singlePageName, options).then((request) => request(axios, basePath));
 | 
			
		||||
        },
 | 
			
		||||
        /**
 | 
			
		||||
         * Upload attachment from the given URL.
 | 
			
		||||
         * @param {AttachmentV1alpha1UcApiExternalTransferAttachment1Request} requestParameters Request parameters.
 | 
			
		||||
         * @param {*} [options] Override http request option.
 | 
			
		||||
         * @throws {RequiredError}
 | 
			
		||||
         */
 | 
			
		||||
        externalTransferAttachment1(requestParameters: AttachmentV1alpha1UcApiExternalTransferAttachment1Request, options?: RawAxiosRequestConfig): AxiosPromise<Attachment> {
 | 
			
		||||
            return localVarFp.externalTransferAttachment1(requestParameters.uploadFromUrlRequest, requestParameters.waitForPermalink, options).then((request) => request(axios, basePath));
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +246,27 @@ export interface AttachmentV1alpha1UcApiCreateAttachmentForPostRequest {
 | 
			
		|||
    readonly singlePageName?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Request parameters for externalTransferAttachment1 operation in AttachmentV1alpha1UcApi.
 | 
			
		||||
 * @export
 | 
			
		||||
 * @interface AttachmentV1alpha1UcApiExternalTransferAttachment1Request
 | 
			
		||||
 */
 | 
			
		||||
export interface AttachmentV1alpha1UcApiExternalTransferAttachment1Request {
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {UploadFromUrlRequest}
 | 
			
		||||
     * @memberof AttachmentV1alpha1UcApiExternalTransferAttachment1
 | 
			
		||||
     */
 | 
			
		||||
    readonly uploadFromUrlRequest: UploadFromUrlRequest
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wait for permalink.
 | 
			
		||||
     * @type {boolean}
 | 
			
		||||
     * @memberof AttachmentV1alpha1UcApiExternalTransferAttachment1
 | 
			
		||||
     */
 | 
			
		||||
    readonly waitForPermalink?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AttachmentV1alpha1UcApi - object-oriented interface
 | 
			
		||||
 * @export
 | 
			
		||||
| 
						 | 
				
			
			@ -191,5 +284,16 @@ export class AttachmentV1alpha1UcApi extends BaseAPI {
 | 
			
		|||
    public createAttachmentForPost(requestParameters: AttachmentV1alpha1UcApiCreateAttachmentForPostRequest, options?: RawAxiosRequestConfig) {
 | 
			
		||||
        return AttachmentV1alpha1UcApiFp(this.configuration).createAttachmentForPost(requestParameters.file, requestParameters.waitForPermalink, requestParameters.postName, requestParameters.singlePageName, options).then((request) => request(this.axios, this.basePath));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Upload attachment from the given URL.
 | 
			
		||||
     * @param {AttachmentV1alpha1UcApiExternalTransferAttachment1Request} requestParameters Request parameters.
 | 
			
		||||
     * @param {*} [options] Override http request option.
 | 
			
		||||
     * @throws {RequiredError}
 | 
			
		||||
     * @memberof AttachmentV1alpha1UcApi
 | 
			
		||||
     */
 | 
			
		||||
    public externalTransferAttachment1(requestParameters: AttachmentV1alpha1UcApiExternalTransferAttachment1Request, options?: RawAxiosRequestConfig) {
 | 
			
		||||
        return AttachmentV1alpha1UcApiFp(this.configuration).externalTransferAttachment1(requestParameters.uploadFromUrlRequest, requestParameters.waitForPermalink, options).then((request) => request(this.axios, this.basePath));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -236,6 +236,7 @@ export * from './totp-auth-link-response';
 | 
			
		|||
export * from './totp-request';
 | 
			
		||||
export * from './two-factor-auth-settings';
 | 
			
		||||
export * from './upgrade-from-uri-request';
 | 
			
		||||
export * from './upload-from-url-request';
 | 
			
		||||
export * from './user';
 | 
			
		||||
export * from './user-connection';
 | 
			
		||||
export * from './user-connection-list';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
/**
 | 
			
		||||
 * Halo
 | 
			
		||||
 * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
 | 
			
		||||
 *
 | 
			
		||||
 * The version of the OpenAPI document: 2.19.0-SNAPSHOT
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 | 
			
		||||
 * https://openapi-generator.tech
 | 
			
		||||
 * Do not edit the class manually.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * @export
 | 
			
		||||
 * @interface UploadFromUrlRequest
 | 
			
		||||
 */
 | 
			
		||||
export interface UploadFromUrlRequest {
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     * @memberof UploadFromUrlRequest
 | 
			
		||||
     */
 | 
			
		||||
    'filename'?: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     * @memberof UploadFromUrlRequest
 | 
			
		||||
     */
 | 
			
		||||
    'url': string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue