Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec2ff8dc25 | ||
|
|
baa1327214 | ||
|
|
44f409a807 | ||
|
|
8b9a6a3f7d | ||
|
|
aac3f97894 | ||
|
|
bfa4d5076b | ||
|
|
90c34db593 | ||
|
|
9d6c6cecc7 |
7
.github/workflows/build.yaml
vendored
7
.github/workflows/build.yaml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.13.x]
|
go-version: [1.15.x]
|
||||||
platform: [ubuntu-latest]
|
platform: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
@@ -16,10 +16,7 @@ jobs:
|
|||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF:10}
|
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF:10}
|
||||||
- name: Build
|
- name: Build
|
||||||
run: docker build \
|
run: docker build -t tomwright/mermaid-server:latest -t tomwright/mermaid-server:${{ env.RELEASE_VERSION }} -f Dockerfile .
|
||||||
-t tomwright/mermaid-server:latest \
|
|
||||||
-t tomwright/mermaid-server:${{ env.RELEASE_VERSION }} \
|
|
||||||
-f Dockerfile .
|
|
||||||
- name: Login
|
- name: Login
|
||||||
run: echo ${{ secrets.DOCKER_PASS }} | docker login -u${{ secrets.DOCKER_USER }} --password-stdin
|
run: echo ${{ secrets.DOCKER_PASS }} | docker login -u${{ secrets.DOCKER_USER }} --password-stdin
|
||||||
- name: Push
|
- name: Push
|
||||||
|
|||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -4,7 +4,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.13.x]
|
go-version: [1.15.x]
|
||||||
platform: [ubuntu-latest]
|
platform: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This stage builds the go executable.
|
# This stage builds the go executable.
|
||||||
FROM golang:1.13-buster as go
|
FROM golang:1.15-buster as go
|
||||||
|
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
COPY ./ ./
|
COPY ./ ./
|
||||||
|
|||||||
21
Makefile
Normal file
21
Makefile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
DOCKER_IMAGE=tomwright/mermaid-server:latest
|
||||||
|
CONTAINER_NAME=mermaid-server
|
||||||
|
|
||||||
|
docker-image:
|
||||||
|
docker build -t ${DOCKER_IMAGE} .
|
||||||
|
|
||||||
|
docker-run:
|
||||||
|
docker run -d --name ${CONTAINER_NAME} -p 80:80 ${DOCKER_IMAGE}
|
||||||
|
|
||||||
|
docker-stop:
|
||||||
|
docker stop ${CONTAINER_NAME} || true
|
||||||
|
|
||||||
|
docker-rm:
|
||||||
|
make docker-stop
|
||||||
|
docker rm ${CONTAINER_NAME} || true
|
||||||
|
|
||||||
|
docker-logs:
|
||||||
|
docker logs -f ${CONTAINER_NAME}
|
||||||
|
|
||||||
|
docker-push:
|
||||||
|
docker push ${DOCKER_IMAGE}
|
||||||
@@ -22,6 +22,8 @@ go run cmd/app/main.go --mermaid=./mermaidcli/node_modules/.bin/mmdc --in=./in -
|
|||||||
|
|
||||||
### Diagram creation
|
### Diagram creation
|
||||||
|
|
||||||
|
Use the query param 'type' to change between 'png' and 'svg' defaults to 'svg'.
|
||||||
|
|
||||||
#### POST
|
#### POST
|
||||||
|
|
||||||
Send a CURL request to generate a diagram via `POST`:
|
Send a CURL request to generate a diagram via `POST`:
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,5 +1,5 @@
|
|||||||
module github.com/tomwright/mermaid-server
|
module github.com/tomwright/mermaid-server
|
||||||
|
|
||||||
go 1.13
|
go 1.15
|
||||||
|
|
||||||
require github.com/tomwright/lifetime v1.0.0
|
require github.com/tomwright/lifetime v1.0.0
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewDiagram returns a new diagram.
|
// NewDiagram returns a new diagram.
|
||||||
func NewDiagram(description []byte) *Diagram {
|
func NewDiagram(description []byte, imgType string) *Diagram {
|
||||||
return &Diagram{
|
return &Diagram{
|
||||||
description: []byte(strings.TrimSpace(string(description))),
|
description: []byte(strings.TrimSpace(string(description))),
|
||||||
lastTouched: time.Now(),
|
lastTouched: time.Now(),
|
||||||
mu: &sync.RWMutex{},
|
mu: &sync.RWMutex{},
|
||||||
|
imgType: imgType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ type Diagram struct {
|
|||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
// lastTouched is the time that the diagram was last used.
|
// lastTouched is the time that the diagram was last used.
|
||||||
lastTouched time.Time
|
lastTouched time.Time
|
||||||
|
// the type of image to generate svg or png
|
||||||
|
imgType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch updates the last touched time of the diagram.
|
// Touch updates the last touched time of the diagram.
|
||||||
@@ -55,7 +58,7 @@ func (d *Diagram) ID() (string, error) {
|
|||||||
|
|
||||||
encoded := base64.StdEncoding.EncodeToString(d.description)
|
encoded := base64.StdEncoding.EncodeToString(d.description)
|
||||||
hash := md5.Sum([]byte(encoded))
|
hash := md5.Sum([]byte(encoded))
|
||||||
d.id = hex.EncodeToString(hash[:])
|
d.id = hex.EncodeToString(hash[:]) + d.imgType
|
||||||
|
|
||||||
return d.id, nil
|
return d.id, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (c cachingGenerator) generate(diagram *Diagram) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inPath := fmt.Sprintf("%s/%s.mmd", c.inPath, id)
|
inPath := fmt.Sprintf("%s/%s.mmd", c.inPath, id)
|
||||||
outPath := fmt.Sprintf("%s/%s.svg", c.outPath, id)
|
outPath := fmt.Sprintf("%s/%s.%s", c.outPath, id, diagram.imgType)
|
||||||
|
|
||||||
if err := ioutil.WriteFile(inPath, diagram.description, 0644); err != nil {
|
if err := ioutil.WriteFile(inPath, diagram.description, 0644); err != nil {
|
||||||
return fmt.Errorf("could not write to input file [%s]: %w", inPath, err)
|
return fmt.Errorf("could not write to input file [%s]: %w", inPath, err)
|
||||||
|
|||||||
@@ -22,12 +22,20 @@ func writeJSON(rw http.ResponseWriter, value interface{}, status int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSVG(rw http.ResponseWriter, data []byte, status int) {
|
func writeImage(rw http.ResponseWriter, data []byte, status int, imgType string) error {
|
||||||
rw.Header().Set("Content-Type", "image/svg+xml")
|
switch imgType {
|
||||||
|
case "png":
|
||||||
|
rw.Header().Set("Content-Type", "image/png")
|
||||||
|
case "svg":
|
||||||
|
rw.Header().Set("Content-Type", "image/svg+xml")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unhandled image type: %s", imgType)
|
||||||
|
}
|
||||||
rw.WriteHeader(status)
|
rw.WriteHeader(status)
|
||||||
if _, err := rw.Write(data); err != nil {
|
if _, err := rw.Write(data); err != nil {
|
||||||
panic("could not write bytes to response: " + err.Error())
|
return fmt.Errorf("could not write image bytes: %w", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeErr(rw http.ResponseWriter, err error, status int) {
|
func writeErr(rw http.ResponseWriter, err error, status int) {
|
||||||
@@ -41,59 +49,72 @@ func writeErr(rw http.ResponseWriter, err error, status int) {
|
|||||||
// URLParam is the URL parameter getDiagramFromGET uses to look for data.
|
// URLParam is the URL parameter getDiagramFromGET uses to look for data.
|
||||||
const URLParam = "data"
|
const URLParam = "data"
|
||||||
|
|
||||||
func getDiagramFromGET(rw http.ResponseWriter, r *http.Request) *Diagram {
|
func getDiagramFromGET(r *http.Request, imgType string) (*Diagram, error) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
writeErr(rw, fmt.Errorf("expected HTTP method GET"), http.StatusBadRequest)
|
return nil, fmt.Errorf("expected HTTP method GET")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queryVal := strings.TrimSpace(r.URL.Query().Get(URLParam))
|
queryVal := strings.TrimSpace(r.URL.Query().Get(URLParam))
|
||||||
if queryVal == "" {
|
if queryVal == "" {
|
||||||
writeErr(rw, fmt.Errorf("missing data"), http.StatusBadRequest)
|
return nil, fmt.Errorf("missing data")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
data, err := url.QueryUnescape(queryVal)
|
data, err := url.QueryUnescape(queryVal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErr(rw, fmt.Errorf("could not read query param: %s", err), http.StatusBadRequest)
|
return nil, fmt.Errorf("could not read query param: %s", err)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a diagram from the description
|
// Create a diagram from the description
|
||||||
d := NewDiagram([]byte(data))
|
d := NewDiagram([]byte(data), imgType)
|
||||||
return d
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDiagramFromPOST(rw http.ResponseWriter, r *http.Request) *Diagram {
|
func getDiagramFromPOST(r *http.Request, imgType string) (*Diagram, error) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
writeErr(rw, fmt.Errorf("expected HTTP method POST"), http.StatusBadRequest)
|
return nil, fmt.Errorf("expected HTTP method POST")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
// Get description from request body
|
// Get description from request body
|
||||||
bytes, err := ioutil.ReadAll(r.Body)
|
bytes, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErr(rw, fmt.Errorf("could not read body: %s", err), http.StatusInternalServerError)
|
return nil, fmt.Errorf("could not read body: %s", err)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a diagram from the description
|
// Create a diagram from the description
|
||||||
d := NewDiagram(bytes)
|
d := NewDiagram(bytes, imgType)
|
||||||
return d
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const URLParamImageType = "type"
|
||||||
|
|
||||||
// generateHTTPHandler returns a HTTP handler used to generate a diagram.
|
// generateHTTPHandler returns a HTTP handler used to generate a diagram.
|
||||||
func generateHTTPHandler(generator Generator) func(rw http.ResponseWriter, r *http.Request) {
|
func generateHTTPHandler(generator Generator) func(rw http.ResponseWriter, r *http.Request) {
|
||||||
return func(rw http.ResponseWriter, r *http.Request) {
|
return func(rw http.ResponseWriter, r *http.Request) {
|
||||||
var diagram *Diagram
|
var diagram *Diagram
|
||||||
|
|
||||||
|
imgType := r.URL.Query().Get(URLParamImageType)
|
||||||
|
|
||||||
|
switch imgType {
|
||||||
|
case "png", "svg":
|
||||||
|
case "":
|
||||||
|
imgType = "svg"
|
||||||
|
default:
|
||||||
|
writeErr(rw, fmt.Errorf("unsupported image type (%s) use svg or png", imgType), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
diagram = getDiagramFromGET(rw, r)
|
diagram, err = getDiagramFromGET(r, imgType)
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
diagram = getDiagramFromPOST(rw, r)
|
diagram, err = getDiagramFromPOST(r, imgType)
|
||||||
default:
|
default:
|
||||||
writeErr(rw, fmt.Errorf("unexpected HTTP method %s", r.Method), http.StatusBadRequest)
|
writeErr(rw, fmt.Errorf("unexpected HTTP method %s", r.Method), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErr(rw, err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
if diagram == nil {
|
if diagram == nil {
|
||||||
writeErr(rw, fmt.Errorf("could not create diagram"), http.StatusInternalServerError)
|
writeErr(rw, fmt.Errorf("could not create diagram"), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -112,6 +133,8 @@ func generateHTTPHandler(generator Generator) func(rw http.ResponseWriter, r *ht
|
|||||||
writeErr(rw, fmt.Errorf("could not read diagram bytes: %s", err), http.StatusInternalServerError)
|
writeErr(rw, fmt.Errorf("could not read diagram bytes: %s", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSVG(rw, diagramBytes, http.StatusOK)
|
if err := writeImage(rw, diagramBytes, http.StatusOK, imgType); err != nil {
|
||||||
|
writeErr(rw, fmt.Errorf("could not write diagram: %w", err), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
mermaidcli/package-lock.json
generated
6
mermaidcli/package-lock.json
generated
@@ -59,9 +59,9 @@
|
|||||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||||
},
|
},
|
||||||
"bl": {
|
"bl": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
|
||||||
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"buffer": "^5.5.0",
|
"buffer": "^5.5.0",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user