Skip to content

Commit e3c8540

Browse files
committed
Avatars
1 parent 5838593 commit e3c8540

46 files changed

Lines changed: 2274 additions & 36 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,22 @@
157157
<version>1.8.1</version>
158158
</dependency>
159159

160+
<!-- File uploading -->
161+
162+
<dependency>
163+
<groupId>commons-fileupload</groupId>
164+
<artifactId>commons-fileupload</artifactId>
165+
<version>1.3.1</version>
166+
</dependency>
167+
168+
<!-- Image scaling -->
169+
170+
<dependency>
171+
<groupId>org.imgscalr</groupId>
172+
<artifactId>imgscalr-lib</artifactId>
173+
<version>4.2</version>
174+
</dependency>
175+
160176
<!-- testing -->
161177

162178
<dependency>

src/main/java/alexp/blog/controller/PostsController.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import alexp.blog.model.PostEditDto;
66
import alexp.blog.service.PostService;
77
import alexp.blog.service.UserService;
8+
import alexp.blog.utils.JsonUtils;
89
import org.springframework.beans.factory.annotation.Autowired;
910
import org.springframework.data.domain.Page;
1011
import org.springframework.security.access.prepost.PreAuthorize;
@@ -165,10 +166,6 @@ public String updatePost(ModelMap model, @Valid @ModelAttribute("post") PostEdit
165166
}
166167

167168
private String toJsonLink(Post post) {
168-
return "{" + toJsonField("id", post.getId().toString()) + ", " + toJsonField("title", post.getTitle()) + "}";
169-
}
170-
171-
private String toJsonField(String name, String value) {
172-
return "\"" + name + "\":\"" + value + "\"";
169+
return "{" + JsonUtils.toJsonField("id", post.getId().toString()) + ", " + JsonUtils.toJsonField("title", post.getTitle()) + "}";
173170
}
174171
}

src/main/java/alexp/blog/controller/UsersController.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package alexp.blog.controller;
22

33
import alexp.blog.model.User;
4-
import alexp.blog.service.AuthException;
5-
import alexp.blog.service.UserService;
4+
import alexp.blog.service.*;
5+
import alexp.blog.utils.JsonUtils;
66
import org.springframework.beans.factory.annotation.Autowired;
77
import org.springframework.security.access.prepost.PreAuthorize;
88
import org.springframework.stereotype.Controller;
@@ -12,17 +12,22 @@
1212
import org.springframework.validation.Validator;
1313
import org.springframework.validation.annotation.Validated;
1414
import org.springframework.web.bind.annotation.*;
15+
import org.springframework.web.multipart.MultipartFile;
1516
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
1617

1718
import javax.servlet.http.HttpServletRequest;
1819
import javax.servlet.http.HttpSession;
20+
import java.io.IOException;
1921

