Sharing: Generate share preview images #18

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-06-26 14:26:36 +02:00
parent 1d2a3aaaea
commit 4aa7b6cc97
19 changed files with 238 additions and 186 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -7,15 +7,15 @@
<title>{{ .config.SiteTitle }}</title>
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta property="og:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
<meta property="og:url" content="{{ .config.SiteUrl }}"/>
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta property="og:image" content="{{ .config.SitePreview }}"/>
<meta property="og:description" content="{{ .config.SiteDescription }}"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta name="twitter:image" content="{{ .config.SitePreview }}"/>
<meta name="twitter:description" content="{{ .config.SiteDescription }}"/>
<meta name="twitter:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
<meta name="author" content="{{ .config.SiteAuthor }}">
<meta name="description" content="{{ .config.SiteDescription }}"/>

View file

@ -7,15 +7,17 @@
<title>{{ .config.SiteTitle }}</title>
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta property="og:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
{{if .previewUrl}}
<meta property="og:url" content="{{ .config.SiteUrl }}"/>
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta property="og:image" content="{{ .previewUrl }}"/>
<meta property="og:description" content="{{ .config.SiteDescription }}"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta name="twitter:image" content="{{ .previewUrl }}"/>
<meta name="twitter:description" content="{{ .config.SiteDescription }}"/>
<meta name="twitter:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
{{end}}
<meta name="author" content="{{ .config.SiteAuthor }}">
<meta name="description" content="{{ .config.SiteDescription }}"/>

View file

@ -7,16 +7,16 @@
<title>{{ .config.SiteTitle }}</title>
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta property="og:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
<meta property="og:url" content="{{ .config.SiteUrl }}"/>
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta property="og:image" content="{{ .config.SitePreview }}"/>
<meta property="og:description" content="{{ .config.SiteDescription }}"/>
<meta name="twitter:site" content="@browseyourlife"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
<meta name="twitter:image" content="{{ .config.SitePreview }}"/>
<meta name="twitter:description" content="{{ .config.SiteDescription }}"/>
<meta name="twitter:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
<meta name="twitter:site" content="@browseyourlife"/>
<meta name="author" content="{{ .config.SiteAuthor }}">
<meta name="description" content="{{ .config.SiteDescription }}"/>
@ -39,7 +39,8 @@
<![endif]-->
<div id="photoprism" class="container">
<div class="loading rainbow">
<div class="logo-large rainbow">
<svg id="b6f816b8-2ec9-4341-9df4-026397ed8ecb" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 283.46"><defs><style>.ba518a38-ee1f-413e-9d3c-314e98d01903{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:4px;}</style></defs><path id="e47e55cb-4800-4b2c-9d8d-6a55eeeef919" data-name="Logo Pfad" class="ba518a38-ee1f-413e-9d3c-314e98d01903" d="M263.24,206.16l-5.8,32.09m-236.61-31,19.09,18.91M167.34,42.91,40,225.58a.13.13,0,0,0,.1.2l217.16,12.45a.12.12,0,0,0,.12-.17L167.55,42.93A.12.12,0,0,0,167.34,42.91ZM141.1,25.24,20.25,207a.16.16,0,0,0,.14.25l242.1-1.05a.17.17,0,0,0,.14-.26L141.37,25.24A.16.16,0,0,0,141.1,25.24Zm.14-.21,26.22,17.7"/></svg>
</div>
</div>

View file

