diff --git a/client/core.go b/client/core.go index 2958ce02..5f5bbdf6 100644 --- a/client/core.go +++ b/client/core.go @@ -133,6 +133,7 @@ type Entry struct { Date time.Time `json:"published_at"` Content string `json:"content"` Author string `json:"author"` + ShareCode string `json:"share_code"` Starred bool `json:"starred"` Enclosures Enclosures `json:"enclosures,omitempty"` Feed *Feed `json:"feed,omitempty"` diff --git a/crypto/crypto.go b/crypto/crypto.go index fa236ab4..c06cd307 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "crypto/sha256" "encoding/base64" + "encoding/hex" "fmt" ) @@ -36,3 +37,8 @@ func GenerateRandomBytes(size int) []byte { func GenerateRandomString(size int) string { return base64.URLEncoding.EncodeToString(GenerateRandomBytes(size)) } + +// GenerateRandomStringHex returns a random hexadecimal string. +func GenerateRandomStringHex(size int) string { + return hex.EncodeToString(GenerateRandomBytes(size)) +} diff --git a/database/migration.go b/database/migration.go index fff815bc..c9abf0e8 100644 --- a/database/migration.go +++ b/database/migration.go @@ -12,7 +12,7 @@ import ( "miniflux.app/logger" ) -const schemaVersion = 27 +const schemaVersion = 28 // Migrate executes database migrations. func Migrate(db *sql.DB) { diff --git a/database/sql.go b/database/sql.go index d79a4529..cd3f418c 100644 --- a/database/sql.go +++ b/database/sql.go @@ -167,6 +167,9 @@ alter table entries alter column changed_at set not null; primary key(id), unique (user_id, description) ); +`, + "schema_version_28": `alter table entries add column share_code text not null default ''; +create unique index entries_share_code_idx on entries using btree(share_code) where share_code <> ''; `, "schema_version_3": `create table tokens ( id text not null, @@ -223,6 +226,7 @@ var SqlMapChecksums = map[string]string{ "schema_version_25": "5262d2d4c88d637b6603a1fcd4f68ad257bd59bd1adf89c58a18ee87b12050d7", "schema_version_26": "64f14add40691f18f514ac0eed10cd9b19c83a35e5c3d8e0bce667e0ceca9094", "schema_version_27": "4235396b37fd7f52ff6f7526416042bb1649701233e2d99f0bcd583834a0a967", + "schema_version_28": "a64b5ba0b37fe3f209617b7d0e4dd05018d2b8362d2c9c528ba8cce19b77e326", "schema_version_3": "a54745dbc1c51c000f74d4e5068f1e2f43e83309f023415b1749a47d5c1e0f12", "schema_version_4": "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9", "schema_version_5": "46397e2f5f2c82116786127e9f6a403e975b14d2ca7b652a48cd1ba843e6a27c", diff --git a/database/sql/schema_version_28.sql b/database/sql/schema_version_28.sql new file mode 100644 index 00000000..fed34cfa --- /dev/null +++ b/database/sql/schema_version_28.sql @@ -0,0 +1,2 @@ +alter table entries add column share_code text not null default ''; +create unique index entries_share_code_idx on entries using btree(share_code) where share_code <> ''; diff --git a/locale/translations.go b/locale/translations.go index 2acbc1ab..0691a1a2 100644 --- a/locale/translations.go +++ b/locale/translations.go @@ -76,6 +76,8 @@ var translations = map[string]string{ "entry.original.label": "Original-Artikel", "entry.comments.label": "Kommentare", "entry.comments.title": "Kommentare anzeigen", + "entry.share.label": "Teilen", + "entry.share.title": "Diesen Artikel teilen", "page.unread.title": "Ungelesen", "page.starred.title": "Lesezeichen", "page.categories.title": "Kategorien", @@ -402,6 +404,8 @@ var translations = map[string]string{ "entry.original.label": "Original", "entry.comments.label": "Comments", "entry.comments.title": "View Comments", + "entry.share.label": "Share", + "entry.share.title": "Share this article", "page.unread.title": "Unread", "page.starred.title": "Starred", "page.categories.title": "Categories", @@ -708,6 +712,8 @@ var translations = map[string]string{ "entry.original.label": "Original", "entry.comments.label": "Comentarios", "entry.comments.title": "Ver comentarios", + "entry.share.label": "Comparta", + "entry.share.title": "Comparta este articulo", "page.unread.title": "No leídos", "page.starred.title": "Marcadores", "page.categories.title": "Categorias", @@ -1014,6 +1020,8 @@ var translations = map[string]string{ "entry.original.label": "Original", "entry.comments.label": "Commentaires", "entry.comments.title": "Voir les commentaires", + "entry.share.label": "Partager", + "entry.share.title": "Partager cet article", "page.unread.title": "Non lus", "page.starred.title": "Favoris", "page.categories.title": "Catégories", @@ -1340,6 +1348,8 @@ var translations = map[string]string{ "entry.original.label": "Contenuto originale", "entry.comments.label": "Commenti", "entry.comments.title": "Mostra i commenti", + "entry.share.label": "Condividi", + "entry.share.title": "Condividi questo articolo", "page.unread.title": "Da leggere", "page.starred.title": "Preferiti", "page.categories.title": "Categorie", @@ -1646,6 +1656,8 @@ var translations = map[string]string{ "entry.original.label": "オリジナル", "entry.comments.label": "コメント", "entry.comments.title": "コメントを見る", + "entry.share.label": "共有", + "entry.share.title": "この記事を共有する", "page.unread.title": "未読", "page.starred.title": "星付き", "page.categories.title": "カテゴリ", @@ -1952,6 +1964,8 @@ var translations = map[string]string{ "entry.original.label": "Origineel", "entry.comments.label": "Comments", "entry.comments.title": "Bekijk de reacties", + "entry.share.label": "Deel", + "entry.share.title": "Deel dit artikel", "page.unread.title": "Ongelezen", "page.starred.title": "Favorieten", "page.categories.title": "Categorieën", @@ -2276,6 +2290,8 @@ var translations = map[string]string{ "entry.original.label": "Oryginalny artykuł", "entry.comments.label": "Komentarze", "entry.comments.title": "Zobacz komentarze", + "entry.share.label": "Podzielić się", + "entry.share.title": "Podzielić się ten artykuł", "page.unread.title": "Nieprzeczytane", "page.starred.title": "Oznaczone gwiazdką", "page.categories.title": "Kategorie", @@ -2608,6 +2624,8 @@ var translations = map[string]string{ "entry.original.label": "Оригинал", "entry.comments.label": "Комментарии", "entry.comments.title": "Показать комментарии", + "entry.share.label": "поделиться", + "entry.share.title": "поделиться эту статью", "page.unread.title": "Непрочитанное", "page.starred.title": "Избранное", "page.categories.title": "Категории", @@ -2922,6 +2940,8 @@ var translations = map[string]string{ "entry.original.label": "原始内容", "entry.comments.label": "评论", "entry.comments.title": "查看评论", + "entry.share.label": "分享", + "entry.share.title": "分享这篇文章", "page.unread.title": "未读", "page.starred.title": "星标", "page.categories.title": "分类", @@ -3169,14 +3189,14 @@ var translations = map[string]string{ } var translationsChecksums = map[string]string{ - "de_DE": "cc826a57cf4bf789df38db4f50626ad8c1c2b84ce34075c2c04de3d1f0dcd2d5", - "en_US": "f7e6db53cdbc2c0d959ac231dbacf0ef4d0ed81248944c4a4f8b83ef000f5349", - "es_ES": "cc727f62eef3a6cba51b65253d70a50161af35bf9c5366281b7984b2fc189961", - "fr_FR": "d3d1a4bf9aa8e4e24bae2f117507dcfc3cf00660a73b44a6c42356e8dbab8ae8", - "it_IT": "5ded991f2c70ec2268e6053bd84a77cf4136ebaea42013d3e79d594f38abb1b3", - "ja_JP": "110d7a7b1c888282b031de340e3318a62cdd62076b05a7fb49759f554c6dbe76", - "nl_NL": "a934ab4b1eff85580425a5859c31fcb227ae8926deba74df4e42b5d4feb67826", - "pl_PL": "6e80c36788723b9a7ff3f372e13a55c68d153727ec0abb56663cadbf6d6e1d9f", - "ru_RU": "d56f9e31f63731d23ce1ea2a8a4cb019f3ab282b23a1f494c47061daea523587", - "zh_CN": "4a5ca40790fceab88257f6742dc05294b79142bee8aad6fc87fbd479d1941292", + "de_DE": "7360a69e038d71e00f64c03891401cd517779687d46a907688f4a9a7b6205146", + "en_US": "92dda79899a673652a43fd8d61c893749713af09909ca03ac6fea06ac617d361", + "es_ES": "813b8cd42907dfbc19ff51f3367e0dbb013d373b013d7854df512e846652ff21", + "fr_FR": "279c52bbf682949cf8782e7e81f2bf5cfd300cebf577d51ce9436d44aaaf6323", + "it_IT": "5e8408e9aee142e1bd7e73f2a91ae96bc9ad0ab61c20416ad9e93b6fe505e8a9", + "ja_JP": "508025c0c7e7f57195ae011c4499ab58a85d043c828565c1740df879fb2376c1", + "nl_NL": "e621a5e7408928624a060a832d9fc36b74026221bd7b07894a4cce267be3cdd1", + "pl_PL": "2383c1a9be451557fe601f346e30bb165a88d9c00d17909a9c747d64864a423d", + "ru_RU": "d7ad59bbd7a150af9d476c4c3034eb85762de7381e2925d75e373584ed45c725", + "zh_CN": "e5f169a3c83c9bd7a41e9737e001e58fec243eee7aa23a71d37bfa8e05d92860", } diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 07543b6f..bf34e833 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -71,6 +71,8 @@ "entry.original.label": "Original-Artikel", "entry.comments.label": "Kommentare", "entry.comments.title": "Kommentare anzeigen", + "entry.share.label": "Teilen", + "entry.share.title": "Diesen Artikel teilen", "page.unread.title": "Ungelesen", "page.starred.title": "Lesezeichen", "page.categories.title": "Kategorien", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index c3dc3b34..7b7ea63e 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -71,6 +71,8 @@ "entry.original.label": "Original", "entry.comments.label": "Comments", "entry.comments.title": "View Comments", + "entry.share.label": "Share", + "entry.share.title": "Share this article", "page.unread.title": "Unread", "page.starred.title": "Starred", "page.categories.title": "Categories", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index d1545e5c..dae98986 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -71,6 +71,8 @@ "entry.original.label": "Original", "entry.comments.label": "Comentarios", "entry.comments.title": "Ver comentarios", + "entry.share.label": "Comparta", + "entry.share.title": "Comparta este articulo", "page.unread.title": "No leídos", "page.starred.title": "Marcadores", "page.categories.title": "Categorias", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index f01c9e35..e75ad613 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -71,6 +71,8 @@ "entry.original.label": "Original", "entry.comments.label": "Commentaires", "entry.comments.title": "Voir les commentaires", + "entry.share.label": "Partager", + "entry.share.title": "Partager cet article", "page.unread.title": "Non lus", "page.starred.title": "Favoris", "page.categories.title": "Catégories", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index 0ba57c8c..e869454a 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -71,6 +71,8 @@ "entry.original.label": "Contenuto originale", "entry.comments.label": "Commenti", "entry.comments.title": "Mostra i commenti", + "entry.share.label": "Condividi", + "entry.share.title": "Condividi questo articolo", "page.unread.title": "Da leggere", "page.starred.title": "Preferiti", "page.categories.title": "Categorie", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 4834b188..b7a6ae38 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -71,6 +71,8 @@ "entry.original.label": "オリジナル", "entry.comments.label": "コメント", "entry.comments.title": "コメントを見る", + "entry.share.label": "共有", + "entry.share.title": "この記事を共有する", "page.unread.title": "未読", "page.starred.title": "星付き", "page.categories.title": "カテゴリ", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index 76dc686b..871271bb 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -71,6 +71,8 @@ "entry.original.label": "Origineel", "entry.comments.label": "Comments", "entry.comments.title": "Bekijk de reacties", + "entry.share.label": "Deel", + "entry.share.title": "Deel dit artikel", "page.unread.title": "Ongelezen", "page.starred.title": "Favorieten", "page.categories.title": "Categorieën", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 7c842f5e..62740eb1 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -71,6 +71,8 @@ "entry.original.label": "Oryginalny artykuł", "entry.comments.label": "Komentarze", "entry.comments.title": "Zobacz komentarze", + "entry.share.label": "Podzielić się", + "entry.share.title": "Podzielić się ten artykuł", "page.unread.title": "Nieprzeczytane", "page.starred.title": "Oznaczone gwiazdką", "page.categories.title": "Kategorie", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index b415a193..e1332011 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -71,6 +71,8 @@ "entry.original.label": "Оригинал", "entry.comments.label": "Комментарии", "entry.comments.title": "Показать комментарии", + "entry.share.label": "поделиться", + "entry.share.title": "поделиться эту статью", "page.unread.title": "Непрочитанное", "page.starred.title": "Избранное", "page.categories.title": "Категории", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index 44675d1e..1117a90b 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -71,6 +71,8 @@ "entry.original.label": "原始内容", "entry.comments.label": "评论", "entry.comments.title": "查看评论", + "entry.share.label": "分享", + "entry.share.title": "分享这篇文章", "page.unread.title": "未读", "page.starred.title": "星标", "page.categories.title": "分类", diff --git a/model/entry.go b/model/entry.go index 0a7a5f7b..54dfe03f 100644 --- a/model/entry.go +++ b/model/entry.go @@ -31,6 +31,7 @@ type Entry struct { Date time.Time `json:"published_at"` Content string `json:"content"` Author string `json:"author"` + ShareCode string `json:"share_code"` Starred bool `json:"starred"` Enclosures EnclosureList `json:"enclosures,omitempty"` Feed *Feed `json:"feed,omitempty"` diff --git a/storage/entry.go b/storage/entry.go index 277eb7a3..f0fa64bc 100644 --- a/storage/entry.go +++ b/storage/entry.go @@ -9,6 +9,7 @@ import ( "fmt" "time" + "miniflux.app/crypto" "miniflux.app/logger" "miniflux.app/model" @@ -351,3 +352,35 @@ func (s *Storage) EntryURLExists(feedID int64, entryURL string) bool { s.db.QueryRow(query, feedID, entryURL).Scan(&result) return result } + +// GetEntryShareCode returns the share code of the provided entry. +// It generates a new one if not already defined. +func (s *Storage) GetEntryShareCode(userID int64, entryID int64) (shareCode string, err error) { + query := `SELECT share_code FROM entries WHERE user_id=$1 AND id=$2` + err = s.db.QueryRow(query, userID, entryID).Scan(&shareCode) + + if err != nil || shareCode != "" { + return + } + + shareCode = crypto.GenerateRandomStringHex(16) + + query = `UPDATE entries SET share_code = $1 WHERE user_id=$2 AND id=$3` + result, err := s.db.Exec(query, shareCode, userID, entryID) + if err != nil { + err = fmt.Errorf(`store: unable to set share_code for entry #%d: %v`, entryID, err) + return + } + + count, err := result.RowsAffected() + if err != nil { + err = fmt.Errorf(`store: unable to set share_code for entry #%d: %v`, entryID, err) + return + } + + if count == 0 { + err = errors.New(`store: nothing has been updated`) + } + + return +} diff --git a/storage/entry_query_builder.go b/storage/entry_query_builder.go index ebc98a12..cf547a22 100644 --- a/storage/entry_query_builder.go +++ b/storage/entry_query_builder.go @@ -128,6 +128,13 @@ func (e *EntryQueryBuilder) WithoutStatus(status string) *EntryQueryBuilder { return e } +// WithShareCode set the entry hash. +func (e *EntryQueryBuilder) WithShareCode(shareCode string) *EntryQueryBuilder { + e.conditions = append(e.conditions, fmt.Sprintf("e.share_code = $%d", len(e.args)+1)) + e.args = append(e.args, shareCode) + return e +} + // WithOrder set the sorting order. func (e *EntryQueryBuilder) WithOrder(order string) *EntryQueryBuilder { e.order = order @@ -198,6 +205,7 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) { e.url, e.comments_url, e.author, + e.share_code, e.content, e.status, e.starred, @@ -255,6 +263,7 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) { &entry.URL, &entry.CommentsURL, &entry.Author, + &entry.ShareCode, &entry.Content, &entry.Status, &entry.Starred, @@ -358,3 +367,10 @@ func NewEntryQueryBuilder(store *Storage, userID int64) *EntryQueryBuilder { conditions: []string{"e.user_id = $1"}, } } + +// NewAnonymousQueryBuilder returns a new EntryQueryBuilder suitable for anonymous users. +func NewAnonymousQueryBuilder(store *Storage) *EntryQueryBuilder { + return &EntryQueryBuilder{ + store: store, + } +} diff --git a/template/html/entry.html b/template/html/entry.html index f9872d9f..3ff2cdb5 100644 --- a/template/html/entry.html +++ b/template/html/entry.html @@ -6,6 +6,7 @@

{{ .entry.Title }}

+ {{ if .user }}
+ {{ end }}
- {{ if ne .entry.Feed.Icon.IconID 0 }} + {{ if and .user (ne .entry.Feed.Icon.IconID 0) }} {{ .entry.Feed.Title }} {{ end }} {{ .entry.Feed.Title }} @@ -75,21 +83,33 @@ {{ end }} {{ end }} - - {{ .entry.Feed.Category.Title }} - + {{ if .user }} + + {{ .entry.Feed.Category.Title }} + + {{ end }}
- + {{ if .user }} + + {{ else }} + + {{ end }}
{{ if gt (len .entry.Content) 120 }} + {{ if .user }}
{{ template "entry_pagination" . }}
{{ end }} + {{ end }}
- {{ noescape (proxyFilter .entry.Content) }} + {{ if .user }} + {{ noescape (proxyFilter .entry.Content) }} + {{ else }} + {{ noescape .entry.Content }} + {{ end }}
{{ if .entry.Enclosures }}
@@ -111,7 +131,11 @@ {{ else if hasPrefix .MimeType "image/" }}
- {{ .URL }} ({{ .MimeType }}) + {{ if .user }} + {{ .URL }} ({{ .MimeType }}) + {{ else }} + {{ .URL }} ({{ .MimeType }}) + {{ end }}
{{ end }} @@ -126,7 +150,9 @@ {{ end }} +{{ if .user }}
{{ template "entry_pagination" . }}
{{ end }} +{{ end }} diff --git a/template/views.go b/template/views.go index c69fa70e..70753ef2 100644 --- a/template/views.go +++ b/template/views.go @@ -653,6 +653,7 @@ var templateViewsMap = map[string]string{

{{ .entry.Title }}

+ {{ if .user }}
+ {{ end }}
- {{ if ne .entry.Feed.Icon.IconID 0 }} + {{ if and .user (ne .entry.Feed.Icon.IconID 0) }} {{ .entry.Feed.Title }} {{ end }} {{ .entry.Feed.Title }} @@ -722,21 +730,33 @@ var templateViewsMap = map[string]string{ {{ end }} {{ end }} - - {{ .entry.Feed.Category.Title }} - + {{ if .user }} + + {{ .entry.Feed.Category.Title }} + + {{ end }}
- + {{ if .user }} + + {{ else }} + + {{ end }}
{{ if gt (len .entry.Content) 120 }} + {{ if .user }}
{{ template "entry_pagination" . }}
{{ end }} + {{ end }}
- {{ noescape (proxyFilter .entry.Content) }} + {{ if .user }} + {{ noescape (proxyFilter .entry.Content) }} + {{ else }} + {{ noescape .entry.Content }} + {{ end }}
{{ if .entry.Enclosures }}
@@ -758,7 +778,11 @@ var templateViewsMap = map[string]string{ {{ else if hasPrefix .MimeType "image/" }}
- {{ .URL }} ({{ .MimeType }}) + {{ if .user }} + {{ .URL }} ({{ .MimeType }}) + {{ else }} + {{ .URL }} ({{ .MimeType }}) + {{ end }}
{{ end }} @@ -773,10 +797,12 @@ var templateViewsMap = map[string]string{ {{ end }} +{{ if .user }}
{{ template "entry_pagination" . }}
{{ end }} +{{ end }} `, "feed_entries": `{{ define "title"}}{{ .feed.Title }} ({{ .total }}){{ end }} @@ -1422,7 +1448,7 @@ var templateViewsMapChecksums = map[string]string{ "edit_category": "b1c0b38f1b714c5d884edcd61e5b5295a5f1c8b71c469b35391e4dcc97cc6d36", "edit_feed": "cc0b5dbb73f81398410958b41771ed38246bc7ae4bd548228f0d48c49a598c2a", "edit_user": "c692db9de1a084c57b93e95a14b041d39bf489846cbb91fc982a62b72b77062a", - "entry": "513183f0f0b11a199630562f5a85eb9a5646051aae278cbc682bac13d62e65cc", + "entry": "ef9cd8bb99c561023c1dcea1dbd7f90c4cdc195ed70e2ed9c88213fec875d770", "feed_entries": "9c70b82f55e4b311eff20be1641733612e3c1b406ce8010861e4c417d97b6dcc", "feeds": "ec7d3fa96735bd8422ba69ef0927dcccddc1cc51327e0271f0312d3f881c64fd", "history_entries": "87e17d39de70eb3fdbc4000326283be610928758eae7924e4b08dcb446f3b6a9", diff --git a/ui/middleware.go b/ui/middleware.go index d023cb0d..65c30733 100644 --- a/ui/middleware.go +++ b/ui/middleware.go @@ -135,6 +135,7 @@ func (m *middleware) isPublicRoute(r *http.Request) bool { "favicon", "webManifest", "robots", + "share", "healthcheck": return true default: diff --git a/ui/share.go b/ui/share.go new file mode 100644 index 00000000..62e38a80 --- /dev/null +++ b/ui/share.go @@ -0,0 +1,57 @@ +// Copyright 2017 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package ui // import "miniflux.app/ui" + +import ( + "net/http" + "time" + + "miniflux.app/http/request" + "miniflux.app/http/response" + "miniflux.app/http/response/html" + "miniflux.app/http/route" + "miniflux.app/storage" + "miniflux.app/ui/session" + "miniflux.app/ui/view" +) + +func (h *handler) shareGenerate(w http.ResponseWriter, r *http.Request) { + entryID := request.RouteInt64Param(r, "entryID") + shareCode, err := h.store.GetEntryShareCode(request.UserID(r), entryID) + if err != nil { + html.ServerError(w, r, err) + return + } + + html.Redirect(w, r, route.Path(h.router, "share", "shareCode", shareCode)) +} + +func (h *handler) sharePage(w http.ResponseWriter, r *http.Request) { + shareCode := request.RouteStringParam(r, "shareCode") + if shareCode == "" { + html.NotFound(w, r) + return + } + + etag := shareCode + response.New(w, r).WithCaching(etag, 72*time.Hour, func(b *response.Builder) { + builder := storage.NewAnonymousQueryBuilder(h.store) + builder.WithShareCode(shareCode) + + entry, err := builder.GetEntry() + if err != nil || entry == nil { + html.NotFound(w, r) + return + } + + sess := session.New(h.store, request.SessionID(r)) + view := view.New(h.tpl, r, sess) + view.Set("entry", entry) + + b.WithHeader("Content-Type", "text/html; charset=utf-8") + b.WithBody(view.Render("entry")) + b.Write() + }) +} diff --git a/ui/ui.go b/ui/ui.go index 918c44eb..a45c0fde 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -88,6 +88,10 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool, feedHa uiRouter.HandleFunc("/proxy/{encodedURL}", handler.imageProxy).Name("proxy").Methods("GET") uiRouter.HandleFunc("/entry/bookmark/{entryID}", handler.toggleBookmark).Name("toggleBookmark").Methods("POST") + // Share pages. + uiRouter.HandleFunc("/entry/share/{entryID}", handler.shareGenerate).Name("shareGenerate").Methods("GET") + uiRouter.HandleFunc("/shared/{shareCode}", handler.sharePage).Name("share").Methods("GET") + // User pages. uiRouter.HandleFunc("/users", handler.showUsersPage).Name("users").Methods("GET") uiRouter.HandleFunc("/user/create", handler.showCreateUserPage).Name("createUser").Methods("GET")