diff options
Diffstat (limited to 'src')
114 files changed, 1274 insertions, 1569 deletions
diff --git a/src/main/assets/icon.js b/src/main/assets/icon.js index 5cd787a5..b4b5aedc 100644 --- a/src/main/assets/icon.js +++ b/src/main/assets/icon.js @@ -1,10 +1,10 @@ -import evilIcons from 'evil-icons/assets/sprite.svg' +const spritesUrl = new URL('evil-icons/assets/sprite.svg', import.meta.url) function icon(name, { size = '', className = '' }) { const classes = `icon icon--${name} icon--${size} ${className}`.trim() var icon = '<svg class="icon__cnt">' + - `<use xlink:href='${evilIcons}#${name}-icon' />` + + `<use xlink:href='${spritesUrl}#${name}-icon' />` + '</svg>' var html = '<div class="' + classes + '">' + diff --git a/src/main/assets/scripts.js b/src/main/assets/scripts.js index 3c83bba4..12c4d9b2 100644 --- a/src/main/assets/scripts.js +++ b/src/main/assets/scripts.js @@ -251,6 +251,10 @@ function closeDialogListener(ev) { } } +/** + * + * @param {Response} response + */ function handleErrors(response) { if (!response.ok) { throw Error(response.statusText) @@ -310,15 +314,14 @@ function showCommentForm(mid, rid) { method: 'POST', body: formData, credentials: 'include' - }).then(handleErrors) - .then(response => response.json()) + }).then(response => response.json()) .then(result => { if (result.newMessage) { window.location.hash = `#${result.newMessage.rid}` + window.location.reload() } else { alert(result.text) } - window.location.reload() }).catch(error => { alert(error.message) }) @@ -535,7 +538,7 @@ function fetchUserUri(dataUri) { const registerServiceWorker = () => { const publicKey = 'BPU0LniKKR0QiaUvILPd9AystmSOU8rWDZobxKm7IJN5HYxOSQdktRdc74TZvyRS9_kyUz7LDN6gUAmAVOmObAU' - navigator.serviceWorker.register('/sw.js', { scope: '/' }) + navigator.serviceWorker.register(new URL('./sw.js', import.meta.url), { scope: '/' }) navigator.serviceWorker.ready.then(reg => { return reg.pushManager.subscribe({ userVisibleOnly: true, @@ -663,8 +666,7 @@ ready(() => { method: 'POST', body: formData, credentials: 'include' - }).then(handleErrors) - .then(response => response.json()) + }).then(response => response.json()) .then(result => { if (result.newMessage) { window.location.href = new URL(`/m/${result.newMessage.mid}`, window.location.href).href diff --git a/src/main/assets/style.css b/src/main/assets/style.css index f5e4ef00..99cefffa 100644 --- a/src/main/assets/style.css +++ b/src/main/assets/style.css @@ -363,8 +363,7 @@ article { background: var(--text-background-color); border: 1px solid var(--border-color); line-height: 140%; - margin-bottom: 10px; - overflow: auto; + margin-bottom: 10px; } article .h, article #postmsg, @@ -375,6 +374,9 @@ article #postmsg, .msg-cont .msg-header { padding: 20px; } +.page-content { + overflow: auto; +} article time { color: #88958d; @@ -443,6 +445,10 @@ article .tags > a::before, padding: 0 10px; } +.dimmed { + color: var(--dimmed-link-color); +} + .l .msg-button { padding: 12px; } diff --git a/src/main/resources/static/sw.js b/src/main/assets/sw.js index a64de7cc..a64de7cc 100644 --- a/src/main/resources/static/sw.js +++ b/src/main/assets/sw.js diff --git a/src/main/java/com/cliqset/xrd/Alias.java b/src/main/java/com/cliqset/xrd/Alias.java deleted file mode 100644 index 3a108f46..00000000 --- a/src/main/java/com/cliqset/xrd/Alias.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import java.net.URI; -import java.util.HashMap; -import java.util.Map; - -import javax.xml.namespace.QName; - -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlAnyAttribute; -import jakarta.xml.bind.annotation.XmlType; -import jakarta.xml.bind.annotation.XmlValue; - -@XmlType(name="Alias", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class Alias { - - @XmlAnyAttribute() - private Map<QName, Object> unknownAttributes; - - @XmlValue - private URI value; - - public void setValue(URI value) { - this.value = value; - } - - public URI getValue() { - return value; - } - - public void setUnknownAttributes(Map<QName, Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName, Object> getUnknownAttributes() { - if (null == this.unknownAttributes) { - this.unknownAttributes = new HashMap<QName, Object>(); - } - return unknownAttributes; - } - - public boolean hasUnknownAttributes() { - return !(null == this.unknownAttributes || this.unknownAttributes.size() < 1); - } -} diff --git a/src/main/java/com/cliqset/xrd/Expires.java b/src/main/java/com/cliqset/xrd/Expires.java deleted file mode 100644 index bb6704f8..00000000 --- a/src/main/java/com/cliqset/xrd/Expires.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import javax.xml.namespace.QName; -import jakarta.xml.bind.annotation.*; - -@XmlType(name="Expires", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class Expires { - - @XmlAnyAttribute - private Map<QName, Object> unknownAttributes; - - @XmlValue - private Date value; - - public void setValue(Date value) { - this.value = value; - } - - public Date getValue() { - return value; - } - - public void setUnknownAttributes(Map<QName, Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName, Object> getUnknownAttributes() { - if (null == this.unknownAttributes) { - this.unknownAttributes = new HashMap<QName, Object>(); - } - return unknownAttributes; - } - - public boolean hasUnknownAttributes() { - return !(null == this.unknownAttributes || this.unknownAttributes.size() < 1); - } -} diff --git a/src/main/java/com/cliqset/xrd/Link.java b/src/main/java/com/cliqset/xrd/Link.java deleted file mode 100644 index 020defe6..00000000 --- a/src/main/java/com/cliqset/xrd/Link.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import java.net.URI; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.xml.namespace.QName; -import jakarta.xml.bind.annotation.XmlAnyElement; -import jakarta.xml.bind.annotation.XmlAnyAttribute; -import jakarta.xml.bind.annotation.XmlType; -import jakarta.xml.bind.annotation.XmlAttribute; -import jakarta.xml.bind.annotation.XmlElement; -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; - -import org.w3c.dom.Element; - -@XmlType(name="Link", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class Link { - - @XmlAttribute(name="rel") - private URI rel; - - @XmlAttribute(name="type") - private String type; - - @XmlAttribute(name="href") - private URI href; - - @XmlAttribute(name="template") - private String template; - - @XmlElement(name="Title") - private List<Title> titles; - - @XmlElement(name="Property") - private List<Property> properties; - - @XmlAnyElement - private List<Element> unknownElements; - - @XmlAnyAttribute - private Map<QName,Object> unknownAttributes; - - private URI processedTemplate; - - public void setRel(URI rel) { - this.rel = rel; - } - - public URI getRel() { - return rel; - } - - public void setType(String type) { - this.type = type; - } - - public String getType() { - return type; - } - - public void setHref(URI href) { - this.href = href; - } - - public URI getHref() { - return href; - } - - public void setTemplate(String template) { - this.template = template; - } - - public String getTemplate() { - return template; - } - - public void setTitles(List<Title> titles) { - this.titles = titles; - } - - public List<Title> getTitles() { - return titles; - } - - public void setProperties(List<Property> properties) { - this.properties = properties; - } - - public List<Property> getProperties() { - return properties; - } - - public void setUnknownElements(List<Element> unknownElements) { - this.unknownElements = unknownElements; - } - - public List<Element> getUnknownElements() { - return unknownElements; - } - - public void setUnknownAttributes(Map<QName,Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName,Object> getUnknownAttributes() { - if (null == this.unknownAttributes) { - this.unknownAttributes = new HashMap<QName, Object>(); - } - return unknownAttributes; - } - - public boolean hasTemplate() { - return null != this.template; - } - - public boolean hasHref() { - return null != this.href; - } - - public boolean hasUnknownAttributes() { - return !(null == this.unknownAttributes || this.unknownAttributes.size() < 1); - } - - public void setProcessedTemplate(URI processedTemplate) { - this.processedTemplate = processedTemplate; - } - - public URI getProcessedTemplate() { - return processedTemplate; - } -} diff --git a/src/main/java/com/cliqset/xrd/Property.java b/src/main/java/com/cliqset/xrd/Property.java deleted file mode 100644 index f7777c94..00000000 --- a/src/main/java/com/cliqset/xrd/Property.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import java.net.URI; - -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlAnyAttribute; -import jakarta.xml.bind.annotation.XmlAttribute; -import jakarta.xml.bind.annotation.XmlType; -import jakarta.xml.bind.annotation.XmlValue; -import javax.xml.namespace.QName; - -import java.util.HashMap; -import java.util.Map; - -@XmlType(name="Property", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class Property { - - @XmlAttribute(name="type", required=true) - private URI type; - - @XmlAnyAttribute() - private Map<QName, Object> unknownAttributes; - - @XmlValue() - private URI value; - - public void setType(URI type) { - this.type = type; - } - - public URI getType() { - return type; - } - - public void setValue(URI value) { - this.value = value; - } - - public URI getValue() { - return value; - } - - public void setUnknownAttributes(Map<QName, Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName, Object> getUnknownAttributes() { - if (null == this.unknownAttributes) { - this.unknownAttributes = new HashMap<QName, Object>(); - } - return unknownAttributes; - } - - public boolean hasUnknownAttributes() { - return !(null == this.unknownAttributes || this.unknownAttributes.size() < 1); - } -} diff --git a/src/main/java/com/cliqset/xrd/Signature.java b/src/main/java/com/cliqset/xrd/Signature.java deleted file mode 100644 index f52f9218..00000000 --- a/src/main/java/com/cliqset/xrd/Signature.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -public class Signature { - -} diff --git a/src/main/java/com/cliqset/xrd/Subject.java b/src/main/java/com/cliqset/xrd/Subject.java deleted file mode 100644 index d2f6b333..00000000 --- a/src/main/java/com/cliqset/xrd/Subject.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import jakarta.xml.bind.annotation.XmlAccessType; -import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlAnyAttribute; -import jakarta.xml.bind.annotation.XmlType; -import jakarta.xml.bind.annotation.XmlValue; -import javax.xml.namespace.QName; - -import java.net.URI; -import java.util.HashMap; -import java.util.Map; - -@XmlType(name="Subject", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class Subject { - - @XmlAnyAttribute() - private Map<QName, Object> unknownAttributes; - - @XmlValue - private URI value; - - public void setValue(URI value) { - this.value = value; - } - - public URI getValue() { - return value; - } - - public void setUnknownAttributes(Map<QName, Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName, Object> getUnknownAttributes() { - if (null == this.unknownAttributes) { - this.unknownAttributes = new HashMap<QName, Object>(); - } - return unknownAttributes; - } - - public boolean hasUnknownAttributes() { - return !(null == this.unknownAttributes || this.unknownAttributes.size() < 1); - } -} diff --git a/src/main/java/com/cliqset/xrd/Title.java b/src/main/java/com/cliqset/xrd/Title.java deleted file mode 100644 index 4bb43c55..00000000 --- a/src/main/java/com/cliqset/xrd/Title.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.xml.bind.annotation.*; -import javax.xml.namespace.QName; - -@XmlType(name="Title", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class Title { - - @XmlAttribute(name="lang", namespace=XRDConstants.XML_NAMESPACE) - private String lang; - - @XmlAnyAttribute() - private Map<QName, Object> unknownAttributes; - - @XmlValue - private String value; - - public void setValue(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public void setLang(String lang) { - this.lang = lang; - } - - public String getLang() { - return lang; - } - - public void setUnknownAttributes(Map<QName, Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName, Object> getUnknownAttributes() { - if (null == this.unknownAttributes) { - this.unknownAttributes = new HashMap<QName, Object>(); - } - return unknownAttributes; - } - - public boolean hasUnknownAttributes() { - return !(null == this.unknownAttributes || this.unknownAttributes.size() < 1); - } -} diff --git a/src/main/java/com/cliqset/xrd/XRD.java b/src/main/java/com/cliqset/xrd/XRD.java deleted file mode 100644 index 8fc6f7de..00000000 --- a/src/main/java/com/cliqset/xrd/XRD.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -import javax.xml.namespace.QName; -import jakarta.xml.bind.annotation.*; -import jakarta.xml.bind.JAXBContext; -import jakarta.xml.bind.JAXBException; - -import org.w3c.dom.Element; - -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -@XmlRootElement(name="XRD", namespace=XRDConstants.XRD_NAMESPACE) -@XmlAccessorType(XmlAccessType.FIELD) -public class XRD { - - @XmlAttribute(name="id", namespace=XRDConstants.XML_NAMESPACE) - private String id; - - @XmlAnyAttribute - private Map<QName, Object> unknownAttributes; - - @XmlElement(name="Expires", namespace=XRDConstants.XRD_NAMESPACE) - private Expires expires; - - @XmlElement(name="Subject", namespace=XRDConstants.XRD_NAMESPACE) - private Subject subject; - - @XmlElement(name="Alias", namespace=XRDConstants.XRD_NAMESPACE) - private List<Alias> aliases; - - @XmlElement(name="Property", namespace=XRDConstants.XRD_NAMESPACE) - private List<Property> properties; - - @XmlElement(name="Link", namespace=XRDConstants.XRD_NAMESPACE) - private List<Link> links; - - @XmlElement(name="Signature", namespace=XRDConstants.XML_SIG_NAMESPACE) - private List<Signature> signatures; - - @XmlAnyElement - private List<Element> unknownElements; - - public void setExpires(Expires expires) { - this.expires = expires; - } - - public Expires getExpires() { - return expires; - } - - public void setSubject(Subject subject) { - this.subject = subject; - } - - public Subject getSubject() { - return subject; - } - - public void setAliases(List<Alias> aliases) { - this.aliases = aliases; - } - - public List<Alias> getAliases() { - return aliases; - } - - public void setProperties(List<Property> properties) { - this.properties = properties; - } - - public List<Property> getProperties() { - return properties; - } - - public void setLinks(List<Link> links) { - this.links = links; - } - - public List<Link> getLinks() { - return links; - } - - public void setSignatures(List<Signature> signatures) { - this.signatures = signatures; - } - - public List<Signature> getSignatures() { - return signatures; - } - - public void setId(String id) { - this.id = id; - } - - public String getId() { - return id; - } - - public void setUnknownAttributes(Map<QName, Object> unknownAttributes) { - this.unknownAttributes = unknownAttributes; - } - - public Map<QName, Object> getUnknownAttributes() { - return unknownAttributes; - } - - public void setUnknownElements(List<Element> unknownElements) { - this.unknownElements = unknownElements; - } - - public List<Element> getUnknownElements() { - return unknownElements; - } - - public boolean hasId() { - return null != this.id; - } - - public boolean hasExpires() { - return null != this.expires; - } - - public boolean hasSubject() { - return null != this.subject; - } - - public boolean hasAliases() { - return null != this.aliases; - } - - public boolean hasProperties() { - return null != this.properties; - } - - public boolean hasLinks() { - return null != this.links; - } - - public static XRD fromStream(InputStream stream) throws XRDException { - JAXBContext context; - try { - context = JAXBContext.newInstance(XRD.class); - return (XRD)context.createUnmarshaller().unmarshal(stream); - } catch (JAXBException e) { - throw new XRDException("Unable to deserialize stream into XRD", e); - } - } -} diff --git a/src/main/java/com/cliqset/xrd/XRDConstants.java b/src/main/java/com/cliqset/xrd/XRDConstants.java deleted file mode 100644 index 39e3c584..00000000 --- a/src/main/java/com/cliqset/xrd/XRDConstants.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -public class XRDConstants { - - public static final String XRD_NAMESPACE = "http://docs.oasis-open.org/ns/xri/xrd-1.0"; - public static final String XML_SIG_NAMESPACE = ""; - public static final String XML_NAMESPACE = ""; - - public static final String XRD_MEDIA_TYPE = "application/xrd+xml"; -} diff --git a/src/main/java/com/cliqset/xrd/XRDException.java b/src/main/java/com/cliqset/xrd/XRDException.java deleted file mode 100644 index da1e6849..00000000 --- a/src/main/java/com/cliqset/xrd/XRDException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2010 Cliqset Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.cliqset.xrd; - -@SuppressWarnings("serial") -public class XRDException extends Exception { - - public XRDException() {} - - public XRDException(String message) { - super(message); - } - - public XRDException(Throwable cause) { - super(cause); - } - - public XRDException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/com/github/scribejava/apis/AppleSignInApi.java b/src/main/java/com/github/scribejava/apis/AppleSignInApi.java index 5d11a2a6..9e487ca0 100644 --- a/src/main/java/com/github/scribejava/apis/AppleSignInApi.java +++ b/src/main/java/com/github/scribejava/apis/AppleSignInApi.java @@ -114,7 +114,8 @@ public class AppleSignInApi extends DefaultApi20 { } String email = (String) claimsSet.get("email"); - boolean verified = claimsSet.get("email_verified").equals("true"); + boolean verified = claimsSet.get("email_verified").equals(true); + logger.debug("Email {} verified: {}", email, verified); return verified ? Optional.of(email) : Optional.empty(); } } diff --git a/src/main/java/com/juick/ActivityPubManager.java b/src/main/java/com/juick/ActivityPubManager.java index 6837a416..a581ef0d 100644 --- a/src/main/java/com/juick/ActivityPubManager.java +++ b/src/main/java/com/juick/ActivityPubManager.java @@ -50,23 +50,19 @@ import org.springframework.http.MediaType; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.tomitribe.auth.signatures.Algorithm; -import org.tomitribe.auth.signatures.MissingRequiredHeaderException; import org.tomitribe.auth.signatures.Signature; import org.tomitribe.auth.signatures.Verifier; -import javax.annotation.Nonnull; -import javax.inject.Inject; +import jakarta.annotation.Nonnull; +import jakarta.inject.Inject; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.security.Key; import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; import java.time.Instant; import java.util.*; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; public class ActivityPubManager implements ActivityListener, NotificationListener { diff --git a/src/main/java/com/juick/CommandsManager.java b/src/main/java/com/juick/CommandsManager.java index 7488e331..a0f6e5e5 100644 --- a/src/main/java/com/juick/CommandsManager.java +++ b/src/main/java/com/juick/CommandsManager.java @@ -30,8 +30,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.inject.Inject; +import jakarta.annotation.Nonnull; +import jakarta.inject.Inject; import com.juick.model.CommandResult; import com.juick.model.Message; @@ -545,7 +545,7 @@ public class CommandsManager { } } Pair<String, Set<Tag>> messageTags = tagService.fromString(txt); - if (user.getUid() == msg.get().getUser().getUid() && rid == 0 && messageTags.getRight().size() > 0) { + if (user.getUid() == msg.get().getUser().getUid() && rid == 0 && !messageTags.getRight().isEmpty()) { var updatedTags = tagService.updateTags(mid, messageTags.getRight()); if (!CollectionUtils.isEqualCollection(updatedTags, msg.get().getTags())) { messagesService.setReadOnly(msg.get().getMid(), TagUtils.hasTag(updatedTags, "readonly")); @@ -554,6 +554,9 @@ public class CommandsManager { return CommandResult.fromString("Tags are NOT updated (5 tags maximum?)"); } } else { + if (!user.isVerified()) { + return CommandResult.fromString("Please, verify your account at https://juick.com/settings"); + } if (!messagesService.canViewThread(mid, user.getUid())) { return CommandResult.fromString("Message unavailable"); } diff --git a/src/main/java/com/juick/EmailManager.java b/src/main/java/com/juick/EmailManager.java index 6958d970..37c8e3c5 100644 --- a/src/main/java/com/juick/EmailManager.java +++ b/src/main/java/com/juick/EmailManager.java @@ -33,7 +33,6 @@ import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMultipart; import com.juick.util.HttpBadRequestException; -import com.juick.www.WebApp; import com.juick.service.EmailService; import com.juick.service.MessagesService; import com.juick.service.UserService; @@ -47,8 +46,8 @@ import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; -import javax.annotation.Nonnull; -import javax.inject.Inject; +import jakarta.annotation.Nonnull; +import jakarta.inject.Inject; import java.util.*; import static com.juick.util.formatters.PlainTextFormatter.formatPost; @@ -67,8 +66,6 @@ public class EmailManager implements NotificationListener { private UserService userService; @Inject private ObjectMapper jsonMapper; - @Inject - private WebApp webApp; @Value("${web_domain:localhost}") private String webDomain; @Value("${service_email:}") diff --git a/src/main/java/com/juick/PremiumManager.java b/src/main/java/com/juick/PremiumManager.java new file mode 100644 index 00000000..f1fd553e --- /dev/null +++ b/src/main/java/com/juick/PremiumManager.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008-2024, 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.juick.service.EmailService; +import com.juick.service.PatreonService; +import com.juick.service.VKService; +import com.juick.service.security.entities.JuickUser; +import jakarta.inject.Inject; +import org.springframework.context.ApplicationListener; +import org.springframework.security.authentication.event.AuthenticationSuccessEvent; + +public class PremiumManager implements ApplicationListener<AuthenticationSuccessEvent> { + @Inject + VKService vkService; + @Inject + PatreonService patreonService; + @Inject + EmailService emailService; + @Override + public void onApplicationEvent(AuthenticationSuccessEvent event) { + var juickUser = (JuickUser)event.getAuthentication().getPrincipal(); + var isDon = vkService.updatePremiumStatus(juickUser.getUser().getUid()); + if (!isDon) { + patreonService.updateStatus(emailService.getEmails(juickUser.getUser().getUid(), false)); + } + } +} diff --git a/src/main/java/com/juick/ServerManager.java b/src/main/java/com/juick/ServerManager.java index 60b6010f..bc2e7a06 100644 --- a/src/main/java/com/juick/ServerManager.java +++ b/src/main/java/com/juick/ServerManager.java @@ -31,8 +31,8 @@ import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import javax.annotation.Nonnull; -import javax.inject.Inject; +import jakarta.annotation.Nonnull; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; diff --git a/src/main/java/com/juick/TelegramBotManager.java b/src/main/java/com/juick/TelegramBotManager.java index cf38ab5c..99b8d873 100644 --- a/src/main/java/com/juick/TelegramBotManager.java +++ b/src/main/java/com/juick/TelegramBotManager.java @@ -55,9 +55,9 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import jakarta.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.net.URI; import java.net.URL; diff --git a/src/main/java/com/juick/TopManager.java b/src/main/java/com/juick/TopManager.java index 13e91b34..a6cd3c0d 100644 --- a/src/main/java/com/juick/TopManager.java +++ b/src/main/java/com/juick/TopManager.java @@ -20,7 +20,7 @@ package com.juick; import java.net.URI; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/src/main/java/com/juick/TwitterManager.java b/src/main/java/com/juick/TwitterManager.java index 7a01debd..10434e3a 100644 --- a/src/main/java/com/juick/TwitterManager.java +++ b/src/main/java/com/juick/TwitterManager.java @@ -26,7 +26,7 @@ import com.juick.util.TagUtils; import com.juick.www.api.SystemActivity; import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; /** * @author Ugnich Anton diff --git a/src/main/java/com/juick/XMPPManager.java b/src/main/java/com/juick/XMPPManager.java index 9e24c347..ab071260 100644 --- a/src/main/java/com/juick/XMPPManager.java +++ b/src/main/java/com/juick/XMPPManager.java @@ -65,7 +65,7 @@ import rocks.xmpp.extensions.vcard.temp.model.VCard; import rocks.xmpp.extensions.version.SoftwareVersionManager; import rocks.xmpp.extensions.version.model.SoftwareVersion; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; diff --git a/src/main/java/com/juick/config/ActivityPubConfig.java b/src/main/java/com/juick/config/ActivityPubConfig.java index 81327a02..264d59e1 100644 --- a/src/main/java/com/juick/config/ActivityPubConfig.java +++ b/src/main/java/com/juick/config/ActivityPubConfig.java @@ -27,7 +27,7 @@ import com.juick.www.api.activity.helpers.ProfileUriBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import javax.inject.Inject; +import jakarta.inject.Inject; @Configuration public class ActivityPubConfig { diff --git a/src/main/java/com/juick/config/AppConfig.java b/src/main/java/com/juick/config/AppConfig.java index cb99c079..a48c7ee0 100644 --- a/src/main/java/com/juick/config/AppConfig.java +++ b/src/main/java/com/juick/config/AppConfig.java @@ -20,15 +20,8 @@ package com.juick.config; import com.juick.*; import com.juick.model.User; import com.juick.service.*; -import com.mitchellbosecke.pebble.extension.FormatterExtension; import com.overzealous.remark.Options; import com.overzealous.remark.Remark; -import io.pebbletemplates.pebble.PebbleEngine; -import io.pebbletemplates.pebble.loader.ClasspathLoader; -import io.pebbletemplates.pebble.loader.Loader; -import io.pebbletemplates.spring.extension.SpringExtension; -import io.pebbletemplates.spring.servlet.PebbleViewResolver; - import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.node.Link; import org.commonmark.parser.Parser; @@ -40,13 +33,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler; import org.springframework.security.web.firewall.RequestRejectedHandler; -import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter; import org.springframework.web.servlet.resource.ResourceUrlProvider; -import java.nio.charset.StandardCharsets; import java.util.Collections; -import javax.inject.Inject; +import jakarta.inject.Inject; /** * Created by aalexeev on 11/22/16. @@ -161,4 +152,5 @@ public class AppConfig { WatchdogService watchdogService() { return new SystemdWatchdogService(); } + } diff --git a/src/main/java/com/juick/config/HttpClientConfig.java b/src/main/java/com/juick/config/HttpClientConfig.java index d838575d..fd94ae4f 100644 --- a/src/main/java/com/juick/config/HttpClientConfig.java +++ b/src/main/java/com/juick/config/HttpClientConfig.java @@ -18,7 +18,6 @@ package com.juick.config; import com.juick.util.ActivityPubRequestInterceptor; -import okhttp3.Dispatcher; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import org.slf4j.Logger; @@ -27,7 +26,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import javax.inject.Inject; +import jakarta.inject.Inject; @Configuration public class HttpClientConfig { diff --git a/src/main/java/com/juick/config/SecurityConfig.java b/src/main/java/com/juick/config/SecurityConfig.java index a7007648..a93a4a5c 100644 --- a/src/main/java/com/juick/config/SecurityConfig.java +++ b/src/main/java/com/juick/config/SecurityConfig.java @@ -62,7 +62,7 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.Arrays; @@ -202,13 +202,10 @@ public class SecurityConfig { "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", "/api/_patreon", "/api/_vk", - "/api/skypebotendpoint", "/api/_fblogin", - "/api/_vklogin", "/api/_tglogin", - "/api/_google", "/api/_applelogin", "/api/signup", + "/api/skypebotendpoint", "/api/signup", "/api/inbox", "/api/events", "/api/u/", "/u/**", "/n/**", "/api/info/**", "/api/v1/apps", "/api/v1/instance", - "/api/v2/instance", "/api/nodeinfo/2.0", "/oauth/**") .permitAll() .anyRequest().hasAnyAuthority("SCOPE_write", "ROLE_USER")) diff --git a/src/main/java/com/juick/config/WebConfig.java b/src/main/java/com/juick/config/WebConfig.java index a5545c48..35436118 100644 --- a/src/main/java/com/juick/config/WebConfig.java +++ b/src/main/java/com/juick/config/WebConfig.java @@ -23,7 +23,7 @@ import java.nio.file.Paths; import java.util.List; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.juick.service.UserService; import com.juick.www.VisitorArgumentResolver; diff --git a/src/main/java/com/juick/config/XMPPConfig.java b/src/main/java/com/juick/config/XMPPConfig.java index 2086f237..345f9b3e 100644 --- a/src/main/java/com/juick/config/XMPPConfig.java +++ b/src/main/java/com/juick/config/XMPPConfig.java @@ -28,7 +28,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.concurrent.Executor; @Configuration diff --git a/src/main/java/com/juick/data/MessagesRepository.java b/src/main/java/com/juick/data/MessagesRepository.java new file mode 100644 index 00000000..7f42c454 --- /dev/null +++ b/src/main/java/com/juick/data/MessagesRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2008-2024, 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.data; + +import com.juick.data.entities.MessageEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MessagesRepository extends JpaRepository<MessageEntity, Integer> { +} diff --git a/src/main/java/com/juick/data/UserMapper.java b/src/main/java/com/juick/data/UserMapper.java new file mode 100644 index 00000000..ad544116 --- /dev/null +++ b/src/main/java/com/juick/data/UserMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008-2024, 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.data; + +import com.juick.data.entities.UserEntity; +import com.juick.model.User; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +import java.time.Instant; +import java.time.OffsetDateTime; + +@Mapper(componentModel = "spring") +public interface UserMapper { + @Mapping(source = "id", target = "uid") + @Mapping(source = "seen", target = "seen", qualifiedByName = "offsetDateTimeConverter") + User userEntityToUser(UserEntity entity); + + @Named("offsetDateTimeConverter") + static Instant map(OffsetDateTime offsetDateTime) { + if (offsetDateTime != null) { + return offsetDateTime.toInstant(); + } + return null; + } +} diff --git a/src/main/java/com/juick/data/UsersRepository.java b/src/main/java/com/juick/data/UsersRepository.java new file mode 100644 index 00000000..d391d52d --- /dev/null +++ b/src/main/java/com/juick/data/UsersRepository.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008-2024, 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.data; + +import com.juick.data.entities.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Stream; + +@Repository +public interface UsersRepository extends JpaRepository<UserEntity, Integer> { + UserEntity findByName(String name); + Optional<UserEntity> findByNameAndPassword(String name, String password); + Optional<UserEntity> findByEmailsEmail(String email); + Stream<UserEntity> findAllByNameIn(Collection<String> names); + Stream<UserEntity> findAllByIdIn(Collection<Integer> uids); + Optional<UserEntity> findByFacebookIds_FacebookId(Long facebookId); + Optional<UserEntity> findByVkIds_VkId(Long vkId); + Optional<UserEntity> findByLogins_Hash(String hash); + Optional<UserEntity> findByTgIds_Identifier(Long telegramId); +} diff --git a/src/main/java/com/cliqset/xrd/package-info.java b/src/main/java/com/juick/data/entities/BaseMessagePropertyEntity.java index dfc11f51..90c26dbb 100644 --- a/src/main/java/com/cliqset/xrd/package-info.java +++ b/src/main/java/com/juick/data/entities/BaseMessagePropertyEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2017, Juick + * Copyright (C) 2008-2024, 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 @@ -15,19 +15,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -@XmlSchema( - namespace=XRD_NAMESPACE, - elementFormDefault = XmlNsForm.QUALIFIED, - xmlns={ - @XmlNs(prefix= StringUtils.EMPTY, namespaceURI=XRD_NAMESPACE) - } -) -package com.cliqset.xrd; +package com.juick.data.entities; -import org.apache.commons.lang3.StringUtils; +import jakarta.persistence.*; -import jakarta.xml.bind.annotation.XmlNs; -import jakarta.xml.bind.annotation.XmlNsForm; -import jakarta.xml.bind.annotation.XmlSchema; +@MappedSuperclass +public class BaseMessagePropertyEntity { + @ManyToOne + @JoinColumn(name = "message_id") + private MessageEntity message; + public MessageEntity getMessage() { + return message; + } -import static com.cliqset.xrd.XRDConstants.XRD_NAMESPACE;
\ No newline at end of file + public void setMessage(MessageEntity message) { + this.message = message; + } +} diff --git a/src/main/java/com/juick/data/entities/BaseUserPropertyEntity.java b/src/main/java/com/juick/data/entities/BaseUserPropertyEntity.java new file mode 100644 index 00000000..0397c409 --- /dev/null +++ b/src/main/java/com/juick/data/entities/BaseUserPropertyEntity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@MappedSuperclass +public class BaseUserPropertyEntity { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private UserEntity user; + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } +} diff --git a/src/main/java/com/juick/data/entities/EmailEntity.java b/src/main/java/com/juick/data/entities/EmailEntity.java new file mode 100644 index 00000000..10c14c1e --- /dev/null +++ b/src/main/java/com/juick/data/entities/EmailEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "emails") +public class EmailEntity extends BaseUserPropertyEntity { + @Id + @Column(name = "email") + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/src/main/java/com/juick/data/entities/FacebookEntity.java b/src/main/java/com/juick/data/entities/FacebookEntity.java new file mode 100644 index 00000000..6c6b4557 --- /dev/null +++ b/src/main/java/com/juick/data/entities/FacebookEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "facebook") +public class FacebookEntity extends BaseUserPropertyEntity { + @Id + @Column(name = "fb_id") + private Long facebookId; + + public Long getFacebookId() { + return facebookId; + } + + public void setFacebookId(Long facebookId) { + this.facebookId = facebookId; + } +} diff --git a/src/main/java/com/juick/data/entities/LoginEntity.java b/src/main/java/com/juick/data/entities/LoginEntity.java new file mode 100644 index 00000000..69737b5d --- /dev/null +++ b/src/main/java/com/juick/data/entities/LoginEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "logins") +public class LoginEntity extends BaseUserPropertyEntity { + @Id + @Column(name = "hash", columnDefinition = "char(16)") + private String hash; + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } +} diff --git a/src/main/java/com/juick/data/entities/MessageEntity.java b/src/main/java/com/juick/data/entities/MessageEntity.java new file mode 100644 index 00000000..21896fb9 --- /dev/null +++ b/src/main/java/com/juick/data/entities/MessageEntity.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; +import java.io.Serializable; +import java.time.Instant; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "messages") +public class MessageEntity implements Serializable { + @Id + @Column(name = "message_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + @ManyToMany + @JoinTable(name = "messages_tags", + joinColumns = @JoinColumn(name = "message_id"), + inverseJoinColumns = @JoinColumn(name = "tag_id") + ) + private Set<TagEntity> tags = new HashSet<>(); + + @OneToMany(mappedBy = "message") + private Set<ReplyEntity> replies = new HashSet<>(); + + @ManyToOne(cascade = CascadeType.MERGE) + private UserEntity user; + + @Column(name = "ts") + private Instant ts; + + @Column(name = "txt") + private String text; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Set<TagEntity> getTags() { + return tags; + } + + public void setTags(Set<TagEntity> tags) { + this.tags = tags; + } + + public Set<ReplyEntity> getReplies() { + return replies; + } + + public void setReplies(Set<ReplyEntity> replies) { + this.replies = replies; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } + + public Instant getTimestamp() { + return ts; + } + + public void setTimestamp(Instant ts) { + this.ts = ts; + } +} diff --git a/src/main/java/com/juick/data/entities/ReplyEntity.java b/src/main/java/com/juick/data/entities/ReplyEntity.java new file mode 100644 index 00000000..bc0795cf --- /dev/null +++ b/src/main/java/com/juick/data/entities/ReplyEntity.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "replies") +public class ReplyEntity extends BaseMessagePropertyEntity { + @Id + @Column(name = "reply_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long replyId; + @Column(name = "txt") + private String text; + @ManyToOne(cascade = CascadeType.MERGE) + private UserEntity user; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Long getReplyId() { + return replyId; + } + + public void setReplyId(Long replyId) { + this.replyId = replyId; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } +} diff --git a/src/main/java/com/juick/data/entities/TagEntity.java b/src/main/java/com/juick/data/entities/TagEntity.java new file mode 100644 index 00000000..e5c5266d --- /dev/null +++ b/src/main/java/com/juick/data/entities/TagEntity.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +import java.util.Set; + +@Entity +@Table(name = "tags") +public class TagEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "tag_id") + private int id; + @Column(name = "name") + private String name; + + @ManyToMany(mappedBy = "tags") + private Set<MessageEntity> messages; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set<MessageEntity> getMessages() { + return messages; + } + + public void setMessages(Set<MessageEntity> messages) { + this.messages = messages; + } +} diff --git a/src/main/java/com/juick/data/entities/TelegramEntity.java b/src/main/java/com/juick/data/entities/TelegramEntity.java new file mode 100644 index 00000000..ccb21022 --- /dev/null +++ b/src/main/java/com/juick/data/entities/TelegramEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "telegram") +public class TelegramEntity extends BaseUserPropertyEntity { + @Id + @Column(name = "tg_id") + private Long identifier; + + public Long getIdentifier() { + return identifier; + } + + public void setIdentifier(Long identifier) { + this.identifier = identifier; + } +} diff --git a/src/main/java/com/juick/data/entities/UserEntity.java b/src/main/java/com/juick/data/entities/UserEntity.java new file mode 100644 index 00000000..63edadd9 --- /dev/null +++ b/src/main/java/com/juick/data/entities/UserEntity.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +import java.time.OffsetDateTime; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "users") +public class UserEntity { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "nick") + private String name; + + @Column(name = "passw") + private String password; + + @Column(name = "banned") + private boolean banned; + @Column(name = "last_seen") + private OffsetDateTime seen; + @Column + private boolean premium; + + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") + private Set<EmailEntity> emails = new HashSet<>(); + + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") + private Set<FacebookEntity> facebookIds = new HashSet<>(); + + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") + private Set<VKEntity> vkIds = new HashSet<>(); + + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") + private Set<LoginEntity> logins = new HashSet<>(); + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") + private Set<TelegramEntity> tgIds = new HashSet<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getId() { + return id; + } + + public void setId(Integer uid) { + this.id = uid; + } + + public boolean isBanned() { + return banned; + } + + public void setBanned(boolean banned) { + this.banned = banned; + } + + public OffsetDateTime getSeen() { + return this.seen; + } + + public void setSeen(OffsetDateTime seen) { + this.seen = seen; + } + + public Set<EmailEntity> getEmails() { + return emails; + } + + public void setEmails(Set<EmailEntity> emails) { + this.emails = emails; + } + + public Set<FacebookEntity> getFacebookIds() { + return facebookIds; + } + + public void setFacebookIds(Set<FacebookEntity> facebookIds) { + this.facebookIds = facebookIds; + } + + public Set<VKEntity> getVkIds() { + return vkIds; + } + + public void setVkIds(Set<VKEntity> vkIds) { + this.vkIds = vkIds; + } + + public Set<LoginEntity> getLogins() { + return logins; + } + + public void setLogins(Set<LoginEntity> logins) { + this.logins = logins; + } + + public Set<TelegramEntity> getTgIds() { + return tgIds; + } + + public void setTgIds(Set<TelegramEntity> tgIds) { + this.tgIds = tgIds; + } + + public boolean isPremium() { + return premium; + } + + public void setPremium(boolean premium) { + this.premium = premium; + } +} diff --git a/src/main/java/com/juick/data/entities/VKEntity.java b/src/main/java/com/juick/data/entities/VKEntity.java new file mode 100644 index 00000000..17840377 --- /dev/null +++ b/src/main/java/com/juick/data/entities/VKEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008-2024, 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.data.entities; + +import jakarta.persistence.*; + +@Entity +@Table(name = "vk") +public class VKEntity extends BaseUserPropertyEntity { + @Id + @Column(name = "vk_id") + private Long vkId; + + public Long getVkId() { + return vkId; + } + + public void setVkId(Long vkId) { + this.vkId = vkId; + } +} diff --git a/src/main/java/com/juick/model/User.java b/src/main/java/com/juick/model/User.java index 7f04a744..dd0feda1 100644 --- a/src/main/java/com/juick/model/User.java +++ b/src/main/java/com/juick/model/User.java @@ -27,7 +27,7 @@ import jakarta.xml.bind.annotation.XmlTransient; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.io.Serializable; import java.net.URI; import java.time.Instant; @@ -64,6 +64,7 @@ public class User implements Serializable { private boolean premium; @XmlTransient private boolean admin; + private String language; public User() { tokens = new ArrayList<>(); @@ -241,7 +242,7 @@ public class User implements Serializable { @XmlTransient public boolean isVerified() { - return verified; + return verified || !uri.toString().isEmpty(); } public void setVerified(boolean verified) { @@ -298,4 +299,12 @@ public class User implements Serializable { public void setAdmin(boolean admin) { this.admin = admin; } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } } diff --git a/src/main/java/com/juick/service/ActivityPubService.java b/src/main/java/com/juick/service/ActivityPubService.java index 75a3b488..a63de1b6 100644 --- a/src/main/java/com/juick/service/ActivityPubService.java +++ b/src/main/java/com/juick/service/ActivityPubService.java @@ -41,8 +41,8 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.tomitribe.auth.signatures.Base64; -import javax.annotation.Nonnull; -import javax.inject.Inject; +import jakarta.annotation.Nonnull; +import jakarta.inject.Inject; import java.io.IOException; import java.net.URI; import java.security.MessageDigest; diff --git a/src/main/java/com/juick/service/BaseJdbcService.java b/src/main/java/com/juick/service/BaseJdbcService.java index b51cbdc7..415efa89 100644 --- a/src/main/java/com/juick/service/BaseJdbcService.java +++ b/src/main/java/com/juick/service/BaseJdbcService.java @@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; diff --git a/src/main/java/com/juick/service/EmailService.java b/src/main/java/com/juick/service/EmailService.java index 614cf412..7d8126c5 100644 --- a/src/main/java/com/juick/service/EmailService.java +++ b/src/main/java/com/juick/service/EmailService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2023, Juick + * Copyright (C) 2008-2024, 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 @@ -39,4 +39,5 @@ public interface EmailService { String getEmailByAuthCode(String code); void deleteAuthCode(String code); Integer cleanupAuthCodes(); + boolean isValidEmail(String email); } diff --git a/src/main/java/com/juick/service/EmailServiceImpl.java b/src/main/java/com/juick/service/EmailServiceImpl.java index 383dbdaf..08aaf1b9 100644 --- a/src/main/java/com/juick/service/EmailServiceImpl.java +++ b/src/main/java/com/juick/service/EmailServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2020, Juick + * Copyright (C) 2008-2024, 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 @@ -17,13 +17,21 @@ package com.juick.service; +import com.fasterxml.jackson.databind.ObjectMapper; import com.juick.model.User; +import jakarta.inject.Inject; +import okhttp3.OkHttpClient; +import okhttp3.Request; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -36,6 +44,13 @@ import java.util.List; @Transactional public class EmailServiceImpl extends BaseJdbcService implements EmailService { + @Inject + private OkHttpClient httpClient; + @Inject + private ObjectMapper jsonMapper; + + @Value("${email_validation_service_url:http://localhost:8080}") + private String emailValidationServiceUrl; @Override public boolean verifyAddressByCode(Integer userId, String code) { try { @@ -120,4 +135,23 @@ public class EmailServiceImpl extends BaseJdbcService implements EmailService { new MapSqlParameterSource() .addValue("day", toDateTime(day.atOffset(ZoneOffset.UTC)), dateTimeType())); } + + @Override + public boolean isValidEmail(String email) { + var request = new Request.Builder() + .url(emailValidationServiceUrl + "/?email=" + email) + .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .build(); + try { + try(var response = httpClient.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + var account = jsonMapper.readTree(response.body().string()); + return account.has("disposable") && account.get("disposable").textValue().equals("false"); + } + } + } catch (IOException e) { + return false; + } + return false; + } } diff --git a/src/main/java/com/juick/service/InfoService.java b/src/main/java/com/juick/service/InfoService.java index 9540243e..f6b3cfe1 100644 --- a/src/main/java/com/juick/service/InfoService.java +++ b/src/main/java/com/juick/service/InfoService.java @@ -23,7 +23,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java index 940cdaba..de342977 100644 --- a/src/main/java/com/juick/service/MessagesServiceImpl.java +++ b/src/main/java/com/juick/service/MessagesServiceImpl.java @@ -38,13 +38,11 @@ import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.jdbc.core.simple.SimpleJdbcInsert; -import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.lang.NonNull; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; @@ -522,7 +520,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ user.setUri(uri); String path = uri.getPath(); String name = path.substring(path.lastIndexOf('/') + 1); - user.setName(name + "@" + uri.getHost()); + user.setName(name); } return new ImmutablePair<>(rs.getInt(1), user); }); diff --git a/src/main/java/com/juick/service/PatreonService.java b/src/main/java/com/juick/service/PatreonService.java index fdd6d3dd..8f21f389 100644 --- a/src/main/java/com/juick/service/PatreonService.java +++ b/src/main/java/com/juick/service/PatreonService.java @@ -20,20 +20,27 @@ package com.juick.service; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.OkHttpClient; import okhttp3.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.stream.StreamSupport; @Component public class PatreonService { + private static final Logger logger = LoggerFactory.getLogger("Patreon"); + + @Inject + private UserService userService; @Inject private OkHttpClient httpClient; @Inject @@ -89,4 +96,30 @@ public class PatreonService { } return List.of(); } + + public void updateStatus(List<String> updatedEmails) { + var campainsResponse = fetchCampaigns(); + List<String> activeEmails = new ArrayList<>(); + campainsResponse.forEach(campaign -> { + var pledgesResponse = fetchPledges(campaign); + pledgesResponse.forEach(pledge -> { + logger.debug("Pledge email: {}", pledge); + activeEmails.add(pledge); + }); + }); + activeEmails.forEach(email -> { + var user = userService.getUserByEmail(email); + if (!user.isAnonymous()) { + userService.setPremium(user.getUid(), true); + } + }); + updatedEmails.stream().filter(email -> !activeEmails.contains(email)) + .forEach(deleted -> { + var user = userService.getUserByEmail(deleted); + if (!user.isAnonymous() && user.isPremium()) { + logger.debug("User is not a patron anymore: {}", deleted); + userService.setPremium(user.getUid(), false); + } + }); + } } diff --git a/src/main/java/com/juick/service/PostgresSearchService.java b/src/main/java/com/juick/service/PostgresSearchService.java index a243b3f3..2becf64c 100644 --- a/src/main/java/com/juick/service/PostgresSearchService.java +++ b/src/main/java/com/juick/service/PostgresSearchService.java @@ -25,7 +25,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.sql.DataSource; import java.sql.SQLException; import java.util.Collections; diff --git a/src/main/java/com/juick/service/SocialService.java b/src/main/java/com/juick/service/SocialService.java index 2dbef8e5..fa280555 100644 --- a/src/main/java/com/juick/service/SocialService.java +++ b/src/main/java/com/juick/service/SocialService.java @@ -19,7 +19,7 @@ package com.juick.service; import com.juick.model.User; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.util.List; public interface SocialService { diff --git a/src/main/java/com/juick/service/SphinxSearchService.java b/src/main/java/com/juick/service/SphinxSearchService.java index 8a608b44..4171b7f3 100644 --- a/src/main/java/com/juick/service/SphinxSearchService.java +++ b/src/main/java/com/juick/service/SphinxSearchService.java @@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Collections; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/com/juick/service/SubscriptionServiceImpl.java b/src/main/java/com/juick/service/SubscriptionServiceImpl.java index 4aa5b5ac..057490f8 100644 --- a/src/main/java/com/juick/service/SubscriptionServiceImpl.java +++ b/src/main/java/com/juick/service/SubscriptionServiceImpl.java @@ -30,8 +30,8 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Nonnull; -import javax.inject.Inject; +import jakarta.annotation.Nonnull; +import jakarta.inject.Inject; import java.util.Collections; import java.util.List; import java.util.Set; diff --git a/src/main/java/com/juick/service/TagServiceImpl.java b/src/main/java/com/juick/service/TagServiceImpl.java index 9f5c9356..1eea6812 100644 --- a/src/main/java/com/juick/service/TagServiceImpl.java +++ b/src/main/java/com/juick/service/TagServiceImpl.java @@ -27,15 +27,12 @@ import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; diff --git a/src/main/java/com/juick/service/TelegramService.java b/src/main/java/com/juick/service/TelegramService.java index 16ba531d..71c9b9e3 100644 --- a/src/main/java/com/juick/service/TelegramService.java +++ b/src/main/java/com/juick/service/TelegramService.java @@ -18,6 +18,7 @@ package com.juick.service; import com.juick.model.User; +import org.springframework.cache.annotation.CacheEvict; import java.util.List; @@ -32,6 +33,7 @@ public interface TelegramService { boolean createTelegramUser(long tgID, String tgName); + @CacheEvict(value = "users_by_name", allEntries = true) boolean deleteTelegramUser(Integer uid); List<Long> getTelegramIdentifiers(List<User> users); diff --git a/src/main/java/com/juick/service/TwitterService.java b/src/main/java/com/juick/service/TwitterService.java index ffcaca13..41e73876 100644 --- a/src/main/java/com/juick/service/TwitterService.java +++ b/src/main/java/com/juick/service/TwitterService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2023, Juick + * Copyright (C) 2008-2024, 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 @@ -17,7 +17,6 @@ package com.juick.service; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.scribejava.apis.TwitterApi20; import com.github.scribejava.core.builder.ServiceBuilder; @@ -27,7 +26,6 @@ import com.github.scribejava.core.model.Verb; import com.github.scribejava.core.oauth.OAuth20Service; import com.juick.model.Message; import com.juick.util.MessageUtils; -import com.juick.util.formatters.PlainTextFormatter; import jakarta.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -36,7 +34,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.util.concurrent.ExecutionException; @@ -68,7 +66,7 @@ public class TwitterService { userService.getTwitterToken(jmsg.getUser().getUid()).ifPresent(t -> { String status = MessageUtils.getMessageHashTags(jmsg) + StringUtils.defaultString(jmsg.getText()); if (status.length() > 253) { - status = PlainTextFormatter.truncateText(status, 252) + "…"; + status = StringUtils.abbreviate(status, "…", 252); } status += " http://juick.com/" + jmsg.getMid(); try { diff --git a/src/main/java/com/juick/service/UserService.java b/src/main/java/com/juick/service/UserService.java index 6f7cb58f..845028f7 100644 --- a/src/main/java/com/juick/service/UserService.java +++ b/src/main/java/com/juick/service/UserService.java @@ -32,7 +32,6 @@ import org.springframework.cache.annotation.Cacheable; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Optional; /** @@ -83,6 +82,8 @@ public interface UserService { boolean updateUserInfo(User info); + boolean updateLanguage(User user); + boolean isInWL(int uid, int check); boolean isInBL(int uid, int check); @@ -170,7 +171,7 @@ public interface UserService { boolean updateFacebookUser(long fbID, String token, String fbName); - int getUIDbyVKID(long vkID); + Optional<User> getUserByVKID(long vkID); boolean createVKUser(long vkID, String loginhash, String token, String vkName, String vkLink); diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java index 0bbeea91..bad1378a 100644 --- a/src/main/java/com/juick/service/UserServiceImpl.java +++ b/src/main/java/com/juick/service/UserServiceImpl.java @@ -27,16 +27,10 @@ import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.UncategorizedSQLException; -import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.simple.SimpleJdbcInsert; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -71,6 +65,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { user.setVerified(rs.getLong(6) > 0); user.setPremium(rs.getInt(7) > 0); user.setAdmin(adminUsers.contains(user.getName())); + user.setLanguage(rs.getString("lang")); return user; } } @@ -125,7 +120,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { public Optional<User> getUserByUID(final int uid) { var list = getJdbcTemplate().query(""" SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, - COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium + COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang FROM users u LEFT JOIN facebook f ON f.user_id = u.id LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id LEFT JOIN emails e ON e.user_id = u.id WHERE u.id = ?""", userMapper, uid); @@ -139,7 +134,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { if (StringUtils.isNotBlank(username)) { var list = getJdbcTemplate().query(""" SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, - COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium + COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang FROM users u LEFT JOIN facebook f ON f.user_id = u.id LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id LEFT JOIN emails e ON e.user_id = u.id @@ -164,7 +159,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { try { List<User> list = getJdbcTemplate().query( "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + "LEFT JOIN emails e ON e.user_id = u.id " + @@ -190,7 +185,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { if (StringUtils.isNotBlank(jid)) { List<User> list = getJdbcTemplate().query( "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + "LEFT JOIN emails e ON e.user_id = u.id " + @@ -212,7 +207,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { return getNamedParameterJdbcTemplate().query( "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + "LEFT JOIN emails e ON e.user_id = u.id " + @@ -229,7 +224,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { return getNamedParameterJdbcTemplate().query( "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + "LEFT JOIN emails e ON e.user_id = u.id " + @@ -264,7 +259,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { if (StringUtils.isNotBlank(hash)) { List<User> list = getJdbcTemplate().query( "SELECT DISTINCT logins.user_id, u.nick, u.passw, u.banned, u.last_seen," + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM logins INNER JOIN users u ON logins.user_id = u.id " + "LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + @@ -302,7 +297,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { if (StringUtils.isNotBlank(username)) { List<User> list = getJdbcTemplate().query( "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + "LEFT JOIN emails e ON e.user_id = u.id " + @@ -368,6 +363,15 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { } } + @Transactional + @Override + public boolean updateLanguage(final User user) { + return getJdbcTemplate().update( + "UPDATE users SET lang=? WHERE id=?", + user.getLanguage(), + user.getUid()) > 0; + } + @Transactional(readOnly = true) @Override public boolean isInWL(final int uid, final int check) { @@ -758,7 +762,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { List<User> list = getJdbcTemplate().query( """ SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, - COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium + COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang FROM users u LEFT JOIN facebook f ON f.user_id = u.id LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id LEFT JOIN emails e ON e.user_id = u.id @@ -791,7 +795,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { public Optional<User> getUserByFacebookId(long facebookId) { List<User> list = getJdbcTemplate().query( "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " + - "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + "LEFT JOIN emails e ON e.user_id = u.id WHERE f.fb_id = ?", new UserMapper(), facebookId); @@ -815,10 +819,15 @@ public class UserServiceImpl extends BaseJdbcService implements UserService { @Transactional(readOnly = true) @Override - public int getUIDbyVKID(long vkID) { - var users = getJdbcTemplate().queryForList(""" - SELECT user_id FROM vk WHERE vk_id=? AND user_id IS NOT NULL""", Integer.class, vkID); - return users.isEmpty() ? 0 : users.get(0); + public Optional<User> getUserByVKID(long vkID) { + List<User> list = getJdbcTemplate().query( + "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " + + "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium, lang " + + "FROM users u LEFT JOIN facebook f ON f.user_id = u.id " + + "LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " + + "LEFT JOIN emails e ON e.user_id = u.id WHERE vk.vk_id = ?", new UserMapper(), vkID); + + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); } @Transactional diff --git a/src/main/java/com/juick/service/VKService.java b/src/main/java/com/juick/service/VKService.java index 14d7e3e9..7f6b2516 100644 --- a/src/main/java/com/juick/service/VKService.java +++ b/src/main/java/com/juick/service/VKService.java @@ -30,8 +30,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; @Service public class VKService { @@ -40,7 +41,8 @@ public class VKService { private String VK_APPID; @Value("${vk_secret:secret}") private String VK_SECRET; - private static final String VK_REDIRECT = "https://juick.com/_vklogin"; + @Value("${ap_base_uri:http://localhost:8080/}") + private String baseUri; @Inject private ObjectMapper jsonMapper; @Inject @@ -49,14 +51,16 @@ public class VKService { @PostConstruct public void init() { + UriComponentsBuilder redirectBuilder = UriComponentsBuilder.fromUriString(baseUri); + String redirectUri = redirectBuilder.replacePath("/_vklogin").build().toUriString(); ServiceBuilder vkBuilder = new ServiceBuilder(VK_APPID); setVkAuthService(vkBuilder.apiSecret(VK_SECRET) .defaultScope("friends,wall,offline,groups") - .callback(VK_REDIRECT) + .callback(redirectUri) .build(VkontakteApi.instance())); } - public void updatePremiumStatus(Integer userId) { + public boolean updatePremiumStatus(Integer userId) { var vkUser = userService.getVkTokens(userId); if (vkUser != null) { OAuth2AccessToken token = new OAuth2AccessToken(vkUser.getRight()); @@ -65,12 +69,13 @@ public class VKService { getVkAuthService().signRequest(token, donRequest); try (Response vkResponse = getVkAuthService().execute(donRequest)) { if (vkResponse.isSuccessful()) { - logger.info(vkResponse.getBody()); + logger.debug(vkResponse.getBody()); var response = jsonMapper.readTree(vkResponse.getBody()); if (response.has("response")) { var isDon = response.get("response").intValue() > 0; - logger.info("{} is Don: {}", vkUser.getLeft(), isDon); + logger.debug("{} is Don: {}", vkUser.getLeft(), isDon); userService.setPremium(userId, isDon); + return isDon; } else { // token is expired or does not have "groups" permissions userService.updateVkToken(userId, ""); @@ -80,8 +85,9 @@ public class VKService { logger.error("Don request error", e); } } else { - logger.warn("User is not connected to VK: {}", userId); + logger.debug("User is not connected to VK: {}", userId); } + return false; } public OAuth20Service getVkAuthService() { diff --git a/src/main/java/com/juick/service/WebfingerService.java b/src/main/java/com/juick/service/WebfingerService.java index 4accfc94..89ffc230 100644 --- a/src/main/java/com/juick/service/WebfingerService.java +++ b/src/main/java/com/juick/service/WebfingerService.java @@ -30,7 +30,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import rocks.xmpp.addr.Jid; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.net.URI; diff --git a/src/main/java/com/juick/service/component/SystemEvent.java b/src/main/java/com/juick/service/component/SystemEvent.java index 830813ad..9acd7a2b 100644 --- a/src/main/java/com/juick/service/component/SystemEvent.java +++ b/src/main/java/com/juick/service/component/SystemEvent.java @@ -17,7 +17,7 @@ package com.juick.service.component; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import com.juick.www.api.SystemActivity; import org.springframework.context.ApplicationEvent; diff --git a/src/main/java/com/juick/util/MessageUtils.java b/src/main/java/com/juick/util/MessageUtils.java index 308d144b..5418ae64 100644 --- a/src/main/java/com/juick/util/MessageUtils.java +++ b/src/main/java/com/juick/util/MessageUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2020, Juick + * Copyright (C) 2008-2024, 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 @@ -17,24 +17,17 @@ package com.juick.util; +import com.juick.model.Entity; import com.juick.model.Message; import com.juick.model.Tag; import com.juick.model.User; -import com.juick.model.Entity; -import com.juick.util.formatters.PlainTextFormatter; import org.apache.commons.lang3.StringUtils; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -49,13 +42,20 @@ public class MessageUtils { } public static String formatQuote(final String quote) { + return formatQuote(quote, false); + } + + public static String formatQuote(final String quote, final boolean isHtml) { String result = quote; + var prefix = isHtml ? "<blockquote>" : ">"; + var suffix = isHtml ? "</blockquote>" : "\n"; + if (quote != null) { if (quote.length() > 50) { - result = ">" + PlainTextFormatter.truncateText(quote, 47).replace('\n', ' ') + "…\n"; + result = prefix + StringUtils.abbreviate(quote, "…", 47).replace('\n', ' ') + suffix; } else if (!quote.isEmpty()) { - result = ">" + quote.replace('\n', ' ') + "\n"; + result = prefix + quote.replace('\n', ' ') + suffix; } } @@ -64,7 +64,7 @@ public class MessageUtils { private final static String urlWhiteSpacePrefix = "((?<=\\s)|(?<=\\A))"; - private final static String urlRegex = "((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\]\\s\\n\\\"\\>]*)"; + private final static String urlRegex = "((?:(?:ht|f)tps?://(?:www\\.)?([^\\s()<>/?#]+)([^\\s()<>«»]*)?))"; private final static String urlWithWhitespacesRegex = urlWhiteSpacePrefix + urlRegex; @@ -227,11 +227,11 @@ public class MessageUtils { m.appendTail(sb); msg = sb.toString(); + + // > citate + msg = msg.replaceAll(citateRegex, "<blockquote>$1</blockquote>"); + msg = msg.replaceAll("</blockquote><blockquote>", "\n"); if (!compatibleWithDurov) { - // > citate - msg = msg.replaceAll(citateRegex, "<blockquote>$1</blockquote>"); - msg = msg.replaceAll("</blockquote><blockquote>", "\n"); - msg = msg.replaceAll("\n", "<br/>\n"); } return msg; diff --git a/src/main/java/com/juick/util/formatters/PlainTextFormatter.java b/src/main/java/com/juick/util/formatters/PlainTextFormatter.java index 2e7722e7..682a15b4 100644 --- a/src/main/java/com/juick/util/formatters/PlainTextFormatter.java +++ b/src/main/java/com/juick/util/formatters/PlainTextFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2020, Juick + * Copyright (C) 2008-2024, 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 @@ -22,7 +22,6 @@ import com.juick.util.MessageUtils; import org.apache.commons.lang3.StringUtils; import org.ocpsoft.prettytime.PrettyTime; -import java.text.BreakIterator; import java.util.Date; import java.util.Locale; @@ -63,7 +62,7 @@ public class PlainTextFormatter { sb.append(attachmentUrl).append("\n"); } if (txt.length() >= cropLength) { - sb.append(PlainTextFormatter.truncateText(txt, cropLength)).append(" [...]"); + sb.append(StringUtils.abbreviate(txt, "[…]", cropLength)); } else { sb.append(txt); } @@ -98,26 +97,4 @@ public class PlainTextFormatter { public static String formatTwitterCard(Message jmsg) { return MessageUtils.getMessageHashTags(jmsg) + StringUtils.defaultString(jmsg.getText()); } - /** - * Truncate text to the nearest word, up to a maximum length specified. - * - * @param text - * @param maxLength - * @return - */ - public static String truncateText(String text, int maxLength) { - if(text != null && text.length() > maxLength) { - BreakIterator bi = BreakIterator.getWordInstance(); - bi.setText(text); - - if(bi.isBoundary(maxLength-1)) { - return text.substring(0, maxLength-2); - } else { - int preceding = bi.preceding(maxLength-1); - return text.substring(0, preceding-1); - } - } else { - return text; - } - } } diff --git a/src/main/java/com/juick/util/xmpp/JidConverter.java b/src/main/java/com/juick/util/xmpp/JidConverter.java index 4f457164..5c40d470 100644 --- a/src/main/java/com/juick/util/xmpp/JidConverter.java +++ b/src/main/java/com/juick/util/xmpp/JidConverter.java @@ -18,13 +18,14 @@ package com.juick.util.xmpp; import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import rocks.xmpp.addr.Jid; public class JidConverter implements Converter<String, Jid> { @Nullable @Override - public Jid convert(String jidStr) { + public Jid convert(@NonNull String jidStr) { return Jid.of(jidStr); } } diff --git a/src/main/java/com/juick/www/VisitorArgumentResolver.java b/src/main/java/com/juick/www/VisitorArgumentResolver.java index a092581a..6b85c0cd 100644 --- a/src/main/java/com/juick/www/VisitorArgumentResolver.java +++ b/src/main/java/com/juick/www/VisitorArgumentResolver.java @@ -29,7 +29,7 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import javax.inject.Inject; +import jakarta.inject.Inject; public class VisitorArgumentResolver implements HandlerMethodArgumentResolver { private final UserService userService; diff --git a/src/main/java/com/juick/www/WebApp.java b/src/main/java/com/juick/www/WebApp.java index cf9570aa..e8c562c5 100644 --- a/src/main/java/com/juick/www/WebApp.java +++ b/src/main/java/com/juick/www/WebApp.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.servlet.resource.ResourceUrlProvider; import org.springframework.web.util.UriComponentsBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/com/juick/www/ad/SapeService.java b/src/main/java/com/juick/www/ad/SapeService.java index 97ab882b..ca823974 100644 --- a/src/main/java/com/juick/www/ad/SapeService.java +++ b/src/main/java/com/juick/www/ad/SapeService.java @@ -31,7 +31,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; import ru.sape.Sape; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.net.URI; @ControllerAdvice(assignableTypes = Site.class) diff --git a/src/main/java/com/juick/www/api/ApiSocialLogin.java b/src/main/java/com/juick/www/api/ApiSocialLogin.java deleted file mode 100644 index c8758d59..00000000 --- a/src/main/java/com/juick/www/api/ApiSocialLogin.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2008-2020, 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.www.api; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.scribejava.apis.AppleClientSecretGenerator; -import com.github.scribejava.apis.AppleSignInApi; -import com.github.scribejava.apis.FacebookApi; -import com.github.scribejava.apis.GoogleTokenVerifier; -import com.github.scribejava.apis.VkontakteApi; -import com.github.scribejava.core.builder.ServiceBuilder; -import com.github.scribejava.core.model.OAuth2AccessToken; -import com.github.scribejava.core.model.OAuthRequest; -import com.github.scribejava.core.model.Verb; -import com.github.scribejava.core.oauth.OAuth20Service; -import com.juick.model.AuthResponse; -import com.juick.model.ext.facebook.User; -import com.juick.model.ext.vk.UsersResponse; -import com.juick.service.EmailService; -import com.juick.service.UserService; -import com.juick.util.HttpBadRequestException; -import com.juick.util.HttpForbiddenException; - -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.util.UriComponentsBuilder; - -import jakarta.annotation.PostConstruct; -import javax.inject.Inject; -import java.io.IOException; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -/** - * - * @author Ugnich Anton - */ -@Controller -public class ApiSocialLogin { - - private static final Logger logger = LoggerFactory.getLogger(ApiSocialLogin.class); - - @Value("${facebook_appid:appid}") - private String FACEBOOK_APPID; - @Value("${facebook_secret:secret}") - private String FACEBOOK_SECRET; - private static final String FACEBOOK_REDIRECT = "https://api.juick.com/_fblogin"; - private static final String VK_REDIRECT = "https://api.juick.com/_vklogin"; - @Inject - private ObjectMapper jsonMapper; - private OAuth20Service facebookAuthService, vkAuthService, appleSignInService; - - @Value("${twitter_consumer_key:appid}") - private String twitterConsumerKey; - @Value("${twitter_consumer_secret:secret}") - private String twitterConsumerSecret; - @Value("${vk_appid:appid}") - private String VK_APPID; - @Value("${vk_secret:secret}") - private String VK_SECRET; - @Value("${google_client_id:}") - private String googleClientId; - @Value("${apple_app_id:appid}") - private String appleApplicationId; - @Value("${ap_base_uri:http://localhost:8080/}") - private String baseUri; - - @Inject - private UserService userService; - @Inject - private EmailService emailService; - @Inject - private AppleClientSecretGenerator clientSecretGenerator; - @Inject - private Users users; - - @PostConstruct - public void init() { - ServiceBuilder facebookBuilder = new ServiceBuilder(FACEBOOK_APPID); - ServiceBuilder vkBuilder = new ServiceBuilder(VK_APPID); - facebookAuthService = facebookBuilder - .apiSecret(FACEBOOK_SECRET) - .callback(FACEBOOK_REDIRECT) - .defaultScope("email") - .build(FacebookApi.instance()); - vkAuthService = vkBuilder - .apiSecret(VK_SECRET) - .defaultScope("friends,wall,offline,groups") - .callback(VK_REDIRECT) - .build(VkontakteApi.instance()); - ServiceBuilder appleSignInBuilder = new ServiceBuilder(appleApplicationId); - UriComponentsBuilder redirectBuilder = UriComponentsBuilder.fromUriString(baseUri); - String appleSignInRedirectUri = redirectBuilder.replacePath("/api/_applelogin").build().toUriString(); - appleSignInService = appleSignInBuilder - .callback(appleSignInRedirectUri) - .defaultScope("email") - .build(new AppleSignInApi(clientSecretGenerator, appleApplicationId)); - } - - @GetMapping("/api/_fblogin") - protected String doFacebookLogin(@RequestParam(required = false) String code, - @RequestParam(required = false) String state) throws IOException, ExecutionException, InterruptedException { - if (StringUtils.isBlank(code)) { - String fbstate = UUID.randomUUID().toString(); - userService.addFacebookState(fbstate, state); - return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate); - } - - String redirectUrl = userService.verifyFacebookState(state); - - if (StringUtils.isEmpty(redirectUrl)) { - logger.error("state is missing"); - throw new HttpBadRequestException(); - } - OAuth2AccessToken token = facebookAuthService.getAccessToken(code); - final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/me?fields=id,name,email"); - facebookAuthService.signRequest(token, meRequest); - String graph = facebookAuthService.execute(meRequest).getBody(); - if (StringUtils.isBlank(graph)) { - logger.error("FACEBOOK GRAPH ERROR"); - throw new HttpBadRequestException(); - } - User fb = jsonMapper.readValue(graph, User.class); - long fbID = NumberUtils.toLong(fb.id(), 0); - if (fbID == 0 || StringUtils.isBlank(fb.name())) { - logger.error("Missing required fields, id: {}, name: {}", fbID, fb.name()); - throw new HttpBadRequestException(); - } - - Optional<com.juick.model.User> existingFacebookUser = userService.getUserByFacebookId(fbID); - if (existingFacebookUser.isPresent()) { - if (!userService.updateFacebookUser(fbID, token.getAccessToken(), fb.name())) { - logger.error("error updating facebook user, id: {}, token: {}", fbID, token.getAccessToken()); - throw new HttpBadRequestException(); - } - if (StringUtils.isNotEmpty(fb.email())) { - logger.info("found {} for facebook user {}", fb.email(), fb.name()); - Integer userId = existingFacebookUser.get().getUid(); - if (!emailService.getEmails(userId, false).contains(fb.email())) { - emailService.addEmail(userId, fb.email()); - } - } - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl); - uriComponentsBuilder.queryParam("hash", userService.getHashByUID(existingFacebookUser.get().getUid())); - uriComponentsBuilder.queryParam("retpath", redirectUrl); - return "redirect:" + uriComponentsBuilder.build().toUriString(); - } else { - if (!userService.createFacebookUser(fbID, state, token.getAccessToken(), fb.name())) { - throw new HttpBadRequestException(); - } - return "redirect:/signup?type=fb&hash=" + state; - } - } - @GetMapping("/api/_vklogin") - protected String doVKLogin(@RequestParam(required = false) String code, - @RequestParam String state) throws IOException, ExecutionException, InterruptedException { - if (StringUtils.isBlank(code)) { - String vkstate = UUID.randomUUID().toString(); - userService.addVKState(vkstate, state); - return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate); - } - - String redirectUrl = userService.verifyVKState(state); - if (StringUtils.isBlank(redirectUrl)) { - logger.error("state is missing"); - throw new HttpBadRequestException(); - } - OAuth2AccessToken token = vkAuthService.getAccessToken(code); - - OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.131"); - vkAuthService.signRequest(token, meRequest); - String graph = vkAuthService.execute(meRequest).getBody(); - - com.juick.model.ext.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).users().get(0); - String vkName = jsonUser.firstName() + " " + jsonUser.lastName(); - String vkLink = jsonUser.screenName(); - - if (vkName.length() == 1 || StringUtils.isBlank(vkLink)) { - logger.error("vk user error"); - throw new HttpBadRequestException(); - } - - long vkID = NumberUtils.toLong(jsonUser.id(), 0); - int uid = userService.getUIDbyVKID(vkID); - if (uid > 0) { - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl); - uriComponentsBuilder.queryParam("hash", userService.getHashByUID(uid)); - uriComponentsBuilder.queryParam("retpath", redirectUrl); - return "redirect:" + uriComponentsBuilder.build().toUriString(); - } else { - String loginhash = UUID.randomUUID().toString(); - if (!userService.createVKUser(vkID, loginhash, token.getAccessToken(), vkName, vkLink)) { - logger.error("create vk user error"); - throw new HttpBadRequestException(); - } - return "redirect:/signup?type=vk&hash=" + loginhash; - } - } - @ResponseBody - @PostMapping("/api/_google") - public AuthResponse googleSignIn(@RequestParam(name = "idToken") String idTokenString) { - logger.info("Token: {}", idTokenString); - logger.info("Client: {}", googleClientId); - Optional<String> verifiedEmail = GoogleTokenVerifier.validateToken(googleClientId, idTokenString); - if (verifiedEmail.isPresent()) { - String email = verifiedEmail.get(); - com.juick.model.User visitor = userService.getUserByEmail(email); - if (visitor.isAnonymous()) { - String verificationCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); - emailService.addVerificationCode(null, email, verificationCode); - return new AuthResponse(null, email, verificationCode); - } else { - return new AuthResponse(users.getMe(visitor), null, null); - } - } - throw new HttpForbiddenException(); - } - @ResponseBody - @PostMapping("/api/signup") - public com.juick.model.User signupWithEmail(String username, String password, String verificationCode) { - if (username.length() < 2 || username.length() > 16 || !username.matches("^[a-zA-Z0-9\\-]+$") - || password.length() < 6 || password.length() > 32) { - throw new HttpBadRequestException(); - } - - String verifiedEmail = emailService.getEmailByAuthCode(verificationCode); - if (StringUtils.isNotEmpty(verifiedEmail)) { - com.juick.model.User newUser = userService.createUser(username, password).orElseThrow(HttpBadRequestException::new); - emailService.addEmail(newUser.getUid(), verifiedEmail); - emailService.deleteAuthCode(verificationCode); - return newUser; - } else { - throw new HttpForbiddenException(); - } - } - @GetMapping("/api/_applelogin") - public String doAppleLogin(@RequestParam(required = false) String code, @RequestParam String state) { - if (StringUtils.isBlank(code)) { - String astate = UUID.randomUUID().toString(); - userService.addVKState(astate, state); - return "redirect:" + appleSignInService.getAuthorizationUrl(astate); - } - throw new HttpBadRequestException(); - } - @PostMapping("/api/_applelogin") - public String doVerifyAppleResponse(@RequestParam Map<String, String> body) throws InterruptedException, ExecutionException, IOException { - OAuth2AccessToken token = appleSignInService.getAccessToken(body.get("code")); - var jsonNode = jsonMapper.readTree(token.getRawResponse()); - var idToken = jsonNode.get("id_token").textValue(); - logger.info("Token: {}", idToken); - AppleSignInApi api = (AppleSignInApi) appleSignInService.getApi(); - var email = api.validateToken(idToken); - - if (email.isPresent()) { - com.juick.model.User user = userService.getUserByEmail(email.get()); - if (!user.isAnonymous()) { - String redirectUrl = userService.verifyVKState(body.get("state")); - if (StringUtils.isBlank(redirectUrl)) { - logger.error("state is missing"); - throw new HttpBadRequestException(); - } - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl); - uriComponentsBuilder.queryParam("hash", userService.getHashByUID(user.getUid())); - uriComponentsBuilder.queryParam("retpath", redirectUrl); - return "redirect:" + uriComponentsBuilder.build().toUriString(); - } else { - String verificationCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); - emailService.addVerificationCode(null, email.get(), verificationCode); - return "redirect:/signup?type=email&hash=" + verificationCode; - } - } - throw new HttpBadRequestException(); - } -} diff --git a/src/main/java/com/juick/www/api/Mastodon.java b/src/main/java/com/juick/www/api/Mastodon.java index 190ee5ef..ca3687e7 100644 --- a/src/main/java/com/juick/www/api/Mastodon.java +++ b/src/main/java/com/juick/www/api/Mastodon.java @@ -49,7 +49,7 @@ import org.springframework.security.oauth2.server.authorization.settings.ClientS import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.web.bind.annotation.*; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.net.URI; import java.time.Duration; import java.time.Instant; @@ -164,7 +164,7 @@ public class Mastodon { ) { } - @GetMapping({"/api/v1/instance", "/api/v2/instance"}) + @GetMapping("/api/v1/instance") public Instance getInstance() { return new Instance(domain, "Microblogging service", "Juick", "2.x","support@juick.com"); } diff --git a/src/main/java/com/juick/www/api/Messages.java b/src/main/java/com/juick/www/api/Messages.java index 969bef4f..ecea513b 100644 --- a/src/main/java/com/juick/www/api/Messages.java +++ b/src/main/java/com/juick/www/api/Messages.java @@ -39,7 +39,7 @@ import org.springframework.http.MediaType; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.util.*; diff --git a/src/main/java/com/juick/www/api/Notifications.java b/src/main/java/com/juick/www/api/Notifications.java index c1ee74ab..f8f8dc14 100644 --- a/src/main/java/com/juick/www/api/Notifications.java +++ b/src/main/java/com/juick/www/api/Notifications.java @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Collections; import java.util.List; import java.util.stream.Stream; diff --git a/src/main/java/com/juick/www/api/PM.java b/src/main/java/com/juick/www/api/PM.java index 5f0988c1..64f9bd82 100644 --- a/src/main/java/com/juick/www/api/PM.java +++ b/src/main/java/com/juick/www/api/PM.java @@ -34,7 +34,7 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/juick/www/api/Post.java b/src/main/java/com/juick/www/api/Post.java index 4f1c2cad..57c23703 100644 --- a/src/main/java/com/juick/www/api/Post.java +++ b/src/main/java/com/juick/www/api/Post.java @@ -22,7 +22,7 @@ import java.net.URL; import java.util.List; import java.util.Optional; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.juick.www.api.activity.helpers.ProfileUriBuilder; import io.swagger.v3.oas.annotations.Parameter; @@ -48,7 +48,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -74,38 +76,36 @@ public class Post { @RequestMapping(value = "/api/post", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(value = HttpStatus.OK) - public CommandResult doPostMessage( + public ResponseEntity<?> doPostMessage( @Parameter(hidden = true) User visitor, @RequestParam(required = false, defaultValue = StringUtils.EMPTY) String body, @RequestParam(required = false) String img, @RequestParam(required = false) MultipartFile attach) throws Exception { body = body.replace("\r", StringUtils.EMPTY); - - URI attachmentFName = HttpUtils.receiveMultiPartFile(attach, storageService.getTemporaryDirectory()); - - if (StringUtils.isBlank(attachmentFName.toString()) && img != null && img.length() > 10) { - URI juickUri = URI.create(img); - if (juickUri.getScheme().equals("juick")) { - attachmentFName = juickUri; - } else { - try { + try { + URI attachmentFName = HttpUtils.receiveMultiPartFile(attach, storageService.getTemporaryDirectory()); + if (StringUtils.isBlank(attachmentFName.toString()) && img != null && img.length() > 10) { + URI juickUri = URI.create(img); + if (juickUri.getScheme().equals("juick")) { + attachmentFName = juickUri; + } else { URL imgUrl = new URL(img); attachmentFName = HttpUtils.downloadImage(imgUrl, storageService.getTemporaryDirectory()); - } catch (Exception e) { - logger.error("DOWNLOAD ERROR", e); - throw new HttpBadRequestException(); } } + if (StringUtils.isBlank(body) && StringUtils.isBlank(attachmentFName.toString())) { + // Should be there for compatibility + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(CommandResult.fromString("Empty message")); + } + return ResponseEntity.ok(commandsManager.processCommand(visitor, body, attachmentFName)); + } catch (Exception e) { + logger.error("DOWNLOAD ERROR", e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(CommandResult.fromString(e.getMessage())); } - if (StringUtils.isBlank(body) && StringUtils.isBlank(attachmentFName.toString())) { - // Should be there for compatibility - throw new HttpBadRequestException(); - } - return commandsManager.processCommand(visitor, body, attachmentFName); } @RequestMapping(value = "/api/comment", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public CommandResult doPostComment( + public ResponseEntity<?> doPostComment( @Parameter(hidden = true) User visitor, @RequestParam(defaultValue = "0") int mid, @RequestParam(defaultValue = "0") int rid, @@ -114,11 +114,11 @@ public class Post { @RequestParam(required = false) MultipartFile attach) throws Exception { if (mid == 0) { - throw new HttpBadRequestException(); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(CommandResult.fromString("Invalid mid")); } Optional<Message> message = messagesService.getMessage(mid); if (message.isEmpty()) { - throw new HttpNotFoundException(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(CommandResult.fromString("Message not found")); } Message msg = message.get(); @@ -127,7 +127,7 @@ public class Post { if (rid > 0) { reply = messagesService.getReply(mid, rid); if (reply == null) { - throw new HttpNotFoundException(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(CommandResult.fromString("Reply not found")); } } @@ -135,25 +135,23 @@ public class Post { || userService.isInBL(visitor.getUid(), msg.getUser().getUid()) || (reply != null && userService.isInBL(visitor.getUid(), reply.getUser().getUid()))) { // TODO: validator - throw new HttpForbiddenException(); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CommandResult.fromString("Forbidden")); } - - URI attachmentFName = HttpUtils.receiveMultiPartFile(attach, storageService.getTemporaryDirectory()); - - if (StringUtils.isBlank(attachmentFName.toString()) && img != null && img.length() > 10) { - try { + try { + URI attachmentFName = HttpUtils.receiveMultiPartFile(attach, storageService.getTemporaryDirectory()); + if (StringUtils.isBlank(attachmentFName.toString()) && img != null && img.length() > 10) { attachmentFName = HttpUtils.downloadImage(new URL(img), storageService.getTemporaryDirectory()); - } catch (Exception e) { - logger.error("DOWNLOAD ERROR", e); - throw new HttpBadRequestException(); } + if (StringUtils.isBlank(body) && StringUtils.isBlank(attachmentFName.toString())) { + // Should be there for compatibility + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(CommandResult.fromString("Empty message")); + } + return ResponseEntity.ok(commandsManager.processCommand(visitor, String.format("#%d/%d %s", mid, rid, body), + attachmentFName)); + } catch (Exception e) { + logger.error("DOWNLOAD ERROR", e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(CommandResult.fromString(e.getMessage())); } - if (StringUtils.isBlank(body) && StringUtils.isBlank(attachmentFName.toString())) { - // Should be there for compatibility - throw new HttpBadRequestException(); - } - return commandsManager.processCommand(visitor, String.format("#%d/%d %s", mid, rid, body), - attachmentFName); } @PostMapping("/api/like") diff --git a/src/main/java/com/juick/www/api/Service.java b/src/main/java/com/juick/www/api/Service.java index 41fdcfbf..ed6699d2 100644 --- a/src/main/java/com/juick/www/api/Service.java +++ b/src/main/java/com/juick/www/api/Service.java @@ -37,6 +37,7 @@ import jakarta.mail.internet.AddressException; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; @@ -53,7 +54,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -221,7 +222,8 @@ public class Service { } @GetMapping("/api/events") - public SseEmitter handle(@Parameter(hidden = true) User visitor) { + public SseEmitter handle(@Parameter(hidden = true) User visitor, HttpServletResponse response) { + response.addHeader("X-Accel-Buffering", "no"); logger.info("{} connected", visitor.getName()); if (!visitor.isAnonymous()) { userService.updateLastSeen(visitor); diff --git a/src/main/java/com/juick/www/api/SystemActivity.java b/src/main/java/com/juick/www/api/SystemActivity.java index a75ef3b6..33d4e1d3 100644 --- a/src/main/java/com/juick/www/api/SystemActivity.java +++ b/src/main/java/com/juick/www/api/SystemActivity.java @@ -23,7 +23,7 @@ import com.juick.model.User; import java.util.Collections; import java.util.List; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; public class SystemActivity { private SystemActivity(ActivityType type) { diff --git a/src/main/java/com/juick/www/api/Tags.java b/src/main/java/com/juick/www/api/Tags.java index 5bb25af6..b89918a6 100644 --- a/src/main/java/com/juick/www/api/Tags.java +++ b/src/main/java/com/juick/www/api/Tags.java @@ -24,7 +24,7 @@ import io.swagger.v3.oas.annotations.Parameter; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; /** diff --git a/src/main/java/com/juick/www/api/Users.java b/src/main/java/com/juick/www/api/Users.java index 34382f8e..923d00b9 100644 --- a/src/main/java/com/juick/www/api/Users.java +++ b/src/main/java/com/juick/www/api/Users.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.juick.model.AnonymousUser; import com.juick.model.ApplicationStatus; diff --git a/src/main/java/com/juick/www/api/activity/Profile.java b/src/main/java/com/juick/www/api/activity/Profile.java index 101c59f9..e5bd04ac 100644 --- a/src/main/java/com/juick/www/api/activity/Profile.java +++ b/src/main/java/com/juick/www/api/activity/Profile.java @@ -52,7 +52,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/com/juick/www/api/hostmeta/HostMeta.java b/src/main/java/com/juick/www/api/hostmeta/HostMeta.java deleted file mode 100644 index c6f1dce6..00000000 --- a/src/main/java/com/juick/www/api/hostmeta/HostMeta.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2008-2020, 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.www.api.hostmeta; - -import com.cliqset.xrd.Link; -import com.cliqset.xrd.XRD; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Collections; - -import static com.cliqset.xrd.XRDConstants.XRD_MEDIA_TYPE; - -@RestController -public class HostMeta { - @Value("${ap_base_uri:http://localhost:8080/}") - private String baseUri; - @GetMapping(value = "/.well-known/host-meta", produces = { XRD_MEDIA_TYPE, MediaType.APPLICATION_XML_VALUE }) - public XRD hostMetaResponse() { - Link webfinger = new Link(); - webfinger.setTemplate(String.format("%swebfinger?resource={uri}", baseUri)); - XRD xrd = new XRD(); - xrd.setLinks(Collections.singletonList(webfinger)); - return xrd; - } -} diff --git a/src/main/java/com/juick/www/api/webfinger/Resource.java b/src/main/java/com/juick/www/api/webfinger/Resource.java index 4b2bc388..3f04097c 100644 --- a/src/main/java/com/juick/www/api/webfinger/Resource.java +++ b/src/main/java/com/juick/www/api/webfinger/Resource.java @@ -17,53 +17,6 @@ package com.juick.www.api.webfinger; -import com.juick.model.User; -import com.juick.www.api.webfinger.model.Account; -import com.juick.www.api.webfinger.model.Link; -import com.juick.util.HttpNotFoundException; -import com.juick.service.UserService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.UriComponentsBuilder; -import rocks.xmpp.addr.Jid; - -import javax.inject.Inject; -import java.util.List; - -import static com.juick.www.api.activity.model.Context.ACTIVITY_MEDIA_TYPE; - -@RestController public class Resource { public static final String MEDIA_TYPE = "application/jrd+json"; - @Inject - private UserService userService; - @Value("${web_domain:localhost}") - private String domain; - @Value("${ap_base_uri:http://localhost:8080/}") - private String baseUri; - - @GetMapping(value = "/.well-known/webfinger", produces = { - Resource.MEDIA_TYPE, MediaType.APPLICATION_JSON_VALUE }) - public Account getWebResource(@RequestParam String resource) { - if (resource.startsWith("acct:")) { - try { - Jid account = Jid.of(resource.substring(5)); - if (account.getDomain().equals(domain)) { - User user = userService.getUserByName(account.getLocal()); - if (!user.isAnonymous()) { - UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseUri); - builder.path(String.format("/u/%s", user.getName())); - Link blog = new Link("self", ACTIVITY_MEDIA_TYPE, builder.toUriString()); - return new Account(resource, List.of(blog)); - } - } - } catch (NullPointerException | IllegalArgumentException e) { - throw new HttpNotFoundException(); - } - } - throw new HttpNotFoundException(); - } } diff --git a/src/main/java/com/juick/www/api/webhooks/PatreonWebhook.java b/src/main/java/com/juick/www/api/webhooks/PatreonWebhook.java index 99999838..c05585ac 100644 --- a/src/main/java/com/juick/www/api/webhooks/PatreonWebhook.java +++ b/src/main/java/com/juick/www/api/webhooks/PatreonWebhook.java @@ -32,7 +32,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -67,28 +67,6 @@ public class PatreonWebhook { var updatedEmails = StreamSupport.stream(json.get("included").spliterator(), false) .filter(node -> node.get("type").textValue().equals("user")) .map(node -> node.get("attributes").get("email").textValue()).toList(); - var campainsResponse = patreonService.fetchCampaigns(); - List<String> activeEmails = new ArrayList<>(); - campainsResponse.forEach(campaign -> { - var pledgesResponse = patreonService.fetchPledges(campaign); - pledgesResponse.forEach(pledge -> { - logger.info("Pledge email: {}", pledge); - activeEmails.add(pledge); - }); - }); - activeEmails.forEach(email -> { - var user = userService.getUserByEmail(email); - if (!user.isAnonymous()) { - userService.setPremium(user.getUid(), true); - } - }); - updatedEmails.stream().filter(email -> !activeEmails.contains(email)) - .forEach(deleted -> { - var user = userService.getUserByEmail(deleted); - if (!user.isAnonymous()) { - logger.info("User is not a patron anymore: {}", deleted); - userService.setPremium(user.getUid(), false); - } - }); + patreonService.updateStatus(updatedEmails); } } diff --git a/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java b/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java index 76b38168..4636e9f6 100644 --- a/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java +++ b/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java @@ -32,7 +32,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.InputStream; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/com/juick/www/api/webhooks/VkWebhook.java b/src/main/java/com/juick/www/api/webhooks/VkWebhook.java index 9e4477b1..d69d00be 100644 --- a/src/main/java/com/juick/www/api/webhooks/VkWebhook.java +++ b/src/main/java/com/juick/www/api/webhooks/VkWebhook.java @@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -62,9 +62,9 @@ public class VkWebhook { if (secretKey.equals(secret)) { if (type.startsWith("donut_")) { var vkId = json.get("object").get("user_id").asLong(0); - var userId = userService.getUIDbyVKID(vkId); - if (userId > 0) { - vkService.updatePremiumStatus(userId); + var user = userService.getUserByVKID(vkId); + if (user.isPresent()) { + vkService.updatePremiumStatus(user.get().getUid()); } } return "ok"; diff --git a/src/main/java/com/juick/www/api/xnodeinfo2/Info.java b/src/main/java/com/juick/www/api/xnodeinfo2/Info.java index 2af252ed..adbd5712 100644 --- a/src/main/java/com/juick/www/api/xnodeinfo2/Info.java +++ b/src/main/java/com/juick/www/api/xnodeinfo2/Info.java @@ -17,27 +17,14 @@ package com.juick.www.api.xnodeinfo2; -import com.cliqset.xrd.Link; -import com.cliqset.xrd.XRD; import com.fasterxml.jackson.annotation.JsonView; import com.juick.service.InfoService; import com.juick.www.api.xnodeinfo2.model.*; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.Cacheable; import org.springframework.http.MediaType; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.inject.Inject; -import java.net.URI; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import jakarta.inject.Inject; @RestController public class Info { @@ -53,18 +40,6 @@ public class Info { return infoService.getCurrentNodeInfo("1.0"); } - @GetMapping(value = "/.well-known/nodeinfo", produces = MediaType.APPLICATION_JSON_VALUE) - public XRD getNodeInfoLinks() { - Link nodeinfo = new Link(); - nodeinfo.setRel(URI.create("http://nodeinfo.diaspora.software/ns/schema/2.0")); - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(baseUri); - uriComponentsBuilder.replacePath("/api/nodeinfo/2.0"); - nodeinfo.setHref(uriComponentsBuilder.build().toUri()); - XRD xrd = new XRD(); - xrd.setLinks(Collections.singletonList(nodeinfo)); - return xrd; - } - @GetMapping(value = "/api/nodeinfo/2.0", produces = MediaType.APPLICATION_JSON_VALUE) @JsonView(NodeInfo.NodeInfoView.class) public NodeInfo showNodeInfo() { diff --git a/src/main/java/com/juick/www/controllers/Compat.java b/src/main/java/com/juick/www/controllers/Compat.java index edd7bc29..334213be 100644 --- a/src/main/java/com/juick/www/controllers/Compat.java +++ b/src/main/java/com/juick/www/controllers/Compat.java @@ -30,7 +30,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.view.RedirectView; -import javax.inject.Inject; +import jakarta.inject.Inject; @Controller @Validated diff --git a/src/main/java/com/juick/www/controllers/Help.java b/src/main/java/com/juick/www/controllers/Help.java index ae722594..5754e55f 100644 --- a/src/main/java/com/juick/www/controllers/Help.java +++ b/src/main/java/com/juick/www/controllers/Help.java @@ -29,7 +29,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Locale; import java.util.Objects; diff --git a/src/main/java/com/juick/www/controllers/Settings.java b/src/main/java/com/juick/www/controllers/Settings.java index d95b21b7..11f31efb 100644 --- a/src/main/java/com/juick/www/controllers/Settings.java +++ b/src/main/java/com/juick/www/controllers/Settings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2023, Juick + * Copyright (C) 2008-2024, 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 @@ -23,7 +23,7 @@ import java.util.Locale; import java.util.ResourceBundle; import java.util.stream.IntStream; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.juick.model.User; import com.juick.service.EmailService; @@ -150,14 +150,20 @@ public class Settings { visitor.setCountry(request.getParameter("country")); visitor.setUrl(request.getParameter("url")); visitor.setDescription(request.getParameter("descr")); - String avatarTmpPath = HttpUtils.receiveMultiPartFile(newAvatar, storageService.getTemporaryDirectory()).getHost(); - if (StringUtils.isNotEmpty(avatarTmpPath)) { - storageService.saveAvatar(avatarTmpPath, visitor); - } - if (userService.updateUserInfo(visitor)) { - result = String.format("<p>Your info is updated.</p><p><a href='/%s/'>Back to blog</a>.</p>", visitor.getName()); + try { + String avatarTmpPath = HttpUtils + .receiveMultiPartFile(newAvatar, storageService.getTemporaryDirectory()).getHost(); + if (StringUtils.isNotEmpty(avatarTmpPath)) { + storageService.saveAvatar(avatarTmpPath, visitor); + } + if (userService.updateUserInfo(visitor)) { + result = String.format("<p>Your info is updated.</p><p><a href='/%s/'>Back to blog</a>.</p>", + visitor.getName()); + } + applicationEventPublisher.publishEvent(new UpdateUserEvent(this, visitor)); + } catch (Exception e) { + result = "<p>" + e.getMessage() + ". <a href=\"/settings\">Back</a>.</p>"; } - applicationEventPublisher.publishEvent(new UpdateUserEvent(this, visitor)); break; case "jid-del": // FIXME: stop using ugnich-csv in parameters @@ -175,25 +181,29 @@ public class Settings { } break; case "email-add": - if (!emailService.verifyAddressByCode(visitor.getUid(), request.getParameter("account"))) { - String authCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); - if (emailService.addVerificationCode(visitor.getUid(), request.getParameter("account"), authCode)) { - Session session = Session.getDefaultInstance(System.getProperties()); - try { - MimeMessage message = new MimeMessage(session); - message.setFrom(new InternetAddress("noreply@juick.com")); - message.addRecipient(Message.RecipientType.TO, new InternetAddress(request.getParameter("account"))); - message.setSubject("Juick authorization link"); - message.setText(String.format("Follow link to attach this email to Juick account:\n" + - "http://juick.com/settings?page=auth-email&code=%s\n\n" + - "If you don't know, what this mean - just ignore this mail.\n", authCode)); - Transport.send(message); - result = "<p>Authorization link has been sent to your email. Follow it to proceed.</p>" + - "<p><a href=\"/settings\">Back</a></p>"; + if (!emailService.isValidEmail(request.getParameter("account"))) { + result = "<p>Invalid email. <a href=\"/settings\">Back</a>.</p>"; + } else { + if (!emailService.verifyAddressByCode(visitor.getUid(), request.getParameter("account"))) { + String authCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); + if (emailService.addVerificationCode(visitor.getUid(), request.getParameter("account"), authCode)) { + Session session = Session.getDefaultInstance(System.getProperties()); + try { + MimeMessage message = new MimeMessage(session); + message.setFrom(new InternetAddress("noreply@juick.com")); + message.addRecipient(Message.RecipientType.TO, new InternetAddress(request.getParameter("account"))); + message.setSubject("Juick authorization link"); + message.setText(String.format("Follow link to attach this email to Juick account:\n" + + "https://juick.com/settings?page=auth-email&code=%s\n\n" + + "If you don't know, what this mean - just ignore this mail.\n", authCode)); + Transport.send(message); + result = "<p>Authorization link has been sent to your email. Follow it to proceed.</p>" + + "<p><a href=\"/settings\">Back</a></p>"; - } catch (MessagingException ex) { - logger.error("mail exception", ex); - throw new HttpBadRequestException(); + } catch (MessagingException ex) { + logger.error("mail exception", ex); + throw new HttpBadRequestException(); + } } } } diff --git a/src/main/java/com/juick/www/controllers/SignUp.java b/src/main/java/com/juick/www/controllers/SignUp.java index 50ce6955..dddefef3 100644 --- a/src/main/java/com/juick/www/controllers/SignUp.java +++ b/src/main/java/com/juick/www/controllers/SignUp.java @@ -37,7 +37,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; -import javax.inject.Inject; +import jakarta.inject.Inject; /** * @@ -92,6 +92,7 @@ public class SignUp { model.addAttribute("account", account); model.addAttribute("type", type); model.addAttribute("hash", hash); + model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); return "views/signup"; } diff --git a/src/main/java/com/juick/www/controllers/Site.java b/src/main/java/com/juick/www/controllers/Site.java index 3af6f2c2..c5074547 100644 --- a/src/main/java/com/juick/www/controllers/Site.java +++ b/src/main/java/com/juick/www/controllers/Site.java @@ -45,7 +45,7 @@ import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.view.RedirectView; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -595,7 +595,7 @@ public class Site { fillUserModel(model, visitor, visitor); visitor.setAvatar(webApp.getAvatarWebPath(visitor)); model.addAttribute("title", "Написать"); - model.addAttribute("headers", ""); + model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); model.addAttribute("visitor", visitor); if (body == null) { body = StringUtils.EMPTY; diff --git a/src/main/java/com/juick/www/controllers/SocialLogin.java b/src/main/java/com/juick/www/controllers/SocialLogin.java index 75099e50..510c7d62 100644 --- a/src/main/java/com/juick/www/controllers/SocialLogin.java +++ b/src/main/java/com/juick/www/controllers/SocialLogin.java @@ -53,11 +53,12 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.util.UriComponentsBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -70,7 +71,7 @@ import java.util.stream.Collectors; @Controller public class SocialLogin { - private static final Logger logger = LoggerFactory.getLogger(SocialLogin.class); + private static final Logger logger = LoggerFactory.getLogger("Social"); public static final String AUTH_ERROR = "SocialLogin.AuthenticationError"; @@ -124,12 +125,12 @@ public class SocialLogin { @GetMapping("/_fblogin") protected String doFacebookLogin(HttpServletRequest request, @RequestParam(required = false) String code, @RequestParam(required = false) String state, - @RequestHeader(value = "referer", required = false) String referer, HttpServletResponse response, + HttpServletResponse response, HttpSession session) throws IOException, ExecutionException, InterruptedException { if (StringUtils.isBlank(code)) { String fbstate = UUID.randomUUID().toString(); if (StringUtils.isBlank(state)) { - state = Optional.ofNullable(referer).orElse("https://juick.com/"); + state = UUID.randomUUID().toString(); } userService.addFacebookState(fbstate, state); return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate); @@ -170,10 +171,12 @@ public class SocialLogin { } } if (!existingFacebookUser.get().isBanned()) { - Cookie c = new Cookie("hash", userService.getHashByUID(existingFacebookUser.get().getUid())); - c.setMaxAge(50 * 24 * 60 * 60); - response.addCookie(c); - return "redirect:" + redirectUrl; + var authentication = new RememberMeAuthenticationToken( + ((AbstractRememberMeServices) rememberMeServices).getKey(), + new JuickUser(existingFacebookUser.get()), JuickUser.USER_AUTHORITY); + SecurityContextHolder.getContext().setAuthentication(authentication); + rememberMeServices.loginSuccess(request, response, authentication); + return "redirect:/"; } else { session.setAttribute(SocialLogin.AUTH_ERROR, "User is disabled"); return "redirect:/login"; @@ -188,9 +191,9 @@ public class SocialLogin { @GetMapping("/_twitter") protected String doTwitterLogin(@RequestParam(required = false) String code, - @RequestParam(required = false) String state, - com.juick.model.User user, - HttpServletRequest request) + @RequestParam(required = false) String state, + com.juick.model.User user, + HttpServletRequest request) throws IOException, ExecutionException, InterruptedException { if (StringUtils.isBlank(code)) { @@ -229,14 +232,17 @@ public class SocialLogin { @GetMapping("/_vklogin") protected String doVKLogin(@RequestParam(required = false) String code, @RequestParam(required = false) String state, - @RequestHeader(value = "referer", required = false) String referer, - @CookieValue(required = false) String vkstate, HttpServletResponse response) + @CookieValue(required = false) String vkstate, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session) throws IOException, ExecutionException, InterruptedException { if (StringUtils.isBlank(code)) { vkstate = UUID.randomUUID().toString(); Cookie c = new Cookie("vkstate", vkstate); response.addCookie(c); - return "redirect:" + vkService.getVkAuthService().getAuthorizationUrl(vkstate); + var redirect = "redirect:" + vkService.getVkAuthService().getAuthorizationUrl(vkstate); + return redirect; } if (StringUtils.isBlank(vkstate) || !vkstate.equals(state)) { @@ -265,13 +271,19 @@ public class SocialLogin { } long vkID = NumberUtils.toLong(jsonUser.id(), 0); - int uid = userService.getUIDbyVKID(vkID); - if (uid > 0) { - userService.updateVkUser(vkID, token.getAccessToken(), vkName, vkLink); - Cookie c = new Cookie("hash", userService.getHashByUID(uid)); - c.setMaxAge(50 * 24 * 60 * 60); - response.addCookie(c); - return "redirect:/" + Optional.ofNullable(referer).orElse(StringUtils.EMPTY); + var user = userService.getUserByVKID(vkID); + if (user.isPresent()) { + if (!user.get().isBanned()) { + var authentication = new RememberMeAuthenticationToken( + ((AbstractRememberMeServices) rememberMeServices).getKey(), + new JuickUser(user.get()), JuickUser.USER_AUTHORITY); + SecurityContextHolder.getContext().setAuthentication(authentication); + rememberMeServices.loginSuccess(request, response, authentication); + return "redirect:/"; + } else { + session.setAttribute(SocialLogin.AUTH_ERROR, "User is disabled"); + return "redirect:/login"; + } } else { String loginhash = UUID.randomUUID().toString(); if (!userService.createVKUser(vkID, loginhash, token.getAccessToken(), vkName, vkLink)) { @@ -290,8 +302,7 @@ public class SocialLogin { @GetMapping("/_tglogin") public String doDurovLogin(@RequestParam Map<String, String> params, @RequestParam String hash, - @RequestHeader(value = "referer", required = false) String referer, - HttpServletRequest request, HttpServletResponse response) { + HttpServletRequest request, HttpServletResponse response, HttpSession session) { String dataCheckString = params.entrySet().stream().filter(p -> !p.getKey().equals("hash")) .sorted(Map.Entry.comparingByKey()).map(p -> p.getKey() + "=" + p.getValue()) .collect(Collectors.joining("\n")); @@ -301,14 +312,19 @@ public class SocialLogin { long tgUser = Long.parseLong(params.get("id")); var user = userService.getUserByTelegramId(tgUser); if (user.isPresent()) { - var authentication = new RememberMeAuthenticationToken( - ((AbstractRememberMeServices) rememberMeServices).getKey(), - new JuickUser(user.get()), JuickUser.USER_AUTHORITY); - SecurityContextHolder.getContext().setAuthentication(authentication); - rememberMeServices.loginSuccess(request, response, authentication); - return "redirect:" + Optional.ofNullable(referer).orElse(StringUtils.EMPTY); + if (!user.get().isBanned()) { + var authentication = new RememberMeAuthenticationToken( + ((AbstractRememberMeServices) rememberMeServices).getKey(), + new JuickUser(user.get()), JuickUser.USER_AUTHORITY); + SecurityContextHolder.getContext().setAuthentication(authentication); + rememberMeServices.loginSuccess(request, response, authentication); + return "redirect:/"; + } else { + session.setAttribute(SocialLogin.AUTH_ERROR, "User is disabled"); + return "redirect:/login"; + } } else { - String username = StringUtils.defaultString(params.get("username"), params.get("first_name")); + String username = Objects.toString(params.get("username"), params.get("first_name")); List<Long> chats = telegramService.getAnonymous(); if (!chats.contains(tgUser)) { logger.info("added chat with {}", username); @@ -347,9 +363,11 @@ public class SocialLogin { com.juick.model.User user = userService.getUserByEmail(email.get()); if (!user.isAnonymous()) { if (!user.isBanned()) { - Cookie c = new Cookie("hash", userService.getHashByUID(user.getUid())); - c.setMaxAge(50 * 24 * 60 * 60); - response.addCookie(c); + var authentication = new RememberMeAuthenticationToken( + ((AbstractRememberMeServices) rememberMeServices).getKey(), + new JuickUser(user), JuickUser.USER_AUTHORITY); + SecurityContextHolder.getContext().setAuthentication(authentication); + rememberMeServices.loginSuccess(request, response, authentication); return "redirect:/"; } else { session.setAttribute(SocialLogin.AUTH_ERROR, "User is disabled"); diff --git a/src/main/java/com/juick/www/filters/AnythingFilter.java b/src/main/java/com/juick/www/filters/AnythingFilter.java index 91422807..463374ee 100644 --- a/src/main/java/com/juick/www/filters/AnythingFilter.java +++ b/src/main/java/com/juick/www/filters/AnythingFilter.java @@ -33,7 +33,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponents; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; @Component diff --git a/src/main/java/com/juick/www/rss/Feeds.java b/src/main/java/com/juick/www/rss/Feeds.java index f19f1505..a0608172 100644 --- a/src/main/java/com/juick/www/rss/Feeds.java +++ b/src/main/java/com/juick/www/rss/Feeds.java @@ -30,7 +30,7 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; /** diff --git a/src/main/java/com/juick/www/rss/MessagesView.java b/src/main/java/com/juick/www/rss/MessagesView.java index cb4eea2e..b6b5e5a1 100644 --- a/src/main/java/com/juick/www/rss/MessagesView.java +++ b/src/main/java/com/juick/www/rss/MessagesView.java @@ -24,7 +24,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.juick.model.Attachment; import com.juick.model.Message; diff --git a/src/main/java/com/juick/www/rss/RepliesView.java b/src/main/java/com/juick/www/rss/RepliesView.java index 80548a79..47fb46b6 100644 --- a/src/main/java/com/juick/www/rss/RepliesView.java +++ b/src/main/java/com/juick/www/rss/RepliesView.java @@ -40,7 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.view.feed.AbstractRssFeedView; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.net.URI; import java.net.URISyntaxException; import java.util.List; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fe206251..21c9f17a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,6 +7,9 @@ spring.jackson.serialization.write-dates-as-timestamps=false spring.jackson.serialization.write-empty-json-arrays=true spring.h2.console.enabled=false spring.datasource.generate-unique-name=false +spring.jpa.generate-ddl=false +spring.jpa.hibernate.ddl-auto=none +spring.jpa.open-in-view=false spring.flyway.enabled=false spring.sql.init.platform=h2 spring.cache.type=simple diff --git a/src/main/resources/data-h2.sql b/src/main/resources/data-h2.sql index 818575e9..72615485 100644 --- a/src/main/resources/data-h2.sql +++ b/src/main/resources/data-h2.sql @@ -1,6 +1,7 @@ INSERT INTO users(id, nick, passw) VALUES(0, 'Anonymous', 'password'); INSERT INTO users(id, nick, passw) VALUES(2, 'juick', 'password'); INSERT INTO users(id, nick, passw) VALUES(5, 'archive', 'password'); +INSERT INTO telegram(user_id, tg_id) VALUES(5, '1'); INSERT INTO reactions (like_id, description) VALUES (1, 'like'); INSERT INTO reactions (like_id, description) VALUES (2, 'love'); INSERT INTO reactions (like_id, description) VALUES (3, 'lol'); diff --git a/src/main/resources/db/migration/V1.50__email_created_at.sql b/src/main/resources/db/migration/V1.50__email_created_at.sql new file mode 100644 index 00000000..aa5b3f4b --- /dev/null +++ b/src/main/resources/db/migration/V1.50__email_created_at.sql @@ -0,0 +1 @@ +ALTER TABLE emails ADD COLUMN created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP diff --git a/src/main/resources/db/migration/V1.51__cast_users_lang.sql b/src/main/resources/db/migration/V1.51__cast_users_lang.sql new file mode 100644 index 00000000..bb65a157 --- /dev/null +++ b/src/main/resources/db/migration/V1.51__cast_users_lang.sql @@ -0,0 +1,2 @@ +DROP CAST IF EXISTS (varchar AS public.users_lang); +CREATE CAST (varchar AS public.users_lang) WITH INOUT AS IMPLICIT; diff --git a/src/main/resources/schema-h2.sql b/src/main/resources/schema-h2.sql index 5fd7fff8..dc705727 100644 --- a/src/main/resources/schema-h2.sql +++ b/src/main/resources/schema-h2.sql @@ -233,7 +233,8 @@ ALTER TABLE "PUBLIC"."USERSINFO" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_BB6" PRIMAR CREATE MEMORY TABLE "PUBLIC"."EMAILS"( "USER_ID" INTEGER NOT NULL, "EMAIL" VARCHAR_IGNORECASE(255) NOT NULL, - "SUBSCR_HOUR" TINYINT DEFAULT NULL + "SUBSCR_HOUR" TINYINT DEFAULT NULL, + "CREATED_AT" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL ); ALTER TABLE "PUBLIC"."EMAILS" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_7A" PRIMARY KEY("EMAIL"); CREATE MEMORY TABLE "PUBLIC"."LOGINS"( diff --git a/src/main/resources/schema-mysql.sql b/src/main/resources/schema-mysql.sql index 0e035d5e..813b9e3d 100644 --- a/src/main/resources/schema-mysql.sql +++ b/src/main/resources/schema-mysql.sql @@ -72,6 +72,7 @@ CREATE TABLE `emails` ( `user_id` int(10) unsigned NOT NULL, `email` char(128) NOT NULL, `subscr_hour` tinyint(4) DEFAULT NULL, + `created_at` timestamp NOT NULL DEFAULT current_timestamp(), KEY `email` (`email`) USING HASH ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/src/main/resources/schema-sqlite.sql b/src/main/resources/schema-sqlite.sql index 74ba28b6..54160c80 100644 --- a/src/main/resources/schema-sqlite.sql +++ b/src/main/resources/schema-sqlite.sql @@ -32,6 +32,7 @@ CREATE TABLE emails ( user_id INTEGER NOT NULL, email character varying(128) NOT NULL, subscr_hour smallint, + created_at DEFAULT (strftime('%s','now') || substr(strftime('%f','now'),4)) NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE facebook ( diff --git a/src/main/resources/schema-sqlserver.sql b/src/main/resources/schema-sqlserver.sql index 947e5403..9ff21ed2 100644 --- a/src/main/resources/schema-sqlserver.sql +++ b/src/main/resources/schema-sqlserver.sql @@ -56,6 +56,7 @@ CREATE TABLE emails ( user_id bigint NOT NULL, email character varying(128) NOT NULL, subscr_hour smallint, + created_at datetimeoffset DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE facebook ( diff --git a/src/main/resources/templates/layouts/default.html b/src/main/resources/templates/layouts/default.html index 3b936e07..b7975b42 100644 --- a/src/main/resources/templates/layouts/default.html +++ b/src/main/resources/templates/layouts/default.html @@ -2,34 +2,34 @@ <html prefix="og: http://ogp.me/ns#"> <head> - <meta charset="utf-8" /> - <meta name="ermp-site-verification" value="6714D81C-9EEF-4283-A726-166680B6ABC8" /> + <meta charset="utf-8"> + <meta name="ermp-site-verification" value="6714D81C-9EEF-4283-A726-166680B6ABC8"> <script type="text/javascript" src="{{ beans.webApp.scriptsUrl }}"></script> - <link rel="stylesheet" type="text/css" href="{{ beans.webApp.styleUrl }}" /> + <link rel="stylesheet" type="text/css" href="{{ beans.webApp.styleUrl }}"> {% block headers %} {{ headers | default('') | raw }} {% endblock %} <title>{{ title | default('Juick') }}</title> - <meta property="og:type" content="{{ ogtype | default('website') }}" /> - <meta property="fb:app_id" content="130568668304" /> - <meta property="fb:pages" content="270127573154958" /> - <meta name="viewport" content="width=device-width,initial-scale=1" /> - <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" /> - <meta name="theme-color" content="#383838" media="(prefers-color-scheme: dark)" /> - <meta name="apple-mobile-web-app-capable" content="yes" /> - <link rel="apple-touch-icon" sizes="57x57" href="//i.juick.com/apple-icon-57x57.png" /> - <link rel="apple-touch-icon" sizes="60x60" href="//i.juick.com/apple-icon-60x60.png" /> - <link rel="apple-touch-icon" sizes="72x72" href="//i.juick.com/apple-icon-72x72.png" /> - <link rel="apple-touch-icon" sizes="76x76" href="//i.juick.com/apple-icon-76x76.png" /> - <link rel="apple-touch-icon" sizes="114x114" href="//i.juick.com/apple-icon-114x114.png" /> - <link rel="apple-touch-icon" sizes="120x120" href="//i.juick.com/apple-icon-120x120.png" /> - <link rel="apple-touch-icon" sizes="144x144" href="//i.juick.com/apple-icon-144x144.png" /> - <link rel="apple-touch-icon" sizes="152x152" href="//i.juick.com/apple-icon-152x152.png" /> - <link rel="apple-touch-icon" sizes="180x180" href="//i.juick.com/apple-icon-180x180.png" /> - <link rel="icon" type="image/png" sizes="32x32" href="//i.juick.com/favicon-32x32.png" /> - <link rel="icon" type="image/png" sizes="96x96" href="//i.juick.com/favicon-96x96.png" /> - <link rel="icon" type="image/png" sizes="16x16" href="//i.juick.com/favicon-16x16.png" /> - <link rel="manifest" href="//i.juick.com/manifest.json" /> + <meta property="og:type" content="{{ ogtype | default('website') }}"> + <meta property="fb:app_id" content="130568668304"> + <meta property="fb:pages" content="270127573154958"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> + <meta name="theme-color" content="#383838" media="(prefers-color-scheme: dark)"> + <meta name="web-app-capable" content="yes"> + <link rel="apple-touch-icon" sizes="57x57" href="//i.juick.com/apple-icon-57x57.png"> + <link rel="apple-touch-icon" sizes="60x60" href="//i.juick.com/apple-icon-60x60.png"> + <link rel="apple-touch-icon" sizes="72x72" href="//i.juick.com/apple-icon-72x72.png"> + <link rel="apple-touch-icon" sizes="76x76" href="//i.juick.com/apple-icon-76x76.png"> + <link rel="apple-touch-icon" sizes="114x114" href="//i.juick.com/apple-icon-114x114.png"> + <link rel="apple-touch-icon" sizes="120x120" href="//i.juick.com/apple-icon-120x120.png"> + <link rel="apple-touch-icon" sizes="144x144" href="//i.juick.com/apple-icon-144x144.png"> + <link rel="apple-touch-icon" sizes="152x152" href="//i.juick.com/apple-icon-152x152.png"> + <link rel="apple-touch-icon" sizes="180x180" href="//i.juick.com/apple-icon-180x180.png"> + <link rel="icon" type="image/png" sizes="32x32" href="//i.juick.com/favicon-32x32.png"> + <link rel="icon" type="image/png" sizes="96x96" href="//i.juick.com/favicon-96x96.png"> + <link rel="icon" type="image/png" sizes="16x16" href="//i.juick.com/favicon-16x16.png"> + <link rel="manifest" href="//i.juick.com/juick.webmanifest"> </head> <body id="body"> @@ -38,12 +38,13 @@ <div id="content_wrapper"> <aside id="column"> <div id="sidebar_wrapper"> - {% block column %} - {% endblock %} + {% block column %} + {% endblock %} </div> </aside> {% flush %} - <section id="content" {% if msg | default('') is not empty %}data-mid="{{ msg.mid }}" {% endif %} class="content--top"> + <section id="content" {% if msg | default('') is not empty %}data-mid="{{ msg.mid }}" {% endif %} + class="content--top"> {% block content %} {% endblock %} </section> diff --git a/src/main/resources/templates/views/thread.html b/src/main/resources/templates/views/thread.html index 9aad1ce5..ab751294 100644 --- a/src/main/resources/templates/views/thread.html +++ b/src/main/resources/templates/views/thread.html @@ -116,7 +116,7 @@ {% if rec.uri.toString() is empty %} <a href="/{{ rec.name }}/">@{{ rec.name }}</a>{% if loop.index < (loop.length - 1) %}, {% endif %} {% else %} - <a href="{{ rec.uri }}">@{{ rec.name }}</a>{% if loop.index < (loop.length - 1) %}, {% endif %} + <a href="{{ rec.uri }}">@{{ rec.name }}<span class="dimmed">@{{ rec.uri.host }}</span></a>{% if loop.index < (loop.length - 1) %}, {% endif %} {% endif %} {% endfor %} </div> diff --git a/src/test/java/com/juick/MessageTest.java b/src/test/java/com/juick/MessageTest.java index 14d6175a..e750f652 100644 --- a/src/test/java/com/juick/MessageTest.java +++ b/src/test/java/com/juick/MessageTest.java @@ -213,8 +213,8 @@ public class MessageTest { assertThat(MessageUtils.formatMessage(msg), is("<blockquote>quote</blockquote>message")); String brokenComment = "<!-- read next"; assertThat(MessageUtils.formatMessage(brokenComment), is("<!-- read next")); - String url = "[ya](http://ya.ru)"; - assertThat(MessageUtils.formatMessage(url), is("<a href=\"http://ya.ru\" rel=\"nofollow\">ya</a>")); + String url = "test [ya](https://juick.com/a/1)? hh"; + assertThat(MessageUtils.formatMessage(url), is("test <a href=\"https://juick.com/a/1\" rel=\"nofollow\">ya</a>? hh")); String complexMessage = "У футболистов нет мозгов. Что в России, что в Беларуси:\n" + "\n" + ">Отец футболиста Лухвича, объехавшего пробку по тротуару: «Сына задержали, Infiniti арестовали» https://auto.onliner.by/2019/01/23/probka-9\n" + @@ -238,4 +238,16 @@ public class MessageTest { testMessage.setTags(MessageUtils.parseTags("NSFW test")); assertThat(MessageUtils.isSensitive(testMessage), is(true)); } + @Test + public void stripHashesTest() { + var safeMessage = "And that is a [odd](https://juick.com/a/1)? aaapaa"; + var nonsafeMessage = "[Here is my link](https://juick.com?hash=12345)"; + var filteredMessage = "[Here is my link](https://juick.com)"; + var pidginMessage = "I'm very smart to post my login url there<https://juick.com/settings?hash=VTYZkKV8FWkmu6g1>"; + var westernMessage = "«Please, verify your account at https://juick.com/settings?hash=12345»"; + assertThat(MessageUtils.stripNonSafeUrls(safeMessage), is(safeMessage)); + assertThat(MessageUtils.stripNonSafeUrls(nonsafeMessage), is(filteredMessage)); + assertThat(MessageUtils.stripNonSafeUrls(pidginMessage), not(containsString("VTYZkKV8FWkmu6g1"))); + assertThat(MessageUtils.stripNonSafeUrls(westernMessage), containsString("»")); + } } diff --git a/src/test/java/com/juick/server/MockDeleteListener.java b/src/test/java/com/juick/server/MockDeleteListener.java index d060d865..20ee99bf 100644 --- a/src/test/java/com/juick/server/MockDeleteListener.java +++ b/src/test/java/com/juick/server/MockDeleteListener.java @@ -20,7 +20,7 @@ package com.juick.server; import com.juick.service.activities.DeleteUserEvent; import org.springframework.context.ApplicationListener; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; public class MockDeleteListener implements ApplicationListener<DeleteUserEvent> { @Override diff --git a/src/test/java/com/juick/server/MockNotificationListener.java b/src/test/java/com/juick/server/MockNotificationListener.java index fd91c3fd..ee9f1696 100644 --- a/src/test/java/com/juick/server/MockNotificationListener.java +++ b/src/test/java/com/juick/server/MockNotificationListener.java @@ -20,7 +20,7 @@ package com.juick.server; import com.juick.service.component.SystemEvent; import org.springframework.context.ApplicationListener; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; public class MockNotificationListener implements ApplicationListener<SystemEvent> { @Override diff --git a/src/test/java/com/juick/server/MockUpdateListener.java b/src/test/java/com/juick/server/MockUpdateListener.java index 08742b12..21c9b673 100644 --- a/src/test/java/com/juick/server/MockUpdateListener.java +++ b/src/test/java/com/juick/server/MockUpdateListener.java @@ -20,7 +20,7 @@ package com.juick.server; import com.juick.service.activities.UpdateEvent; import org.springframework.context.ApplicationListener; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; public class MockUpdateListener implements ApplicationListener<UpdateEvent> { @Override diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index d61725e6..84034428 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2019, Juick + * Copyright (C) 2008-2024, 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 @@ -30,6 +30,10 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.github.scribejava.apis.AppleClientSecretGenerator; import com.jayway.jsonpath.JsonPath; import com.juick.*; +import com.juick.data.MessagesRepository; +import com.juick.data.UsersRepository; +import com.juick.data.entities.MessageEntity; +import com.juick.data.entities.ReplyEntity; import com.juick.model.Tag; import com.juick.model.*; import com.juick.server.MockNotificationListener; @@ -38,7 +42,10 @@ import com.juick.service.*; import com.juick.service.activities.UpdateEvent; import com.juick.service.component.SystemEvent; import com.juick.test.util.MockUtils; -import com.juick.util.*; +import com.juick.util.DateFormattersHolder; +import com.juick.util.HttpUtils; +import com.juick.util.MessageUtils; +import com.juick.util.WebUtils; import com.juick.util.formatters.PlainTextFormatter; import com.juick.www.WebApp; import com.juick.www.ad.models.Site; @@ -111,7 +118,7 @@ import rocks.xmpp.core.session.XmppSession; import rocks.xmpp.core.session.XmppSessionConfiguration; import ru.sape.SapePageLinks; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -119,9 +126,6 @@ import java.io.*; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.*; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.spec.InvalidKeySpecException; import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -196,6 +200,10 @@ public class ServerTests { @Inject private ProfileUriBuilder profileUriBuilder; @Inject + private MessagesRepository messagesRepository; + @Inject + private UsersRepository usersRepository; + @Inject private WebApp webApp; @Value("${ap_base_uri:http://localhost:8080/}") private String baseUri; @@ -278,6 +286,12 @@ public class ServerTests { .orElseThrow(IllegalStateException::new); freefd = userService.createUser(freefdName, freefdPassword) .orElseThrow(IllegalStateException::new); + ugnich = makeUserVerified(ugnich); + freefd = makeUserVerified(freefd); + assertThat(ugnich.getLanguage(), is("__")); + ugnich.setLanguage("en"); + userService.updateLanguage(ugnich); + assertThat(ugnich.getLanguage(), is("en")); webClient.getOptions().setJavaScriptEnabled(false); webClient.getOptions().setCssEnabled(false); isSetUp = true; @@ -660,8 +674,9 @@ public class ServerTests { new TypeReference<>() { }); assertThat(users.size(), is(1)); - assertThat(users.get(0).getTokens().size(), is(1)); - assertThat(users.get(0).getTokens().get(0).token(), equalTo(token)); + // ugnich has durov and apns token + assertThat(users.get(0).getTokens().size(), is(2)); + assertThat(users.get(0).getTokens().stream().filter(t -> t.type().equals("apns")).findFirst().get().token(), equalTo(token)); } @Test @@ -775,6 +790,7 @@ public class ServerTests { public void protocolTests() throws Exception { String tmpDir = storageService.getTemporaryDirectory(); User user = userService.createUser("me", "secret").orElseThrow(IllegalStateException::new); + user = makeUserVerified(user); Tag yo = tagService.getTag("yo", true); Message msg = commandsManager .processCommand(user, "*yo yoyo", @@ -846,6 +862,7 @@ public class ServerTests { last = list.get(0); assertThat(last, equalTo(reply.getCreated())); assertEquals(2, reply.getReplyto()); + readerUser = makeUserVerified(readerUser); assertThat(commandsManager.processCommand(readerUser, "#" + mid + " *yo *there", emptyUri).getText(), startsWith("Reply posted")); assertEquals("Tags are updated", @@ -900,12 +917,6 @@ public class ServerTests { assertThat(result.getNewMessage(), is(Optional.empty())); assertThat(result.getText(), is("Tags are NOT updated (5 tags maximum?)")); result = commandsManager.processCommand(user, - "I'm very smart to post my login url there" - + "<https://juick.com/settings?hash=VTYZkKV8FWkmu6g1>", - emptyUri); - assertThat(result.getNewMessage().isPresent(), is(true)); - assertFalse(result.getNewMessage().get().getText().contains("VTYZkKV8FWkmu6g1")); - result = commandsManager.processCommand(user, "*корм *juick_ppl *рационализм *? *мюсли а сколько микроморт в дневной порции сверхмюслей?", emptyUri); assertThat(result.getNewMessage().isPresent(), is(true)); @@ -1230,8 +1241,9 @@ public class ServerTests { CommandResult result = commandsManager.processCommand(ugnich, "freefd - dick", emptyUri); int mid = result.getNewMessage().get().getMid(); commandsManager.processCommand(freefd, String.format("#%d ugnich - dick too", mid), emptyUri); - commandsManager.processCommand(serviceUser, String.format("#%d/1 ban for a hour!", mid), emptyUri); - commandsManager.processCommand(serviceUser, + var juick = makeUserVerified(serviceUser); + commandsManager.processCommand(juick, String.format("#%d/1 ban for a hour!", mid), emptyUri); + commandsManager.processCommand(juick, String.format("#%d freefd is here but it is hidden from you", mid), emptyUri); assertThat(messagesService.getMessage(mid).get().getReplies(), is(3)); @@ -1246,7 +1258,7 @@ public class ServerTests { mockMvc.perform(get("/api/thread").with(httpBasic(ugnichName, ugnichPassword)).param("mid", String.valueOf(mid))) .andExpect(jsonPath("$[0].replies", is(1))); - commandsManager.processCommand(serviceUser, String.format("#%d/4 mmm?!", mid), emptyUri); + commandsManager.processCommand(juick, String.format("#%d/4 mmm?!", mid), emptyUri); assertThat(messagesService.getMessage(mid).get().getReplies(), is(5)); replies = messagesService.getReplies(ugnich, mid); reply = messagesService.getReply(mid, 5); @@ -1264,8 +1276,8 @@ public class ServerTests { int freefdMsg = messagesService.createMessage(freefd.getUid(), "sux", null, Set.of(tag)); assertThat(messagesService.getTag(tag.getId(), freefd.getUid(), 0, 10).size(), is(1)); assertThat(messagesService.getTag(tag.getId(), ugnich.getUid(), 0, 10).size(), is(0)); - messagesService.recommendMessage(freefdMsg, serviceUser.getUid()); - assertThat(messagesService.getUserBlogWithRecommendations(serviceUser, ugnich, 0, 0) + messagesService.recommendMessage(freefdMsg, juick.getUid()); + assertThat(messagesService.getUserBlogWithRecommendations(juick, ugnich, 0, 0) .contains(freefdMsg), is(false)); commandsManager.processCommand(ugnich, "BL @freefd", emptyUri); @@ -1516,8 +1528,8 @@ public class ServerTests { jsonMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); String jsonUser = jsonMapper.writeValueAsString(yyy); Map<String, Object> user = JsonPath.read(jsonUser, "$"); - // only uid, name and uri - assertThat(user.keySet().size(), is(3)); + // only uid, name, lang and uri + assertThat(user.keySet().size(), is(4)); JAXBContext context = JAXBContext.newInstance(User.class); Marshaller m = context.createMarshaller(); @@ -1591,14 +1603,14 @@ public class ServerTests { assertThat(CollectionUtils.isEqualCollection( messagesService.getMessagesRecommendations(monstreek.getUid(), Collections.singletonList(mid)).stream() .map(Pair::getRight).map(User::getName).toList(), - Arrays.asList("ermine", "pogo", "test@example.com")), is(true)); + Arrays.asList("ermine", "pogo", "test")), is(true)); privacyQueriesService.blacklistUser(userService.getUserByName("monstreek"), userService.getUserByName("pogo")); assertThat(messagesService.getMessage(mid).get().getRecommendations().size(), is(3)); assertThat(CollectionUtils.isEqualCollection( messagesService.getMessagesRecommendations(monstreek.getUid(), Collections.singletonList(mid)).stream() .map(Pair::getRight).map(User::getName).toList(), - Arrays.asList("ermine", "test@example.com")), is(true)); + Arrays.asList("ermine", "test")), is(true)); jdbcTemplate.execute("DELETE FROM favorites"); } @@ -1629,19 +1641,6 @@ public class ServerTests { } @Test - public void accountUrlShouldBeExposedOverWebfinger() throws Exception { - mockMvc.perform(get("/.well-known/webfinger?resource=acct:ugnich@" + webDomain) - .accept("application/jrd+json")).andExpect(status().isOk()) - .andExpect(jsonPath("$.subject", is("acct:ugnich@" + webDomain))) - .andExpect(jsonPath("$.links", hasSize(1))) - .andExpect(jsonPath("$.links[0].href", is(baseUri + "u/ugnich"))); - mockMvc.perform(get("/.well-known/webfinger?resource=acct:durov@" + webDomain)) - .andExpect(status().isNotFound()); - mockMvc.perform(get("/.well-known/webfinger?resource=acct:@" + webDomain)) - .andExpect(status().isNotFound()); - } - - @Test public void userProfileAndBlogShouldBeExposedAsActivityStream() throws Exception { ClassPathResource defaultAvatar = new ClassPathResource("static/av-96.png"); String hash = DigestUtils.md5DigestAsHex(IOUtils.toByteArray(defaultAvatar.getInputStream())); @@ -2078,14 +2077,6 @@ public class ServerTests { } @Test - public void hostmeta() throws Exception { - MvcResult result = mockMvc.perform(get("/.well-known/host-meta")).andExpect(status().isOk()) - .andReturn(); - String xrd = result.getResponse().getContentAsString(); - result = mockMvc.perform(get("/.well-known/x-nodeinfo2")).andExpect(status().isOk()).andReturn(); - } - - @Test public void pms() throws Exception { jdbcTemplate.execute("DELETE FROM pm"); jdbcTemplate.execute("DELETE FROM bl_users"); @@ -2110,12 +2101,14 @@ public class ServerTests { } @Test + @Disabled("FIXME: rewrite signup flow") public void signupTest() throws Exception { emailService.addVerificationCode(null, "demo@email.com", "123456"); - MvcResult result = mockMvc.perform(post("/api/signup").param("username", "testuser") + MvcResult result = mockMvc.perform(post("/signup").param("username", "testuser") + .param("type", "email") .param("password", "demopassword").param("verificationCode", "123456")) - .andExpect(status().isOk()) - .andReturn(); + .andExpect(status().isOk()) + .andReturn(); User testuser = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); assertThat(testuser.getName(), is("testuser")); } @@ -2167,19 +2160,30 @@ public class ServerTests { assertThat(top.size(), is(1)); } + private User makeUserVerified(User user) { + var id = System.currentTimeMillis(); + telegramService.createTelegramUser(id, user.getName()); + var hash = userService.getSignUpHashByTelegramID(id, user.getName()); + userService.setTelegramUser(hash, user.getUid()); + return userService.getUserByName(user.getName()); + } + @Test public void verifiedUsersTest() { - assertThat(userService.getUserByName("ugnich").isVerified(), is(false)); - jdbcTemplate.update("INSERT INTO telegram(user_id, tg_id, tg_name) VALUES(?, ?, ?)", ugnich.getUid(), - 100001866137681L, "tg_test"); - assertThat(userService.canDeleteTelegramUser(userService.getUserByName("ugnich")), is(false)); + var user = userService.createUser("new_unverified_user", "evil").orElseThrow(); + assertThat(userService.getUserByName(user.getName()).isVerified(), is(false)); + user = makeUserVerified(user); + assertThat(userService.getUserByName(user.getName()).isVerified(), is(true)); + assertThat(userService.canDeleteTelegramUser(userService.getUserByName(user.getName())), is(false)); userService.addFacebookState("12345", "http://localhost"); - userService.createFacebookUser(12345, "12345", "5678", "ugnich"); - userService.setFacebookUser("12345", ugnich.getUid()); - assertThat(userService.getUserByName("ugnich").isVerified(), is(true)); - assertThat(userService.canDeleteTelegramUser(userService.getUserByName("ugnich")), is(true)); + userService.createFacebookUser(12345, "12345", "5678", user.getName()); + userService.setFacebookUser("12345", user.getUid()); + assertThat(userService.canDeleteTelegramUser(userService.getUserByName(user.getName())), is(true)); jdbcTemplate.update("DELETE FROM facebook"); - assertThat(userService.canDeleteTelegramUser(userService.getUserByName("ugnich")), is(false)); + assertThat(userService.canDeleteTelegramUser(userService.getUserByName(user.getName())), is(false)); + assertThat(userService.getUserByName(user.getName()).isVerified(), is(true)); + telegramService.deleteTelegramUser(user.getUid()); + assertThat(userService.getUserByName(user.getName()).isVerified(), is(false)); } @Test @@ -2298,27 +2302,6 @@ public class ServerTests { } @Test - public void nodeinfo() throws Exception { - MvcResult nodeinfoXRD = mockMvc - .perform(get("/.well-known/nodeinfo").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()).andReturn(); - JsonNode node = jsonMapper.readTree(nodeinfoXRD.getResponse().getContentAsString()); - assertThat(node.get("links"), notNullValue()); - String nodeinfoUrl = node.get("links").get(0).get("href").textValue(); - MvcResult nodeinfoData = mockMvc.perform(get(nodeinfoUrl).contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()).andReturn(); - JsonNode nodeinfo = jsonMapper.readTree(nodeinfoData.getResponse().getContentAsString()); - assertThat(nodeinfo.get("software"), notNullValue()); - assertThat(nodeinfo.get("server"), nullValue()); - MvcResult xnodeinfoData = mockMvc - .perform(get("/.well-known/x-nodeinfo2").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()).andReturn(); - JsonNode xnodeinfo = jsonMapper.readTree(xnodeinfoData.getResponse().getContentAsString()); - assertThat(xnodeinfo.get("server"), notNullValue()); - assertThat(xnodeinfo.get("software"), nullValue()); - } - - @Test public void anonymousUserFromZero() { User user = userService.getUserByUID(0).orElse(AnonymousUser.INSTANCE); assertThat(user.isAnonymous(), is(true)); @@ -2696,8 +2679,51 @@ public class ServerTests { @Test public void textTruncationShouldNotBreakEmojis() { var text = "Так ты же написал, чтоб я сам поправил отступ \uD83D\uDE00\n"; - var expected = "Так ты же написал, чтоб я сам поправил отступ"; - var truncated = PlainTextFormatter.truncateText(text, 47); + var expected = "Так ты же написал, чтоб я сам поправил отступ …"; + var truncated = StringUtils.abbreviate(text, "…", 47); assertThat(truncated, is(expected)); } + @Test + @Transactional + public void jpaMessageTests() { + var user = usersRepository.findByName(ugnichName); + var message = new MessageEntity(); + message.setUser(user); + message.setText("Hello, JPA!"); + message.setTimestamp(Instant.now()); + message = messagesRepository.save(message); + var msg = messagesService.getMessage(message.getId()); + assertThat(msg.get().getText(), is(notNullValue())); + assertThat(msg.get().getText(), is (message.getText())); + } + @Test + @Transactional + public void jpaReplyTests() { + var mid = messagesService.createMessage(ugnich.getUid(), "new message", null, Set.of()); + var rid = messagesService.createReply(mid, 0, ugnich, "reply 1", null); + var message = messagesRepository.getReferenceById(mid); + var replies = message.getReplies(); + assertThat(replies.size(), is(1)); + } + @Test + @Transactional + public void unsupportedMediaShouldBeHandledCorrectly() throws Exception { + ClassPathResource newMedia = new ClassPathResource("sample1.dng"); + byte[] newMediaData = IOUtils.toByteArray(newMedia.getInputStream()); + var response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/post") + .file(new MockMultipartFile("attach", "sample1.dng", "image/dng", newMediaData)) + .param("body", "test") + .with(httpBasic(freefdName, freefdPassword))).andExpect(status().isBadRequest()).andReturn(); + var result = jsonMapper.readValue(response.getResponse().getContentAsString(), CommandResult.class); + assertThat(result.getText(), is("Wrong file type: tif")); + var r = commandsManager.processCommand(freefd, "tst", emptyUri); + response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/comment") + .file(new MockMultipartFile("attach", "sample1.dng", "image/dng", newMediaData)) + .param("body", "test") + .param("mid", String.valueOf(r.getNewMessage().get().getMid())) + .with(httpBasic(freefdName, freefdPassword))).andExpect(status().isBadRequest()).andReturn(); + result = jsonMapper.readValue(response.getResponse().getContentAsString(), CommandResult.class); + assertThat(result.getText(), is("Wrong file type: tif")); + + } } diff --git a/src/test/resources/application-sqlite.yml b/src/test/resources/application-sqlite.yml index 9ffa0833..60cd1392 100644 --- a/src/test/resources/application-sqlite.yml +++ b/src/test/resources/application-sqlite.yml @@ -8,4 +8,5 @@ spring: init: platform: sqlite mode: always - + jpa: + database-platform: org.hibernate.community.dialect.SQLiteDialect diff --git a/src/test/resources/sample1.dng b/src/test/resources/sample1.dng Binary files differnew file mode 100644 index 00000000..e732e009 --- /dev/null +++ b/src/test/resources/sample1.dng |