1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2018 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.technical.storage
21 
22 import java.io.{ByteArrayOutputStream, File, FileInputStream, InputStream}
23 import java.net.URL
24 
25 import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
26 import io.circe.{Decoder, Encoder}
27 import io.swagger.annotations.ApiModelProperty
28 import org.make.api.ConfigComponent
29 import org.make.core.user.UserType
30 
31 import scala.annotation.meta.field
32 import scala.concurrent.Future
33 
34 trait StorageService {
35 
36   def uploadImage(fileType: FileType, name: String, contentType: String, content: Content): Future[String]
37   def uploadUserAvatar(name: String, contentType: String, content: Content): Future[String]
38   def uploadAdminUserAvatar(
39     extension: String,
40     contentType: String,
41     content: Content,
42     userType: UserType
43   ): Future[String]
44   def uploadReport(fileType: FileType, name: String, contentType: String, content: Content): Future[String]
45 }
46 
47 final case class UploadResponse(
48   @(ApiModelProperty @field)(dataType = "string", example = "https://example.com/path/to/image.png", required = true)
49   path: String
50 )
51 
52 object UploadResponse {
53   implicit val encoder: Encoder[UploadResponse] = deriveEncoder[UploadResponse]
54   implicit val decoder: Decoder[UploadResponse] = deriveDecoder[UploadResponse]
55 }
56 
57 sealed trait FileType {
58   def name: String
59   def path: String
60 }
61 
62 object FileType {
63 
64   // Use this file type for logos and avatars
65   case object Avatar extends FileType {
66     override def name: String = "Avatar"
67     override def path: String = "avatars"
68   }
69 
70   // Use this file type for the images related to operations, on home page or operation page
71   case object Operation extends FileType {
72     override def name: String = "Operation"
73     override def path: String = "content/operations"
74   }
75 
76   case object Home extends FileType {
77     override def name: String = "Home"
78     override def path: String = "content/home"
79   }
80 
81   case object Report extends FileType {
82     override def name: String = "Report"
83     override def path: String = "consultations"
84   }
85 }
86 
87 trait Content {
88   def toByteArray(): Array[Byte]
89 }
90 
91 object Content {
92   @SuppressWarnings(Array("org.wartremover.warts.ArrayEquals"))
93   final case class ByteArrayContent(content: Array[Byte]) extends Content {
94     override def toByteArray(): Array[Byte] = content
95   }
96 
97   // *Blocking* implementation of reading files
98   final case class InputStreamContent(content: InputStream) extends Content {
99     @SuppressWarnings(Array("org.wartremover.warts.While"))
100     override def toByteArray(): Array[Byte] = {
101       val bufferSize = 2048
102       val buffer = Array.ofDim[Byte](bufferSize)
103       val output = new ByteArrayOutputStream()
104       while ({
105         val readBytes = content.read(buffer)
106         if (readBytes != -1) {
107           output.write(buffer, 0, readBytes)
108         }
109         readBytes != -1
110       }) {}
111 
112       output.toByteArray
113     }
114   }
115 
116   final case class FileContent(content: File) extends Content {
117     override def toByteArray(): Array[Byte] = InputStreamContent(new FileInputStream(content)).toByteArray()
118   }
119 
120   // *Blocking* implementation of retrieving files from URL
121   final case class UrlContent(content: URL) extends Content {
122     override def toByteArray(): Array[Byte] = {
123       InputStreamContent(content.openStream()).toByteArray()
124     }
125   }
126 
127 }
128 
129 trait StorageServiceComponent {
130   def storageService: StorageService
131 }
132 
133 final case class StorageConfiguration(bucketName: String, reportBucketName: String, baseUrl: String, maxFileSize: Long)
134 
135 trait StorageConfigurationComponent {
136   def storageConfiguration: StorageConfiguration
137 }
138 
139 trait DefaultStorageConfigurationComponent extends StorageConfigurationComponent {
140   self: ConfigComponent =>
141 
142   override lazy val storageConfiguration: StorageConfiguration = {
143     val configuration = config.getConfig("make-api.storage")
144     StorageConfiguration(
145       bucketName = configuration.getString("bucket-name"),
146       reportBucketName = configuration.getString("report-bucket-name"),
147       baseUrl = configuration.getString("base-url"),
148       maxFileSize = configuration.getLong("max-upload-file-size")
149     )
150   }
151 }
Line Stmt Id Pos Tree Symbol Tests Code
53 28338 1885 - 1914 ApplyToImplicitArgs io.circe.generic.semiauto.deriveEncoder io.circe.generic.semiauto.deriveEncoder[org.make.api.technical.storage.UploadResponse]({ val inst$macro$8: io.circe.generic.encoding.DerivedAsObjectEncoder[org.make.api.technical.storage.UploadResponse] = { final class anon$lazy$macro$7 extends AnyRef with Serializable { def <init>(): anon$lazy$macro$7 = { anon$lazy$macro$7.super.<init>(); () }; <stable> <accessor> lazy val inst$macro$1: io.circe.generic.encoding.DerivedAsObjectEncoder[org.make.api.technical.storage.UploadResponse] = encoding.this.DerivedAsObjectEncoder.deriveEncoder[org.make.api.technical.storage.UploadResponse, shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](shapeless.this.LabelledGeneric.materializeProduct[org.make.api.technical.storage.UploadResponse, (Symbol @@ String("path")) :: shapeless.HNil, String :: shapeless.HNil, shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](DefaultSymbolicLabelling.instance[org.make.api.technical.storage.UploadResponse, (Symbol @@ String("path")) :: shapeless.HNil](::.apply[Symbol @@ String("path"), shapeless.HNil.type](scala.Symbol.apply("path").asInstanceOf[Symbol @@ String("path")], HNil)), Generic.instance[org.make.api.technical.storage.UploadResponse, String :: shapeless.HNil](((x0$3: org.make.api.technical.storage.UploadResponse) => x0$3 match { case (path: String): org.make.api.technical.storage.UploadResponse((path$macro$5 @ _)) => ::.apply[String, shapeless.HNil.type](path$macro$5, HNil).asInstanceOf[String :: shapeless.HNil] }), ((x0$4: String :: shapeless.HNil) => x0$4 match { case (head: String, tail: shapeless.HNil): String :: shapeless.HNil((path$macro$4 @ _), HNil) => storage.this.UploadResponse.apply(path$macro$4) })), hlist.this.ZipWithKeys.hconsZipWithKeys[Symbol @@ String("path"), String, shapeless.HNil, shapeless.HNil, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](hlist.this.ZipWithKeys.hnilZipWithKeys, Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("path")]](scala.Symbol.apply("path").asInstanceOf[Symbol @@ String("path")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("path")]])), scala.this.<:<.refl[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]), shapeless.Lazy.apply[io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]](anon$lazy$macro$7.this.inst$macro$6)).asInstanceOf[io.circe.generic.encoding.DerivedAsObjectEncoder[org.make.api.technical.storage.UploadResponse]]; <stable> <accessor> lazy val inst$macro$6: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ({ final class $anon extends io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] { def <init>(): <$anon: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]> = { $anon.super.<init>(); () }; private[this] val circeGenericEncoderForpath: io.circe.Encoder[String] = circe.this.Encoder.encodeString; final def encodeObject(a: shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out): io.circe.JsonObject = a match { case (head: shapeless.labelled.FieldType[Symbol @@ String("path"),String], tail: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out): shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out((circeGenericHListBindingForpath @ _), shapeless.HNil) => io.circe.JsonObject.fromIterable(scala.collection.immutable.Vector.apply[(String, io.circe.Json)](scala.Tuple2.apply[String, io.circe.Json]("path", $anon.this.circeGenericEncoderForpath.apply(circeGenericHListBindingForpath)))) } }; new $anon() }: io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]).asInstanceOf[io.circe.generic.encoding.ReprAsObjectEncoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]] }; new anon$lazy$macro$7().inst$macro$1 }; shapeless.Lazy.apply[io.circe.generic.encoding.DerivedAsObjectEncoder[org.make.api.technical.storage.UploadResponse]](inst$macro$8) })
54 29706 1965 - 1994 ApplyToImplicitArgs io.circe.generic.semiauto.deriveDecoder io.circe.generic.semiauto.deriveDecoder[org.make.api.technical.storage.UploadResponse]({ val inst$macro$16: io.circe.generic.decoding.DerivedDecoder[org.make.api.technical.storage.UploadResponse] = { final class anon$lazy$macro$15 extends AnyRef with Serializable { def <init>(): anon$lazy$macro$15 = { anon$lazy$macro$15.super.<init>(); () }; <stable> <accessor> lazy val inst$macro$9: io.circe.generic.decoding.DerivedDecoder[org.make.api.technical.storage.UploadResponse] = decoding.this.DerivedDecoder.deriveDecoder[org.make.api.technical.storage.UploadResponse, shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](shapeless.this.LabelledGeneric.materializeProduct[org.make.api.technical.storage.UploadResponse, (Symbol @@ String("path")) :: shapeless.HNil, String :: shapeless.HNil, shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](DefaultSymbolicLabelling.instance[org.make.api.technical.storage.UploadResponse, (Symbol @@ String("path")) :: shapeless.HNil](::.apply[Symbol @@ String("path"), shapeless.HNil.type](scala.Symbol.apply("path").asInstanceOf[Symbol @@ String("path")], HNil)), Generic.instance[org.make.api.technical.storage.UploadResponse, String :: shapeless.HNil](((x0$7: org.make.api.technical.storage.UploadResponse) => x0$7 match { case (path: String): org.make.api.technical.storage.UploadResponse((path$macro$13 @ _)) => ::.apply[String, shapeless.HNil.type](path$macro$13, HNil).asInstanceOf[String :: shapeless.HNil] }), ((x0$8: String :: shapeless.HNil) => x0$8 match { case (head: String, tail: shapeless.HNil): String :: shapeless.HNil((path$macro$12 @ _), HNil) => storage.this.UploadResponse.apply(path$macro$12) })), hlist.this.ZipWithKeys.hconsZipWithKeys[Symbol @@ String("path"), String, shapeless.HNil, shapeless.HNil, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out](hlist.this.ZipWithKeys.hnilZipWithKeys, Witness.mkWitness[Symbol with shapeless.tag.Tagged[String("path")]](scala.Symbol.apply("path").asInstanceOf[Symbol @@ String("path")].asInstanceOf[Symbol with shapeless.tag.Tagged[String("path")]])), scala.this.<:<.refl[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]), shapeless.Lazy.apply[io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]](anon$lazy$macro$15.this.inst$macro$14)).asInstanceOf[io.circe.generic.decoding.DerivedDecoder[org.make.api.technical.storage.UploadResponse]]; <stable> <accessor> lazy val inst$macro$14: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ({ final class $anon extends io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] { def <init>(): <$anon: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]> = { $anon.super.<init>(); () }; private[this] val circeGenericDecoderForpath: io.circe.Decoder[String] = circe.this.Decoder.decodeString; final def apply(c: io.circe.HCursor): io.circe.Decoder.Result[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ReprDecoder.consResults[io.circe.Decoder.Result, Symbol @@ String("path"), String, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]($anon.this.circeGenericDecoderForpath.tryDecode(c.downField("path")), ReprDecoder.hnilResult)(io.circe.Decoder.resultInstance); final override def decodeAccumulating(c: io.circe.HCursor): io.circe.Decoder.AccumulatingResult[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out] = ReprDecoder.consResults[io.circe.Decoder.AccumulatingResult, Symbol @@ String("path"), String, shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]($anon.this.circeGenericDecoderForpath.tryDecodeAccumulating(c.downField("path")), ReprDecoder.hnilResultAccumulating)(io.circe.Decoder.accumulatingResultInstance) }; new $anon() }: io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]).asInstanceOf[io.circe.generic.decoding.ReprDecoder[shapeless.labelled.FieldType[Symbol @@ String("path"),String] :: shapeless.ops.hlist.ZipWithKeys.hnilZipWithKeys.Out]] }; new anon$lazy$macro$15().inst$macro$9 }; shapeless.Lazy.apply[io.circe.generic.decoding.DerivedDecoder[org.make.api.technical.storage.UploadResponse]](inst$macro$16) })
66 28888 2200 - 2208 Literal <nosymbol> "Avatar"
67 28518 2241 - 2250 Literal <nosymbol> "avatars"
72 29783 2424 - 2435 Literal <nosymbol> "Operation"
73 29002 2468 - 2488 Literal <nosymbol> "content/operations"
77 30390 2564 - 2570 Literal <nosymbol> "Home"
78 29523 2603 - 2617 Literal <nosymbol> "content/home"
82 29208 2695 - 2703 Literal <nosymbol> "Report"
83 28370 2736 - 2751 Literal <nosymbol> "consultations"
94 29711 3014 - 3021 Select org.make.api.technical.storage.Content.ByteArrayContent.content ByteArrayContent.this.content
101 28935 3284 - 3288 Literal <nosymbol> 2048
102 28526 3308 - 3337 ApplyToImplicitArgs scala.Array.ofDim scala.Array.ofDim[Byte](bufferSize)((ClassTag.Byte: scala.reflect.ClassTag[Byte]))
103 29787 3357 - 3384 Apply java.io.ByteArrayOutputStream.<init> new java.io.ByteArrayOutputStream()
104 29630 3391 - 3391 Block <nosymbol> ()
104 29067 3564 - 3566 Block <nosymbol> { (); while$1() }
104 30385 3391 - 3391 Literal <nosymbol> ()
105 29065 3424 - 3444 Apply java.io.InputStream.read InputStreamContent.this.content.read(buffer)
106 30393 3457 - 3472 Apply scala.Int.!= readBytes.!=(-1)
106 29759 3453 - 3453 Block <nosymbol> ()
106 28372 3453 - 3453 Literal <nosymbol> ()
107 29626 3486 - 3520 Apply java.io.ByteArrayOutputStream.write output.write(buffer, 0, readBytes)
107 29216 3486 - 3520 Block java.io.ByteArrayOutputStream.write output.write(buffer, 0, readBytes)
109 28940 3539 - 3554 Apply scala.Int.!= readBytes.!=(-1)
110 29834 3564 - 3564 Apply org.make.api.technical.storage.Content.InputStreamContent.while$1 while$1()
110 28479 3564 - 3566 Literal <nosymbol> ()
112 29222 3574 - 3592 Apply java.io.ByteArrayOutputStream.toByteArray output.toByteArray()
117 28337 3714 - 3776 Apply org.make.api.technical.storage.Content.InputStreamContent.toByteArray Content.this.InputStreamContent.apply(new java.io.FileInputStream(FileContent.this.content)).toByteArray()
123 29760 3958 - 4012 Apply org.make.api.technical.storage.Content.InputStreamContent.toByteArray Content.this.InputStreamContent.apply(UrlContent.this.content.openStream()).toByteArray()