List Backgrounds #568
|
@ -178,3 +178,19 @@ avatar:
|
|||
provider: gravatar
|
||||
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
gravatarexpiration: 3600
|
||||
|
||||
backgrounds:
|
||||
# Whether to enable backgrounds for lists at all.
|
||||
enabled: false
|
||||
providers:
|
||||
unsplash:
|
||||
# Whether to enable setting backgrounds from unsplash as list backgrounds
|
||||
enabled: false
|
||||
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
|
||||
# and set the access token below.
|
||||
accesstoken:
|
||||
# The unsplash application id is only used for pingback and required as per their api guidelines.
|
||||
# You can find the Application ID in the dashboard for your API application. It should be a numeric ID.
|
||||
# It will only show in the UI if your application has been approved for Enterprise usage, therefore if
|
||||
# you’re in Demo mode, you can also find the ID in the URL at the end: https://unsplash.com/oauth/applications/:application_id
|
||||
applicationid:
|
||||
|
|
|
@ -221,4 +221,20 @@ avatar:
|
|||
provider: gravatar
|
||||
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
gravatarexpiration: 3600
|
||||
|
||||
backgrounds:
|
||||
# Whether to enable backgrounds for lists at all.
|
||||
enabled: false
|
||||
providers:
|
||||
unsplash:
|
||||
# Whether to enable setting backgrounds from unsplash as list backgrounds
|
||||
enabled: false
|
||||
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
|
||||
# and set the access token below.
|
||||
accesstoken:
|
||||
# The unsplash application id is only used for pingback and required as per their api guidelines.
|
||||
# You can find the Application ID in the dashboard for your API application. It should be a numeric ID.
|
||||
# It will only show in the UI if your application has been approved for Enterprise usage, therefore if
|
||||
# you’re in Demo mode, you can also find the ID in the URL at the end: https://unsplash.com/oauth/applications/:application_id
|
||||
applicationid:
|
||||
{{< /highlight >}}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -47,7 +47,6 @@ require (
|
|||
github.com/lib/pq v1.5.2
|
||||
github.com/mailru/easyjson v0.7.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/onsi/ginkgo v1.12.0 // indirect
|
||||
|
|
45
go.sum
45
go.sum
|
@ -160,10 +160,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
|||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.1.1 h1:SBIfzULODQQ7JV6AD931MvAz8pnkk6QCfMIcoOBDaXQ=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.1.1/go.mod h1:RZctY24ixituGC73XlAV1gkCwYMVwiSwPm26MNlQIhE=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.1.2 h1:sACIJoknTtWHbz5wahA6f50IGsz7oBXS+jgm0eKl4eI=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.1.2/go.mod h1:RZctY24ixituGC73XlAV1gkCwYMVwiSwPm26MNlQIhE=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.2.0 h1:FGAW3z5UzmrZGjR/dZp1u3Tbld0SDmirLO4RrR5++7Q=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.2.0/go.mod h1:RZctY24ixituGC73XlAV1gkCwYMVwiSwPm26MNlQIhE=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
|
@ -184,8 +180,6 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg
|
|||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
|
@ -300,12 +294,6 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
|||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc=
|
||||
github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.5.0 h1:Hq6pEflc2Q3hP5iEH3Q6XopXrJXxjhwbvMpj9eZnpp0=
|
||||
github.com/lib/pq v1.5.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.5.1 h1:Jn6HYxiYrtQ92CopqJLvfPCJUrrruw1+1cn0jM9dKrI=
|
||||
github.com/lib/pq v1.5.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
|
||||
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
|
@ -357,8 +345,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
|
@ -403,8 +389,6 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+Ci
|
|||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
|
||||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
|
||||
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
|
@ -429,8 +413,6 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
|||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI=
|
||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
|
@ -462,8 +444,6 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
|||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU=
|
||||
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
|
@ -475,8 +455,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
||||
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -544,22 +522,6 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhi
|
|||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y=
|
||||
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200420192832-a76a400e3025 h1:U821GPBRQKVHpr6Os5UDBgF2FveIjlZYbz+e2n5wmmQ=
|
||||
golang.org/x/crypto v0.0.0-20200420192832-a76a400e3025/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a h1:y6sBfNd1b9Wy08a6K1Z1DZc4aXABUN5TKjkYhz7UKmo=
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825 h1:dSChiwOTvzwbHFTMq2l6uRardHH7/E6SqEkqccinS/o=
|
||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc h1:ZGI/fILM2+ueot/UixBSoj9188jCAxVHEZEGhqq67I4=
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -617,6 +579,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7
|
|||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
|
@ -656,13 +619,10 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
|
||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
|
@ -718,6 +678,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
|||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
|
||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
@ -781,8 +742,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
src.techknowlogick.com/xgo v0.0.0-20200408234745-bb0faa361273 h1:dE6ry9rVwDn3soD4wPCXqEG60AZTuhniZzHdnj3c+74=
|
||||
src.techknowlogick.com/xgo v0.0.0-20200408234745-bb0faa361273/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xgo v0.0.0-20200514233805-209a5cf70012 h1:k1/qGRpsaGka4IHT2UIUdPqM6+oo3O7+8S3ACN2kJZQ=
|
||||
src.techknowlogick.com/xgo v0.0.0-20200514233805-209a5cf70012/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xormigrate v1.2.0 h1:bq9JaI48bxB+OddMghicjmV7sGmBUogJq4HmTN0DOcw=
|
||||
|
|
|
@ -112,6 +112,11 @@ const (
|
|||
|
||||
AvatarProvider Key = `avatar.provider`
|
||||
AvatarGravaterExpiration Key = `avatar.gravatarexpiration`
|
||||
|
||||
BackgroundsEnabled Key = `backgrounds.enabled`
|
||||
BackgroundsUnsplashEnabled Key = `backgrounds.providers.unsplash.enabled`
|
||||
BackgroundsUnsplashAccessToken Key = `backgrounds.providers.unsplash.accesstoken`
|
||||
BackgroundsUnsplashApplicationID Key = `backgrounds.providers.unsplash.applicationid`
|
||||
)
|
||||
|
||||
// GetString returns a string config value
|
||||
|
@ -243,6 +248,9 @@ func InitDefaultConfig() {
|
|||
// Avatar
|
||||
AvatarProvider.setDefault("gravatar")
|
||||
AvatarGravaterExpiration.setDefault(3600)
|
||||
// List Backgrounds
|
||||
BackgroundsEnabled.setDefault(false)
|
||||
BackgroundsUnsplashEnabled.setDefault(false)
|
||||
}
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
|
|
|
@ -50,3 +50,20 @@ func IsErrFileIsTooLarge(err error) bool {
|
|||
_, ok := err.(ErrFileIsTooLarge)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrFileIsNotUnsplashFile defines an error where a file is not downloaded from unsplash.
|
||||
// Used in cases whenever unsplash information about a file is requested, but the file was not downloaded from unsplash.
|
||||
type ErrFileIsNotUnsplashFile struct {
|
||||
FileID int64
|
||||
}
|
||||
|
||||
// Error is the error implementation of ErrFileIsNotUnsplashFile
|
||||
func (err ErrFileIsNotUnsplashFile) Error() string {
|
||||
return fmt.Sprintf("file was not downloaded from unsplash [FileID: %d]", err.FileID)
|
||||
}
|
||||
|
||||
//IsErrFileIsNotUnsplashFile checks if an error is ErrFileIsNotUnsplashFile
|
||||
func IsErrFileIsNotUnsplashFile(err error) bool {
|
||||
_, ok := err.(ErrFileIsNotUnsplashFile)
|
||||
return ok
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type unsplashPhoto20200524221534 struct {
|
||||
ID int64 `xorm:"autoincr not null unique pk"`
|
||||
FileID int64 `xorm:"not null"`
|
||||
UnsplashID string `xorm:"varchar(50)"`
|
||||
Author string `xorm:"text"`
|
||||
AuthorName string `xorm:"text"`
|
||||
}
|
||||
|
||||
func (u unsplashPhoto20200524221534) TableName() string {
|
||||
return "unsplash_photos"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20200524221534",
|
||||
Description: "Create unsplash photo table",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(unsplashPhoto20200524221534{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.DropTables(unsplashPhoto20200524221534{})
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type list20200524224611 struct {
|
||||
BackgroundFileID int64 `xorm:"null" json:"-"`
|
||||
}
|
||||
|
||||
func (s list20200524224611) TableName() string {
|
||||
return "list"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20200524224611",
|
||||
Description: "Add background file id property to list",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(list20200524224611{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.DropTables(list20200524224611{})
|
||||
},
|
||||
})
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/metrics"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
@ -51,6 +52,11 @@ type List struct {
|
|||
// Whether or not a list is archived.
|
||||
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
|
||||
|
||||
// The id of the file this list has set as background
|
||||
BackgroundFileID int64 `xorm:"null" json:"-"`
|
||||
// Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background
|
||||
BackgroundInformation interface{} `xorm:"-" json:"background_information"`
|
||||
|
||||
// A timestamp when this list was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this list was last updated. You cannot change this value.
|
||||
|
@ -166,6 +172,16 @@ func (l *List) ReadOne() (err error) {
|
|||
l.IsArchived = true
|
||||
}
|
||||
}
|
||||
|
||||
// Get any background information if there is one set
|
||||
if l.BackgroundFileID != 0 {
|
||||
// Currently unsplash only
|
||||
l.BackgroundInformation, err = GetUnsplashPhotoByFileID(l.BackgroundFileID)
|
||||
if err != nil && !files.IsErrFileIsNotUnsplashFile(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -560,3 +576,16 @@ func (l *List) Delete() (err error) {
|
|||
_, err = x.Where("list_id = ?", l.ID).Delete(&Task{})
|
||||
return
|
||||
}
|
||||
|
||||
// SetListBackground sets a background file as list background in the db
|
||||
func SetListBackground(listID int64, background *files.File) (err error) {
|
||||
l := &List{
|
||||
ID: listID,
|
||||
BackgroundFileID: background.ID,
|
||||
}
|
||||
_, err = x.
|
||||
Where("id = ?", l.ID).
|
||||
Cols("background_file_id").
|
||||
Update(l)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
|
||||
import "code.vikunja.io/api/pkg/files"
|
||||
|
||||
// Unsplash requires us to do pingbacks to their site and also name the image author.
|
||||
// To do this properly, we need to save these information somewhere.
|
||||
|
||||
// UnsplashPhoto is an unsplash photo in the db
|
||||
type UnsplashPhoto struct {
|
||||
ID int64 `xorm:"autoincr not null unique pk" json:"id,omitempty"`
|
||||
FileID int64 `xorm:"not null" json:"-"`
|
||||
UnsplashID string `xorm:"varchar(50)" json:"unsplash_id"`
|
||||
Author string `xorm:"text" json:"author"`
|
||||
AuthorName string `xorm:"text" json:"author_name"`
|
||||
}
|
||||
|
||||
// TableName contains the table name for an unsplash photo
|
||||
func (u *UnsplashPhoto) TableName() string {
|
||||
return "unsplash_photos"
|
||||
}
|
||||
|
||||
// Save persists an unsplash photo to the db
|
||||
func (u *UnsplashPhoto) Save() error {
|
||||
_, err := x.Insert(u)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetUnsplashPhotoByFileID returns an unsplash photo by its saved file id
|
||||
func GetUnsplashPhotoByFileID(fileID int64) (u *UnsplashPhoto, err error) {
|
||||
u = &UnsplashPhoto{}
|
||||
exists, err := x.Where("file_id = ?", fileID).Get(u)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
return nil, files.ErrFileIsNotUnsplashFile{FileID: fileID}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveUnsplashPhoto removes an unsplash photo from the db
|
||||
func RemoveUnsplashPhoto(fileID int64) (err error) {
|
||||
// This is intentionally "fire and forget" which is why we don't check if we have an
|
||||
// unsplash entry for that file at all. If there is one, it will be deleted.
|
||||
// We do this to keep the function simple.
|
||||
_, err = x.Where("file_id = ?", fileID).Delete(&UnsplashPhoto{})
|
||||
return
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package background
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// Image represents an image which can be used as a list background
|
||||
type Image struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Thumb string `json:"thumb"`
|
||||
// This can be used to supply extra information from an image provider to clients
|
||||
Info interface{} `json:"info,omitempty"`
|
||||
}
|
||||
|
||||
// Provider represents something that is able to get a list of images and set one of them as background
|
||||
type Provider interface {
|
||||
// Search is used to either return a pre-defined list of Image or let the user search for an image
|
||||
Search(search string, page int64) (result []*Image, err error)
|
||||
// Set sets an image which was most likely previously obtained by Search as list background
|
||||
Set(image *Image, list *models.List, auth web.Auth) (err error)
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/background"
|
||||
"code.vikunja.io/api/pkg/modules/background/unsplash"
|
||||
v1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// BackgroundProvider represents a thing which holds a background provider
|
||||
// Lets us get a new fresh provider every time we need one.
|
||||
type BackgroundProvider struct {
|
||||
Provider func() background.Provider
|
||||
}
|
||||
|
||||
// SearchBackgrounds is the web handler to search for backgrounds
|
||||
func (bp *BackgroundProvider) SearchBackgrounds(c echo.Context) error {
|
||||
p := bp.Provider()
|
||||
|
||||
err := c.Bind(p)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided: "+err.Error())
|
||||
}
|
||||
|
||||
search := c.QueryParam("s")
|
||||
var page int64 = 1
|
||||
pg := c.QueryParam("p")
|
||||
if pg != "" {
|
||||
page, err = strconv.ParseInt(pg, 10, 64)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid page number: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
result, err := p.Search(search, page)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "An error occurred: "+err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// SetBackground sets an Image as list background
|
||||
func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
|
||||
auth, err := v1.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error())
|
||||
}
|
||||
|
||||
p := bp.Provider()
|
||||
|
||||
listID, err := strconv.ParseInt(c.Param("list"), 10, 64)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error())
|
||||
}
|
||||
|
||||
// Check if the user has the right to change the list background
|
||||
list := &models.List{ID: listID}
|
||||
can, err := list.CanUpdate(auth)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
if !can {
|
||||
log.Infof("Tried to update list background of list %d while not having the rights for it (User: %v)", listID, auth)
|
||||
return echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
image := &background.Image{}
|
||||
err = c.Bind(image)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided: "+err.Error())
|
||||
}
|
||||
|
||||
err = p.Set(image, list, auth)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
return c.JSON(http.StatusOK, list)
|
||||
}
|
||||
|
||||
// GetListBackground serves a previously set background from a list
|
||||
// It has no knowledge of the provider that was responsible for setting the background.
|
||||
// @Summary Get the list background
|
||||
// @Description Get the list background of a specific list. **Returns json on error.**
|
||||
// @tags list
|
||||
// @Produce octet-stream
|
||||
// @Param id path int true "List ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {} string "The list background file."
|
||||
// @Failure 403 {object} models.Message "No access to this list."
|
||||
// @Failure 404 {object} models.Message "The list does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/background [get]
|
||||
func GetListBackground(c echo.Context) error {
|
||||
|
||||
auth, err := v1.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error())
|
||||
}
|
||||
|
||||
listID, err := strconv.ParseInt(c.Param("list"), 10, 64)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error())
|
||||
}
|
||||
|
||||
// Check if a background for this list exists + Rights
|
||||
list := &models.List{ID: listID}
|
||||
can, err := list.CanRead(auth)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
if !can {
|
||||
log.Infof("Tried to get list background of list %d while not having the rights for it (User: %v)", listID, auth)
|
||||
return echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
if list.BackgroundFileID == 0 {
|
||||
return echo.NotFoundHandler(c)
|
||||
}
|
||||
|
||||
// Get the file
|
||||
bgFile := &files.File{
|
||||
ID: list.BackgroundFileID,
|
||||
}
|
||||
if err := bgFile.LoadFileByID(); err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Unsplash requires pingbacks as per their api usage guidelines.
|
||||
// To do this in a privacy-preserving manner, we do the ping from inside of Vikunja to not expose any user details.
|
||||
// FIXME: This should use an event once we have events
|
||||
unsplash.Pingback(bgFile)
|
||||
|
||||
// Serve the file
|
||||
return c.Stream(http.StatusOK, "image/jpg", bgFile.File)
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package unsplash
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/background"
|
||||
"code.vikunja.io/web"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Provider represents an unsplash image provider
|
||||
type Provider struct {
|
||||
}
|
||||
|
||||
// SearchResult is a search result from unsplash's api
|
||||
type SearchResult struct {
|
||||
Total int `json:"total"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Results []*Photo `json:"results"`
|
||||
}
|
||||
|
||||
// Photo represents an unpslash photo as returned by their api
|
||||
type Photo struct {
|
||||
ID string `json:"id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Color string `json:"color"`
|
||||
Description string `json:"description"`
|
||||
User struct {
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
} `json:"user"`
|
||||
Urls struct {
|
||||
Raw string `json:"raw"`
|
||||
Full string `json:"full"`
|
||||
Regular string `json:"regular"`
|
||||
Small string `json:"small"`
|
||||
Thumb string `json:"thumb"`
|
||||
} `json:"urls"`
|
||||
Links struct {
|
||||
Self string `json:"self"`
|
||||
HTML string `json:"html"`
|
||||
Download string `json:"download"`
|
||||
} `json:"links"`
|
||||
}
|
||||
|
||||
// Very simple caching method - pretty much only used to retain information when saving an image
|
||||
// FIXME: Should use a proper cache
|
||||
var photos map[string]*Photo
|
||||
|
||||
func init() {
|
||||
photos = make(map[string]*Photo)
|
||||
}
|
||||
|
||||
func doGet(url string, result interface{}) (err error) {
|
||||
req, err := http.NewRequest("GET", "https://api.unsplash.com/"+url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Client-ID "+config.BackgroundsUnsplashAccessToken.GetString())
|
||||
hc := http.Client{}
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Search is the implementation to search on unsplash
|
||||
// @Summary Search for a background from unsplash
|
||||
// @Description Search for a list background from unsplash
|
||||
// @tags list
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param s query string false "Search backgrounds from unsplash with this search term."
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Success 200 {array} background.Image "An array with photos"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /backgrounds/unsplash/search [get]
|
||||
func (p *Provider) Search(search string, page int64) (result []*background.Image, err error) {
|
||||
|
||||
// If we don't have a search query, return results from the unsplash featured collection
|
||||
if search == "" {
|
||||
collectionResult := []*Photo{}
|
||||
err = doGet("collections/317099/photos?page="+strconv.FormatInt(page, 10)+"&per_page=25&order_by=latest", &collectionResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result = []*background.Image{}
|
||||
for _, p := range collectionResult {
|
||||
result = append(result, &background.Image{
|
||||
ID: p.ID,
|
||||
URL: p.Urls.Raw,
|
||||
Thumb: p.Urls.Thumb,
|
||||
Info: &models.UnsplashPhoto{
|
||||
UnsplashID: p.ID,
|
||||
Author: p.User.Username,
|
||||
AuthorName: p.User.Name,
|
||||
},
|
||||
})
|
||||
photos[p.ID] = p
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
searchResult := &SearchResult{}
|
||||
err = doGet("search/photos?query="+search+"&page="+strconv.FormatInt(page, 10)+"&per_page=25", &searchResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result = []*background.Image{}
|
||||
for _, p := range searchResult.Results {
|
||||
result = append(result, &background.Image{
|
||||
ID: p.ID,
|
||||
URL: p.Urls.Raw,
|
||||
Thumb: p.Urls.Thumb,
|
||||
})
|
||||
photos[p.ID] = p
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set sets an unsplash photo as list background
|
||||
// @Summary Set an unsplash photo as list background
|
||||
// @Description Sets a photo from unsplash as list background.1
|
||||
// @tags list
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Param list body background.Image true "The image you want to set as background"
|
||||
// @Success 200 {object} models.List "The background has been successfully set."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid image object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/backgrounds/unsplash [post]
|
||||
func (p *Provider) Set(image *background.Image, list *models.List, auth web.Auth) (err error) {
|
||||
|
||||
// Find the photo
|
||||
var photo *Photo
|
||||
var exists bool
|
||||
photo, exists = photos[image.ID]
|
||||
if !exists {
|
||||
log.Debugf("Image information for unsplash photo %s not cached, requesting from unsplash...", image.ID)
|
||||
photo = &Photo{}
|
||||
err = doGet("photos/"+image.ID, photo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Download the photo from unsplash
|
||||
// The parameters crop the image to a max width of 2560 and a max height of 2048 to save bandwidth and storage.
|
||||
resp, err := http.Get(photo.Urls.Raw + "&w=2560&h=2048&q=90")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
log.Debugf("Downloaded Unsplash Photo %s", image.ID)
|
||||
|
||||
// Save it as a file in vikunja
|
||||
file, err := files.Create(resp.Body, "", 0, auth)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the old background if one exists
|
||||
if list.BackgroundFileID != 0 {
|
||||
file := files.File{ID: list.BackgroundFileID}
|
||||
if err := file.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := models.RemoveUnsplashPhoto(list.BackgroundFileID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save the relation that we got it from unsplash
|
||||
unsplashPhoto := &models.UnsplashPhoto{
|
||||
FileID: file.ID,
|
||||
UnsplashID: image.ID,
|
||||
Author: photo.User.Username,
|
||||
AuthorName: photo.User.Name,
|
||||
}
|
||||
err = unsplashPhoto.Save()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("Saved unsplash photo %s as file %d with new entry %d", image.ID, file.ID, unsplashPhoto.ID)
|
||||
|
||||
// Set the file in the list
|
||||
list.BackgroundFileID = file.ID
|
||||
list.BackgroundInformation = unsplashPhoto
|
||||
|
||||
// Set it as the list background
|
||||
return models.SetListBackground(list.ID, file)
|
||||
}
|
||||
|
||||
// Pingback pings the unsplash api if an unsplash photo has been accessed.
|
||||
func Pingback(f *files.File) {
|
||||
// Check if the file is actually downloaded from unsplash
|
||||
unsplashPhoto, err := models.GetUnsplashPhotoByFileID(f.ID)
|
||||
if err != nil {
|
||||
log.Errorf("Unsplash Pingback: %s", err.Error())
|
||||
}
|
||||
|
||||
// Do the ping
|
||||
if _, err := http.Get("https://views.unsplash.com/v?app_id=" + config.BackgroundsUnsplashApplicationID.GetString() + "&photo_id=" + unsplashPhoto.UnsplashID); err != nil {
|
||||
log.Errorf("Unsplash Pingback Failed: %s", err.Error())
|
||||
}
|
||||
log.Debugf("Pinged unsplash for photo %s", unsplashPhoto.UnsplashID)
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -26,14 +26,15 @@ import (
|
|||
)
|
||||
|
||||
type vikunjaInfos struct {
|
||||
Version string `json:"version"`
|
||||
FrontendURL string `json:"frontend_url"`
|
||||
Motd string `json:"motd"`
|
||||
LinkSharingEnabled bool `json:"link_sharing_enabled"`
|
||||
MaxFileSize string `json:"max_file_size"`
|
||||
RegistrationEnabled bool `json:"registration_enabled"`
|
||||
AvailableMigrators []string `json:"available_migrators"`
|
||||
TaskAttachmentsEnabled bool `json:"task_attachments_enabled"`
|
||||
Version string `json:"version"`
|
||||
FrontendURL string `json:"frontend_url"`
|
||||
Motd string `json:"motd"`
|
||||
LinkSharingEnabled bool `json:"link_sharing_enabled"`
|
||||
MaxFileSize string `json:"max_file_size"`
|
||||
RegistrationEnabled bool `json:"registration_enabled"`
|
||||
AvailableMigrators []string `json:"available_migrators"`
|
||||
TaskAttachmentsEnabled bool `json:"task_attachments_enabled"`
|
||||
EnabledBackgroundProviders []string `json:"enabled_background_providers"`
|
||||
}
|
||||
|
||||
// Info is the handler to get infos about this vikunja instance
|
||||
|
@ -44,7 +45,7 @@ type vikunjaInfos struct {
|
|||
// @Success 200 {object} v1.vikunjaInfos
|
||||
// @Router /info [get]
|
||||
func Info(c echo.Context) error {
|
||||
infos := vikunjaInfos{
|
||||
info := vikunjaInfos{
|
||||
Version: version.Version,
|
||||
FrontendURL: config.ServiceFrontendurl.GetString(),
|
||||
Motd: config.ServiceMotd.GetString(),
|
||||
|
@ -57,12 +58,18 @@ func Info(c echo.Context) error {
|
|||
// Migrators
|
||||
if config.MigrationWunderlistEnable.GetBool() {
|
||||
m := &wunderlist.Migration{}
|
||||
infos.AvailableMigrators = append(infos.AvailableMigrators, m.Name())
|
||||
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
|
||||
}
|
||||
if config.MigrationTodoistEnable.GetBool() {
|
||||
m := &todoist.Migration{}
|
||||
infos.AvailableMigrators = append(infos.AvailableMigrators, m.Name())
|
||||
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, infos)
|
||||
if config.BackgroundsEnabled.GetBool() {
|
||||
if config.BackgroundsUnsplashEnabled.GetBool() {
|
||||
info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "unsplash")
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, info)
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ import (
|
|||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/background"
|
||||
backgroundHandler "code.vikunja.io/api/pkg/modules/background/handler"
|
||||
"code.vikunja.io/api/pkg/modules/background/unsplash"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler"
|
||||
"code.vikunja.io/api/pkg/modules/migration/todoist"
|
||||
|
@ -444,6 +447,20 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
}
|
||||
todoistMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
|
||||
// List Backgrounds
|
||||
if config.BackgroundsEnabled.GetBool() {
|
||||
a.GET("/lists/:list/background", backgroundHandler.GetListBackground)
|
||||
if config.BackgroundsUnsplashEnabled.GetBool() {
|
||||
unsplashBackgroundProvider := &backgroundHandler.BackgroundProvider{
|
||||
Provider: func() background.Provider {
|
||||
return &unsplash.Provider{}
|
||||
},
|
||||
}
|
||||
a.GET("/backgrounds/unsplash/search", unsplashBackgroundProvider.SearchBackgrounds)
|
||||
a.POST("/lists/:list/backgrounds/unsplash", unsplashBackgroundProvider.SetBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerCalDavRoutes(c *echo.Group) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
module github.com/hashicorp/hcl
|
||||
|
||||
require github.com/davecgh/go-spew v1.1.1
|
||||
|
||||
go 1.13
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
module github.com/spf13/afero
|
||||
|
||||
require golang.org/x/text v0.3.0
|
||||
|
||||
go 1.13
|
||||
|
|
Loading…
Reference in New Issue