Public Access
feat: basalt 0.0.1
This commit is contained in:
22 files changed
+1364
-1
No files matched your search
@@ -0,0 +1,39 @@
|
||||
package gapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"{{ .ModuleName }}/pb" // We need the module name to import pb correctly!
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
)
|
||||
|
||||
func (s *Server) {{ .Name }}(ctx context.Context, req *pb.{{ .Name }}Request) (*pb.{{ .Name }}Response, error) {
|
||||
violations := validate{{ .Name }}Request(req)
|
||||
if violations != nil {
|
||||
return nil, invalidArgumentError(violations)
|
||||
}
|
||||
|
||||
// TODO: Call service layer
|
||||
// result, err := s.service.{{ .Name }}(ctx, ...)
|
||||
|
||||
/*
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to execute: %v", err)
|
||||
}
|
||||
*/
|
||||
|
||||
resp := &pb.{{ .Name }}Response{
|
||||
// TODO: Map result to response
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func validate{{ .Name }}Request(req *pb.{{ .Name }}Request) (violations []*errdetails.BadRequest_FieldViolation) {
|
||||
// TODO: Add validation logic
|
||||
// if err := val.ValidateString(req.GetField()); err != nil {
|
||||
// violations = append(violations, fieldViolation("field", err))
|
||||
// }
|
||||
return violations
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
// We can assume imports like 'response' are available in the same package
|
||||
// or we might need to fix imports if this file is in a subfolder.
|
||||
)
|
||||
|
||||
type {{ .Name }}Request struct {
|
||||
// TODO: Add request fields
|
||||
// Username string `json:"username" binding:"required"`
|
||||
}
|
||||
|
||||
type {{ .Name }}Response struct {
|
||||
// TODO: Add response fields
|
||||
}
|
||||
|
||||
// {{ .Name }} godoc
|
||||
// @Summary {{ .Name }}
|
||||
// @Description {{ .Name }} handler
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body {{ .Name }}Request true "Request body"
|
||||
// @Success 200 {object} response.JSONResponse{data={{ .Name }}Response}
|
||||
// @Router /v1/{{ .Name }} [post]
|
||||
func (s *Server) create{{ .Name }}(ctx *gin.Context) {
|
||||
var req {{ .Name }}Request
|
||||
// We assume response.JSONResponse is available or imported
|
||||
var resp response.JSONResponse
|
||||
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
resp = response.JSONResponse{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
}
|
||||
ctx.JSON(http.StatusBadRequest, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Call your service here
|
||||
// result, err := s.service.{{ .Name }}(ctx, req)
|
||||
|
||||
/*
|
||||
if err != nil {
|
||||
resp = response.JSONResponse{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
}
|
||||
ctx.JSON(http.StatusInternalServerError, resp)
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
resp = response.JSONResponse{
|
||||
Success: true,
|
||||
Message: "Operation successful",
|
||||
Data: nil, // Replace with actual data
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
{{ if .UseGRPC }}
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
{{ end }}
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
{{ if .UseGRPC }}
|
||||
"github.com/rs/cors"
|
||||
{{ end }}
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"{{ .ModuleName }}/internal/auth"
|
||||
"{{ .ModuleName }}/internal/config"
|
||||
db "{{ .ModuleName }}/internal/db/sqlc"
|
||||
{{ if .UseGRPC }}
|
||||
gapi "{{ .ModuleName }}/internal/handlers/grpc"
|
||||
{{ end }}
|
||||
{{ if not .UseGRPC }}
|
||||
api "{{ .ModuleName }}/internal/handlers/http"
|
||||
{{ end }}
|
||||
"{{ .ModuleName }}/internal/mailer"
|
||||
"{{ .ModuleName }}/internal/service"
|
||||
"{{ .ModuleName }}/internal/worker"
|
||||
{{ if .UseGRPC }}
|
||||
"{{ .ModuleName }}/pb"
|
||||
{{ end }}
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
{{ if .UseGRPC }}
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
_ "{{ .ModuleName }}/docs/statik"
|
||||
{{ end }}
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
|
||||
|
||||
)
|
||||
|
||||
var interruptSignals = []os.Signal{
|
||||
os.Interrupt,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGINT,
|
||||
}
|
||||
|
||||
// @title Simple Bank API
|
||||
// @version 1.0
|
||||
// @description API for Simple Bank application
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
|
||||
// @contact.name API Support
|
||||
// @contact.email [email protected]
|
||||
|
||||
// @license.name MIT
|
||||
// @license.url https://opensource.org/licenses/MIT
|
||||
|
||||
// @host localhost:8080
|
||||
// @BasePath /v1
|
||||
// @schemes http https
|
||||
|
||||
// @securityDefinitions.apikey BearerAuth
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @description Type "Bearer" followed by a space and JWT token.
|
||||
func main() {
|
||||
config, err := config.LoadConfig(".")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot load config")
|
||||
}
|
||||
|
||||
if config.Environment == "development" {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
}
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), interruptSignals...)
|
||||
defer cancel()
|
||||
|
||||
connPool, err := pgxpool.New(ctx, config.DBAddr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot connect to db")
|
||||
}
|
||||
|
||||
runDBMigration(config.MigrationURL, config.DBAddr)
|
||||
|
||||
redisOpt := asynq.RedisClientOpt{
|
||||
Addr: config.RedisAddr,
|
||||
Password: config.RedisPassword,
|
||||
DB: config.RedisDB,
|
||||
}
|
||||
|
||||
taskDist := worker.NewRedisTaskDistributor(redisOpt)
|
||||
store := db.NewPostgresStore(connPool)
|
||||
|
||||
tokenMaker, err := auth.NewPasetoMaker(config)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create token maker")
|
||||
}
|
||||
|
||||
userService := service.NewUserService(store, taskDist)
|
||||
authService := service.NewAuthService(store, tokenMaker, config)
|
||||
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
{{ if not .UseGRPC }}
|
||||
runGinServer(ctx, wg, config, store, taskDist, tokenMaker, userService, authService)
|
||||
{{ else }}
|
||||
// Assuming you want Gateway AND gRPC when gRPC is selected
|
||||
runGatewayServer(ctx, wg, config, store, taskDist, tokenMaker, userService, authService)
|
||||
runGrpcServer(ctx, wg, config, store, taskDist, tokenMaker, userService, authService)
|
||||
{{ end }}
|
||||
|
||||
runTaskProcessor(ctx, wg, redisOpt, store, config)
|
||||
|
||||
err = wg.Wait()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("application terminated with error")
|
||||
}
|
||||
}
|
||||
|
||||
func runTaskProcessor(ctx context.Context, wg *errgroup.Group, redisOpt asynq.RedisClientOpt, store db.Store, config config.Config) {
|
||||
mailer := mailer.NewGmailSender(config.EmailSenderName, config.EmailSender, config.EmailPassword)
|
||||
taskProcessor := worker.NewRedisTaskProcessor(redisOpt, store, mailer, config)
|
||||
log.Info().Msg("start task processor")
|
||||
if err := taskProcessor.Start(); err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot start task processor")
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("gracefully shutting down task processor")
|
||||
taskProcessor.Shutdown()
|
||||
log.Info().Msg("task processor shut down")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func runDBMigration(migrationURL, dbSource string) {
|
||||
migration, err := migrate.New(migrationURL, dbSource)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create new migrate instance")
|
||||
}
|
||||
|
||||
if err = migration.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
log.Fatal().Err(err).Msg("failed to run migrate up")
|
||||
}
|
||||
|
||||
log.Info().Msg("db migrated successfully")
|
||||
}
|
||||
|
||||
{{ if not .UseGRPC }}
|
||||
func runGinServer(ctx context.Context, wg *errgroup.Group, config config.Config, store db.Store, td worker.TaskDistributor, tokenMaker auth.Maker, userService service.UserService, authService service.AuthService) {
|
||||
s, err := api.NewServer(config, store, td, tokenMaker, userService, authService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create server")
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
err = s.Start(config.HTTPServerAddr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot start server:")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("shutting down server")
|
||||
err := s.Shutdown(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot shutdown server")
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("server shut down")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ if .UseGRPC }}
|
||||
func runGrpcServer(ctx context.Context, wg *errgroup.Group, config config.Config, store db.Store, taskDistributor worker.TaskDistributor, tokenMaker auth.Maker, userService service.UserService, authService service.AuthService) {
|
||||
server, err := gapi.NewServer(config, store, taskDistributor, tokenMaker, userService, authService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create server:")
|
||||
}
|
||||
|
||||
grpcLogger := grpc.UnaryInterceptor(gapi.GrpcLogger)
|
||||
|
||||
grpcServer := grpc.NewServer(grpcLogger)
|
||||
pb.RegisterSimpleBankServer(grpcServer, server)
|
||||
reflection.Register(grpcServer)
|
||||
|
||||
listener, err := net.Listen("tcp", config.GrpcServerAddr)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create listener")
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
log.Info().Msgf("start gRPC server at %s", listener.Addr().String())
|
||||
err = grpcServer.Serve(listener)
|
||||
if err != nil {
|
||||
if errors.Is(err, grpc.ErrServerStopped) {
|
||||
return nil
|
||||
}
|
||||
log.Error().Err(err).Msg("gRPC server failed to serve")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("gracefully shutting down gRPC server")
|
||||
grpcServer.GracefulStop()
|
||||
log.Info().Msg("gRPC server shut down")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func runGatewayServer(ctx context.Context, wg *errgroup.Group, config config.Config, store db.Store, taskDistributor worker.TaskDistributor, tokenMaker auth.Maker, userService service.UserService, authService service.AuthService) {
|
||||
server, err := gapi.NewServer(config, store, taskDistributor, tokenMaker, userService, authService)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create server:")
|
||||
}
|
||||
|
||||
opts := runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
},
|
||||
UnmarshalOptions: protojson.UnmarshalOptions{
|
||||
DiscardUnknown: true,
|
||||
},
|
||||
})
|
||||
|
||||
grpcMux := runtime.NewServeMux(opts)
|
||||
|
||||
err = pb.RegisterSimpleBankHandlerServer(ctx, grpcMux, server)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot register handler server")
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", grpcMux)
|
||||
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("cannot create statik fs")
|
||||
}
|
||||
|
||||
swaggerHandler := http.StripPrefix("/swagger/", http.FileServer(statikFS))
|
||||
mux.Handle("/swagger/", swaggerHandler)
|
||||
|
||||
c := cors.New(
|
||||
cors.Options{
|
||||
AllowedOrigins: config.AllowedOrigins,
|
||||
AllowedMethods: []string{
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
http.MethodOptions,
|
||||
},
|
||||
AllowedHeaders: []string{
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"X-CSRF-Token",
|
||||
},
|
||||
AllowCredentials: true,
|
||||
Debug: config.Environment == "development",
|
||||
},
|
||||
)
|
||||
handler := c.Handler(gapi.HTTPLogger(mux))
|
||||
httpServer := &http.Server{
|
||||
Handler: handler,
|
||||
Addr: config.HTTPServerAddr,
|
||||
}
|
||||
|
||||
wg.Go(func() error {
|
||||
log.Info().Msgf("start HTTP gateway server at %s", httpServer.Addr)
|
||||
|
||||
err = httpServer.ListenAndServe()
|
||||
if err != nil {
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return nil
|
||||
}
|
||||
log.Error().Err(err).Msg("cannot start http gateway server")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
wg.Go(func() error {
|
||||
<-ctx.Done()
|
||||
log.Info().Msg("gracefully shutting down HTTP gateway server")
|
||||
err := httpServer.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cannot shut down http gateway server")
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("HTTP gateway server shut down")
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,23 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
db "{{ .ModuleName }}/internal/db/sqlc"
|
||||
// TODO: Add other imports as needed
|
||||
// "{{ .ModuleName }}/internal/worker"
|
||||
)
|
||||
|
||||
type {{ .LowerName }}Service struct {
|
||||
store db.Store
|
||||
// TODO: Add other dependencies
|
||||
// taskDistributor worker.TaskDistributor
|
||||
}
|
||||
|
||||
// New{{ .Name }}Service creates a new {{ .LowerName }} service
|
||||
func New{{ .Name }}Service(store db.Store) {{ .Name }}Service {
|
||||
return &{{ .LowerName }}Service{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user