refactor: simplified code structure

This commit is contained in:
Tigor Hutasuhut 2024-04-09 14:30:54 +07:00
parent 33b35e6839
commit e5002d8187
13 changed files with 138 additions and 150 deletions

View file

@ -1,18 +1,20 @@
package cli package cli
import ( import (
"io/fs"
"os" "os"
"os/signal" "os/signal"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tigorlazuardi/redmage/api"
"github.com/tigorlazuardi/redmage/db" "github.com/tigorlazuardi/redmage/db"
"github.com/tigorlazuardi/redmage/db/queries/subreddits" "github.com/tigorlazuardi/redmage/db/queries"
"github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/server" "github.com/tigorlazuardi/redmage/server"
"github.com/tigorlazuardi/redmage/server/routes/api"
"github.com/tigorlazuardi/redmage/server/routes/www"
) )
var PublicDir fs.FS = os.DirFS("public")
var serveCmd = &cobra.Command{ var serveCmd = &cobra.Command{
Use: "serve", Use: "serve",
Short: "Starts the HTTP Server", Short: "Starts the HTTP Server",
@ -24,18 +26,14 @@ var serveCmd = &cobra.Command{
os.Exit(1) os.Exit(1)
} }
subreddits := subreddits.New(db) queries := queries.New(db)
api := &api.API{ api := &api.API{
Subreddits: subreddits, Queries: queries,
Config: cfg, DB: db,
} }
www := &www.WWW{ server := server.New(cfg, api, PublicDir)
Subreddits: subreddits,
Config: cfg,
}
server := server.New(cfg, api, www)
exit := make(chan struct{}, 1) exit := make(chan struct{}, 1)

View file

@ -10,7 +10,6 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/tigorlazuardi/redmage/cli" "github.com/tigorlazuardi/redmage/cli"
"github.com/tigorlazuardi/redmage/db" "github.com/tigorlazuardi/redmage/db"
"github.com/tigorlazuardi/redmage/server/routes/www"
) )
//go:embed db/migrations/*.sql //go:embed db/migrations/*.sql
@ -23,7 +22,7 @@ func main() {
_ = godotenv.Load() _ = godotenv.Load()
db.Migrations = Migrations db.Migrations = Migrations
var err error var err error
www.PublicFS, err = fs.Sub(PublicFS, "public") cli.PublicDir, err = fs.Sub(PublicFS, "public")
if err != nil { if err != nil {
panic(errors.New("failed to create sub filesystem")) panic(errors.New("failed to create sub filesystem"))
} }

View file

@ -1,24 +0,0 @@
package api
import (
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/tigorlazuardi/redmage/config"
"github.com/tigorlazuardi/redmage/db/queries/subreddits"
"github.com/tigorlazuardi/redmage/server/routes/middleware"
)
type API struct {
Subreddits *subreddits.Queries
Config *config.Config
}
func (api *API) Register(router chi.Router) {
router.Use(chimiddleware.RequestID)
router.Get("/", HealthCheck)
router.Get("/health", HealthCheck)
router.Route("/subreddits", func(r chi.Router) {
r.Use(chimiddleware.RequestLogger(middleware.ChiLogger{}))
r.Get("/", api.ListSubreddits)
})
}

View file

@ -1,10 +0,0 @@
package api
import (
"net/http"
)
func HealthCheck(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("Content-Type", "application/json")
_, _ = rw.Write([]byte(`{"status":"ok"}`))
}

View file

@ -1,40 +0,0 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/tigorlazuardi/redmage/db/queries/subreddits"
"github.com/tigorlazuardi/redmage/pkg/log"
)
func (api *API) ListSubreddits(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("Content-Type", "application/json")
subs, err := api.Subreddits.ListSubreddits(r.Context(), parseListSubredditsQuery(r))
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
msg := fmt.Sprintf("failed to list subreddits: %s", err)
_ = json.NewEncoder(rw).Encode(map[string]string{"error": msg})
return
}
if err := json.NewEncoder(rw).Encode(subs); err != nil {
log.New(r.Context()).Err(err).Error("failed to list subreddits")
}
}
func parseListSubredditsQuery(r *http.Request) subreddits.ListSubredditsParams {
params := subreddits.ListSubredditsParams{}
params.Limit, _ = strconv.ParseInt(r.URL.Query().Get("limit"), 10, 64)
params.Offset, _ = strconv.ParseInt(r.URL.Query().Get("offset"), 10, 64)
if params.Limit < 1 {
params.Limit = 10
} else if params.Limit > 100 {
params.Limit = 100
}
return params
}

