diff options
author | Vitaly Takmazov | 2018-03-16 10:46:23 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2018-03-16 10:46:23 +0300 |
commit | 9ef168a03b75aeca0c2f7dda9ce87d4014c703a9 (patch) | |
tree | 8ace0ada0f576d97592f8bc84803a2dcf3e7a244 /juick-common/src/main/java/com/juick | |
parent | 7cadae166b103182d7d1daaafe602cd8fb145c53 (diff) |
merge common projects
Diffstat (limited to 'juick-common/src/main/java/com/juick')
71 files changed, 4823 insertions, 0 deletions
diff --git a/juick-common/src/main/java/com/juick/Attachment.java b/juick-common/src/main/java/com/juick/Attachment.java new file mode 100644 index 00000000..76f2995a --- /dev/null +++ b/juick-common/src/main/java/com/juick/Attachment.java @@ -0,0 +1,58 @@ +package com.juick; + +public class Attachment { + private String url; + private Integer height; + private Integer width; + private Attachment small; + private Attachment medium; + private Attachment thumbnail; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Attachment getSmall() { + return small; + } + + public void setSmall(Attachment small) { + this.small = small; + } + + public Attachment getMedium() { + return medium; + } + + public void setMedium(Attachment medium) { + this.medium = medium; + } + + public Attachment getThumbnail() { + return thumbnail; + } + + public void setThumbnail(Attachment thumbnail) { + this.thumbnail = thumbnail; + } +} diff --git a/juick-common/src/main/java/com/juick/ExternalToken.java b/juick-common/src/main/java/com/juick/ExternalToken.java new file mode 100644 index 00000000..f6094478 --- /dev/null +++ b/juick-common/src/main/java/com/juick/ExternalToken.java @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Created by vitalyster on 22.11.2016. + */ +public class ExternalToken { + private String name; + private String type; + private String token; + private String secret; + + @JsonCreator + public ExternalToken(@JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("token") String token, + @JsonProperty("secret") String secret) { + this.name = name; + this.type = type; + this.token = token; + this.secret = secret; + if (this.type == null) { + throw new IllegalStateException("Token must have type"); + } + if (this.token == null) { + throw new IllegalStateException("Token must have value"); + } + } + + public String getType() { + return type; + } + + public String getToken() { + return token; + } + + public String getName() { + return name; + } + + public String getSecret() { + return secret; + } +} diff --git a/juick-common/src/main/java/com/juick/Message.java b/juick-common/src/main/java/com/juick/Message.java new file mode 100644 index 00000000..e574e3cc --- /dev/null +++ b/juick-common/src/main/java/com/juick/Message.java @@ -0,0 +1,293 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +package com.juick; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.juick.adapters.SimpleDateAdapter; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Ugnich Anton + */ +@XmlRootElement(name = "juick", namespace = "http://juick.com/message") +@XmlAccessorType() +public class Message implements Comparable { + private int mid = 0; + private int rid = 0; + private int replyto = 0; + private String text = null; + private User user = null; + private final List<Tag> tags; + private Instant ts; + private Instant updated; + @XmlTransient + @JsonIgnore + public int TimeAgo = 0; + @JsonIgnore + private int privacy = 1; + @XmlTransient + @JsonIgnore + public boolean FriendsOnly = false; + @XmlTransient + @JsonIgnore + public boolean ReadOnly = false; + @XmlTransient + @JsonIgnore + public boolean Hidden = false; + @JsonIgnore + @XmlTransient + public boolean VisitorCanComment = true; + private int replies = 0; + private String repliesBy; + private String attachmentType; + @XmlTransient + private Photo photo; + @XmlTransient + private Attachment attachment; + private int likes; + private User to; + private Recommendation Recommendation; + private String replyQuote; + + public Message() { + tags = new ArrayList<>(); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("mid", mid) + .append("rid", rid) + .append("replyto", replyto) + .append("TimeAgo", TimeAgo) + .append("privacy", privacy) + .append("FriendsOnly", FriendsOnly) + .append("ReadOnly", ReadOnly) + .append("Hidden", Hidden) + .append("VisitorCanComment", VisitorCanComment) + .append("replies", replies) + .append("likes", likes) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + + if (!(obj instanceof Message)) + return false; + + Message jmsg = (Message) obj; + return (this.getMid() == jmsg.getMid() && this.getRid() == jmsg.getRid()); + } + + @Override + public int compareTo(Object obj) throws ClassCastException { + if (obj == this) + return 0; + + if (!(obj instanceof Message)) + throw new ClassCastException(); + + Message jmsg = (Message) obj; + + int cmp = Integer.compare(jmsg.getMid(), getMid()); + + if (cmp == 0) + cmp = Integer.compare(getRid(), jmsg.getRid()); + + return cmp; + } + + @JsonProperty("mid") + @XmlAttribute(name = "mid") + public int getMid() { + return mid; + } + + public void setMid(int mid) { + this.mid = mid; + } + + @JsonProperty("rid") + @XmlAttribute(name = "rid") + public int getRid() { + return rid; + } + + public void setRid(int rid) { + this.rid = rid; + } + + @XmlElement(name = "user", namespace = "http://juick.com/user") + public com.juick.User getUser() { + return user; + } + + public void setUser(com.juick.User user) { + this.user = user; + } + + @JsonProperty("body") + @XmlElement(name = "body") + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @JsonProperty("timestamp") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC") + @XmlAttribute(name = "ts") + @XmlJavaTypeAdapter(SimpleDateAdapter.class) + public Instant getTimestamp() { + return ts; + } + + public void setTimestamp(Instant timestamp) { + this.ts = timestamp; + } + + @XmlElement(name = "to", namespace = "http://juick.com/user") + public User getTo() { + return to; + } + + public void setTo(User to) { + this.to = to; + } + + public Recommendation getRecommendation() { + return Recommendation; + } + + public void setRecommendation(Recommendation recommendation) { + this.Recommendation = recommendation; + } + + @XmlAttribute(name = "quote") + public String getReplyQuote() { + return replyQuote; + } + + public void setReplyQuote(String quote) { + replyQuote = quote; + } + + @JsonProperty("replyto") + @XmlAttribute(name = "replyto") + public int getReplyto() { + return replyto; + } + + public void setReplyto(int replyto) { + this.replyto = replyto; + } + + @JsonProperty("tags") + @XmlElement(name = "tag") + public List<Tag> getTags() { + return tags; + } + + public void setTags(List<Tag> tags) { + this.tags.clear(); + if (CollectionUtils.isNotEmpty(tags)) + this.tags.addAll(tags); + } + + @XmlAttribute + public int getPrivacy() { + return privacy; + } + + public void setPrivacy(int privacy) { + this.privacy = privacy; + } + + public Photo getPhoto() { + return photo; + } + + public void setPhoto(Photo photo) { + this.photo = photo; + } + + @XmlAttribute(name = "attach") + @JsonProperty("attach") + public String getAttachmentType() { + return attachmentType; + } + + public void setAttachmentType(String attachmentType) { + this.attachmentType = attachmentType; + } + + @XmlTransient + public int getReplies() { + return replies; + } + + public void setReplies(int replies) { + this.replies = replies; + } + + @XmlTransient + public int getLikes() { + return likes; + } + + public void setLikes(int likes) { + this.likes = likes; + } + + @JsonProperty("repliesby") + public String getRepliesBy() { + return repliesBy; + } + + public void setRepliesBy(String repliesBy) { + this.repliesBy = repliesBy; + } + + public Attachment getAttachment() { + return attachment; + } + public void setAttachment(Attachment attachment) { + this.attachment = attachment; + } + + public Instant getUpdated() { + return updated; + } + + public void setUpdated(Instant updated) { + this.updated = updated; + } +} diff --git a/juick-common/src/main/java/com/juick/Photo.java b/juick-common/src/main/java/com/juick/Photo.java new file mode 100644 index 00000000..06299610 --- /dev/null +++ b/juick-common/src/main/java/com/juick/Photo.java @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick; + +/** + * Created by vitalyster on 30.11.2016. + */ +// used for compatibility +@Deprecated +public class Photo { + private String small; + private String medium; + private String thumbnail; + + public String getSmall() { + return small; + } + + public void setSmall(String small) { + this.small = small; + } + + public String getMedium() { + return medium; + } + + public void setMedium(String medium) { + this.medium = medium; + } + + public String getThumbnail() { + return thumbnail; + } + + public void setThumbnail(String thumbnail) { + this.thumbnail = thumbnail; + } +} diff --git a/juick-common/src/main/java/com/juick/Recommendation.java b/juick-common/src/main/java/com/juick/Recommendation.java new file mode 100644 index 00000000..0a74b9ea --- /dev/null +++ b/juick-common/src/main/java/com/juick/Recommendation.java @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick; + +/** + * Created by vt on 08/02/16. + */ +public class Recommendation { + private final User from; + + + public Recommendation(User from) { + this.from = from; + } + + public User getFrom() { + return from; + } +} diff --git a/juick-common/src/main/java/com/juick/Status.java b/juick-common/src/main/java/com/juick/Status.java new file mode 100644 index 00000000..d7983536 --- /dev/null +++ b/juick-common/src/main/java/com/juick/Status.java @@ -0,0 +1,49 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Created by vitalyster on 25.07.2016. + */ +public class Status { + private final String value; + + public static final Status OK = new Status("ok"); + public static final Status FAIL = new Status("Fail"); + public static final Status ERROR = new Status("Error"); + + public static Status getStatus(final String stringStatus) { + return new Status(stringStatus); + } + + private Status(String value) { + this.value = value; + } + + @JsonProperty("status") + public String getValue() { + return value; + } + + @Override + public String toString() { + return "value = " + value; + } +} diff --git a/juick-common/src/main/java/com/juick/Tag.java b/juick-common/src/main/java/com/juick/Tag.java new file mode 100644 index 00000000..29e5c767 --- /dev/null +++ b/juick-common/src/main/java/com/juick/Tag.java @@ -0,0 +1,68 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +package com.juick; + +import com.fasterxml.jackson.annotation.JsonValue; + +import javax.xml.bind.annotation.*; +import java.util.Comparator; +import java.util.Objects; + +/** + * @author Ugnich Anton + */ +@XmlRootElement(name = "tag", namespace = "http://juick.com/message") +@XmlAccessorType(XmlAccessType.FIELD) +public class Tag implements Comparable<Tag> { + @XmlValue + private String name; + + @XmlTransient + public int TID = 0; + @XmlTransient + public int SynonymID = 0; + + public Tag() { + // required for (de)serialization + } + + public Tag(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + return o == this || + (o instanceof Tag) && Objects.equals(name, ((Tag) o).name); + } + + @XmlTransient + @JsonValue + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + + @Override + public int compareTo(Tag o) { + return Objects.compare(name, o.getName(), Comparator.naturalOrder()); + } +} diff --git a/juick-common/src/main/java/com/juick/User.java b/juick-common/src/main/java/com/juick/User.java new file mode 100644 index 00000000..478c6a48 --- /dev/null +++ b/juick-common/src/main/java/com/juick/User.java @@ -0,0 +1,187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +package com.juick; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import javax.annotation.Nonnull; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Ugnich Anton + */ +@XmlRootElement(name = "user", namespace = "http://juick.com/user") +@XmlAccessorType() +public class User implements Serializable { + private int uid; + private String name; + private Object avatar; + private String fullName; + private int messagesCount; + private String authHash; + private boolean banned; + private String credentials; + private String lang; + private List<ExternalToken> tokens; + + public User() { + tokens = new ArrayList<>(); + } + + @Override + public boolean equals(Object obj) { + return obj == this || + (obj instanceof User && ((User) obj).getUid() == this.getUid()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .append(getUid()) + .toHashCode(); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("uid", uid) + .append("name", name) + .append("fullName", fullName) + .append("messagesCount", messagesCount) + .append("lang", lang) + .append("banned", banned) + .toString(); + } + + @JsonProperty("uid") + @XmlAttribute(name = "uid") + public int getUid() { + return uid; + } + + public void setUid(int uid) { + this.uid = uid; + } + + @JsonProperty("uname") + @XmlAttribute(name = "uname") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @JsonProperty("fullname") + @XmlTransient + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + @XmlTransient + @JsonIgnore + public String getAuthHash() { + return authHash; + } + + public void setAuthHash(String authHash) { + this.authHash = authHash; + } + + @JsonProperty("unreadCount") + @XmlTransient + public Integer getUnreadCount() { + return messagesCount; + } + + public void setUnreadCount(Integer count) { + this.messagesCount = count; + } + + @XmlTransient + public boolean isBanned() { + return banned; + } + + public void setBanned(boolean banned) { + this.banned = banned; + } + + public Object getAvatar() { + return avatar; + } + + public void setAvatar(Object avatar) { + this.avatar = avatar; + } + + @XmlTransient + @JsonIgnore + public String getCredentials() { + return credentials; + } + + public void setCredentials(String credentials) { + this.credentials = credentials; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + @XmlTransient + public int getMessagesCount() { + return messagesCount; + } + + public void setMessagesCount(int messagesCount) { + this.messagesCount = messagesCount; + } + + @XmlTransient + @JsonIgnore + public boolean isAnonymous() { + return false; + } + + @Nonnull + public List<ExternalToken> getTokens() { + return tokens; + } + + public void setTokens(List<ExternalToken> tokens) { + this.tokens = tokens; + } +} diff --git a/juick-common/src/main/java/com/juick/adapters/SimpleDateAdapter.java b/juick-common/src/main/java/com/juick/adapters/SimpleDateAdapter.java new file mode 100644 index 00000000..b8e08599 --- /dev/null +++ b/juick-common/src/main/java/com/juick/adapters/SimpleDateAdapter.java @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.adapters; + +import com.juick.util.DateFormattersHolder; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.time.Instant; + +/** + * Created by vitalyster on 15.11.2016. + */ + +public class SimpleDateAdapter extends XmlAdapter<String, Instant> { + + @Override + public String marshal(Instant v) throws Exception { + return DateFormattersHolder.getMessageFormatterInstance().format(v); + } + + @Override + public Instant unmarshal(String v) throws Exception { + return DateFormattersHolder.getMessageFormatterInstance().parse(v); + } +} diff --git a/juick-common/src/main/java/com/juick/formatters/PlainTextFormatter.java b/juick-common/src/main/java/com/juick/formatters/PlainTextFormatter.java new file mode 100644 index 00000000..c599d33b --- /dev/null +++ b/juick-common/src/main/java/com/juick/formatters/PlainTextFormatter.java @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.formatters; + +import com.juick.Message; +import com.juick.util.MessageUtils; +import org.apache.commons.lang3.StringUtils; +import org.ocpsoft.prettytime.PrettyTime; + +import java.util.Date; +import java.util.Locale; + +/** + * Created by vitalyster on 12.10.2016. + */ +public class PlainTextFormatter { + static PrettyTime pt = new PrettyTime(new Locale("ru")); + + public static String formatPost(Message jmsg) { + return formatPost(jmsg, false); + } + + public static String formatPost(Message jmsg, boolean markdown) { + StringBuilder sb = new StringBuilder(); + boolean isReply = jmsg.getRid() > 0; + String title = isReply ? "Reply by @" : "@"; + String subtitle = isReply ? markdown ? MessageUtils.escapeMarkdown(StringUtils.defaultString(jmsg.getReplyQuote())) + : jmsg.getReplyQuote() + : markdown ? MessageUtils.getMarkdownTags(jmsg) : MessageUtils.getTagsString(jmsg); + sb.append(title).append(markdown ? MessageUtils.getMarkdownUser(jmsg) : jmsg.getUser().getName()).append(":\n") + .append(subtitle).append("\n"); + if (markdown) { + sb.append(MessageUtils.escapeMarkdown(StringUtils.defaultString(jmsg.getText()))); + } else { + sb.append(StringUtils.defaultString(jmsg.getText())); + } + sb.append("\n"); + if (!markdown && StringUtils.isNotEmpty(jmsg.getAttachmentType())) { + sb.append(MessageUtils.attachmentUrl(jmsg)); + } + return sb.toString(); + } + + public static String formatPostSummary(Message m) { + int cropLength = 384; + String timeAgo = pt.format(Date.from(m.getTimestamp())); + String repliesCount = m.getReplies() == 1 ? "; 1 reply" : m.getReplies() == 0 ? "" + : String.format("; %d replies", m.getReplies()); + StringBuilder sb = new StringBuilder(); + String txt = StringUtils.defaultString(m.getText()); + String attachmentUrl = MessageUtils.attachmentUrl(m); + if (StringUtils.isNotEmpty(attachmentUrl)) { + sb.append(attachmentUrl).append("\n"); + } + if (txt.length() >= cropLength) { + sb.append(StringUtils.substring(txt, 0, cropLength)).append(" [...]"); + } else { + sb.append(txt); + } + return String.format("@%s:%s\n%s\n#%s (%s%s) %s", + m.getUser().getName(), MessageUtils.getTagsString(m), sb.toString(), formatPostNumber(m), timeAgo, repliesCount, formatUrl(m)); + } + + public static String formatUrl(com.juick.Message jmsg) { + if (jmsg.getRid() > 0) { + return String.format("https://juick.com/%d#%d", jmsg.getMid(), jmsg.getRid()); + } + return "https://juick.com/" + jmsg.getMid(); + } + + public static String formatPostNumber(com.juick.Message jmsg) { + if (jmsg.getRid() > 0) { + return String.format("%d/%d", jmsg.getMid(), jmsg.getRid()); + } + return String.format("%d", jmsg.getMid()); + } + + public static String formatTwitterCard(Message jmsg) { + return MessageUtils.getMessageHashTags(jmsg) + StringUtils.defaultString(jmsg.getText()); + } +} diff --git a/juick-common/src/main/java/com/juick/package-info.java b/juick-common/src/main/java/com/juick/package-info.java new file mode 100644 index 00000000..c9023417 --- /dev/null +++ b/juick-common/src/main/java/com/juick/package-info.java @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/** + * Created by vitalyster on 15.11.2016. + */ +@XmlSchema( + namespace="http://juick.com/message", + elementFormDefault = XmlNsForm.QUALIFIED, + xmlns={ + @XmlNs(prefix= StringUtils.EMPTY, namespaceURI="http://juick.com/message"), + @XmlNs(prefix="user", namespaceURI="http://juick.com/user") + } +) +package com.juick; + +import org.apache.commons.lang3.StringUtils; + +import javax.xml.bind.annotation.XmlNs; +import javax.xml.bind.annotation.XmlNsForm; +import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file diff --git a/juick-common/src/main/java/com/juick/server/component/DisconnectedEvent.java b/juick-common/src/main/java/com/juick/server/component/DisconnectedEvent.java new file mode 100644 index 00000000..9da6d7a9 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/component/DisconnectedEvent.java @@ -0,0 +1,14 @@ +package com.juick.server.component; + +import org.springframework.context.ApplicationEvent; + +public class DisconnectedEvent extends ApplicationEvent { + /** + * Create a new ApplicationEvent. + * + * @param source the object on which the event initially occurred (never {@code null}) + */ + public DisconnectedEvent(Object source) { + super(source); + } +} diff --git a/juick-common/src/main/java/com/juick/server/component/JuickServerComponent.java b/juick-common/src/main/java/com/juick/server/component/JuickServerComponent.java new file mode 100644 index 00000000..792f446f --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/component/JuickServerComponent.java @@ -0,0 +1,71 @@ +package com.juick.server.component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.PingMessage; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +public class JuickServerComponent extends TextWebSocketHandler { + private static Logger logger = LoggerFactory.getLogger(JuickServerComponent.class); + @Inject + private ApplicationEventPublisher applicationEventPublisher; + @Inject + private ObjectMapper jsonMapper; + + private WebSocketSession session; + private final AtomicBoolean closeFlag = new AtomicBoolean(false); + + @PostConstruct + public void init() { + closeFlag.set(false); + } + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + logger.info("WebSocket connected"); + this.session = session; + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + logger.info("WebSocket disconnected with code {}: {}", status.getCode(), status.getReason()); + applicationEventPublisher.publishEvent(new DisconnectedEvent(this)); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage text) throws Exception { + com.juick.Message jmsg = jsonMapper.readValue(text.asBytes(), com.juick.Message.class); + + if (logger.isInfoEnabled()) // prevent writeValueAsString execution if logger disabled + logger.info("got jmsg: {}", jsonMapper.writeValueAsString(jmsg)); + if (!closeFlag.get()) { + applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg)); + } + } + + @Scheduled(fixedRate = 30000, initialDelay = 30000) + public void ping() throws IOException { + if (session != null && session.isOpen()) { + logger.debug("Sending WebSocket ping"); + session.sendMessage(new PingMessage()); + } else if (!closeFlag.get()) { + applicationEventPublisher.publishEvent(new DisconnectedEvent(this)); + } + } + @PreDestroy + public void close() { + closeFlag.set(true); + } + +} diff --git a/juick-common/src/main/java/com/juick/server/component/JuickServerReconnectManager.java b/juick-common/src/main/java/com/juick/server/component/JuickServerReconnectManager.java new file mode 100644 index 00000000..a662e4fb --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/component/JuickServerReconnectManager.java @@ -0,0 +1,22 @@ +package com.juick.server.component; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.client.WebSocketConnectionManager; + +import javax.inject.Inject; + +@Component +public class JuickServerReconnectManager implements ApplicationListener<DisconnectedEvent> { + private static Logger logger = LoggerFactory.getLogger(JuickServerReconnectManager.class); + @Inject + private WebSocketConnectionManager webSocketConnectionManager; + @Override + public void onApplicationEvent(DisconnectedEvent event) { + logger.info("retrying..."); + webSocketConnectionManager.stop(); + webSocketConnectionManager.start(); + } +} diff --git a/juick-common/src/main/java/com/juick/server/component/MessageEvent.java b/juick-common/src/main/java/com/juick/server/component/MessageEvent.java new file mode 100644 index 00000000..048de6a6 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/component/MessageEvent.java @@ -0,0 +1,22 @@ +package com.juick.server.component; + +import com.juick.Message; +import org.springframework.context.ApplicationEvent; + +public class MessageEvent extends ApplicationEvent { + private Message message; + /** + * Create a new ApplicationEvent. + * + * @param source the object on which the event initially occurred (never {@code null}) + * @param message app message + */ + public MessageEvent(Object source, Message message) { + super(source); + this.message = message; + } + + public Message getMessage() { + return message; + } +} diff --git a/juick-common/src/main/java/com/juick/server/component/UserUpdatedEvent.java b/juick-common/src/main/java/com/juick/server/component/UserUpdatedEvent.java new file mode 100644 index 00000000..059aeefd --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/component/UserUpdatedEvent.java @@ -0,0 +1,23 @@ +package com.juick.server.component; + +import com.juick.User; +import org.springframework.context.ApplicationEvent; +import org.springframework.lang.NonNull; + +public class UserUpdatedEvent extends ApplicationEvent { + private User user; + /** + * Generated when user is updated (avatar changed, etc). + * + * @param source the object on which the event initially occurred (never {@code null}) + * @param user updated user + */ + public UserUpdatedEvent(@NonNull Object source, User user) { + super(source); + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/juick-common/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java b/juick-common/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java new file mode 100644 index 00000000..d9b842af --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.configuration; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.juick.server.xmpp.JidConverter; +import com.juick.server.xmpp.s2s.BasicXmppSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.commons.CommonsMultipartResolver; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import rocks.xmpp.core.session.Extension; +import rocks.xmpp.core.session.XmppSessionConfiguration; +import rocks.xmpp.core.session.debug.LogbackDebugger; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by vitalyster on 28.06.2016. + */ +@Configuration +public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigurer { + + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + configurer.setUseSuffixPatternMatch(false); + } + + @Override + public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(jsonMapper()); + converters.add(converter); + } + + @Bean + public ObjectMapper jsonMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); + mapper.registerModule(new Jdk8Module()); + mapper.registerModule(new JavaTimeModule()); + return mapper; + } + + @Bean + public MultipartResolver multipartResolver() { + CommonsMultipartResolver resolver = new CommonsMultipartResolver(); + resolver.setMaxUploadSize(10000000); + return resolver; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskExecutor()); + } + + @Bean(destroyMethod="shutdown") + public Executor taskExecutor() { + return Executors.newScheduledThreadPool(100); + } + @Value("${hostname:localhost}") + private String hostname; + + @Bean + public ExecutorService service() { + return Executors.newCachedThreadPool(); + } + @Bean + public BasicXmppSession session() { + XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() + .extensions(Extension.of(com.juick.Message.class)) + .debugger(LogbackDebugger.class) + .build(); + return BasicXmppSession.create(hostname, configuration); + } + @Bean + public static ConversionService conversionService() { + DefaultFormattingConversionService cs = new DefaultFormattingConversionService(); + cs.addConverter(new JidConverter()); + return cs; + } +} diff --git a/juick-common/src/main/java/com/juick/server/configuration/JuickServerWebsocketConfiguration.java b/juick-common/src/main/java/com/juick/server/configuration/JuickServerWebsocketConfiguration.java new file mode 100644 index 00000000..18501eaf --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/configuration/JuickServerWebsocketConfiguration.java @@ -0,0 +1,72 @@ +package com.juick.server.configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.juick.server.component.JuickServerComponent; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.http.client.support.BasicAuthorizationInterceptor; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.socket.client.WebSocketConnectionManager; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.inject.Inject; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +@Lazy +@Configuration +@EnableScheduling +public class JuickServerWebsocketConfiguration { + private static final Logger logger = LoggerFactory.getLogger(JuickServerWebsocketConfiguration.class); + @Value("${websocket_url:ws://localhost:8080/ws/}") + private String baseUri; + @Value("${api_user:juick}") + private String serviceUser; + @Value("${api_password:secret}") + private String servicePassword; + @Inject + ObjectMapper jsonMapper; + @Inject + private JuickServerComponent juickServerComponent; + @Bean + public RestTemplate rest() { + RestTemplate rest = new RestTemplate(); + List<ClientHttpRequestInterceptor> interceptors = Collections.singletonList( + new BasicAuthorizationInterceptor(serviceUser, servicePassword)); + + rest.setRequestFactory(new InterceptingClientHttpRequestFactory(rest.getRequestFactory(), interceptors)); + return rest; + } + @Bean + public WebSocketConnectionManager connectionManager() { + String hash = StringUtils.EMPTY; + try { + ResponseEntity<String> response = rest().exchange("https://api.juick.com/auth", + HttpMethod.GET, null, String.class); + hash = jsonMapper.readValue(response.getBody(), String.class); + } catch (HttpClientErrorException | IOException e) { + logger.warn("service component is not authenticated", e); + } + String websocketURI = UriComponentsBuilder.fromUriString(baseUri) + .queryParam("hash", hash).build().toUriString(); + WebSocketConnectionManager manager = new WebSocketConnectionManager(client(), juickServerComponent, websocketURI); + return manager; + } + @Bean + public StandardWebSocketClient client() { + return new StandardWebSocketClient(); + } +} diff --git a/juick-common/src/main/java/com/juick/server/configuration/StorageConfiguration.java b/juick-common/src/main/java/com/juick/server/configuration/StorageConfiguration.java new file mode 100644 index 00000000..94b23037 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/configuration/StorageConfiguration.java @@ -0,0 +1,14 @@ +package com.juick.server.configuration; + +import com.juick.service.ImagesService; +import com.juick.service.ImagesServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class StorageConfiguration { + @Bean + public ImagesService imagesService() { + return new ImagesServiceImpl(); + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/AnonymousUser.java b/juick-common/src/main/java/com/juick/server/helpers/AnonymousUser.java new file mode 100644 index 00000000..122bbe29 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/AnonymousUser.java @@ -0,0 +1,139 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +import com.juick.User; + +/** + * Created by aalexeev on 12/11/16. + */ +public final class AnonymousUser extends User { + public static final AnonymousUser INSTANCE = new AnonymousUser(); + + private AnonymousUser() { + super.setUid(getUid()); + super.setName(getName()); + super.setAvatar(getAvatar()); + super.setFullName(getFullName()); + super.setMessagesCount(getMessagesCount()); + super.setAuthHash(getAuthHash()); + super.setBanned(isBanned()); + super.setCredentials(getCredentials()); + super.setLang(getLang()); + } + + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof AnonymousUser; + } + + @Override + public int getUid() { + return 0; + } + + @Override + public String getName() { + return "Anonymous"; + } + + @Override + public String getFullName() { + return getName(); + } + + @Override + public String getAuthHash() { + return null; + } + + @Override + public Integer getUnreadCount() { + return 0; + } + + @Override + public boolean isBanned() { + return false; + } + + @Override + public Object getAvatar() { + return null; + } + + @Override + public String getCredentials() { + return null; + } + + @Override + public String getLang() { + return "__"; + } + + @Override + public int getMessagesCount() { + return 0; + } + + @Override + public boolean isAnonymous() { + return true; + } + + @Override + public void setUid(int uid) { + } + + @Override + public void setName(String name) { + } + + @Override + public void setFullName(String fullName) { + } + + @Override + public void setAuthHash(String authHash) { + } + + @Override + public void setUnreadCount(Integer count) { + } + + @Override + public void setBanned(boolean banned) { + } + + @Override + public void setAvatar(Object avatar) { + } + + @Override + public void setCredentials(String credentials) { + } + + @Override + public void setLang(String lang) { + } + + @Override + public void setMessagesCount(int messagesCount) { + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/ApplicationStatus.java b/juick-common/src/main/java/com/juick/server/helpers/ApplicationStatus.java new file mode 100644 index 00000000..8f57b2a6 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/ApplicationStatus.java @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +/** + * Created by vt on 03/09/16. + */ +public class ApplicationStatus { + private boolean connected; + private boolean crosspostEnabled; + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("connected", connected) + .append("crosspostEnabled", crosspostEnabled) + .toString(); + } + + public boolean isConnected() { + return connected; + } + + public void setConnected(boolean connected) { + this.connected = connected; + } + + public boolean isCrosspostEnabled() { + return crosspostEnabled; + } + + public void setCrosspostEnabled(boolean crosspostEnabled) { + this.crosspostEnabled = crosspostEnabled; + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/Auth.java b/juick-common/src/main/java/com/juick/server/helpers/Auth.java new file mode 100644 index 00000000..d01efadd --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/Auth.java @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +/** + * Created by vt on 09/02/16. + */ +public class Auth { + private String account; + private String authCode; + + public Auth(String account, String authCode) { + this.account = account; + this.authCode = authCode; + } + + public String getAccount() { + return account; + } + + public String getAuthCode() { + return authCode; + } +}
\ No newline at end of file diff --git a/juick-common/src/main/java/com/juick/server/helpers/NotifyOpts.java b/juick-common/src/main/java/com/juick/server/helpers/NotifyOpts.java new file mode 100644 index 00000000..0e49a424 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/NotifyOpts.java @@ -0,0 +1,51 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +/** + * Created by vt on 03/09/16. + */ +public class NotifyOpts { + private boolean repliesEnabled; + private boolean subscriptionsEnabled; + private boolean recommendationsEnabled; + + public boolean isRepliesEnabled() { + return repliesEnabled; + } + + public void setRepliesEnabled(boolean repliesEnabled) { + this.repliesEnabled = repliesEnabled; + } + + public boolean isSubscriptionsEnabled() { + return subscriptionsEnabled; + } + + public void setSubscriptionsEnabled(boolean subscriptionsEnabled) { + this.subscriptionsEnabled = subscriptionsEnabled; + } + + public boolean isRecommendationsEnabled() { + return recommendationsEnabled; + } + + public void setRecommendationsEnabled(boolean recommendationsEnabled) { + this.recommendationsEnabled = recommendationsEnabled; + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/PrivacyOpts.java b/juick-common/src/main/java/com/juick/server/helpers/PrivacyOpts.java new file mode 100644 index 00000000..86281d4a --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/PrivacyOpts.java @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +/** + * Created by vt on 16/01/16. + */ +public class PrivacyOpts { + private int uid; + private int privacy; + + public PrivacyOpts() { + + } + + public int getUid() { + return uid; + } + + public void setUid(int uid) { + this.uid = uid; + } + + public int getPrivacy() { + return privacy; + } + + public void setPrivacy(int privacy) { + this.privacy = privacy; + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/PrivateChats.java b/juick-common/src/main/java/com/juick/server/helpers/PrivateChats.java new file mode 100644 index 00000000..6e446f98 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/PrivateChats.java @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.juick.User; + +import java.util.List; + +/** + * Created by vt on 24/11/2016. + */ +public class PrivateChats { + private List<User> users; + + @JsonProperty("pms") + public List<User> getUsers() { + return users; + } + + public void setUsers(List<User> users) { + this.users = users; + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/ResponseReply.java b/juick-common/src/main/java/com/juick/server/helpers/ResponseReply.java new file mode 100644 index 00000000..91ba2b8a --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/ResponseReply.java @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +import java.util.Date; + +/** + * Created by vitalyster on 13.12.2016. + */ +public class ResponseReply { + private String muname; + private int mid; + private int rid; + private String uname; + private String description; + private Date pubDate; + private String attachmentType; + + public String getMuname() { + return muname; + } + + public void setMuname(String muname) { + this.muname = muname; + } + + public int getMid() { + return mid; + } + + public void setMid(int mid) { + this.mid = mid; + } + + public int getRid() { + return rid; + } + + public void setRid(int rid) { + this.rid = rid; + } + + public String getUname() { + return uname; + } + + public void setUname(String uname) { + this.uname = uname; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getPubDate() { + return pubDate; + } + + public void setPubDate(Date pubDate) { + this.pubDate = pubDate; + } + + public String getAttachmentType() { + return attachmentType; + } + + public void setAttachmentType(String attachmentType) { + this.attachmentType = attachmentType; + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/TagStats.java b/juick-common/src/main/java/com/juick/server/helpers/TagStats.java new file mode 100644 index 00000000..ab24983f --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/TagStats.java @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.juick.Tag; + +/** + * Created by vitalyster on 01.12.2016. + */ +public class TagStats { + private Tag tag; + private int usageCount; + + public Tag getTag() { + return tag; + } + + public void setTag(Tag tag) { + this.tag = tag; + } + + @JsonProperty("messages") + public int getUsageCount() { + return usageCount; + } + + public void setUsageCount(int usageCount) { + this.usageCount = usageCount; + } +} diff --git a/juick-common/src/main/java/com/juick/server/helpers/UserInfo.java b/juick-common/src/main/java/com/juick/server/helpers/UserInfo.java new file mode 100644 index 00000000..284cd2e8 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/helpers/UserInfo.java @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.helpers; + +/** + * Created by vt on 03/09/16. + */ +public class UserInfo { + private String fullName; + private String country; + private String url; + private String description; + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/juick-common/src/main/java/com/juick/server/protocol/JuickProtocol.java b/juick-common/src/main/java/com/juick/server/protocol/JuickProtocol.java new file mode 100644 index 00000000..1be34a3f --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/protocol/JuickProtocol.java @@ -0,0 +1,188 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.protocol; + +import com.juick.Message; +import com.juick.Tag; +import com.juick.User; +import com.juick.formatters.PlainTextFormatter; +import com.juick.server.protocol.annotation.UserCommand; +import com.juick.server.util.TagUtils; +import com.juick.service.*; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.reflect.MethodUtils; + +import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Created by oxpa on 22.03.16. + */ + +public class JuickProtocol { + + private String baseUri; + private ProtocolListener listener; + + @Inject + UserService userService; + @Inject + TagService tagService; + @Inject + MessagesService messagesService; + @Inject + SubscriptionService subscriptionService; + @Inject + PMQueriesService pmQueriesService; + @Inject + PrivacyQueriesService privacyQueriesService; + @Inject + ShowQueriesService showQueriesService; + + public JuickProtocol(String baseUri) { + this.baseUri = baseUri; + } + + /** + * find command by pattern and invoke + * @param user who send command + * @param userInput given by user + * @return command result + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + */ + public String getReply(User user, String userInput) throws InvocationTargetException, + IllegalAccessException, NoSuchMethodException { + Optional<Method> cmd = MethodUtils.getMethodsListWithAnnotation(getClass(), UserCommand.class).stream() + .filter(m -> Pattern.compile(m.getAnnotation(UserCommand.class).pattern(), + m.getAnnotation(UserCommand.class).patternFlags()).matcher(userInput).matches()) + .findFirst(); + if (!cmd.isPresent()) { + // default command - post as new message + return postMessage(user, userInput.trim()); + } else { + Matcher matcher = Pattern.compile(cmd.get().getAnnotation(UserCommand.class).pattern(), + cmd.get().getAnnotation(UserCommand.class).patternFlags()).matcher(userInput.trim()); + List<String> groups = new ArrayList<>(); + while (matcher.find()) { + for (int i = 1; i <= matcher.groupCount(); i++) { + groups.add(matcher.group(i)); + } + } + return (String) getClass().getMethod(cmd.get().getName(), User.class, String[].class) + .invoke(this, user, groups.toArray(new String[groups.size()])); + } + } + + public String postMessage(User user, String input) { + List<Tag> tags = tagService.fromString(input, false); + String body = input.substring(TagUtils.toString(tags).length()); + int mid = messagesService.createMessage(user.getUid(), body, null, tags); + subscriptionService.subscribeMessage(mid, user.getUid()); + listener.messagePosted(messagesService.getMessage(mid)); + return "New message posted.\n#" + mid + " " + baseUri + mid; + } + + + + + @UserCommand(pattern = "^d\\s*\\#([0-9]+)$", patternFlags = Pattern.CASE_INSENSITIVE, + help = "D #12345 - delete the message") + public String commandDel(User user, String... args) { + int mid = NumberUtils.toInt(args[0], 0); + if (messagesService.deleteMessage(user.getUid(), mid)) { + return String.format("Message %s deleted", mid); + } + return "Error"; + } + + + @UserCommand(pattern = "^(#+)$", help = "# - Show last messages from your feed (## - second page, ...)") + public String commandMyFeed(User user, String... arguments) { + // number of # is the page count + int page = arguments[0].length() - 1; + List<Integer> mids = messagesService.getMyFeed(user.getUid(), page, false); + List<Message> messages = messagesService.getMessages(mids); + // TODO: add instructions for empty feed + return "Your feed: \n" + String.join("\n", + messages.stream().map(PlainTextFormatter::formatPost).collect(Collectors.toList())); + } + @UserCommand(pattern = "^(#|\\.)(\\d+)((\\.|\\-|\\/)(\\d+))?\\s([\\s\\S]+)", + help = "#1234 *tag *tag2 - edit tags\n#1234 text - reply to message") + public String EditOrReply(User user, String... args) { + int mid = NumberUtils.toInt(args[1]); + int rid = NumberUtils.toInt(args[4], 0); + String txt = args[5]; + List<Tag> messageTags = tagService.fromString(txt, true); + if (messageTags.size() > 0) { + if (user.getUid() != messagesService.getMessageAuthor(mid).getUid()) { + return "It is not your message"; + } + tagService.updateTags(mid, messageTags); + return "Tags are updated"; + } else { + int newrid = messagesService.createReply(mid, rid, user.getUid(), txt, null); + listener.messagePosted(messagesService.getReply(mid, newrid)); + return "Reply posted.\n#" + mid + "/" + newrid + " " + + baseUri + mid + "#" + newrid; + } + } + + + @UserCommand(pattern = "^(s|u)\\s+\\@(\\S+)$", help = "S @user - subscribe to user's posts", + patternFlags = Pattern.CASE_INSENSITIVE) + public String commandSubscribeUser(User user, String... args) { + boolean subscribe = args[0].equalsIgnoreCase("s"); + User toUser = userService.getUserByName(args[1]); + if (toUser.getUid() > 0) { + if (subscribe) { + if (subscriptionService.subscribeUser(user, toUser)) { + listener.userSubscribed(user, toUser); + return "Subscribed"; + // TODO: already subscribed case + } + } else { + if (subscriptionService.unSubscribeUser(user, toUser)) { + return "Unsubscribed from @" + toUser.getName(); + } + return "You was not subscribed to @" + toUser.getName(); + } + } + return "Error"; + } + + public String getBaseUri() { + return baseUri; + } + + public ProtocolListener getListener() { + return listener; + } + + public void setListener(ProtocolListener listener) { + this.listener = listener; + } +} diff --git a/juick-common/src/main/java/com/juick/server/protocol/ProtocolListener.java b/juick-common/src/main/java/com/juick/server/protocol/ProtocolListener.java new file mode 100644 index 00000000..f051e6d0 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/protocol/ProtocolListener.java @@ -0,0 +1,30 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.protocol; + +import com.juick.Message; +import com.juick.User; + +/** + * Created by vitalyster on 19.12.2016. + */ +public interface ProtocolListener { + void privateMessage(User from, User to, String body); + void userSubscribed(User from, User to); + void messagePosted(Message msg); +} diff --git a/juick-common/src/main/java/com/juick/server/protocol/annotation/UserCommand.java b/juick-common/src/main/java/com/juick/server/protocol/annotation/UserCommand.java new file mode 100644 index 00000000..ab37a4e1 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/protocol/annotation/UserCommand.java @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.protocol.annotation; + +import org.apache.commons.lang3.StringUtils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created by oxpa on 22.03.16. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface UserCommand { + /** + * + * @return a command pattern + */ + String pattern() default StringUtils.EMPTY; + + /** + * + * @return pattern flags + */ + int patternFlags() default 0; + + /** + * + * @return a string used in HELP command output. Basically, only 1 string + */ + String help() default StringUtils.EMPTY; +} diff --git a/juick-common/src/main/java/com/juick/server/util/HashUtils.java b/juick-common/src/main/java/com/juick/server/util/HashUtils.java new file mode 100644 index 00000000..b4500457 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/HashUtils.java @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import java.util.Random; + +/** + * Created by vitalyster on 29.06.2017. + */ +public class HashUtils { + private static final String ABCDEF = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static String generateHash(final int len) { + Random rnd = new Random(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + sb.append(ABCDEF.charAt(rnd.nextInt(ABCDEF.length()))); + } + return sb.toString(); + } +} diff --git a/juick-common/src/main/java/com/juick/server/util/HttpBadRequestException.java b/juick-common/src/main/java/com/juick/server/util/HttpBadRequestException.java new file mode 100644 index 00000000..1c3b4e66 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/HttpBadRequestException.java @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Created by vt on 24/11/2016. + */ +@ResponseStatus(value = HttpStatus.BAD_REQUEST) +public class HttpBadRequestException extends RuntimeException { + public HttpBadRequestException() { + super(StringUtils.EMPTY, null, false, false); + } +} diff --git a/juick-common/src/main/java/com/juick/server/util/HttpForbiddenException.java b/juick-common/src/main/java/com/juick/server/util/HttpForbiddenException.java new file mode 100644 index 00000000..3251ca38 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/HttpForbiddenException.java @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Created by vt on 24/11/2016. + */ +@ResponseStatus(value = HttpStatus.FORBIDDEN) +public class HttpForbiddenException extends RuntimeException { + public HttpForbiddenException() { + super(StringUtils.EMPTY, null, false, false); + } + +} diff --git a/juick-common/src/main/java/com/juick/server/util/HttpNotFoundException.java b/juick-common/src/main/java/com/juick/server/util/HttpNotFoundException.java new file mode 100644 index 00000000..f66ece8b --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/HttpNotFoundException.java @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Created by vt on 24/11/2016. + */ +@ResponseStatus(value = HttpStatus.NOT_FOUND) +public class HttpNotFoundException extends RuntimeException { + public HttpNotFoundException() { + super(StringUtils.EMPTY, null, false, false); + } +} diff --git a/juick-common/src/main/java/com/juick/server/util/HttpUtils.java b/juick-common/src/main/java/com/juick/server/util/HttpUtils.java new file mode 100644 index 00000000..35f594f3 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/HttpUtils.java @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +package com.juick.server.util; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.UUID; + +/** + * + * @author Ugnich Anton + */ +public class HttpUtils { + private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); + + public static String receiveMultiPartFile(MultipartFile attach, String tmpDir) { + if (attach !=null && !attach.isEmpty()) { + String partname = attach.getOriginalFilename(); + if (partname != null && partname.length() > 0) { + String attachmentType = partname.substring(partname.length() - 3).toLowerCase(); + if (attachmentType.equals("jpg") || attachmentType.equals("peg") || attachmentType.equals("png")) { + if (attachmentType.equals("peg")) { + attachmentType = "jpg"; + } + String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType; + try { + Files.write(Paths.get(tmpDir, attachmentFName), + attach.getBytes()); + return attachmentFName; + } catch (IOException e) { + logger.warn("file receive error", e); + } + } + } + } + return StringUtils.EMPTY; + } + public static String downloadImage(URL url, String tmpDir) throws Exception { + URLConnection urlConn; + try { + urlConn = url.openConnection(); + } catch (IOException e) { + logger.error(String.format("Failed open url: %s", url.toString())); + throw e; + } + + try (InputStream is = urlConn.getInputStream()) { + String mime = urlConn.getContentType(); + + String attachmentType; + if (mime != null && mime.equals("image/jpeg")) { + attachmentType = "jpg"; + } else if (mime != null && mime.equals("image/png")) { + attachmentType = "png"; + } else if (url.getFile().toLowerCase().endsWith("jpg")) { + attachmentType = "jpg"; + } else if (url.getFile().toLowerCase().endsWith("png")) { + attachmentType = "png"; + } else { + throw new Exception("Wrong file type: " + mime); + } + + String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType; + Files.copy(is, Paths.get(tmpDir, attachmentFName)); + return attachmentFName; + } catch (Exception e) { + logger.error(String.format("Failed download image by url: %s", url.toString()), e); + throw e; + } + } +} diff --git a/juick-common/src/main/java/com/juick/server/util/ImageUtils.java b/juick-common/src/main/java/com/juick/server/util/ImageUtils.java new file mode 100644 index 00000000..94ecf71e --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/ImageUtils.java @@ -0,0 +1,168 @@ + +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import org.apache.commons.imaging.ImageInfo; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.Imaging; +import org.apache.commons.imaging.common.ImageMetadata; +import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata; +import org.apache.commons.imaging.formats.tiff.TiffField; +import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; +import org.apache.commons.io.FilenameUtils; +import org.imgscalr.Scalr; +import org.imgscalr.Scalr.Rotation; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class ImageUtils { + +/** + * Returns <code>BufferedImage</code>, same as <code>ImageIO.read()</code> does. + * + * <p>JPEG images with EXIF metadata are rotated according to Orientation tag. + * + * @param imageFile a <code>File</code> to read from. + */ + private static BufferedImage readImageWithOrientation(File imageFile) + throws IOException { + + BufferedImage image = ImageIO.read(imageFile); + if (!FilenameUtils.getExtension(imageFile.getName()).equals("jpg")) { + return image; + } + + try { + ImageMetadata metadata = Imaging.getMetadata(imageFile); + + if (metadata instanceof JpegImageMetadata) { + JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata; + TiffField orientationField = jpegMetadata.findEXIFValue(TiffTagConstants.TIFF_TAG_ORIENTATION); + + if (orientationField != null) { + int orientation = orientationField.getIntValue(); + switch (orientation) { + case TiffTagConstants.ORIENTATION_VALUE_ROTATE_90_CW: + image = Scalr.rotate(image, Rotation.CW_90); + break; + case TiffTagConstants.ORIENTATION_VALUE_ROTATE_180: + image = Scalr.rotate(image, Rotation.CW_180); + break; + case TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW: + image = Scalr.rotate(image, Rotation.CW_270); + break; + case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL: + image = Scalr.rotate(image, Rotation.FLIP_HORZ); + break; + case TiffTagConstants.ORIENTATION_VALUE_MIRROR_VERTICAL: + image = Scalr.rotate(image, Rotation.FLIP_VERT); + break; + case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW: + image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_90); + break; + case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW: + image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_270); + break; + case TiffTagConstants.ORIENTATION_VALUE_HORIZONTAL_NORMAL: + default: + // do nothing + break; + } + } + } + } catch (ImageReadException e) { + // failed to read metadata. + // nothing to do here, return image as is. + } + + return image; + } + + /** + * Move attached image from temp folder to image folder. + * Create preview images in corresponding folders. + * + * @param tempFilename Name of the image file in the temp folder. + * @param outputFilename Name that will be used in the image folder. + * @param tmpDir Path string for the temp folder. + * @param imgDir Path string for the image folder. + */ + public static void saveImageWithPreviews(String tempFilename, String outputFilename, String tmpDir, String imgDir) + throws IOException { + String ext = FilenameUtils.getExtension(outputFilename); + + Path outputImagePath = Paths.get(imgDir, "p", outputFilename); + Files.move(Paths.get(tmpDir, tempFilename), outputImagePath); + BufferedImage originalImage = readImageWithOrientation(outputImagePath.toFile()); + + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + int maxDimension = (width > height) ? width : height; + BufferedImage image1024 = (maxDimension > 1024) ? Scalr.resize(originalImage, 1024) : originalImage; + BufferedImage image0512 = (maxDimension > 512) ? Scalr.resize(originalImage, 512) : originalImage; + BufferedImage image0160 = (maxDimension > 160) ? Scalr.resize(originalImage, 160) : originalImage; + ImageIO.write(image1024, ext, Paths.get(imgDir, "photos-1024", outputFilename).toFile()); + ImageIO.write(image0512, ext, Paths.get(imgDir, "photos-512", outputFilename).toFile()); + ImageIO.write(image0160, ext, Paths.get(imgDir, "ps", outputFilename).toFile()); + } + + /** + * Save new avatar in all required sizes. + * + * @param tempFilename Name of the image file in the temp folder. + * @param uid User id that is used to build image file names. + * @param tmpDir Path string for the temp folder. + * @param imgDir Path string for the image folder. + */ + public static void saveAvatar(String tempFilename, int uid, String tmpDir, String imgDir) + throws IOException { + String ext = FilenameUtils.getExtension(tempFilename); + String originalName = String.format("%s.%s", uid, ext); + Path originalPath = Paths.get(imgDir, "ao", originalName); + Files.move(Paths.get(tmpDir, tempFilename), originalPath, StandardCopyOption.REPLACE_EXISTING); + BufferedImage originalImage = ImageIO.read(originalPath.toFile()); + + String targetExt = "png"; + String targetName = String.format("%s.%s", uid, targetExt); + ImageIO.write(Scalr.resize(originalImage, 96), targetExt, Paths.get(imgDir, "a", targetName).toFile()); + ImageIO.write(Scalr.resize(originalImage, 32), targetExt, Paths.get(imgDir, "as", targetName).toFile()); + } + + public static Integer getImageHeight(File imageFile) throws IOException, ImageReadException { + if (imageFile.exists()) { + ImageInfo info = Imaging.getImageInfo(imageFile); + return info.getHeight(); + } + return 0; + } + public static Integer getImageWidth(File imageFile) throws IOException, ImageReadException { + if (imageFile.exists()) { + ImageInfo info = Imaging.getImageInfo(imageFile); + return info.getWidth(); + } + return 0; + } +}
\ No newline at end of file diff --git a/juick-common/src/main/java/com/juick/server/util/TagUtils.java b/juick-common/src/main/java/com/juick/server/util/TagUtils.java new file mode 100644 index 00000000..9edeab32 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/TagUtils.java @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import com.juick.Tag; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created by aalexeev on 11/13/16. + */ +public class TagUtils { + private TagUtils() { + throw new IllegalStateException(); + } + + public static String toString(final List<Tag> tags) { + if (CollectionUtils.isEmpty(tags)) + return StringUtils.EMPTY; + + return tags.stream().map(t -> " *" + t.getName()) + .collect(Collectors.joining()); + } +} diff --git a/juick-common/src/main/java/com/juick/server/util/UserUtils.java b/juick-common/src/main/java/com/juick/server/util/UserUtils.java new file mode 100644 index 00000000..ab5c320b --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/UserUtils.java @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import com.juick.User; +import com.juick.server.helpers.AnonymousUser; +import com.juick.service.security.entities.JuickUser; +import javax.annotation.Nonnull; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * Created by aalexeev on 11/14/16. + */ +public class UserUtils { + private UserUtils() { + throw new IllegalStateException(); + } + + public static Authentication getAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + + public static Object getPrincipal(final Authentication authentication) { + return authentication == null ? null : authentication.getPrincipal(); + } + + @Nonnull + public static User getCurrentUser() { + Object principal = getPrincipal(getAuthentication()); + + if (principal instanceof JuickUser) + return ((JuickUser) principal).getUser(); + + if (principal instanceof User) + return (User) principal; + + return AnonymousUser.INSTANCE; + } +} diff --git a/juick-common/src/main/java/com/juick/server/util/WebUtils.java b/juick-common/src/main/java/com/juick/server/util/WebUtils.java new file mode 100644 index 00000000..9dd628ee --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/util/WebUtils.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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.util; + +import java.util.regex.Pattern; + +/** + * Created by aalexeev on 11/28/16. + */ +public class WebUtils { + private WebUtils() { + throw new IllegalStateException(); + } + + private static final Pattern USER_NAME_PATTERN = Pattern.compile("[a-zA-Z-_\\d]{2,16}"); + + private static final Pattern POST_NUMBER_PATTERN = Pattern.compile("-?\\d+"); + + private static final Pattern JID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\\\-\\\\_\\\\@\\\\.]{6,64}$"); + + + public static boolean isPostNumber(final String aString) { + return aString != null && POST_NUMBER_PATTERN.matcher(aString).matches(); + } + + public static boolean isNotPostNumber(final String aString) { + return !isPostNumber(aString); + } + + public static boolean isUserName(final String aString) { + return aString != null && USER_NAME_PATTERN.matcher(aString).matches(); + } + + public static boolean isNotUserName(final String aString) { + return !isUserName(aString); + } + + public static boolean isJid(final String aString) { + return aString != null && JID_PATTERN.matcher(aString).matches(); + } + + public static boolean isNotJid(final String aString) { + return !isJid(aString); + } + + +} diff --git a/juick-common/src/main/java/com/juick/server/xmpp/JidConverter.java b/juick-common/src/main/java/com/juick/server/xmpp/JidConverter.java new file mode 100644 index 00000000..e9a9707e --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/xmpp/JidConverter.java @@ -0,0 +1,13 @@ +package com.juick.server.xmpp; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; +import rocks.xmpp.addr.Jid; + +public class JidConverter implements Converter<String, Jid> { + @Nullable + @Override + public Jid convert(String jidStr) { + return Jid.of(jidStr); + } +} diff --git a/juick-common/src/main/java/com/juick/server/xmpp/extensions/JuickMessage.java b/juick-common/src/main/java/com/juick/server/xmpp/extensions/JuickMessage.java new file mode 100644 index 00000000..6956a99a --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/xmpp/extensions/JuickMessage.java @@ -0,0 +1,162 @@ +/* + * Juick + * Copyright (C) 2008-2011, Ugnich Anton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.juick.server.xmpp.extensions; + +import com.juick.Tag; +import com.juick.xmpp.StanzaChild; +import com.juick.xmpp.utils.XmlUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; +/** + * + * @author Ugnich Anton + */ +public class JuickMessage extends com.juick.Message implements StanzaChild { + public final static String XMLNS = "http://juick.com/message"; + public final static String TagName = "juick"; + private SimpleDateFormat df; + public JuickMessage() { + df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + @Override + public String getXMLNS() { + return XMLNS; + } + @Override + public JuickMessage parse(XmlPullParser parser) throws XmlPullParserException, IOException, ParseException { + JuickMessage jmsg = new JuickMessage(); + final String sMID = parser.getAttributeValue(null, "mid"); + if (sMID != null) { + jmsg.setMid(Integer.parseInt(sMID)); + } + final String sRID = parser.getAttributeValue(null, "rid"); + if (sRID != null) { + jmsg.setRid(Integer.parseInt(sRID)); + } + final String sReplyTo = parser.getAttributeValue(null, "replyto"); + if (sReplyTo != null) { + jmsg.setReplyto(Integer.parseInt(sReplyTo)); + } + final String sPrivacy = parser.getAttributeValue(null, "privacy"); + if (sPrivacy != null) { + jmsg.setPrivacy(Integer.parseInt(sPrivacy)); + } + final String sFriendsOnly = parser.getAttributeValue(null, "friendsonly"); + if (sFriendsOnly != null) { + jmsg.FriendsOnly = true; + } + final String sReadOnly = parser.getAttributeValue(null, "readonly"); + if (sReadOnly != null) { + jmsg.ReadOnly = true; + } + jmsg.setTimestamp(df.parse(parser.getAttributeValue(null, "ts")).toInstant()); + jmsg.setAttachmentType(parser.getAttributeValue(null, "attach")); + while (parser.next() == XmlPullParser.START_TAG) { + final String tag = parser.getName(); + final String xmlns = parser.getNamespace(); + if (tag.equals("body")) { + jmsg.setText(XmlUtils.getTagText(parser)); + } else if (tag.equals(JuickUser.TagName) && xmlns != null && xmlns.equals(JuickUser.XMLNS)) { + jmsg.setUser(new JuickUser().parse(parser)); + } else if (tag.equals("tag")) { + jmsg.getTags().add(new Tag(XmlUtils.getTagText(parser))); + } else { + XmlUtils.skip(parser); + } + } + return jmsg; + } + @Override + public String toString() { + StringBuilder ret = new StringBuilder("<").append(TagName).append(" xmlns=\"").append(XMLNS).append("\""); + if (getMid() > 0) { + ret.append(" mid=\"").append(getMid()).append("\""); + } + if (getRid() > 0) { + ret.append(" rid=\"").append(getRid()).append("\""); + } + if (getReplyto() > 0) { + ret.append(" replyto=\"").append(getReplyto()).append("\""); + } + ret.append(" privacy=\"").append(getPrivacy()).append("\""); + if (FriendsOnly) { + ret.append(" friendsonly=\"1\""); + } + if (ReadOnly) { + ret.append(" readonly=\"1\""); + } + if (getTimestamp() != null) { + ret.append(" ts=\"").append(df.format(Date.from(getTimestamp()))).append("\""); + } + if (getAttachmentType() != null) { + ret.append(" attach=\"").append(getAttachmentType()).append("\""); + } + ret.append(">"); + if (getUser() != null) { + ret.append(JuickUser.toString(getUser())); + } + if (getText() != null) { + ret.append("<body>").append(StringEscapeUtils.escapeXml10(StringUtils.defaultString(getText()))).append("</body>"); + } + for (Tag Tag : getTags()) { + ret.append("<tag>").append(StringEscapeUtils.escapeXml10(Tag.getName())).append("</tag>"); + } + ret.append("</").append(TagName).append(">"); + return ret.toString(); + } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof JuickMessage)) { + return false; + } + JuickMessage jmsg = (JuickMessage) obj; + return (this.getMid() == jmsg.getMid() && this.getRid() == jmsg.getRid()); + } + @Override + public int compareTo(Object obj) throws ClassCastException { + if (!(obj instanceof JuickMessage)) { + throw new ClassCastException(); + } + JuickMessage jmsg = (JuickMessage) obj; + if (this.getMid() != jmsg.getMid()) { + if (this.getMid() > jmsg.getMid()) { + return -1; + } else { + return 1; + } + } + if (this.getRid() != jmsg.getRid()) { + if (this.getRid() < jmsg.getRid()) { + return -1; + } else { + return 1; + } + } + return 0; + } +}
\ No newline at end of file diff --git a/juick-common/src/main/java/com/juick/server/xmpp/extensions/JuickUser.java b/juick-common/src/main/java/com/juick/server/xmpp/extensions/JuickUser.java new file mode 100644 index 00000000..534efcc9 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/xmpp/extensions/JuickUser.java @@ -0,0 +1,65 @@ +/* + * Juick + * Copyright (C) 2008-2011, Ugnich Anton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package com.juick.server.xmpp.extensions; +import com.juick.xmpp.StanzaChild; +import com.juick.xmpp.utils.XmlUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +/** + * + * @author Ugnich Anton + */ +public class JuickUser extends com.juick.User implements StanzaChild { + public final static String XMLNS = "http://juick.com/user"; + public final static String TagName = "user"; + public JuickUser() { + } + @Override + public String getXMLNS() { + return XMLNS; + } + @Override + public JuickUser parse(final XmlPullParser parser) throws XmlPullParserException, IOException { + JuickUser juser = new JuickUser(); + String strUID = parser.getAttributeValue(null, "uid"); + if (strUID != null) { + juser.setUid(Integer.parseInt(strUID)); + } + juser.setName(parser.getAttributeValue(null, "uname")); + XmlUtils.skip(parser); + return juser; + } + public static String toString(com.juick.User user) { + String str = "<" + TagName + " xmlns='" + XMLNS + "'"; + if (user.getUid() > 0) { + str += " uid='" + user.getUid() + "'"; + } + if (user.getName() != null && user.getName().length() > 0) { + str += " uname='" + StringEscapeUtils.escapeXml10(user.getName()) + "'"; + } + str += "/>"; + return str; + } + @Override + public String toString() { + return toString(this); + } +}
\ No newline at end of file diff --git a/juick-common/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java b/juick-common/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java new file mode 100644 index 00000000..647f2717 --- /dev/null +++ b/juick-common/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.server.xmpp.s2s; + +import rocks.xmpp.addr.Jid; +import rocks.xmpp.core.XmppException; +import rocks.xmpp.core.session.ConnectionConfiguration; +import rocks.xmpp.core.session.XmppSession; +import rocks.xmpp.core.session.XmppSessionConfiguration; +import rocks.xmpp.core.stanza.model.IQ; +import rocks.xmpp.core.stanza.model.Message; +import rocks.xmpp.core.stanza.model.Presence; +import rocks.xmpp.core.stanza.model.server.ServerIQ; +import rocks.xmpp.core.stanza.model.server.ServerMessage; +import rocks.xmpp.core.stanza.model.server.ServerPresence; +import rocks.xmpp.core.stream.model.StreamElement; + +/** + * Created by vitalyster on 06.02.2017. + */ +public class BasicXmppSession extends XmppSession { + protected BasicXmppSession(String xmppServiceDomain, XmppSessionConfiguration configuration, ConnectionConfiguration... connectionConfigurations) { + super(xmppServiceDomain, configuration, connectionConfigurations); + } + + public static BasicXmppSession create(String xmppServiceDomain, XmppSessionConfiguration configuration) { + BasicXmppSession session = new BasicXmppSession(xmppServiceDomain, configuration); + notifyCreationListeners(session); + return session; + } + + @Override + public void connect(Jid from) throws XmppException { + + } + + @Override + public Jid getConnectedResource() { + return null; + } + + @Override + protected StreamElement prepareElement(StreamElement element) { + if (element instanceof Message) { + element = ServerMessage.from((Message) element); + } else if (element instanceof Presence) { + element = ServerPresence.from((Presence) element); + } else if (element instanceof IQ) { + element = ServerIQ.from((IQ) element); + } + + return element; + } +} diff --git a/juick-common/src/main/java/com/juick/service/BaseRestService.java b/juick-common/src/main/java/com/juick/service/BaseRestService.java new file mode 100644 index 00000000..13604a89 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/BaseRestService.java @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import org.springframework.web.client.RestTemplate; + +/** + * Created by vitalyster on 15.12.2016. + */ +public abstract class BaseRestService { + private RestTemplate rest; + + public BaseRestService(RestTemplate rest) { + this.rest = rest; + } + + public RestTemplate getRest() { + return rest; + } +} diff --git a/juick-common/src/main/java/com/juick/service/CrosspostService.java b/juick-common/src/main/java/com/juick/service/CrosspostService.java new file mode 100644 index 00000000..b82621e5 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/CrosspostService.java @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.ExternalToken; +import com.juick.server.helpers.ApplicationStatus; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import java.util.Optional; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface CrosspostService { + + Optional<ExternalToken> getTwitterToken(int uid); + + boolean deleteTwitterToken(Integer uid); + + Optional<Pair<String, String>> getFacebookTokens(int uid); + + ApplicationStatus getFbCrossPostStatus(int uid); + + boolean enableFBCrosspost(Integer uid); + + void disableFBCrosspost(Integer uid); + + @Nonnull + String getTwitterName(int uid); + + String getTelegramName(int uid); + + Optional<Pair<String, String>> getVkTokens(int uid); + + void deleteVKUser(Integer uid); + + int getUIDbyFBID(long fbID); + + boolean createFacebookUser(long fbID, String loginhash, String token, String fbName, String fbLink); + + boolean updateFacebookUser(long fbID, String token, String fbName, String fbLink); + + int getUIDbyVKID(long vkID); + + boolean createVKUser(long vkID, String loginhash, String token, String vkName, String vkLink); + + String getFacebookNameByHash(String hash); + + String getTelegramNameByHash(String hash); + + boolean setFacebookUser(String hash, int uid); + + String getVKNameByHash(String hash); + + boolean setVKUser(String hash, int uid); + + boolean setTelegramUser(String hash, int uid); + + String getJIDByHash(String hash); + + boolean setJIDUser(String hash, int uid); +} diff --git a/juick-common/src/main/java/com/juick/service/EmailService.java b/juick-common/src/main/java/com/juick/service/EmailService.java new file mode 100644 index 00000000..2440bcb4 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/EmailService.java @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import java.util.List; + +/** + * Created by vitalyster on 09.12.2016. + */ +public interface EmailService { + boolean verifyAddressByCode(Integer userId, String code); + boolean addVerificationCode(Integer userId, String account, String code); + boolean addEmail(Integer userId, String email); + boolean deleteEmail(Integer userId, String account); + String getNotificationsEmail(Integer userId); + boolean setNotificationsEmail(Integer userId, String account); + List<String> getEmails(Integer userId, boolean active); +} diff --git a/juick-common/src/main/java/com/juick/service/ImagesService.java b/juick-common/src/main/java/com/juick/service/ImagesService.java new file mode 100644 index 00000000..b5cff16e --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/ImagesService.java @@ -0,0 +1,7 @@ +package com.juick.service; + +import com.juick.Message; + +public interface ImagesService { + void setAttachmentMetadata(String imgDir, String baseUrl, Message msg) throws Exception; +} diff --git a/juick-common/src/main/java/com/juick/service/ImagesServiceImpl.java b/juick-common/src/main/java/com/juick/service/ImagesServiceImpl.java new file mode 100644 index 00000000..3a5c77dd --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/ImagesServiceImpl.java @@ -0,0 +1,71 @@ +package com.juick.service; + +import com.juick.Attachment; +import com.juick.Message; +import com.juick.Photo; +import com.juick.server.util.ImageUtils; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.nio.file.Paths; + +public class ImagesServiceImpl implements ImagesService { + @Override + public void setAttachmentMetadata(String imgDir, String baseUrl, Message msg) throws Exception { + if (!StringUtils.isEmpty(msg.getAttachmentType())) { + Photo photo = new Photo(); + if (msg.getRid()> 0) { + photo.setSmall(String.format("%sphotos-512/%d-%d.%s", baseUrl, msg.getMid(), msg.getRid(), msg.getAttachmentType())); + photo.setMedium(String.format("%sphotos-1024/%d-%d.%s", baseUrl, msg.getMid(), msg.getRid(), msg.getAttachmentType())); + photo.setThumbnail(String.format("%sps/%d-%d.%s", baseUrl, msg.getMid(), msg.getRid(), msg.getAttachmentType())); + } else { + photo.setSmall(String.format("%sphotos-512/%d.%s", baseUrl, msg.getMid(), msg.getAttachmentType())); + photo.setMedium(String.format("%sphotos-1024/%d.%s", baseUrl, msg.getMid(), msg.getAttachmentType())); + photo.setThumbnail(String.format("%sps/%d.%s", baseUrl, msg.getMid(), msg.getAttachmentType())); + } + msg.setPhoto(photo); + String imageName = String.format("%s.%s", msg.getMid(), msg.getAttachmentType()); + if (msg.getRid() > 0) { + imageName = String.format("%s-%s.%s", msg.getMid(), msg.getRid(), msg.getAttachmentType()); + } + File fullImage = Paths.get(imgDir, "p", imageName).toFile(); + File mediumImage = Paths.get(imgDir, "photos-1024", imageName).toFile(); + File smallImage = Paths.get(imgDir, "photos-512", imageName).toFile(); + File thumbnailImage = Paths.get(imgDir, "ps", imageName).toFile(); + StringBuilder builder = new StringBuilder(); + builder.append(baseUrl); + builder.append(msg.getAttachmentType().equals("mp4") ? "video" : "p"); + builder.append("/").append(msg.getMid()); + if (msg.getRid() > 0) { + builder.append("-").append(msg.getRid()); + } + builder.append(".").append(msg.getAttachmentType()); + String originalUrl = builder.toString(); + + Attachment original = new Attachment(); + original.setUrl(originalUrl); + original.setHeight(ImageUtils.getImageHeight(fullImage)); + original.setWidth(ImageUtils.getImageWidth(fullImage)); + + Attachment medium = new Attachment(); + medium.setUrl(photo.getMedium()); + medium.setWidth(ImageUtils.getImageWidth(mediumImage)); + medium.setHeight(ImageUtils.getImageHeight(mediumImage)); + original.setMedium(medium); + + Attachment small = new Attachment(); + small.setUrl(photo.getSmall()); + small.setWidth(ImageUtils.getImageWidth(smallImage)); + small.setHeight(ImageUtils.getImageHeight(smallImage)); + original.setSmall(small); + + Attachment thumb = new Attachment(); + thumb.setUrl(photo.getMedium()); + thumb.setWidth(ImageUtils.getImageWidth(thumbnailImage)); + thumb.setHeight(ImageUtils.getImageHeight(thumbnailImage)); + original.setThumbnail(thumb); + + msg.setAttachment(original); + } + } +} diff --git a/juick-common/src/main/java/com/juick/service/MessagesService.java b/juick-common/src/main/java/com/juick/service/MessagesService.java new file mode 100644 index 00000000..341175dd --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/MessagesService.java @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.User; +import com.juick.server.helpers.ResponseReply; + +import java.util.Collection; +import java.util.List; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface MessagesService { + int createMessage(int uid, String txt, String attachment, Collection<com.juick.Tag> tags); + + int createReply(int mid, int rid, int uid, String txt, String attachment); + + int getReplyIDIncrement(int mid); + + boolean recommendMessage(int mid, int vuid); + + boolean canViewThread(int mid, int uid); + + boolean isReadOnly(int mid); + + boolean isSubscribed(int uid, int mid); + + int getMessagePrivacy(int mid); + + com.juick.Message getMessage(int mid); + + com.juick.Message getReply(int mid, int rid); + + User getMessageAuthor(int mid); + + List<String> getMessageRecommendations(int mid); + + List<Integer> getAll(int visitorUid, int before); + + List<Integer> getTag(int tid, int visitorUid, int before, int cnt); + + List<Integer> getTags(String tids, int visitorUid, int before, int cnt); + + List<Integer> getPlace(int placeId, int visitorUid, int before); + + List<Integer> getMyFeed(int uid, int before, boolean recommended); + + List<Integer> getPrivate(int uid, int before); + + List<Integer> getDiscussions(int uid, Long to); + + List<Integer> getRecommended(int uid, int before); + + List<Integer> getPopular(int visitorUid, int before); + + List<Integer> getPhotos(int visitorUid, int before); + + List<Integer> getSearch(String search, int before); + + List<Integer> getUserBlog(int uid, int privacy, int before); + + List<Integer> getUserTag(int uid, int tid, int privacy, int before); + + List<Integer> getUserBlogAtDay(int uid, int privacy, int daysback); + + List<Integer> getUserBlogWithRecommendations(int uid, int privacy, int before); + + List<Integer> getUserRecommendations(int uid, int before); + + List<Integer> getUserPhotos(int uid, int privacy, int before); + + List<Integer> getUserSearch(int UID, String search, int privacy, int before); + + List<com.juick.Message> getMessages(List<Integer> mids); + + List<com.juick.Message> getReplies(int mid); + + boolean setMessagePopular(int mid, int popular); + + boolean setMessagePrivacy(int mid); + + boolean deleteMessage(int uid, int mid); + + boolean deleteReply(int uid, int mid, int rid); + + List<Integer> getLastMessages(int hours); + + List<ResponseReply> getLastReplies(int hours); + + List<Integer> getPopularCandidates(); +} diff --git a/juick-common/src/main/java/com/juick/service/MessengerService.java b/juick-common/src/main/java/com/juick/service/MessengerService.java new file mode 100644 index 00000000..e07c73fe --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/MessengerService.java @@ -0,0 +1,14 @@ +package com.juick.service; + +import com.juick.User; + +import java.util.Optional; + +public interface MessengerService { + Integer getUserId(String senderId); + Optional<String> getSenderId(User user); + boolean createMessengerUser(String senderId, String displayName); + String getDisplayName(String hash); + String getSignUpHash(String senderId, String username); + boolean linkMessengerUser(String hash, int uid); +} diff --git a/juick-common/src/main/java/com/juick/service/PMQueriesService.java b/juick-common/src/main/java/com/juick/service/PMQueriesService.java new file mode 100644 index 00000000..4c70eece --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/PMQueriesService.java @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.User; + +import java.util.List; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface PMQueriesService { + boolean createPM(int uidFrom, int uid_to, String body); + + boolean addPMinRoster(int uid, String jid); + + boolean removePMinRoster(int uid, String jid); + + boolean havePMinRoster(int uid, String jid); + + String getLastView(int uidFrom, int uidTo); + + List<User> getPMLastConversationsUsers(int uid, int cnt); + + List<com.juick.Message> getPMMessages(int uid, int uidTo); + + List<com.juick.Message> getLastPMInbox(int uid); + + List<com.juick.Message> getLastPMSent(int uid); +} diff --git a/juick-common/src/main/java/com/juick/service/PrivacyQueriesService.java b/juick-common/src/main/java/com/juick/service/PrivacyQueriesService.java new file mode 100644 index 00000000..17dd6a9b --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/PrivacyQueriesService.java @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.Tag; +import com.juick.User; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface PrivacyQueriesService { + enum PrivacyResult { + Removed, Added + } + + PrivacyResult blacklistUser(User user, User target); + + PrivacyResult blacklistTag(User user, Tag tag); +} diff --git a/juick-common/src/main/java/com/juick/service/PushQueriesService.java b/juick-common/src/main/java/com/juick/service/PushQueriesService.java new file mode 100644 index 00000000..f84a83e4 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/PushQueriesService.java @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import java.util.Collection; +import java.util.List; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface PushQueriesService { + List<String> getGCMRegID(int uid); + + List<String> getGCMTokens(Collection<Integer> uids); + + boolean addGCMToken(Integer uid, String token); + + boolean deleteGCMToken(String token); + + List<String> getMPNSURL(int uid); + + List<String> getMPNSTokens(Collection<Integer> uids); + + boolean addMPNSToken(Integer uid, String token); + + boolean deleteMPNSToken(String token); + + List<String> getAPNSToken(int uid); + + List<String> getAPNSTokens(Collection<Integer> uids); + + boolean addAPNSToken(Integer uid, String token); + + boolean deleteAPNSToken(String token); +} diff --git a/juick-common/src/main/java/com/juick/service/ShowQueriesService.java b/juick-common/src/main/java/com/juick/service/ShowQueriesService.java new file mode 100644 index 00000000..32b34b4e --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/ShowQueriesService.java @@ -0,0 +1,31 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.User; + +import java.util.List; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface ShowQueriesService { + List<String> getRecommendedUsers(User forUser); + + List<String> getTopUsers(); +} diff --git a/juick-common/src/main/java/com/juick/service/SubscriptionService.java b/juick-common/src/main/java/com/juick/service/SubscriptionService.java new file mode 100644 index 00000000..47f81415 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/SubscriptionService.java @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.Tag; +import com.juick.User; +import com.juick.server.helpers.NotifyOpts; + +import java.util.List; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface SubscriptionService { + List<String> getJIDSubscribedToUser(int uid, boolean friendsonly); + + List<User> getSubscribedUsers(int uid, int mid); + + List<User> getUsersSubscribedToComments(int mid, int ignore_uid); + + List<User> getUsersSubscribedToUserRecommendations(int uid, int mid, int muid); + + boolean subscribeMessage(int mid, int vuid); + + boolean unSubscribeMessage(int mid, int vuid); + + boolean subscribeUser(User user, User toUser); + + boolean unSubscribeUser(User user, User fromUser); + + boolean subscribeTag(User user, Tag toTag); + + boolean unSubscribeTag(User user, Tag toTag); + + List<String> getSubscribedTags(User user); + + NotifyOpts getNotifyOptions(User user); + + boolean setNotifyOptions(User user, NotifyOpts options); +} diff --git a/juick-common/src/main/java/com/juick/service/TagService.java b/juick-common/src/main/java/com/juick/service/TagService.java new file mode 100644 index 00000000..7cd7768f --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/TagService.java @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.Tag; +import com.juick.User; +import com.juick.server.helpers.TagStats; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface TagService { + Tag getTag(int tid); + + Tag getTag(String tag, boolean autoCreate); + + List<Tag> getTags(Stream<String> tags, boolean autoCreate); + + boolean getTagNoIndex(int tagId); + + int createTag(String name); + + List<TagStats> getUserTagStats(int uid); + + List<String> getUserBLTags(int uid); + + List<String> getPopularTags(); + + List<TagStats> getTagStats(); + + List<Tag> updateTags(int mid, Collection<Tag> newTags); + + List<Tag> fromString(String txt, boolean tagsOnly); + + List<TagStats> getMessageTags(int mid); + + List<Integer> getMessageTagsIDs(int mid); + + boolean blacklistTag(User user, Tag tag); + + boolean isInBL(User user, Tag tag); + + boolean isSubscribed(User user, Tag tag); +} diff --git a/juick-common/src/main/java/com/juick/service/TelegramService.java b/juick-common/src/main/java/com/juick/service/TelegramService.java new file mode 100644 index 00000000..7786ca9f --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/TelegramService.java @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.User; + +import java.util.List; + +/** + * Created by vt on 24/11/2016. + */ +public interface TelegramService { + boolean addChat(Long id); + + boolean deleteChat(Long id); + + List<Long> getChats(); + + int getUser(long tgId); + + boolean createTelegramUser(long tgID, String tgName); + + boolean deleteTelegramUser(Integer uid); + + List<Long> getTelegramIdentifiers(List<User> users); +} diff --git a/juick-common/src/main/java/com/juick/service/UserService.java b/juick-common/src/main/java/com/juick/service/UserService.java new file mode 100644 index 00000000..115c7dfc --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/UserService.java @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service; + +import com.juick.User; +import com.juick.server.helpers.Auth; +import com.juick.server.helpers.UserInfo; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * Created by aalexeev on 11/13/16. + */ +public interface UserService { + enum ActiveStatus { + Inactive, + Active + } + + String getSignUpHashByJID(String jid); + + String getSignUpHashByTelegramID(Long telegramId, String username); + + int createUser(String username, String password); + + Optional<User> getUserByUID(int uid); + + User getUserByName(String username); + + User getFullyUserByName(String username); + + User getUserByEmail(String email); + + List<User> getFullyUsersByNames(Collection<String> usernames); + + User getUserByJID(String jid); + + List<User> getUsersByName(Collection<String> unames); + + List<User> getUsersByID(Collection<Integer> uids); + + List<String> getJIDsbyUID(int uid); + + int getUIDbyJID(String jid); + + int getUIDbyName(String uname); + + int getUIDbyHash(String hash); + + com.juick.User getUserByHash(String hash); + + String getHashByUID(int uid); + + int getUIDByHttpAuth(String header); + + int checkPassword(String username, String password); + + boolean updatePassword(User user, String newPassword); + + int getUserOptionInt(int uid, String option, int defaultValue); + + int setUserOptionInt(int uid, String option, int value); + + UserInfo getUserInfo(User user); + + boolean updateUserInfo(User user, UserInfo info); + + boolean getCanMedia(int uid); + + boolean isInWL(int uid, int check); + + boolean isInBL(int uid, int check); + + boolean isInBLAny(int uid, int uid2); + + List<Integer> checkBL(int visitor, Collection<Integer> uids); + + boolean isSubscribed(int uid, int check); + + List<Integer> getUserRead(int uid); + + List<com.juick.User> getUserReadLeastPopular(int uid, int cnt); + + List<User> getUserReaders(int uid); + + List<User> getUserFriends(int uid); + + List<com.juick.User> getUserBLUsers(int uid); + + boolean linkTwitterAccount(User user, String accessToken, String accessTokenSecret, String screenName); + + int getStatsIRead(int uid); + + int getStatsMyReaders(int uid); + + int getStatsMessages(int uid); + + int getStatsReplies(int uid); + + boolean setActiveStatusForJID(String JID, ActiveStatus jidStatus); + + List<String> getAllJIDs(User user); + + List<Auth> getAuthCodes(User user); + + List<String> getEmails(User user); + + String getEmailHash(User user); + + int deleteLoginForUser(String name); + + int setLoginForUser(int uid, String loginHash); + + void logout(int uid); + + boolean deleteJID(int uid, String jid); + + boolean unauthJID(int uid, String jid); + + List<String> getActiveJIDs(); +} diff --git a/juick-common/src/main/java/com/juick/service/search/SearchService.java b/juick-common/src/main/java/com/juick/service/search/SearchService.java new file mode 100644 index 00000000..b1ea9374 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/search/SearchService.java @@ -0,0 +1,31 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.search; + +import java.util.List; + +/** + * Created by aalexeev on 11/18/16. + */ +public interface SearchService { + void setMaxResult(int maxResult); + + List<Integer> searchInAllMessages(String searchString, int messageIdBefore); + + List<Integer> searchByStringAndUser(String searchString, final int userId, int messageIdBefore); +} diff --git a/juick-common/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java b/juick-common/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java new file mode 100644 index 00000000..b56b98c8 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java @@ -0,0 +1,103 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security; + +import com.juick.User; +import com.juick.service.security.entities.JuickUser; +import com.juick.service.UserService; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.RememberMeAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; +import org.springframework.util.Assert; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by aalexeev on 4/5/17. + */ +public class HashParamAuthenticationFilter extends OncePerRequestFilter { + public static final String PARAM_NAME = "hash"; + + private final UserService userService; + private final RememberMeServices rememberMeServices; + + + public HashParamAuthenticationFilter( + final UserService userService, + final RememberMeServices rememberMeServices) { + Assert.notNull(userService, "userService should not be null"); + Assert.notNull(rememberMeServices, "rememberMeServices should not be null"); + + this.userService = userService; + this.rememberMeServices = rememberMeServices; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String hash = getHashFromRequest(request); + + if (hash != null && authenticationIsRequired()) { + User user = userService.getUserByHash(hash); + + if (!user.isAnonymous()) { + User userWithPassword = userService.getFullyUserByName(user.getName()); + userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid())); + Authentication authentication = new RememberMeAuthenticationToken( + ((AbstractRememberMeServices)rememberMeServices).getKey(), new JuickUser(userWithPassword), JuickUser.USER_AUTHORITY); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + rememberMeServices.loginSuccess(request, response, authentication); + } + } + + filterChain.doFilter(request, response); + } + + private boolean authenticationIsRequired() { + Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); + + return existingAuth == null || + !existingAuth.isAuthenticated() || + existingAuth instanceof AnonymousAuthenticationToken; + } + + private String getHashFromRequest(HttpServletRequest request) { + String paramHash = request.getParameter(PARAM_NAME); + Cookie cookieHash = WebUtils.getCookie(request, PARAM_NAME); + + if (paramHash == null && cookieHash != null) { + return cookieHash.getValue(); + } + return paramHash; + } +} diff --git a/juick-common/src/main/java/com/juick/service/security/JuickUserDetailsService.java b/juick-common/src/main/java/com/juick/service/security/JuickUserDetailsService.java new file mode 100644 index 00000000..f6ae8909 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/JuickUserDetailsService.java @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security; + +import com.juick.service.UserService; +import com.juick.service.security.entities.JuickUser; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.util.Assert; + +/** + * Created by aalexeev on 11/28/16. + */ +public class JuickUserDetailsService implements UserDetailsService { + private final UserService userService; + + public JuickUserDetailsService(final UserService userService) { + Assert.notNull(userService, "UserService must be initialized"); + this.userService = userService; + } + + @Override + public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { + if (StringUtils.isBlank(username)) + throw new UsernameNotFoundException("Invalid user name " + username); + + com.juick.User user = userService.getFullyUserByName(username); + + if (user != null) { + user.setAuthHash(userService.getHashByUID(user.getUid())); + return new JuickUser(user); + } + + throw new UsernameNotFoundException("The username " + username + " is not found"); + } +} diff --git a/juick-common/src/main/java/com/juick/service/security/NotAuthorizedAuthenticationEntryPoint.java b/juick-common/src/main/java/com/juick/service/security/NotAuthorizedAuthenticationEntryPoint.java new file mode 100644 index 00000000..b9bdcaa9 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/NotAuthorizedAuthenticationEntryPoint.java @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Created by vitalyster on 25.11.2016. + */ +public class NotAuthorizedAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } +} diff --git a/juick-common/src/main/java/com/juick/service/security/NullUserDetailsService.java b/juick-common/src/main/java/com/juick/service/security/NullUserDetailsService.java new file mode 100644 index 00000000..91acefa3 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/NullUserDetailsService.java @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * Created by aalexeev on 11/28/16. + */ +public class NullUserDetailsService implements UserDetailsService { + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + throw new UsernameNotFoundException( + "loadUserByUsername called for NullUserDetailsService, user " + username + "can not be found"); + } +} diff --git a/juick-common/src/main/java/com/juick/service/security/deprecated/CookieSimpleHashRememberMeServices.java b/juick-common/src/main/java/com/juick/service/security/deprecated/CookieSimpleHashRememberMeServices.java new file mode 100644 index 00000000..a8b956c1 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/deprecated/CookieSimpleHashRememberMeServices.java @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security.deprecated; + +import com.juick.User; +import com.juick.server.util.HashUtils; +import com.juick.service.security.entities.JuickUser; +import com.juick.service.UserService; +import com.juick.service.security.NullUserDetailsService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; +import org.springframework.security.web.authentication.rememberme.InvalidCookieException; +import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +/** + * Created by aalexeev on 11/28/16. + * + * @deprecated not recommended use for secure reasons + */ +@Deprecated +public class CookieSimpleHashRememberMeServices extends AbstractRememberMeServices implements RememberMeServices { + private static final Logger logger = LoggerFactory.getLogger(CookieSimpleHashRememberMeServices.class); + + private static final String COOKIE_PARAM_NAME = "hash"; + + private final UserService userService; + + public CookieSimpleHashRememberMeServices( + final String key, final UserService userService, final Environment environment) { + super(key, new NullUserDetailsService()); + + Assert.notNull(userService); + Assert.notNull(environment); + + this.userService = userService; + + setCookieName(COOKIE_PARAM_NAME); + setCookieDomain(environment.getProperty("web_domain", "localhost")); + setAlwaysRemember(true); + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + super.logout(request, response, authentication); + userService.deleteLoginForUser(authentication.getName()); + } + + @Override + protected void onLoginSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { + String username = successfulAuthentication.getName(); + + logger.debug("Creating new persistent login for user {}", username); + + try { + int uid = userService.getUIDbyName(username); + + Assert.isTrue(uid > 0); + + String hash = HashUtils.generateHash(16); + + userService.setLoginForUser(uid, hash); + + setCookie(new String[]{hash}, getTokenValiditySeconds(), request, response); + } catch (Exception e) { + logger.error("Failed to save cookies", e); + } + } + + @Override + protected UserDetails processAutoLoginCookie( + String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) + throws RememberMeAuthenticationException, UsernameNotFoundException { + String hash = cookieTokens[0]; + + if (StringUtils.isBlank(hash)) { + hash = request.getParameter("hash"); + } + if (StringUtils.isBlank(hash)) { + throw new InvalidCookieException("Cookie is invalid and hash parameter not found"); + } + + int uid = userService.getUIDbyHash(hash); + if (uid <= 0) + throw new UsernameNotFoundException("User not found by hash, cookies" + cookieTokens); + + Optional<User> userOptional = userService.getUserByUID(uid); + + Assert.isTrue(userOptional.isPresent()); + + return new JuickUser(userOptional.get()); + } + + @Override + protected String[] decodeCookie(String cookieValue) throws InvalidCookieException { + return new String[]{cookieValue}; + } + + @Override + protected String encodeCookie(String[] cookieTokens) { + return cookieTokens != null && cookieTokens.length > 0 ? cookieTokens[0] : StringUtils.EMPTY; + } +} diff --git a/juick-common/src/main/java/com/juick/service/security/deprecated/RequestParamHashRememberMeServices.java b/juick-common/src/main/java/com/juick/service/security/deprecated/RequestParamHashRememberMeServices.java new file mode 100644 index 00000000..04794d07 --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/deprecated/RequestParamHashRememberMeServices.java @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security.deprecated; + +import com.juick.User; +import com.juick.service.security.entities.JuickUser; +import com.juick.service.UserService; +import com.juick.service.security.NullUserDetailsService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; +import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Created by aalexeev on 11/30/16. + * + * @deprecated for security reasons + */ +@Deprecated +public class RequestParamHashRememberMeServices extends AbstractRememberMeServices implements RememberMeServices { + private static final String PARAM_NAME = "hash"; + + private final UserService userService; + + public RequestParamHashRememberMeServices(String key, UserService userService) { + super(key, new NullUserDetailsService()); + + Assert.notNull(userService); + this.userService = userService; + setAlwaysRemember(false); + } + + @Override + protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { + // do nothing + } + + @Override + protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { + return false; // always false + } + + @Override + protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) { + // do nothing + } + + @Override + protected String extractRememberMeCookie(HttpServletRequest request) { + return PARAM_NAME; // return any not blank value + } + + @Override + protected UserDetails processAutoLoginCookie( + String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) + throws RememberMeAuthenticationException, UsernameNotFoundException { + String hash = request.getParameter(PARAM_NAME); + + if (StringUtils.isNotBlank(hash)) { + User user = userService.getUserByHash(hash); + if (user.getUid() > 0) + return new JuickUser(user); + } + throw new UsernameNotFoundException("User not found by hash " + hash); + } +} diff --git a/juick-common/src/main/java/com/juick/service/security/entities/JuickUser.java b/juick-common/src/main/java/com/juick/service/security/entities/JuickUser.java new file mode 100644 index 00000000..6e72117e --- /dev/null +++ b/juick-common/src/main/java/com/juick/service/security/entities/JuickUser.java @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.service.security.entities; + +import com.juick.User; +import com.juick.server.helpers.AnonymousUser; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Created by aalexeev on 11/21/16. + */ +public class JuickUser implements UserDetails { + static final GrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER"); + static final GrantedAuthority ROLE_ANONYMOUS = new SimpleGrantedAuthority("ROLE_ANONYMOUS"); + + public static final List<GrantedAuthority> USER_AUTHORITY = Collections.singletonList(ROLE_USER); + public static final List<GrantedAuthority> ANONYMOUS_AUTHORITY = Collections.singletonList(ROLE_ANONYMOUS); + + public static final JuickUser ANONYMOUS_USER = new JuickUser(AnonymousUser.INSTANCE, ANONYMOUS_AUTHORITY); + + private final com.juick.User user; + private final Collection<? extends GrantedAuthority> authorities; + + public JuickUser(com.juick.User user) { + this(user, USER_AUTHORITY); + } + + public JuickUser(com.juick.User user, Collection<? extends GrantedAuthority> authorities) { + this.user = user; + this.authorities = authorities; + } + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return "{noop}" + user.getCredentials(); + } + + @Override + public String getUsername() { + return user.getName(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return !user.isBanned(); + } + + public User getUser() { + return user; + } +} diff --git a/juick-common/src/main/java/com/juick/util/DateFormatter.java b/juick-common/src/main/java/com/juick/util/DateFormatter.java new file mode 100644 index 00000000..f9e23a91 --- /dev/null +++ b/juick-common/src/main/java/com/juick/util/DateFormatter.java @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.util; + +import org.apache.commons.lang3.StringUtils; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * Created by aalexeev on 12/7/16. + */ +public class DateFormatter { + private final DateTimeFormatter formatter; + + public DateFormatter(String pattern) { + formatter = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH); + } + + public String format(final Instant ts) { + if (ts == null) + return null; + + LocalDateTime ldt = LocalDateTime.ofInstant(ts, ZoneOffset.UTC); + + return ldt.format(formatter); + } + + + public Instant parse(final String v) { + if (StringUtils.isBlank(v)) + return null; + + LocalDateTime ldt = LocalDateTime.parse(v, formatter); + + return ldt.toInstant(ZoneOffset.UTC); + } +} diff --git a/juick-common/src/main/java/com/juick/util/DateFormattersHolder.java b/juick-common/src/main/java/com/juick/util/DateFormattersHolder.java new file mode 100644 index 00000000..09fd17d7 --- /dev/null +++ b/juick-common/src/main/java/com/juick/util/DateFormattersHolder.java @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.util; + +/** + * Created by aalexeev on 12/7/16. + */ +public class DateFormattersHolder { + + private DateFormattersHolder() { + throw new IllegalStateException(); + } + + private static volatile DateFormatter messageFormatter; + + public static DateFormatter getMessageFormatterInstance() { + DateFormatter localInstance = messageFormatter; + + if (localInstance == null) { + synchronized (DateFormatter.class) { + localInstance = messageFormatter; + + if (localInstance == null) + messageFormatter = localInstance = new DateFormatter("yyyy-MM-dd HH:mm:ss"); + } + } + return localInstance; + } + + private static volatile DateFormatter rssFormatter; + + public static DateFormatter getRssFormatterInstance() { + DateFormatter localInstance = rssFormatter; + + if (localInstance == null) { + synchronized (DateFormatter.class) { + localInstance = rssFormatter; + + if (localInstance == null) + rssFormatter = localInstance = new DateFormatter("EEE, d MMM yyyy HH:mm:ss"); + } + } + return localInstance; + } +} diff --git a/juick-common/src/main/java/com/juick/util/MessageUtils.java b/juick-common/src/main/java/com/juick/util/MessageUtils.java new file mode 100644 index 00000000..87a10351 --- /dev/null +++ b/juick-common/src/main/java/com/juick/util/MessageUtils.java @@ -0,0 +1,264 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.util; + +import com.juick.Message; +import com.juick.Tag; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.lang3.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Created by aalexeev on 11/13/16. + */ +public class MessageUtils { + private MessageUtils() { + throw new IllegalStateException(); + } + + public static String formatQuote(final String quote) { + String result = quote; + + if (quote != null) { + if (quote.length() > 50) { + result = ">" + quote.substring(0, 47).replace('\n', ' ') + "...\n"; + } else if (!quote.isEmpty()) { + result = ">" + quote.replace('\n', ' ') + "\n"; + } + } + + return result; + } + + private static Pattern regexLinks2 = Pattern.compile("((?<=\\s)|(?<=\\A))([\\[\\{]|<)((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\\"\\)\\!]+)/?(?:[^\\]\\}](?<!>))*)([\\]\\}]|>)"); + + public static String formatMessageCode(String msg) { + msg = msg.replaceAll("&", "&"); + msg = msg.replaceAll("<", "<"); + msg = msg.replaceAll(">", ">"); + + // http://juick.com/last?page=2 + // <a href="http://juick.com/last?page=2" rel="nofollow">http://juick.com/last?page=2</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\s\\n\\\"]*)", "$1<a href=\"$2\" rel=\"nofollow\">$2</a>"); + + // (http://juick.com/last?page=2) + // (<a href="http://juick.com/last?page=2" rel="nofollow">http://juick.com/last?page=2</a>) + Matcher m = regexLinks2.matcher(msg); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String url = m.group(3).replace(" ", "%20").replaceAll("\\s+", StringUtils.EMPTY); + m.appendReplacement(sb, "$1$2<a href=\"" + url + "\" rel=\"nofollow\">" + url + "</a>$5"); + } + m.appendTail(sb); + msg = sb.toString(); + + return "<pre>" + msg + "</pre>"; + } + + public static String formatMessage(String msg) { + msg = msg.replaceAll("&", "&"); + msg = msg.replaceAll("<", "<"); + msg = msg.replaceAll(">", ">"); + + // -- + // — + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\-\\-?((?=\\s)|(?=\\Z))", "$1—$2"); + + // http://juick.com/last?page=2 + // <a href="http://juick.com/last?page=2" rel="nofollow">juick.com</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\s\\n\\\"]*)", "$1<a href=\"$2\" rel=\"nofollow\">$3</a>"); + + // [link text][http://juick.com/last?page=2] + // <a href="http://juick.com/last?page=2" rel="nofollow">link text</a> + msg = msg.replaceAll("\\[([^\\]]+)\\]\\[((?:ht|f)tps?://[^\\]]+)\\]", "<a href=\"$2\" rel=\"nofollow\">$1</a>"); + msg = msg.replaceAll("\\[([^\\]]+)\\]\\(((?:ht|f)tps?://[^\\)]+)\\)", "<a href=\"$2\" rel=\"nofollow\">$1</a>"); + + // #12345 + // <a href="http://juick.com/12345">#12345</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)((?=\\s)|(?=\\Z)|(?=\\))|(?=\\.)|(?=\\,))", "$1<a href=\"http://juick.com/$2\">#$2</a>$3"); + + // #12345/65 + // <a href="http://juick.com/12345#65">#12345/65</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"http://juick.com/$2#$3\">#$2/$3</a>$4"); + + // *bold* + // <b>bold</b> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))\\*([^\\*\\n<>]+)\\*((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<b>$2</b>$3"); + + // /italic/ + // <i>italic</i> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))/([^\\/\\n<>]+)/((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<i>$2</i>$3"); + + // _underline_ + // <span class="u">underline</span> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))_([^\\_\\n<>]+)_((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<span class=\"u\">$2</span>$3"); + + // /12 + // <a href="#12">/12</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"#$2\">/$2</a>$3"); + + // @username@jabber.org + // <a href="http://juick.com/username@jabber.org/">@username@jabber.org</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-\\.]+@[\\w\\-\\.]+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"http://juick.com/$2/\">@$2</a>$3"); + + // @username + // <a href="http://juick.com/username/">@username</a> + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-]{2,16})((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"http://juick.com/$2/\">@$2</a>$3"); + + // (http://juick.com/last?page=2) + // (<a href="http://juick.com/last?page=2" rel="nofollow">juick.com</a>) + Matcher m = regexLinks2.matcher(msg); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String url = m.group(3).replace(" ", "%20").replaceAll("\\s+", StringUtils.EMPTY); + m.appendReplacement(sb, "$1$2<a href=\"" + url + "\" rel=\"nofollow\">$4</a>$5"); + } + m.appendTail(sb); + msg = sb.toString(); + + // > citate + msg = msg.replaceAll("(?:(?<=\\n)|(?<=\\A))> *(.*)?(\\n|(?=\\Z))", "<q>$1</q>"); + msg = msg.replaceAll("</q><q>", "\n"); + + msg = msg.replaceAll("\n", "<br/>\n"); + return msg; + } + + public static String formatHtml(Message jmsg) { + StringBuilder sb = new StringBuilder(); + boolean isReply = jmsg.getRid() > 0; + String title = isReply ? "<b>Reply by @" : "<b>@"; + String subtitle = isReply ? "<blockquote>" + jmsg.getReplyQuote() + "</blockquote>" : "<i>" + getTagsString(jmsg) + "</i>"; + boolean isCode = jmsg.getTags().stream().anyMatch(t -> t.getName().equals("code")); + + sb.append(title).append(jmsg.getUser().getName()).append(":</b></br/>") + .append(subtitle).append("<br/>") + .append(isCode ? formatMessageCode(StringUtils.defaultString(jmsg.getText())) + : formatMessage(StringUtils.defaultString(jmsg.getText()))).append("<br />"); + if (StringUtils.isNotEmpty(jmsg.getAttachmentType())) { + // FIXME: attachment does not serialized to xml + if (jmsg.getAttachment() == null) { + if (jmsg.getRid() > 0) { + sb.append(String.format("<img src=\"http://i.juick.com/photos-1024/%d-%d.%s\" />", jmsg.getMid(), + jmsg.getRid(), jmsg.getAttachmentType())); + } else { + sb.append(String.format("<img src=\"http://i.juick.com/photos-1024/%d.%s\" />", jmsg.getMid(), + jmsg.getAttachmentType())); + } + } else { + sb.append("<img src=\"").append(jmsg.getAttachment().getMedium().getUrl()).append("\" />"); + } + } + return sb.toString(); + } + + public static String getMessageHashTags(final Message jmsg) { + StringBuilder hashtags = new StringBuilder(); + for (Tag tag : jmsg.getTags()) { + hashtags.append("#").append(tag).append(" "); + } + return hashtags.toString(); + } + public static String getMarkdownTags(final Message jmsg) { + return jmsg.getTags().stream().map(t -> String.format("[%s](http://juick.com/tag/%s)", t.getName(), percentEncode(t.getName()))) + .collect(Collectors.joining(", ")); + } + + public static String getMarkdownUser(final Message jmsg) { + return String.format("[%s](https://juick.com/%s/)", jmsg.getUser().getName(), jmsg.getUser().getName()); + } + + // TODO: check if it is really needed + public static String percentEncode(final String s) { + String ret = StringUtils.EMPTY; + try { + ret = URLEncoder.encode(s, CharEncoding.UTF_8).replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); + } catch (UnsupportedEncodingException e) { + } + return ret; + } + public static String escapeMarkdown(final String s) { + return s.replace("_", "\\_").replace("*", "\\*") + .replace("`", "\\`"); + } + public static String attachmentUrl(final Message jmsg) { + if (StringUtils.isEmpty(jmsg.getAttachmentType())) { + return StringUtils.EMPTY; + } + // FIXME: attachment does not serialized to xml + if (jmsg.getAttachment() == null) { + if (jmsg.getRid() > 0) { + return String.format("http://i.juick.com/photos-1024/%d-%d.%s", jmsg.getMid(), + jmsg.getRid(), jmsg.getAttachmentType()); + } else { + return String.format("http://i.juick.com/photos-1024/%d.%s", jmsg.getMid(), + jmsg.getAttachmentType()); + } + } else { + return jmsg.getAttachment().getMedium().getUrl(); + } + } + public static boolean replyStartsWithQuote(Message msg) { + return msg.getRid() > 0 && StringUtils.defaultString(msg.getText()).startsWith(">"); + } + public static List<Tag> parseTags(String strTags) { + List<Tag> tags = new ArrayList<>(); + if (StringUtils.isNotEmpty(strTags)) { + Set<Tag> tagSet = new TreeSet<>(tags); + for (String str : strTags.split(" ")) { + Tag tag = new Tag(str); + if (!tagSet.contains(tag)) { + tags.add(tag); + tagSet.add(tag); + } + } + } + return tags; + } + public static String getTagsString(Message msg) { + StringBuilder builder = new StringBuilder(); + List<Tag> tags = msg.getTags(); + if (!tags.isEmpty()) { + for (Tag Tag : tags) + builder.append(" *").append(Tag.getName()); + + if (msg.FriendsOnly) + builder.append(" *friends"); + + if (msg.getPrivacy() == -2) + builder.append(" *private"); + else if (msg.getPrivacy() == -1) + builder.append(" *friends"); + else if (msg.getPrivacy() == 2) + builder.append(" *public"); + + if (msg.ReadOnly) + builder.append(" *readonly"); + } + return builder.toString(); + } +} diff --git a/juick-common/src/main/java/com/juick/util/PrettyTimeFormatter.java b/juick-common/src/main/java/com/juick/util/PrettyTimeFormatter.java new file mode 100644 index 00000000..383f4d9a --- /dev/null +++ b/juick-common/src/main/java/com/juick/util/PrettyTimeFormatter.java @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +package com.juick.util; + +import org.ocpsoft.prettytime.PrettyTime; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Created by vitalyster on 04.05.2017. + */ +public class PrettyTimeFormatter { + private static final int MAX_CACHE_SIZE = 20; + + // Cache PrettyTime per locale. LRU cache to prevent memory leak. + private static final Map<Locale, PrettyTime> PRETTY_TIME_LOCALE_MAP = + new LinkedHashMap<Locale, PrettyTime>(MAX_CACHE_SIZE + 1, 1.1F, true) + { + @Override + protected boolean removeEldestEntry(Map.Entry<Locale, PrettyTime> eldest) + { + return size() > MAX_CACHE_SIZE; + } + }; + + public String format(final Locale locale, final Date value) + { + PrettyTime prettyTime; + + synchronized (PRETTY_TIME_LOCALE_MAP) { + prettyTime = PRETTY_TIME_LOCALE_MAP.computeIfAbsent(locale, PrettyTime::new); + } + return prettyTime.format(value); + } +} |