UserService.java
package com.nonononoki.alovoa.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.linkedin.urls.Url;
import com.linkedin.urls.detection.UrlDetector;
import com.linkedin.urls.detection.UrlDetectorOptions;
import com.nonononoki.alovoa.Tools;
import com.nonononoki.alovoa.component.ExceptionHandler;
import com.nonononoki.alovoa.component.TextEncryptorConverter;
import com.nonononoki.alovoa.entity.User;
import com.nonononoki.alovoa.entity.user.*;
import com.nonononoki.alovoa.lib.OxCaptcha;
import com.nonononoki.alovoa.model.*;
import com.nonononoki.alovoa.repo.*;
import com.sipgate.mp3wav.Converter;
import jakarta.mail.MessagingException;
import lombok.Getter;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.*;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.imageio.ImageIO;
import javax.sound.sampled.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class UserService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
private static final int verificationStringLength = 5;
private static final String MIME_X_WAV = "x-wav";
private static final String MIME_WAV = "wav";
private static final String MIME_MPEG = "mpeg";
private static final String MIME_MP3 = "mp3";
private final Set<Long> drugsAlcoholMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.DRUGS_ALCOHOL_YES, UserMiscInfo.DRUGS_ALCOHOL_SOMETIMES, UserMiscInfo.DRUGS_ALCOHOL_NO));
private final Set<Long> drugsTobaccoMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.DRUGS_TOBACCO_YES, UserMiscInfo.DRUGS_TOBACCO_SOMETIMES, UserMiscInfo.DRUGS_TOBACCO_NO));
private final Set<Long> drugsCannabisMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.DRUGS_CANNABIS_YES, UserMiscInfo.DRUGS_CANNABIS_SOMETIMES, UserMiscInfo.DRUGS_CANNABIS_NO));
private final Set<Long> drugsOtherMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.DRUGS_OTHER_YES, UserMiscInfo.DRUGS_OTHER_SOMETIMES, UserMiscInfo.DRUGS_OTHER_NO));
private final Set<Long> kidsMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.KIDS_NO, UserMiscInfo.KIDS_YES));
private final Set<Long> relationshipMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.RELATIONSHIP_SINGLE, UserMiscInfo.RELATIONSHIP_TAKEN, UserMiscInfo.RELATIONSHIP_OPEN));
private final Set<Long> politicsMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.POLITICS_MODERATE, UserMiscInfo.POLITICS_LEFT, UserMiscInfo.POLITICS_RIGHT));
private final Set<Long> genderIdentityMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.GENDER_IDENTITY_CIS, UserMiscInfo.GENDER_IDENTITY_TRANS));
private final Set<Long> religionMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.RELIGION_NO, UserMiscInfo.RELIGION_YES));
private final Set<Long> familyMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.FAMILY_WANT, UserMiscInfo.FAMILY_NOT_WANT, UserMiscInfo.FAMILY_NOT_UNSURE));
private final Set<Long> relationshipTypeMiscInfoSet = new HashSet<>(Set.of(UserMiscInfo.RELATIONSHIP_TYPE_POLYAMOROUS, UserMiscInfo.RELATIONSHIP_TYPE_MONOGAMOUS));
@Autowired
private AuthService authService;
@Autowired
private MediaService mediaService;
@Autowired
private UserRepository userRepo;
@Autowired
private GenderRepository genderRepo;
@Autowired
private UserMiscInfoRepository userMiscInfoRepo;
@Autowired
private UserIntentionRepository userIntentionRepo;
@Autowired
private UserInterestRepository userInterestRepo;
@Autowired
private UserImageRepository userImageRepo;
@Autowired
private UserLikeRepository userLikeRepo;
@Autowired
private UserHideRepository userHideRepo;
@Autowired
private UserBlockRepository userBlockRepo;
@Autowired
private UserReportRepository userReportRepo;
@Autowired
private UserNotificationRepository userNotificationRepo;
@Autowired
private UserProfilePictureRepository userProfilePictureRepository;
@Autowired
private UserVerificationPictureRepository userVerificationPictureRepo;
@Autowired
private ConversationRepository conversationRepo;
@Autowired
private CaptchaService captchaService;
@Autowired
private MailService mailService;
@Autowired
private TextEncryptorConverter textEncryptor;
@Autowired
private ObjectMapper objectMapper;
@Value("${app.age.min}")
private int minAge;
@Value("${app.age.max}")
private int maxAge;
@Value("${app.profile.image.length}")
private int imageLength;
@Value("${app.profile.image.max}")
private int imageMax;
@Value("${app.profile.description.size}")
private int descriptionSize;
@Value("${app.token.length}")
private int tokenLength;
@Value("${app.interest.max}")
private int interestSize;
@Value("${app.interest.min-chars}")
private int interestMinCharSize;
@Value("${app.interest.max-chars}")
private int interestMaxCharSize;
@Value("${app.interest.autocomplete.max}")
private int interestAutocompleteMax;
@Value("${app.audio.max-time}")
private int audioMaxTime; // in seconds
@Value("${app.user.delete.duration.valid}")
private long userDeleteDuration;
@Value("${app.intention.delay}")
private long intentionDelay;
@Value("${app.like.message.length}")
private int likeMessageLength;
@Value("${app.search.ignore-intention}")
private boolean ignoreIntention;
@Getter
@Value("${app.domain}")
public String domain;
public static synchronized void removeUserDataCascading(User user, UserDeleteParams userDeleteParam) {
UserRepository userRepo = userDeleteParam.getUserRepo();
UserLikeRepository userLikeRepo = userDeleteParam.getUserLikeRepo();
ConversationRepository conversationRepo = userDeleteParam.getConversationRepo();
UserNotificationRepository userNotificationRepo = userDeleteParam.getUserNotificationRepo();
UserHideRepository userHideRepo = userDeleteParam.getUserHideRepo();
UserBlockRepository userBlockRepo = userDeleteParam.getUserBlockRepo();
UserReportRepository userReportRepo = userDeleteParam.getUserReportRepo();
UserVerificationPictureRepository userVerificationPictureRepo = userDeleteParam.getUserVerificationPictureRepo();
// DELETE USER LIKE
userLikeRepo.deleteAll(userLikeRepo.findByUserFrom(user));
List<UserLike> likeTos = userLikeRepo.findByUserTo(user);
userLikeRepo.deleteAll(likeTos);
userLikeRepo.flush();
for (UserLike like : likeTos) {
User u = like.getUserFrom();
if (u != null && u.getLikes() != null) {
u.getLikes().remove(like);
userRepo.save(u);
}
}
userRepo.flush();
// DELETE USER NOTIFICATION
userNotificationRepo.deleteAll(userNotificationRepo.findByUserFrom(user));
List<UserNotification> notificationTos = userNotificationRepo.findByUserTo(user);
userNotificationRepo.deleteAll(notificationTos);
userNotificationRepo.flush();
for (UserNotification notification : notificationTos) {
User u = notification.getUserFrom();
if (u != null && u.getNotifications() != null) {
u.getNotifications().remove(notification);
userRepo.save(u);
}
}
userRepo.flush();
// DELETE USER HIDE
userHideRepo.deleteAll(userHideRepo.findByUserFrom(user));
List<UserHide> hideTos = userHideRepo.findByUserTo(user);
userHideRepo.deleteAll(hideTos);
userHideRepo.flush();
for (UserHide hide : hideTos) {
User u = hide.getUserFrom();
if (u != null && u.getHiddenUsers() != null) {
u.getHiddenUsers().remove(hide);
userRepo.save(u);
}
}
userRepo.flush();
// DELETE USER BLOCK
userBlockRepo.deleteAll(userBlockRepo.findByUserFrom(user));
userBlockRepo.deleteAll(userBlockRepo.findByUserTo(user));
userBlockRepo.flush();
// DELETE USER REPORT
userReportRepo.deleteAll(userReportRepo.findByUserFrom(user));
List<UserReport> reportTos = userReportRepo.findByUserTo(user);
userReportRepo.deleteAll(reportTos);
userReportRepo.flush();
for (UserReport report : reportTos) {
User u = report.getUserFrom();
if (u != null && u.getReported() != null) {
u.getReported().remove(report);
userRepo.save(u);
}
}
userRepo.flush();
// DELETE USER CONVERSATION
for (Conversation c : conversationRepo.findByUsers_Id(user.getId())) {
for (User u : c.getUsers()) {
if (u != null && u.getConversations() != null) {
u.getConversations().remove(c);
userRepo.save(u);
}
}
conversationRepo.delete(c);
}
conversationRepo.flush();
// DELETE USER VERIFICATION
for (UserVerificationPicture v : userVerificationPictureRepo.findByUserNo(user)) {
v.getUserNo().remove(user);
userVerificationPictureRepo.save(v);
}
for (UserVerificationPicture v : userVerificationPictureRepo.findByUserYes(user)) {
v.getUserYes().remove(user);
userVerificationPictureRepo.save(v);
}
userVerificationPictureRepo.flush();
userRepo.flush();
}
public static String stripB64Type(String s) {
if (s.contains(",")) {
return s.split(",")[1];
}
return s;
}
private static byte[] convertAudioMp3Wav(byte[] bytes) {
InputStream inputStream = new ByteArrayInputStream(bytes);
ByteArrayOutputStream output = new ByteArrayOutputStream();
final AudioFormat audioFormat = new AudioFormat(16000, 8, 1, false, false);
Converter.convertFrom(inputStream).withTargetFormat(audioFormat).to(output);
return output.toByteArray();
}
public UserDeleteToken deleteAccountRequest() throws MessagingException, IOException, AlovoaException {
User user = authService.getCurrentUser(true);
return deleteAccountRequestBase(user);
}
public UserDeleteToken deleteAccountRequestBase(User user) throws MessagingException, IOException, AlovoaException {
UserDeleteToken token = new UserDeleteToken();
Date currentDate = new Date();
token.setContent(RandomStringUtils.random(tokenLength, 0, 0, true, true, null, new SecureRandom()));
token.setDate(currentDate);
token.setUser(user);
if (user.getDeleteToken() != null) {
token.setId(user.getDeleteToken().getId());
}
user.setDeleteToken(token);
user = userRepo.saveAndFlush(user);
mailService.sendAccountDeleteRequest(user);
return user.getDeleteToken();
}
public synchronized void deleteAccountConfirm(UserDeleteAccountDto dto)
throws MessagingException, IOException, AlovoaException, NoSuchAlgorithmException {
User user = userRepo.findByEmail(Tools.cleanEmail(dto.getEmail()));
if (user == null) {
throw new AlovoaException(ExceptionHandler.USER_NOT_FOUND);
}
UserDeleteToken deleteToken = user.getDeleteToken();
if (!dto.isConfirm() || deleteToken == null) {
throw new AlovoaException("deletion_not_confirmed");
}
String userTokenString = deleteToken.getContent();
long ms = new Date().getTime();
if (ms - user.getDeleteToken().getDate().getTime() > userDeleteDuration) {
throw new AlovoaException("deletion_not_valid");
}
if (!dto.getTokenString().equals(userTokenString)) {
LOGGER.debug("Expected:" + userTokenString + ". Got: " + dto.getTokenString());
throw new AlovoaException("deletion_wrong_token");
}
if (!captchaService.isValid(dto.getCaptchaId(), dto.getCaptchaText())) {
throw new AlovoaException("captcha_invalid");
}
UserDeleteParams userDeleteParam = UserDeleteParams.builder().conversationRepo(conversationRepo)
.userBlockRepo(userBlockRepo).userHideRepo(userHideRepo).userLikeRepo(userLikeRepo)
.userNotificationRepo(userNotificationRepo).userRepo(userRepo).userReportRepo(userReportRepo)
.userVerificationPictureRepo(userVerificationPictureRepo).build();
removeUserDataCascading(user, userDeleteParam);
user = userRepo.saveAndFlush(user);
userRepo.delete(user);
userRepo.flush();
SecurityContextHolder.clearContext();
mailService.sendAccountDeleteConfirm(user);
}
@SuppressWarnings("deprecation")
public void updateProfilePicture(byte[] bytes, String mimeType) throws AlovoaException, IOException {
User user = authService.getCurrentUser(true);
user.setVerificationPicture(null);
AbstractMap.SimpleEntry<byte[], String> adjustedImage = adjustPicture(bytes, mimeType);
if (user.getProfilePicture() == null) {
UserProfilePicture profilePic = new UserProfilePicture();
profilePic.setBin(adjustedImage.getKey());
profilePic.setBinMime(adjustedImage.getValue());
profilePic.setUser(user);
profilePic.setUuid(UUID.randomUUID());
user.setProfilePicture(profilePic);
} else {
user.getProfilePicture().setBin(adjustedImage.getKey());
user.getProfilePicture().setBinMime(adjustedImage.getValue());
user.getProfilePicture().setData(null);
user.getProfilePicture().setUuid(UUID.randomUUID());
}
userRepo.saveAndFlush(user);
}
public User onboarding(byte[] bytes, ProfileOnboardingDto model) throws AlovoaException, IOException {
User user = authService.getCurrentUser(true);
if (user.getProfilePicture() != null || user.getDescription() != null) {
return null;
}
Date now = new Date();
UserProfilePicture profilePic = new UserProfilePicture();
AbstractMap.SimpleEntry<byte[], String> adjustedImage = adjustPicture(bytes, model.getProfilePictureMime());
profilePic.setBin(adjustedImage.getKey());
profilePic.setBinMime(adjustedImage.getValue());
user.setProfilePicture(profilePic);
user.setVerificationPicture(null);
UserIntention intention = userIntentionRepo.findById(model.getIntention()).orElse(null);
user.getDates().setIntentionChangeDate(now);
user.setIntention(intention);
List<Gender> preferredGenders = genderRepo.findAllById(model.getPreferredGenders());
user.setPreferedGenders(new HashSet<>(preferredGenders));
user.setDescription(model.getDescription());
user.getInterests().addAll((model.getInterests().stream().map(i -> {
UserInterest interest = new UserInterest();
interest.setText(i.toLowerCase());
interest.setUser(user);
return interest;
}).toList()));
user.getUserSettings().setEmailLike(model.isNotificationLike());
user.getUserSettings().setEmailChat(model.isNotificationChat());
return userRepo.saveAndFlush(user);
}
public void updateDescription(String description) throws AlovoaException {
if (description != null) {
if (description.length() > descriptionSize) {
throw new AlovoaException("max_length_exceeded");
}
if (description.trim().isEmpty()) {
description = null;
} else {
UrlDetector parser = new UrlDetector(description, UrlDetectorOptions.Default);
List<Url> urls = parser.detect();
if (!urls.isEmpty()) {
throw new AlovoaException("url_detected");
}
}
}
User user = authService.getCurrentUser(true);
user.setDescription(description);
userRepo.saveAndFlush(user);
}
public void updateIntention(long intention) throws AlovoaException {
User user = authService.getCurrentUser(true);
Date now = new Date();
if (user.getDates().getIntentionChangeDate() == null
|| now.getTime() >= user.getDates().getIntentionChangeDate().getTime() + intentionDelay) {
boolean isLegal = Tools.calcUserAge(user) >= Tools.AGE_LEGAL;
if (!isLegal && intention == UserIntention.SEX) {
throw new AlovoaException("not_supported");
}
UserIntention i = userIntentionRepo.findById(intention).orElse(null);
user.setIntention(i);
user.getDates().setIntentionChangeDate(now);
userRepo.saveAndFlush(user);
} else {
throw new AlovoaException("intention cooldown not finished");
}
}
public void updateMinAge(int userMinAge) throws AlovoaException {
if (userMinAge < minAge) {
userMinAge = minAge;
}
User user = authService.getCurrentUser(true);
user.setPreferedMinAge(userMinAge);
userRepo.saveAndFlush(user);
}
public void updateMaxAge(int userMaxAge) throws AlovoaException {
if (userMaxAge > maxAge) {
userMaxAge = maxAge;
}
User user = authService.getCurrentUser(true);
user.setPreferedMaxAge(userMaxAge);
userRepo.saveAndFlush(user);
}
public void updatePreferedGender(long genderId, boolean activated) throws AlovoaException {
User user = authService.getCurrentUser(true);
Set<Gender> list = user.getPreferedGenders();
if (list == null) {
list = new HashSet<>();
}
Gender g = genderRepo.findById(genderId).orElse(null);
if (activated) {
list.add(g);
} else {
list.remove(g);
}
user.setPreferedGenders(list);
userRepo.saveAndFlush(user);
}
public Set<UserMiscInfo> updateUserMiscInfo(long infoValue, boolean activated) throws AlovoaException {
User user = authService.getCurrentUser(true);
Set<UserMiscInfo> list = user.getMiscInfos();
if (list == null) {
list = new HashSet<>();
}
if (kidsMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(kidsMiscInfoSet, infoValue));
} else if (relationshipMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(relationshipMiscInfoSet, infoValue));
} else if (drugsAlcoholMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(drugsAlcoholMiscInfoSet, infoValue));
} else if (drugsTobaccoMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(drugsAlcoholMiscInfoSet, infoValue));
} else if (drugsCannabisMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(drugsCannabisMiscInfoSet, infoValue));
} else if (drugsOtherMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(drugsOtherMiscInfoSet, infoValue));
} else if (politicsMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(politicsMiscInfoSet, infoValue));
} else if (genderIdentityMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(genderIdentityMiscInfoSet, infoValue));
} else if (religionMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(religionMiscInfoSet, infoValue));
} else if (familyMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(familyMiscInfoSet, infoValue));
} else if (relationshipTypeMiscInfoSet.contains(infoValue)) {
list.removeIf(conditionToRemoveIfExistent(relationshipTypeMiscInfoSet, infoValue));
}
if (activated) {
UserMiscInfo info = userMiscInfoRepo.findByValue(infoValue);
list.add(info);
}
user.setMiscInfos(list);
userRepo.saveAndFlush(user);
return list;
}
private Predicate<UserMiscInfo> conditionToRemoveIfExistent(final Set<Long> options, final long infoValue) {
return c -> options.contains(c.getValue()) && c.getValue() != infoValue;
}
public void addInterest(String value) throws AlovoaException {
User user = authService.getCurrentUser(true);
if (value.length() < interestMinCharSize || value.length() > interestMaxCharSize
|| user.getInterests().size() >= interestSize) {
throw new AlovoaException("interest_invalid_size");
}
Pattern pattern = Pattern.compile("[a-zA-Z0-9-]+");
Matcher matcher = pattern.matcher(value);
if (!matcher.matches()) {
throw new AlovoaException("interest_unsupported_characters");
}
UserInterest interest = new UserInterest();
interest.setText(value.toLowerCase());
interest.setUser(user);
if (user.getInterests() == null) {
user.setInterests(new ArrayList<>());
}
if (user.getInterests().contains(interest)) {
throw new AlovoaException("interest_already_exists");
}
user.getInterests().add(interest);
userRepo.saveAndFlush(user);
}
public void deleteInterest(String interest) throws AlovoaException {
User user = authService.getCurrentUser(true);
Optional<UserInterest> interestOpt = user.getInterests().stream().filter(i -> i.getText().equals(interest))
.findFirst();
if (interest.isEmpty() || interestOpt.isEmpty()) {
throw new AlovoaException("interest_does_not_exists");
}
user.getInterests().remove(interestOpt.get());
userRepo.saveAndFlush(user);
}
public List<UserInterestDto> getInterestAutocomplete(String name) throws AlovoaException {
User user = authService.getCurrentUser(true);
name = "%" + URLDecoder.decode(name, StandardCharsets.UTF_8) + "%";
List<String> interestTexts = user.getInterests().stream().map(UserInterest::getText).collect(Collectors.toList());
if (interestTexts.isEmpty()) {
interestTexts.add("");
}
List<UserInterestDto> interests = userInterestRepo.getInterestAutocomplete(name, interestTexts,
PageRequest.of(0, interestAutocompleteMax));
interests.forEach(i -> i.setCountString(Tools.largeNumberToString(i.getCount())));
return interests;
}
public void updateShowZodiac(int showZodiac) throws AlovoaException {
User user = authService.getCurrentUser(true);
user.setShowZodiac(showZodiac == 1);
userRepo.saveAndFlush(user);
}
public void updateUnits(int units) throws AlovoaException {
User user = authService.getCurrentUser(true);
user.setUnits(units);
userRepo.saveAndFlush(user);
}
public List<UserImageDto> addImage(byte[] bytes, String mimeType) throws AlovoaException, IOException {
User user = authService.getCurrentUser(true);
if (user.getImages() != null && user.getImages().size() < imageMax) {
UserImage img = new UserImage();
AbstractMap.SimpleEntry<byte[], String> adjustedImage = adjustPicture(bytes, mimeType);
img.setBin(adjustedImage.getKey());
img.setBinMime(adjustedImage.getValue());
img.setDate(new Date());
img.setUser(user);
user.getImages().add(img);
user = userRepo.saveAndFlush(user);
return UserImageDto.buildFromUserImages(user, this);
} else {
throw new AlovoaException("max_image_amount_exceeded");
}
}
public void deleteImage(long id) throws AlovoaException {
User user = authService.getCurrentUser(true);
UserImage img = userImageRepo.findById(id).orElse(null);
if (img != null && user.getImages().contains(img)) {
user.getImages().remove(img);
userRepo.saveAndFlush(user);
}
}
private AbstractMap.SimpleEntry<byte[], String> adjustPicture(byte[] bytes, String mimeType) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
BufferedImage image = ImageIO.read(bis);
if (image.getWidth() == imageLength && image.getHeight() == imageLength) {
return new AbstractMap.SimpleEntry<>(bytes, mimeType);
}
if (image.getWidth() != image.getHeight()) {
int idealLength = image.getHeight();
boolean heightShorter = true;
if (image.getWidth() < image.getHeight()) {
heightShorter = false;
idealLength = image.getWidth();
}
// cut to a square
int x = 0;
int y = 0;
if (heightShorter) {
x = image.getWidth() / 2 - image.getHeight() / 2;
} else {
y = image.getHeight() / 2 - image.getWidth() / 2;
}
image = image.getSubimage(x, y, idealLength, idealLength);
}
// all images are equal in size
BufferedImage scaledImage = new BufferedImage(imageLength, imageLength, image.getType());
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(image, 0, 0, imageLength, imageLength, null);
graphics2D.dispose();
image = scaledImage;
// image to b64
ByteArrayOutputStream bos = new ByteArrayOutputStream();
final String fileType = "webp";
ImageIO.write(image, fileType, bos);
return new AbstractMap.SimpleEntry<>(bos.toByteArray(), MediaService.MEDIA_TYPE_IMAGE_WEBP);
}
public void likeUser(UUID uuid, String message) throws AlovoaException {
User user = findUserByUuid(uuid);
User currUser = authService.getCurrentUser(true);
if (user.equals(currUser)) {
throw new AlovoaException("user_is_same_user");
}
if (user.getBlockedUsers().stream().filter(o -> o.getUserTo().getId() != null)
.anyMatch(o -> o.getUserTo().getId().equals(currUser.getId()))) {
throw new AlovoaException("is_blocked");
}
if (currUser.getBlockedUsers().stream().filter(o -> o.getUserTo().getId() != null)
.anyMatch(o -> o.getUserTo().getId().equals(user.getId()))) {
throw new AlovoaException("is_blocked");
}
if (!Tools.usersCompatible(currUser, user, ignoreIntention)) {
throw new AlovoaException(ExceptionHandler.USER_NOT_COMPATIBLE);
}
if (message != null && message.length() > likeMessageLength) {
throw new AlovoaException("max_length_exceeded");
}
if (userLikeRepo.findByUserFromAndUserTo(currUser, user) == null) {
UserLike like = new UserLike();
like.setDate(new Date());
like.setUserFrom(currUser);
like.setUserTo(user);
currUser.getLikes().add(like);
if (!currUser.isDisabled()) {
UserNotification not = new UserNotification();
not.setContent(UserNotification.USER_LIKE);
not.setDate(new Date());
not.setUserFrom(currUser);
not.setUserTo(user);
not.setMessage(message);
currUser.getNotifications().add(not);
user.getDates().setNotificationDate(new Date());
currUser.getHiddenUsers().removeIf(hide -> hide.getUserTo().getId().equals(user.getId()));
}
userRepo.saveAndFlush(user);
userRepo.saveAndFlush(currUser);
final boolean isMatch = user.getLikes().stream().filter(o -> o.getUserTo().getId() != null)
.anyMatch(o -> o.getUserTo().getId().equals(currUser.getId()));
if (!currUser.isDisabled() && isMatch) {
Conversation convo = new Conversation();
convo.setUsers(new ArrayList<>());
convo.setDate(new Date());
convo.getUsers().add(currUser);
convo.getUsers().add(user);
convo.setLastUpdated(new Date());
convo.setMessages(new ArrayList<>());
conversationRepo.saveAndFlush(convo);
user.getConversations().add(convo);
currUser.getConversations().add(convo);
userRepo.saveAndFlush(currUser);
userRepo.saveAndFlush(user);
if (user.getUserSettings().isEmailLike()) {
mailService.sendMatchNotificationMail(user);
}
} else if (!currUser.isDisabled() && user.getUserSettings().isEmailLike()) {
mailService.sendLikeNotificationMail(user);
}
}
}
public void hideUser(UUID uuid)
throws NumberFormatException, AlovoaException {
User user = findUserByUuid(uuid);
User currUser = authService.getCurrentUser(true);
if (userHideRepo.findByUserFromAndUserTo(currUser, user) == null) {
UserHide hide = new UserHide();
hide.setDate(new Date());
hide.setUserFrom(currUser);
hide.setUserTo(user);
currUser.getHiddenUsers().add(hide);
userRepo.saveAndFlush(currUser);
if (user.getLikes().stream().anyMatch(l -> l.getUserTo().getId().equals(currUser.getId()))) {
blockUser(uuid);
}
}
}
public void blockUser(UUID uuid)
throws AlovoaException, NumberFormatException {
User user = findUserByUuid(uuid);
User currUser = authService.getCurrentUser(true);
if (userBlockRepo.findByUserFromAndUserTo(currUser, user) == null) {
UserBlock block = new UserBlock();
block.setDate(new Date());
block.setUserFrom(currUser);
block.setUserTo(user);
currUser.getBlockedUsers().add(block);
userRepo.saveAndFlush(currUser);
}
}
public void unblockUser(UUID uuid)
throws AlovoaException, NumberFormatException {
User user = findUserByUuid(uuid);
User currUser = authService.getCurrentUser(true);
UserBlock block = userBlockRepo.findByUserFromAndUserTo(currUser, user);
if (block != null) {
currUser.getBlockedUsers().remove(block);
userRepo.save(currUser);
}
}
public UserReport reportUser(UUID uuid, String comment)
throws AlovoaException, NumberFormatException {
User user = findUserByUuid(uuid);
User currUser = authService.getCurrentUser(true);
if (userReportRepo.findByUserFromAndUserTo(currUser, user) == null) {
UserReport report = new UserReport();
report.setDate(new Date());
report.setUserFrom(currUser);
report.setUserTo(user);
report.setComment(comment);
currUser.getReported().add(report);
currUser = userRepo.saveAndFlush(currUser);
return currUser.getReported().get(currUser.getReported().size() - 1);
}
return null;
}
public boolean hasNewAlert() throws AlovoaException {
return hasNewAlert(null);
}
public boolean hasNewAlert(String lang) throws AlovoaException {
User user = authService.getCurrentUser(true);
// user always check their alerts periodically in the background, so just update
// it here
if (user != null) {
updateUserInfo(user, lang);
return user.getDates().getNotificationDate().after(user.getDates().getNotificationCheckedDate());
} else {
return false;
}
}
public boolean hasNewMessage() throws AlovoaException {
User user = authService.getCurrentUser(true);
if (user != null && user.getDates().getMessageDate() != null
&& user.getDates().getMessageCheckedDate() != null) {
return user.getDates().getMessageDate().after(user.getDates().getMessageCheckedDate());
} else {
return false;
}
}
public void updateUserInfo(User user) {
updateUserInfo(user, null);
}
public void updateUserInfo(User user, String language) {
if (user != null && user.getDates() != null) {
user.getDates().setActiveDate(new Date());
String lang;
if (language == null) {
lang = LocaleContextHolder.getLocale().getLanguage();
} else {
lang = language;
}
if (!lang.equals(user.getLanguage())) {
user.setLanguage(lang);
}
userRepo.saveAndFlush(user);
}
}
public ResponseEntity<Resource> getUserdata(UUID uuid) throws AlovoaException, JsonProcessingException,
NumberFormatException {
User user = authService.getCurrentUser(true);
User userFromUuid = findUserByUuid(uuid);
if (!user.getId().equals(userFromUuid.getId())) {
throw new AlovoaException("users_not_equal");
}
UserGdpr ug = UserGdpr.userToUserGdpr(user);
String json = objectMapper.writeValueAsString(ug);
ByteArrayResource resource = new ByteArrayResource(json.getBytes(StandardCharsets.UTF_8));
ContentDisposition contentDisposition = ContentDisposition.builder("inline").filename("alovoa_userdata.json")
.build();
MediaType mediaType = MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType);
headers.setContentDisposition(contentDisposition);
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
@SuppressWarnings("deprecation")
public String getAudio(UUID uuid)
throws NumberFormatException, AlovoaException {
User user = findUserByUuid(uuid);
if (user.getAudio() == null) {
return null;
}
return user.getAudio().getData();
}
public void deleteAudio() throws AlovoaException {
User user = authService.getCurrentUser(true);
user.setAudio(null);
userRepo.saveAndFlush(user);
}
@SuppressWarnings("deprecation")
public void updateAudio(byte[] bytes, String mimeType)
throws AlovoaException, UnsupportedAudioFileException, IOException {
User user = authService.getCurrentUser(true);
byte[] newAudioB64 = adjustAudio(bytes, mimeType);
if (user.getAudio() == null) {
UserAudio audio = new UserAudio();
audio.setBin(newAudioB64);
audio.setUser(user);
audio.setUuid(UUID.randomUUID());
user.setAudio(audio);
} else {
user.getAudio().setBin(newAudioB64);
user.getAudio().setData(null);
user.getAudio().setUuid(UUID.randomUUID());
}
userRepo.saveAndFlush(user);
}
public void updateCountry(String countryIso) throws AlovoaException {
User user = authService.getCurrentUser(true);
boolean validCountryIso = Arrays.asList(Locale.getISOCountries()).contains(countryIso);
if (validCountryIso) {
user.setCountry(countryIso);
} else {
// remove flag if user is not in a valid country
user.setCountry(null);
}
userRepo.saveAndFlush(user);
}
public String getVerificationCode() throws AlovoaException {
User user = authService.getCurrentUser(true);
return getVerificationCode(user);
}
public String getVerificationCode(User user) {
if (user.getVerificationCode() != null) {
return user.getVerificationCode();
} else {
String verificationString = new String(OxCaptcha.genText(verificationStringLength));
user.setVerificationCode(verificationString);
userRepo.saveAndFlush(user);
return verificationString;
}
}
public void updateVerificationPicture(byte[] bytes, String mimeType) throws AlovoaException, IOException {
User user = authService.getCurrentUser(true);
//verification picture only usable with profile picture
if (user.getProfilePicture() == null) {
throw new AlovoaException("need_profile_picture");
}
user.setVerificationPicture(null);
userRepo.saveAndFlush(user);
AbstractMap.SimpleEntry<byte[], String> adjustedImage = adjustPicture(bytes, mimeType);
UserVerificationPicture verificationPicture = new UserVerificationPicture();
verificationPicture.setBin(adjustedImage.getKey());
verificationPicture.setBinMime(adjustedImage.getValue());
verificationPicture.setDate(new Date());
verificationPicture.setUser(user);
verificationPicture.setUserNo(new ArrayList<>());
verificationPicture.setUserYes(new ArrayList<>());
verificationPicture.setUuid(UUID.randomUUID());
user.setVerificationPicture(verificationPicture);
userRepo.saveAndFlush(user);
}
public void upvoteVerificationPicture(UUID uuid) throws AlovoaException, IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {
User currentUser = authService.getCurrentUser(true);
User user = findUserByUuid(uuid);
if (currentUser.equals(user)) {
throw new AlovoaException("invalid_user");
}
if (user.getVerificationPicture() == null) {
throw new AlovoaException("no_verification_picture");
}
if (user.getVerificationPicture().getUserYes().contains(currentUser) || user.getVerificationPicture().getUserNo().contains(currentUser)) {
throw new AlovoaException("already_voted");
}
user.getVerificationPicture().getUserYes().add(currentUser);
userRepo.saveAndFlush(user);
}
public void downvoteVerificationPicture(UUID uuid) throws AlovoaException {
User currentUser = authService.getCurrentUser(true);
User user = findUserByUuid(uuid);
if (currentUser.equals(user)) {
throw new AlovoaException("invalid_user");
}
if (user.getVerificationPicture() == null) {
throw new AlovoaException("no_verification_picture");
}
if (user.getVerificationPicture().getUserYes().contains(currentUser) || user.getVerificationPicture().getUserNo().contains(currentUser)) {
throw new AlovoaException("already_voted");
}
user.getVerificationPicture().getUserNo().add(currentUser);
userRepo.saveAndFlush(user);
}
public void updateUserLocation(Double latitude, Double longitude) throws AlovoaException {
User user = authService.getCurrentUser(true);
// rounding to improve privacy
DecimalFormat df = new DecimalFormat("#.##");
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
user.setLocationLatitude(Double.valueOf(df.format(latitude)));
user.setLocationLongitude(Double.valueOf(df.format(longitude)));
userRepo.saveAndFlush(user);
}
public void updateProfilePictureUUID(UserProfilePicture image, UUID uuid) {
if (image.getUuid() == null) {
image.setUuid(uuid);
userProfilePictureRepository.saveAndFlush(image);
}
}
public void updateUUID(User user, UUID uuid) {
if (user.getUuid() == null) {
user.setUuid(uuid);
userRepo.saveAndFlush(user);
}
}
public void updateImageUUID(UserImage image, UUID uuid) {
if (image.getUuid() == null) {
image.setUuid(uuid);
userImageRepo.saveAndFlush(image);
}
}
public User findUserByUuid(UUID uuid) throws AlovoaException {
User user = userRepo.findByUuid(uuid);
if (user == null) {
throw new AlovoaException("user_not_found: " + uuid);
}
return user;
}
private byte[] adjustAudio(byte[] bytes, String mimeType) throws UnsupportedAudioFileException, IOException {
if (mimeType.equals(MIME_X_WAV) || mimeType.equals(MIME_WAV)) {
return trimAudioWav(bytes, audioMaxTime);
} else if (mimeType.equals(MIME_MPEG) || mimeType.equals(MIME_MP3)) {
return trimAudioWav(convertAudioMp3Wav(bytes), audioMaxTime);
}
return null;
}
private byte[] trimAudioWav(byte[] bytes, int maxSeconds) throws UnsupportedAudioFileException, IOException {
ByteArrayInputStream bis;
ByteArrayOutputStream baos;
AudioInputStream ais = null;
AudioInputStream aisShort = null;
try {
bis = new ByteArrayInputStream(bytes);
ais = AudioSystem.getAudioInputStream(bis);
AudioFormat format = ais.getFormat();
long frameLength = ais.getFrameLength();
// check if audio is shorter or equal max length
double durationInSeconds = frameLength * format.getFrameRate();
if (durationInSeconds < 0.0) {
durationInSeconds = durationInSeconds * (-1);
}
if (durationInSeconds <= maxSeconds) {
ais.close();
return bytes;
} else {
long frames = (long) (format.getFrameRate() * maxSeconds);
aisShort = new AudioInputStream(ais, format, frames);
AudioFileFormat.Type targetType = AudioFileFormat.Type.WAVE;
baos = new ByteArrayOutputStream();
AudioSystem.write(aisShort, targetType, baos);
aisShort.close();
ais.close();
return baos.toByteArray();
}
} catch (Exception e) {
if (ais != null) {
ais.close();
}
if (aisShort != null) {
aisShort.close();
}
throw e;
}
}
}