@ -1,112 +0,0 @@
package api
import (
"fmt"
"image"
"image/color"
"net/http"
"os"
"path"
"time"
"github.com/disintegration/imaging"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
)
// GET /api/v1/preview
func GetPreview(router *gin.RouterGroup) {
router.GET("/preview", func(c *gin.Context) {
// TODO: proof of concept - code needs refactoring!
t := time.Now().Format("20060102")
conf := service.Config()
thumbPath := path.Join(conf.ThumbPath(), "preview", t[0:4], t[4:6])
if err := os.MkdirAll(thumbPath, os.ModePerm); err != nil {
log.Error(err)
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
return
}
previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, t[6:8])
if fs.FileExists(previewFilename) {
c.File(previewFilename)
return
}
var f form.PhotoSearch
f.Public = true
f.Safe = true
f.Count = 12
f.Order = "relevance"
p, _, err := query.PhotoSearch(f)
if err != nil {
log.Error(err)
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
return
}
width := 908
height := 680
x := 0
y := 0
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
thumbType, _ := thumb.Types["tile_224"]
for _, f := range p {
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("preview: file %s is missing", txt.Quote(f.FileName))
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
return
}
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
if err != nil {
log.Error(err)
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
}
src, err := imaging.Open(thumbnail)
if err != nil {
log.Error(err)
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
return
}
preview = imaging.Paste(preview, src, image.Pt(x, y))
x += 228
if x > width {
x = 0
y += 228
}
}
// Save the resulting image as JPEG.
err = imaging.Save(preview, previewFilename)
if err != nil {
log.Error(err)
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
return
}
c.File(previewFilename)
})
}

View file

@ -1,38 +1,51 @@
package api
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt"
)
func shareHandler(c *gin.Context, conf *config.Config) {
token := c.Param("token")
links := entity.FindLinks(token, "")
// GET /s/:token/...
func Shares(router *gin.RouterGroup) {
router.GET("/:token", func(c *gin.Context) {
conf := service.Config()
shareToken := c.Param("token")
links := entity.FindValidLinks(shareToken, "")
if len(links) == 0 {
log.Warnf("sharing: invalid token %s", txt.Quote(token))
log.Warn("share: invalid token")
c.Redirect(http.StatusTemporaryRedirect, "/")
return
}
clientConfig := conf.GuestConfig()
c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig})
}
c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig, "previewUrl": clientConfig.SitePreview})
})
func InitShare(router *gin.RouterGroup) {
router.GET("/:token/:uid", func(c *gin.Context) {
conf := service.Config()
router.GET("/:token", func(c *gin.Context) {
shareHandler(c, conf)
})
shareToken := c.Param("token")
shareUID := c.Param("uid")
router.GET("/:token/*uid", func(c *gin.Context) {
shareHandler(c, conf)
links := entity.FindValidLinks(shareToken, shareUID)
if len(links) != 1 {
log.Warn("share: invalid token or uid")
c.Redirect(http.StatusTemporaryRedirect, "/")
return
}
clientConfig := conf.GuestConfig()
sharePreview := fmt.Sprintf("%ss/%s/%s/preview", clientConfig.SiteUrl, shareToken, shareUID)
c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig, "previewUrl": sharePreview})
})
}

View file

@ -0,0 +1,139 @@
package api
import (
"fmt"
"image"
"image/color"
"net/http"
"os"
"path"
"time"
"github.com/disintegration/imaging"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
)
// GET /s/:token/:uid/preview
// TODO: Proof of concept, needs refactoring.
func SharePreview(router *gin.RouterGroup) {
router.GET("/:token/:uid/preview", func(c *gin.Context) {
conf := service.Config()
shareToken := c.Param("token")
shareUID := c.Param("uid")
links := entity.FindLinks(shareToken, shareUID)
if len(links) != 1 {
log.Warn("share: invalid token (preview)")
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
thumbPath := path.Join(conf.ThumbPath(), "share")
if err := os.MkdirAll(thumbPath, os.ModePerm); err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, shareUID)
yesterday := time.Now().Add(-24 * time.Hour)
if info, err := os.Stat(previewFilename); err != nil {
log.Debugf("share: creating new preview for %s", shareUID)
} else if info.ModTime().After(yesterday) {
log.Debugf("share: using cached preview for %s", shareUID)
c.File(previewFilename)
return
} else if err := os.Remove(previewFilename); err != nil {
log.Errorf("share: could not remove old preview of %s", shareUID)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
var f form.PhotoSearch
// Previews may only contain public content in shared albums.
f.Album = shareUID
f.Public = true
f.Private = false
f.Hidden = false
f.Archived = false
f.Review = false
f.Merged = true
// Get first 12 album entries.
f.Count = 12
f.Order = "relevance"
p, _, err := query.PhotoSearch(f)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
width := 908
height := 680
x := 0
y := 0
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
thumbType, _ := thumb.Types["tile_224"]
for _, f := range p {
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("share: file %s is missing (preview)", txt.Quote(f.FileName))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
}
src, err := imaging.Open(thumbnail)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
preview = imaging.Paste(preview, src, image.Pt(x, y))
x += 228
if x > width {
x = 0
y += 228
}
}
// Save the resulting image as JPEG.
err = imaging.Save(preview, previewFilename)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
c.File(previewFilename)
})
}

View file

@ -10,8 +10,8 @@ import (
func TestGetPreview(t *testing.T) {
t.Run("not found", func(t *testing.T) {
app, router, _ := NewApiTest()
GetPreview(router)
r := PerformRequest(app, "GET", "/api/v1/preview")
SharePreview(router)
r := PerformRequest(app, "GET", "/a/1jxf3jfn2k/st9lxuqxpogaaba7/preview")
assert.Equal(t, http.StatusNotFound, r.Code)
})
}

View file

@ -32,6 +32,7 @@ func configAction(ctx *cli.Context) error {
// Site information.
fmt.Printf("%-25s %s\n", "site-url", conf.SiteUrl())
fmt.Printf("%-25s %s\n", "site-preview", conf.SitePreview())
fmt.Printf("%-25s %s\n", "site-title", conf.SiteTitle())
fmt.Printf("%-25s %s\n", "site-caption", conf.SiteCaption())
fmt.Printf("%-25s %s\n", "site-description", conf.SiteDescription())

View file

@ -18,6 +18,7 @@ type ClientConfig struct {
Copyright string `json:"copyright"`
Flags string `json:"flags"`
SiteUrl string `json:"siteUrl"`
SitePreview string `json:"sitePreview"`
SiteTitle string `json:"siteTitle"`
SiteCaption string `json:"siteCaption"`
SiteDescription string `json:"siteDescription"`
@ -129,6 +130,7 @@ func (c *Config) PublicConfig() ClientConfig {
Flags: strings.Join(c.Flags(), " "),
Name: c.Name(),
SiteUrl: c.SiteUrl(),
SitePreview: c.SitePreview(),
SiteTitle: c.SiteTitle(),
SiteCaption: c.SiteCaption(),
SiteDescription: c.SiteDescription(),
@ -167,6 +169,7 @@ func (c *Config) GuestConfig() ClientConfig {
Flags: "readonly public shared",
Name: c.Name(),
SiteUrl: c.SiteUrl(),
SitePreview: c.SitePreview(),
SiteTitle: c.SiteTitle(),
SiteCaption: c.SiteCaption(),
SiteDescription: c.SiteDescription(),
@ -200,6 +203,7 @@ func (c *Config) UserConfig() ClientConfig {
Flags: strings.Join(c.Flags(), " "),
Name: c.Name(),
SiteUrl: c.SiteUrl(),
SitePreview: c.SitePreview(),
SiteTitle: c.SiteTitle(),
SiteCaption: c.SiteCaption(),
SiteDescription: c.SiteDescription(),

View file

@ -3,6 +3,7 @@ package config
import (
"context"
"runtime"
"strings"
"sync"
"time"
@ -110,6 +111,19 @@ func (c *Config) SiteUrl() string {
return c.params.SiteUrl
}
// SitePreview returns the site preview image URL for sharing.
func (c *Config) SitePreview() string {
if c.params.SitePreview == "" {
return c.SiteUrl() + "static/img/preview.jpg"
}
if !strings.HasPrefix(c.params.SitePreview, "http") {
return c.SiteUrl() + c.params.SitePreview
}
return c.params.SitePreview
}
// SiteTitle returns the main site title (default is application name).
func (c *Config) SiteTitle() string {
if c.params.SiteTitle == "" {

View file

@ -40,7 +40,6 @@ var GlobalFlags = []cli.Flag{
cli.StringFlag{
Name: "webdav-password",
Usage: "WebDAV password (none to disable)",
Value: "",
EnvVar: "PHOTOPRISM_WEBDAV_PASSWORD",
},
cli.IntFlag{
@ -55,10 +54,15 @@ var GlobalFlags = []cli.Flag{
},
cli.StringFlag{
Name: "site-url",
Usage: "canonical / public site URL",
Usage: "public site `URL`",
Value: "http://localhost:2342/",
EnvVar: "PHOTOPRISM_SITE_URL",
},
cli.StringFlag{
Name: "site-preview",
Usage: "public preview image `URL`",
EnvVar: "PHOTOPRISM_SITE_PREVIEW",
},
cli.StringFlag{
Name: "site-title",
Usage: "site title",
@ -67,31 +71,29 @@ var GlobalFlags = []cli.Flag{
},
cli.StringFlag{
Name: "site-caption",
Usage: "short caption / tagline",
Usage: "short site caption",
Value: "Browse Your Life",
EnvVar: "PHOTOPRISM_SITE_CAPTION",
},
cli.StringFlag{
Name: "site-description",
Usage: "long site description",
Value: "Open-Source Personal Photo Management.",
EnvVar: "PHOTOPRISM_SITE_DESCRIPTION",
},
cli.StringFlag{
Name: "site-author",
Usage: "site owner / copyright",
Value: "Anonymous",
Usage: "site artist or copyright",
EnvVar: "PHOTOPRISM_SITE_AUTHOR",
},
cli.IntFlag{
Name: "http-port",
Value: 2342,
Usage: "HTTP server port",
Usage: "http server port `NUMBER``",
EnvVar: "PHOTOPRISM_HTTP_PORT",
},
cli.StringFlag{
Name: "http-host",
Usage: "HTTP server host",
Usage: "http server `IP` address",
EnvVar: "PHOTOPRISM_HTTP_HOST",
},
cli.StringFlag{
@ -101,14 +103,13 @@ var GlobalFlags = []cli.Flag{
},
cli.StringFlag{
Name: "database-driver",
Usage: "database `DRIVER` (sqlite or mysql)",
Usage: "database driver `NAME` (sqlite or mysql)",
Value: "sqlite",
EnvVar: "PHOTOPRISM_DATABASE_DRIVER",
},
cli.StringFlag{
Name: "database-dsn",
Usage: "database data source or file name (`DSN`)",
Value: "",
Usage: "data source or file name (`DSN`)",
EnvVar: "PHOTOPRISM_DATABASE_DSN",
},
cli.IntFlag{
@ -120,31 +121,27 @@ var GlobalFlags = []cli.Flag{
cli.StringFlag{
Name: "assets-path",
Usage: "assets `PATH` for static files like templates and TensorFlow models",
Value: "",
EnvVar: "PHOTOPRISM_ASSETS_PATH",
},
cli.StringFlag{
Name: "storage-path",
Usage: "storage `PATH` for generated files like cache and index",
Value: "",
EnvVar: "PHOTOPRISM_STORAGE_PATH",
},
cli.StringFlag{
Name: "import-path",
Usage: "optional import `PATH` for copying files to originals",
Value: "",
EnvVar: "PHOTOPRISM_IMPORT_PATH",
},
cli.StringFlag{
Name: "originals-path",
Usage: "originals `PATH` for photo, video and sidecar files",
Value: "",
EnvVar: "PHOTOPRISM_ORIGINALS_PATH",
},
cli.IntFlag{
Name: "originals-limit",
Value: 1000,
Usage: "file `SIZE` limit for originals in MB",
Usage: "file size limit for originals in `MEGABYTE`",
EnvVar: "PHOTOPRISM_ORIGINALS_LIMIT",
},
cli.StringFlag{
@ -163,30 +160,25 @@ var GlobalFlags = []cli.Flag{
Name: "pid-filename",
Usage: "filename for the server process id (pid)",
EnvVar: "PHOTOPRISM_PID_FILENAME",
Value: "",
},
cli.StringFlag{
Name: "cache-path",
Usage: "cache `PATH`",
Value: "",
EnvVar: "PHOTOPRISM_CACHE_PATH",
},
cli.StringFlag{
Name: "temp-path",
Usage: "temporary `PATH` for uploads and downloads",
Value: "",
EnvVar: "PHOTOPRISM_TEMP_PATH",
},
cli.StringFlag{
Name: "config-file, c",
Usage: "load configuration from `FILENAME`",
Value: "",
EnvVar: "PHOTOPRISM_CONFIG_FILE",
},
cli.StringFlag{
Name: "settings-path",
Usage: "settings `PATH`",
Value: "",
EnvVar: "PHOTOPRISM_SETTINGS_PATH",
},
cli.BoolFlag{
@ -242,7 +234,6 @@ var GlobalFlags = []cli.Flag{
cli.StringFlag{
Name: "sidecar-path",
Usage: "storage `PATH` for automatically created sidecar files (relative or absolute)",
Value: "",
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
},
cli.BoolFlag{
@ -263,19 +254,18 @@ var GlobalFlags = []cli.Flag{
},
cli.StringFlag{
Name: "download-token",
Usage: "url `TOKEN` for file downloads",
Value: "",
Usage: "`SECRET` url token for file downloads",
EnvVar: "PHOTOPRISM_DOWNLOAD_TOKEN",
},
cli.StringFlag{
Name: "preview-token",
Usage: "url `TOKEN` for thumbnails and video streaming",
Usage: "`SECRET` url token for thumbnails and video streaming",
Value: "static",
EnvVar: "PHOTOPRISM_PREVIEW_TOKEN",
},
cli.StringFlag{
Name: "thumb-filter, f",
Usage: "resample filter (best to worst: blackman, lanczos, cubic, linear)",
Usage: "resample filter `NAME` (best to worst: blackman, lanczos, cubic, linear)",
Value: "lanczos",
EnvVar: "PHOTOPRISM_THUMB_FILTER",
},
@ -286,19 +276,19 @@ var GlobalFlags = []cli.Flag{
},
cli.IntFlag{
Name: "thumb-size, s",
Usage: "default thumbnail size limit in pixels (720-3840)",
Usage: "default thumbnail size limit in `PIXELS` (720-3840)",
Value: 2048,
EnvVar: "PHOTOPRISM_THUMB_SIZE",
},
cli.IntFlag{
Name: "thumb-limit, x",
Usage: "on-demand thumbnail size limit in pixels (720-3840)",
Usage: "on-demand thumbnail size limit in `PIXELS` (720-3840)",
Value: 3840,
EnvVar: "PHOTOPRISM_THUMB_LIMIT",
},
cli.IntFlag{
Name: "jpeg-quality, q",
Usage: "set to 95 for high-quality thumbnails (25-100)",
Usage: "choose 95 for high-quality thumbnails (25-100)",
Value: 90,
EnvVar: "PHOTOPRISM_JPEG_QUALITY",
},

View file

@ -32,6 +32,7 @@ type Params struct {
Version string
Copyright string
SiteUrl string `yaml:"site-url" flag:"site-url"`
SitePreview string `yaml:"site-preview" flag:"site-preview"`
SiteTitle string `yaml:"site-title" flag:"site-title"`
SiteCaption string `yaml:"site-caption" flag:"site-caption"`
SiteDescription string `yaml:"site-description" flag:"site-description"`

View file

@ -21,7 +21,7 @@ func TestLink_Expired(t *testing.T) {
link := NewLink("st9lxuqxpogaaba1", true, false)
link.ModifiedAt = Timestamp().Add(-7* Day)
link.ModifiedAt = Timestamp().Add(-7 * Day)
link.ShareExpires = 0
assert.False(t, link.Expired())

View file

@ -25,7 +25,6 @@ type PhotoSearch struct {
Public bool `form:"public"`
Private bool `form:"private"`
Favorite bool `form:"favorite"`
Safe bool `form:"safe"`
Lat float32 `form:"lat"`
Lng float32 `form:"lng"`
Dist uint `form:"dist"`

View file

@ -30,7 +30,6 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.CreateSession(v1)
api.DeleteSession(v1)
api.GetPreview(v1)
api.GetThumbnail(v1)
api.GetDownload(v1)
api.GetVideo(v1)
@ -115,10 +114,11 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
api.Websocket(v1)
}
// Link sharing.
share := router.Group("/s")
// Configure link sharing.
s := router.Group("/s")
{
api.InitShare(share)
api.Shares(s)
api.SharePreview(s)
}
// WebDAV server for file management, sync and sharing.