create ticket
This commit is contained in:
parent
6dfcd7bbed
commit
7ab03b279c
22
app/app.go
22
app/app.go
@ -1,14 +1,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bitbucket.org/BlackBroker/heruvym/dal"
|
||||
"bitbucket.org/BlackBroker/heruvym/middleware"
|
||||
"bitbucket.org/BlackBroker/heruvym/service"
|
||||
"bitbucket.org/BlackBroker/heruvym/version"
|
||||
"github.com/skeris/authService/router"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/BlackBroker/trashlog/wrappers/zaptg"
|
||||
"github.com/skeris/appInit"
|
||||
"github.com/skeris/authService/http_middleware"
|
||||
"github.com/skeris/authService/router"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -112,13 +114,25 @@ func New(ctx context.Context, opts interface{}) (appInit.CommonApp, error) {
|
||||
logger := hlog.New(zapLogger)
|
||||
logger.Emit(InfoSvcStarted{})
|
||||
|
||||
apiMux := router.NewRouter(http.DefaultServeMux)
|
||||
apiMux := router.NewRouter(http.NewServeMux())
|
||||
|
||||
database, err := dal.New(
|
||||
ctx,
|
||||
options.MongoURI,
|
||||
"support",
|
||||
logger,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
heruvym := service.New(database, logger)
|
||||
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
server := &http.Server{
|
||||
Handler: http_middleware.Wrap(apiMux, zapLogger),
|
||||
Handler: http_middleware.Wrap(heruvym.Register(apiMux), zapLogger),
|
||||
Addr: fmt.Sprintf(":%s", options.NumberPortLocal),
|
||||
}
|
||||
|
||||
|
58
dal/dal.go
58
dal/dal.go
@ -11,12 +11,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const collMessages = "messages"
|
||||
const (
|
||||
collMessages = "messages"
|
||||
collTickets = "tickets"
|
||||
)
|
||||
|
||||
type DAL struct {
|
||||
logger hlog.Logger
|
||||
col *mongo.Collection
|
||||
client *mongo.Client
|
||||
logger hlog.Logger
|
||||
colMsg, colTck *mongo.Collection
|
||||
client *mongo.Client
|
||||
}
|
||||
|
||||
type ErrorConnectToDB struct {
|
||||
@ -31,7 +34,7 @@ type ErrorPingDB struct {
|
||||
|
||||
type InfoPing struct {
|
||||
MongoURI string
|
||||
Nanoseconds int
|
||||
Nanoseconds int64
|
||||
}
|
||||
|
||||
func New(
|
||||
@ -48,7 +51,7 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
before := time.Now().Nanosecond()
|
||||
before := time.Now().Unix()
|
||||
|
||||
if err := client.Ping(ctx, readpref.PrimaryPreferred()); err != nil {
|
||||
log.Emit(ErrorPingDB{
|
||||
@ -61,32 +64,33 @@ func New(
|
||||
|
||||
log.Emit(InfoPing{
|
||||
MongoURI: mongoURI,
|
||||
Nanoseconds: time.Now().Nanosecond() - before,
|
||||
Nanoseconds: time.Now().Unix() - before,
|
||||
})
|
||||
|
||||
return &DAL{
|
||||
client: client,
|
||||
col: client.Database(database).Collection(collMessages),
|
||||
colMsg: client.Database(database).Collection(collMessages),
|
||||
colTck: client.Database(database).Collection(collTickets),
|
||||
logger: log.Module("DAL"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ErrorInsert struct {
|
||||
Err error
|
||||
Err error
|
||||
UserID, SessionID string
|
||||
}
|
||||
|
||||
func (d *DAL) PutMessage(
|
||||
ctx context.Context,
|
||||
title, message, userID, sessionID string,
|
||||
message, userID, sessionID, ticketID string,
|
||||
files []string,
|
||||
) error {
|
||||
) error {
|
||||
|
||||
if _, err := d.col.InsertOne(ctx, &model.Message{
|
||||
if _, err := d.colMsg.InsertOne(ctx, &model.Message{
|
||||
ID: xid.New().String(),
|
||||
UserID: userID,
|
||||
SessionID: sessionID,
|
||||
Title: title,
|
||||
TicketID: ticketID,
|
||||
Message: message,
|
||||
Files: files,
|
||||
CreatedAt: time.Now(),
|
||||
@ -102,3 +106,31 @@ func (d *DAL) PutMessage(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DAL) CreateTicket(
|
||||
ctx context.Context,
|
||||
userID,
|
||||
sessionID,
|
||||
title string,
|
||||
) (string, error) {
|
||||
|
||||
tiketID := xid.New().String()
|
||||
|
||||
if _, err := d.colTck.InsertOne(ctx, &model.Ticket{
|
||||
ID: tiketID,
|
||||
UserID: userID,
|
||||
SessionID: sessionID,
|
||||
Title: title,
|
||||
CreatedAt: time.Time{},
|
||||
}); err != nil {
|
||||
d.logger.Emit(ErrorInsert{
|
||||
Err: err,
|
||||
UserID: userID,
|
||||
SessionID: sessionID,
|
||||
})
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tiketID, nil
|
||||
}
|
||||
|
5
go.mod
5
go.mod
@ -3,12 +3,15 @@ module bitbucket.org/BlackBroker/heruvym
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
bitbucket.org/skeris/profile v0.0.0-20210410182208-cf9db6d1452d
|
||||
bitbucket.org/skeris/profile v0.0.0-20210411000338-496c859828a5 // indirect
|
||||
github.com/BlackBroker/trashlog v0.0.0-20210406151703-e2c4874359bf
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/skeris/appInit v0.1.0
|
||||
github.com/skeris/authService v1.1.1 // indirect
|
||||
github.com/skeris/identity v0.0.7 // indirect
|
||||
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 // indirect
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf
|
||||
go.mongodb.org/mongo-driver v1.5.1
|
||||
go.uber.org/zap v1.16.0
|
||||
|
16
go.sum
16
go.sum
@ -1,5 +1,9 @@
|
||||
bitbucket.org/skeris/profile v0.0.0-20210410182208-cf9db6d1452d h1:WAp3CsNISnHvvbn60wYK2AbCYb/UxGk+6EkiTQFX7mw=
|
||||
bitbucket.org/skeris/profile v0.0.0-20210410182208-cf9db6d1452d/go.mod h1:nFz19Zl7b9CNqFXWDdvJKrFS7S/th4dX3GnP5F0Yu5M=
|
||||
bitbucket.org/skeris/profile v0.0.0-20210410222825-ad4fbe380f68 h1:OquUpSs9fjMRMzRErW3a2o3FF9QbKjBzvveT4MPnsY0=
|
||||
bitbucket.org/skeris/profile v0.0.0-20210410222825-ad4fbe380f68/go.mod h1:nFz19Zl7b9CNqFXWDdvJKrFS7S/th4dX3GnP5F0Yu5M=
|
||||
bitbucket.org/skeris/profile v0.0.0-20210411000338-496c859828a5 h1:nAtrodBJM8auGMZB4IrLFmdM/VpDsXjycC+e53SFNJ0=
|
||||
bitbucket.org/skeris/profile v0.0.0-20210411000338-496c859828a5/go.mod h1:nFz19Zl7b9CNqFXWDdvJKrFS7S/th4dX3GnP5F0Yu5M=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
@ -62,6 +66,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
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=
|
||||
@ -187,6 +193,10 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mailjet/mailjet-apiv3-go v0.0.0-20201009050126-c24bc15a9394/go.mod h1:ogN8Sxy3n5VKLhQxbtSBM3ICG/VgjXS/akQJIoDSrgA=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
|
||||
@ -248,8 +258,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 h1:N9f/Q+2Ssa+yDcbfaoLTYvXmdeyUUxsJKdPUVsjSmiA=
|
||||
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33/go.mod h1:rpcH99JknBh8seZmlOlUg51gasZH6QH34oXNsIwYT6E=
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo=
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf/go.mod h1:1FsorU3vnXO9xS9SrhUp8fRb/6H/Zfll0rPt1i4GWaA=
|
||||
github.com/themakers/identity v0.0.0-20200703212242-9142bb6b35e1 h1:DuzLgIqC0AxW3Gy+Mhwn15R9KvFe8nifO6Pbe3j07Ng=
|
||||
github.com/themakers/identity v0.0.0-20200703212242-9142bb6b35e1/go.mod h1:lHYuLs7VL+KVZpEzfXnrv8YwlGXcuZgUZjV5pSRb+Fc=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
@ -398,6 +411,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -414,6 +428,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b h1:ggRgirZABFolTmi3sn6Ivd9SipZwLedQ5wR0aAKnFxU=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
119
jwt_adapter/jwt_adapter.go
Normal file
119
jwt_adapter/jwt_adapter.go
Normal file
@ -0,0 +1,119 @@
|
||||
package jwt_adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/rs/xid"
|
||||
"github.com/skeris/identity/cookie"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ cookie.Cookie = new(JwtAdapter)
|
||||
|
||||
func init() {
|
||||
aS := os.Getenv("JWT_SECRET")
|
||||
if len(aS) != 0 {
|
||||
accessSecret = aS
|
||||
}
|
||||
}
|
||||
|
||||
type JwtAdapter struct {
|
||||
ID string
|
||||
Session string
|
||||
User string
|
||||
|
||||
Tariff uint8
|
||||
|
||||
Created int64
|
||||
LastSeen int64
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) Init() {
|
||||
if c.ID == "" {
|
||||
c.ID = xid.New().String()
|
||||
}
|
||||
|
||||
c.Session = xid.New().String()
|
||||
c.User = ""
|
||||
c.Tariff = uint8(0)
|
||||
|
||||
t := Timestamp()
|
||||
c.Created = t
|
||||
c.LastSeen = t
|
||||
}
|
||||
|
||||
func Get(ctx context.Context) *JwtAdapter {
|
||||
if adapter, ok := ctx.Value(DefaultHeaderKey).(*JwtAdapter); ok {
|
||||
return adapter
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) SetUserID(id string) {
|
||||
c.User = id
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) GetUserID() string {
|
||||
return c.User
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) GetTariff() uint8 {
|
||||
return c.Tariff
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) SetTariff(status uint8) {
|
||||
c.Tariff = status
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) GetSessionID() string {
|
||||
return c.Session
|
||||
}
|
||||
|
||||
func (c *JwtAdapter) SetSessionID(id string) {
|
||||
c.Session = id
|
||||
c.User = ""
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultAccessSecret = "awesomeAC"
|
||||
DefaultHeaderKey = "Authorization"
|
||||
)
|
||||
|
||||
var accessSecret = DefaultAccessSecret
|
||||
|
||||
func (c *JwtAdapter) Encode() (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
|
||||
ss, err := token.SignedString([]byte(accessSecret))
|
||||
|
||||
return ss, err
|
||||
}
|
||||
|
||||
func Decode(tokenString string) (*JwtAdapter, error) {
|
||||
claims := JwtAdapter{}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||
//Make sure that the token method conform to "SigningMethodHMAC"
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(accessSecret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {
|
||||
return nil, fmt.Errorf("ErrorNoValidClaims")
|
||||
}
|
||||
|
||||
return &claims, nil
|
||||
}
|
||||
|
||||
func Timestamp() int64 {
|
||||
return time.Now().UnixNano() / int64(time.Millisecond)
|
||||
}
|
66
middleware/hijack/response_writer.go
Normal file
66
middleware/hijack/response_writer.go
Normal file
@ -0,0 +1,66 @@
|
||||
package hijack
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func New(w http.ResponseWriter, commit func(http.ResponseWriter)) (http.ResponseWriter, func()) {
|
||||
responseWriter := &responseWriter{
|
||||
commit: commit,
|
||||
w: w,
|
||||
}
|
||||
|
||||
if _, ok := w.(http.Hijacker); ok {
|
||||
return &responseWriterHijacker{
|
||||
responseWriter: responseWriter,
|
||||
}, responseWriter.ensureCommitted
|
||||
} else {
|
||||
return responseWriter, responseWriter.ensureCommitted
|
||||
}
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = new(responseWriter)
|
||||
var _ http.ResponseWriter = new(responseWriterHijacker)
|
||||
var _ http.Hijacker = new(responseWriterHijacker)
|
||||
|
||||
type responseWriter struct {
|
||||
commit func(http.ResponseWriter)
|
||||
w http.ResponseWriter
|
||||
hijacked bool
|
||||
writeOnce sync.Once
|
||||
}
|
||||
|
||||
func (rw *responseWriter) ensureCommitted() {
|
||||
rw.writeOnce.Do(func() {
|
||||
if rw.hijacked {
|
||||
return
|
||||
}
|
||||
rw.commit(rw.w)
|
||||
})
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Header() http.Header {
|
||||
return rw.w.Header()
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(data []byte) (int, error) {
|
||||
rw.ensureCommitted()
|
||||
return rw.w.Write(data)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(status int) {
|
||||
rw.ensureCommitted()
|
||||
rw.w.WriteHeader(status)
|
||||
}
|
||||
|
||||
type responseWriterHijacker struct {
|
||||
*responseWriter
|
||||
}
|
||||
|
||||
func (rw *responseWriterHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
rw.hijacked = true
|
||||
return rw.w.(http.Hijacker).Hijack()
|
||||
}
|
234
middleware/http_middleware.go
Normal file
234
middleware/http_middleware.go
Normal file
@ -0,0 +1,234 @@
|
||||
package http_middleware
|
||||
|
||||
import (
|
||||
"bitbucket.org/BlackBroker/heruvym/jwt_adapter"
|
||||
"bitbucket.org/BlackBroker/heruvym/middleware/hijack"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/skeris/authService/errors"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type MiddlewareFunc func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
|
||||
|
||||
type AfterFunc func(ctx context.Context, r *http.Request) (context.Context, error)
|
||||
|
||||
type SetCookieValueFunc func(w http.ResponseWriter, value string)
|
||||
|
||||
func DefaultChain(
|
||||
log *zap.Logger,
|
||||
recFn RecoverFunc,
|
||||
afn AfterFunc,
|
||||
setCookieValue SetCookieValueFunc,
|
||||
mws ...MiddlewareFunc,
|
||||
) http.HandlerFunc {
|
||||
return Chain(
|
||||
append(
|
||||
[]MiddlewareFunc{
|
||||
MiddlewareRecovery(log, recFn),
|
||||
MiddlewareLogger(log),
|
||||
DefaultCookieAndRecoveryMiddleware(
|
||||
log,
|
||||
recFn, afn, setCookieValue,
|
||||
),
|
||||
},
|
||||
mws...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
func DefaultCookieAndRecoveryMiddleware(
|
||||
log *zap.Logger,
|
||||
recFn RecoverFunc,
|
||||
afn AfterFunc,
|
||||
setCookieValue SetCookieValueFunc,
|
||||
) MiddlewareFunc {
|
||||
|
||||
const headerKey = jwt_adapter.DefaultHeaderKey
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
var err error
|
||||
cookie := &jwt_adapter.JwtAdapter{}
|
||||
tokenHeader := r.Header.Get(headerKey)
|
||||
|
||||
if tokenHeader == "" {
|
||||
fmt.Println("ERROR NO authHEader")
|
||||
cookie.Init()
|
||||
} else {
|
||||
|
||||
splitted := strings.Split(tokenHeader, " ")
|
||||
if len(splitted) != 2 {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
return
|
||||
}
|
||||
|
||||
tokenPart := splitted[1]
|
||||
|
||||
cookie, err = jwt_adapter.Decode(tokenPart)
|
||||
if err != nil {
|
||||
cookie.Init()
|
||||
}
|
||||
}
|
||||
|
||||
recovery := struct {
|
||||
val interface{}
|
||||
trace string
|
||||
}{}
|
||||
|
||||
ctx := context.WithValue(r.Context(), headerKey, cookie)
|
||||
|
||||
if afn != nil {
|
||||
c, err := afn(ctx, r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx = c
|
||||
}
|
||||
|
||||
w, commit := hijack.New(w, func(w http.ResponseWriter) {
|
||||
cookie.LastSeen = jwt_adapter.Timestamp()
|
||||
if val, err := cookie.Encode(); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
setCookieValue(w, val)
|
||||
}
|
||||
|
||||
if recovery.val != nil {
|
||||
log.Error("handler recovered", zap.Any("recovered", recovery.val))
|
||||
code, message := recFn(recovery.val, ctx)
|
||||
w.WriteHeader(code)
|
||||
if _, err := fmt.Fprint(w, message); err != nil {
|
||||
log.Error("error writing panic response", zap.Error(err))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
recovery.val = rec
|
||||
recovery.trace = string(debug.Stack())
|
||||
}
|
||||
commit()
|
||||
}()
|
||||
|
||||
next(w, r.WithContext(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func Chain(mws ...MiddlewareFunc) http.HandlerFunc {
|
||||
if len(mws) == 0 {
|
||||
return func(w http.ResponseWriter, r *http.Request) {}
|
||||
}
|
||||
h := link(mws[len(mws)-1], nil)
|
||||
for i := len(mws) - 2; i >= 0; i-- {
|
||||
mw := mws[i]
|
||||
|
||||
h = link(mw, h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
func link(mw MiddlewareFunc, next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
mw(w, r, next)
|
||||
}
|
||||
}
|
||||
|
||||
type RecoverFunc func(rec interface{}, ctx context.Context) (code int, message string)
|
||||
|
||||
func MiddlewareRecovery(log *zap.Logger, recFn RecoverFunc) MiddlewareFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
code, message := recFn(rec, r.Context())
|
||||
w.WriteHeader(code)
|
||||
if _, err := fmt.Fprint(w, message); err != nil {
|
||||
log.Error("error writing panic response", zap.Error(err))
|
||||
}
|
||||
log.Error("panic in http handler", zap.Int("code", code), zap.String("message", message), zap.Any("recovered", rec))
|
||||
}
|
||||
}()
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
func MiddlewareLogger(log *zap.Logger) MiddlewareFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
log.Debug("http request", zap.String("url", r.URL.String()))
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func Handler(h http.HandlerFunc) MiddlewareFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
h.ServeHTTP(w, r)
|
||||
if next != nil {
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Wrap(mux *http.ServeMux, logger *zap.Logger) http.HandlerFunc {
|
||||
return DefaultChain(
|
||||
logger,
|
||||
func(rec interface{}, ctx context.Context) (int, string) {
|
||||
var (
|
||||
code int
|
||||
message string
|
||||
)
|
||||
|
||||
if err, ok := rec.(error); ok {
|
||||
if v, ok := errors.IsForbidden(err); ok {
|
||||
code = http.StatusForbidden
|
||||
message = v.Error()
|
||||
} else if v, ok := errors.IsUnauthenticated(err); ok {
|
||||
code = http.StatusUnauthorized
|
||||
message = v.Error()
|
||||
} else {
|
||||
code = http.StatusInternalServerError
|
||||
message = err.Error()
|
||||
}
|
||||
} else {
|
||||
code = http.StatusInternalServerError
|
||||
message = fmt.Sprintf("%v", rec)
|
||||
}
|
||||
|
||||
return code, message
|
||||
},
|
||||
nil,
|
||||
func(w http.ResponseWriter, val string) {
|
||||
var setFn func(key, value string)
|
||||
if w.Header().Get(jwt_adapter.DefaultHeaderKey) == "" {
|
||||
setFn = w.Header().Add
|
||||
} else {
|
||||
setFn = w.Header().Set
|
||||
}
|
||||
|
||||
setFn(jwt_adapter.DefaultHeaderKey, fmt.Sprintf("Bearer %s", val))
|
||||
},
|
||||
Handler((func() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
//todo specify origins
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
w.Header().Set("Access-Control-Expose-Headers", "*")
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
|
||||
mux.ServeHTTP(w, r)
|
||||
|
||||
}
|
||||
})()))
|
||||
}
|
@ -3,14 +3,23 @@ package model
|
||||
import "time"
|
||||
|
||||
type Message struct {
|
||||
ID string `bson:"_id"`
|
||||
TicketID string `bson:"TicketID"`
|
||||
UserID string `bson:"UserID"`
|
||||
SessionID string `bson:"SessionID"`
|
||||
|
||||
Message string `bson:"Messsage"`
|
||||
Files []string `bson:"Files"`
|
||||
|
||||
CreatedAt time.Time `bson:"CreatedAt"`
|
||||
}
|
||||
|
||||
type Ticket struct {
|
||||
ID string `bson:"_id"`
|
||||
UserID string `bson:"UserID"`
|
||||
SessionID string `bson:"SessionID"`
|
||||
|
||||
Title string `bson:"Title"`
|
||||
Message string `bson:"Messsage"`
|
||||
|
||||
Files []string `bson:"Files"`
|
||||
Title string `bson:"Title"`
|
||||
|
||||
CreatedAt time.Time `bson:"CreatedAt"`
|
||||
}
|
||||
|
13
service/errors.go
Normal file
13
service/errors.go
Normal file
@ -0,0 +1,13 @@
|
||||
package service
|
||||
|
||||
type ErrorClose struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
type ErrorMarshal struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
type ErrorWrite struct {
|
||||
Err error
|
||||
}
|
111
service/service.go
Normal file
111
service/service.go
Normal file
@ -0,0 +1,111 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bitbucket.org/BlackBroker/heruvym/dal"
|
||||
"bitbucket.org/BlackBroker/heruvym/jwt_adapter"
|
||||
"encoding/json"
|
||||
"github.com/themakers/hlog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Heruvym struct {
|
||||
logger hlog.Logger
|
||||
dal *dal.DAL
|
||||
}
|
||||
|
||||
func New(dataAccessLayer *dal.DAL, log hlog.Logger) *Heruvym {
|
||||
return &Heruvym{
|
||||
logger: log.Module("Service"),
|
||||
dal: dataAccessLayer,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Heruvym) Register(m *http.ServeMux) *http.ServeMux {
|
||||
|
||||
m.HandleFunc("/create", h.CreateTicket)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type CreateTicketReq struct {
|
||||
Title string `json:"Title"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
type CreateTicketResp struct {
|
||||
Ticket string `json:"Ticket"`
|
||||
}
|
||||
|
||||
func (h *Heruvym) CreateTicket(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := r.Body.Close(); err != nil {
|
||||
h.logger.Emit(ErrorClose{
|
||||
Err: err,
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
var request CreateTicketReq
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, "Invalid json", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if request.Title == "" {
|
||||
http.Error(w, "No Title", http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
if request.Message == "" {
|
||||
http.Error(w, "No Message", http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
session := jwt_adapter.Get(ctx)
|
||||
|
||||
if session == nil {
|
||||
http.Error(w, "No session", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
ticketID, err := h.dal.CreateTicket(
|
||||
ctx,
|
||||
session.User,
|
||||
session.ID,
|
||||
request.Title,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "CannotCreateTicket", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.dal.PutMessage(ctx,
|
||||
request.Message,
|
||||
session.User,
|
||||
session.Session,
|
||||
ticketID,
|
||||
[]string{},
|
||||
); err != nil {
|
||||
http.Error(w, "CannotCreateMessage", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := json.Marshal(CreateTicketResp{Ticket: ticketID})
|
||||
if err != nil {
|
||||
h.logger.Emit(ErrorMarshal{
|
||||
Err: err,
|
||||
})
|
||||
http.Error(w, "CannotMarshalMessage", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(response); err != nil {
|
||||
h.logger.Emit(ErrorMarshal{
|
||||
Err: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
64
test/main_test.go
Normal file
64
test/main_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bitbucket.org/BlackBroker/heruvym/app"
|
||||
"bitbucket.org/BlackBroker/heruvym/service"
|
||||
"bitbucket.org/skeris/profile/jwt_adapter"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/themakers/bdd"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTicket(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
bdd.Scenario(t, "SupportChat", func(t *testing.T, runID string) {
|
||||
go func () {
|
||||
_, err := app.New(ctx, app.Options{
|
||||
MongoURI: "mongodb://localhost:27017",
|
||||
NumberPortLocal: "1488",
|
||||
LoggerDevMode: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
bdd.Act(t, "client", func() {
|
||||
bdd.Test(t, "CreateTicket", func() {
|
||||
buf, err := json.Marshal(service.CreateTicketReq{
|
||||
Title: "TestTitle",
|
||||
Message: "test",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err :=http.NewRequestWithContext(
|
||||
ctx,
|
||||
"POST",
|
||||
"http://localhost:1488/create", bytes.NewBuffer(buf))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add(jwt_adapter.DefaultHeaderKey, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6ImMxcDM0YjhqZHBmOG04Zm43cnIwIiwiU2Vzc2lvbiI6ImMxcDM0YjhqZHBmOG04Zm43cnJnIiwiVXNlciI6IiIsIlRhcmlmZiI6MCwiQ3JlYXRlZCI6MTYxODA5NjY4NTc4MCwiTGFzdFNlZW4iOjE2MTgwOTY2ODU3ODF9.ciJoJiOxzIPv0LY4h3rG8Tf3AsSBXXLcYEpyN9mIki0")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
panic("NotAccepted")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user