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
53
README.md
53
README.md
|
@ -2,35 +2,60 @@
|
|||
|
||||
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
|
||||
|
||||
Currently, this information is extracted from the site (if possbile):
|
||||
|
||||
``` js
|
||||
{
|
||||
"image_url": "<url>", // (string) URL to an image file
|
||||
"in_stock": false, // (bool) If the item is currently available for purchase
|
||||
"name": "<name>", // (string) The name of the product as it appears on the site
|
||||
"price": 14.21, // (optional, float) The price of the product [parsed by the library]
|
||||
"price_text": "14,21 €", // (optional, string) The price of the product as it appears on the site (with currency)
|
||||
"release_date": "2021-03-22T00:00:00Z", // (optional, string RFC3339) the release date of the item
|
||||
"url": "<url>" // (string) The URL of the item
|
||||
"description": "...",
|
||||
"image_url": "https://...",
|
||||
"in_stock": false,
|
||||
"name": "...",
|
||||
"price": 0.0,
|
||||
"price_text": "0,0 €",
|
||||
"release_date": "2019-03-08T00:00:00Z",
|
||||
"url": "https://..."
|
||||
}
|
||||
```
|
||||
|
||||
[pkg/models/product.go](./pkg/models/product.go)
|
||||
|
||||
## Supported sites
|
||||
|
||||
Support is handled in a _best effort_ basis. Some sites do not provided all exposed fields.
|
||||
|
||||
- [Amazon.es](https://amazon.es)
|
||||
- [Amazon.com](https://amazon.com)
|
||||
- [Akira Comics](https://www.akiracomics.com)
|
||||
- [AkiraComics](https://www.akiracomics.com)
|
||||
- [Casa del libro](https://www.casadellibro.com)
|
||||
- [Heroes De Papel](https://heroesdepapel.es)
|
||||
- [Steam](https://store.steampowered.com)
|
||||
|
||||
## Running
|
||||
|
||||
```
|
||||
go run cmd/server/main.go
|
||||
```
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/fmartingr/bazaar/internal/server"
|
||||
"github.com/fmartingr/bazaar/pkg/manager"
|
||||
"github.com/fmartingr/bazaar/pkg/shop/akiracomics"
|
||||
|
@ -20,11 +23,13 @@ func main() {
|
|||
m.Register(gtmstore.Domains, gtmstore.NewGTMStoreShopFactory())
|
||||
m.Register(casadellibro.Domains, casadellibro.NewCasaDelLibroShopFactory())
|
||||
|
||||
server := server.NewServer(server.ServerConf{
|
||||
HttpPort: 8080,
|
||||
}, &m)
|
||||
ctx := context.Background()
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.19
|
|||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
github.com/goodsign/monday v1.0.0
|
||||
github.com/sethvargo/go-envconfig v0.8.2
|
||||
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/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
||||
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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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)
|
||||
|
||||
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) {
|
||||
|
|
|
@ -14,54 +14,58 @@ import (
|
|||
"github.com/fmartingr/bazaar/pkg/manager"
|
||||
)
|
||||
|
||||
type ServerConf struct {
|
||||
HttpPort int
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
http internalModels.Server
|
||||
Http internalModels.Server
|
||||
config *ServerConfig
|
||||
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
s.cancel = cancel
|
||||
|
||||
go func() {
|
||||
if err := s.http.Start(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("error starting server: %s", err)
|
||||
}
|
||||
}()
|
||||
if s.config.Http.Enabled {
|
||||
go func() {
|
||||
if err := s.Http.Start(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("error starting server: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) WaitStop() error {
|
||||
func (s *Server) WaitStop() {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
sig := <-signals
|
||||
log.Printf("signal %s received, shutting down", sig)
|
||||
|
||||
return s.Stop()
|
||||
s.Stop()
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
func (s *Server) Stop() {
|
||||
s.cancel()
|
||||
|
||||
shuwdownContext, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := s.http.Stop(shuwdownContext); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("error shutting down http server: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewServer(serverConf ServerConf, m *manager.Manager) *Server {
|
||||
return &Server{
|
||||
http: NewHttpServer(serverConf.HttpPort, m),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewServer(conf *ServerConfig, m *manager.Manager) *Server {
|
||||
server := &Server{
|
||||
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
|
||||
}
|
||||
|
||||
func (m *Manager) Register(domains []string, shopFactory models.ShopFactory) error {
|
||||
func (m *Manager) Register(domains []string, shopFactory models.ShopFactory) {
|
||||
baseShop := models.NewShopOptions(clients.NewBasicHttpClient())
|
||||
shop := shopFactory(baseShop)
|
||||
|
||||
for _, domain := range domains {
|
||||
if _, exists := m.domains[domain]; exists {
|
||||
return fmt.Errorf("domain %s is already registered", domain)
|
||||
} else {
|
||||
if _, exists := m.domains[domain]; !exists {
|
||||
m.domains[domain] = shop
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetShop(host string) models.Shop {
|
||||
|
|
Loading…
Reference in New Issue