update new route middlewares

This commit is contained in:
Jay 2020-08-16 17:39:13 +08:00
parent 77cc488d89
commit b3a93abfb9
29 changed files with 643 additions and 5 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
static/bundle.go
spec.json

View File

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

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

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

View File

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

61
public/swagger/index.html Normal file
View 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>

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

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

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

View 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()
}
}

View 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()
}
}
}

View File

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

@ -0,0 +1 @@
-- empty

3
schema/version.yml Normal file
View File

@ -0,0 +1,3 @@
versions:
- file: 'main.sql'
version: 1

62
static/static.go Normal file
View 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
}