Compare commits
10 Commits
a6f5bc3192
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b7b3e3c68 | ||
|
|
7ce2104b61 | ||
|
|
002595a407 | ||
|
|
4d8f75f7c9 | ||
|
|
3a3ee3912d | ||
|
|
cf3a289a54 | ||
|
|
ec5223d530 | ||
|
|
25a43c8a0b | ||
|
|
c2cb535105 | ||
|
|
d67654e601 |
@@ -4,6 +4,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"local/args"
|
"local/args"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -13,6 +14,7 @@ type Config struct {
|
|||||||
Database string
|
Database string
|
||||||
Driver []string
|
Driver []string
|
||||||
FilePrefix string
|
FilePrefix string
|
||||||
|
APIPrefix string
|
||||||
FileRoot string
|
FileRoot string
|
||||||
Auth bool
|
Auth bool
|
||||||
AuthLifetime time.Duration
|
AuthLifetime time.Duration
|
||||||
@@ -20,6 +22,7 @@ type Config struct {
|
|||||||
RPS int
|
RPS int
|
||||||
SysRPS int
|
SysRPS int
|
||||||
Delay time.Duration
|
Delay time.Duration
|
||||||
|
StaticRoot string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() Config {
|
func New() Config {
|
||||||
@@ -33,6 +36,8 @@ func New() Config {
|
|||||||
|
|
||||||
as.Append(args.INT, "p", "port to listen on", 18114)
|
as.Append(args.INT, "p", "port to listen on", 18114)
|
||||||
as.Append(args.STRING, "fileprefix", "path prefix for file service", "/__files__")
|
as.Append(args.STRING, "fileprefix", "path prefix for file service", "/__files__")
|
||||||
|
as.Append(args.STRING, "api-prefix", "path prefix for api", "api")
|
||||||
|
as.Append(args.STRING, "static-root", "path to the root of a static file server", "./public")
|
||||||
as.Append(args.STRING, "fileroot", "path to file hosting root", "/tmp/")
|
as.Append(args.STRING, "fileroot", "path to file hosting root", "/tmp/")
|
||||||
as.Append(args.STRING, "database", "database name to use", "db")
|
as.Append(args.STRING, "database", "database name to use", "db")
|
||||||
as.Append(args.STRING, "driver", "database driver args to use, like [local/storage.Type,arg1,arg2...] or [/path/to/boltdb]", "map")
|
as.Append(args.STRING, "driver", "database driver args to use, like [local/storage.Type,arg1,arg2...] or [/path/to/boltdb]", "map")
|
||||||
@@ -60,5 +65,7 @@ func New() Config {
|
|||||||
MaxFileSize: int64(as.GetInt("max-file-size")),
|
MaxFileSize: int64(as.GetInt("max-file-size")),
|
||||||
RPS: as.GetInt("rps"),
|
RPS: as.GetInt("rps"),
|
||||||
SysRPS: as.GetInt("sys-rps"),
|
SysRPS: as.GetInt("sys-rps"),
|
||||||
|
APIPrefix: strings.TrimPrefix(as.GetString("api-prefix"), "/"),
|
||||||
|
StaticRoot: path.Join(as.GetString("static-root")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ function main() {
|
|||||||
go test ./...
|
go test ./...
|
||||||
fi
|
fi
|
||||||
GOOS=linux GOARCH=arm GOARM=5 gobuild $exec
|
GOOS=linux GOARCH=arm GOARM=5 gobuild $exec
|
||||||
scp -r ./public/swagger/* zach@tickle.lan:./dndex1/files/swagger/
|
|
||||||
scp $exec zach@tickle.lan:./dndex1/dndex.new
|
scp $exec zach@tickle.lan:./dndex1/dndex.new
|
||||||
ssh zach@tickle.lan bash -c "true; while [ -e ./dndex1/dndex.new ]; do printf '.'; sleep 3; done; echo"
|
ssh zach@tickle.lan bash -c "true; while [ -e ./dndex1/dndex.new ]; do printf '.'; sleep 3; done; echo"
|
||||||
if [ -n "$BIG" ]; then
|
if [ -n "$BIG" ]; then
|
||||||
@@ -17,6 +16,9 @@ function main() {
|
|||||||
echo ssh bel@remote.blapointe.com bash -c 'true; md5sum ./services/bin/dndex*; while [ -e ./services/bin/dndex.new ]; do printf "."; sleep 3; done; md5sum ./services/bin/dndex*'
|
echo ssh bel@remote.blapointe.com bash -c 'true; md5sum ./services/bin/dndex*; while [ -e ./services/bin/dndex.new ]; do printf "."; sleep 3; done; md5sum ./services/bin/dndex*'
|
||||||
big
|
big
|
||||||
fi
|
fi
|
||||||
|
ssh zach@tickle.lan bash -c "true; rm -rf ./dndex1/files/swagger/"
|
||||||
|
scp -r ./public/swagger/ zach@tickle.lan:./dndex1/files/swagger/
|
||||||
|
ssh zach@tickle.lan bash -c "true; cd ./dndex1/public; rm -rf ./swagger; ln -s ../files/swagger"
|
||||||
rm $exec
|
rm $exec
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +44,10 @@ function big() {
|
|||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
ssh zach@tickle.lan rm -rf ./dndex-ui/public
|
ssh zach@tickle.lan rm -rf ./dndex-ui/public
|
||||||
|
ssh zach@tickle.lan bash -c "true; rm -rf ./dndex-ui/public"
|
||||||
scp -r ./dist zach@tickle.lan:./dndex-ui/public
|
scp -r ./dist zach@tickle.lan:./dndex-ui/public
|
||||||
|
ssh zach@tickle.lan bash -c "true; rm -rf ./dndex1/public"
|
||||||
|
scp -r ./dist zach@tickle.lan:./dndex1/public
|
||||||
popd
|
popd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"local/dndex/config"
|
||||||
"local/dndex/server/auth"
|
"local/dndex/server/auth"
|
||||||
"local/dndex/storage/entity"
|
"local/dndex/storage/entity"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -27,6 +28,8 @@ func Test(t *testing.T) {
|
|||||||
s := httptest.NewServer(http.HandlerFunc(nil))
|
s := httptest.NewServer(http.HandlerFunc(nil))
|
||||||
s.Close()
|
s.Close()
|
||||||
p := strings.Split(s.URL, ":")[2]
|
p := strings.Split(s.URL, ":")[2]
|
||||||
|
os.Args = []string{"a"}
|
||||||
|
s.URL = s.URL + "/" + config.New().APIPrefix
|
||||||
os.Args = strings.Split(fmt.Sprintf(`dndex -auth=true -database db -delay 5ms -driver map -fileprefix /files -fileroot %s -p %v -rps 50 -sys-rps 40`, d, p), " ")
|
os.Args = strings.Split(fmt.Sprintf(`dndex -auth=true -database db -delay 5ms -driver map -fileprefix /files -fileroot %s -p %v -rps 50 -sys-rps 40`, d, p), " ")
|
||||||
|
|
||||||
go main()
|
go main()
|
||||||
|
|||||||
BIN
public/swagger/favicon-16x16.png
Normal file
BIN
public/swagger/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
BIN
public/swagger/favicon-32x32.png
Normal file
BIN
public/swagger/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 628 B |
@@ -18,6 +18,7 @@ paths:
|
|||||||
parameters:
|
parameters:
|
||||||
- $ref: "#/components/parameters/token"
|
- $ref: "#/components/parameters/token"
|
||||||
- $ref: "#/components/parameters/id"
|
- $ref: "#/components/parameters/id"
|
||||||
|
- $ref: "#/components/parameters/md"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/schemas/responseOneResolved"
|
$ref: "#/components/schemas/responseOneResolved"
|
||||||
@@ -54,6 +55,13 @@ components:
|
|||||||
$ref: "../swagger.yaml#/components/parameters/token"
|
$ref: "../swagger.yaml#/components/parameters/token"
|
||||||
id:
|
id:
|
||||||
$ref: "../swagger.yaml#/components/parameters/id"
|
$ref: "../swagger.yaml#/components/parameters/id"
|
||||||
|
md:
|
||||||
|
name: md
|
||||||
|
description: "render the text section as markdown"
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: bool
|
||||||
|
|
||||||
schemas:
|
schemas:
|
||||||
requestOne:
|
requestOne:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ paths:
|
|||||||
- files
|
- files
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: "#/components/parameters/token"
|
- $ref: "#/components/parameters/token"
|
||||||
|
- $ref: "#/components/parameters/direct"
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: "#/components/schemas/requestForm"
|
$ref: "#/components/schemas/requestForm"
|
||||||
|
|
||||||
@@ -12,6 +13,13 @@ components:
|
|||||||
parameters:
|
parameters:
|
||||||
token:
|
token:
|
||||||
$ref: "../swagger.yaml#/components/parameters/token"
|
$ref: "../swagger.yaml#/components/parameters/token"
|
||||||
|
direct:
|
||||||
|
name: direct
|
||||||
|
description: "interpret content as a direct link to actual content"
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: bool
|
||||||
schemas:
|
schemas:
|
||||||
requestForm:
|
requestForm:
|
||||||
$ref: "../swagger.yaml#/components/schemas/requestForm"
|
$ref: "../swagger.yaml#/components/schemas/requestForm"
|
||||||
|
|||||||
6
public/swagger/v1/index.yaml
Normal file
6
public/swagger/v1/index.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
paths:
|
||||||
|
get:
|
||||||
|
tags: []
|
||||||
|
summary: "Access a static read-only file server"
|
||||||
|
responses:
|
||||||
|
200: {}
|
||||||
@@ -15,24 +15,26 @@ servers:
|
|||||||
- url: http://api1.dndex.lan:8080/
|
- url: http://api1.dndex.lan:8080/
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/version:
|
/api/version:
|
||||||
$ref: "./version.yaml#/paths"
|
$ref: "./version.yaml#/paths"
|
||||||
/dump:
|
/api/dump:
|
||||||
$ref: "./dump.yaml#/paths"
|
$ref: "./dump.yaml#/paths"
|
||||||
/files:
|
/api/files:
|
||||||
$ref: "./files/index.yaml#/paths"
|
$ref: "./files/index.yaml#/paths"
|
||||||
/files/{path}:
|
/api/files/{path}:
|
||||||
$ref: "./files/one.yaml#/paths"
|
$ref: "./files/one.yaml#/paths"
|
||||||
/users/register:
|
/api/users/register:
|
||||||
$ref: "./users/register.yaml#/paths"
|
$ref: "./users/register.yaml#/paths"
|
||||||
/users/login:
|
/api/users/login:
|
||||||
$ref: "./users/login.yaml#/paths"
|
$ref: "./users/login.yaml#/paths"
|
||||||
/entities:
|
/api/entities:
|
||||||
$ref: "./entities/index.yaml#/paths"
|
$ref: "./entities/index.yaml#/paths"
|
||||||
/entities/{id}:
|
/api/entities/{id}:
|
||||||
$ref: "./entities/id.yaml#/paths"
|
$ref: "./entities/id.yaml#/paths"
|
||||||
/entities/{id}/{path}:
|
/api/entities/{id}/{path}:
|
||||||
$ref: "./entities/idsub.yaml#/paths"
|
$ref: "./entities/idsub.yaml#/paths"
|
||||||
|
/:
|
||||||
|
$ref: "./index.yaml#/paths"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ paths:
|
|||||||
- users
|
- users
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
|
application/json:
|
||||||
|
$ref: "#/components/schemas/requestLogin"
|
||||||
application/x-www-form-urlencoded:
|
application/x-www-form-urlencoded:
|
||||||
schema:
|
$ref: "#/components/schemas/requestLogin"
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
DnDex-User:
|
|
||||||
type: string
|
|
||||||
example: "namespace"
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
content:
|
content:
|
||||||
@@ -26,3 +23,13 @@ paths:
|
|||||||
salt:
|
salt:
|
||||||
type: string
|
type: string
|
||||||
example: def-456
|
example: def-456
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
requestLogin:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
DnDex-User:
|
||||||
|
type: string
|
||||||
|
example: "namespace"
|
||||||
|
|||||||
@@ -5,16 +5,10 @@ paths:
|
|||||||
- users
|
- users
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
|
application/json:
|
||||||
|
$ref: "#/components/schemas/requestRegister"
|
||||||
application/x-www-form-urlencoded:
|
application/x-www-form-urlencoded:
|
||||||
schema:
|
$ref: "#/components/schemas/requestRegister"
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
DnDex-User:
|
|
||||||
type: string
|
|
||||||
example: "namespace"
|
|
||||||
DnDex-Auth:
|
|
||||||
type: string
|
|
||||||
example: "password"
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
$ref: "#/components/schemas/responseOK"
|
$ref: "#/components/schemas/responseOK"
|
||||||
@@ -23,3 +17,13 @@ components:
|
|||||||
schemas:
|
schemas:
|
||||||
responseOK:
|
responseOK:
|
||||||
$ref: "../swagger.yaml#/components/schemas/responseOK"
|
$ref: "../swagger.yaml#/components/schemas/responseOK"
|
||||||
|
requestRegister:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
DnDex-User:
|
||||||
|
type: string
|
||||||
|
example: "namespace"
|
||||||
|
DnDex-Auth:
|
||||||
|
type: string
|
||||||
|
example: "password"
|
||||||
|
|||||||
42
public/ui/index.html
Normal file
42
public/ui/index.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>DnDex UI</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">
|
||||||
|
<script src="/ui/dndex.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="has-navbar-fixed-top">
|
||||||
|
<nav class="navbar is-fixed-top is-primary is-bold" role="navigation">
|
||||||
|
<div class="navbar-brand"></div>
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<a class="navbar-link">top</a>
|
||||||
|
<div class="navbar-dropdown">
|
||||||
|
<a class="navbar-item">a</a>
|
||||||
|
<hr class="navbar-divider">
|
||||||
|
<a class="navbar-item">b</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="buttons">
|
||||||
|
<a class="button">Sign Up</a>
|
||||||
|
<a class="button">Log In</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<section class="hero is-primary is-bold is-fullheight-with-navbar">
|
||||||
|
<div class="hero-head">
|
||||||
|
<div class="container">
|
||||||
|
<div class="title">Hero</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Submodule public/vue/dndex-ui updated: 72595fae58...70133bf814
@@ -1,13 +1,19 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"local/dndex/storage"
|
"local/dndex/storage"
|
||||||
"local/dndex/storage/entity"
|
"local/dndex/storage/entity"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GeneratePlain(g storage.RateLimitedGraph, r *http.Request) (string, error) {
|
func GeneratePlain(g storage.RateLimitedGraph, r *http.Request) (string, error) {
|
||||||
@@ -47,7 +53,26 @@ func readRequestedNamespace(r *http.Request) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readRequested(r *http.Request, key string) string {
|
func readRequested(r *http.Request, key string) string {
|
||||||
return r.FormValue(key)
|
switch r.Header.Get("Content-Type") {
|
||||||
|
case "application/json":
|
||||||
|
b, _ := ioutil.ReadAll(r.Body)
|
||||||
|
r.Body = struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{
|
||||||
|
Reader: bytes.NewReader(b),
|
||||||
|
Closer: r.Body,
|
||||||
|
}
|
||||||
|
m := bson.M{}
|
||||||
|
json.Unmarshal(b, &m)
|
||||||
|
v, ok := m[key]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprint(v)
|
||||||
|
default:
|
||||||
|
return r.FormValue(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeyForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) (string, error) {
|
func getKeyForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) (string, error) {
|
||||||
|
|||||||
@@ -95,3 +95,30 @@ func TestGenerate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadRequested(t *testing.T) {
|
||||||
|
t.Run("form: ignore query params", func(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/a=c", nil)
|
||||||
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if got := readRequested(r, "a"); got != "" {
|
||||||
|
t.Fatal(got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("form: body beats query params", func(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/a=c", strings.NewReader(`a=b`))
|
||||||
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if got := readRequested(r, "a"); got != "b" {
|
||||||
|
t.Fatal(got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("json: OK", func(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/a=c", strings.NewReader(`{"a": "b"}`))
|
||||||
|
r.Header.Set("Content-Type", "application/json")
|
||||||
|
if got := readRequested(r, "a"); got != "b" {
|
||||||
|
t.Fatal(got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
"github.com/gomarkdown/markdown/html"
|
||||||
|
"github.com/gomarkdown/markdown/parser"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"gopkg.in/mgo.v2/bson"
|
"gopkg.in/mgo.v2/bson"
|
||||||
@@ -119,6 +122,12 @@ func (rest *REST) entitiesGetOne(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
resp[entity.Connections] = m
|
resp[entity.Connections] = m
|
||||||
}
|
}
|
||||||
|
_, md := r.URL.Query()["md"]
|
||||||
|
if md {
|
||||||
|
renderer := html.NewRenderer(html.RendererOptions{Flags: html.CommonFlags | html.TOC})
|
||||||
|
parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock)
|
||||||
|
resp["md"] = markdownHead + string(markdown.ToHTML([]byte(one.Text), parser, renderer)) + markdownTail
|
||||||
|
}
|
||||||
rest.respMap(w, entityScope[0], resp)
|
rest.respMap(w, entityScope[0], resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,21 @@ func TestEntities(t *testing.T) {
|
|||||||
return one.Name == "myname"
|
return one.Name == "myname"
|
||||||
})
|
})
|
||||||
|
|
||||||
w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/"+id, ``)
|
w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/"+id+"?md", ``)
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Fatal(w.Code)
|
t.Fatal(w.Code)
|
||||||
}
|
}
|
||||||
|
b := w.Body.Bytes()
|
||||||
|
var markdowned map[string]struct {
|
||||||
|
MD string `json:"md"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &markdowned); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(markdowned) != 1 {
|
||||||
|
t.Fatal(len(markdowned))
|
||||||
|
} else if len(markdowned[id].MD) == 0 {
|
||||||
|
t.Fatal(markdowned, string(b))
|
||||||
|
}
|
||||||
id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool {
|
id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool {
|
||||||
return one.Name == "myname"
|
return one.Name == "myname"
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"local/dndex/config"
|
"local/dndex/config"
|
||||||
|
"local/simpleserve/simpleserve"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -50,12 +53,34 @@ func (rest *REST) filesCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if _, err := io.Copy(f, r.Body); err != nil {
|
if err := rest.filesStream(r, f); err != nil {
|
||||||
rest.respError(w, err)
|
rest.respError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.Write([]byte(id))
|
w.Write([]byte(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rest *REST) filesStream(r *http.Request, f io.Writer) error {
|
||||||
|
var reader io.Reader = r.Body
|
||||||
|
_, direct := r.URL.Query()["direct"]
|
||||||
|
if direct {
|
||||||
|
target, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := http.Get(string(target))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
reader = resp.Body
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(f, reader); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rest *REST) filesDelete(w http.ResponseWriter, r *http.Request) {
|
func (rest *REST) filesDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
localPath := rest.filesPath(r)
|
localPath := rest.filesPath(r)
|
||||||
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
||||||
@@ -72,6 +97,8 @@ func (rest *REST) filesDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rest *REST) filesGet(w http.ResponseWriter, r *http.Request) {
|
func (rest *REST) filesGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
simpleserve.SetContentTypeIfMedia(w, r)
|
||||||
|
r.URL.Path = strings.TrimSuffix(r.URL.Path, path.Ext(r.URL.Path))
|
||||||
localPath := rest.filesPath(r)
|
localPath := rest.filesPath(r)
|
||||||
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
if stat, err := os.Stat(localPath); os.IsNotExist(err) {
|
||||||
rest.respNotFound(w)
|
rest.respNotFound(w)
|
||||||
@@ -106,10 +133,12 @@ func (rest *REST) filesUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if _, err := io.Copy(f, r.Body); err != nil {
|
|
||||||
|
if err := rest.filesStream(r, f); err != nil {
|
||||||
rest.respError(w, err)
|
rest.respError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(localPath+".tmp", localPath); err != nil {
|
if err := os.Rename(localPath+".tmp", localPath); err != nil {
|
||||||
rest.respError(w, err)
|
rest.respError(w, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,14 +13,17 @@ import (
|
|||||||
|
|
||||||
func TestFiles(t *testing.T) {
|
func TestFiles(t *testing.T) {
|
||||||
cases := map[string]func(*testing.T, *REST, func(*http.Request)){
|
cases := map[string]func(*testing.T, *REST, func(*http.Request)){
|
||||||
"create-get": func(t *testing.T, rest *REST, scope func(r *http.Request)) {
|
"create-get w/ content type": func(t *testing.T, rest *REST, scope func(r *http.Request)) {
|
||||||
content := uuid.New().String()
|
content := uuid.New().String()
|
||||||
w := testFilesPost(t, rest, scope, content)
|
w := testFilesPost(t, rest, scope, content)
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Fatal(w.Code, string(w.Body.Bytes()))
|
t.Fatal(w.Code, string(w.Body.Bytes()))
|
||||||
}
|
}
|
||||||
s := string(w.Body.Bytes())
|
s := fmt.Sprintf("%s.jpg", w.Body.Bytes())
|
||||||
w = testFilesGet(t, rest, s, scope)
|
w = testFilesGet(t, rest, s, scope)
|
||||||
|
if w.Header().Get("Content-Type") != "image/jpeg" {
|
||||||
|
t.Fatal(w.Header())
|
||||||
|
}
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Fatal(w.Code, string(w.Body.Bytes()))
|
t.Fatal(w.Code, string(w.Body.Bytes()))
|
||||||
}
|
}
|
||||||
@@ -101,3 +106,37 @@ func testFilesReq(t *testing.T, rest *REST, id string, scope func(*http.Request)
|
|||||||
rest.files(w, r)
|
rest.files(w, r)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFilesStream(t *testing.T) {
|
||||||
|
rest, _, clean := testREST(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
t.Run("simple upload", func(t *testing.T) {
|
||||||
|
value := uuid.New().String()
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(value))
|
||||||
|
buff := bytes.NewBuffer(nil)
|
||||||
|
if err := rest.filesStream(r, buff); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s := string(buff.Bytes()); s != value {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("direct upload", func(t *testing.T) {
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(http.NotFound))
|
||||||
|
defer s.Close()
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/?direct", strings.NewReader(s.URL))
|
||||||
|
buff := bytes.NewBuffer(nil)
|
||||||
|
if err := rest.filesStream(r, buff); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
http.NotFound(w, nil)
|
||||||
|
want := string(w.Body.Bytes())
|
||||||
|
got := string(buff.Bytes())
|
||||||
|
if want != got {
|
||||||
|
t.Fatal(want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
10
server/markdown.go
Normal file
10
server/markdown.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
const (
|
||||||
|
markdownHead = `<div>
|
||||||
|
<style scoped>
|
||||||
|
@charset"UTF-8";button,input,textarea{transition:background-color 0.1s linear, border-color 0.1s linear, color 0.1s linear, box-shadow 0.1s linear, transform 0.1s ease}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px}h1,h2,h3,h4,h5,h6,strong{color:#000000}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}blockquote{border-left:4px solid #0096bfab;margin:1.5em 0;padding:0.5em 1em;font-style:italic}blockquote > footer{margin-top:10px;font-style:normal}blockquote cite{font-style:normal}address{font-style:normal}a[href^='mailto']::before{content:'📧 '}a[href^='tel']::before{content:'📞 '}a[href^='sms']::before{content:'💬 '}button,input[type='submit'],input[type='button'],input[type='checkbox']{cursor:pointer}input:not([type='checkbox']):not([type='radio']),select{display:block}button,input,select,textarea{color:#000000;background-color:#efefef;font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}input:not([type='checkbox']):not([type='radio']),select,button,textarea{-webkit-appearance:none}textarea{margin-right:0;width:100%;box-sizing:border-box;resize:vertical}button,input[type='submit'],input[type='button']{padding-right:30px;padding-left:30px}button:hover,input[type='submit']:hover,input[type='button']:hover{background:#dddddd}button:focus,input:focus,select:focus,textarea:focus{box-shadow:0 0 0 2px #0096bfab}input[type='checkbox']:active,input[type='radio']:active,input[type='submit']:active,input[type='button']:active,button:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:0.5}::-webkit-input-placeholder{color:#949494}:-ms-input-placeholder{color:#949494}::-ms-input-placeholder{color:#949494}::placeholder{color:#949494}a{text-decoration:none;color:#0076d1}a:hover{text-decoration:underline}code,kbd{background:#efefef;color:#000000;padding:5px;border-radius:6px}pre > code{padding:10px;display:block;overflow-x:auto}img{max-width:100%}hr{border:none;border-top:1px solid #dbdbdb}table{border-collapse:collapse;margin-bottom:10px;width:100%}td,th{padding:6px;text-align:left}th{border-bottom:1px solid #dbdbdb}tbody tr:nth-child(even){background-color:#efefef}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;border-radius:6px}::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#c4c4c4}
|
||||||
|
</style>
|
||||||
|
`
|
||||||
|
markdownTail = `</div>`
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"local/dndex/server/auth"
|
"local/dndex/server/auth"
|
||||||
"local/gziphttp"
|
"local/gziphttp"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -69,3 +70,11 @@ func (rest *REST) shift(foo http.HandlerFunc) http.HandlerFunc {
|
|||||||
foo(w, r)
|
foo(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rest *REST) deprefix(foo http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
newpath := strings.TrimPrefix(r.URL.Path, path.Join("/", config.New().APIPrefix))
|
||||||
|
r.URL.Path = newpath
|
||||||
|
foo(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -131,3 +131,20 @@ func TestMiddlewareShift(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareDeprefix(t *testing.T) {
|
||||||
|
resolved := ""
|
||||||
|
bar := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
resolved = r.URL.Path
|
||||||
|
})
|
||||||
|
|
||||||
|
os.Args = []string{"a"}
|
||||||
|
os.Setenv("API_PREFIX", "myprefix")
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/myprefix/abc", nil)
|
||||||
|
rest := &REST{}
|
||||||
|
rest.deprefix(bar)(nil, r)
|
||||||
|
|
||||||
|
if resolved != "/abc" {
|
||||||
|
t.Fatal(resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"local/dndex/storage"
|
"local/dndex/storage"
|
||||||
"local/router"
|
"local/router"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,11 +43,11 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) {
|
|||||||
fmt.Sprintf("dump"): rest.dump,
|
fmt.Sprintf("dump"): rest.dump,
|
||||||
}
|
}
|
||||||
|
|
||||||
for path, foo := range paths {
|
for urlpath, foo := range paths {
|
||||||
bar := foo
|
bar := foo
|
||||||
bar = rest.shift(bar)
|
bar = rest.shift(bar)
|
||||||
bar = rest.scoped(bar)
|
bar = rest.scoped(bar)
|
||||||
switch strings.Split(path, "/")[0] {
|
switch strings.Split(urlpath, "/")[0] {
|
||||||
case "users":
|
case "users":
|
||||||
case "version":
|
case "version":
|
||||||
default:
|
default:
|
||||||
@@ -54,11 +55,20 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) {
|
|||||||
}
|
}
|
||||||
bar = rest.defend(bar)
|
bar = rest.defend(bar)
|
||||||
bar = rest.delay(bar)
|
bar = rest.delay(bar)
|
||||||
if err := rest.router.Add(path, bar); err != nil {
|
bar = rest.deprefix(bar)
|
||||||
|
routerpath := path.Join("/", config.New().APIPrefix, urlpath)
|
||||||
|
if err := rest.router.Add(routerpath, bar); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bar := rest.static
|
||||||
|
bar = rest.defend(bar)
|
||||||
|
bar = rest.delay(bar)
|
||||||
|
if err := rest.router.Add(params, bar); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return rest, nil
|
return rest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,10 +163,10 @@ func TestRESTRouter(t *testing.T) {
|
|||||||
|
|
||||||
for name, d := range cases {
|
for name, d := range cases {
|
||||||
c := d
|
c := d
|
||||||
path := name
|
urlpath := path.Join("/", config.New().APIPrefix, name)
|
||||||
rest, setAuth, clean := testREST(t)
|
rest, setAuth, clean := testREST(t)
|
||||||
defer clean()
|
defer clean()
|
||||||
r := httptest.NewRequest(c.method, path, strings.NewReader(``))
|
r := httptest.NewRequest(c.method, urlpath, strings.NewReader(``))
|
||||||
setAuth(r)
|
setAuth(r)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
rest.router.ServeHTTP(w, r)
|
rest.router.ServeHTTP(w, r)
|
||||||
|
|||||||
18
server/static.go
Normal file
18
server/static.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/dndex/config"
|
||||||
|
"local/simpleserve/simpleserve"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rest *REST) static(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, err := os.Stat(path.Join(config.New().StaticRoot, r.URL.Path)); os.IsNotExist(err) {
|
||||||
|
r.URL.Path = "/"
|
||||||
|
}
|
||||||
|
simpleserve.SetContentTypeIfMedia(w, r)
|
||||||
|
server := http.FileServer(http.Dir(config.New().StaticRoot))
|
||||||
|
server.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
46
server/static_test.go
Normal file
46
server/static_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"local/dndex/config"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRESTStatic(t *testing.T) {
|
||||||
|
os.Args = []string{"a"}
|
||||||
|
d, err := ioutil.TempDir(os.TempDir(), "static*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Setenv("STATIC_ROOT", d)
|
||||||
|
if err := ioutil.WriteFile(path.Join(d, "index.html"), []byte("Hello, world"), os.ModePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rest, _, clean := testREST(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
t.Run("assert nonstatic OK", func(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodGet, path.Join("/", config.New().APIPrefix, "version"), nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
rest.router.ServeHTTP(w, r)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatal(w.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("assert static OK", func(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
rest.router.ServeHTTP(w, r)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatal(w.Code)
|
||||||
|
}
|
||||||
|
if s := string(w.Body.Bytes()); s != "Hello, world" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) {
|
|||||||
rest.respNotFound(w)
|
rest.respNotFound(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.Header.Set("Application-Type", "application/x-www-form-urlencoded")
|
rest.usersContentType(r)
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/register":
|
case "/register":
|
||||||
rest.usersRegister(w, r)
|
rest.usersRegister(w, r)
|
||||||
@@ -28,6 +28,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rest *REST) usersRegister(w http.ResponseWriter, r *http.Request) {
|
func (rest *REST) usersRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rest.usersContentType(r)
|
||||||
err := auth.Register(rest.g, r)
|
err := auth.Register(rest.g, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.respError(w, err)
|
rest.respError(w, err)
|
||||||
@@ -37,6 +38,7 @@ func (rest *REST) usersRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rest *REST) usersLogin(w http.ResponseWriter, r *http.Request) {
|
func (rest *REST) usersLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rest.usersContentType(r)
|
||||||
salt := uuid.New().String()[:5]
|
salt := uuid.New().String()[:5]
|
||||||
var token string
|
var token string
|
||||||
var err error
|
var err error
|
||||||
@@ -56,3 +58,9 @@ func (rest *REST) usersLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
"salt": salt,
|
"salt": salt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rest *REST) usersContentType(r *http.Request) {
|
||||||
|
if r.Header.Get("Application-Type") == "" {
|
||||||
|
r.Header.Set("Application-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ func TestUsersRegister(t *testing.T) {
|
|||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
t.Run("register ok", func(t *testing.T) {
|
t.Run("register ok", func(t *testing.T) {
|
||||||
user := uuid.New().String()[:5]
|
for _, json := range []bool{false, true} {
|
||||||
pwd := uuid.New().String()[:5]
|
user := uuid.New().String()[:5]
|
||||||
testRegisterOK(t, rest, user, pwd)
|
pwd := uuid.New().String()[:5]
|
||||||
|
testRegisterOK(t, rest, user, pwd, json)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("register 400: nil body", func(t *testing.T) {
|
t.Run("register 400: nil body", func(t *testing.T) {
|
||||||
@@ -76,36 +78,54 @@ func TestUsersLogin(t *testing.T) {
|
|||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
t.Run("login ok", func(t *testing.T) {
|
t.Run("login ok", func(t *testing.T) {
|
||||||
user := uuid.New().String()[:5]
|
for _, json := range []bool{false, true} {
|
||||||
pwd := uuid.New().String()[:5]
|
user := uuid.New().String()[:5]
|
||||||
testRegisterOK(t, rest, user, pwd)
|
pwd := uuid.New().String()[:5]
|
||||||
testLoginOK(t, rest, user, pwd)
|
testRegisterOK(t, rest, user, pwd, json)
|
||||||
|
testLoginOK(t, rest, user, pwd, json)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("login 404 user", func(t *testing.T) {
|
t.Run("login 404 user", func(t *testing.T) {
|
||||||
pwd := uuid.New().String()[:5]
|
for _, json := range []bool{false, true} {
|
||||||
testLoginNotOK(t, rest, "bad", pwd)
|
pwd := uuid.New().String()[:5]
|
||||||
|
testLoginNotOK(t, rest, "bad", pwd, json)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("login bad user", func(t *testing.T) {
|
t.Run("login bad user", func(t *testing.T) {
|
||||||
user := uuid.New().String()[:5]
|
for _, json := range []bool{false, true} {
|
||||||
pwd := uuid.New().String()[:5]
|
user := uuid.New().String()[:5]
|
||||||
testRegisterOK(t, rest, user, pwd)
|
pwd := uuid.New().String()[:5]
|
||||||
testLoginNotOK(t, rest, "bad", pwd)
|
testRegisterOK(t, rest, user, pwd, json)
|
||||||
|
testLoginNotOK(t, rest, "bad", pwd, json)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("login bad pwd", func(t *testing.T) {
|
t.Run("login bad pwd", func(t *testing.T) {
|
||||||
user := uuid.New().String()[:5]
|
for _, json := range []bool{false, true} {
|
||||||
pwd := uuid.New().String()[:5]
|
user := uuid.New().String()[:5]
|
||||||
testRegisterOK(t, rest, user, pwd)
|
pwd := uuid.New().String()[:5]
|
||||||
testLoginNotOK(t, rest, user, "bad")
|
testRegisterOK(t, rest, user, pwd, json)
|
||||||
|
testLoginNotOK(t, rest, user, "bad", json)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRegisterOK(t *testing.T, rest *REST, user, pwd string) {
|
func testRegisterOK(t *testing.T, rest *REST, user, pwd string, useJSON bool) {
|
||||||
body := fmt.Sprintf(`%s=%s&%s=%s`, auth.UserKey, user, auth.AuthKey, pwd)
|
body := fmt.Sprintf(`%s=%s&%s=%s`, auth.UserKey, user, auth.AuthKey, pwd)
|
||||||
|
if useJSON {
|
||||||
|
s, _ := json.Marshal(map[string]string{
|
||||||
|
auth.UserKey: user,
|
||||||
|
auth.AuthKey: pwd,
|
||||||
|
})
|
||||||
|
body = string(s)
|
||||||
|
}
|
||||||
r := httptest.NewRequest(http.MethodPost, "/register", strings.NewReader(body))
|
r := httptest.NewRequest(http.MethodPost, "/register", strings.NewReader(body))
|
||||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if useJSON {
|
||||||
|
r.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
rest.users(w, r)
|
rest.users(w, r)
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
@@ -113,10 +133,19 @@ func testRegisterOK(t *testing.T, rest *REST, user, pwd string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoginNotOK(t *testing.T, rest *REST, user, pwd string) {
|
func testLoginNotOK(t *testing.T, rest *REST, user, pwd string, useJSON bool) {
|
||||||
body := fmt.Sprintf(`%s=%s`, auth.UserKey, user)
|
body := fmt.Sprintf(`%s=%s`, auth.UserKey, user)
|
||||||
|
if useJSON {
|
||||||
|
s, _ := json.Marshal(map[string]string{
|
||||||
|
auth.UserKey: user,
|
||||||
|
})
|
||||||
|
body = string(s)
|
||||||
|
}
|
||||||
r := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(body))
|
r := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(body))
|
||||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if useJSON {
|
||||||
|
r.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
rest.users(w, r)
|
rest.users(w, r)
|
||||||
if w.Code < http.StatusBadRequest {
|
if w.Code < http.StatusBadRequest {
|
||||||
@@ -136,10 +165,19 @@ func testLoginNotOK(t *testing.T, rest *REST, user, pwd string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoginOK(t *testing.T, rest *REST, user, pwd string) string {
|
func testLoginOK(t *testing.T, rest *REST, user, pwd string, useJSON bool) string {
|
||||||
body := fmt.Sprintf(`%s=%s`, auth.UserKey, user)
|
body := fmt.Sprintf(`%s=%s`, auth.UserKey, user)
|
||||||
|
if useJSON {
|
||||||
|
s, _ := json.Marshal(map[string]string{
|
||||||
|
auth.UserKey: user,
|
||||||
|
})
|
||||||
|
body = string(s)
|
||||||
|
}
|
||||||
r := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(body))
|
r := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(body))
|
||||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if useJSON {
|
||||||
|
r.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
rest.users(w, r)
|
rest.users(w, r)
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package driver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"local/dndex/storage/entity"
|
"local/dndex/storage/entity"
|
||||||
"local/storage"
|
"local/storage"
|
||||||
@@ -63,7 +64,7 @@ func (s *Storage) Update(ctx context.Context, ns string, filter, operator interf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v, err = bson.Marshal(n)
|
v, err = json.Marshal(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -72,12 +73,12 @@ func (s *Storage) Update(ctx context.Context, ns string, filter, operator interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) Insert(ctx context.Context, ns string, doc interface{}) error {
|
func (s *Storage) Insert(ctx context.Context, ns string, doc interface{}) error {
|
||||||
b, err := bson.Marshal(doc)
|
b, err := json.Marshal(doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m := bson.M{}
|
m := bson.M{}
|
||||||
if err := bson.Unmarshal(b, &m); err != nil {
|
if err := json.Unmarshal(b, &m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,11 +123,15 @@ func (s *Storage) forEach(ctx context.Context, ns string, filter interface{}, fo
|
|||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
n := bson.M{}
|
n := bson.M{}
|
||||||
if err := bson.Unmarshal(v, &n); err != nil {
|
if err := json.Unmarshal(v, &n); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if matches(n, m) {
|
if matches(n, m) {
|
||||||
if err := foo(id, append(bson.Raw{}, bson.Raw(v)...)); err != nil {
|
b, err := bson.Marshal(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := foo(id, b); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package driver
|
package driver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"local/dndex/storage/entity"
|
"local/dndex/storage/entity"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewStorage(t *testing.T) {
|
func TestNewStorage(t *testing.T) {
|
||||||
@@ -37,7 +37,7 @@ func fillStorage(t *testing.T, s *Storage) {
|
|||||||
Connections: map[string]entity.Connection{p.ID: entity.Connection{p.Name}},
|
Connections: map[string]entity.Connection{p.ID: entity.Connection{p.Name}},
|
||||||
Attachments: map[string]entity.Attachment{"filename": {"/path/to/file"}},
|
Attachments: map[string]entity.Attachment{"filename": {"/path/to/file"}},
|
||||||
}
|
}
|
||||||
b, err := bson.Marshal(o)
|
b, err := json.Marshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package entity
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
@@ -62,6 +61,18 @@ func (o One) Generic() bson.M {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o One) MarshalJSON() ([]byte, error) {
|
||||||
|
b, err := o.MarshalBSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := bson.M{}
|
||||||
|
if err := bson.Unmarshal(b, &v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
func (o One) MarshalBSON() ([]byte, error) {
|
func (o One) MarshalBSON() ([]byte, error) {
|
||||||
isMin := fmt.Sprint(o) == fmt.Sprint(o.Query())
|
isMin := fmt.Sprint(o) == fmt.Sprint(o.Query())
|
||||||
if !isMin {
|
if !isMin {
|
||||||
@@ -73,19 +84,39 @@ func (o One) MarshalBSON() ([]byte, error) {
|
|||||||
if o.Attachments == nil {
|
if o.Attachments == nil {
|
||||||
o.Attachments = make(map[string]Attachment)
|
o.Attachments = make(map[string]Attachment)
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(o)
|
var v interface{}
|
||||||
|
b, err := o.toBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m := bson.M{}
|
if err := fromBytes(b, &v); err != nil {
|
||||||
if err := json.Unmarshal(b, &m); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for k, v := range m {
|
return bson.Marshal(v)
|
||||||
switch v.(type) {
|
}
|
||||||
case string:
|
|
||||||
m[k] = strings.TrimSpace(v.(string))
|
func (o One) toBytes() ([]byte, error) {
|
||||||
}
|
return json.Marshal(struct {
|
||||||
}
|
ID string `bson:"_id,omitempty" json:"_id"`
|
||||||
return bson.Marshal(m)
|
Name string `bson:"name,omitempty" json:"name"`
|
||||||
|
Type string `bson:"type,omitempty" json:"type"`
|
||||||
|
Title string `bson:"title,omitempty" json:"title"`
|
||||||
|
Text string `bson:"text,omitempty" json:"text"`
|
||||||
|
Modified int64 `bson:"modified,omitempty" json:"modified"`
|
||||||
|
Connections map[string]Connection `bson:"connections" json:"connections"`
|
||||||
|
Attachments map[string]Attachment `bson:"attachments" json:"attachments"`
|
||||||
|
}{
|
||||||
|
ID: o.ID,
|
||||||
|
Name: o.Name,
|
||||||
|
Type: o.Type,
|
||||||
|
Title: o.Title,
|
||||||
|
Text: o.Text,
|
||||||
|
Modified: o.Modified,
|
||||||
|
Connections: o.Connections,
|
||||||
|
Attachments: o.Attachments,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromBytes(b []byte, v interface{}) error {
|
||||||
|
return json.Unmarshal(b, v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,3 +57,24 @@ func TestOneMarshalBSON(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToFromBytes(t *testing.T) {
|
||||||
|
o := One{
|
||||||
|
Name: "hello",
|
||||||
|
Connections: map[string]Connection{
|
||||||
|
"world": Connection{Relationship: "!"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := o.toBytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v interface{}
|
||||||
|
if err := fromBytes(b, &v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(v)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user