Compare commits
No commits in common. "dev" and "go" have entirely different histories.
@ -1,22 +0,0 @@
|
|||||||
image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
@ -1,22 +0,0 @@
|
|||||||
image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
178
.drone.yml
178
.drone.yml
@ -1,178 +0,0 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-arm64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add git go-bindata
|
|
||||||
- go generate -v
|
|
||||||
- go get -v -d
|
|
||||||
- go build -v -ldflags="-s -w" -o youp0m
|
|
||||||
- wget -O Dockerfile https://ankh.serekh.nemunai.re/local/Dockerfile-youp0m
|
|
||||||
- wget -O entrypoint.sh https://ankh.serekh.nemunai.re/local/entrypoint.sh-youp0m && chmod +x entrypoint.sh
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: nemunaire/youp0m
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
experimental: true
|
|
||||||
squash: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish on nemunai.re
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
registry: registry.nemunai.re
|
|
||||||
repo: registry.nemunai.re/youp0m
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
experimental: true
|
|
||||||
squash: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_nemunai.re_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_nemunai.re_password
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-amd64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add git go-bindata
|
|
||||||
- go generate -v
|
|
||||||
- go get -v -d
|
|
||||||
- go build -v -ldflags="-s -w" -o youp0m
|
|
||||||
- wget -O Dockerfile https://ankh.serekh.nemunai.re/local/Dockerfile-youp0m
|
|
||||||
- wget -O entrypoint.sh https://ankh.serekh.nemunai.re/local/entrypoint.sh-youp0m && chmod +x entrypoint.sh
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: nemunaire/youp0m
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
experimental: true
|
|
||||||
squash: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish on nemunai.re
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
registry: registry.nemunai.re
|
|
||||||
repo: registry.nemunai.re/youp0m
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
experimental: true
|
|
||||||
squash: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_nemunai.re_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_nemunai.re_password
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-arm
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add git go-bindata
|
|
||||||
- go generate -v
|
|
||||||
- go get -v -d
|
|
||||||
- go build -v -ldflags="-s -w" -o youp0m
|
|
||||||
- wget -O Dockerfile https://ankh.serekh.nemunai.re/local/Dockerfile-youp0m
|
|
||||||
- wget -O entrypoint.sh https://ankh.serekh.nemunai.re/local/entrypoint.sh-youp0m && chmod +x entrypoint.sh
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/docker:linux-arm
|
|
||||||
settings:
|
|
||||||
repo: nemunaire/youp0m
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
experimental: true
|
|
||||||
squash: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish on nemunai.re
|
|
||||||
image: plugins/docker:linux-arm
|
|
||||||
settings:
|
|
||||||
registry: registry.nemunai.re
|
|
||||||
repo: registry.nemunai.re/youp0m
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
experimental: true
|
|
||||||
squash: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_nemunai.re_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_nemunai.re_password
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: docker-manifest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: publish
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish on nemunai.re
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-local.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_nemunai.re_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_nemunai.re_password
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- build-arm
|
|
||||||
- build-arm64
|
|
||||||
- build-amd64
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
youp0m
|
|
||||||
vendor
|
|
76
api.go
76
api.go
@ -2,48 +2,25 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DispatchFunction func(*User, []string, io.ReadCloser) (interface{}, error)
|
type DispatchFunction func([]string, []byte) (interface{}, error)
|
||||||
|
|
||||||
type apiHandler struct {
|
var apiRouting = map[string]*(map[string]DispatchFunction){
|
||||||
PE *PictureExplorer
|
"version": &ApiVersionRouting,
|
||||||
Authenticate func(*http.Request) *User
|
|
||||||
routes map[string](map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIHandler(pe *PictureExplorer, authenticate func(*http.Request) *User) *apiHandler {
|
func ApiRouting(w http.ResponseWriter, r *http.Request) {
|
||||||
return &apiHandler{
|
log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent())
|
||||||
pe,
|
|
||||||
authenticate,
|
|
||||||
map[string](map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
}){
|
|
||||||
"images": ApiImagesRouting(pe),
|
|
||||||
"next": ApiNextImagesRouting(pe),
|
|
||||||
"version": ApiVersionRouting,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
user := a.Authenticate(r)
|
|
||||||
|
|
||||||
log.Printf("Handling %s API request from %s: %s %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, user, r.UserAgent())
|
|
||||||
|
|
||||||
// Extract URL arguments
|
// Extract URL arguments
|
||||||
var sURL = strings.Split(r.URL.Path[1:], "/")
|
var sURL = strings.Split(r.URL.Path, "/")
|
||||||
if sURL[len(sURL)-1] == "" && len(sURL) > 0 {
|
if sURL[len(sURL)-1] == "" && len(sURL) > 2 {
|
||||||
// Remove trailing /
|
// Remove trailing /
|
||||||
sURL = sURL[:len(sURL)-1]
|
sURL = sURL[:len(sURL)-1]
|
||||||
}
|
}
|
||||||
@ -53,27 +30,36 @@ func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
var ret interface{}
|
var ret interface{}
|
||||||
var err error = nil
|
var err error = nil
|
||||||
|
|
||||||
// Refuse too large requests
|
// Read the body
|
||||||
if r.ContentLength < 0 || r.ContentLength > 10485760 || ((r.Method == "DELETE" || r.Method == "GET" || r.Method == "HEAD") && r.ContentLength > 0) {
|
if r.ContentLength < 0 || r.ContentLength > 10485760 {
|
||||||
http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge)
|
http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var body []byte
|
||||||
|
if r.ContentLength > 0 {
|
||||||
|
tmp := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
n, err := r.Body.Read(tmp)
|
||||||
|
for j := 0; j < n; j++ {
|
||||||
|
body = append(body, tmp[j])
|
||||||
|
}
|
||||||
|
if err != nil || n <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Route request
|
// Route request
|
||||||
if len(sURL) == 0 {
|
if len(sURL) > 2 {
|
||||||
err = errors.New(fmt.Sprintf("No action provided"))
|
if h, ok := apiRouting[sURL[2]]; ok {
|
||||||
} else if h, ok := a.routes[sURL[0]]; ok {
|
if f, ok := (*h)[r.Method]; ok {
|
||||||
if f, ok := h[r.Method]; ok {
|
ret, err = f(sURL[3:], body)
|
||||||
if f.AuthFunction(user, sURL[1:]) {
|
|
||||||
ret, err = f.DispatchFunction(user, sURL[1:], r.Body)
|
|
||||||
} else {
|
} else {
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"YouP0m\"")
|
err = errors.New(fmt.Sprintf("Invalid action (%s) provided for %s.", r.Method, sURL[2]))
|
||||||
http.Error(w, "{errmsg:\"You are not allowed to do this request.\"}", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
err = errors.New(fmt.Sprintf("Invalid action (%s) provided for %s.", r.Method, sURL[0]))
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err = errors.New(fmt.Sprintf("No action provided for %s", sURL[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format response
|
// Format response
|
||||||
@ -89,7 +75,7 @@ func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if j, err := json.Marshal(ret); err != nil {
|
if j, err := json.Marshal(ret); err != nil {
|
||||||
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("{errmsg:\"%q\"}", err), http.StatusInternalServerError)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(resStatus)
|
w.WriteHeader(resStatus)
|
||||||
w.Write(j)
|
w.Write(j)
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ApiImagesRouting(pe *PictureExplorer) map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
} {
|
|
||||||
return map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
}{
|
|
||||||
"GET": {PublicPage, pe.listImages},
|
|
||||||
"POST": {PublicPage, pe.addImage},
|
|
||||||
"DELETE": {PrivatePage, pe.hideImage},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApiNextImagesRouting(pe *PictureExplorer) map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
} {
|
|
||||||
return map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
}{
|
|
||||||
"GET": {PrivatePage, pe.listNextImages},
|
|
||||||
"POST": {PublicPage, pe.addImage},
|
|
||||||
"DELETE": {PrivatePage, pe.deleteImage},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pe *PictureExplorer) listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return pe.GetPublishedImages()
|
|
||||||
} else if args[0] == "last" {
|
|
||||||
return pe.GetLastImage()
|
|
||||||
} else {
|
|
||||||
return pe.GetPublishedImage(args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pe *PictureExplorer) listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return pe.GetNextImages()
|
|
||||||
} else if len(args) >= 2 && args[1] == "publish" {
|
|
||||||
if pict, err := pe.GetNextImage(args[0]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if err := pe.Publish(pict); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return pe.GetNextImage(args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pe *PictureExplorer) addImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return nil, errors.New("Need an image identifier to create")
|
|
||||||
} else if err := pe.AddImage(args[0], body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pe *PictureExplorer) hideImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return nil, errors.New("Need an image identifier to delete")
|
|
||||||
} else if pict, err := pe.GetPublishedImage(args[0]); err != nil {
|
|
||||||
return nil, errors.New("No matching image")
|
|
||||||
} else if err := pe.Unpublish(pict); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pe *PictureExplorer) deleteImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return nil, errors.New("Need an image identifier to delete")
|
|
||||||
} else if pict, err := pe.GetNextImage(args[0]); err != nil {
|
|
||||||
return nil, errors.New("No matching image")
|
|
||||||
} else if err := pe.Remove(pict); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
9
api_version.go
Normal file
9
api_version.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
var ApiVersionRouting = map[string]DispatchFunction{
|
||||||
|
"GET": showVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
func showVersion(args []string, body []byte) (interface{}, error) {
|
||||||
|
return map[string]interface{}{"version": 0.1}, nil
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
//go:build dev
|
|
||||||
// +build dev
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Assets http.FileSystem
|
|
||||||
StaticDir string = "static/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.StringVar(&StaticDir, "static", StaticDir, "Directory containing static files")
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeStaticOptions() error {
|
|
||||||
StaticDir, _ = filepath.Abs(StaticDir)
|
|
||||||
if _, err := os.Stat(StaticDir); os.IsNotExist(err) {
|
|
||||||
StaticDir, _ = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "static"))
|
|
||||||
if _, err := os.Stat(StaticDir); os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assets = http.Dir(StaticDir)
|
|
||||||
return nil
|
|
||||||
}
|
|
28
assets.go
28
assets.go
@ -1,28 +0,0 @@
|
|||||||
//go:build !dev
|
|
||||||
// +build !dev
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed static/* static/js/* static/css/*
|
|
||||||
var _assets embed.FS
|
|
||||||
|
|
||||||
var Assets http.FileSystem
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sub, err := fs.Sub(_assets, "static")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to cd to static/ directory:", err)
|
|
||||||
}
|
|
||||||
Assets = http.FS(sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeStaticOptions() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
76
auth.go
76
auth.go
@ -1,76 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gitlab.com/nyarla/go-crypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Htpasswd struct {
|
|
||||||
entries map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHtpasswd(path string) (*Htpasswd, error) {
|
|
||||||
if fd, err := os.Open(path); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
htpasswd := Htpasswd{
|
|
||||||
map[string]string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(fd)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.SplitN(strings.TrimSpace(scanner.Text()), ":", 2)
|
|
||||||
if len(line) == 2 && len(line[1]) > 2 {
|
|
||||||
htpasswd.entries[line[0]] = line[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &htpasswd, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Htpasswd) Authenticate(username, password string) *User {
|
|
||||||
if hash, ok := h.entries[username]; !ok {
|
|
||||||
return nil
|
|
||||||
} else if crypt.Crypt(password, hash[:2]) != hash {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
var u = User{username}
|
|
||||||
return &u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request authentication
|
|
||||||
|
|
||||||
func Authenticate(htpasswd Htpasswd, r *http.Request) *User {
|
|
||||||
// Authenticate the user if any
|
|
||||||
if username, password, ok := r.BasicAuth(); ok {
|
|
||||||
return htpasswd.Authenticate(username, password)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Page rules
|
|
||||||
|
|
||||||
type AuthFunction func(*User, []string) bool
|
|
||||||
|
|
||||||
func PublicPage(u *User, args []string) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrivatePage(u *User, args []string) bool {
|
|
||||||
return u != nil
|
|
||||||
}
|
|
44
backend.go
44
backend.go
@ -1,44 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var existing_backends []string
|
|
||||||
|
|
||||||
type BackendSelector string
|
|
||||||
|
|
||||||
func (s *BackendSelector) String() string {
|
|
||||||
return string(*s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BackendSelector) Set(v string) error {
|
|
||||||
found := false
|
|
||||||
for _, b := range existing_backends {
|
|
||||||
if strings.ToLower(v) == b {
|
|
||||||
found = true
|
|
||||||
tmp := BackendSelector(v)
|
|
||||||
s = &tmp
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("%q is not a known file backend (existing backends: %v)", v, existing_backends)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileBackend interface {
|
|
||||||
DeletePicture(string, string) error
|
|
||||||
ServeFile() http.Handler
|
|
||||||
GetPicture(string, string, io.Writer) error
|
|
||||||
GetPictureInfo(string, string) (*Picture, error)
|
|
||||||
ListPictures(string) ([]*Picture, error)
|
|
||||||
MovePicture(string, string, string) error
|
|
||||||
PutPicture(string, string, *image.Image) error
|
|
||||||
}
|
|
106
backend_local.go
106
backend_local.go
@ -1,106 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/jpeg"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
existing_backends = append(existing_backends, "local")
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocalFileBackend struct {
|
|
||||||
BaseDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) getPath(box, name string) string {
|
|
||||||
return path.Join(l.BaseDir, box, name+".jpg")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) DeletePicture(box, name string) error {
|
|
||||||
return os.Remove(l.getPath(box, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) ServeFile() http.Handler {
|
|
||||||
return http.FileServer(http.Dir(l.BaseDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) createPictureInfo(local_path, name string, file os.FileInfo) *Picture {
|
|
||||||
return &Picture{
|
|
||||||
local_path, // Path
|
|
||||||
name, // Basename
|
|
||||||
name[:len(name)-len(path.Ext(name))], // Sanitized filename
|
|
||||||
file.ModTime(), // UploadTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) GetPicture(box, name string, w io.Writer) error {
|
|
||||||
local_path := l.getPath(box, name)
|
|
||||||
|
|
||||||
fd, err := os.Open(local_path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(w, fd)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) GetPictureInfo(box, name string) (*Picture, error) {
|
|
||||||
local_path := l.getPath(box, name)
|
|
||||||
|
|
||||||
file, err := os.Stat(local_path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.createPictureInfo(path.Join(box, name+".jpg"), name, file), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) ListPictures(box string) ([]*Picture, error) {
|
|
||||||
files, err := ioutil.ReadDir(path.Join(l.BaseDir, box))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pictures := make([]*Picture, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
if !file.IsDir() {
|
|
||||||
pictures = append(pictures, l.createPictureInfo(path.Join(box, file.Name()), file.Name(), file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pictures, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) MovePicture(box_from, box_to, name string) error {
|
|
||||||
newpath := l.getPath(box_to, name)
|
|
||||||
|
|
||||||
err := os.Rename(
|
|
||||||
l.getPath(box_from, name),
|
|
||||||
newpath,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Chtimes(newpath, time.Now(), time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LocalFileBackend) PutPicture(box, name string, img *image.Image) error {
|
|
||||||
fd, err := os.OpenFile(l.getPath(box, name), os.O_RDWR|os.O_CREATE, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
return jpeg.Encode(fd, *img, nil)
|
|
||||||
}
|
|
272
backend_s3.go
272
backend_s3.go
@ -1,272 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/jpeg"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
s3_endpoint string
|
|
||||||
s3_region = "us"
|
|
||||||
s3_bucket string
|
|
||||||
s3_access_key string
|
|
||||||
s3_secret_key string
|
|
||||||
s3_path_style bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
existing_backends = append(existing_backends, "s3")
|
|
||||||
|
|
||||||
if endpoint, ok := os.LookupEnv("S3_ENDPOINT"); ok {
|
|
||||||
backend = "s3"
|
|
||||||
s3_endpoint = endpoint
|
|
||||||
}
|
|
||||||
if region, ok := os.LookupEnv("S3_REGION"); ok {
|
|
||||||
backend = "s3"
|
|
||||||
s3_region = region
|
|
||||||
}
|
|
||||||
s3_bucket, _ = os.LookupEnv("S3_BUCKET")
|
|
||||||
s3_access_key, _ = os.LookupEnv("S3_ACCESS_KEY")
|
|
||||||
s3_secret_key, _ = os.LookupEnv("S3_SECRET_KEY")
|
|
||||||
if path_style, ok := os.LookupEnv("S3_PATH_STYLE"); ok {
|
|
||||||
s3_path_style = path_style == "1" || path_style == "ON" || path_style == "on" || path_style == "TRUE" || path_style == "true" || path_style == "yes" || path_style == "YES"
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.StringVar(&s3_endpoint, "s3-endpoint", s3_endpoint, "When using S3 backend, endpoint to use")
|
|
||||||
flag.StringVar(&s3_region, "s3-region", s3_region, "When using S3 backend, region to use")
|
|
||||||
flag.StringVar(&s3_bucket, "s3-bucket", s3_bucket, "When using S3 backend, bucket to use")
|
|
||||||
flag.StringVar(&s3_access_key, "s3-access-key", s3_access_key, "When using S3 backend, Access Key")
|
|
||||||
flag.StringVar(&s3_secret_key, "s3-secret-key", s3_secret_key, "When using S3 backend, Secret Key")
|
|
||||||
flag.BoolVar(&s3_path_style, "s3-path-style", s3_path_style, "When using S3 backend, force path style (when using minio)")
|
|
||||||
}
|
|
||||||
|
|
||||||
type S3FileBackend struct {
|
|
||||||
Endpoint string
|
|
||||||
Region string
|
|
||||||
Bucket string
|
|
||||||
AccessKey string
|
|
||||||
SecretKey string
|
|
||||||
PathStyle bool
|
|
||||||
BaseDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) getPath(box, name string) string {
|
|
||||||
if l.BaseDir == "" {
|
|
||||||
return path.Join(box, name+".jpg")
|
|
||||||
} else {
|
|
||||||
return path.Join(l.BaseDir, box, name+".jpg")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) newSession() (*session.Session, error) {
|
|
||||||
return session.NewSession(&aws.Config{
|
|
||||||
Credentials: credentials.NewStaticCredentials(l.AccessKey, l.SecretKey, ""),
|
|
||||||
Endpoint: &l.Endpoint,
|
|
||||||
Region: &l.Region,
|
|
||||||
S3ForcePathStyle: &l.PathStyle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) DeletePicture(box, name string) error {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(l.getPath(box, name))
|
|
||||||
|
|
||||||
input := &s3.DeleteObjectsInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
Delete: &s3.Delete{
|
|
||||||
Objects: []*s3.ObjectIdentifier{
|
|
||||||
{
|
|
||||||
Key: aws.String(l.getPath(box, name)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s3.New(s).DeleteObjects(input)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) ServeFile() http.Handler {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := s3.New(s).GetObject(&s3.GetObjectInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
Key: aws.String(r.URL.Path),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
io.Copy(w, result.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) createPictureInfo(local_path, name string, file *s3.Object) *Picture {
|
|
||||||
return &Picture{
|
|
||||||
local_path, // Path
|
|
||||||
name, // Basename
|
|
||||||
name[:len(name)-len(path.Ext(name))], // Sanitized filename
|
|
||||||
*file.LastModified, // UploadTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) GetPicture(box, name string, w io.Writer) error {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s3_path := l.getPath(box, name)
|
|
||||||
|
|
||||||
input := &s3.GetObjectInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
Key: aws.String(s3_path),
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := s3.New(s).GetObject(input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(w, result.Body)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) GetPictureInfo(box, name string) (*Picture, error) {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
input := &s3.ListObjectsInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
Prefix: aws.String(l.getPath(box, name)),
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := s3.New(s).ListObjects(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var pictures []*Picture
|
|
||||||
for _, file := range result.Contents {
|
|
||||||
pictures = append(pictures, l.createPictureInfo(*file.Key, path.Base(*file.Key), file))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pictures) == 0 {
|
|
||||||
return nil, fmt.Errorf("Object not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pictures[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) ListPictures(box string) ([]*Picture, error) {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
input := &s3.ListObjectsInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.BaseDir == "" {
|
|
||||||
input.Prefix = aws.String(box)
|
|
||||||
} else {
|
|
||||||
input.Prefix = aws.String(path.Join(l.BaseDir, box))
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := s3.New(s).ListObjects(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pictures := make([]*Picture, 0)
|
|
||||||
for _, file := range result.Contents {
|
|
||||||
pictures = append(pictures, l.createPictureInfo(path.Join(box, *file.Key), path.Base(*file.Key), file))
|
|
||||||
}
|
|
||||||
|
|
||||||
return pictures, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) MovePicture(box_from, box_to, name string) error {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(l.getPath(box_from, name), l.getPath(box_to, name))
|
|
||||||
|
|
||||||
_, err = s3.New(s).CopyObject(&s3.CopyObjectInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
CopySource: aws.String(path.Join("", l.Bucket, l.getPath(box_from, name))),
|
|
||||||
Key: aws.String(l.getPath(box_to, name)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(l.getPath(box_from, name))
|
|
||||||
|
|
||||||
input := &s3.DeleteObjectsInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
Delete: &s3.Delete{
|
|
||||||
Objects: []*s3.ObjectIdentifier{
|
|
||||||
{
|
|
||||||
Key: aws.String(l.getPath(box_from, name)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s3.New(s).DeleteObjects(input)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *S3FileBackend) PutPicture(box, name string, img *image.Image) error {
|
|
||||||
s, err := l.newSession()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bbuf := new(bytes.Buffer)
|
|
||||||
err = jpeg.Encode(bbuf, *img, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s3manager.NewUploader(s).Upload(&s3manager.UploadInput{
|
|
||||||
Bucket: aws.String(l.Bucket),
|
|
||||||
ACL: aws.String("public-read"),
|
|
||||||
Key: aws.String(l.getPath(box, name)),
|
|
||||||
Body: bbuf,
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
1
contents/config.json
Normal file
1
contents/config.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name":"YouP0m"}
|
9
go.mod
9
go.mod
@ -1,9 +0,0 @@
|
|||||||
module git.nemunai.re/youp0m
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/aws/aws-sdk-go v1.44.136
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b
|
|
||||||
)
|
|
108
go.sum
108
go.sum
@ -1,108 +0,0 @@
|
|||||||
github.com/aws/aws-sdk-go v1.44.91 h1:SRWmuX7PTyhBdLuvSfM7KWrWISJsrRsUPcFDSFduRxY=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.91/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.95 h1:QwmA+PeR6v4pF0f/dPHVPWGAshAhb9TnGZBTM5uKuI8=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.95/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.96 h1:S9paaqnJ0AJ95t5AB+iK8RM6YNZN0W0Lek1gOVJsEr8=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.96/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.97 h1:lxgxp7d6uuGsP7jHKIX3GHd7ExFigCIF04VuKf8XUII=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.97/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.98 h1:fX+NxebSdO/9T6DTNOLhpC+Vv6RNkKRfsMg0a7o/yBo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.98/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.99 h1:ITZ9q/fmH+Ksaz2TbyMU2d19vOOWs/hAlt8NbXAieHw=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.99/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.100 h1:7I86bWNQB+HGDT5z/dJy61J7qgbgLoZ7O51C9eL6hrA=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.100/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.101 h1:O/em5aIxKI/FkwcWAFKEY+JhPDCRsqoVUC6xEF4tGic=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.101/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.102 h1:6tUCTGL2UDbFZae1TLGk8vTgeXuzkb8KbAe2FiAeKHc=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.102/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.103 h1:tbhBHKgiZSIUkG8FcHy3wYKpPVvp65Wn7ZiX0B8phpY=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.103/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.104 h1:NiPYL60aOSH0TsAzQngx/aBdxC12TXhgw07DQFh76GU=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.104/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.115 h1:qFYIx97cT3k54Bn/lfM6idHbqRHILJyG0SY/0qlKiG0=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.115/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.117 h1:mZuODB3Y4soG9QWAXyGb2po+6Easa/enifpj4MnZ91s=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.117/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.118 h1:FJOqIRTukf7+Ulp047/k7JB6eqMXNnj7eb+coORThHQ=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.118/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.119 h1:TPkpDsanBMcZaF5wHwpKhjkapRV/b7d2qdC+a+IPbmY=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.119/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.120 h1:dsOxGf17H9hCVCA4aWpFWEcJMHkX+Uw7l4pGcxb27wM=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.120/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.121 h1:ahBRUqUp4qLyGmSM5KKn+TVpZkRmtuLxTWw+6Hq/ebs=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.121/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.123 h1:+vVGJ7+vQU6/wRcgRwSBBrIuG/lLL/0LB3HlN5jFv3c=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.123/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.124 h1:Xe1WQRUUekZf6ZFm3SD0vplB/AP/hymVqMiRS9LQRIs=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.124/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.125 h1:yIyCs6HX1BOj6SFTirvBwVM1tTfplKrJOyilIZPtKV8=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.125/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.126 h1:7HQJw2DNiwpxqMe2H7odGNT2rhO4SRrUe5/8dYXl0Jk=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.126/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.128 h1:X34pX5t0LIZXjBY11yf9JKMP3c1aZgirh+5PjtaZyJ4=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.128/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.129 h1:yld8Rc8OCahLtenY1mnve4w1jVeBu/rSiscGzodaDOs=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.129/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.130 h1:a/qwOxmYJF47xTZvTjECSJXnfRbjegb3YxvCXfETtnY=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.130/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.132 h1:+IjL9VoR0OXScQ5gyme9xjcolwUkd3uaH144f4Ao+4s=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.132/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.133 h1:+pWxt9nyKc0jf33rORBaQ93KPjYpmIIy3ozVXdJ82Oo=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.133/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.135 h1:DJJP/CkEpgafA5p5jlY9VzDRyKrfABVixzIxrK/3tWU=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.135/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.136 h1:J1KJJssa8pjU8jETYUxwRS37KTcxjACfKd9GK8t+5ZU=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.136/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
56
images.go
56
images.go
@ -1,56 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type imagesHandler struct {
|
|
||||||
name string
|
|
||||||
auth func(*http.Request) bool
|
|
||||||
hndlr http.Handler
|
|
||||||
getter func(fname string) (*Picture, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImagesHandler(name string, auth func(*http.Request) bool, hndlr http.Handler, getter func(fname string) (*Picture, error)) imagesHandler {
|
|
||||||
return imagesHandler{name, auth, hndlr, getter}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i imagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
log.Printf("Handling %s %s request from %s: %s [%s]\n", r.Method, i.name, r.RemoteAddr, r.URL.Path, r.UserAgent())
|
|
||||||
|
|
||||||
// Don't handle subdirectories
|
|
||||||
if strings.Contains(r.URL.Path[1:], "/") {
|
|
||||||
http.Error(w, "Image not found.", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refuse most methods
|
|
||||||
if r.Method != "GET" && r.Method != "HEAD" {
|
|
||||||
http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refuse content
|
|
||||||
if r.ContentLength != 0 {
|
|
||||||
http.Error(w, "This request doesn't accept content.", http.StatusRequestEntityTooLarge)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check rights
|
|
||||||
if !i.auth(r) {
|
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"YouP0m\"")
|
|
||||||
http.Error(w, "You are not allowed to perform this request.", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search the picture
|
|
||||||
if pict, err := i.getter(r.URL.Path[1:]); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
r.URL.Path = "/" + pict.path
|
|
||||||
i.hndlr.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
150
main.go
150
main.go
@ -1,129 +1,49 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mux = http.NewServeMux()
|
func readConfig(path string) (map[string]string, error) {
|
||||||
|
var config map[string]string
|
||||||
|
|
||||||
var (
|
if cnt_raw, err := ioutil.ReadFile(path); err != nil {
|
||||||
ThumbsDir string
|
return nil, err
|
||||||
backend = "local"
|
} else if err := json.Unmarshal(cnt_raw, &config); err != nil {
|
||||||
)
|
return nil, err
|
||||||
|
|
||||||
func main() {
|
|
||||||
bind := flag.String("bind", ":8080", "Bind port/socket")
|
|
||||||
htpasswd_file := flag.String("htpasswd", "", "Admin passwords file, Apache htpasswd format")
|
|
||||||
publishedImgDir := flag.String("publishedimgdir", "published/", "Directory where save published pictures")
|
|
||||||
nextImgDir := flag.String("nextimgdir", "next/", "Directory where save pictures to review")
|
|
||||||
baseDir := flag.String("basedir", "images/", "Local base directory where find published and next dirs")
|
|
||||||
flag.StringVar(&backend, "storage-backend", backend, fmt.Sprintf("Storage backend to use (between: %s)", existing_backends))
|
|
||||||
flag.StringVar(&ThumbsDir, "thumbsdir", "./images/thumbs/", "Directory where generate thumbs")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
htpasswd := &Htpasswd{}
|
|
||||||
|
|
||||||
if htpasswd_file != nil && *htpasswd_file != "" {
|
|
||||||
log.Println("Reading htpasswd file...")
|
|
||||||
var err error
|
|
||||||
if htpasswd, err = NewHtpasswd(*htpasswd_file); htpasswd == nil {
|
|
||||||
log.Fatal("Unable to parse htpasswd:", err)
|
|
||||||
}
|
|
||||||
} else if *nextImgDir == "next/" {
|
|
||||||
log.Println("Disable admin interface, images will be published without moderation")
|
|
||||||
nextImgDir = publishedImgDir
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Checking paths...")
|
|
||||||
if _, err := os.Stat(ThumbsDir); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(ThumbsDir, 0755); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Registering handlers...")
|
|
||||||
authFunc := func(r *http.Request) *User { return Authenticate(*htpasswd, r) }
|
|
||||||
|
|
||||||
if err := sanitizeStaticOptions(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.URL.Path = "/"
|
|
||||||
http.FileServer(Assets).ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
var storage_backend FileBackend
|
|
||||||
if backend == "local" {
|
|
||||||
storage_backend = &LocalFileBackend{*baseDir}
|
|
||||||
if _, err := os.Stat(path.Join(*baseDir, *publishedImgDir)); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(path.Join(*baseDir, *publishedImgDir), 0755); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(path.Join(*baseDir, *nextImgDir)); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(path.Join(*baseDir, *nextImgDir), 0755); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if backend == "s3" {
|
|
||||||
storage_backend = &S3FileBackend{s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, *baseDir}
|
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("%q is not a valid storage backend.", backend)
|
return config, nil
|
||||||
}
|
}
|
||||||
log.Printf("Using %s storage backend", backend)
|
}
|
||||||
|
|
||||||
pe := &PictureExplorer{
|
func main() {
|
||||||
FileBackend: storage_backend,
|
var port = flag.Int("port", 8080, "Listening port")
|
||||||
PublishedImgDir: *publishedImgDir,
|
var sitedir = flag.String("sitedir", "./contents/", "Directory containing site content and pictures")
|
||||||
NextImgDir: *nextImgDir,
|
flag.Parse()
|
||||||
}
|
|
||||||
|
log.Println("Reading site configuration...")
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", NewAPIHandler(pe, authFunc)))
|
if config, err := readConfig(filepath.Join(*sitedir, "config.json")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
mux.Handle("/images/", http.StripPrefix("/images", ImagesHandler(
|
os.Exit(1)
|
||||||
"images",
|
} else {
|
||||||
func(*http.Request) bool { return true },
|
log.Println("Registering handlers...")
|
||||||
pe.ServeFile(),
|
mux := http.NewServeMux()
|
||||||
pe.GetPublishedImage,
|
mux.Handle("/favicon.ico", http.FileServer(http.Dir("./static/")))
|
||||||
)))
|
mux.Handle("/static/", http.FileServer(http.Dir("./")))
|
||||||
mux.Handle("/images/next/", http.StripPrefix("/images/next", ImagesHandler(
|
mux.Handle("/doc/", http.FileServer(http.Dir("./")))
|
||||||
"next",
|
mux.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir(filepath.Join(*sitedir, "current")))))
|
||||||
func(r *http.Request) bool { return authFunc(r) != nil },
|
mux.HandleFunc("/api/", ApiRouting)
|
||||||
pe.ServeFile(),
|
mux.HandleFunc("/", SeeRouting)
|
||||||
pe.GetNextImage,
|
http.HandleFunc("/", mux.ServeHTTP)
|
||||||
)))
|
|
||||||
|
log.Println(fmt.Sprintf("Site ready (n=%s), listening on port %d", config["name"], *port))
|
||||||
mux.Handle("/images/thumbs/", http.StripPrefix("/images/thumbs", ImagesHandler(
|
http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
|
||||||
"thumbs",
|
|
||||||
func(*http.Request) bool { return true },
|
|
||||||
http.FileServer(http.Dir(ThumbsDir)),
|
|
||||||
pe.GetPublishedImage,
|
|
||||||
)))
|
|
||||||
mux.Handle("/images/next/thumbs/", http.StripPrefix("/images/next/thumbs", ImagesHandler(
|
|
||||||
"nexthumbs",
|
|
||||||
func(r *http.Request) bool { return authFunc(r) != nil },
|
|
||||||
http.FileServer(http.Dir(ThumbsDir)),
|
|
||||||
pe.GetNextImage,
|
|
||||||
)))
|
|
||||||
|
|
||||||
mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if authFunc(r) == nil {
|
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"YouP0m\"")
|
|
||||||
http.Error(w, "You are not allowed to perform this request.", http.StatusUnauthorized)
|
|
||||||
} else {
|
|
||||||
r.URL.Path = "/admin.html"
|
|
||||||
http.FileServer(Assets).ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Println("Ready, listening on", *bind)
|
|
||||||
if err := http.ListenAndServe(*bind, mux); err != nil {
|
|
||||||
log.Fatal("Unable to listen and serve: ", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
120
picture.go
120
picture.go
@ -1,120 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
_ "image/gif"
|
|
||||||
"image/jpeg"
|
|
||||||
_ "image/png"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/nfnt/resize"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PictureExplorer struct {
|
|
||||||
FileBackend
|
|
||||||
PublishedImgDir string
|
|
||||||
NextImgDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Picture struct {
|
|
||||||
path string
|
|
||||||
basename string
|
|
||||||
Name string `json:"name"`
|
|
||||||
UploadTime time.Time `json:"upload_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ByUploadTime []*Picture
|
|
||||||
|
|
||||||
func (a ByUploadTime) Len() int { return len(a) }
|
|
||||||
func (a ByUploadTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a ByUploadTime) Less(i, j int) bool {
|
|
||||||
return a[i].UploadTime.Sub(a[j].UploadTime).Nanoseconds() < 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) GetNextImages() (pictures []*Picture, err error) {
|
|
||||||
pictures, err = e.ListPictures(e.NextImgDir)
|
|
||||||
sort.Sort(ByUploadTime(pictures))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) GetPublishedImages() (pictures []*Picture, err error) {
|
|
||||||
pictures, err = e.ListPictures(e.PublishedImgDir)
|
|
||||||
sort.Sort(ByUploadTime(pictures))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) GetLastImage() (*Picture, error) {
|
|
||||||
picts, err := e.GetPublishedImages()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return picts[len(picts)-1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) GetPublishedImage(fname string) (*Picture, error) {
|
|
||||||
return e.GetPictureInfo(e.PublishedImgDir, fname)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) GetNextImage(fname string) (*Picture, error) {
|
|
||||||
return e.GetPictureInfo(e.NextImgDir, fname)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) IsUniqueName(filename string) bool {
|
|
||||||
if pict, _ := e.GetPublishedImage(filename); pict != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if pict, _ := e.GetNextImage(filename); pict != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) AddImage(filename string, blob io.ReadCloser) error {
|
|
||||||
// Check the name is not already used
|
|
||||||
if ok := e.IsUniqueName(filename); !ok {
|
|
||||||
return errors.New("This filename is already used, please choose another one.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to JPEG
|
|
||||||
img, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, blob))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save file
|
|
||||||
if err := e.PutPicture(e.NextImgDir, filename, &img); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3)
|
|
||||||
|
|
||||||
// Save thumbnail
|
|
||||||
fw, err := os.Create(path.Join(ThumbsDir, filename+".jpg"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fw.Close()
|
|
||||||
|
|
||||||
return jpeg.Encode(fw, thumb, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) Publish(p *Picture) error {
|
|
||||||
return e.MovePicture(e.NextImgDir, e.PublishedImgDir, p.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) Unpublish(p *Picture) error {
|
|
||||||
return e.MovePicture(e.PublishedImgDir, e.NextImgDir, p.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PictureExplorer) Remove(p *Picture) error {
|
|
||||||
return e.DeletePicture(path.Dir(p.path), p.Name)
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchPackageNames": ["github.com/aws/aws-sdk-go"],
|
|
||||||
"automerge": true,
|
|
||||||
"automergeType": "branch"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
44
see.go
Normal file
44
see.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SeeRouting(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Printf("Handling request %s: %s\n", r.Method, r.URL.Path)
|
||||||
|
|
||||||
|
// Extract URL arguments
|
||||||
|
var sURL = strings.Split(r.URL.Path, "/")
|
||||||
|
if sURL[len(sURL)-1] == "" && len(sURL) > 2 {
|
||||||
|
// Remove trailing /
|
||||||
|
sURL = sURL[:len(sURL)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sURL) < 3 {
|
||||||
|
http.Error(w, "Please provide a valid hash", http.StatusForbidden)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
||||||
|
w.Write([]byte(`<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OhSnap</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="stylesheet" media="all" href="/static/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<figure class="big">
|
||||||
|
<img src="/images/` + html.EscapeString("1") + `.jpg" alt="` + html.EscapeString("name") + `">`))
|
||||||
|
if "name" != "" {
|
||||||
|
w.Write([]byte(`<figcaption>` + html.EscapeString("name") + `</figcaption>`))
|
||||||
|
}
|
||||||
|
w.Write([]byte(` </figure>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
}
|
||||||
|
}
|
14
static.go
14
static.go
@ -1,14 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
mux.HandleFunc("/css/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.FileServer(Assets).ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
mux.HandleFunc("/js/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.FileServer(Assets).ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>YouP0m Administration</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
||||||
<link rel="stylesheet" media="all" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome on YouP0m!</h1>
|
|
||||||
<noscript>
|
|
||||||
<p>
|
|
||||||
You need a JavaScript capable browser to use this website.<br>
|
|
||||||
Please enable-it or switch to a JavaScript capable browser.
|
|
||||||
</p>
|
|
||||||
</noscript>
|
|
||||||
<script type="text/javascript">
|
|
||||||
document.write("<h2>We are loading the newest cute pictures for you, please wait…</h2>");
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="/js/youp0m.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/youp0m-admin.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,74 +1,39 @@
|
|||||||
body {
|
body {
|
||||||
background: #181818;
|
background: #222;
|
||||||
color: #ffffff;
|
color: #EEE;
|
||||||
font-familly: sans-serif;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure.big {
|
figure.big {
|
||||||
display: table-cell;
|
display: table;
|
||||||
height: 100vh;
|
margin: auto;
|
||||||
width: 100vw;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
figure.moz {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
height: 190px;
|
|
||||||
width: 300px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure.moz .menu {
|
|
||||||
position: absolute;
|
|
||||||
margin: inherit;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
figure.moz:hover .menu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
figure.moz:hover .menu a {
|
|
||||||
background: rgba(0,0,0,0.5);
|
|
||||||
color: #eee;
|
|
||||||
padding: 6px 10px 8px 10px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
figure.moz .menu a:hover {
|
|
||||||
background: rgba(0,0,0,0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.big img {
|
.big img {
|
||||||
max-height: calc(100vh - 5px);
|
height: calc(100vh - 20px);
|
||||||
max-width: calc(100vw - 5px);
|
max-width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.moz img {
|
img
|
||||||
max-height: 190px;
|
{
|
||||||
max-width: 300px;
|
border-radius: 20px 0 20px 0;
|
||||||
|
box-shadow: 0px 0px 10px #9AB;
|
||||||
|
transition: box-shadow 1s ease-out;
|
||||||
|
-moz-transition: box-shadow 1.23s ease-out;
|
||||||
|
}
|
||||||
|
img:hover
|
||||||
|
{
|
||||||
|
display: table-cell;
|
||||||
|
box-shadow: 0px 0px 20px #FED;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
.big figcaption
|
||||||
box-shadow: 0px 0px 10px #9AB;
|
{
|
||||||
transition: box-shadow 1s ease-out;
|
|
||||||
}
|
|
||||||
img:hover {
|
|
||||||
box-shadow: 0px 0px 20px #FED;
|
|
||||||
}
|
|
||||||
|
|
||||||
.big figcaption {
|
|
||||||
background: #656565;
|
background: #656565;
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>YouP0m</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
||||||
<link rel="stylesheet" media="all" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Welcome on YouP0m!</h1>
|
|
||||||
<noscript>
|
|
||||||
<p>
|
|
||||||
You need a JavaScript capable browser to use this website.<br>
|
|
||||||
Please enable-it or switch to a JavaScript capable browser.
|
|
||||||
</p>
|
|
||||||
</noscript>
|
|
||||||
<script type="text/javascript">
|
|
||||||
document.write("<h2>We are loading a cute picture just for you, please wait…</h2>");
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript" src="/js/youp0m.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,53 +0,0 @@
|
|||||||
api_url = "/api/next/";
|
|
||||||
img_url = "/images/next/";
|
|
||||||
|
|
||||||
function publish_pict(pict) {
|
|
||||||
fetch(api_url + "/" + pict.name + "/publish").then(response => {
|
|
||||||
picts = null;
|
|
||||||
sync()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function delete_pict(pict) {
|
|
||||||
fetch(api_url + "/" + pict.name, { method: "DELETE" }).then(response => {
|
|
||||||
picts = null;
|
|
||||||
sync()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function admin_ctrl(pict) {
|
|
||||||
var figure = document.createElement("figure");
|
|
||||||
figure.className = "moz";
|
|
||||||
var img = document.createElement("img");
|
|
||||||
img.src = img_url + pict.name;
|
|
||||||
img.alt = pict.name;
|
|
||||||
var div = document.createElement("div");
|
|
||||||
div.className = "menu"
|
|
||||||
var linkPub = document.createElement("a");
|
|
||||||
linkPub.onclick = function(e) {
|
|
||||||
publish_pict(pict);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
linkPub.href = "#";
|
|
||||||
linkPub.innerHTML = "Publier";
|
|
||||||
var linkDel = document.createElement("a");
|
|
||||||
linkDel.onclick = function(e) {
|
|
||||||
delete_pict(pict);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
linkDel.href = "#";
|
|
||||||
linkDel.innerHTML = "Supprimer";
|
|
||||||
div.appendChild(linkPub);
|
|
||||||
div.appendChild(linkDel);
|
|
||||||
figure.appendChild(div);
|
|
||||||
figure.appendChild(img);
|
|
||||||
document.body.appendChild(figure);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sync() {
|
|
||||||
path = window.location.pathname.replace(/.*\/([^\/]*)/, "$1");
|
|
||||||
if (path == "" || path == "all")
|
|
||||||
show_mozaic(admin_ctrl);
|
|
||||||
else
|
|
||||||
show_picture(path);
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
var picts = null;
|
|
||||||
var api_url = "/api/images";
|
|
||||||
var img_url = "/images/";
|
|
||||||
|
|
||||||
function get_picts(then, then_value) {
|
|
||||||
fetch(api_url)
|
|
||||||
.then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(my_picts) {
|
|
||||||
picts = my_picts;
|
|
||||||
if (then) then(then_value);
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
display_error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function show_mozaic(callpict) {
|
|
||||||
if (!picts) {
|
|
||||||
get_picts(show_mozaic, callpict);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!picts.length) {
|
|
||||||
document.body.innerHTML = '<h1>Welcome on YouP0m!</h1><h2>There is no image currently, come back soon!</h2>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callpict) {
|
|
||||||
callpict = function (pict) {
|
|
||||||
var figure = document.createElement("figure");
|
|
||||||
figure.className = "moz";
|
|
||||||
var img = document.createElement("img");
|
|
||||||
img.src = img_url + pict.name;
|
|
||||||
img.alt = pict.name;
|
|
||||||
var link = document.createElement("a");
|
|
||||||
link.onclick = function(e) {
|
|
||||||
window.history.pushState(null, "YouP0m", link.href);
|
|
||||||
sync();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
link.href = "/" + pict.name;
|
|
||||||
link.appendChild(img);
|
|
||||||
figure.appendChild(link);
|
|
||||||
document.body.appendChild(figure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (document.body.hasChildNodes())
|
|
||||||
document.body.removeChild(document.body.lastChild);
|
|
||||||
|
|
||||||
picts.forEach(callpict);
|
|
||||||
}
|
|
||||||
|
|
||||||
function show_picture(id) {
|
|
||||||
if (!picts) {
|
|
||||||
get_picts(show_picture, id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (picts.length == 0) {
|
|
||||||
document.body.innerHTML = '<h1>Welcome on YouP0m!</h1><h2>There is no image currently, come back soon!</h2>';
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == null || id == "")
|
|
||||||
id = picts[Math.floor(Math.random() * picts.length)].name;
|
|
||||||
|
|
||||||
var i = parseInt(id);
|
|
||||||
if (!isNaN(i) && i < picts.length) {
|
|
||||||
display_picture(picts[i], (picts.length+i-1)%picts.length);
|
|
||||||
} else {
|
|
||||||
var found = false;
|
|
||||||
picts.forEach(function (pict, index) {
|
|
||||||
if (pict.name == id)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
display_picture(pict, picts[(picts.length+index-1)%picts.length].name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!found) {
|
|
||||||
get_picture(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_picture(id) {
|
|
||||||
if (id == null) id = "last";
|
|
||||||
|
|
||||||
fetch(api_url + "/" + id)
|
|
||||||
.then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(my_pict) {
|
|
||||||
display_picture(my_pict);
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
display_error(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function display_picture(pict, next) {
|
|
||||||
while (document.body.hasChildNodes()) document.body.removeChild(document.body.lastChild);
|
|
||||||
|
|
||||||
var figure = document.createElement("figure");
|
|
||||||
figure.className = "big";
|
|
||||||
var img = document.createElement("img");
|
|
||||||
img.src = img_url + pict.name;
|
|
||||||
img.alt = pict.name;
|
|
||||||
|
|
||||||
if (next != null) {
|
|
||||||
var link = document.createElement("a");
|
|
||||||
link.onclick = function(e) {
|
|
||||||
window.history.pushState(null, "YouP0m", link.href);
|
|
||||||
sync();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
link.href = "/" + next;
|
|
||||||
link.appendChild(img);
|
|
||||||
figure.appendChild(link);
|
|
||||||
} else {
|
|
||||||
figure.appendChild(img);
|
|
||||||
}
|
|
||||||
document.body.appendChild(figure);
|
|
||||||
}
|
|
||||||
|
|
||||||
function display_error(msg) {
|
|
||||||
document.body.innerHTML = '<h1>An error occurs</h1><h2>' + msg + '</h2>';
|
|
||||||
}
|
|
||||||
|
|
||||||
function sync() {
|
|
||||||
path = window.location.pathname.slice(1);
|
|
||||||
if (path == "all")
|
|
||||||
show_mozaic();
|
|
||||||
else
|
|
||||||
show_picture(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onpopstate = sync;
|
|
||||||
document.onreadystatechange = function() {
|
|
||||||
if (document.readyState == "complete")
|
|
||||||
sync();
|
|
||||||
}
|
|
24
version.go
24
version.go
@ -1,24 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
const VERSION = 0.3
|
|
||||||
|
|
||||||
var ApiVersionRouting = map[string]struct {
|
|
||||||
AuthFunction
|
|
||||||
DispatchFunction
|
|
||||||
}{
|
|
||||||
"GET": {PublicPage, showVersion},
|
|
||||||
}
|
|
||||||
|
|
||||||
func showVersion(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
|
||||||
m := map[string]interface{}{"version": VERSION}
|
|
||||||
|
|
||||||
if u != nil {
|
|
||||||
m["youare"] = *u
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user