mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
use COnfig struct to parse env variables
This commit is contained in:
parent
26207baee8
commit
75eb3b8502
6 changed files with 86 additions and 140 deletions
1
go.mod
1
go.mod
|
|
@ -45,6 +45,7 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sethvargo/go-envconfig v1.1.1
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -142,6 +142,8 @@ github.com/rjNemo/underscore v0.7.0 h1:CeWQaDDWl541/gCj7ti8W7/koXNHwu73Riuc4SQZj
|
|||
github.com/rjNemo/underscore v0.7.0/go.mod h1:NJl2GYBIOdEaXdTD/MDyKgG6Wq7ZT+BOXlrU8GZEbdc=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sethvargo/go-envconfig v1.1.1 h1:JDu8Q9baIzJf47NPkzhIB6aLYL0vQ+pPypoYrejS9QY=
|
||||
github.com/sethvargo/go-envconfig v1.1.1/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
|
|
|||
|
|
@ -1,29 +1,65 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
)
|
||||
|
||||
type Config map[string]string
|
||||
|
||||
var DefaultConfig = Config{
|
||||
"PORT": "8000",
|
||||
"DEBUG": "false",
|
||||
// Config holds the application configuration settings loaded from environment variables
|
||||
type Config struct {
|
||||
// AppName is the name of the application
|
||||
AppName string `env:"APP_NAME, default=rentease"`
|
||||
// DatabaseUrl is the connection string for the database
|
||||
DatabaseUrl string `env:"DATABASE_URL, required"`
|
||||
// SecretKey is the secret key used for JWT token signing
|
||||
SecretKey string `env:"SECRET_KEY, required"`
|
||||
// SessionSecret is the secret key used for session signing
|
||||
SessionSecret string `env:"SESSION_SECRET, required"`
|
||||
// ApiKey is the API access key
|
||||
ApiKey string `env:"API_KEY, required"`
|
||||
// Admin is the email used to access the admin panel
|
||||
Admin string `env:"ADMIN, required"`
|
||||
// AdminSecret is the password used to access the admin panel
|
||||
AdminSecret string `env:"ADMIN_SECRET, required"`
|
||||
// Origins is the list of allowed origins
|
||||
Origins []string `env:"ORIGINS, required"`
|
||||
// Port is the HTTP server port number
|
||||
Port int `env:"PORT, default=4200"`
|
||||
// Debug enables debug mode when true
|
||||
Debug bool `env:"DEBUG, default=false"`
|
||||
// ParserBaseUrl is the base url for the parser service
|
||||
ParserBaseUrl string `env:"PARSER_BASE_URL, required"`
|
||||
// RequestTimeout is the maximum time allowed for a request. It prevents slowloris attacks that result in DDOS
|
||||
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT, default=5s"`
|
||||
// SentryDsn is the DSN for Sentry error reporting
|
||||
SentryDsn string `env:"SENTRY_DSN"`
|
||||
}
|
||||
|
||||
func NewConfig() func(string) string {
|
||||
// New creates a [Config] struct. It first parses the environment variables. You can use a .env file.
|
||||
// Please note that the env variables must be prefix with "REEMIND_" to be accounted for.
|
||||
func New(ctx context.Context) (*Config, error) {
|
||||
_ = godotenv.Load()
|
||||
log.Info("loaded env variables")
|
||||
config := new(Config)
|
||||
if err := envconfig.ProcessWith(ctx, &envconfig.Config{
|
||||
Target: config,
|
||||
Lookuper: envconfig.PrefixLookuper("APP_", envconfig.OsLookuper()),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("could not parse environment variables: %w", err)
|
||||
}
|
||||
|
||||
return func(key string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
} else {
|
||||
log.Warnf("no value found for %s using defaults", key)
|
||||
return DefaultConfig[key]
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// MustConfig is a helper that wraps [New] and panics if an error occurs.
|
||||
// Use it in cases a [Config] is required and you want to exit the application if an error occurs.
|
||||
func MustConfig(ctx context.Context) *Config {
|
||||
c, err := New(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
package cron
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cron handles jobs scheduling and execution
|
||||
type Cron struct {
|
||||
jobs []Job
|
||||
ErrChan chan error
|
||||
DoneChan chan struct{}
|
||||
SuccessChan chan string
|
||||
}
|
||||
|
||||
// Job is a type that holds the details for each job.
|
||||
type Job struct {
|
||||
Name string
|
||||
Schedule string
|
||||
Action JobFunc
|
||||
}
|
||||
|
||||
type JobFunc func() error
|
||||
|
||||
func New() *Cron {
|
||||
return &Cron{
|
||||
jobs: make([]Job, 0),
|
||||
ErrChan: make(chan error),
|
||||
SuccessChan: make(chan string),
|
||||
DoneChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cron) AddJob(job Job) {
|
||||
c.jobs = append(c.jobs, job)
|
||||
}
|
||||
|
||||
func (c *Cron) Start() {
|
||||
for _, j := range c.jobs {
|
||||
go c.scheduleJob(j)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cron) Stop() {
|
||||
close(c.DoneChan)
|
||||
close(c.SuccessChan)
|
||||
close(c.ErrChan)
|
||||
}
|
||||
|
||||
// scheduleJob adds a task to the Cron schedule based on its schedule
|
||||
func (c *Cron) scheduleJob(j Job) {
|
||||
for {
|
||||
select {
|
||||
case <-c.DoneChan:
|
||||
log.Printf("stopping job %s", j.Name)
|
||||
return
|
||||
default:
|
||||
now := time.Now()
|
||||
|
||||
var next time.Time
|
||||
switch j.Schedule {
|
||||
case "minute":
|
||||
next = now.Add(10 * time.Second)
|
||||
case "daily":
|
||||
next = now.AddDate(0, 0, 1).Truncate(24 * time.Hour)
|
||||
case "weekly":
|
||||
next = now.AddDate(0, 0, 7).Truncate(24 * time.Hour)
|
||||
case "monthly":
|
||||
nextMonth := now.AddDate(0, 1, 0)
|
||||
next = time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, nextMonth.Location())
|
||||
default:
|
||||
log.Printf("Unknown schedule %q for job %q", j.Schedule, j.Name)
|
||||
return
|
||||
}
|
||||
|
||||
sleepDuration := time.Until(next)
|
||||
log.Printf("Job %q will run in %s", j.Name, sleepDuration.String())
|
||||
time.Sleep(sleepDuration)
|
||||
|
||||
if err := j.Action(); err != nil {
|
||||
c.ErrChan <- fmt.Errorf("job %s failed: %w", j.Name, err)
|
||||
} else {
|
||||
c.SuccessChan <- fmt.Sprintf("job %s completed successfully", j.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,15 +45,13 @@ type parserClient interface {
|
|||
type Service struct {
|
||||
store Store
|
||||
parser parserClient
|
||||
calendar CalendarClient
|
||||
pdf PdfClient
|
||||
}
|
||||
|
||||
func NewService(store Store, parser parserClient, calendar CalendarClient, pdf PdfClient) (*Service, error) {
|
||||
func NewService(store Store, parser parserClient, pdf PdfClient) (*Service, error) {
|
||||
return &Service{
|
||||
store: store,
|
||||
parser: parser,
|
||||
calendar: calendar,
|
||||
pdf: pdf,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
49
main.go
49
main.go
|
|
@ -5,14 +5,11 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
|
||||
"github.com/rjNemo/rentease/assets"
|
||||
"github.com/rjNemo/rentease/internal/config"
|
||||
"github.com/rjNemo/rentease/internal/driver/calendar"
|
||||
"github.com/rjNemo/rentease/internal/driver/database"
|
||||
"github.com/rjNemo/rentease/internal/driver/parser"
|
||||
"github.com/rjNemo/rentease/internal/driver/pdf"
|
||||
|
|
@ -25,19 +22,24 @@ import (
|
|||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
if err := run(ctx, config.NewConfig()); err != nil {
|
||||
if err := run(ctx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(c context.Context, getEnv func(string) string) error {
|
||||
func run(c context.Context) error {
|
||||
ctx, cancel := signal.NotifyContext(c, os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
appConfig, err := config.New(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// init sentry
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: getEnv("SENTRY_DSN"),
|
||||
Dsn: appConfig.SentryDsn,
|
||||
EnableTracing: true,
|
||||
TracesSampleRate: 1.0,
|
||||
}); err != nil {
|
||||
|
|
@ -45,7 +47,7 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
}
|
||||
|
||||
// init database
|
||||
db, err := database.New(getEnv("DATABASE_URL"))
|
||||
db, err := database.New(appConfig.DatabaseUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error connecting to the database %w", err)
|
||||
}
|
||||
|
|
@ -56,10 +58,10 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
|
||||
bookingStore := bookingRepo.NewPgStore(db)
|
||||
|
||||
gc, err := calendar.NewGoogleClient(ctx, getEnv("CALENDAR_CREDENTIALS"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building calendar client %w", err)
|
||||
}
|
||||
// gc, err := calendar.NewGoogleClient(ctx, appConfig.CalendarCredentials)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error building calendar client %w", err)
|
||||
// }
|
||||
|
||||
// build pdf client
|
||||
pc, err := pdf.NewPdfClient()
|
||||
|
|
@ -67,32 +69,27 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
return fmt.Errorf("error starting pdf client %w", err)
|
||||
}
|
||||
|
||||
parsingClient := parser.NewBookingAgentParser(getEnv("PARSER_BASE_URL"))
|
||||
parsingClient := parser.NewBookingAgentParser(appConfig.ParserBaseUrl)
|
||||
|
||||
bookingService, err := booking.NewService(bookingStore, parsingClient, gc, pc)
|
||||
bookingService, err := booking.NewService(bookingStore, parsingClient, pc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating booking service: %w", err)
|
||||
}
|
||||
|
||||
// build authentication service
|
||||
as, err := auth.NewService(
|
||||
getEnv("SESSION_SECRET"),
|
||||
getEnv("ADMIN"),
|
||||
getEnv("ADMIN_SECRET"),
|
||||
getEnv("API_KEY"),
|
||||
appConfig.SessionSecret,
|
||||
appConfig.Admin,
|
||||
appConfig.AdminSecret,
|
||||
appConfig.ApiKey,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting auth service %w", err)
|
||||
}
|
||||
|
||||
p := getEnv("PORT")
|
||||
port, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing PORT env %w", err)
|
||||
}
|
||||
port := appConfig.Port
|
||||
|
||||
ogs := getEnv("ORIGINS")
|
||||
origins := strings.Split(ogs, ",")
|
||||
origins := appConfig.Origins
|
||||
|
||||
srv, err := server.New(
|
||||
bookingService,
|
||||
|
|
@ -100,8 +97,8 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
config.NewHost(), // TODO: move to the database at some point
|
||||
server.WithPort(port),
|
||||
server.WithFileSystem(assets.Static),
|
||||
server.WithDebug(strings.ToLower(getEnv("DEBUG")) == "true"),
|
||||
server.WithSecretKey(getEnv("SECRET_KEY")),
|
||||
server.WithDebug(appConfig.Debug),
|
||||
server.WithSecretKey(appConfig.SecretKey),
|
||||
server.WithOrigins(origins),
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue