8 Commits

Author SHA1 Message Date
Tom Wright
ec2ff8dc25 Use go v1.15 2020-10-02 14:48:02 +01:00
Tom Wright
baa1327214 Update build workflow 2020-10-02 14:46:48 +01:00
Tom Wright
44f409a807 Merge pull request #6 from TomWright/png-functionality
Cleanup and Makefile
2020-10-02 14:39:02 +01:00
Tom Wright
8b9a6a3f7d Merge pull request #5 from Umaaz/master
feat(png): add support for generating the images as png
2020-10-02 14:37:41 +01:00
Tom Wright
aac3f97894 Do some cleanup and add a Makefile 2020-10-02 14:33:34 +01:00
Ben Donnelly
bfa4d5076b feat(png): add support for generating the images as png 2020-10-01 19:25:40 +02:00
Tom Wright
90c34db593 Merge pull request #4 from TomWright/dependabot/npm_and_yarn/mermaidcli/bl-4.0.3
Bump bl from 4.0.2 to 4.0.3 in /mermaidcli
2020-09-02 20:52:31 +01:00
dependabot[bot]
9d6c6cecc7 Bump bl from 4.0.2 to 4.0.3 in /mermaidcli
Bumps [bl](https://github.com/rvagg/bl) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-02 19:46:27 +00:00
10 changed files with 82 additions and 36 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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
View 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}

View File

@@ -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
View File

@@ -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

View File

@@ -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
} }

View File

@@ -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)

View File

@@ -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)
}
} }
} }

View File

@@ -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",