Commit 12557874 authored by Benoit Lavenier's avatar Benoit Lavenier

[enh] Add share URL, with open graph tag - fix issue #35

parent 076c4fb3
[submodule "duniter4j-cesium/src/main/cesium"]
path = duniter4j-cesium/src/main/cesium
url = git://github.com/duniter/cesium.git
[submodule "duniter4j-elasticsearch/src/main/resources/cities"]
path = duniter4j-elasticsearch/src/main/resources/cities
url = git://github.com/David-Haim/CountriesToCitiesJSON.git
......@@ -249,4 +249,19 @@ duniter.subscription.enable: false
#
# Email subscription: URL to a Cesium site, for links in the email content (default: https://g1.duniter.fr)
#
# duniter.subscription.email.cesium.url: 'http://domain.com/cesium'
\ No newline at end of file
# duniter.subscription.email.cesium.url: 'http://domain.com/cesium'
#
# ---------------------------------- Duniter4j User (profile, message) module -------------------
#
#
# Share link: `og:site_name` (default: 'Cesium')
#
# duniter.user.share.site.name: 'Cesium - Ğ1'
#
# Share link: `og:url` - URL to a Cesium site, for links in the email content (default: https://g1.duniter.fr)
#
# duniter.share.cesium.url: 'https://domain.com/cesium'
#
# Share link: Base URL of the ES cluster, to resolve `og:image` URL (default: none => /!\ Will use relative image path)
#
# duniter.share.base.url: 'https://data.domain.com'
\ No newline at end of file
......@@ -251,4 +251,19 @@ duniter.subscription.enable: true
#
# Email subscription: URL to a Cesium site, for links in the email content (default: https://g1.duniter.fr)
#
#duniter.subscription.email.cesium.url: 'https://domain.com/cesium'
\ No newline at end of file
#duniter.subscription.email.cesium.url: 'https://domain.com/cesium'
#
# ---------------------------------- Duniter4j User (profile, message) module -------------------
#
#
# Share link: og:site_name (default: 'Cesium')
#
# duniter.user.share.site.name: 'Cesium - Ğ1'
#
# Share link : URL to a Cesium site, for links in the email content (default: https://g1.duniter.fr)
#
#duniter.share.cesium.url: 'https://domain.com/cesium'
#
# Share link : Base URL of cluster, to resolve image (default: none => /!\ Will use relative image path)
#
#duniter.share.base.url: 'http://localhost:9200'
\ No newline at end of file
......@@ -76,6 +76,14 @@
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>stringtemplate</artifactId>
<version>${stringtemplate.version}</version>
<scope>compile</scope>
</dependency>
<!-- JNA (need for OS shutdown hook) -->
<dependency>
<groupId>net.java.dev.jna</groupId>
......
......@@ -95,4 +95,17 @@ public class RestImageAttachmentAction extends BaseRestHandler {
}
});
}
public static String computeImageUrl(String index,
String type,
String id,
String imageField,
String contentType) {
int lastSlashIndex = contentType != null ? contentType.lastIndexOf('/') : -1;
String extension = (lastSlashIndex >= 0) ? contentType.substring(lastSlashIndex+1) : contentType;
return String.format("/%s/%s/%s/_image/%s.%s", index, type, id, imageField, extension);
}
}
\ No newline at end of file
package org.duniter.elasticsearch.rest.share;
import org.apache.http.entity.ContentType;
import org.duniter.core.exception.BusinessException;
import org.duniter.core.util.Preconditions;
import org.duniter.core.util.StringUtils;
import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
import org.duniter.elasticsearch.util.opengraph.OGData;
import org.duniter.elasticsearch.util.springtemplate.STUtils;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.*;
import org.nuiton.i18n.I18n;
import org.stringtemplate.v4.*;
import java.util.Locale;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestStatus.OK;
public abstract class AbstractRestShareLinkAction extends BaseRestHandler {
protected final ESLogger log;
public interface OGDataResolver {
OGData resolve(String id) throws DuniterElasticsearchException, BusinessException;
}
private OGDataResolver resolver;
private STGroup templates;
public AbstractRestShareLinkAction(Settings settings, RestController controller, Client client,
String indexName,
String typeName,
OGDataResolver resolver
) {
super(settings, controller, client);
log = Loggers.getLogger("duniter.rest." + indexName, settings, String.format("[%s]", indexName));
controller.registerHandler(GET,
String.format("/%s/%s/{id}/_share", indexName, typeName),
this);
this.resolver = resolver;
// Configure springtemplate engine
this.templates = STUtils.newSTGroup("org/duniter/elasticsearch/templates");
Preconditions.checkNotNull(this.templates.getInstanceOf("html_share"), "Unable to load ST template for share page");
}
@Override
protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception {
String id = request.param("id");
try {
OGData data = resolver.resolve(id);
Preconditions.checkNotNull(data);
Preconditions.checkNotNull(data.title);
// Compute HTML content
ST template = templates.getInstanceOf("html_share");
template.add("type", data.type);
template.add("title", data.title);
template.add("summary", StringUtils.truncate(data.description, 500));
template.add("description", data.description);
template.add("siteName", data.siteName);
template.add("image", data.image);
template.add("url", data.url);
template.add("locale", data.locale);
if (StringUtils.isNotBlank(data.url)) {
Locale locale = data.locale != null ? new Locale(data.locale) : I18n.getDefaultLocale();
template.add("redirectMessage", I18n.l(locale, "duniter4j.share.redirection.help"));
}
String html = template.render();
restChannel.sendResponse(new BytesRestResponse(OK, ContentType.TEXT_HTML.getMimeType(), html));
}
catch(DuniterElasticsearchException | BusinessException e) {
log.error(e.getMessage(), e);
restChannel.sendResponse(new XContentThrowableRestResponse(request, e));
}
catch(Exception e) {
log.error(e.getMessage(), e);
}
}
}
package org.duniter.elasticsearch.util.opengraph;
public class OGData {
public String type;
public String title;
public String description;
public String image;
public String url;
public String locale;
public String imageType;
public String siteName;
}
package org.duniter.elasticsearch.util.springtemplate;
/*-
* #%L
* Duniter4j :: ElasticSearch Subscription plugin
* %%
* Copyright (C) 2014 - 2017 EIS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import org.stringtemplate.v4.AttributeRenderer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class DateRenderer implements AttributeRenderer {
public DateRenderer() {
}
public String toString(Object o, String formatString, Locale locale) {
if(formatString == null) {
formatString = "short";
}
Date d;
if(o instanceof Calendar) {
d = ((Calendar)o).getTime();
} else {
d = (Date)o;
}
Integer styleI = (Integer)org.stringtemplate.v4.DateRenderer.formatToInt.get(formatString);
Object f;
if(styleI == null) {
f = new SimpleDateFormat(formatString, locale);
} else {
int style = styleI.intValue();
if(formatString.startsWith("date:")) {
f = DateFormat.getDateInstance(style, locale);
} else if(formatString.startsWith("time:")) {
f = DateFormat.getTimeInstance(style, locale);
} else {
f = DateFormat.getDateTimeInstance(style, style, locale);
}
}
return ((DateFormat)f).format(d);
}
}
\ No newline at end of file
package org.duniter.elasticsearch.util.springtemplate;
import org.stringtemplate.v4.DateRenderer;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.STGroupDir;
import org.stringtemplate.v4.StringRenderer;
import java.util.Date;
public class STUtils {
private STUtils() {
/*help class*/
}
public static STGroup newSTGroup(String dirName) {
// Configure springtemplate engine
STGroup templates = new STGroupDir(dirName, '$', '$');
templates.registerRenderer(Date.class, new DateRenderer());
templates.registerRenderer(String.class, new StringRenderer());
return templates;
}
}
package org.duniter.elasticsearch.util.springtemplate;
/*-
* #%L
* Duniter4j :: ElasticSearch Subscription plugin
* %%
* Copyright (C) 2014 - 2017 EIS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import org.duniter.core.client.model.ModelUtils;
import org.duniter.core.util.CollectionUtils;
import org.nuiton.i18n.I18n;
import java.util.Locale;
/**
* Add format capabilities: i18n, pubkey
* Created by blavenie on 10/04/17.
*/
public class StringRenderer extends org.stringtemplate.v4.StringRenderer{
@Override
public String toString(Object o, String formatString, Locale locale) {
return formatString == null ? (String)o :
(formatString.equals("pubkey") ? ModelUtils.minifyPubkey((String)o) :
(formatString.startsWith("i18n") ? toI18nString(o, formatString, locale) :
super.toString(o, formatString, locale)));
}
protected String toI18nString(Object key, String formatString, Locale locale) {
String[] params = formatString.startsWith("i18n:") ? formatString.substring(5).split(",") : null;
if (CollectionUtils.isNotEmpty(params)) {
return I18n.l(locale, key.toString(), params);
}
return I18n.l(locale, key.toString());
}
}
......@@ -47,6 +47,7 @@ duniter4j.job.stopped=
duniter4j.job.stopping=
duniter4j.job.success=
duniter4j.service.waitThenRetry=Error [%s]... will retry [%s/%s]
duniter4j.share.redirection.help=If you are not redirected automatically, follow this link\:
duniter4j.task.issuer.system=System
duniter4j.task.starting=Starting task...
duniter4j.threadPool.clusterHealthStatus.changed=Cluster health status changed to [%s]
......@@ -49,6 +49,7 @@ duniter4j.job.stopped=
duniter4j.job.stopping=
duniter4j.job.success=
duniter4j.service.waitThenRetry=Echec [%s]... tentative [%s/%s]
duniter4j.share.redirection.help=Si vous n'êtes pas redirigé automatiquement, cliquez sur le lien suivant \:
duniter4j.task.issuer.system=Système
duniter4j.task.starting=Démarrage du traitement...
duniter4j.threadPool.clusterHealthStatus.changed=Cluster health status changed to [%s]
html_share(type, title, summary, description, image, siteName, locale, url, redirectMessage) ::= <<
<html prefix="og: http://ogp.me/ns#">
<head>
<meta charset="UTF-8">
$if(siteName)$
<title>$siteName$ | $title$</title>
$else$
<title>$title$</title>
$endif$
$if(type)$
<meta property="og:type" content="$type$" />
$else$
<meta property="og:type" content="website" />
$endif$
<meta property="og:title" content="$title$" />
$if(summary)$
<meta property="og:description" content="$summary$" />
$else$
<meta property="og:description" content="$description$" />
$endif$
$if(siteName)$
<meta property="og:site_name" content="$siteName$" />
$endif$
$if(image)$
<meta property="og:image" content="$image$" />
$endif$
$if(locale)$
<meta property="og:locale" content="$locale$" />
$endif$
$if(url)$
<meta property="og:url" content="$url$" />
<script type="text/javascript">
window.location.href = "$url$"
</script>
<META HTTP-EQUIV="Refresh" CONTENT="0; URL=$url$">
$endif$
</head>
<body>
$if(image)$
<p>
<img src="$image$"/>
</p>
$endif$
<h1>$title$</h1>
<p>$description$</p>
$if(url)$
<p>
$redirectMessage$ <a href='$url$'>$title$</a>.
</p>
$endif$
</body>
</html>
>>
......@@ -46,7 +46,7 @@
<groupId>org.antlr</groupId>
<artifactId>stringtemplate</artifactId>
<version>${stringtemplate.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<!-- Unit test -->
......
......@@ -51,6 +51,7 @@ import org.duniter.elasticsearch.user.service.AdminService;
import org.duniter.elasticsearch.user.service.MailService;
import org.duniter.elasticsearch.user.service.UserEventService;
import org.duniter.elasticsearch.user.service.UserService;
import org.duniter.elasticsearch.util.springtemplate.STUtils;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.unit.TimeValue;
import org.nuiton.i18n.I18n;
......@@ -76,6 +77,7 @@ public class SubscriptionService extends AbstractService {
private UserEventService userEventService;
private UserService userService;
private String emailSubjectPrefix;
private STGroup templates;
@Inject
public SubscriptionService(Duniter4jClient client,
......@@ -100,6 +102,11 @@ public class SubscriptionService extends AbstractService {
if (StringUtils.isNotBlank(emailSubjectPrefix)) {
emailSubjectPrefix += " "; // add one trailing space
}
// Configure springtemplate engine
templates = STUtils.newSTGroup("org/duniter/elasticsearch/subscription/templates");
Preconditions.checkNotNull(templates.getInstanceOf("text_email"), "Missing ST template {text_email}");
Preconditions.checkNotNull(templates.getInstanceOf("html_email_content"), "Missing ST template {html_email_content}");
}
public String create(String json) {
......@@ -308,11 +315,7 @@ public class SubscriptionService extends AbstractService {
subscription.getContent().getLocale().split("-") : new String[]{"en", "GB"};
Locale issuerLocale = localParts.length >= 2 ? new Locale(localParts[0].toLowerCase(), localParts[1].toUpperCase()) : new Locale(localParts[0].toLowerCase());
// Configure templates engine
STGroup templates = new STGroupDir("templates", '$', '$');
templates.registerRenderer(Date.class, new DateRenderer());
templates.registerRenderer(String.class, new StringRenderer());
//templates.registerRenderer(Number.class, new NumberRenderer());
// Compute text content
final String text = fillTemplate(
......
cesium_logo(url, data) ::= <<
$if(data)$<img height="144" width="144" src="data:image/png;base64,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">
$else$
<img height="144" width="144" src="$url$/img/logo_128px.png">
$endif$
>>
\ No newline at end of file
grant codeBase "file:${es.path.home}/plugins/duniter4j-es-gchange/"{
grant codeBase "file:${es.path.home}/plugins/duniter4j-es-subscription/"{
permission java.io.FilePermission "/etc/ld.so.conf", "read";
permission java.io.FilePermission "/etc/ld.so.conf.d/*.conf", "read";
permission java.io.FilePermission "/usr/local/lib/*", "read";
......
......@@ -212,8 +212,17 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
return this.nodePubkey;
}
public String getCesiumUrl() {
return this.settings.get("duniter.share.cesium.url", "https://g1.duniter.fr");
}
public String getShareSiteName() {
return this.settings.get("duniter.user.share.site.name", "Cesium");
}
public String getBaseUrl() {
return settings.get("duniter.share.base.url");
}
/* -- protected methods -- */
......
package org.duniter.elasticsearch.user.model;
/*
* #%L
* Duniter4j :: Core Client API
* %%
* Copyright (C) 2014 - 2016 EIS
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonSetter;
import org.duniter.core.client.model.elasticsearch.Record;
/**
* Created by blavenie on 01/03/16.
*/
public class Attachment extends Record {
public static final String JSON_PROPERTY_CONTENT_TYPE = "_content_type";
public static final String JSON_PROPERTY_CONTENT = "_content";
public static final String PROPERTY_CONTENT_TYPE = "contentType";
public static final String PROPERTY_CONTENT = "content";
private String contentType;
private String content;
@JsonSetter(JSON_PROPERTY_CONTENT_TYPE)
public void setContentType(String contentType) {
this.contentType = contentType;
}
@JsonGetter(JSON_PROPERTY_CONTENT_TYPE)
public String getContentType() {
return contentType;
}
@JsonGetter(JSON_PROPERTY_CONTENT)
public String getContent() {
return content;
}
@JsonSetter(JSON_PROPERTY_CONTENT)
public void setContent(String content) {
this.content = content;
}
}
......@@ -43,6 +43,7 @@ public class UserProfile extends Record {
private String locale;
private String address;
private String city;
private Attachment avatar;
public String getTitle() {
return title;
......@@ -91,4 +92,12 @@ public class UserProfile extends Record {
public void setCity(String city) {
this.city = city;
}
public Attachment getAvatar() {
return avatar;
}
public void setAvatar(Attachment avatar) {
this.avatar = avatar;
}
}
......@@ -50,6 +50,7 @@ public class RestModule extends AbstractModule implements Module {
bind(RestUserEventMarkAsReadAction.class).asEagerSingleton();
bind(RestUserEventSearchAction.class).asEagerSingleton();
bind(RestUserAvatarAction.class).asEagerSingleton();
bind(RestUserShareLinkAction.class).asEagerSingleton();
// Group
bind(RestGroupIndexAction.class).asEagerSingleton();
......@@ -76,6 +77,7 @@ public class RestModule extends AbstractModule implements Module {
bind(RestPageCommentUpdateAction.class).asEagerSingleton();
bind(RestPageCategoryAction.class).asEagerSingleton();
bind(RestPageImageAction.class).asEagerSingleton();
bind(RestPageShareLinkAction.class).asEagerSingleton();
// Mixed search
bind(RestMixedSearchAction.class).asEagerSingleton();
......
package org.duniter.elasticsearch.user.rest.page;
import com.google.common.html.HtmlEscapers;
import org.duniter.core.exception.BusinessException;
import org.duniter.core.exception.TechnicalException;
import org.duniter.core.util.StringUtils;
import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
import org.duniter.elasticsearch.rest.attachment.RestImageAttachmentAction;
import org.duniter.elasticsearch.rest.share.AbstractRestShareLinkAction;
import org.duniter.elasticsearch.user.PluginSettings;
import org.duniter.elasticsearch.user.dao.page.PageIndexDao;
import org.duniter.elasticsearch.user.dao.page.PageRecordDao;
import org.duniter.elasticsearch.user.model.UserProfile;
import org.duniter.elasticsearch.user.model.page.RegistryRecord;
import org.duniter.elasticsearch.user.service.PageService;
import org.duniter.elasticsearch.user.service.UserService;
import org.duniter.elasticsearch.util.opengraph.OGData;