update new route middlewares
This commit is contained in:
parent
77cc488d89
commit
b3a93abfb9
|
@ -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,
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 665 B |
Binary file not shown.
After Width: | Height: | Size: 628 B |
|
@ -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>
|
|
@ -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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
|||
package controller
|
||||
|
||||
import "go-api/pkg/context"
|
||||
|
||||
func Health() context.CustomHandler {
|
||||
return func(c *context.C) {
|
||||
c.String(200, "ok")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
-- empty
|
|
@ -0,0 +1,3 @@
|
|||
versions:
|
||||
- file: 'main.sql'
|
||||
version: 1
|
|
@ -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