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/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // 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/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // 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/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 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,65 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/sethvargo/go-envconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config map[string]string
|
// Config holds the application configuration settings loaded from environment variables
|
||||||
|
type Config struct {
|
||||||
var DefaultConfig = Config{
|
// AppName is the name of the application
|
||||||
"PORT": "8000",
|
AppName string `env:"APP_NAME, default=rentease"`
|
||||||
"DEBUG": "false",
|
// 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()
|
_ = godotenv.Load()
|
||||||
log.Info("loaded env variables")
|
config := new(Config)
|
||||||
|
if err := envconfig.ProcessWith(ctx, &envconfig.Config{
|
||||||
return func(key string) string {
|
Target: config,
|
||||||
if value := os.Getenv(key); value != "" {
|
Lookuper: envconfig.PrefixLookuper("APP_", envconfig.OsLookuper()),
|
||||||
return value
|
}); err != nil {
|
||||||
} else {
|
return nil, fmt.Errorf("could not parse environment variables: %w", err)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -43,18 +43,16 @@ type parserClient interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store Store
|
store Store
|
||||||
parser parserClient
|
parser parserClient
|
||||||
calendar CalendarClient
|
pdf PdfClient
|
||||||
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{
|
return &Service{
|
||||||
store: store,
|
store: store,
|
||||||
parser: parser,
|
parser: parser,
|
||||||
calendar: calendar,
|
pdf: pdf,
|
||||||
pdf: pdf,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
49
main.go
49
main.go
|
|
@ -5,14 +5,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
|
|
||||||
"github.com/rjNemo/rentease/assets"
|
"github.com/rjNemo/rentease/assets"
|
||||||
"github.com/rjNemo/rentease/internal/config"
|
"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/database"
|
||||||
"github.com/rjNemo/rentease/internal/driver/parser"
|
"github.com/rjNemo/rentease/internal/driver/parser"
|
||||||
"github.com/rjNemo/rentease/internal/driver/pdf"
|
"github.com/rjNemo/rentease/internal/driver/pdf"
|
||||||
|
|
@ -25,19 +22,24 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if err := run(ctx, config.NewConfig()); err != nil {
|
if err := run(ctx); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
os.Exit(1)
|
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)
|
ctx, cancel := signal.NotifyContext(c, os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
appConfig, err := config.New(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// init sentry
|
// init sentry
|
||||||
if err := sentry.Init(sentry.ClientOptions{
|
if err := sentry.Init(sentry.ClientOptions{
|
||||||
Dsn: getEnv("SENTRY_DSN"),
|
Dsn: appConfig.SentryDsn,
|
||||||
EnableTracing: true,
|
EnableTracing: true,
|
||||||
TracesSampleRate: 1.0,
|
TracesSampleRate: 1.0,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|
@ -45,7 +47,7 @@ func run(c context.Context, getEnv func(string) string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// init database
|
// init database
|
||||||
db, err := database.New(getEnv("DATABASE_URL"))
|
db, err := database.New(appConfig.DatabaseUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error connecting to the database %w", err)
|
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)
|
bookingStore := bookingRepo.NewPgStore(db)
|
||||||
|
|
||||||
gc, err := calendar.NewGoogleClient(ctx, getEnv("CALENDAR_CREDENTIALS"))
|
// gc, err := calendar.NewGoogleClient(ctx, appConfig.CalendarCredentials)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("error building calendar client %w", err)
|
// return fmt.Errorf("error building calendar client %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// build pdf client
|
// build pdf client
|
||||||
pc, err := pdf.NewPdfClient()
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating booking service: %w", err)
|
return fmt.Errorf("error creating booking service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// build authentication service
|
// build authentication service
|
||||||
as, err := auth.NewService(
|
as, err := auth.NewService(
|
||||||
getEnv("SESSION_SECRET"),
|
appConfig.SessionSecret,
|
||||||
getEnv("ADMIN"),
|
appConfig.Admin,
|
||||||
getEnv("ADMIN_SECRET"),
|
appConfig.AdminSecret,
|
||||||
getEnv("API_KEY"),
|
appConfig.ApiKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error starting auth service %w", err)
|
return fmt.Errorf("error starting auth service %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := getEnv("PORT")
|
port := appConfig.Port
|
||||||
port, err := strconv.Atoi(p)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error parsing PORT env %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ogs := getEnv("ORIGINS")
|
origins := appConfig.Origins
|
||||||
origins := strings.Split(ogs, ",")
|
|
||||||
|
|
||||||
srv, err := server.New(
|
srv, err := server.New(
|
||||||
bookingService,
|
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
|
config.NewHost(), // TODO: move to the database at some point
|
||||||
server.WithPort(port),
|
server.WithPort(port),
|
||||||
server.WithFileSystem(assets.Static),
|
server.WithFileSystem(assets.Static),
|
||||||
server.WithDebug(strings.ToLower(getEnv("DEBUG")) == "true"),
|
server.WithDebug(appConfig.Debug),
|
||||||
server.WithSecretKey(getEnv("SECRET_KEY")),
|
server.WithSecretKey(appConfig.SecretKey),
|
||||||
server.WithOrigins(origins),
|
server.WithOrigins(origins),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue