forked from mirrors/homebox
feat: item-attachments CRUD (#22)
* change /content/ -> /homebox/ * add cache to code generators * update env variables to set data storage * update env variables * set env variables in prod container * implement attachment post route (WIP) * get attachment endpoint * attachment download * implement string utilities lib * implement generic drop zone * use explicit truncate * remove clean dir * drop strings composable for lib * update item types and add attachments * add attachment API * implement service context * consolidate API code * implement editing attachments * implement upload limit configuration * improve error handling * add docs for max upload size * fix test cases
This commit is contained in:
parent
852d312ba7
commit
31b34241e0
165 changed files with 2509 additions and 664 deletions
64
backend/pkgs/pathlib/pathlib.go
Normal file
64
backend/pkgs/pathlib/pathlib.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package pathlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dirReaderFunc func(name string) []string
|
||||
|
||||
var dirReader dirReaderFunc = func(directory string) []string {
|
||||
f, err := os.Open(directory)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func hasConflict(path string, neighbors []string) bool {
|
||||
filename := strings.ToLower(filepath.Base(path))
|
||||
|
||||
for _, n := range neighbors {
|
||||
if strings.ToLower(n) == filename {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Safe will take a destination path and return a validated path that is safe to use.
|
||||
// without overwriting any existing files. If a conflict exists, it will append a number
|
||||
// to the end of the file name. If the parent directory does not exist this function will
|
||||
// return the original path.
|
||||
func Safe(path string) string {
|
||||
parent := filepath.Dir(path)
|
||||
|
||||
neighbors := dirReader(parent)
|
||||
if neighbors == nil {
|
||||
return path
|
||||
}
|
||||
|
||||
if hasConflict(path, neighbors) {
|
||||
ext := filepath.Ext(path)
|
||||
|
||||
name := strings.TrimSuffix(filepath.Base(path), ext)
|
||||
|
||||
for i := 1; i < 1000; i++ {
|
||||
newName := fmt.Sprintf("%s (%d)%s", name, i, ext)
|
||||
newPath := filepath.Join(parent, newName)
|
||||
if !hasConflict(newPath, neighbors) {
|
||||
return newPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
94
backend/pkgs/pathlib/pathlib_test.go
Normal file
94
backend/pkgs/pathlib/pathlib_test.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package pathlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_hasConflict(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
neighbors []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no conflict",
|
||||
args: args{
|
||||
path: "foo",
|
||||
neighbors: []string{"bar", "baz"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "conflict",
|
||||
args: args{
|
||||
path: "foo",
|
||||
neighbors: []string{"bar", "foo"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "conflict with different case",
|
||||
args: args{
|
||||
path: "foo",
|
||||
neighbors: []string{"bar", "Foo"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := hasConflict(tt.args.path, tt.args.neighbors); got != tt.want {
|
||||
t.Errorf("hasConflict() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafePath(t *testing.T) {
|
||||
// override dirReader
|
||||
dirReader = func(name string) []string {
|
||||
return []string{"bar.pdf", "bar (1).pdf", "bar (2).pdf"}
|
||||
}
|
||||
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no conflict",
|
||||
args: args{
|
||||
path: "/foo/foo.pdf",
|
||||
},
|
||||
want: "/foo/foo.pdf",
|
||||
},
|
||||
{
|
||||
name: "conflict",
|
||||
args: args{
|
||||
path: "/foo/bar.pdf",
|
||||
},
|
||||
want: "/foo/bar (3).pdf",
|
||||
},
|
||||
{
|
||||
name: "conflict with different case",
|
||||
args: args{
|
||||
path: "/foo/BAR.pdf",
|
||||
},
|
||||
want: "/foo/BAR (3).pdf",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Safe(tt.args.path); got != tt.want {
|
||||
t.Errorf("SafePath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,22 +4,47 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
type ValidationError struct {
|
||||
Field string `json:"field"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type ValidationErrors []ValidationError
|
||||
|
||||
func (ve *ValidationErrors) HasErrors() bool {
|
||||
if (ve == nil) || (len(*ve) == 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, err := range *ve {
|
||||
if err.Field != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ve ValidationErrors) Append(field, reasons string) ValidationErrors {
|
||||
return append(ve, ValidationError{
|
||||
Field: field,
|
||||
Reason: reasons,
|
||||
})
|
||||
}
|
||||
|
||||
// ErrorBuilder is a helper type to build a response that contains an array of errors.
|
||||
// Typical use cases are for returning an array of validation errors back to the user.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
//
|
||||
// {
|
||||
// "errors": [
|
||||
// "invalid id",
|
||||
// "invalid name",
|
||||
// "invalid description"
|
||||
// ],
|
||||
// "message": "Unprocessable Entity",
|
||||
// "status": 422
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "errors": [
|
||||
// "invalid id",
|
||||
// "invalid name",
|
||||
// "invalid description"
|
||||
// ],
|
||||
// "message": "Unprocessable Entity",
|
||||
// "status": 422
|
||||
// }
|
||||
type ErrorBuilder struct {
|
||||
errs []string
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/hay-kot/content/backend/pkgs/faker"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/faker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue