diff --git a/.drone-manifest-local.yml b/.drone-manifest-local.yml deleted file mode 100644 index d9da4dd..0000000 --- a/.drone-manifest-local.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest.yml b/.drone-manifest.yml deleted file mode 100644 index ade2314..0000000 --- a/.drone-manifest.yml +++ /dev/null @@ -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 diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 358f8fc..0000000 --- a/.drone.yml +++ /dev/null @@ -1,130 +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 -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-arm64 -- build-amd64 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 656e38e..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -youp0m -vendor \ No newline at end of file diff --git a/api.go b/api.go index 6fcd695..66cecd0 100644 --- a/api.go +++ b/api.go @@ -12,31 +12,21 @@ import ( type DispatchFunction func(*User, []string, io.ReadCloser) (interface{}, error) +var apiRoutes = map[string]*(map[string]struct{AuthFunction;DispatchFunction}){ + "images": &ApiImagesRouting, + "next": &ApiNextImagesRouting, + "version": &ApiVersionRouting, +} + type apiHandler struct { - PE *PictureExplorer - Authenticate func(*http.Request) *User - routes map[string](map[string]struct { - AuthFunction - DispatchFunction - }) + Authenticate func(*http.Request) (*User) } -func NewAPIHandler(pe *PictureExplorer, authenticate func(*http.Request) *User) *apiHandler { - return &apiHandler{ - pe, - authenticate, - map[string](map[string]struct { - AuthFunction - DispatchFunction - }){ - "images": ApiImagesRouting(pe), - "next": ApiNextImagesRouting(pe), - "version": ApiVersionRouting, - }, - } +func ApiHandler(Authenticate func(*http.Request) (*User)) (apiHandler) { + return apiHandler{Authenticate} } -func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +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()) @@ -62,8 +52,8 @@ func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Route request if len(sURL) == 0 { err = errors.New(fmt.Sprintf("No action provided")) - } else if h, ok := a.routes[sURL[0]]; ok { - if f, ok := h[r.Method]; ok { + } else if h, ok := apiRoutes[sURL[0]]; ok { + if f, ok := (*h)[r.Method]; ok { if f.AuthFunction(user, sURL[1:]) { ret, err = f.DispatchFunction(user, sURL[1:], r.Body) } else { diff --git a/api_images.go b/api_images.go index 685215b..422b767 100644 --- a/api_images.go +++ b/api_images.go @@ -5,88 +5,72 @@ import ( "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}, - } +var ApiImagesRouting = map[string]struct{AuthFunction; DispatchFunction}{ + "GET": {PublicPage, listImages}, + "POST": {PublicPage, addImage}, + "DELETE": {PrivatePage, 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}, - } +var ApiNextImagesRouting = map[string]struct{AuthFunction; DispatchFunction}{ + "GET": {PrivatePage, listNextImages}, + "POST": {PublicPage, addImage}, + "DELETE": {PrivatePage, deleteImage}, } -func (pe *PictureExplorer) listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) { +func listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) { if len(args) < 1 { - return pe.GetPublishedImages() + return GetPublishedImages() } else if args[0] == "last" { - return pe.GetLastImage() + return GetLastImage() } else { - return pe.GetPublishedImage(args[0]) + return GetPublishedImage(args[0]) } } -func (pe *PictureExplorer) listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) { +func listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) { if len(args) < 1 { - return pe.GetNextImages() + return GetNextImages() } else if len(args) >= 2 && args[1] == "publish" { - if pict, err := pe.GetNextImage(args[0]); err != nil { + if pict, err := GetNextImage(args[0]); err != nil { return nil, err - } else if err := pe.Publish(pict); err != nil { + } else if err := pict.Publish(); err != nil { return nil, err } else { return true, nil } } else { - return pe.GetNextImage(args[0]) + return GetNextImage(args[0]) } } -func (pe *PictureExplorer) addImage(u *User, args []string, body io.ReadCloser) (interface{}, error) { +func 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 { + } else if err := 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) { +func 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 { + } else if pict, err := GetPublishedImage(args[0]); err != nil { return nil, errors.New("No matching image") - } else if err := pe.Unpublish(pict); err != nil { + } else if err := pict.Unpublish(); err != nil { return nil, err } else { return true, nil } } -func (pe *PictureExplorer) deleteImage(u *User, args []string, body io.ReadCloser) (interface{}, error) { +func 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 { + } else if pict, err := GetNextImage(args[0]); err != nil { return nil, errors.New("No matching image") - } else if err := pe.Remove(pict); err != nil { + } else if err := pict.Remove(); err != nil { return nil, err } else { return true, nil diff --git a/assets-dev.go b/assets-dev.go deleted file mode 100644 index 549607f..0000000 --- a/assets-dev.go +++ /dev/null @@ -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 -} diff --git a/assets.go b/assets.go deleted file mode 100644 index 1841ebe..0000000 --- a/assets.go +++ /dev/null @@ -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 -} diff --git a/auth.go b/auth.go index 0860bd9..96fe98d 100644 --- a/auth.go +++ b/auth.go @@ -6,7 +6,7 @@ import ( "os" "strings" - "gitlab.com/nyarla/go-crypt" + "github.com/nyarla/go-crypt" ) type User struct { @@ -52,9 +52,10 @@ func (h Htpasswd) Authenticate(username, password string) *User { } } + /// Request authentication -func Authenticate(htpasswd Htpasswd, r *http.Request) *User { +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) @@ -63,6 +64,7 @@ func Authenticate(htpasswd Htpasswd, r *http.Request) *User { } } + /// Page rules type AuthFunction func(*User, []string) bool diff --git a/backend.go b/backend.go deleted file mode 100644 index e5d2073..0000000 --- a/backend.go +++ /dev/null @@ -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 -} diff --git a/backend_local.go b/backend_local.go deleted file mode 100644 index 55aa353..0000000 --- a/backend_local.go +++ /dev/null @@ -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) -} diff --git a/backend_s3.go b/backend_s3.go deleted file mode 100644 index 4d6c589..0000000 --- a/backend_s3.go +++ /dev/null @@ -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 -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 20b88a8..0000000 --- a/go.mod +++ /dev/null @@ -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 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 7195d1a..0000000 --- a/go.sum +++ /dev/null @@ -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= diff --git a/images.go b/images.go index af74ce7..a18baf6 100644 --- a/images.go +++ b/images.go @@ -6,14 +6,14 @@ import ( "strings" ) -type imagesHandler struct { +type imagesHandler struct{ name string - auth func(*http.Request) bool + auth func(*http.Request) (bool) hndlr http.Handler - getter func(fname string) (*Picture, error) + 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 { +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} } @@ -39,7 +39,7 @@ func (i imagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Check rights - if !i.auth(r) { + 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 @@ -50,7 +50,7 @@ func (i imagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusNotFound) return } else { - r.URL.Path = "/" + pict.path + r.URL.Path = "/" + pict.basename i.hndlr.ServeHTTP(w, r) } } diff --git a/main.go b/main.go index 7203564..1ca07d2 100644 --- a/main.go +++ b/main.go @@ -2,31 +2,27 @@ package main import ( "flag" - "fmt" "log" "net/http" "os" - "path" + "path/filepath" ) -var mux = http.NewServeMux() - -var ( - ThumbsDir string - backend = "local" -) +var PublishedImgDir string +var NextImgDir string +var ThumbsDir string 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(&PublishedImgDir, "publishedimgdir", "./images/published/", "Directory where save published pictures") + flag.StringVar(&NextImgDir, "nextimgdir", "./images/next/", "Directory where save pictures to review") flag.StringVar(&ThumbsDir, "thumbsdir", "./images/thumbs/", "Directory where generate thumbs") flag.Parse() - htpasswd := &Htpasswd{} + htpasswd := &Htpasswd{ + entries: map[string]string{"admin": "2fClb0C8dIphk"}, + } if htpasswd_file != nil && *htpasswd_file != "" { log.Println("Reading htpasswd file...") @@ -34,12 +30,21 @@ func main() { 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 + } else { + log.Println("Using default credentials for administrative part: admin:admin") } log.Println("Checking paths...") + if _, err := os.Stat(PublishedImgDir); os.IsNotExist(err) { + if err := os.MkdirAll(PublishedImgDir, 0755); err != nil { + log.Fatal(err) + } + } + if _, err := os.Stat(NextImgDir); os.IsNotExist(err) { + if err := os.MkdirAll(NextImgDir, 0755); err != nil { + log.Fatal(err) + } + } if _, err := os.Stat(ThumbsDir); os.IsNotExist(err) { if err := os.MkdirAll(ThumbsDir, 0755); err != nil { log.Fatal(err) @@ -47,69 +52,47 @@ func main() { } log.Println("Registering handlers...") - authFunc := func(r *http.Request) *User { return Authenticate(*htpasswd, r) } + 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) - } + staticDir, _ := filepath.Abs("static") + 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) { + 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 { - log.Fatalf("%q is not a valid storage backend.", backend) - } - log.Printf("Using %s storage backend", backend) - - pe := &PictureExplorer{ - FileBackend: storage_backend, - PublishedImgDir: *publishedImgDir, - NextImgDir: *nextImgDir, } - mux.Handle("/api/", http.StripPrefix("/api", NewAPIHandler(pe, authFunc))) + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filepath.Join(staticDir, "index.html")) }) + mux.Handle("/css/", http.FileServer(http.Dir(staticDir))) + mux.Handle("/js/", http.FileServer(http.Dir(staticDir))) + + mux.Handle("/api/", http.StripPrefix("/api", ApiHandler(authFunc))) mux.Handle("/images/", http.StripPrefix("/images", ImagesHandler( "images", - func(*http.Request) bool { return true }, - pe.ServeFile(), - pe.GetPublishedImage, + func (*http.Request) (bool){ return true; }, + http.FileServer(http.Dir(PublishedImgDir)), + GetPublishedImage, ))) mux.Handle("/images/next/", http.StripPrefix("/images/next", ImagesHandler( "next", - func(r *http.Request) bool { return authFunc(r) != nil }, - pe.ServeFile(), - pe.GetNextImage, + func (r *http.Request) (bool){ return authFunc(r) != nil; }, + http.FileServer(http.Dir(NextImgDir)), + GetNextImage, ))) mux.Handle("/images/thumbs/", http.StripPrefix("/images/thumbs", ImagesHandler( "thumbs", - func(*http.Request) bool { return true }, + func (*http.Request) (bool){ return true; }, http.FileServer(http.Dir(ThumbsDir)), - pe.GetPublishedImage, + GetPublishedImage, ))) mux.Handle("/images/next/thumbs/", http.StripPrefix("/images/next/thumbs", ImagesHandler( "nexthumbs", - func(r *http.Request) bool { return authFunc(r) != nil }, + func (r *http.Request) (bool){ return authFunc(r) != nil; }, http.FileServer(http.Dir(ThumbsDir)), - pe.GetNextImage, + GetNextImage, ))) mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) { @@ -117,8 +100,7 @@ func main() { 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) + http.ServeFile(w, r, filepath.Join(staticDir, "admin.html")) } }) diff --git a/picture.go b/picture.go index 42fb39a..ac05793 100644 --- a/picture.go +++ b/picture.go @@ -1,27 +1,22 @@ package main import ( - "encoding/base64" "errors" + "io" + "io/ioutil" "image" _ "image/gif" "image/jpeg" _ "image/png" - "io" "os" - "path" + "path/filepath" "sort" + "strconv" "time" "github.com/nfnt/resize" ) -type PictureExplorer struct { - FileBackend - PublishedImgDir string - NextImgDir string -} - type Picture struct { path string basename string @@ -29,92 +24,143 @@ type Picture struct { UploadTime time.Time `json:"upload_time"` } -type ByUploadTime []*Picture +type ByUploadTime []Picture -func (a ByUploadTime) Len() int { return len(a) } +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 (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 { +func getImages(dir string) ([]Picture, error) { + if files, err := ioutil.ReadDir(dir); err != nil { return nil, err + } else { + pictures := make([]Picture, 0) + for _, file := range files { + if !file.IsDir() { + filename := file.Name() + pictures = append(pictures, Picture{ + filepath.Join(dir, filename), // Path + filename, // Basename + filename[:len(filename) - len(filepath.Ext(filename))], // Sanitized filename + file.ModTime(), // UploadTime + }) + } + } + sort.Sort(ByUploadTime(pictures)) + return pictures, nil } - - return picts[len(picts)-1], nil } -func (e *PictureExplorer) GetPublishedImage(fname string) (*Picture, error) { - return e.GetPictureInfo(e.PublishedImgDir, fname) +func GetNextImages() ([]Picture, error) { + return getImages(NextImgDir) } -func (e *PictureExplorer) GetNextImage(fname string) (*Picture, error) { - return e.GetPictureInfo(e.NextImgDir, fname) +func GetPublishedImages() ([]Picture, error) { + return getImages(PublishedImgDir) } -func (e *PictureExplorer) IsUniqueName(filename string) bool { - if pict, _ := e.GetPublishedImage(filename); pict != nil { +func GetLastImage() (Picture, error) { + if picts, err := GetPublishedImages(); err != nil { + return Picture{}, err + } else { + return picts[len(picts)-1], nil + } +} + +func getImage(flist func() ([]Picture, error), fname string) (Picture, error) { + if picts, err := flist(); err != nil { + return Picture{}, err + } else if pid, err := strconv.Atoi(fname); err == nil { + if pid < len(picts) { + return picts[pid], nil + } else { + return Picture{}, errors.New("Invalid picture identifier") + } + } else { + for _, pict := range picts { + if pict.Name == fname || pict.basename == fname { + return pict, nil + } + } + return Picture{}, errors.New("No such picture") + } +} + +func GetPublishedImage(fname string) (Picture, error) { + return getImage(GetPublishedImages, fname) +} + +func GetNextImage(fname string) (Picture, error) { + return getImage(GetNextImages, fname) +} + +func UniqueImage(filename string) (bool) { + if pict, _ := GetPublishedImage(filename); pict.path != "" { return false + } else { + if pict, _ := GetNextImage(filename); pict.path != "" { + return false + } else { + return true + } } - - if pict, _ := e.GetNextImage(filename); pict != nil { - return false - } - - return true } -func (e *PictureExplorer) AddImage(filename string, blob io.ReadCloser) error { +func AddImage(filename string, blob io.ReadCloser) (error) { // Check the name is not already used - if ok := e.IsUniqueName(filename); !ok { + if ok := UniqueImage(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 { + } else if img, _, err := image.Decode(blob); err != nil { return err - } // Save file - if err := e.PutPicture(e.NextImgDir, filename, &img); err != nil { + } else if fw, err := os.Create(filepath.Join(NextImgDir, filename + ".jpg")); err != nil { return err + } else if err := jpeg.Encode(fw, img, nil); err != nil { + return err + } else { + fw.Close() + + thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3) + + // Save thumbnail + if fw, err := os.Create(filepath.Join(ThumbsDir, filename + ".jpg")); err != nil { + return err + } else if err := jpeg.Encode(fw, thumb, nil); err != nil { + return err + } else { + fw.Close() + } } - thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3) + return nil +} - // Save thumbnail - fw, err := os.Create(path.Join(ThumbsDir, filename+".jpg")) - if err != nil { +func (p Picture) Publish() (error) { + npath := filepath.Join(PublishedImgDir, p.basename) + if err := os.Rename(p.path, npath); err != nil { return err + } else if err := os.Chtimes(npath, time.Now(), time.Now()); err != nil { + return err + } else { + return nil } - 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 (p Picture) Unpublish() (error) { + if err := os.Rename(p.path, filepath.Join(NextImgDir, p.basename)); err != nil { + return err + } else { + return nil + } } -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) +func (p Picture) Remove() (error) { + if err := os.Remove(p.path); err != nil { + return err + } else { + return nil + } } diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 7979833..0000000 --- a/renovate.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "packageRules": [ - { - "matchPackageNames": ["github.com/aws/aws-sdk-go"], - "automerge": true, - "automergeType": "branch" - } - ] -} diff --git a/static.go b/static.go deleted file mode 100644 index f8525be..0000000 --- a/static.go +++ /dev/null @@ -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) - }) -} diff --git a/static/admin.html b/static/admin.html index 2fc8f91..c1b1445 100644 --- a/static/admin.html +++ b/static/admin.html @@ -4,6 +4,9 @@