2022
@Controller
2123
public class UsersController {
2224

2325
@Autowired
2426
private UserService userService;
2527

28+
@Autowired
29+
private AvatarService avatarService;
30+
2631
@Autowired
2732
private Validator userValidator;
2833

@@ -160,8 +165,12 @@ public String showEditProfilePage(ModelMap model) {
160165
@PreAuthorize("hasRole('ROLE_USER')")
161166
@RequestMapping(value = "/edit_profile", method = RequestMethod.POST)
162167
public String editProfile(@Validated({User.ProfileInfoValidationGroup.class}) @ModelAttribute(value = "user") User user, BindingResult result,
163-
RedirectAttributes redirectAttributes, ModelMap model) {
168+
RedirectAttributes redirectAttributes) {
164169
if (result.hasErrors()) {
170+
// quick workaround to show avatar
171+
User currentUser = userService.currentUser();
172+
user.setBigAvatarLink(currentUser.getBigAvatarLink());
173+
165174
return "editprofile";
166175
}
167176

@@ -172,6 +181,28 @@ public String editProfile(@Validated({User.ProfileInfoValidationGroup.class}) @M
172181
return "redirect:/edit_profile";
173182
}
174183

184+
@PreAuthorize("hasRole('ROLE_USER')")
185+
@RequestMapping(value = "/upload_avatar", method = RequestMethod.POST)
186+
public @ResponseBody String uploadAvatar(@RequestParam("avatarFile") MultipartFile file) throws IOException {
187+
try {
188+
UploadedAvatarInfo result = avatarService.upload(file);
189+
190+
userService.changeAvatar(result);
191+
192+
return makeAvatarUploadResponse("ok", result);
193+
} catch (UnsupportedFormatException e) {
194+
return makeAvatarUploadResponse("invalid_format", null);
195+
}
196+
}
197+
198+
@PreAuthorize("hasRole('ROLE_USER')")
199+
@RequestMapping(value = "/remove_avatar", method = RequestMethod.POST)
200+
public @ResponseBody String removeAvatar() throws IOException {
201+
userService.removeAvatar();
202+
203+
return "ok";
204+
}
205+
175206
@RequestMapping(value = "/users/{username}", method = RequestMethod.GET)
176207
public String showProfile(@PathVariable("username") String username, ModelMap model) {
177208
User user = userService.findByUsername(username);
@@ -183,4 +214,10 @@ public String showProfile(@PathVariable("username") String username, ModelMap mo
183214

184215
return "profile";
185216
}
217+
218+
private String makeAvatarUploadResponse(String status, UploadedAvatarInfo uploadedAvatarInfo) {
219+
return "{" + JsonUtils.toJsonField("status", status) +
220+
(uploadedAvatarInfo == null ? "" : (", " + JsonUtils.toJsonField("link", uploadedAvatarInfo.bigImageLink))) +
221+
"}";
222+
}
186223
}

src/main/java/alexp/blog/model/User.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ public interface ProfileInfoValidationGroup {}
7676
@Size(max = 80, groups = {ProfileInfoValidationGroup.class})
7777
public String websiteLink;
7878

79+
@Column(nullable = true)
80+
public String smallAvatarLink;
81+
82+
@Column(nullable = true)
83+
public String bigAvatarLink;
84+
7985
public Long getId() {
8086
return Id;
8187
}
@@ -165,6 +171,22 @@ public void setWebsiteLink(String websiteLink) {
165171
this.websiteLink = websiteLink;
166172
}
167173

174+
public String getSmallAvatarLink() {
175+
return smallAvatarLink;
176+
}
177+
178+
public void setSmallAvatarLink(String smallAvatarLink) {
179+
this.smallAvatarLink = smallAvatarLink;
180+
}
181+
182+
public String getBigAvatarLink() {
183+
return bigAvatarLink;
184+
}
185+
186+
public void setBigAvatarLink(String bigAvatarLink) {
187+
this.bigAvatarLink = bigAvatarLink;
188+
}
189+
168190
public boolean hasRole(String role) {
169191
role = role.toUpperCase();
170192

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package alexp.blog.service;
2+
3+
import org.springframework.web.multipart.MultipartFile;
4+
5+
import java.io.IOException;
6+
7+
public interface AvatarService {
8+
9+
UploadedAvatarInfo upload(MultipartFile file) throws IOException, UnsupportedFormatException;
10+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package alexp.blog.service;
2+
3+
import org.apache.commons.io.FilenameUtils;
4+
import org.imgscalr.Scalr;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.web.multipart.MultipartFile;
9+
10+
import javax.imageio.ImageIO;
11+
import java.awt.image.BufferedImage;
12+
import java.io.*;
13+
import java.lang.reflect.Array;
14+
import java.util.Arrays;
15+
import java.util.List;
16+
17+
@Service("avatarService")
18+
public class AvatarServiceImpl implements AvatarService {
19+
20+
@Value("${uploading.dirpath}")
21+
private String uploadingDirPath;
22+
23+
@Autowired
24+
private FileNameGenerator fileNameGenerator;
25+
26+
public AvatarServiceImpl() {
27+
ImageIO.setUseCache(false);
28+
}
29+
30+
public final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png");
31+
32+
@Override
33+
public UploadedAvatarInfo upload(MultipartFile file) throws IOException, UnsupportedFormatException {
34+
String fileName = file.getOriginalFilename();
35+
String ext = FilenameUtils.getExtension(fileName).toLowerCase();
36+
37+
if (!SUPPORTED_EXTENSIONS.contains(ext))
38+
throw new UnsupportedFormatException(fileName);
39+
40+
String name = fileNameGenerator.getFileName(fileName, "avatar");
41+
String bigImageName = name + "_big" + "." + ext;
42+
String smallImageName = name + "_small" + "." + ext;
43+
44+
BufferedImage image = ImageIO.read(file.getInputStream());
45+
46+
BufferedImage bigImage = resize(image, 160);
47+
48+
new File(uploadingDirPath).mkdirs();
49+
50+
ImageIO.write(bigImage, ext, new File(uploadingDirPath + bigImageName));
51+
52+
BufferedImage smallImage = resize(image, 28);
53+
54+
ImageIO.write(smallImage, ext, new File(uploadingDirPath + smallImageName));
55+
56+
return new UploadedAvatarInfo(bigImageName, smallImageName);
57+
}
58+
59+
private BufferedImage resize(BufferedImage image, int size) {
60+
BufferedImage result = Scalr.resize(image, Scalr.Mode.FIT_EXACT, size, size);
61+
62+
// would be better to crop image if not square, instead of just resizing without preserving proportions
63+
64+
return result;
65+
}
66+
67+
public FileNameGenerator getFileNameGenerator() {
68+
return fileNameGenerator;
69+
}
70+
71+
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
72+
this.fileNameGenerator = fileNameGenerator;
73+
}
74+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package alexp.blog.service;
2+
3+
public interface FileNameGenerator {
4+
5+
String getFileName(String filename, String prefix);
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package alexp.blog.service;
2+
3+
import org.apache.commons.io.FilenameUtils;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service("fileNameGenerator")
7+
public class FileNameGeneratorImpl implements FileNameGenerator {
8+
@Override
9+
public String getFileName(String filename, String prefix) {
10+
return prefix + filename.hashCode() + System.currentTimeMillis();
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package alexp.blog.service;
2+
3+
public class UnsupportedFormatException extends Exception {
4+
5+
public UnsupportedFormatException(String message) {
6+
super(message);
7+
}
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package alexp.blog.service;
2+
3+
public class UploadedAvatarInfo {
4+
public final String bigImageLink;
5+
6+
public final String smallImageLink;
7+
8+
public UploadedAvatarInfo(String bigImageLink, String smallImageLink) {
9+
this.bigImageLink = bigImageLink;
10+
this.smallImageLink = smallImageLink;
11+
}
12+
}

0 commit comments

Comments
 (0)