/*
* Copyright (C) 2008-2023, 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 <http://www.gnu.org/licenses/>.
*/
package com.juick.www.controllers;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.stream.IntStream;
import javax.inject.Inject;
import com.juick.model.User;
import com.juick.service.EmailService;
import com.juick.service.StorageService;
import com.juick.service.TagService;
import com.juick.service.TelegramService;
import com.juick.service.UserService;
import com.juick.service.activities.UpdateUserEvent;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpUtils;
import com.juick.www.WebApp;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
/**
*
* @author Ugnich Anton
*/
@Controller
public class Settings {
private static final Logger logger = LoggerFactory.getLogger(Settings.class);
@Inject
private TagService tagService;
@Inject
private UserService userService;
@Inject
private EmailService emailService;
@Inject
private TelegramService telegramService;
@Inject
private StorageService storageService;
@Inject
private WebApp webApp;
@Inject
private ApplicationEventPublisher applicationEventPublisher;
@GetMapping("/settings")
protected String doGet(
@Parameter(hidden = true) User visitor,
Locale locale,
@RequestParam(required = false, defaultValue = "main") String page,
@RequestParam(required = false) String code, ModelMap model) {
visitor.setAvatar(webApp.getAvatarWebPath(visitor));
List<String> pages = Arrays.asList("main", "password", "about", "auth-email", "privacy");
if (!pages.contains(page)) {
page = "main";
}
model.addAttribute("title", ResourceBundle.getBundle("messages", locale).getString("link.settings"));
model.addAttribute("visitor", visitor);
model.addAttribute("tags", tagService.getPopularTags());
model.addAttribute("auths", userService.getAuthCodes(visitor));
model.addAttribute("email_active", emailService.getNotificationsEmail(visitor.getUid()));
model.addAttribute("emails", userService.getEmails(visitor));
model.addAttribute("jids", userService.getAllJIDs(visitor));
List<String> hours = IntStream.rangeClosed(0, 23).boxed()
.map(i -> StringUtils.leftPad(String.format("%d", i), 2, "0")).toList();
model.addAttribute("hours", hours);
model.addAttribute("fbstatus", userService.getFbCrossPostStatus(visitor.getUid()));
model.addAttribute("twitter_name", userService.getTwitterName(visitor.getUid()));
model.addAttribute("telegram_name", userService.getTelegramName(visitor.getUid()));
model.addAttribute("userinfo", userService.getUserInfo(visitor));
if (page.equals("auth-email")) {
if (emailService.verifyAddressByCode(visitor.getUid(), code)) {
model.addAttribute("result", "OK!");
} else {
model.addAttribute("result", "Sorry, code unknown.");
}
}
return String.format("views/settings_%s", page);
}
@PostMapping("/settings")
protected String doPost(
@Parameter(hidden = true) User visitor,
HttpServletRequest request, HttpServletResponse response,
@RequestParam(required = false) MultipartFile newAvatar,
ModelMap model)
throws IOException {
if (visitor.isAnonymous()) {
throw new HttpBadRequestException();
}
List<String> pages = Arrays.asList("main", "password", "about", "email", "email-add", "email-del",
"email-subscr", "auth-email", "privacy", "jid-del", "twitter-del", "telegram-del", "facebook-disable",
"facebook-enable", "vk-del");
String page = request.getParameter("page");
if (StringUtils.isEmpty(page) || !pages.contains(page)) {
throw new HttpBadRequestException();
}
String result = StringUtils.EMPTY;
switch (page) {
case "password":
if (userService.updatePassword(visitor, request.getParameter("password"))) {
result = "<p>Password has been changed.</p>";
String hash = userService.getHashByUID(visitor.getUid());
Cookie c = new Cookie("hash", hash);
c.setMaxAge(365 * 24 * 60 * 60);
response.addCookie(c);
}
break;
case "about":
visitor.setFullName(request.getParameter("fullname"));
visitor.setCountry(request.getParameter("country"));
visitor.setUrl(request.getParameter("url"));
visitor.setDescription(request.getParameter("descr"));
String avatarTmpPath = HttpUtils.receiveMultiPartFile(newAvatar, storageService.getTemporaryDirectory()).getHost();
if (StringUtils.isNotEmpty(avatarTmpPath)) {
storageService.saveAvatar(avatarTmpPath, visitor);
}
if (userService.updateUserInfo(visitor)) {
result = String.format("<p>Your info is updated.</p><p><a href='/%s/'>Back to blog</a>.</p>", visitor.getName());
}
applicationEventPublisher.publishEvent(new UpdateUserEvent(this, visitor));
break;
case "jid-del":
// FIXME: stop using ugnich-csv in parameters
String[] params = request.getParameter("delete").split(";", 2);
boolean res = false;
if (params[0].equals("xmpp")) {
res = userService.deleteJID(visitor.getUid(), params[1]);
} else if (params[0].equals("xmpp-unauth")) {
res = userService.unauthJID(visitor.getUid(), params[1]);
}
if (res) {
result = "<p>Deleted. <a href=\"/settings\">Back</a>.</p>";
} else {
result = "<p>Error</p>";
}
break;
case "email-add":
if (!emailService.verifyAddressByCode(visitor.getUid(), request.getParameter("account"))) {
String authCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase();
if (emailService.addVerificationCode(visitor.getUid(), request.getParameter("account"), authCode)) {
Session session = Session.getDefaultInstance(System.getProperties());
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("noreply@juick.com"));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(request.getParameter("account")));
message.setSubject("Juick authorization link");
message.setText(String.format("Follow link to attach this email to Juick account:\n" +
"http://juick.com/settings?page=auth-email&code=%s\n\n" +
"If you don't know, what this mean - just ignore this mail.\n", authCode));
Transport.send(message);
result = "<p>Authorization link has been sent to your email. Follow it to proceed.</p>" +
"<p><a href=\"/settings\">Back</a></p>";
} catch (MessagingException ex) {
logger.error("mail exception", ex);
throw new HttpBadRequestException();
}
}
}
break;
case "email-del":
if (emailService.deleteEmail(visitor.getUid(), request.getParameter("account"))) {
result = "<p>Deleted. <a href=\"/settings\">Back</a>.</p>";
} else {
result = "<p>An error occured while deleting.</p>";
}
break;
case "email-subscr":
if (emailService.setNotificationsEmail(visitor.getUid(), request.getParameter("account"))) {
result = String.format("<p>Saved! Will send notifications to <strong>%s</strong>." +
"</p><p><a href=\"/settings\">Back</a></p>", request.getParameter("account"));
} else {
result = "<p>Disabled.</p><p><a href=\"/settings\">Back</a></p>";
}
break;
case "twitter-del":
userService.deleteTwitterToken(visitor.getUid());
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals("request_token")) {
cookie.setMaxAge(0);
response.addCookie(cookie);
}
if (cookie.getName().equals("request_token_secret")) {
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
result = "<p><a href=\"/settings\">Back</a></p>";
break;
case "telegram-del":
if (userService.canDeleteTelegramUser(visitor)) {
telegramService.deleteTelegramUser(visitor.getUid());
}
result = "<p><a href=\"/settings\">Back</a></p>";
break;
case "facebook-disable":
userService.disableFBCrosspost(visitor.getUid());
result = "<p><a href=\"/settings\">Back</a></p>";
break;
case "facebook-enable":
userService.enableFBCrosspost(visitor.getUid());
result = "<p><a href=\"/settings\">Back</a></p>";
break;
case "vk-del":
userService.deleteVKUser(visitor.getUid());
result = "<p><a href=\"/settings\">Back</a></p>";
break;
default:
throw new HttpBadRequestException();
}
model.addAttribute("title", "Настройки");
model.addAttribute("visitor", visitor);
model.addAttribute("result", result);
return "views/settings_result";
}
@PostMapping("/settings/unsubscribe")
public String unsubscribeOneClick(
User user,
@RequestParam(name = "List-Unsubscribe") String unsubscribe,
ModelMap model) {
if (!user.isAnonymous()) {
if (unsubscribe.equals("One-Click")) {
emailService.setNotificationsEmail(user.getUid(), StringUtils.EMPTY);
model.addAttribute("title", "Настройки");
model.addAttribute("visitor", user);
model.addAttribute("result", "Unsubscribed");
return "views/settings_result";
}
}
throw new HttpBadRequestException();
}
}