View file

@ -0,0 +1,9 @@
package routes
import (
"net/http"
)
func (routes *Routes) HealthCheck(rw http.ResponseWriter, req *http.Request) {
_, _ = rw.Write([]byte{})
}

View file

@ -1,4 +1,4 @@
package www package routes
import ( import (
"errors" "errors"
@ -9,7 +9,7 @@ import (
"github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/pkg/log"
) )
func (www *WWW) CreateHotReloadRoute() http.HandlerFunc { func (routes *Routes) CreateHotReloadRoute() http.HandlerFunc {
var mu sync.Mutex var mu sync.Mutex
knownClients := make(map[string]chan struct{}) knownClients := make(map[string]chan struct{})
firstTime := make(chan struct{}, 1) firstTime := make(chan struct{}, 1)

View file

@ -0,0 +1,17 @@
package routes
import (
"net/http"
"github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/views"
"github.com/tigorlazuardi/redmage/views/homeview"
)
func (routes *Routes) PageHome(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vc := views.NewContext(routes.Config, r)
if err := homeview.Home(vc).Render(ctx, rw); err != nil {
log.New(ctx).Err(err).Error("failed to render home view")
}
}

45
server/routes/routes.go Normal file
View file

@ -0,0 +1,45 @@
package routes
import (
"io/fs"
"net/http"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/tigorlazuardi/redmage/api"
"github.com/tigorlazuardi/redmage/config"
"github.com/tigorlazuardi/redmage/server/routes/middleware"
)
type Routes struct {
API *api.API
Config *config.Config
PublicDir fs.FS
}
func (routes *Routes) Register(router chi.Router) {
router.HandleFunc("/ping", routes.HealthCheck)
router.HandleFunc("/health", routes.HealthCheck)
if routes.Config.Bool("http.hotreload") {
router.Get("/hot_reload", routes.CreateHotReloadRoute())
}
router.Group(routes.registerWWWRoutes)
router.Route("/api/v1", routes.registerV1APIRoutes)
}
func (routes *Routes) registerV1APIRoutes(router chi.Router) {
router.Use(chimiddleware.RequestLogger(middleware.ChiLogger{}))
router.Use(chimiddleware.SetHeader("Content-Type", "application/json"))
router.Get("/subreddits", routes.SubredditsListAPI)
}
func (routes *Routes) registerWWWRoutes(router chi.Router) {
router.Mount("/public", http.StripPrefix("/public", http.FileServer(http.FS(routes.PublicDir))))
router.Group(func(r chi.Router) {
r.Use(chimiddleware.RequestLogger(middleware.ChiLogger{}))
r.Get("/", routes.PageHome)
})
}

View file

@ -0,0 +1,41 @@
package routes
import (
"encoding/json"
"net/http"
"strconv"
"github.com/tigorlazuardi/redmage/api"
"github.com/tigorlazuardi/redmage/pkg/log"
)
func (r *Routes) SubredditsListAPI(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
params := parseSubredditListQuery(req)
result, err := r.API.ListSubreddits(ctx, params)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(rw).Encode(map[string]string{"error": err.Error()})
return
}
if err := json.NewEncoder(rw).Encode(result); err != nil {
log.New(ctx).Err(err).Error("failed to encode subreddits into json")
}
}
func (r *Routes) SubredditsListPage(rw http.ResponseWriter, req *http.Request) {
}
func parseSubredditListQuery(req *http.Request) (params api.ListSubredditsParams) {
params.Name = req.FormValue("name")
params.Limit, _ = strconv.ParseInt(req.FormValue("limit"), 10, 64)
if params.Limit < 1 {
params.Limit = 10
} else if params.Limit > 100 {
params.Limit = 100
}
params.Offset, _ = strconv.ParseInt(req.FormValue("offset"), 10, 64)
return params
}

View file

@ -1,12 +0,0 @@
package www
import (
"net/http"
"github.com/tigorlazuardi/redmage/views"
"github.com/tigorlazuardi/redmage/views/homeview"
)
func (www *WWW) Home(rw http.ResponseWriter, r *http.Request) {
_ = homeview.Home(views.NewContext(www.Config, r)).Render(r.Context(), rw)
}

View file

@ -1,42 +0,0 @@
package www
import (
"context"
"net/http"
"os"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/tigorlazuardi/redmage/config"
"github.com/tigorlazuardi/redmage/db/queries/subreddits"
"github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/server/routes/middleware"
)
var PublicFS = os.DirFS("public")
type WWW struct {
Subreddits *subreddits.Queries
Config *config.Config
}
func (www *WWW) Register(router chi.Router) {
router.Use(chimiddleware.RealIP)
router.
With(chimiddleware.RequestLogger(middleware.ChiSimpleLogger{})).
Mount("/public", http.StripPrefix("/public", http.FileServer(http.FS(PublicFS))))
if www.Config.Bool("http.hotreload") {
log.New(context.Background()).Debug("enabled hot reload")
router.
With(chimiddleware.RequestLogger(middleware.ChiSimpleLogger{})).
Get("/hot_reload", www.CreateHotReloadRoute())
}
router.Group(func(r chi.Router) {
r.Use(chimiddleware.RequestID)
r.Use(chimiddleware.RequestLogger(middleware.ChiLogger{}))
r.Use(chimiddleware.SetHeader("Content-Type", "text/html; utf-8"))
r.Get("/", www.Home)
})
}

View file

@ -3,20 +3,22 @@ package server
import ( import (
"context" "context"
"errors" "errors"
"io/fs"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/tigorlazuardi/redmage/api"
"github.com/tigorlazuardi/redmage/config" "github.com/tigorlazuardi/redmage/config"
"github.com/tigorlazuardi/redmage/pkg/caller" "github.com/tigorlazuardi/redmage/pkg/caller"
"github.com/tigorlazuardi/redmage/pkg/errs" "github.com/tigorlazuardi/redmage/pkg/errs"
"github.com/tigorlazuardi/redmage/pkg/log" "github.com/tigorlazuardi/redmage/pkg/log"
"github.com/tigorlazuardi/redmage/server/routes/api" "github.com/tigorlazuardi/redmage/server/routes"
"github.com/tigorlazuardi/redmage/server/routes/www"
) )
type Server struct { type Server struct {
server *http.Server server *http.Server
config *config.Config config *config.Config
PublicDir fs.FS
} }
func (srv *Server) Start(exit <-chan struct{}) error { func (srv *Server) Start(exit <-chan struct{}) error {
@ -41,11 +43,16 @@ func (srv *Server) Start(exit <-chan struct{}) error {
} }
} }
func New(cfg *config.Config, api *api.API, www *www.WWW) *Server { func New(cfg *config.Config, api *api.API, publicDir fs.FS) *Server {
router := chi.NewRouter() router := chi.NewRouter()
router.Route("/api", api.Register) routes := routes.Routes{
router.Route("/", www.Register) API: api,
Config: cfg,
PublicDir: publicDir,
}
routes.Register(router)
server := &http.Server{ server := &http.Server{
Handler: router, Handler: router,