update new route middlewares
This commit is contained in:
parent
77cc488d89
commit
b3a93abfb9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
static/bundle.go
|
||||
spec.json
|
2
Makefile
2
Makefile
@ -24,7 +24,7 @@ clean:
|
||||
generate-spec:
|
||||
swagger generate spec -m --compact -o spec.json
|
||||
|
||||
build-static:
|
||||
bundle-static:
|
||||
go-bindata -fs -pkg static -ignore .git -o static/bundle.go public/ schema/ spec.json
|
||||
|
||||
test:
|
||||
|
2
go.mod
2
go.mod
@ -5,6 +5,7 @@ go 1.14
|
||||
require (
|
||||
git.trj.tw/golang/argparse v1.0.1
|
||||
git.trj.tw/golang/config-loader v1.0.1
|
||||
github.com/gabriel-vasile/mimetype v1.1.1
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-playground/validator/v10 v10.3.0 // indirect
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
@ -16,4 +17,5 @@ require (
|
||||
github.com/onsi/ginkgo v1.14.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20200805065543-0cf7623e9dbd // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -17,6 +17,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA=
|
||||
github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
|
@ -27,6 +27,28 @@ func (e *APIError) Error() string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *APIError) SetCode(code response.MessageCode) *APIError {
|
||||
e = &APIError{
|
||||
status: e.status,
|
||||
code: &code,
|
||||
message: e.message,
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIError) SetMessage(s string) *APIError {
|
||||
e = &APIError{
|
||||
status: e.status,
|
||||
code: e.code,
|
||||
message: s,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIError) Code() *response.MessageCode { return e.code }
|
||||
func (e *APIError) Status() response.RespType { return e.status }
|
||||
|
||||
func New(status response.RespType, code ...response.MessageCode) *APIError {
|
||||
e := &APIError{
|
||||
status: status,
|
||||
|
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 |
61
public/swagger/index.html
Normal file
61
public/swagger/index.html
Normal file
@ -0,0 +1,61 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "https://petstore.swagger.io/v2/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
docExpansion: 'none',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
68
public/swagger/oauth2-redirect.html
Normal file
68
public/swagger/oauth2-redirect.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
92
public/swagger/swagger-ui-bundle.js
Normal file
92
public/swagger/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
public/swagger/swagger-ui-bundle.js.map
Normal file
1
public/swagger/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
9
public/swagger/swagger-ui-es-bundle-core.js
Normal file
9
public/swagger/swagger-ui-es-bundle-core.js
Normal file
File diff suppressed because one or more lines are too long
1
public/swagger/swagger-ui-es-bundle-core.js.map
Normal file
1
public/swagger/swagger-ui-es-bundle-core.js.map
Normal file
File diff suppressed because one or more lines are too long
92
public/swagger/swagger-ui-es-bundle.js
Normal file
92
public/swagger/swagger-ui-es-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
public/swagger/swagger-ui-es-bundle.js.map
Normal file
1
public/swagger/swagger-ui-es-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
22
public/swagger/swagger-ui-standalone-preset.js
Normal file
22
public/swagger/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
1
public/swagger/swagger-ui-standalone-preset.js.map
Normal file
1
public/swagger/swagger-ui-standalone-preset.js.map
Normal file
File diff suppressed because one or more lines are too long
4
public/swagger/swagger-ui.css
Normal file
4
public/swagger/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
public/swagger/swagger-ui.css.map
Normal file
1
public/swagger/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
9
public/swagger/swagger-ui.js
Normal file
9
public/swagger/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
public/swagger/swagger-ui.js.map
Normal file
1
public/swagger/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
9
router/controller/health.go
Normal file
9
router/controller/health.go
Normal file
@ -0,0 +1,9 @@
|
||||
package controller
|
||||
|
||||
import "go-api/pkg/context"
|
||||
|
||||
func Health() context.CustomHandler {
|
||||
return func(c *context.C) {
|
||||
c.String(200, "ok")
|
||||
}
|
||||
}
|
14
router/middleware/cors.go
Normal file
14
router/middleware/cors.go
Normal file
@ -0,0 +1,14 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Cors() gin.HandlerFunc {
|
||||
corsOpts := cors.DefaultConfig()
|
||||
corsOpts.AllowCredentials = true
|
||||
corsOpts.AllowOriginFunc = func(origin string) bool { return origin != "" }
|
||||
|
||||
return cors.New(corsOpts)
|
||||
}
|
53
router/middleware/panicCatch.go
Normal file
53
router/middleware/panicCatch.go
Normal file
@ -0,0 +1,53 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-api/pkg/context"
|
||||
apierr "go-api/pkg/errors"
|
||||
"go-api/pkg/response"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ServerPanicCatch() context.CustomHandler {
|
||||
return func(c *context.C) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// show error Stack
|
||||
// *****
|
||||
builder := &strings.Builder{}
|
||||
fmt.Printf("[Server Error Catch]\n")
|
||||
|
||||
pc := make([]uintptr, 10)
|
||||
n := runtime.Callers(1, pc)
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
builder.WriteString(fmt.Sprintf("%s:%d %s\n", frame.File, frame.Line, frame.Function))
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
// *****
|
||||
|
||||
var e *apierr.APIError
|
||||
if convertedErr, ok := err.(*apierr.APIError); ok {
|
||||
e = convertedErr
|
||||
} else {
|
||||
e = apierr.ErrInternalError
|
||||
}
|
||||
|
||||
code := make([]response.MessageCode, 0)
|
||||
if c := e.Code(); c != nil {
|
||||
code = append(code, *c)
|
||||
}
|
||||
resp := response.Get(e.Status(), code...)
|
||||
|
||||
c.AbortWithStatusJSON(resp.Status, resp.Body)
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
|
||||
}
|
||||
}
|
85
router/middleware/swaggerServe.go
Normal file
85
router/middleware/swaggerServe.go
Normal file
@ -0,0 +1,85 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go-api/static"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SwaggerOptions struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
var regex = regexp.MustCompile(`(src|href)="./`)
|
||||
var specRegex = regexp.MustCompile(`url:.+`)
|
||||
|
||||
func SwaggerSpecServe() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
b, err := static.GetSpec()
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "not found")
|
||||
return
|
||||
}
|
||||
|
||||
var j map[string]interface{}
|
||||
if err := json.Unmarshal(b, &j); err != nil {
|
||||
c.String(http.StatusInternalServerError, "internal error")
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := j["host"]; ok {
|
||||
j["host"] = c.Request.Host
|
||||
}
|
||||
if xfp := c.Request.Header.Get(textproto.CanonicalMIMEHeaderKey("X-Forwarded-Proto")); xfp != "" && xfp == "https" {
|
||||
j["schemes"] = []string{xfp}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, j)
|
||||
}
|
||||
}
|
||||
|
||||
func SwaggerServe(prefix string, opts *SwaggerOptions) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
fp := c.Request.URL.Path
|
||||
|
||||
if prefix != "" {
|
||||
if !strings.HasPrefix(fp, prefix) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if fp = strings.Replace(fp, prefix, "", 1); fp == "" {
|
||||
fp = "/"
|
||||
}
|
||||
}
|
||||
|
||||
// end of slash replace to index.html file
|
||||
if strings.HasSuffix(fp, "/") {
|
||||
fp += "index.html"
|
||||
}
|
||||
|
||||
if b, mime, err := static.GetWebFile(fp); err == nil {
|
||||
// 如果是 index.html 要更新內部的連結 轉換成絕對路徑
|
||||
if fp == "/index.html" {
|
||||
replPrefix := prefix
|
||||
if !strings.HasSuffix(replPrefix, "/") {
|
||||
replPrefix += "/"
|
||||
}
|
||||
|
||||
b = regex.ReplaceAll(b, []byte(fmt.Sprintf(`$1="%s`, replPrefix)))
|
||||
b = specRegex.ReplaceAll(b, []byte(fmt.Sprintf(`url: '%s.json',`, prefix)))
|
||||
}
|
||||
|
||||
c.Writer.Header().Set(textproto.CanonicalMIMEHeaderKey("Content-Type"), mime)
|
||||
c.Status(http.StatusOK)
|
||||
c.Writer.Write(b)
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,22 +1,42 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"go-api/pkg/context"
|
||||
"go-api/router/controller"
|
||||
"go-api/router/middleware"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var patch = context.PatchContext
|
||||
|
||||
// New web server engine
|
||||
func New() *gin.Engine {
|
||||
e := gin.New()
|
||||
|
||||
e.Use(gin.Logger())
|
||||
e.Use(gin.Recovery())
|
||||
e.Use(middleware.Cors())
|
||||
|
||||
// if in release mode dont serve swagger api docs
|
||||
if os.Getenv("RELEASE") != "1" {
|
||||
// serve static swagger ui
|
||||
e.Use(middleware.SwaggerServe("/api-docs", nil))
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// SetRoute setup all routes
|
||||
func SetRoute(e *gin.Engine) {
|
||||
e.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "ok")
|
||||
})
|
||||
e.GET("/", patch(controller.Health()))
|
||||
|
||||
// if in release mode dont serve swagger api docs
|
||||
if os.Getenv("RELEASE") != "1" {
|
||||
e.GET("/api-docs.json", middleware.SwaggerSpecServe())
|
||||
}
|
||||
|
||||
api := e.Group("/api", patch(middleware.ServerPanicCatch()))
|
||||
_ = api
|
||||
}
|
||||
|
1
schema/mail.sql
Normal file
1
schema/mail.sql
Normal file
@ -0,0 +1 @@
|
||||
-- empty
|
3
schema/version.yml
Normal file
3
schema/version.yml
Normal file
@ -0,0 +1,3 @@
|
||||
versions:
|
||||
- file: 'main.sql'
|
||||
version: 1
|
62
static/static.go
Normal file
62
static/static.go
Normal file
@ -0,0 +1,62 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Version db version info
|
||||
type Version struct {
|
||||
File string `yaml:"file"`
|
||||
Version uint `yaml:"version"`
|
||||
}
|
||||
|
||||
// DBVersion all versions
|
||||
type DBVersion struct {
|
||||
Versions []Version `yaml:"versions"`
|
||||
}
|
||||
|
||||
func GetDBVersions() (*DBVersion, error) {
|
||||
b, err := Asset("schema/version.yml")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ver := &DBVersion{}
|
||||
|
||||
if err := yaml.Unmarshal(b, ver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
func GetSQL(s string) ([]byte, error) {
|
||||
return Asset(path.Join("schema", s))
|
||||
}
|
||||
|
||||
func GetSpec() ([]byte, error) {
|
||||
return Asset("spec.json")
|
||||
}
|
||||
|
||||
func GetWebFile(fp string) (body []byte, mime string, err error) {
|
||||
p := filepath.Join("public", fp)
|
||||
b, err := Asset(p)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
mt := mimetype.Detect(b)
|
||||
mime = mt.String()
|
||||
if strings.HasSuffix(fp, ".css") {
|
||||
mime = "text/css"
|
||||
} else if strings.HasSuffix(fp, ".js") {
|
||||
mime = "text/javascript"
|
||||
}
|
||||
|
||||
return b, mime, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user