mirror of https://github.com/fmartingr/bazaar.git
feat: added server configuration using env vars (#7)
* feat: added server configuration using env vars * updated readme * refactor: removed error returns where unneeded
This commit is contained in:
parent
e69ecef6c8
commit
5543a19df1
51
README.md
51
README.md
|
@ -2,22 +2,53 @@
|
||||||
|
|
||||||
A service/library to extract product information from URLs.
|
A service/library to extract product information from URLs.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Variable name | Default | Description |
|
||||||
|
| -------------- | ------- | ------------------------------ |
|
||||||
|
| `HTTP_ENABLED` | `true` | Enable/Disable the HTTP server |
|
||||||
|
| `HTTP_PORT` | `8080` | Port to serve the HTTP in |
|
||||||
|
|
||||||
|
## Servers
|
||||||
|
|
||||||
|
### HTTP
|
||||||
|
|
||||||
|
- `POST /item`
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- **url**: The URL to extract information from
|
||||||
|
|
||||||
|
Responses:
|
||||||
|
- `200`: Information extracted
|
||||||
|
- `400`: Shop not supported, missing parameters
|
||||||
|
- `500`: Internal error, check logs
|
||||||
|
|
||||||
|
|
||||||
|
- `GET /liveness`
|
||||||
|
|
||||||
|
Responses:
|
||||||
|
- `200`: Server running
|
||||||
|
|
||||||
|
|
||||||
## Data model
|
## Data model
|
||||||
|
|
||||||
Currently, this information is extracted from the site (if possbile):
|
Currently, this information is extracted from the site (if possbile):
|
||||||
|
|
||||||
``` js
|
``` js
|
||||||
{
|
{
|
||||||
"image_url": "<url>", // (string) URL to an image file
|
"description": "...",
|
||||||
"in_stock": false, // (bool) If the item is currently available for purchase
|
"image_url": "https://...",
|
||||||
"name": "<name>", // (string) The name of the product as it appears on the site
|
"in_stock": false,
|
||||||
"price": 14.21, // (optional, float) The price of the product [parsed by the library]
|
"name": "...",
|
||||||
"price_text": "14,21 €", // (optional, string) The price of the product as it appears on the site (with currency)
|
"price": 0.0,
|
||||||
"release_date": "2021-03-22T00:00:00Z", // (optional, string RFC3339) the release date of the item
|
"price_text": "0,0 €",
|
||||||
"url": "<url>" // (string) The URL of the item
|
"release_date": "2019-03-08T00:00:00Z",
|
||||||
|
"url": "https://..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[pkg/models/product.go](./pkg/models/product.go)
|
||||||
|
|
||||||
## Supported sites
|
## Supported sites
|
||||||
|
|
||||||
Support is handled in a _best effort_ basis. Some sites do not provided all exposed fields.
|
Support is handled in a _best effort_ basis. Some sites do not provided all exposed fields.
|
||||||
|
@ -28,9 +59,3 @@ Support is handled in a _best effort_ basis. Some sites do not provided all expo
|
||||||
- [Casa del libro](https://www.casadellibro.com)
|
- [Casa del libro](https://www.casadellibro.com)
|
||||||
- [Heroes De Papel](https://heroesdepapel.es)
|
- [Heroes De Papel](https://heroesdepapel.es)
|
||||||
- [Steam](https://store.steampowered.com)
|
- [Steam](https://store.steampowered.com)
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
```
|
|
||||||
go run cmd/server/main.go
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/fmartingr/bazaar/internal/server"
|
"github.com/fmartingr/bazaar/internal/server"
|
||||||
"github.com/fmartingr/bazaar/pkg/manager"
|
"github.com/fmartingr/bazaar/pkg/manager"
|
||||||
"github.com/fmartingr/bazaar/pkg/shop/akiracomics"
|
"github.com/fmartingr/bazaar/pkg/shop/akiracomics"
|
||||||
|
@ -20,11 +23,13 @@ func main() {
|
||||||
m.Register(gtmstore.Domains, gtmstore.NewGTMStoreShopFactory())
|
m.Register(gtmstore.Domains, gtmstore.NewGTMStoreShopFactory())
|
||||||
m.Register(casadellibro.Domains, casadellibro.NewCasaDelLibroShopFactory())
|
m.Register(casadellibro.Domains, casadellibro.NewCasaDelLibroShopFactory())
|
||||||
|
|
||||||
server := server.NewServer(server.ServerConf{
|
ctx := context.Background()
|
||||||
HttpPort: 8080,
|
|
||||||
}, &m)
|
|
||||||
|
|
||||||
server.Start()
|
server := server.NewServer(server.ParseServerConfiguration(ctx), &m)
|
||||||
|
|
||||||
|
if err := server.Start(ctx); err != nil {
|
||||||
|
log.Panicf("error starting server: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
server.WaitStop()
|
server.WaitStop()
|
||||||
}
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.19
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.8.0
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
github.com/goodsign/monday v1.0.0
|
github.com/goodsign/monday v1.0.0
|
||||||
|
github.com/sethvargo/go-envconfig v0.8.2
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -7,8 +7,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
||||||
github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
|
github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sethvargo/go-envconfig v0.8.2 h1:DDUVuG21RMgeB/bn4leclUI/837y6cQCD4w8hb5797k=
|
||||||
|
github.com/sethvargo/go-envconfig v0.8.2/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
|
||||||
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/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sethvargo/go-envconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readDotEnv reads the configuration from variables in a .env file (only for contributing)
|
||||||
|
func readDotEnv() map[string]string {
|
||||||
|
file, err := os.Open(".env")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
result := make(map[string]string)
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keyval := strings.SplitN(line, "=", 2)
|
||||||
|
result[keyval[0]] = keyval[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Hostname string `env:"HOSTNAME,required"`
|
||||||
|
Http struct {
|
||||||
|
Enabled bool `env:"HTTP_ENABLED,default=True"`
|
||||||
|
Port int `env:"HTTP_PORT,default=8080"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseServerConfiguration(ctx context.Context) *ServerConfig {
|
||||||
|
var cfg ServerConfig
|
||||||
|
|
||||||
|
lookuper := envconfig.MultiLookuper(
|
||||||
|
envconfig.MapLookuper(map[string]string{"HOSTNAME": os.Getenv("HOSTNAME")}),
|
||||||
|
envconfig.MapLookuper(readDotEnv()),
|
||||||
|
envconfig.PrefixLookuper("BAZAAR_", envconfig.OsLookuper()),
|
||||||
|
envconfig.OsLookuper(),
|
||||||
|
)
|
||||||
|
if err := envconfig.ProcessWith(ctx, &cfg, lookuper); err != nil {
|
||||||
|
log.Fatalf("Error parsing configuration: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg
|
||||||
|
}
|
|
@ -50,7 +50,9 @@ func NewHttpServer(port int, m *manager.Manager) *httpServer {
|
||||||
payload, _ := json.Marshal(product)
|
payload, _ := json.Marshal(product)
|
||||||
|
|
||||||
rw.Header().Add("Content-Type", "application/json")
|
rw.Header().Add("Content-Type", "application/json")
|
||||||
rw.Write(payload)
|
if _, err := rw.Write(payload); err != nil {
|
||||||
|
log.Printf("error writting response: %s", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/liveness", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/liveness", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -14,54 +14,58 @@ import (
|
||||||
"github.com/fmartingr/bazaar/pkg/manager"
|
"github.com/fmartingr/bazaar/pkg/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerConf struct {
|
|
||||||
HttpPort int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
http internalModels.Server
|
Http internalModels.Server
|
||||||
|
config *ServerConfig
|
||||||
|
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start(ctx context.Context) error {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
s.cancel = cancel
|
s.cancel = cancel
|
||||||
|
|
||||||
|
if s.config.Http.Enabled {
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.http.Start(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := s.Http.Start(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("error starting server: %s", err)
|
log.Fatalf("error starting server: %s", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) WaitStop() error {
|
func (s *Server) WaitStop() {
|
||||||
signals := make(chan os.Signal, 1)
|
signals := make(chan os.Signal, 1)
|
||||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
sig := <-signals
|
sig := <-signals
|
||||||
log.Printf("signal %s received, shutting down", sig)
|
log.Printf("signal %s received, shutting down", sig)
|
||||||
|
|
||||||
return s.Stop()
|
s.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
func (s *Server) Stop() {
|
||||||
s.cancel()
|
s.cancel()
|
||||||
|
|
||||||
shuwdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
shuwdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := s.http.Stop(shuwdownContext); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if s.config.Http.Enabled {
|
||||||
|
if err := s.Http.Stop(shuwdownContext); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("error shutting down http server: %s", err)
|
log.Fatalf("error shutting down http server: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(serverConf ServerConf, m *manager.Manager) *Server {
|
func NewServer(conf *ServerConfig, m *manager.Manager) *Server {
|
||||||
return &Server{
|
server := &Server{
|
||||||
http: NewHttpServer(serverConf.HttpPort, m),
|
config: conf,
|
||||||
}
|
}
|
||||||
|
if conf.Http.Enabled {
|
||||||
|
server.Http = NewHttpServer(conf.Http.Port, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return server
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,15 @@ type Manager struct {
|
||||||
domains map[string]models.Shop
|
domains map[string]models.Shop
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Register(domains []string, shopFactory models.ShopFactory) error {
|
func (m *Manager) Register(domains []string, shopFactory models.ShopFactory) {
|
||||||
baseShop := models.NewShopOptions(clients.NewBasicHttpClient())
|
baseShop := models.NewShopOptions(clients.NewBasicHttpClient())
|
||||||
shop := shopFactory(baseShop)
|
shop := shopFactory(baseShop)
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if _, exists := m.domains[domain]; exists {
|
if _, exists := m.domains[domain]; !exists {
|
||||||
return fmt.Errorf("domain %s is already registered", domain)
|
|
||||||
} else {
|
|
||||||
m.domains[domain] = shop
|
m.domains[domain] = shop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetShop(host string) models.Shop {
|
func (m *Manager) GetShop(host string) models.Shop {
|
||||||
|
|
Loading…
Reference in New Issue