From 7414f1034d32c249294a081f1e176a9266fc92ac Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 8 Feb 2018 22:06:08 +0300 Subject: reorganize project structure --- .../src/main/java/com/juick/server/api/Index.java | 54 ++++ .../main/java/com/juick/server/api/Messages.java | 180 +++++++++++++ .../java/com/juick/server/api/Notifications.java | 190 ++++++++++++++ .../src/main/java/com/juick/server/api/PM.java | 148 +++++++++++ .../src/main/java/com/juick/server/api/Post.java | 281 +++++++++++++++++++++ .../main/java/com/juick/server/api/Service.java | 74 ++++++ .../src/main/java/com/juick/server/api/Tags.java | 54 ++++ .../src/main/java/com/juick/server/api/Users.java | 125 +++++++++ .../server/api/webhooks/MessengerWebhook.java | 62 +++++ .../juick/server/api/webhooks/SkypeWebhook.java | 47 ++++ .../juick/server/api/webhooks/TelegramWebhook.java | 48 ++++ 11 files changed, 1263 insertions(+) create mode 100644 juick-server/src/main/java/com/juick/server/api/Index.java create mode 100644 juick-server/src/main/java/com/juick/server/api/Messages.java create mode 100644 juick-server/src/main/java/com/juick/server/api/Notifications.java create mode 100644 juick-server/src/main/java/com/juick/server/api/PM.java create mode 100644 juick-server/src/main/java/com/juick/server/api/Post.java create mode 100644 juick-server/src/main/java/com/juick/server/api/Service.java create mode 100644 juick-server/src/main/java/com/juick/server/api/Tags.java create mode 100644 juick-server/src/main/java/com/juick/server/api/Users.java create mode 100644 juick-server/src/main/java/com/juick/server/api/webhooks/MessengerWebhook.java create mode 100644 juick-server/src/main/java/com/juick/server/api/webhooks/SkypeWebhook.java create mode 100644 juick-server/src/main/java/com/juick/server/api/webhooks/TelegramWebhook.java (limited to 'juick-server/src/main/java/com/juick/server/api') diff --git a/juick-server/src/main/java/com/juick/server/api/Index.java b/juick-server/src/main/java/com/juick/server/api/Index.java new file mode 100644 index 00000000..dba8357d --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Index.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008-2017, 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.Status; +import com.juick.server.WebsocketManager; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import springfox.documentation.annotations.ApiIgnore; + +import javax.inject.Inject; +import java.net.URI; + +/** + * Created by vitalyster on 25.07.2016. + */ +@ApiIgnore +@RestController +public class Index { + @Inject + private WebsocketManager wsHandler; + + @RequestMapping(value = "/", method = RequestMethod.GET, headers = "Connection!=Upgrade") + public ResponseEntity description() { + URI redirectUri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/swagger-ui.html").build().toUri(); + return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).location(redirectUri).build(); + } + + @RequestMapping(value = "/api/status", method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE, headers = "Connection!=Upgrade") + public Status status() { + return Status.getStatus(String.valueOf(wsHandler.getClients().size())); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/Messages.java b/juick-server/src/main/java/com/juick/server/api/Messages.java new file mode 100644 index 00000000..21156d79 --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Messages.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2008-2017, 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.Status; +import com.juick.Tag; +import com.juick.User; +import com.juick.server.util.HttpForbiddenException; +import com.juick.server.util.UserUtils; +import com.juick.service.MessagesService; +import com.juick.service.TagService; +import com.juick.service.UserService; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import javax.inject.Inject; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Collections; +import java.util.List; + +/** + * @author ugnich + */ +@RestController +@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) +public class Messages { + + private static final ResponseEntity> NOT_FOUND = ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(Collections.emptyList()); + + private static final ResponseEntity> FORBIDDEN = ResponseEntity + .status(HttpStatus.FORBIDDEN) + .body(Collections.emptyList()); + + @Inject + private MessagesService messagesService; + @Inject + private UserService userService; + @Inject + private TagService tagService; + + // TODO: serialize image urls + + @RequestMapping("/home") + public ResponseEntity> getHome( + @RequestParam(defaultValue = "0") int before_mid) { + User visitor = UserUtils.getCurrentUser(); + if (!visitor.isAnonymous()) { + int vuid = visitor.getUid(); + List mids = messagesService.getMyFeed(vuid, before_mid, true); + + if (!mids.isEmpty()) + return ResponseEntity.ok(messagesService.getMessages(mids)); + + return NOT_FOUND; + } + return FORBIDDEN; + } + + @RequestMapping("/messages") + public ResponseEntity> getMessages( + @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 media, + @RequestParam(required = false) String tag) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + + List mids; + if (!StringUtils.isEmpty(uname)) { + User user = userService.getUserByName(uname); + if (user != null) { + if (!StringUtils.isEmpty(media)) { + mids = messagesService.getUserPhotos(user.getUid(), 0, before); + } else if (!StringUtils.isEmpty(tag)) { + Tag tagObject = tagService.getTag(tag, false); + if (tagObject != null) { + mids = messagesService.getUserTag(user.getUid(), tagObject.TID, 0, before); + } else { + return NOT_FOUND; + } + } else if (!StringUtils.isEmpty(withrecommended)) { + mids = messagesService.getUserBlogWithRecommendations(user.getUid(), 0, before); + } else if (daysback > 0) { + mids = messagesService.getUserBlogAtDay(user.getUid(), 0, daysback); + } else { + mids = messagesService.getUserBlog(user.getUid(), 0, before); + } + } else { + return NOT_FOUND; + } + } else { + if (!StringUtils.isEmpty(popular)) { + mids = messagesService.getPopular(vuid, before); + } else if (!StringUtils.isEmpty(media)) { + mids = messagesService.getPhotos(vuid, before); + } else if (!StringUtils.isEmpty(tag)) { + Tag tagObject = tagService.getTag(tag, false); + if (tagObject != null) { + mids = messagesService.getTag(tagObject.TID, vuid, before, 20); + } else { + return NOT_FOUND; + } + } else { + mids = messagesService.getAll(vuid, before); + } + } + return ResponseEntity.ok(messagesService.getMessages(mids)); + } + + @GetMapping("/messages/notifications") + public ResponseEntity> getNotifications( + @RequestParam(required = false) Long before + ) { + User visitor = UserUtils.getCurrentUser(); + LocalDateTime beforeTime = before != null ? + LocalDateTime.ofInstant(Instant.ofEpochMilli(before), ZoneId.systemDefault()) + : null; + return ResponseEntity.ok(messagesService.getNotifications(visitor, beforeTime)); + } + @RequestMapping("/thread") + public ResponseEntity> getThread( + @RequestParam(defaultValue = "0") int mid) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + com.juick.Message msg = messagesService.getMessage(mid); + if (msg != null) { + if (!messagesService.canViewThread(mid, vuid)) { + return FORBIDDEN; + } else { + List replies = messagesService.getReplies(mid); + replies.add(0, msg); + return ResponseEntity.ok(replies); + } + } + return NOT_FOUND; + } + + @ApiIgnore + @RequestMapping("/messages/set_privacy") + @ResponseBody + public ResponseEntity doSetPrivacy( + @RequestParam(defaultValue = "0") int mid) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + com.juick.User user = messagesService.getMessageAuthor(mid); + if (user != null && user.getUid() == vuid && messagesService.setMessagePrivacy(mid)) { + return ResponseEntity.ok(Status.OK); + } + throw new HttpForbiddenException(); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/Notifications.java b/juick-server/src/main/java/com/juick/server/api/Notifications.java new file mode 100644 index 00000000..5f849080 --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Notifications.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2008-2017, 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.Message; +import com.juick.Status; +import com.juick.ExternalToken; +import com.juick.User; +import com.juick.server.helpers.AnonymousUser; +import com.juick.server.util.HttpBadRequestException; +import com.juick.server.util.HttpForbiddenException; +import com.juick.service.MessagesService; +import com.juick.service.PushQueriesService; +import com.juick.service.SubscriptionService; +import com.juick.server.util.UserUtils; +import com.juick.service.UserService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import javax.inject.Inject; +import java.io.IOException; +import java.security.Principal; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created by vitalyster on 24.10.2016. + */ +@RestController +public class Notifications { + + @Inject + private PushQueriesService pushQueriesService; + @Inject + private MessagesService messagesService; + @Inject + private SubscriptionService subscriptionService; + @Inject + private UserService userService; + + + private User collectTokens(Integer uid) { + User user = userService.getUserByUID(uid).orElse(AnonymousUser.INSTANCE); + pushQueriesService.getGCMRegID(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "gcm", t, null))); + pushQueriesService.getAPNSToken(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "apns", t, null))); + pushQueriesService.getMPNSURL(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "mpns", t, null))); + return user; + } + + @ApiIgnore + @RequestMapping(value = "/notifications", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity> doGet( + @RequestParam(required = false, defaultValue = "0") int uid, + @RequestParam(required = false, defaultValue = "0") int mid, + @RequestParam(required = false, defaultValue = "0") int rid) { + User visitor = UserUtils.getCurrentUser(); + if ((visitor.getUid() == 0) || !(visitor.getName().equals("juick"))) { + throw new HttpForbiddenException(); + } + if (uid > 0 && mid == 0) { + // PM + return ResponseEntity.ok(Collections.singletonList(collectTokens(uid))); + } else { + if (mid > 0) { + Message msg = messagesService.getMessage(mid); + if (msg != null) { + List users; + if (rid > 0) { + Message reply = messagesService.getReply(mid, rid); + users = subscriptionService.getUsersSubscribedToComments(mid, reply.getUser().getUid()); + } else { + users = subscriptionService.getSubscribedUsers(msg.getUser().getUid(), mid); + } + + return ResponseEntity.ok(users.stream().map(User::getUid) + .map(this::collectTokens).collect(Collectors.toList())); + } + } + } + throw new HttpBadRequestException(); + } + + @ApiIgnore + @RequestMapping(value = "/notifications", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status doDelete( + @RequestBody List list) throws IOException { + User visitor = UserUtils.getCurrentUser(); + // FIXME: it is possible to delete other user's tokens + if ((visitor.getUid() == 0) || !(visitor.getName().equals("juick"))) { + throw new HttpForbiddenException(); + } + list.forEach(t -> { + switch (t.getType()) { + case "gcm": + pushQueriesService.deleteGCMToken(t.getToken()); + break; + case "apns": + pushQueriesService.deleteAPNSToken(t.getToken()); + break; + case "mpns": + pushQueriesService.deleteMPNSToken(t.getToken()); + break; + default: + throw new HttpBadRequestException(); + } + }); + + return Status.OK; + } + + @ApiIgnore + @RequestMapping(value = "/notifications", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status doPut( + @RequestBody List list) throws IOException { + User visitor = UserUtils.getCurrentUser(); + if (visitor.getUid() == 0) { + throw new HttpForbiddenException(); + } + list.forEach(t -> { + switch (t.getType()) { + case "gcm": + pushQueriesService.addGCMToken(visitor.getUid(), t.getToken()); + break; + case "apns": + pushQueriesService.addAPNSToken(visitor.getUid(), t.getToken()); + break; + case "mpns": + pushQueriesService.addMPNSToken(visitor.getUid(), t.getToken()); + break; + default: + throw new HttpBadRequestException(); + } + }); + return Status.OK; + } + + @Deprecated + @RequestMapping(value = "/android/register", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status doAndroidRegister( + @RequestParam(name = "regid") String regId) { + User visitor = UserUtils.getCurrentUser(); + if (visitor.getUid() == 0) { + throw new HttpForbiddenException(); + } + pushQueriesService.addGCMToken(visitor.getUid(), regId); + return Status.OK; + } + + @Deprecated + @RequestMapping(value = "/android/unregister", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status doAndroidUnRegister(@RequestParam(name = "regid") String regId) { + pushQueriesService.deleteGCMToken(regId); + return Status.OK; + } + + @Deprecated + @RequestMapping(value = "/winphone/register", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status doWinphoneRegister( + Principal principal, + @RequestParam(name = "url") String regId) { + User visitor = UserUtils.getCurrentUser(); + pushQueriesService.addMPNSToken(visitor.getUid(), regId); + return Status.OK; + } + + @Deprecated + @RequestMapping(value = "/winphone/unregister", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Status doWinphoneUnRegister(@RequestParam(name = "url") String regId) { + pushQueriesService.deleteMPNSToken(regId); + return Status.OK; + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/PM.java b/juick-server/src/main/java/com/juick/server/api/PM.java new file mode 100644 index 00000000..4c3128ed --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/PM.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2008-2017, 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.User; +import com.juick.server.ServerManager; +import com.juick.server.helpers.AnonymousUser; +import com.juick.server.helpers.PrivateChats; +import com.juick.server.util.*; +import com.juick.service.PMQueriesService; +import com.juick.service.UserService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import rocks.xmpp.addr.Jid; +import rocks.xmpp.core.stanza.model.Message; + +import javax.inject.Inject; +import java.util.List; + +/** + * @author ugnich + */ +@RestController +public class PM { + @Inject + private UserService userService; + @Inject + private PMQueriesService pmQueriesService; + @Inject + private ServerManager serverManager; + + @RequestMapping(value = "/pm", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public List doGetPM( + @RequestParam(required = false) String uname) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + int uid = 0; + if (uname != null && uname.matches("^[a-zA-Z0-9\\-]{2,16}$")) { + uid = userService.getUIDbyName(uname); + } + + if (uid == 0) { + throw new HttpBadRequestException(); + } + + return pmQueriesService.getPMMessages(vuid, uid); + } + + @RequestMapping(value = "/pm", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public com.juick.Message doPostPM( + @RequestParam String uname, + @RequestParam String body) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + User userTo = AnonymousUser.INSTANCE; + if (WebUtils.isUserName(uname)) { + userTo = userService.getUserByName(uname); + } + + if (userTo.getUid() == 0 || body == null || body.length() < 1 || body.length() > 10240) { + throw new HttpBadRequestException(); + } + + if (userService.isInBLAny(userTo.getUid(), vuid)) { + throw new HttpForbiddenException(); + } + + if (pmQueriesService.createPM(vuid, userTo.getUid(), body)) { + Message msg = new Message(); + msg.setFrom(Jid.of("juick@juick.com")); + msg.setTo(Jid.of(String.format("%d@push.juick.com", userTo.getUid()))); + com.juick.Message jmsg = new com.juick.Message(); + jmsg.setUser(visitor); + jmsg.setText(body); + jmsg.setTo(userTo); + msg.addExtension(jmsg); + serverManager.sendMessage(msg); + + msg.setTo(Jid.of(String.format("%d@ws.juick.com", userTo.getUid()))); + serverManager.sendMessage(msg); + + List jids = userService.getJIDsbyUID(userTo.getUid()); + for (String jid : jids) { + Message mm = new Message(); + mm.setTo(Jid.of(jid)); + mm.setType(Message.Type.CHAT); + if (pmQueriesService.havePMinRoster(vuid, jid)) { + mm.setFrom(Jid.of(jmsg.getUser().getName(), "juick.com", "Juick")); + mm.setBody(body); + } else { + mm.setFrom(Jid.of("juick", "juick.com", "Juick")); + mm.setBody("Private message from @" + jmsg.getUser().getName() + ":\n" + body); + } + serverManager.sendMessage(mm); + } + return jmsg; + + } + throw new HttpBadRequestException(); + } + @RequestMapping(value = "groups_pms", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public PrivateChats doGetGroupsPMs( + @RequestParam(defaultValue = "5") int cnt) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + if (cnt < 3) { + cnt = 3; + } + if (cnt > 10) { + cnt = 10; + } + + List lastconv = pmQueriesService.getPMLastConversationsUsers(vuid, cnt); + if (lastconv != null && !lastconv.isEmpty()) { + PrivateChats pms = new PrivateChats(); + pms.setUsers(lastconv); + return pms; + } + throw new HttpNotFoundException(); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/Post.java b/juick-server/src/main/java/com/juick/server/api/Post.java new file mode 100644 index 00000000..7f014d9e --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Post.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2008-2017, 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.User; +import com.juick.server.ServerManager; +import com.juick.server.EmailManager; +import com.juick.server.util.*; +import com.juick.service.MessagesService; +import com.juick.service.SubscriptionService; +import com.juick.service.UserService; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.mail.util.MimeMessageParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import rocks.xmpp.addr.Jid; +import rocks.xmpp.core.stanza.model.Message; +import rocks.xmpp.extensions.nick.model.Nickname; +import rocks.xmpp.extensions.oob.model.x.OobX; + +import javax.inject.Inject; +import javax.mail.Session; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.Scanner; +import java.util.UUID; + +/** + * Created by vt on 24/11/2016. + */ +@RestController +public class Post { + private static Logger logger = LoggerFactory.getLogger(ServerManager.class); + + @Inject + private UserService userService; + @Inject + private ServerManager serverManager; + @Inject + private MessagesService messagesService; + @Inject + private SubscriptionService subscriptionService; + @Value("${upload_tmp_dir:/var/www/juick.com/i/tmp/}") + private String tmpDir; + @Value("${img_path:/var/www/juick.com/i/}") + private String imgDir; + + @RequestMapping(value = "/post", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + @ResponseStatus(value = HttpStatus.OK) + public void doPostMessage( + @RequestParam String body, + @RequestParam(required = false) String img, + @RequestParam(required = false) MultipartFile attach) throws IOException { + User visitor = UserUtils.getCurrentUser(); + + if (visitor.isAnonymous()) + throw new HttpForbiddenException(); + + if (body == null || body.length() < 1 || body.length() > 4096) { + throw new HttpBadRequestException(); + } + body = body.replace("\r", StringUtils.EMPTY); + + String attachmentFName = HttpUtils.receiveMultiPartFile(attach, tmpDir); + + if (StringUtils.isBlank(attachmentFName) && img != null && img.length() > 10) { + try { + URL imgUrl = new URL(img); + attachmentFName = HttpUtils.downloadImage(imgUrl, tmpDir); + } catch (Exception e) { + logger.error("DOWNLOAD ERROR", e); + throw new HttpBadRequestException(); + } + } + serverManager.processMessage(visitor, body, attachmentFName); + } + + @RequestMapping(value = "/comment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public com.juick.Message doPostComment( + @RequestParam(defaultValue = "0") int mid, + @RequestParam(defaultValue = "0") int rid, + @RequestParam String body, + @RequestParam(required = false) String img, + @RequestParam(required = false) MultipartFile attach) + throws IOException { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + if (mid == 0) { + throw new HttpBadRequestException(); + } + com.juick.Message msg = messagesService.getMessage(mid); + if (msg == null) { + throw new HttpNotFoundException(); + } + + com.juick.Message reply = null; + if (rid > 0) { + reply = messagesService.getReply(mid, rid); + if (reply == null) { + throw new HttpNotFoundException(); + } + } + + if (body == null || body.length() < 1 || body.length() > 4096) { + throw new HttpBadRequestException(); + } + body = body.replace("\r", StringUtils.EMPTY); + + if ((msg.ReadOnly && msg.getUser().getUid() != vuid) || userService.isInBLAny(msg.getUser().getUid(), vuid) + || (reply != null && userService.isInBLAny(reply.getUser().getUid(), vuid))) { + throw new HttpForbiddenException(); + } + + String attachmentFName = HttpUtils.receiveMultiPartFile(attach, tmpDir); + + if (StringUtils.isBlank(attachmentFName) && img != null && img.length() > 10) { + try { + attachmentFName = HttpUtils.downloadImage(new URL(img), tmpDir); + } catch (Exception e) { + logger.error("DOWNLOAD ERROR", e); + throw new HttpBadRequestException(); + } + } + + String attachmentType = StringUtils.isNotEmpty(attachmentFName) ? attachmentFName.substring(attachmentFName.length() - 3) : null; + int ridnew = messagesService.createReply(mid, rid, vuid, body, attachmentType); + subscriptionService.subscribeMessage(mid, vuid); + + com.juick.Message jmsg = messagesService.getReply(mid, ridnew); + + Message xmsg = new Message(); + xmsg.setFrom(Jid.of("juick@juick.com")); + xmsg.setType(Message.Type.CHAT); + xmsg.setThread("juick-" + mid); + xmsg.addExtension(jmsg); + + String quote = reply != null ? StringUtils.defaultString(reply.getText()) : StringUtils.defaultString(msg.getText()); + if (quote.length() >= 50) { + quote = quote.substring(0, 47) + "..."; + } + + xmsg.addExtension(new Nickname("@" + jmsg.getUser().getName())); + + if (StringUtils.isNotEmpty(attachmentFName)) { + String fname = mid + "-" + ridnew + "." + attachmentType; + String attachmentURL = "http://i.juick.com/photos-1024/" + fname; + + ImageUtils.saveImageWithPreviews(attachmentFName, fname, tmpDir, imgDir); + + body = attachmentURL + "\n" + body; + try { + xmsg.addExtension(new OobX(new URI(attachmentURL))); + } catch (URISyntaxException e) { + logger.error("invalid uri: {}, exception {}", attachmentURL, e); + } + } + + xmsg.setBody("Reply by @" + jmsg.getUser().getName() + ":\n>" + quote + "\n" + body + "\n\n#" + + mid + "/" + ridnew + " http://juick.com/" + mid + "#" + ridnew); + + xmsg.setTo(Jid.of("juick@s2s.juick.com")); + serverManager.sendMessage(xmsg); + + xmsg.setTo(Jid.of("juick@ws.juick.com")); + serverManager.sendMessage(xmsg); + + xmsg.setTo(Jid.of("juick@push.juick.com")); + serverManager.sendMessage(xmsg); + return jmsg; + } + + Session session = Session.getDefaultInstance(new Properties()); + + @PostMapping("/mail") + @ResponseStatus(value = HttpStatus.OK) + public void processMail(InputStream data) throws Exception { + MimeMessage msg = new MimeMessage(session, data); + String from = msg.getFrom().length > 1 ? ((InternetAddress) msg.getSender()).getAddress() + : ((InternetAddress) msg.getFrom()[0]).getAddress(); + logger.info("got msg from {}", from); + + User visitor = userService.getUserByEmail(from); + if (!visitor.isAnonymous()) { + MimeMessageParser parser = new MimeMessageParser(msg); + parser.parse(); + final String[] body = {parser.getPlainContent()}; + if (body[0] == null) { + parser.getAttachmentList().stream() + .filter(a -> a.getContentType().equals("text/plain")).findFirst() + .ifPresent(a -> { + try { + body[0] = IOUtils.toString(a.getInputStream(), StandardCharsets.UTF_8); + logger.info("got text: {}", body[0]); + } catch (IOException e) { + logger.info("attachment error: {}", e); + } + }); + } + final String[] attachmentFName = new String[1]; + parser.getAttachmentList().stream().filter(a -> + a.getContentType().equals("image/jpeg") || a.getContentType().equals("image/png")) + .findFirst().ifPresent(a -> { + logger.info("got attachment: {}", a.getContentType()); + String attachmentType; + if (a.getContentType().equals("image/jpeg")) { + attachmentType = "jpg"; + } else { + attachmentType = "png"; + } + attachmentFName[0] = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType; + try { + logger.info("got inputstream: {}", a.getInputStream()); + FileOutputStream fos = new FileOutputStream(Paths.get(tmpDir, attachmentFName[0]).toString()); + IOUtils.copy(a.getInputStream(), fos); + fos.close(); + } catch (IOException e) { + logger.info("attachment error: {}", e); + } + }); + String[] inReplyToHeaders = msg.getHeader("In-Reply-To"); + if (inReplyToHeaders != null && inReplyToHeaders.length > 0) { + Scanner inReplyToScanner = new Scanner(inReplyToHeaders[0].trim()).useDelimiter(EmailManager.MSGID_PATTERN); + int mid = Integer.parseInt(inReplyToScanner.next()); + int rid = Integer.parseInt(inReplyToScanner.next()); + logger.info("Message is reply to #{}/{}", mid, rid); + body[0] = rid > 0 ? String.format("#%d/%d %s", mid, rid, body[0]) + : String.format("#%d %s", mid, body[0]); + } + rocks.xmpp.core.stanza.model.Message xmsg = new rocks.xmpp.core.stanza.model.Message(); + xmsg.setType(rocks.xmpp.core.stanza.model.Message.Type.CHAT); + xmsg.setFrom(Jid.of(String.valueOf(visitor.getUid()), "uid.juick.com", "mail")); + xmsg.setTo(Jid.of("juick@juick.com/Juick")); + xmsg.setBody(body[0]); + try { + if (StringUtils.isNotEmpty(attachmentFName[0])) { + String attachmentUrl = String.format("juick://%s", attachmentFName[0]); + xmsg.addExtension(new OobX(new URI(attachmentUrl), "!!!!Juick!!")); + } + serverManager.sendMessage(xmsg); + } catch (URISyntaxException e1) { + logger.warn("attachment error", e1); + } + } else { + logger.info("not registered: {}", from); + } + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/Service.java b/juick-server/src/main/java/com/juick/server/api/Service.java new file mode 100644 index 00000000..12ffee9c --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Service.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2017, 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.Message; +import com.juick.User; +import com.juick.server.util.HttpBadRequestException; +import com.juick.server.util.HttpForbiddenException; +import com.juick.server.util.UserUtils; +import com.juick.service.CrosspostService; +import com.juick.service.MessagesService; +import com.juick.service.SubscriptionService; +import com.juick.service.UserService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import springfox.documentation.annotations.ApiIgnore; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.List; + +/** + * TODO: configure spring-security to allow only admin role + */ +@ApiIgnore +@RestController +public class Service { + @Inject + private SubscriptionService subscriptionService; + @Inject + private MessagesService messagesService; + @Inject + private CrosspostService crosspostService; + @Inject + private UserService userService; + + @RequestMapping(value = "/subscriptions", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public List doGet( + @RequestParam(defaultValue = "0") int mid, + @RequestParam(defaultValue = "0") int uid) throws IOException { + User visitor = UserUtils.getCurrentUser(); + if ((visitor.getUid() == 0) && !(visitor.getName().equals("juick"))) { + throw new HttpForbiddenException(); + } + if (uid > 0) { + return subscriptionService.getSubscribedUsers(uid, mid); + } else { + // thread + Message msg = messagesService.getMessage(mid); + if (msg != null) { + return subscriptionService.getUsersSubscribedToComments(mid, msg.getUser().getUid()); + } + } + throw new HttpBadRequestException(); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/Tags.java b/juick-server/src/main/java/com/juick/server/api/Tags.java new file mode 100644 index 00000000..8ced4ec9 --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Tags.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2008-2017, 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.User; +import com.juick.server.helpers.TagStats; +import com.juick.server.util.UserUtils; +import com.juick.service.TagService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.inject.Inject; +import java.util.List; + +/** + * Created by vitalyster on 29.11.2016. + */ +@RestController +public class Tags { + @Inject + private TagService tagService; + + @RequestMapping(value = "/tags", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public List tags( + @RequestParam(required = false, defaultValue = "0") int user_id + ) { + User visitor = UserUtils.getCurrentUser(); + if (user_id == 0) { + user_id = visitor.getUid(); + } + if (user_id > 0) { + return tagService.getUserTagStats(user_id); + } + return tagService.getTagStats(); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/Users.java b/juick-server/src/main/java/com/juick/server/api/Users.java new file mode 100644 index 00000000..8b273354 --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/Users.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008-2017, 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.User; +import com.juick.server.util.HttpForbiddenException; +import com.juick.server.util.HttpNotFoundException; +import com.juick.service.UserService; +import com.juick.server.util.UserUtils; +import com.juick.server.util.WebUtils; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author ugnich + */ +@RestController +public class Users { + @Inject + private UserService userService; + + @RequestMapping(value = "/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public String getAuthToken() { + return userService.getHashByUID(UserUtils.getCurrentUser().getUid()); + } + + @RequestMapping(value = "/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public List doGetUsers( + @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)); + } + + if (!users.isEmpty()) + return users; + if (!UserUtils.getCurrentUser().isAnonymous()) { + return Collections.singletonList(UserUtils.getCurrentUser()); + } + + throw new HttpNotFoundException(); + } + + @RequestMapping(value = "/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public List doGetUserRead( + @RequestParam String uname) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + int uid = 0; + if (uname == null) { + uid = vuid; + } else { + if (WebUtils.isUserName(uname)) { + com.juick.User u = userService.getUserByName(uname); + if (u != null && u.getUid() > 0) { + uid = u.getUid(); + } + } + } + + if (uid > 0) { + List uids = userService.getUserRead(uid); + if (uids.size() > 0) { + List users = userService.getUsersByID(uids); + if (users.size() > 0) { + return users; + } + } + } + throw new HttpNotFoundException(); + } + + @RequestMapping(value = "/users/readers", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public List doGetUserReaders( + @RequestParam String uname) { + User visitor = UserUtils.getCurrentUser(); + int vuid = visitor.getUid(); + if (vuid == 0) { + throw new HttpForbiddenException(); + } + int uid = 0; + if (uname == null) { + uid = vuid; + } else { + if (WebUtils.isUserName(uname)) { + com.juick.User u = userService.getUserByName(uname); + if (u != null && u.getUid() > 0) { + uid = u.getUid(); + } + } + } + + if (uid > 0) { + return userService.getUserReaders(uid); + } + throw new HttpNotFoundException(); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/webhooks/MessengerWebhook.java b/juick-server/src/main/java/com/juick/server/api/webhooks/MessengerWebhook.java new file mode 100644 index 00000000..e746b67d --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/webhooks/MessengerWebhook.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008-2017, 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.webhooks; + +import com.github.messenger4j.exception.MessengerVerificationException; +import com.juick.server.MessengerManager; +import com.juick.server.util.HttpForbiddenException; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import javax.inject.Inject; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Created by vitalyster on 27.03.2017. + */ +@ApiIgnore +@RestController +public class MessengerWebhook { + private static Logger logger = LoggerFactory.getLogger(MessengerWebhook.class); + + @Inject + private MessengerManager messengerManager; + + @RequestMapping(value = "/fbwbhk", method = RequestMethod.GET) + public ResponseEntity verifyHook(@RequestParam(name = "hub.mode") String hubMode, + @RequestParam(name = "hub.challenge") Integer hubChallenge, + @RequestParam(name = "hub.verify_token") String verifyToken) { + if (hubMode.equals("subscribe") && verifyToken.equals(messengerManager.getFacebookVerifyToken())) { + return new ResponseEntity<>(hubChallenge, HttpStatus.OK); + } + throw new HttpForbiddenException(); + } + @RequestMapping(value = "/fbwbhk", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void processUpdate(@RequestHeader(name = "X-Hub-Signature", required = false) String signature, InputStream body) throws IOException, MessengerVerificationException { + String data = IOUtils.toString(body, StandardCharsets.UTF_8); + messengerManager.processUpdate(signature, data); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/webhooks/SkypeWebhook.java b/juick-server/src/main/java/com/juick/server/api/webhooks/SkypeWebhook.java new file mode 100644 index 00000000..425a9d10 --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/webhooks/SkypeWebhook.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2017, 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.webhooks; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import springfox.documentation.annotations.ApiIgnore; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Created by vitalyster on 18.07.2016. + */ +@ApiIgnore +@RestController +public class SkypeWebhook { + private static final Logger logger = LoggerFactory.getLogger(SkypeWebhook.class); + @RequestMapping(value = "/skypebotendpoint", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void doPost(InputStream body) throws IOException { + String data = IOUtils.toString(body, StandardCharsets.UTF_8); + logger.info("got data: {}", data); + } +} diff --git a/juick-server/src/main/java/com/juick/server/api/webhooks/TelegramWebhook.java b/juick-server/src/main/java/com/juick/server/api/webhooks/TelegramWebhook.java new file mode 100644 index 00000000..9c4f64ce --- /dev/null +++ b/juick-server/src/main/java/com/juick/server/api/webhooks/TelegramWebhook.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008-2017, 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.webhooks; + +import com.juick.server.TelegramBotManager; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import springfox.documentation.annotations.ApiIgnore; + +import javax.inject.Inject; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Created by vt on 24/11/2016. + */ +@ApiIgnore +@RestController +public class TelegramWebhook { + @Inject + private TelegramBotManager telegramBotManager; + + @RequestMapping(value = "/tlgmbtwbhk", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void processUpdate(InputStream body) throws Exception { + String data = IOUtils.toString(body, StandardCharsets.UTF_8); + telegramBotManager.processUpdate(data); + } +} -- cgit v1.2.3