Skip to content

Commit 610c7a3

Browse files
committed
FINERACT-1878: Separate profile image paths for staff and clients
1 parent 8a61e30 commit 610c7a3

File tree

7 files changed

+304
-20
lines changed

7 files changed

+304
-20
lines changed

fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/ContentRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ public interface ContentRepository {
4343

4444
FileData fetchFile(DocumentData documentData);
4545

46-
String saveImage(InputStream uploadedInputStream, Long resourceId, String imageName, Long fileSize);
46+
String saveImage(InputStream uploadedInputStream, String entityName, Long resourceId, String imageName, Long fileSize);
4747

48-
String saveImage(Base64EncodedImage base64EncodedImage, Long resourceId, String imageName);
48+
String saveImage(Base64EncodedImage base64EncodedImage, String entityName, Long resourceId, String imageName);
4949

5050
void deleteImage(String location);
5151

fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,17 @@ public String saveFile(final InputStream uploadedInputStream, final DocumentComm
6161
}
6262

6363
@Override
64-
public String saveImage(final InputStream uploadedInputStream, final Long resourceId, final String imageName, final Long fileSize) {
64+
public String saveImage(final InputStream uploadedInputStream, final String entityName, final Long resourceId, final String imageName,
65+
final Long fileSize) {
6566
ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(fileSize, imageName);
66-
final String fileLocation = generateClientImageParentDirectory(resourceId) + File.separator + imageName;
67+
final String fileLocation = generateImageParentDirectory(entityName, resourceId) + File.separator + imageName;
6768
return writeFileToFileSystem(imageName, uploadedInputStream, fileLocation);
6869
}
6970

7071
@Override
71-
public String saveImage(final Base64EncodedImage base64EncodedImage, final Long resourceId, final String imageName) {
72-
final String fileLocation = generateClientImageParentDirectory(resourceId) + File.separator + imageName
72+
public String saveImage(final Base64EncodedImage base64EncodedImage, final String entityName, final Long resourceId,
73+
final String imageName) {
74+
final String fileLocation = generateImageParentDirectory(entityName, resourceId) + File.separator + imageName
7375
+ base64EncodedImage.getFileExtension();
7476
String base64EncodedImageString = base64EncodedImage.getBase64EncodedString();
7577
try {
@@ -136,10 +138,11 @@ private String generateFileParentDirectory(final String entityType, final Long e
136138
* Generate ContentRepositoryUtilsfineractProperties.getContentgetWhitelist()getBlacklist() path for storing new
137139
* Image
138140
*/
139-
private String generateClientImageParentDirectory(final Long resourceId) {
141+
private String generateImageParentDirectory(final String entityName, final Long resourceId) {
142+
final String imageEntityType = "staff".equalsIgnoreCase(entityName) ? "staff" : "clients";
140143
return fineractProperties.getContent().getFilesystem().getRootFolder() + File.separator
141144
+ ThreadLocalContextUtil.getTenant().getName().replace(" ", "").trim() + File.separator + "images" + File.separator
142-
+ "clients" + File.separator + resourceId;
145+
+ imageEntityType + File.separator + resourceId;
143146
}
144147

145148
/**

fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/S3ContentRepository.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,20 @@ public void deleteFile(final String documentPath) {
7676
}
7777

7878
@Override
79-
public String saveImage(final InputStream toUploadInputStream, final Long resourceId, final String imageName, final Long fileSize) {
79+
public String saveImage(final InputStream toUploadInputStream, final String entityName, final Long resourceId, final String imageName,
80+
final Long fileSize) {
8081
ContentRepositoryUtils.validateFileSizeWithinPermissibleRange(fileSize, imageName);
81-
final String uploadImageLocation = generateClientImageParentDirectory(resourceId);
82+
final String uploadImageLocation = generateImageParentDirectory(entityName, resourceId);
8283
final String fileLocation = uploadImageLocation + File.separator + imageName;
8384

8485
putObject(imageName, toUploadInputStream, fileLocation);
8586
return fileLocation;
8687
}
8788

8889
@Override
89-
public String saveImage(final Base64EncodedImage base64EncodedImage, final Long resourceId, final String imageName) {
90-
final String uploadImageLocation = generateClientImageParentDirectory(resourceId);
90+
public String saveImage(final Base64EncodedImage base64EncodedImage, final String entityName, final Long resourceId,
91+
final String imageName) {
92+
final String uploadImageLocation = generateImageParentDirectory(entityName, resourceId);
9193
final String fileLocation = uploadImageLocation + File.separator + imageName + base64EncodedImage.getFileExtension();
9294
final InputStream toUploadInputStream = new ByteArrayInputStream(
9395
Base64.getMimeDecoder().decode(base64EncodedImage.getBase64EncodedString()));
@@ -135,8 +137,9 @@ private String generateFileParentDirectory(final String entityType, final Long e
135137
+ ContentRepositoryUtils.generateRandomString();
136138
}
137139

138-
private String generateClientImageParentDirectory(final Long resourceId) {
139-
return "images" + File.separator + "clients" + File.separator + resourceId;
140+
private String generateImageParentDirectory(final String entityName, final Long resourceId) {
141+
final String imageEntityType = "staff".equalsIgnoreCase(entityName) ? "staff" : "clients";
142+
return "images" + File.separator + imageEntityType + File.separator + resourceId;
140143
}
141144

142145
private void deleteObject(final String location) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.infrastructure.documentmanagement.contentrepository;
20+
21+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import static org.mockito.ArgumentMatchers.any;
24+
import static org.mockito.ArgumentMatchers.anyString;
25+
import static org.mockito.Mockito.when;
26+
27+
import java.io.BufferedInputStream;
28+
import java.io.ByteArrayInputStream;
29+
import java.io.File;
30+
import java.nio.file.Path;
31+
import org.apache.fineract.infrastructure.core.config.FineractProperties;
32+
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
33+
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
34+
import org.junit.jupiter.api.AfterEach;
35+
import org.junit.jupiter.api.BeforeEach;
36+
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.io.TempDir;
38+
import org.mockito.Mockito;
39+
40+
class FileSystemContentRepositoryTest {
41+
42+
@TempDir
43+
Path tempDir;
44+
45+
private FileSystemContentRepository repository;
46+
47+
@BeforeEach
48+
void setUp() {
49+
ThreadLocalContextUtil.setTenant(FineractPlatformTenant.builder().name("default").build());
50+
51+
FileSystemContentPathSanitizer pathSanitizer = Mockito.mock(FileSystemContentPathSanitizer.class);
52+
when(pathSanitizer.sanitize(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
53+
when(pathSanitizer.sanitize(anyString(), any(BufferedInputStream.class))).thenAnswer(invocation -> invocation.getArgument(0));
54+
55+
FineractProperties properties = new FineractProperties();
56+
FineractProperties.FineractContentProperties contentProperties = new FineractProperties.FineractContentProperties();
57+
FineractProperties.FineractContentFilesystemProperties filesystemProperties = new FineractProperties.FineractContentFilesystemProperties();
58+
filesystemProperties.setRootFolder(tempDir.toString());
59+
contentProperties.setFilesystem(filesystemProperties);
60+
properties.setContent(contentProperties);
61+
62+
repository = new FileSystemContentRepository(pathSanitizer, properties);
63+
}
64+
65+
@AfterEach
66+
void tearDown() {
67+
ThreadLocalContextUtil.reset();
68+
}
69+
70+
@Test
71+
void saveImageShouldStoreClientAndStaffImagesInDifferentDirectoriesForSameId() {
72+
Long resourceId = 7L;
73+
String imageName = "profile.png";
74+
byte[] content = new byte[] { 1, 2, 3 };
75+
76+
String clientLocation = repository.saveImage(new ByteArrayInputStream(content), "clients", resourceId, imageName,
77+
Long.valueOf(content.length));
78+
String staffLocation = repository.saveImage(new ByteArrayInputStream(content), "staff", resourceId, imageName,
79+
Long.valueOf(content.length));
80+
81+
assertTrue(clientLocation.contains(File.separator + "images" + File.separator + "clients" + File.separator + resourceId
82+
+ File.separator + imageName));
83+
assertTrue(staffLocation.contains(File.separator + "images" + File.separator + "staff" + File.separator + resourceId
84+
+ File.separator + imageName));
85+
assertNotEquals(clientLocation, staffLocation);
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.infrastructure.documentmanagement.contentrepository;
20+
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.ArgumentMatchers.anyString;
24+
import static org.mockito.ArgumentMatchers.eq;
25+
import static org.mockito.Mockito.doNothing;
26+
import static org.mockito.Mockito.verify;
27+
28+
import java.io.ByteArrayInputStream;
29+
import java.io.File;
30+
import java.io.InputStream;
31+
import org.apache.fineract.infrastructure.core.config.FineractProperties;
32+
import org.junit.jupiter.api.BeforeEach;
33+
import org.junit.jupiter.api.Test;
34+
import org.mockito.Mockito;
35+
import software.amazon.awssdk.services.s3.S3Client;
36+
37+
class S3ContentRepositoryTest {
38+
39+
private S3ContentRepository repository;
40+
41+
@BeforeEach
42+
void setUp() {
43+
S3Client s3Client = Mockito.mock(S3Client.class);
44+
FineractProperties properties = new FineractProperties();
45+
FineractProperties.FineractContentProperties contentProperties = new FineractProperties.FineractContentProperties();
46+
FineractProperties.FineractContentS3Properties s3Properties = new FineractProperties.FineractContentS3Properties();
47+
s3Properties.setBucketName("bucket");
48+
contentProperties.setS3(s3Properties);
49+
properties.setContent(contentProperties);
50+
51+
repository = Mockito.spy(new S3ContentRepository(s3Client, properties));
52+
doNothing().when(repository).putObject(anyString(), any(InputStream.class), anyString());
53+
}
54+
55+
@Test
56+
void saveImageShouldStoreClientImageUnderClientsDirectory() {
57+
byte[] content = new byte[] { 1, 2, 3 };
58+
String imageName = "image.png";
59+
Long entityId = 10L;
60+
61+
String location = repository.saveImage(new ByteArrayInputStream(content), "clients", entityId, imageName,
62+
Long.valueOf(content.length));
63+
64+
String expectedPath = "images" + File.separator + "clients" + File.separator + entityId + File.separator + imageName;
65+
assertTrue(location.endsWith(expectedPath));
66+
verify(repository).putObject(eq(imageName), any(InputStream.class), eq(location));
67+
}
68+
69+
@Test
70+
void saveImageShouldStoreStaffImageUnderStaffDirectory() {
71+
byte[] content = new byte[] { 1, 2, 3 };
72+
String imageName = "image.png";
73+
Long entityId = 10L;
74+
75+
String location = repository.saveImage(new ByteArrayInputStream(content), "STAFF", entityId, imageName,
76+
Long.valueOf(content.length));
77+
78+
String expectedPath = "images" + File.separator + "staff" + File.separator + entityId + File.separator + imageName;
79+
assertTrue(location.endsWith(expectedPath));
80+
verify(repository).putObject(eq(imageName), any(InputStream.class), eq(location));
81+
}
82+
}

fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceJpaRepositoryImpl.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,22 @@ public ImageWritePlatformServiceJpaRepositoryImpl(final ContentRepositoryFactory
5555

5656
@Transactional
5757
@Override
58-
public CommandProcessingResult saveOrUpdateImage(String entityName, final Long clientId, final String imageName,
58+
public CommandProcessingResult saveOrUpdateImage(String entityName, final Long entityId, final String imageName,
5959
final InputStream inputStream, final Long fileSize) {
60-
Object owner = deletePreviousImage(entityName, clientId);
60+
Object owner = deletePreviousImage(entityName, entityId);
6161

6262
final ContentRepository contentRepository = this.contentRepositoryFactory.getRepository();
63-
final String imageLocation = contentRepository.saveImage(inputStream, clientId, imageName, fileSize);
63+
final String imageLocation = contentRepository.saveImage(inputStream, entityName, entityId, imageName, fileSize);
6464
return updateImage(owner, imageLocation, contentRepository.getStorageType());
6565
}
6666

6767
@Transactional
6868
@Override
69-
public CommandProcessingResult saveOrUpdateImage(String entityName, final Long clientId, final Base64EncodedImage encodedImage) {
70-
Object owner = deletePreviousImage(entityName, clientId);
69+
public CommandProcessingResult saveOrUpdateImage(String entityName, final Long entityId, final Base64EncodedImage encodedImage) {
70+
Object owner = deletePreviousImage(entityName, entityId);
7171

7272
final ContentRepository contenRepository = this.contentRepositoryFactory.getRepository();
73-
final String imageLocation = contenRepository.saveImage(encodedImage, clientId, "image");
73+
final String imageLocation = contenRepository.saveImage(encodedImage, entityName, entityId, "image");
7474

7575
return updateImage(owner, imageLocation, contenRepository.getStorageType());
7676
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.infrastructure.documentmanagement.service;
20+
21+
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.ArgumentMatchers.eq;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.verifyNoInteractions;
26+
import static org.mockito.Mockito.when;
27+
28+
import java.io.ByteArrayInputStream;
29+
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
30+
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepository;
31+
import org.apache.fineract.infrastructure.documentmanagement.contentrepository.ContentRepositoryFactory;
32+
import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
33+
import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository;
34+
import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
35+
import org.apache.fineract.organisation.staff.domain.Staff;
36+
import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
37+
import org.apache.fineract.portfolio.client.domain.Client;
38+
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
39+
import org.junit.jupiter.api.Test;
40+
import org.junit.jupiter.api.extension.ExtendWith;
41+
import org.mockito.InjectMocks;
42+
import org.mockito.Mock;
43+
import org.mockito.junit.jupiter.MockitoExtension;
44+
45+
@ExtendWith(MockitoExtension.class)
46+
class ImageWritePlatformServiceJpaRepositoryImplTest {
47+
48+
@Mock
49+
private ContentRepositoryFactory contentRepositoryFactory;
50+
@Mock
51+
private ClientRepositoryWrapper clientRepositoryWrapper;
52+
@Mock
53+
private ImageRepository imageRepository;
54+
@Mock
55+
private StaffRepositoryWrapper staffRepositoryWrapper;
56+
@Mock
57+
private ContentRepository contentRepository;
58+
@Mock
59+
private Staff staff;
60+
@Mock
61+
private Client client;
62+
63+
@InjectMocks
64+
private ImageWritePlatformServiceJpaRepositoryImpl service;
65+
66+
@Test
67+
void saveOrUpdateImageShouldUseStaffEntityNameWhenSavingStaffImage() {
68+
Long entityId = 9L;
69+
when(staffRepositoryWrapper.findOneWithNotFoundDetection(entityId)).thenReturn(staff);
70+
when(contentRepositoryFactory.getRepository()).thenReturn(contentRepository);
71+
when(contentRepository.getStorageType()).thenReturn(StorageType.FILE_SYSTEM);
72+
when(contentRepository.saveImage(any(ByteArrayInputStream.class), eq("staff"), eq(entityId), eq("profile.png"), eq(3L)))
73+
.thenReturn("images/staff/9/profile.png");
74+
when(staff.getId()).thenReturn(entityId);
75+
when(staff.getImage()).thenReturn(null);
76+
77+
CommandProcessingResult result = service.saveOrUpdateImage("staff", entityId, "profile.png", new ByteArrayInputStream(new byte[] { 1,
78+
2, 3 }), 3L);
79+
80+
verify(contentRepository).saveImage(any(ByteArrayInputStream.class), eq("staff"), eq(entityId), eq("profile.png"), eq(3L));
81+
verify(staff).setImage(any(Image.class));
82+
verify(staffRepositoryWrapper).save(staff);
83+
verify(imageRepository).save(any(Image.class));
84+
verifyNoInteractions(clientRepositoryWrapper);
85+
assertEquals(entityId, result.getResourceId());
86+
}
87+
88+
@Test
89+
void saveOrUpdateImageShouldUseClientEntityNameWhenSavingClientImage() {
90+
Long entityId = 11L;
91+
when(clientRepositoryWrapper.findOneWithNotFoundDetection(entityId)).thenReturn(client);
92+
when(contentRepositoryFactory.getRepository()).thenReturn(contentRepository);
93+
when(contentRepository.getStorageType()).thenReturn(StorageType.FILE_SYSTEM);
94+
when(contentRepository.saveImage(any(ByteArrayInputStream.class), eq("clients"), eq(entityId), eq("profile.png"), eq(3L)))
95+
.thenReturn("images/clients/11/profile.png");
96+
when(client.getId()).thenReturn(entityId);
97+
when(client.getImage()).thenReturn(null);
98+
99+
CommandProcessingResult result = service.saveOrUpdateImage("clients", entityId, "profile.png",
100+
new ByteArrayInputStream(new byte[] { 1, 2, 3 }), 3L);
101+
102+
verify(contentRepository).saveImage(any(ByteArrayInputStream.class), eq("clients"), eq(entityId), eq("profile.png"), eq(3L));
103+
verify(client).setImage(any(Image.class));
104+
verify(clientRepositoryWrapper).save(client);
105+
verify(imageRepository).save(any(Image.class));
106+
verifyNoInteractions(staffRepositoryWrapper);
107+
assertEquals(entityId, result.getResourceId());
108+
}
109+
}

0 commit comments

Comments
 (0)