diff options
author | 2019-03-16 23:56:27 +0300 | |
---|---|---|
committer | 2019-03-16 23:56:27 +0300 | |
commit | 06105f76dbfa3b65e63ed06f9c4d5107bd49ed88 (patch) | |
tree | 5702c01cec9688039d891f4a711878706101c1c5 /src/main/java/com/juick/server/api | |
parent | 3ea4cd1942fa4e763034da11c5fa429407b67829 (diff) | |
parent | a49105285d0d7719d7f222a507af2d5ac5b4bdb1 (diff) |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'src/main/java/com/juick/server/api')
19 files changed, 294 insertions, 135 deletions
diff --git a/src/main/java/com/juick/server/api/ApiSocialLogin.java b/src/main/java/com/juick/server/api/ApiSocialLogin.java index 72cda0af..be306fe9 100644 --- a/src/main/java/com/juick/server/api/ApiSocialLogin.java +++ b/src/main/java/com/juick/server/api/ApiSocialLogin.java @@ -82,6 +82,7 @@ public class ApiSocialLogin { @Inject private ObjectMapper jsonMapper; private ServiceBuilder facebookBuilder, twitterBuilder, vkBuilder; + private OAuth20Service facebookAuthService, vkAuthService; @Value("${twitter_consumer_key:appid}") private String twitterConsumerKey; @@ -117,6 +118,16 @@ public class ApiSocialLogin { verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) .setAudience(Collections.singletonList(googleClientId)) .build(); + facebookAuthService = facebookBuilder + .apiSecret(FACEBOOK_SECRET) + .callback(FACEBOOK_REDIRECT) + .scope("email") + .build(FacebookApi.instance()); + vkAuthService = vkBuilder + .apiSecret(VK_SECRET) + .scope("friends,wall,offline") + .callback(VK_REDIRECT) + .build(VkontakteApi.instance()); } @GetMapping("/api/_fblogin") @@ -125,13 +136,7 @@ public class ApiSocialLogin { if (StringUtils.isBlank(code)) { String fbstate = UUID.randomUUID().toString(); crosspostService.addFacebookState(fbstate, state); - OAuth20Service facebookAuthService = facebookBuilder - .apiSecret(FACEBOOK_SECRET) - .callback(FACEBOOK_REDIRECT) - .scope("email") - .state(fbstate) - .build(FacebookApi.instance()); - return "redirect:" + facebookAuthService.getAuthorizationUrl(); + return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate); } String redirectUrl = crosspostService.verifyFacebookState(state); @@ -140,53 +145,43 @@ public class ApiSocialLogin { logger.error("state is missing"); throw new HttpBadRequestException(); } - OAuth20Service facebookService = facebookBuilder - .apiKey(FACEBOOK_APPID) - .apiSecret(FACEBOOK_SECRET) - .callback(FACEBOOK_REDIRECT) - .scope("email") - .state(state) - .build(FacebookApi.instance()); - OAuth2AccessToken token = facebookService.getAccessToken(code); - final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.10/me?fields=id,name,link,verified,email"); - facebookService.signRequest(token, meRequest); - String graph = facebookService.execute(meRequest).getBody(); + OAuth2AccessToken token = facebookAuthService.getAccessToken(code); + final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,email"); + facebookAuthService.signRequest(token, meRequest); + String graph = facebookAuthService.execute(meRequest).getBody(); if (StringUtils.isBlank(graph)) { logger.error("FACEBOOK GRAPH ERROR"); throw new HttpBadRequestException(); } User fb = jsonMapper.readValue(graph, User.class); long fbID = NumberUtils.toLong(fb.getId(), 0); - if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) { - logger.error("Missing required fields, id: {}, name: {}, link: {}", fbID, fb.getName(), fb.getLink()); + if (fbID == 0 || StringUtils.isBlank(fb.getName())) { + logger.error("Missing required fields, id: {}, name: {}", fbID, fb.getName()); throw new HttpBadRequestException(); } int uid = crosspostService.getUIDbyFBID(fbID); if (uid > 0) { - if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName(), fb.getLink())) { + if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName())) { logger.error("error updating facebook user, id: {}, token: {}", fbID, token.getAccessToken()); throw new HttpBadRequestException(); } UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl); uriComponentsBuilder.queryParam("hash", userService.getHashByUID(uid)); return "redirect:" + uriComponentsBuilder.build().toUriString(); - } else if (fb.getVerified()) { - if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName(), fb.getLink())) { + } else { + if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName())) { if (StringUtils.isNotEmpty(fb.getEmail())) { - logger.info("found {} for facebook user {}", fb.getEmail(), fb.getLink()); + logger.info("found {} for facebook user {}", fb.getEmail()); Integer userId = crosspostService.getUIDbyFBID(fbID); if (!emailService.getEmails(userId, false).contains(fb.getEmail())) { emailService.addEmail(userId, fb.getEmail()); } } - logger.info("email not found for facebook user {}", fb.getLink()); + logger.info("email not found for facebook user {}", fb.getName()); throw new HttpBadRequestException(); } return "redirect:/signup?type=fb&hash=" + state; - } else { - logger.error("Facebook account is not verified, id: {}", fbID); - throw new HttpBadRequestException(); } }/* @GetMapping("/_twitter") @@ -244,13 +239,7 @@ public class ApiSocialLogin { if (StringUtils.isBlank(code)) { String vkstate = UUID.randomUUID().toString(); crosspostService.addVKState(vkstate, state); - OAuth20Service vkAuthService = vkBuilder - .apiSecret(VK_SECRET) - .scope("friends,wall,offline") - .state(vkstate) - .callback(VK_REDIRECT) - .build(VkontakteApi.instance()); - return "redirect:" + vkAuthService.getAuthorizationUrl(); + return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate); } String redirectUrl = crosspostService.verifyVKState(state); @@ -258,16 +247,11 @@ public class ApiSocialLogin { logger.error("state is missing"); throw new HttpBadRequestException(); } - - OAuth20Service vkService = vkBuilder - .apiKey(VK_APPID) - .apiSecret(VK_SECRET) - .build(VkontakteApi.instance()); - OAuth2AccessToken token = vkService.getAccessToken(code); + OAuth2AccessToken token = vkAuthService.getAccessToken(code); OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.73"); - vkService.signRequest(token, meRequest); - String graph = vkService.execute(meRequest).getBody(); + vkAuthService.signRequest(token, meRequest); + String graph = vkAuthService.execute(meRequest).getBody(); com.juick.model.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).getUsers().get(0); String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName(); diff --git a/src/main/java/com/juick/server/api/Index.java b/src/main/java/com/juick/server/api/Index.java index 56f01370..4573641b 100644 --- a/src/main/java/com/juick/server/api/Index.java +++ b/src/main/java/com/juick/server/api/Index.java @@ -17,10 +17,7 @@ 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; @@ -28,7 +25,6 @@ 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; /** @@ -36,19 +32,11 @@ import java.net.URI; */ @RestController public class Index { - @Inject - private WebsocketManager wsHandler; @ApiIgnore - @RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET, headers = "Connection!=Upgrade") + @RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET) public ResponseEntity<Void> description() { URI redirectUri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/swagger-ui.html").build().toUri(); return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).location(redirectUri).build(); } - @ApiIgnore - @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/src/main/java/com/juick/server/api/Messages.java b/src/main/java/com/juick/server/api/Messages.java index e105890f..5c791df4 100644 --- a/src/main/java/com/juick/server/api/Messages.java +++ b/src/main/java/com/juick/server/api/Messages.java @@ -179,6 +179,9 @@ public class Messages { } msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser())); msg.setRecommendations(new HashSet<>(messagesService.getMessageRecommendations(msg.getMid()))); + msg.getRecommendations().forEach(r -> { + r.setAvatar(webApp.getAvatarUrl(r)); + }); List<com.juick.Message> replies = messagesService.getReplies(visitor, mid); replies.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser()))); if (!visitor.isAnonymous()) { diff --git a/src/main/java/com/juick/server/api/Notifications.java b/src/main/java/com/juick/server/api/Notifications.java index 000a9f3b..ea1d5c54 100644 --- a/src/main/java/com/juick/server/api/Notifications.java +++ b/src/main/java/com/juick/server/api/Notifications.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017, Juick + * Copyright (C) 2008-2019, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -24,14 +24,19 @@ import com.juick.User; import com.juick.model.AnonymousUser; import com.juick.server.util.HttpBadRequestException; import com.juick.server.util.HttpForbiddenException; +import com.juick.server.util.UserUtils; 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.TelegramService; import com.juick.service.UserService; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RequestBody; +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; @@ -55,6 +60,8 @@ public class Notifications { private SubscriptionService subscriptionService; @Inject private UserService userService; + @Inject + private TelegramService telegramService; private User collectTokens(Integer uid) { @@ -63,6 +70,14 @@ public class Notifications { 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))); + List<ExternalToken> xmppJids = userService.getJIDsbyUID(uid).stream() + .map(jid -> new ExternalToken(null, "xmpp", jid, null)) + .collect(Collectors.toList()); + user.getTokens().addAll(xmppJids); + List<ExternalToken> tgIds = telegramService.getTelegramIdentifiers(Collections.singletonList(user)).stream() + .map(tgId -> new ExternalToken(null, "durov", String.valueOf(tgId), null)) + .collect(Collectors.toList()); + user.getTokens().addAll(tgIds); return user; } diff --git a/src/main/java/com/juick/server/api/PM.java b/src/main/java/com/juick/server/api/PM.java index a80ad0dc..e61fef6e 100644 --- a/src/main/java/com/juick/server/api/PM.java +++ b/src/main/java/com/juick/server/api/PM.java @@ -99,6 +99,7 @@ public class PM { jmsg.setUser(visitor); jmsg.setText(body); jmsg.setTo(userTo); + jmsg.getUser().setAvatar(webApp.getAvatarUrl(jmsg.getUser())); applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg, Collections.singletonList(jmsg.getTo()))); return jmsg; diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java index 6f9ab290..33b3704b 100644 --- a/src/main/java/com/juick/server/api/Users.java +++ b/src/main/java/com/juick/server/api/Users.java @@ -18,21 +18,24 @@ package com.juick.server.api; import com.juick.User; +import com.juick.model.AnonymousUser; import com.juick.model.ApplicationStatus; -import com.juick.model.UserInfo; -import com.juick.server.util.HttpForbiddenException; import com.juick.server.util.HttpNotFoundException; -import com.juick.server.www.WebApp; -import com.juick.service.CrosspostService; -import com.juick.service.EmailService; -import com.juick.service.MessagesService; -import com.juick.service.UserService; +import com.juick.server.util.HttpUtils; import com.juick.server.util.UserUtils; import com.juick.server.util.WebUtils; +import com.juick.server.www.WebApp; +import com.juick.service.*; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.inject.Inject; +import java.io.IOException; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -52,6 +55,10 @@ public class Users { private EmailService emailService; @Inject private WebApp webApp; + @Inject + private ImagesService imagesService; + @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") + private String tmpDir; @RequestMapping(value = "/api/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public String getAuthToken() { @@ -94,16 +101,21 @@ public class Users { me.setRead(userService.getUserFriends(visitor.getUid())); me.setReaders(userService.getUserReaders(visitor.getUid())); me.setAvatar(webApp.getAvatarUrl(visitor)); - return me; + return (SecureUser)userService.getUserInfo(me); + } + @PostMapping("/api/me/upload") + public void updateInfo(@RequestParam MultipartFile avatar) throws IOException { + User visitor = UserUtils.getCurrentUser(); + String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, tmpDir).getHost(); + if (StringUtils.isNotEmpty(avatarTmpPath)) { + imagesService.saveAvatar(avatarTmpPath, visitor.getUid()); + } } @RequestMapping(value = "/api/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public List<User> doGetUserRead( @RequestParam String uname) { User visitor = UserUtils.getCurrentUser(); - if (visitor.isAnonymous()) { - throw new HttpForbiddenException(); - } int uid = 0; if (uname == null) { uid = visitor.getUid(); @@ -128,9 +140,6 @@ public class Users { public List<User> doGetUserReaders( @RequestParam String uname) { User visitor = UserUtils.getCurrentUser(); - if (visitor.isAnonymous()) { - throw new HttpForbiddenException(); - } int uid = 0; if (uname == null) { uid = visitor.getUid(); @@ -152,22 +161,36 @@ public class Users { } @GetMapping("/api/info/{uname}") - public UserInfo getUserInfo(@PathVariable String uname) { + public User getUserInfo(@PathVariable String uname) { User user = userService.getUserByName(uname); if (!user.isBanned()) { + user.setRead(doGetUserRead(uname)); + user.setReaders(doGetUserReaders(uname)); user.setAvatar(webApp.getAvatarUrl(user)); return userService.getUserInfo(user); } throw new HttpNotFoundException(); } + @Deprecated + @GetMapping(value = "/api/avatar", produces = MediaType.IMAGE_PNG_VALUE) + public byte[] getAvatarUrl( + @RequestParam(required = false) String uname, + @RequestParam(required = false) String jid) + throws IOException { + User user = AnonymousUser.INSTANCE; + if (StringUtils.isNotEmpty(uname)) { + user = userService.getUserByName(uname); + } + if (user.isAnonymous() && StringUtils.isNotEmpty(jid)) { + user = userService.getUserByJID(jid); + } + return IOUtils.toByteArray(URI.create(webApp.getAvatarUrl(user))); + } class SecureUser extends User { public String getHash() { return getAuthHash(); } - public UserInfo getUserInfo() { - return userService.getUserInfo(this); - } public List<String> getJIDs() { return userService.getAllJIDs(this); } diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java index 305b7c4a..4e375a54 100644 --- a/src/main/java/com/juick/server/api/activity/Profile.java +++ b/src/main/java/com/juick/server/api/activity/Profile.java @@ -3,6 +3,7 @@ package com.juick.server.api.activity; import com.fasterxml.jackson.databind.ObjectMapper; import com.juick.Message; import com.juick.User; +import com.juick.formatters.PlainTextFormatter; import com.juick.model.CommandResult; import com.juick.server.ActivityPubManager; import com.juick.server.CommandsManager; @@ -29,6 +30,8 @@ import com.juick.server.www.WebApp; import com.juick.service.MessagesService; import com.juick.service.UserService; import com.juick.service.activities.*; +import com.overzealous.remark.Remark; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,12 +47,15 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import javax.inject.Inject; +import java.io.InputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -81,6 +87,8 @@ public class Profile { private ObjectMapper jsonMapper; @Inject private WebApp webApp; + @Inject + private Remark remarkConverter; @GetMapping(value = "/u/{userName}", produces = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) public Person getUser(@PathVariable String userName) { @@ -252,32 +260,17 @@ public class Profile { } @PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE}) - public ResponseEntity<Void> processInbox(@RequestBody Activity activity, - @RequestHeader(name = "Host") String host, - @RequestHeader(name = "Date") String date, - @RequestHeader(name = "Digest", required = false) String digest, - @RequestHeader(name = "Content-Type") String contentType, - @RequestHeader(name = "User-Agent", required = false) String userAgent, - @RequestHeader(name = "Accept-Encoding", required = false) String acceptEncoding, - @RequestHeader(name = "Signature", required = false) String signature) throws Exception { - UriComponents componentsBuilder = ServletUriComponentsBuilder.fromCurrentRequestUri().build(); - Map<String, String> headers = new HashMap<>(); - headers.put("host", host.split(":", 2)[0]); - headers.put("date", date); - headers.put("digest", digest); - headers.put("content-type", contentType); - headers.put("user-agent", userAgent); - headers.put("accept-encoding", acceptEncoding); - boolean valid = signatureManager.verifySignature(signature, URI.create(activity.getActor()), "POST", - componentsBuilder.getPath(), headers); - if (valid) { + public ResponseEntity<CommandResult> processInbox(InputStream inboxData) throws Exception { + String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8); + logger.info("Inbox: {}", inbox); + Activity activity = jsonMapper.readValue(inbox, Activity.class); + User visitor = UserUtils.getCurrentUser(); + if ((StringUtils.isNotEmpty(visitor.getUri().toString()) && visitor.getUri().equals(URI.create(activity.getActor()))) || !visitor.isAnonymous()) { if (activity instanceof Follow) { Follow followRequest = (Follow) activity; - String actor = followRequest.getActor(); - Person follower = (Person) signatureManager.getContext(URI.create(actor)).orElseThrow(HttpBadRequestException::new); applicationEventPublisher.publishEvent( new FollowEvent(this, followRequest)); - return new ResponseEntity<>(HttpStatus.ACCEPTED); + return new ResponseEntity<>(CommandResult.fromString("Follow request accepted"), HttpStatus.ACCEPTED); } if (activity instanceof Undo) { @@ -286,17 +279,10 @@ public class Profile { String objectObject = (String) object.get("object"); if (objectType.equals("Follow")) { applicationEventPublisher.publishEvent(new UndoFollowEvent(this, activity.getActor(), objectObject)); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Undo follow request accepted"), HttpStatus.OK); } else if (objectType.equals("Like") || objectType.equals("Announce")) { applicationEventPublisher.publishEvent(new UndoAnnounceEvent(this, activity.getActor(), objectObject)); - return new ResponseEntity<>(HttpStatus.OK); - } - } - if (activity instanceof Delete) { - if (activity.getObject() instanceof String) { - // Delete user - applicationEventPublisher.publishEvent(new DeleteUserEvent(this, (String)activity.getObject())); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Undo like/announce request accepted"), HttpStatus.OK); } } if (activity instanceof Create) { @@ -305,7 +291,7 @@ public class Profile { if (note.get("type").equals("Note")) { URI noteId = URI.create((String) note.get("id")); if (messagesService.replyExists(noteId)) { - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Reply already exists"), HttpStatus.OK); } else { String inReplyTo = (String) note.get("inReplyTo"); if (StringUtils.isNotBlank(inReplyTo)) { @@ -313,36 +299,50 @@ public class Profile { String postId = activityPubManager.postId(inReplyTo); User user = new User(); user.setUri(URI.create(activity.getActor())); - String attachment = StringUtils.EMPTY; - if (note.get("attachment") != null && ((List) note.get("attachment")).size() > 0) { - Map<String, Object> attachmentObj = (Map<String, Object>) ((List<Object>) note.get("attachment")).get(0); - attachment = (String) attachmentObj.get("url"); - } - CommandResult result = commandsManager.processCommand(user, String.format("#%s %s", postId, note.get("content")), URI.create(attachment)); + String markdown = remarkConverter.convertFragment((String) note.get("content")); + String commandBody = note.get("attachment") == null ? markdown : + ((List<Object>) note.get("attachment")).stream().map(attachmentObj -> { + Map<String, String> attachment = (Map<String, String>) attachmentObj; + String attachmentUrl = attachment.get("url"); + String attachmentName = attachment.get("name"); + return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); + }).reduce((source, url) -> String.format("%s\n%s", source, url)) + .orElse(markdown); + + CommandResult result = commandsManager.processCommand( + user, String.format("#%s %s", postId, commandBody), + URI.create(StringUtils.EMPTY)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(result, HttpStatus.OK); } else { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } } else { Message reply = messagesService.getReplyByUri(inReplyTo); if (reply != null) { User user = new User(); user.setUri(URI.create(activity.getActor())); - String attachment = StringUtils.EMPTY; - if (note.get("attachment") != null && ((List) note.get("attachment")).size() > 0) { - Map<String, Object> attachmentObj = (Map<String, Object>) ((List<Object>) note.get("attachment")).get(0); - attachment = (String) attachmentObj.get("url"); - } - CommandResult result = commandsManager.processCommand(user, String.format("#%d/%d %s", reply.getMid(), reply.getRid(), note.get("content")), URI.create(attachment)); + String markdown = remarkConverter.convertFragment((String)note.get("content")); + String commandBody = note.get("attachment") == null ? markdown : + ((List<Object>) note.get("attachment")).stream().map(attachmentObj -> { + Map<String, String> attachment = (Map<String, String>) attachmentObj; + String attachmentUrl = attachment.get("url"); + String attachmentName = attachment.get("name"); + return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); + }).reduce((source, url) -> String.format("%s\n%s", source, url)) + .orElse(markdown); + CommandResult result = commandsManager.processCommand( + user, + String.format("#%d/%d %s", reply.getMid(), reply.getRid(), commandBody), + URI.create(StringUtils.EMPTY)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(result, HttpStatus.OK); } else { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } } } @@ -357,17 +357,28 @@ public class Profile { URI actor = URI.create(activity.getActor()); URI reply = URI.create((String)tombstone.get("id")); messagesService.deleteReply(actor, reply); - return new ResponseEntity<>(HttpStatus.OK); + return new ResponseEntity<>(CommandResult.fromString("Delete request accepted"), HttpStatus.OK); } } if (activity instanceof Like || activity instanceof Announce) { - applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), (String)activity.getObject())); - return new ResponseEntity<>(HttpStatus.OK); + String messageUri = activity.getObject() instanceof String ? (String) activity.getObject() + : activity.getObject() instanceof Context ? ((Context) activity.getObject()).getId() + : (String) ((Map)activity.getObject()).get("id"); + applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), messageUri)); + return new ResponseEntity<>(CommandResult.fromString("Like/announce request accepted"), HttpStatus.OK); } logger.warn("Unknown activity: {}", jsonMapper.writeValueAsString(activity)); - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + return new ResponseEntity<>(CommandResult.fromString("Unknown activity"), HttpStatus.NOT_IMPLEMENTED); + } + if (activity instanceof Delete) { + if (activity.getObject() instanceof String) { + // Delete gone user + if (activity.getActor().equals(activity.getObject())) { + return new ResponseEntity<>(CommandResult.fromString("Delete request accepted"), HttpStatus.ACCEPTED); + } + } } - return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>(CommandResult.fromString("Can not authenticate"), HttpStatus.UNAUTHORIZED); } @PostMapping(value = "/u/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public User fetchUser(@RequestParam URI uri) { diff --git a/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java new file mode 100644 index 00000000..ba8cfb87 --- /dev/null +++ b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java @@ -0,0 +1,22 @@ +package com.juick.server.api.activity.helpers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +public class ActivityIdDeserializer extends JsonDeserializer<String> { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonToken jsonToken = p.getCurrentToken(); + if (jsonToken == JsonToken.START_OBJECT) { + JsonNode node = p.getCodec().readTree(p); + return node.get("id").textValue(); + } + return p.getValueAsString(); + } +} diff --git a/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java new file mode 100644 index 00000000..a635ea95 --- /dev/null +++ b/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java @@ -0,0 +1,21 @@ +package com.juick.server.api.activity.helpers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; + +public class LinkValueDeserializer extends JsonDeserializer<String> { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonToken jsonToken = p.getCurrentToken(); + if (jsonToken == JsonToken.VALUE_STRING) { + return p.getValueAsString(); + } + JsonNode node = p.getCodec().readTree(p); + return node.get("href").textValue(); + } +} diff --git a/src/main/java/com/juick/server/api/activity/model/Activity.java b/src/main/java/com/juick/server/api/activity/model/Activity.java index ec126b88..2af14479 100644 --- a/src/main/java/com/juick/server/api/activity/model/Activity.java +++ b/src/main/java/com/juick/server/api/activity/model/Activity.java @@ -1,7 +1,11 @@ package com.juick.server.api.activity.model; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.juick.server.api.activity.helpers.ActivityIdDeserializer; + public abstract class Activity extends Context { + @JsonDeserialize(using = ActivityIdDeserializer.class) private String actor; private Object object; diff --git a/src/main/java/com/juick/server/api/activity/model/Context.java b/src/main/java/com/juick/server/api/activity/model/Context.java index 515ee3da..2ba4606e 100644 --- a/src/main/java/com/juick/server/api/activity/model/Context.java +++ b/src/main/java/com/juick/server/api/activity/model/Context.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.juick.server.api.activity.helpers.LinkValueDeserializer; import com.juick.server.api.activity.model.activities.*; import com.juick.server.api.activity.model.objects.*; @@ -46,6 +48,7 @@ public abstract class Context { private Instant published; + @JsonDeserialize(using = LinkValueDeserializer.class) private String url; private List<String> to; diff --git a/src/main/java/com/juick/server/api/rss/MessagesView.java b/src/main/java/com/juick/server/api/rss/MessagesView.java index 05bc0bd6..2c05c8cb 100644 --- a/src/main/java/com/juick/server/api/rss/MessagesView.java +++ b/src/main/java/com/juick/server/api/rss/MessagesView.java @@ -126,7 +126,7 @@ public class MessagesView extends AbstractRssFeedView { description.setType("text/html"); description.setValue(messageDescription); item.setDescription(description); - item.setPubDate(Date.from(msg.getTimestamp())); + item.setPubDate(Date.from(msg.getCreated())); item.setComments(messageUrl); msg.getTags().stream().map(t -> { Category category = new Category(); diff --git a/src/main/java/com/juick/server/api/webfinger/Resource.java b/src/main/java/com/juick/server/api/webfinger/Resource.java index 71a0ca31..4e0447f7 100644 --- a/src/main/java/com/juick/server/api/webfinger/Resource.java +++ b/src/main/java/com/juick/server/api/webfinger/Resource.java @@ -26,7 +26,7 @@ public class Resource { @Value("${ap_base_uri:http://localhost:8080/}") private String baseUri; - @GetMapping("/.well-known/webfinger") + @GetMapping(value = "/.well-known/webfinger", produces = "application/jrd+json;charset=utf-8") public Account getWebResource(@RequestParam String resource) { if (resource.startsWith("acct:")) { Jid account = Jid.of(resource.substring(5)); diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java index 36e36bdd..493bff6a 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java @@ -1,15 +1,24 @@ package com.juick.server.api.xnodeinfo2; +import com.cliqset.xrd.Link; +import com.cliqset.xrd.XRD; +import com.fasterxml.jackson.annotation.JsonView; import com.juick.server.api.xnodeinfo2.model.*; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; import javax.inject.Inject; +import java.net.URI; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; @RestController public class Info { @@ -18,9 +27,9 @@ public class Info { @Inject private JdbcTemplate jdbcTemplate; - @GetMapping("/.well-known/x-nodeinfo2") - public NodeInfo showNodeInfo() { + private NodeInfo getCurrentNodeInfo(String version) { NodeInfo nodeInfo = new NodeInfo(); + nodeInfo.setVersion(version); Server server = new Server(); server.setBaseUrl(baseUri); server.setName("Juick"); @@ -28,6 +37,9 @@ public class Info { server.setVersion("2.x"); nodeInfo.setServer(server); nodeInfo.setProtocols(Arrays.asList("xmpp", "activitypub", "smtp")); + Map<String, String> metadata = new HashMap<>(); + metadata.put("email", "support@juick.com"); + nodeInfo.setMetadata(metadata); ServiceInfo serviceInfo = new ServiceInfo(); serviceInfo.setInbound(Arrays.asList("jabber", "mastodon", "email", "telegram")); serviceInfo.setOutbound(Arrays.asList("jabber", "mastodon", "telegram", "twitter", "email", "rss")); @@ -47,4 +59,28 @@ public class Info { nodeInfo.setUsage(usage); return nodeInfo; } + + @GetMapping(value = "/.well-known/x-nodeinfo2", produces = MediaType.APPLICATION_JSON_VALUE) + @JsonView(NodeInfo.XNodeInfoView.class) + public NodeInfo showXNodeInfo() { + return getCurrentNodeInfo("1.0"); + } + + @GetMapping(value = "/.well-known/nodeinfo", produces = MediaType.APPLICATION_JSON_VALUE) + public XRD getNodeInfoLinks() { + Link nodeinfo = new Link(); + nodeinfo.setRel(URI.create("http://nodeinfo.diaspora.software/ns/schema/2.0")); + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(baseUri); + uriComponentsBuilder.replacePath("/api/nodeinfo/2.0"); + nodeinfo.setHref(uriComponentsBuilder.build().toUri()); + XRD xrd = new XRD(); + xrd.setLinks(Collections.singletonList(nodeinfo)); + return xrd; + } + + @GetMapping(value = "/api/nodeinfo/2.0", produces = MediaType.APPLICATION_JSON_VALUE) + @JsonView(NodeInfo.NodeInfoView.class) + public NodeInfo showNodeInfo() { + return getCurrentNodeInfo("2.0"); + } } diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java index 06fe354f..1ebe39a8 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java @@ -1,27 +1,45 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + import java.util.List; +import java.util.Map; public class NodeInfo { + private String version; + private Server server; private List<String> protocols; private ServiceInfo services; + private Map<String, String> metadata; + + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public String getVersion() { - return "1.0"; + return version; } + public void setVersion(String version) { + this.version = version; + } + + @JsonView(XNodeInfoView.class) public Server getServer() { return server; } + @JsonView(NodeInfoView.class) + public Server getSoftware() { + return server; + } public void setServer(Server server) { this.server = server; } + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public List<String> getProtocols() { return protocols; } @@ -30,6 +48,7 @@ public class NodeInfo { this.protocols = protocols; } + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public ServiceInfo getServices() { return services; } @@ -38,12 +57,14 @@ public class NodeInfo { this.services = services; } + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public boolean getOpenRegistrations() { return true; } private Usage usage; + @JsonView({XNodeInfoView.class, NodeInfoView.class}) public Usage getUsage() { return usage; } @@ -51,4 +72,16 @@ public class NodeInfo { public void setUsage(Usage usage) { this.usage = usage; } + + @JsonView(NodeInfoView.class) + public Map<String, String> getMetadata() { + return metadata; + } + + public void setMetadata(Map<String, String> metadata) { + this.metadata = metadata; + } + + public interface NodeInfoView {} + public interface XNodeInfoView {} } diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java index a772d268..08c1a696 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java @@ -1,11 +1,14 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + public class Server { private String baseUrl; private String name; private String software; private String version; + @JsonView(NodeInfo.XNodeInfoView.class) public String getBaseUrl() { return baseUrl; } @@ -14,6 +17,7 @@ public class Server { this.baseUrl = baseUrl; } + @JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public String getName() { return name; } @@ -22,6 +26,7 @@ public class Server { this.name = name; } + @JsonView(NodeInfo.XNodeInfoView.class) public String getSoftware() { return software; } @@ -30,6 +35,7 @@ public class Server { this.software = software; } + @JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public String getVersion() { return version; } diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java index 5b6d2baa..0114488f 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java @@ -1,7 +1,10 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + import java.util.List; +@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public class ServiceInfo { private List<String> inbound; private List<String> outbound; diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java index e04ea48b..f270296f 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java @@ -1,5 +1,8 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + +@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public class Usage { private UserStats users; private int localPosts; diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java index 515661e3..cca31be6 100644 --- a/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java +++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java @@ -1,5 +1,8 @@ package com.juick.server.api.xnodeinfo2.model; +import com.fasterxml.jackson.annotation.JsonView; + +@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class}) public class UserStats { private int total; private int activeHalfyear; |