-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrest.go
179 lines (153 loc) · 5.92 KB
/
rest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package rest
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"github.com/gin-gonic/gin"
"github.com/spf13/afero"
_ "github.com/surahman/FTeX/docs" // Swaggo generated Swagger documentation
"github.com/surahman/FTeX/pkg/auth"
"github.com/surahman/FTeX/pkg/logger"
"github.com/surahman/FTeX/pkg/postgres"
"github.com/surahman/FTeX/pkg/quotes"
"github.com/surahman/FTeX/pkg/redis"
restHandlers "github.com/surahman/FTeX/pkg/rest/handlers"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"go.uber.org/zap"
)
// Format and generate Swagger UI files using makefile.
//go:generate make -C ../../ swagger
// Server is the HTTP REST server.
type Server struct {
auth auth.Auth
cache redis.Redis
db postgres.Postgres
quotes quotes.Quotes
conf *config
logger *logger.Logger
router *gin.Engine
wg *sync.WaitGroup
}
// NewServer will create a new REST server instance in a non-running state.
func NewServer(fs *afero.Fs, auth auth.Auth, postgres postgres.Postgres, redis redis.Redis, quotes quotes.Quotes,
logger *logger.Logger, wg *sync.WaitGroup) (server *Server, err error) {
// Load configurations.
conf := newConfig()
if err = conf.Load(*fs); err != nil {
return
}
return &Server{
conf: conf,
auth: auth,
cache: redis,
db: postgres,
quotes: quotes,
logger: logger,
wg: wg,
},
err
}
// initialize will configure the HTTP server routes.
func (s *Server) initialize() {
s.router = gin.Default()
// @title FTeX, Inc. (Formerly Crypto-Bro's Bank, Inc.)
// @version 1.2.6
// @description FTeX Fiat and Cryptocurrency Banking API.
// @description Bank, buy, and sell Fiat and Cryptocurrencies. Prices for all currencies are retrieved from real-time quote providers.
//
// @schemes http
// @host localhost:33723
// @BasePath /api/rest/v1
//
// @accept json
// @produce json
//
// @contact.name Saad Ur Rahman
// @contact.url https://www.linkedin.com/in/saad-ur-rahman/
// @contact.email [email protected]
//
// @license.name GPL-3.0
// @license.url https://opensource.org/licenses/GPL-3.0
//
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
s.router.GET(s.conf.Server.SwaggerPath, ginSwagger.WrapHandler(swaggerfiles.Handler))
// Endpoint configurations
authMiddleware := restHandlers.AuthMiddleware(s.auth, s.db, s.logger, s.conf.Authorization.HeaderKey)
api := s.router.Group(s.conf.Server.BasePath)
api.GET("/health", restHandlers.Healthcheck(s.logger, s.db, s.cache))
userGroup := api.Group("/user")
userGroup.POST("/register", restHandlers.RegisterUser(s.logger, s.auth, s.db))
userGroup.POST("/login", restHandlers.LoginUser(s.logger, s.auth, s.db))
userGroup.
Use(authMiddleware).
POST("/refresh", restHandlers.LoginRefresh(s.logger, s.auth, s.db))
userGroup.
Use(authMiddleware).
DELETE("/delete", restHandlers.DeleteUser(s.logger, s.auth, s.db))
fiatGroup := api.Group("/fiat").Use(authMiddleware)
fiatGroup.POST("/open", restHandlers.OpenFiat(s.logger, s.auth, s.db))
fiatGroup.POST("/deposit", restHandlers.DepositFiat(s.logger, s.auth, s.db))
fiatGroup.POST("/exchange/offer", restHandlers.ExchangeOfferFiat(s.logger, s.auth, s.cache, s.quotes))
fiatGroup.POST("/exchange/transfer", restHandlers.ExchangeTransferFiat(s.logger, s.auth, s.cache, s.db))
fiatGroup.GET("/info/balance/:ticker", restHandlers.BalanceFiat(s.logger, s.auth, s.db))
fiatGroup.GET("/info/balance/", restHandlers.BalanceFiatPaginated(s.logger, s.auth, s.db))
fiatGroup.GET("/info/transaction/:transactionID", restHandlers.TxDetailsFiat(s.logger, s.auth, s.db))
fiatGroup.GET("/info/transaction/all/:currencyCode", restHandlers.TxDetailsFiatPaginated(s.logger, s.auth, s.db))
cryptoGroup := api.Group("/crypto").Use(authMiddleware)
cryptoGroup.POST("/open", restHandlers.OpenCrypto(s.logger, s.auth, s.db))
cryptoGroup.POST("/offer", restHandlers.OfferCrypto(s.logger, s.auth, s.cache, s.quotes))
cryptoGroup.POST("/exchange", restHandlers.ExchangeCrypto(s.logger, s.auth, s.cache, s.db))
cryptoGroup.GET("/info/balance/:ticker", restHandlers.BalanceCrypto(s.logger, s.auth, s.db))
cryptoGroup.GET("/info/transaction/:transactionID", restHandlers.TxDetailsCrypto(s.logger, s.auth, s.db))
cryptoGroup.GET("/info/balance/", restHandlers.BalanceCryptoPaginated(s.logger, s.auth, s.db))
cryptoGroup.GET("/info/transaction/all/:ticker", restHandlers.TxDetailsCryptoPaginated(s.logger, s.auth, s.db))
}
// Run brings the HTTP service up.
func (s *Server) Run() {
// Indicate to bootstrapping thread to wait for completion.
defer s.wg.Done()
// Configure routes.
s.initialize()
// Create server.
srv := &http.Server{
ReadTimeout: s.conf.Server.ReadTimeout,
WriteTimeout: s.conf.Server.WriteTimeout,
ReadHeaderTimeout: s.conf.Server.ReadHeaderTimeout,
Addr: fmt.Sprintf(":%d", s.conf.Server.PortNumber),
Handler: s.router,
}
// Error channel for failed server start.
serverErr := make(chan error, 1)
// Wait for interrupt signal to gracefully shut down the server.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Start HTTP listener.
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
serverErr <- err
}
}()
// Check for server start failure or shutdown signal.
select {
case err := <-serverErr:
s.logger.Error(fmt.Sprintf("REST server failed to listen on port %d", s.conf.Server.PortNumber), zap.Error(err))
return
case <-quit:
s.logger.Info("Shutting down REST server...", zap.Duration("waiting", s.conf.Server.ShutdownDelay))
}
ctx, cancel := context.WithTimeout(context.Background(), s.conf.Server.ShutdownDelay)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
s.logger.Panic("Failed to shutdown REST server", zap.Error(err))
}
// 5 second wait to exit.
<-ctx.Done()
s.logger.Info("REST server exited")
}