/*
* Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.juick.server.api;
import com.juick.model.User;
import com.juick.model.AnonymousUser;
import com.juick.model.ApplicationStatus;
import com.juick.server.util.HttpBadRequestException;
import com.juick.server.util.HttpNotFoundException;
import com.juick.server.util.HttpUtils;
import com.juick.server.util.WebUtils;
import com.juick.www.WebApp;
import com.juick.service.*;
import com.juick.service.component.MailVerificationEvent;
import com.juick.service.security.annotation.Visitor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.inject.Inject;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author ugnich
*/
@RestController
public class Users {
@Inject
private UserService userService;
@Inject
private MessagesService messagesService;
@Inject
private CrosspostService crosspostService;
@Inject
private TelegramService telegramService;
@Inject
private EmailService emailService;
@Inject
private TagService tagService;
@Inject
private WebApp webApp;
@Inject
private ImagesService imagesService;
@Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
private String tmpDir;
@Inject
private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping(value = "/api/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public String getAuthToken(@Visitor User visitor) {
return userService.getHashByUID(visitor.getUid());
}
@RequestMapping(value = "/api/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetUsers(
@Visitor User visitor,
@RequestParam(value = "uname", required = false) List unames) {
List users = new ArrayList<>();
if (unames != null) {
unames.removeIf(WebUtils::isNotUserName);
if (!unames.isEmpty() && unames.size() < 20)
users.addAll(userService.getUsersByName(unames));
}
users.forEach(u -> u.setAvatar(webApp.getAvatarUrl(u)));
if (!users.isEmpty())
return users;
if (!visitor.isAnonymous()) {
visitor.setAvatar(webApp.getAvatarUrl(visitor));
return Collections.singletonList(visitor);
}
throw new HttpNotFoundException();
}
@GetMapping("/api/me")
public SecureUser getMe(@Visitor User visitor) {
SecureUser me = new SecureUser();
me.setUid(visitor.getUid());
me.setName(visitor.getName());
me.setAuthHash(getAuthToken(visitor));
List unread = messagesService.getUnread(visitor);
me.setUnread(unread);
me.setUnreadCount(unread.size());
var friends = userService.getUserFriends(visitor.getUid());
friends.forEach(r -> r.setAvatar(webApp.getAvatarUrl(r)));
me.setRead(friends);
var readers = userService.getUserReaders(visitor.getUid());
readers.forEach(r -> r.setAvatar(webApp.getAvatarUrl(r)));
me.setReaders(readers);
me.setAvatar(webApp.getAvatarUrl(visitor));
me.getTagStats().addAll(tagService.getUserTagStats(me.getUid()));
return (SecureUser)userService.getUserInfo(me);
}
@PostMapping("/api/me")
public ResponseEntity updateMe(@Visitor User visitor,
@RequestParam(required = false) String password,
@RequestParam(value = "jid-del", required = false) String jidForDeletion,
@RequestParam(value = "email-add", required = false) String newEmail,
@RequestParam(value = "email-del", required = false) String emailForDeletion,
@RequestParam(value = "account-del", required = false) String accountToDelete) {
if (StringUtils.isNotEmpty(password)) {
if (!userService.updatePassword(visitor, password)) {
throw new HttpBadRequestException();
}
}
if (StringUtils.isNotEmpty(jidForDeletion)) {
if (!userService.deleteJID(visitor.getUid(), jidForDeletion)) {
throw new HttpBadRequestException();
}
}
if (StringUtils.isNotEmpty(newEmail)) {
if (!emailService.verifyAddressByCode(visitor.getUid(), newEmail)) {
String authCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase();
if (emailService.addVerificationCode(visitor.getUid(), newEmail, authCode)) {
applicationEventPublisher.publishEvent(new MailVerificationEvent(this, newEmail , authCode));
}
}
}
if (StringUtils.isNotEmpty(emailForDeletion)) {
if (!emailService.deleteEmail(visitor.getUid(), emailForDeletion)) {
throw new HttpBadRequestException();
}
}
if (StringUtils.isNotEmpty(accountToDelete)) {
switch (accountToDelete) {
case "twitter":
crosspostService.deleteTwitterToken(visitor.getUid());
break;
case "vk":
crosspostService.deleteVKUser(visitor.getUid());
break;
case "durov":
telegramService.deleteTelegramUser(visitor.getUid());
break;
}
}
return ResponseEntity.ok().build();
}
@PostMapping("/api/me/subscribe")
public ResponseEntity subscribeMe(@Visitor User visitor, String email) {
// TODO: check status
emailService.setNotificationsEmail(visitor.getUid(), email);
return ResponseEntity.ok().build();
}
@PostMapping("/api/me/upload")
public void updateInfo(@Visitor User visitor,
@RequestParam MultipartFile avatar) throws IOException {
String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, tmpDir).getHost();
if (StringUtils.isNotEmpty(avatarTmpPath)) {
imagesService.saveAvatar(avatarTmpPath, visitor.getUid());
}
}
@RequestMapping(value = "/api/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetUserRead(
@Visitor User visitor,
@RequestParam String uname) {
int uid = 0;
if (uname == null) {
uid = visitor.getUid();
} else {
if (WebUtils.isUserName(uname)) {
User u = userService.getUserByName(uname);
if (!u.isAnonymous()) {
uid = u.getUid();
}
}
}
if (uid > 0) {
List friends = userService.getUserFriends(uid);
friends.forEach(f -> f.setAvatar(webApp.getAvatarUrl(f)));
return friends;
}
throw new HttpNotFoundException();
}
@RequestMapping(value = "/api/users/readers", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List doGetUserReaders(
@Visitor User visitor,
@RequestParam String uname) {
int uid = 0;
if (uname == null) {
uid = visitor.getUid();
} else {
if (WebUtils.isUserName(uname)) {
User u = userService.getUserByName(uname);
if (!u.isAnonymous()) {
uid = u.getUid();
}
}
}
if (uid > 0) {
List readers = userService.getUserReaders(uid);
readers.forEach(r -> r.setAvatar(webApp.getAvatarUrl(r)));
return readers;
}
throw new HttpNotFoundException();
}
@GetMapping("/api/info/{uname}")
public User getUserInfo(@Visitor User visitor, @PathVariable String uname) {
User user = userService.getUserByName(uname);
if (!user.isBanned()) {
user.setRead(doGetUserRead(visitor, uname));
user.setReaders(doGetUserReaders(visitor, uname));
user.setAvatar(webApp.getAvatarUrl(user));
return userService.getUserInfo(user);
}
throw new HttpNotFoundException();
}
@Deprecated
@GetMapping(value = "/api/avatar", produces = MediaType.IMAGE_PNG_VALUE)
public byte[] getAvatarUrl(
@RequestParam(required = false) String uname,
@RequestParam(required = false) String jid)
throws IOException {
User user = AnonymousUser.INSTANCE;
if (StringUtils.isNotEmpty(uname)) {
user = userService.getUserByName(uname);
}
if (user.isAnonymous() && StringUtils.isNotEmpty(jid)) {
user = userService.getUserByJID(jid);
}
return IOUtils.toByteArray(URI.create(webApp.getAvatarUrl(user)));
}
public class SecureUser extends User {
public String getHash() {
return getAuthHash();
}
public List getJIDs() {
return userService.getAllJIDs(this);
}
public List getEmails() {
return userService.getEmails(this);
}
public String getActiveEmail() {
return emailService.getNotificationsEmail(this.getUid());
}
public String getTwitterName() {
return crosspostService.getTwitterName(this.getUid());
}
public String getTelegramName() {
return crosspostService.getTelegramName(this.getUid());
}
public ApplicationStatus getFacebookStatus() {
return crosspostService.getFbCrossPostStatus(this.getUid());
}
}
}