/*
* 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 .
*/
package com.juick.www.api;
import com.juick.model.Message;
import com.juick.model.Tag;
import com.juick.model.User;
import com.juick.service.activities.UpdateEvent;
import com.juick.www.WebApp;
import com.juick.model.CommandResult;
import com.juick.util.HttpBadRequestException;
import com.juick.util.HttpForbiddenException;
import com.juick.util.HttpNotFoundException;
import com.juick.service.MessagesService;
import com.juick.service.TagService;
import com.juick.service.UserService;
import com.juick.service.component.SystemEvent;
import io.swagger.v3.oas.annotations.Parameter;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import jakarta.inject.Inject;
import java.io.IOException;
import java.util.*;
/**
* @author ugnich
*/
@RestController
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class Messages {
@Inject
private MessagesService messagesService;
@Inject
private UserService userService;
@Inject
private TagService tagService;
@Inject
private ApplicationEventPublisher applicationEventPublisher;
@Inject
private WebApp webApp;
@Value("classpath:Transparent.gif")
private Resource invisiblePixel;
@Inject
private User serviceUser;
// TODO: serialize image urls
@GetMapping({"/api/home"})
public List getHome(@Parameter(hidden = true) User visitor,
@RequestParam(defaultValue = "0") int before_mid) {
int vuid = visitor.getUid();
List mids = messagesService.getMyFeed(vuid, before_mid, true);
List msgs = messagesService.getMessages(vuid, mids);
msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser())));
return msgs;
}
@GetMapping("/api/messages")
public List getMessages(@Parameter(hidden = true) User visitor,
@RequestParam(required = false) String uname,
@RequestParam(name = "before_mid", defaultValue = "0") Integer before,
@RequestParam(required = false, defaultValue = "0") Integer daysback,
@RequestParam(required = false) String withrecommended, @RequestParam(required = false) String popular,
@RequestParam(required = false) String search,
@RequestParam(required = false, defaultValue = "0") Integer page,
@RequestParam(required = false) String media, @RequestParam(required = false) String tag) {
List mids;
if (StringUtils.hasText(uname)) {
User user = userService.getUserByName(uname);
int privacy = 0;
if (!visitor.isAnonymous()) {
if (user.getUid() == visitor.getUid()) {
privacy = -3;
} else if (userService.isInWL(user.getUid(), visitor.getUid())) {
privacy = -2;
}
}
if (!user.isAnonymous() && !user.isBanned()) {
if (StringUtils.hasText(media)) {
mids = messagesService.getUserPhotos(user.getUid(), privacy, before);
} else if (StringUtils.hasText(tag)) {
Tag tagObject = tagService.getTag(tag, false);
if (tagObject != null) {
mids = messagesService.getUserTag(user.getUid(), tagObject.getId(), privacy, before);
} else {
throw new HttpNotFoundException();
}
} else if (StringUtils.hasText(withrecommended)) {
mids = messagesService.getUserBlogWithRecommendations(user, visitor, privacy, before);
} else if (daysback > 0) {
mids = messagesService.getUserBlogAtDay(user.getUid(), privacy, daysback);
} else if (StringUtils.hasText(search)) {
mids = messagesService.getUserSearch(visitor, user.getUid(), search, privacy,
page);
} else {
mids = messagesService.getUserBlog(user.getUid(), privacy, before);
}
} else {
throw new HttpNotFoundException();
}
} else {
if (StringUtils.hasText(popular)) {
mids = messagesService.getUserBlogWithRecommendations(serviceUser, visitor, 0, before);
} else if (StringUtils.hasText(media)) {
mids = messagesService.getPhotos(visitor.getUid(), before);
} else if (StringUtils.hasText(tag)) {
Tag tagObject = tagService.getTag(tag, false);
if (tagObject != null) {
mids = messagesService.getTag(tagObject.getId(), visitor.getUid(), before, 20);
} else {
throw new HttpNotFoundException();
}
} else if (StringUtils.hasText(search)) {
mids = messagesService.getSearch(visitor, search, page);
} else {
mids = messagesService.getAll(visitor.getUid(), before);
}
}
List msgs = messagesService.getMessages(visitor.getUid(), mids);
msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser())));
return msgs;
}
@DeleteMapping("/api/messages")
public CommandResult deleteMessage(@Parameter(hidden = true) User visitor, @RequestParam int mid,
@RequestParam(required = false, defaultValue = "0") int rid) {
if (rid > 0) {
if (messagesService.deleteReply(visitor.getUid(), mid, rid)) {
return CommandResult.fromString("Reply deleted");
}
}
if (messagesService.deleteMessage(visitor.getUid(), mid)) {
return CommandResult.fromString("Message deleted");
}
throw new HttpBadRequestException();
}
@GetMapping("/api/messages/discussions")
public List getDiscussions(@Parameter(hidden = true) User visitor,
@RequestParam(required = false, defaultValue = "0") Long to) {
List msgs = messagesService.getMessages(visitor.getUid(), messagesService.getDiscussions(visitor.getUid(), to));
msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser())));
return msgs;
}
@GetMapping("/api/thread")
public List getThread(@Parameter(hidden = true) User visitor, @RequestParam(defaultValue = "0") int mid,
@RequestParam(defaultValue = "true") boolean showReplies) {
Optional message = messagesService.getMessage(visitor.getUid(), mid);
if (message.isPresent()) {
Message msg = message.get();
if (!messagesService.canViewThread(mid, visitor.getUid())) {
throw new HttpForbiddenException();
} else {
msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser()));
msg.getRecommendations().forEach(r -> r.setAvatar(webApp.getAvatarUrl(r)));
if (showReplies) {
List replies = messagesService.getReplies(visitor, mid);
// update replies counter to exclude banned replies
msg.setReplies(replies.size());
if (!visitor.isAnonymous()) {
if (replies.size() > 0) {
messagesService.setRead(visitor, mid);
}
userService.updateLastSeen(visitor);
applicationEventPublisher
.publishEvent(new SystemEvent(this, SystemActivity.read(visitor, msg)));
}
replies.add(0, msg);
return replies;
} else {
return List.of(msg);
}
}
}
throw new HttpNotFoundException();
}
@GetMapping(value = "/api/thread/mark_read/{mid}-{rid}.gif", produces = MediaType.IMAGE_GIF_VALUE)
public byte[] markThreadRead(@Parameter(hidden = true) User visitor, @PathVariable int mid, @PathVariable int rid)
throws IOException {
if (!visitor.isAnonymous()) {
messagesService.setLastReadComment(visitor, mid, rid);
Message msg = messagesService.getMessage(mid).orElseThrow(HttpNotFoundException::new);
userService.updateLastSeen(visitor);
applicationEventPublisher.publishEvent(new SystemEvent(this, SystemActivity.read(visitor, msg)));
return IOUtils.toByteArray(invisiblePixel.getInputStream());
}
throw new HttpBadRequestException();
}
@PostMapping("/api/messages/set_privacy")
public void togglePrivacy(@Parameter(hidden = true) User visitor, @RequestParam Integer mid) {
if (visitor.isPremium() || visitor.isAdmin()) {
var msg = messagesService.getMessage(mid).orElseThrow(HttpNotFoundException::new);
messagesService.setFriendsOnly(mid, !msg.isFriendsOnly());
msg = messagesService.getMessage(mid).orElseThrow(HttpNotFoundException::new);
applicationEventPublisher.publishEvent(
new UpdateEvent(this, visitor, msg));
} else {
throw new HttpBadRequestException();
}
}
}