229 lines
5.6 KiB
Go
229 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
|
|
}
|