Files
LK_API_Temp/cmd/api/api.go
T

227 lines
5.6 KiB
Go

package main
import (
"context"
"errors"
"expvar"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/FernandoVideira/LK_API_Temp/docs" // This is required to autogenerate swagger docs
"github.com/FernandoVideira/LK_API_Temp/internal/auth"
"github.com/FernandoVideira/LK_API_Temp/internal/mailer"
"github.com/FernandoVideira/LK_API_Temp/internal/ratelimiter"
"github.com/FernandoVideira/LK_API_Temp/internal/store"
"github.com/FernandoVideira/LK_API_Temp/internal/store/cache"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware
"go.uber.org/zap"
)
type api struct {
config config
store store.Storage
cacheStore cache.Storage
logger *zap.SugaredLogger
mailer mailer.Client
authenticator auth.Authenticator
rateLimiter ratelimiter.Limiter
}
type config struct {
addr string
db dbConfig
env string
apiURL string
mail mailConfig
frontEndURL string
auth authConfig
redisCfg redisConfig
rateLimiter ratelimiter.Config
}
type mailConfig struct {
inviteExp time.Duration
passwordRefreshExp time.Duration
fromEmail string
sendGrid sendGridConfig
}
type authConfig struct {
basic basicConfig
token tokenConfig
}
type basicConfig struct {
username string
password string
}
type tokenConfig struct {
secret string
exp time.Duration
refreshExp time.Duration
iss string
aud string
}
type redisConfig struct {
addr string
pw string
db int
enabled bool
}
// TODO: Switch to previously made SMTP mailer
type sendGridConfig struct {
apiKey string
}
type dbConfig struct {
addr string
maxOpenConns int
maxIdleConns int
maxIdleTime time.Duration
}
// This `mount` function is responsible for setting up the routing for your API
// using the Chi router. Here's a breakdown of what it does:
func (api *api) mount() http.Handler {
r := chi.NewRouter()
// *CORS
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"https://*", "http://*"}, //* Use this for development
//AllowedOrigins: []string{env.GetList("ALLOWED_ORIGINS", []string{"https://lk-api-temp.onrender.com"})}, //* Use this for production
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: false,
MaxAge: 300,
}))
r.Use(api.RateLimiterMiddleware)
r.Use(middleware.Timeout(60 * time.Second))
r.Route("/v1", func(r chi.Router) {
//r.With(api.BasicAuthMiddleware()).Get("/health", api.healthCheckHandler)
r.Get("/health", api.healthCheckHandler)
r.With(api.BasicAuthMiddleware()).Get("/debug/vars", expvar.Handler().ServeHTTP)
docsURL := fmt.Sprintf("%s/swagger/doc.json", api.config.addr)
r.Get("/swagger/*", httpSwagger.Handler(httpSwagger.URL(docsURL)))
r.Route("/posts", func(r chi.Router) {
r.Use(api.AuthTokenMiddleware)
r.Post("/", api.createPostHandler)
r.Route("/{id}", func(r chi.Router) {
r.Use(api.postsContextMiddleware)
r.Get("/", api.getPostHandler)
r.Patch("/", api.checkPostOwnership("moderator", api.updatePostHandler))
r.Delete("/", api.checkPostOwnership("admin", api.deletePostHandler))
r.Post("/comments", api.createCommentHandler)
})
})
r.Route("/users", func(r chi.Router) {
r.Put("/activate/{token}", api.activateUserHandler)
r.Put("/reset-password/{token}", api.resetPasswordHandler)
r.Route("/me", func(r chi.Router) {
r.Use(api.AuthTokenMiddleware)
r.Get("/", api.getUserHandler)
r.Patch("/", api.checkProfileOwnership(api.updateUserHandler))
})
})
r.Route("/authentication", func(r chi.Router) {
r.Post("/register", api.registerUserHandler)
r.Post("/token", api.createTokenHandler)
r.Post("/forgot-password", api.forgotPasswordHandler)
r.Post("/refresh", api.refreshTokenHandler)
})
r.Route("/admin", func(r chi.Router) {
r.Use(api.AuthTokenMiddleware)
r.Route("/users", func(r chi.Router) {
r.Get("/", api.protectAdminRoutes("moderator", api.getUsersHandler))
r.Route("/{id}", func(r chi.Router) {
//r.Get("/", api.protectAdminRoutes("moderator", api.getUserByIdHandler))
})
})
})
})
return r
}
func (api *api) run(mux http.Handler) error {
//Docs
docs.SwaggerInfo.Version = "0.0.1"
docs.SwaggerInfo.Host = api.config.apiURL
docs.SwaggerInfo.BasePath = "/v1"
srv := http.Server{
Addr: api.config.addr,
Handler: mux,
WriteTimeout: 30 * time.Second,
ReadTimeout: 10 * time.Second,
IdleTimeout: time.Minute,
}
shutdown := make(chan error)
go func() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
s := <-quit
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
api.logger.Infow("shutting down server", "signal", s.String())
shutdown <- srv.Shutdown(ctx)
}()
api.logger.Infow("server has started", "addr", api.config.addr, "env", api.config.env)
err := srv.ListenAndServe()
if !errors.Is(err, http.ErrServerClosed) {
return err
}
err = <-shutdown
if err != nil {
return err
}
if userStore, ok := api.store.Users.(*store.UserStore); ok {
if err := userStore.Close(); err != nil {
api.logger.Errorw("error closing userstore", err)
}
}
api.logger.Infow("server has been shutdown")
return nil
}