Skip to content

File Upload

BrunoRosendo edited this page Sep 11, 2023 · 4 revisions

There are several instances on the website where file uploads are required, such as images for member profiles, projects, and events. This section provides an overview of how the upload functionality is implemented and how it can be utilized.

Contents

File Uploaders

To facilitate the implementation of file uploads across various services without the need for extensive code modifications, we've established a shared abstract class. Each service extends this common class to harness its file upload capabilities. Currently, the class only features a method for image uploads, but its functionality can be further expanded as necessary to include tasks like file deletion.

abstract class FileUploader {
    abstract fun uploadImage(folder: String, fileName: String, image: ByteArray): String

    fun buildFileName(photoFile: MultipartFile, prefix: String = ""): String {
        val limitedPrefix = prefix.take(100) // File name length has a limit of 256 characters
        return "$limitedPrefix-${UUID.randomUUID()}.${photoFile.filenameExtension()}"
    }
}

The application operates with a single service, and this decision is determined during startup. Further details on this are elaborated in the Configuration section.

Static File Uploader

The static file uploader simply utilizes the server's file system for local storage of files. While this may not be the preferred production setup, it serves as a reliable alternative during testing and in case any issues arise with cloud storage.

class StaticFileUploader(private val storePath: String, private val servePath: String) : FileUploader() {
    override fun uploadImage(folder: String, fileName: String, image: ByteArray): String {
        val file = File("$storePath/$folder/$fileName")
        file.parentFile.mkdirs()
        file.createNewFile()
        file.writeBytes(image)

        return "$servePath/$folder/$fileName"
    }
}
  • The storePath refers to the directory where files are stored on the server.
  • The servePath represents the URL used to serve static files.
  • file.parentFile.mkdirs() has been included to ensure the creation of all necessary directories.

Cloudinary File Uploader

Cloudinary is the cloud storage service we utilize. Employing a service like this is essential to safeguard crucial data and prevent any potential loss that might occur if the data were stored solely on the server.

class CloudinaryFileUploader(private val basePath: String, private val cloudinary: Cloudinary) : FileUploader() {
    override fun uploadImage(folder: String, fileName: String, image: ByteArray): String {
        val path = "$basePath/$folder/$fileName"

        val imageTransformation = Transformation().width(250).height(250).crop("thumb").chain()

        val result = cloudinary.uploader().upload(
            image,
            mapOf(
                "public_id" to path,
                "overwrite" to true,
                "transformation" to imageTransformation
            )
        )

        return result["url"]?.toString() ?: ""
    }
}
  • The folder parameter specifies the directory where files are stored on Cloudinary.
  • We utilize the Cloudinary class, which is part of an external library, to establish communication with the Cloudinary service.
  • Prior to uploading, the image undergoes transformation to prevent excessive storage consumption.

Configuration

Having learned how to implement various types of file uploaders, the remaining aspect is understanding how the application determines which uploader to use. To properly understand this, please refer to the Configuration wiki page.

To begin, a set of configuration properties is utilized to inform the application about the chosen file-uploading service:

@ConfigurationProperties(prefix = "upload")
class UploadConfigProperties(
    val provider: String?,
    val cloudinaryUrl: String?,
    val cloudinaryBasePath: String?,
    val staticPath: String?,
    val staticServe: String?
)
  • provider is the name of the service provider in use, which can currently be either static or cloudinary.
  • cloudinaryUrl and cloudinaryBasePath represent the URL of your Cloudinary dashboard and the directory where files will be stored.
  • staticPath and staticServe correspond to the directory where files are stored on the server and the URL used for serving them.

The decision regarding which uploader to employ at startup is made by defining a custom bean for FileUploader. The specified provider is examined and the respective service is configured accordingly:

@Configuration
class UploadConfig(
    private val uploadConfigProperties: UploadConfigProperties
) {
    @Bean
    fun fileUploader(): FileUploader {
        return when (uploadConfigProperties.provider) {
            "cloudinary" -> CloudinaryFileUploader(
                uploadConfigProperties.cloudinaryBasePath ?: "/",
                Cloudinary(uploadConfigProperties.cloudinaryUrl ?: throw Error("Cloudinary URL not provided"))
            )
            else -> StaticFileUploader(
                uploadConfigProperties.staticPath?.let { ResourceUtils.getFile(it).absolutePath } ?: "",
                uploadConfigProperties.staticServe ?: "localhost:8080"
            )
        }
    }
}