From 323eaa60a37f109bb529d65ed1b824f69738b20e Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 14 Mar 2021 13:43:23 +0100
Subject: [PATCH] #419 - save image as binary blob serve image base64 encoded

---
 .../budgetmaster/images/Image.java            | 57 ++++++++++++++-----
 .../budgetmaster/utils/Mappings.java          |  1 +
 .../templates/accounts/accountFunctions.ftl   |  2 +-
 .../resources/templates/accounts/accounts.ftl |  2 +-
 .../templates/accounts/newAccount.ftl         |  2 +-
 5 files changed, 48 insertions(+), 16 deletions(-)

diff --git a/src/main/java/de/deadlocker8/budgetmaster/images/Image.java b/src/main/java/de/deadlocker8/budgetmaster/images/Image.java
index 973a25107..6458e49ff 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/images/Image.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/images/Image.java
@@ -2,10 +2,13 @@ package de.deadlocker8.budgetmaster.images;
 
 import com.google.gson.annotations.Expose;
 import de.deadlocker8.budgetmaster.accounts.Account;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.tomcat.util.codec.binary.Base64;
 
 import javax.persistence.*;
 import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
+import java.text.MessageFormat;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -18,16 +21,23 @@ public class Image
 	private Integer ID;
 
 	@NotNull
-	@Size(min = 1)
 	@Expose
-	private String imagePath;
+	@Lob
+	private Byte[] image;
+
+	@NotNull
+	@Expose
+	private String fileExtension;
 
 	@OneToMany(mappedBy = "icon", fetch = FetchType.LAZY)
 	private List<Account> referringAccounts;
 
-	public Image(String imagePath)
+	private static final String BASE_64_IMAGE_FORMAT = "data:image/{0};base64,{1}";
+
+	public Image(@NotNull Byte[] image, String fileExtension)
 	{
-		this.imagePath = imagePath;
+		this.image = image;
+		this.fileExtension = fileExtension;
 	}
 
 	public Image()
@@ -44,14 +54,30 @@ public class Image
 		this.ID = ID;
 	}
 
-	public String getImagePath()
+	public Byte[] getImage()
+	{
+		return image;
+	}
+
+	public void setImage(Byte[] image)
+	{
+		this.image = image;
+	}
+
+	public String getBase64EncodedImage()
+	{
+		final String encoded = Base64.encodeBase64String(ArrayUtils.toPrimitive(this.image));
+		return MessageFormat.format(BASE_64_IMAGE_FORMAT, fileExtension, encoded);
+	}
+
+	public String getFileExtension()
 	{
-		return imagePath;
+		return fileExtension;
 	}
 
-	public void setImagePath(String imagePath)
+	public void setFileExtension(String fileExtension)
 	{
-		this.imagePath = imagePath;
+		this.fileExtension = fileExtension;
 	}
 
 	public List<Account> getReferringAccounts()
@@ -64,7 +90,8 @@ public class Image
 	{
 		return "Image{" +
 				"ID=" + ID +
-				", imagePath='" + imagePath + '\'' +
+				", image='" + image + '\'' +
+				", fileExtension='" + fileExtension + '\'' +
 				'}';
 	}
 
@@ -73,13 +100,17 @@ public class Image
 	{
 		if(this == o) return true;
 		if(o == null || getClass() != o.getClass()) return false;
-		Image image = (Image) o;
-		return Objects.equals(ID, image.ID) && Objects.equals(imagePath, image.imagePath);
+		Image other = (Image) o;
+		return Objects.equals(ID, other.ID) &&
+				Arrays.equals(image, other.image) &&
+				Objects.equals(fileExtension, other.fileExtension);
 	}
 
 	@Override
 	public int hashCode()
 	{
-		return Objects.hash(ID, imagePath);
+		int result = Objects.hash(ID, fileExtension);
+		result = 31 * result + Arrays.hashCode(image);
+		return result;
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java b/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java
index ad3c5dd04..d1b66d84b 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/utils/Mappings.java
@@ -14,6 +14,7 @@ public final class Mappings
 	public static final String ERROR = "/error";
 	public static final String FILTER = "/filter";
 	public static final String HOTKEYS = "/hotkeys";
+	public static final String IMAGES = "/media";
 	public static final String LOGIN = "/login";
 	public static final String REPORTS = "/reports";
 	public static final String SEARCH = "/search";
diff --git a/src/main/resources/templates/accounts/accountFunctions.ftl b/src/main/resources/templates/accounts/accountFunctions.ftl
index 99fabce75..a3ff14c09 100644
--- a/src/main/resources/templates/accounts/accountFunctions.ftl
+++ b/src/main/resources/templates/accounts/accountFunctions.ftl
@@ -19,7 +19,7 @@
 <#macro accountIconOption image>
     <div class="col s4 m2 l2 account-icon-option-column">
         <div class="account-icon-option">
-            <img src="${image.getImagePath()}" class="account-icon-preview" alt="${image.getImagePath()}" data-image-id="${image.getID()}"/>
+            <img src="${image.getBase64EncodedImage()}" class="account-icon-preview" data-image-id="${image.getID()}"/>
         </div>
     </div>
 </#macro>
\ No newline at end of file
diff --git a/src/main/resources/templates/accounts/accounts.ftl b/src/main/resources/templates/accounts/accounts.ftl
index f8256aeff..0f7d695ab 100644
--- a/src/main/resources/templates/accounts/accounts.ftl
+++ b/src/main/resources/templates/accounts/accounts.ftl
@@ -41,7 +41,7 @@
                                             <a href="<@s.url '/accounts/${account.getID()?c}/toggleReadOnly'/>" class="btn-flat no-padding text-default tooltipped" data-position="right" data-tooltip="${toolTipText}">${lockIcon}</a>
                                         </#if>
                                     </td>
-                                    <td><#if account.getIcon()??><img src="${account.getIcon().getImagePath()}" class="account-icon"/></#if></td>
+                                    <td><#if account.getIcon()??><img src="${account.getIcon().getBase64EncodedImage()}" class="account-icon"/></#if></td>
                                     <td>${account.getName()}</td>
                                     <td>
                                         <a href="<@s.url '/accounts/${account.getID()?c}/edit'/>" class="btn-flat no-padding text-default"><i class="material-icons left">edit</i></a>
diff --git a/src/main/resources/templates/accounts/newAccount.ftl b/src/main/resources/templates/accounts/newAccount.ftl
index 83b2aa607..674668fd6 100644
--- a/src/main/resources/templates/accounts/newAccount.ftl
+++ b/src/main/resources/templates/accounts/newAccount.ftl
@@ -54,7 +54,7 @@
 
                                 <div id="account-icon" class="valign-wrapper">
                                     <a href="#modalAccountIconSelect" id="account-icon-preview" class="modal-trigger">
-                                        <img id="account-icon-preview-icon" src="<#if account.getIcon()??>${account.getIcon().getImagePath()}</#if>" class="account-icon-preview <#if account.getIcon()?? == false>hidden</#if>"/>
+                                        <img id="account-icon-preview-icon" src="<#if account.getIcon()??>${account.getIcon().getBase64EncodedImage()}</#if>" class="account-icon-preview <#if account.getIcon()?? == false>hidden</#if>"/>
                                         <div id="account-icon-placeholder" class="<#if account.getIcon()??>hidden</#if>">${locale.getString("account.new.icon.placeholder")}</div>
                                     </a>
                                     <@header.buttonFlat url='' icon='delete' id='button-remove-account-icon' localizationKey='' classes="no-padding text-default" noUrl=true/>
-- 
GitLab